|
//----------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//----------------------------------------------------------------
namespace System
{
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Runtime;
using System.ServiceModel;
using System.Text;
// Thin wrapper around formatted string; use type system to help ensure we
// are doing canonicalization right/consistently - the literal sections are held in an
// un-escaped format
// We are assuming that the string will be always built as Lit{Var}Lit[{Var}Lit[{Var}Lit[...]]],
// when the first and last literals may be empty
class UriTemplateCompoundPathSegment : UriTemplatePathSegment, IComparable<UriTemplateCompoundPathSegment>
{
readonly string firstLiteral;
readonly List<VarAndLitPair> varLitPairs;
CompoundSegmentClass csClass;
UriTemplateCompoundPathSegment(string originalSegment, bool endsWithSlash, string firstLiteral)
: base(originalSegment, UriTemplatePartType.Compound, endsWithSlash)
{
this.firstLiteral = firstLiteral;
this.varLitPairs = new List<VarAndLitPair>();
}
public static new UriTemplateCompoundPathSegment CreateFromUriTemplate(string segment, UriTemplate template)
{
string origSegment = segment;
bool endsWithSlash = segment.EndsWith("/", StringComparison.Ordinal);
if (endsWithSlash)
{
segment = segment.Remove(segment.Length - 1);
}
int nextVarStart = segment.IndexOf("{", StringComparison.Ordinal);
Fx.Assert(nextVarStart >= 0, "The method is only called after identifying a '{' character in the segment");
string firstLiteral = ((nextVarStart > 0) ? segment.Substring(0, nextVarStart) : string.Empty);
if (firstLiteral.IndexOf(UriTemplate.WildcardPath, StringComparison.Ordinal) != -1)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new FormatException(
SR.GetString(SR.UTInvalidWildcardInVariableOrLiteral, template.originalTemplate, UriTemplate.WildcardPath)));
}
UriTemplateCompoundPathSegment result = new UriTemplateCompoundPathSegment(origSegment, endsWithSlash,
((firstLiteral != string.Empty) ? Uri.UnescapeDataString(firstLiteral) : string.Empty));
do
{
int nextVarEnd = segment.IndexOf("}", nextVarStart + 1, StringComparison.Ordinal);
if (nextVarEnd < nextVarStart + 2)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new FormatException(
SR.GetString(SR.UTInvalidFormatSegmentOrQueryPart, segment)));
}
bool hasDefault;
string varName = template.AddPathVariable(UriTemplatePartType.Compound,
segment.Substring(nextVarStart + 1, nextVarEnd - nextVarStart - 1), out hasDefault);
if (hasDefault)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(
SR.GetString(SR.UTDefaultValueToCompoundSegmentVar, template, origSegment, varName)));
}
nextVarStart = segment.IndexOf("{", nextVarEnd + 1, StringComparison.Ordinal);
string literal;
if (nextVarStart > 0)
{
if (nextVarStart == nextVarEnd + 1)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("template",
SR.GetString(SR.UTDoesNotSupportAdjacentVarsInCompoundSegment, template, segment));
}
literal = segment.Substring(nextVarEnd + 1, nextVarStart - nextVarEnd - 1);
}
else if (nextVarEnd + 1 < segment.Length)
{
literal = segment.Substring(nextVarEnd + 1);
}
else
{
literal = string.Empty;
}
if (literal.IndexOf(UriTemplate.WildcardPath, StringComparison.Ordinal) != -1)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new FormatException(
SR.GetString(SR.UTInvalidWildcardInVariableOrLiteral, template.originalTemplate, UriTemplate.WildcardPath)));
}
if (literal.IndexOf('}') != -1)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new FormatException(
SR.GetString(SR.UTInvalidFormatSegmentOrQueryPart, segment)));
}
result.varLitPairs.Add(new VarAndLitPair(varName, ((literal == string.Empty) ? string.Empty : Uri.UnescapeDataString(literal))));
} while (nextVarStart > 0);
if (string.IsNullOrEmpty(result.firstLiteral))
{
if (string.IsNullOrEmpty(result.varLitPairs[result.varLitPairs.Count - 1].Literal))
{
result.csClass = CompoundSegmentClass.HasNoPrefixNorSuffix;
}
else
{
result.csClass = CompoundSegmentClass.HasOnlySuffix;
}
}
else
{
if (string.IsNullOrEmpty(result.varLitPairs[result.varLitPairs.Count - 1].Literal))
{
result.csClass = CompoundSegmentClass.HasOnlyPrefix;
}
else
{
result.csClass = CompoundSegmentClass.HasPrefixAndSuffix;
}
}
return result;
}
public override void Bind(string[] values, ref int valueIndex, StringBuilder path)
{
Fx.Assert(valueIndex + this.varLitPairs.Count <= values.Length, "Not enough values to bind");
path.Append(this.firstLiteral);
for (int pairIndex = 0; pairIndex < this.varLitPairs.Count; pairIndex++)
{
path.Append(values[valueIndex++]);
path.Append(this.varLitPairs[pairIndex].Literal);
}
if (this.EndsWithSlash)
{
path.Append("/");
}
}
public override bool IsEquivalentTo(UriTemplatePathSegment other, bool ignoreTrailingSlash)
{
if (other == null)
{
Fx.Assert("why would we ever call this?");
return false;
}
if (!ignoreTrailingSlash && (this.EndsWithSlash != other.EndsWithSlash))
{
return false;
}
UriTemplateCompoundPathSegment otherAsCompound = other as UriTemplateCompoundPathSegment;
if (otherAsCompound == null)
{
// if other can't be cast as a compound then it can't be equivalent
return false;
}
if (this.varLitPairs.Count != otherAsCompound.varLitPairs.Count)
{
return false;
}
if (StringComparer.OrdinalIgnoreCase.Compare(this.firstLiteral, otherAsCompound.firstLiteral) != 0)
{
return false;
}
for (int pairIndex = 0; pairIndex < this.varLitPairs.Count; pairIndex++)
{
if (StringComparer.OrdinalIgnoreCase.Compare(this.varLitPairs[pairIndex].Literal,
otherAsCompound.varLitPairs[pairIndex].Literal) != 0)
{
return false;
}
}
return true;
}
public override bool IsMatch(UriTemplateLiteralPathSegment segment, bool ignoreTrailingSlash)
{
if (!ignoreTrailingSlash && (this.EndsWithSlash != segment.EndsWithSlash))
{
return false;
}
return TryLookup(segment.AsUnescapedString(), null);
}
public override void Lookup(string segment, NameValueCollection boundParameters)
{
if (!TryLookup(segment, boundParameters))
{
Fx.Assert("How can that be? Lookup is expected to be called after IsMatch");
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(
SR.GetString(SR.UTCSRLookupBeforeMatch)));
}
}
bool TryLookup(string segment, NameValueCollection boundParameters)
{
int segmentPosition = 0;
if (!string.IsNullOrEmpty(this.firstLiteral))
{
if (segment.StartsWith(this.firstLiteral, StringComparison.Ordinal))
{
segmentPosition = this.firstLiteral.Length;
}
else
{
return false;
}
}
for (int pairIndex = 0; pairIndex < this.varLitPairs.Count - 1; pairIndex++)
{
int nextLiteralPosition = segment.IndexOf(this.varLitPairs[pairIndex].Literal, segmentPosition, StringComparison.Ordinal);
if (nextLiteralPosition < segmentPosition + 1)
{
return false;
}
if (boundParameters != null)
{
string varValue = segment.Substring(segmentPosition, nextLiteralPosition - segmentPosition);
boundParameters.Add(this.varLitPairs[pairIndex].VarName, varValue);
}
segmentPosition = nextLiteralPosition + this.varLitPairs[pairIndex].Literal.Length;
}
if (segmentPosition < segment.Length)
{
if (string.IsNullOrEmpty(this.varLitPairs[varLitPairs.Count - 1].Literal))
{
if (boundParameters != null)
{
boundParameters.Add(this.varLitPairs[varLitPairs.Count - 1].VarName,
segment.Substring(segmentPosition));
}
return true;
}
else if ((segmentPosition + this.varLitPairs[varLitPairs.Count - 1].Literal.Length < segment.Length) &&
segment.EndsWith(this.varLitPairs[varLitPairs.Count - 1].Literal, StringComparison.Ordinal))
{
if (boundParameters != null)
{
boundParameters.Add(this.varLitPairs[varLitPairs.Count - 1].VarName,
segment.Substring(segmentPosition, segment.Length - segmentPosition - this.varLitPairs[varLitPairs.Count - 1].Literal.Length));
}
return true;
}
else
{
return false;
}
}
else
{
return false;
}
}
// A note about comparing compound segments:
// We are using this for generating the sorted collections at the nodes of the UriTemplateTrieNode.
// The idea is that we are sorting the segments based on preferred matching, when we have two
// compound segments matching the same wire segment, we will give preference to the preceding one.
// The order is based on the following concepts:
// - We are defining four classes of compound segments: prefix+suffix, prefix-only, suffix-only
// and none
// - Whenever we are comparing segments from different class the preferred one is the segment with
// the prefared class, based on the order we defined them (p+s \ p \ s \ n).
// - Within each class the preference is based on the prefix\suffix, while prefix has precedence
// over suffix if both exists.
// - If after comparing the class, as well as the prefix\suffix, we didn't reach to a conclusion,
// the preference is given to the segment with more variables parts.
// This order mostly follows the intuitive common sense; the major issue comes from preferring the
// prefix over the suffix in the case where both exist. This is derived from the problematic of any
// other type of solution that don't prefere the prefix over the suffix or vice versa. To better
// understanding lets considered the following example:
// In comparing 'foo{x}bar' and 'food{x}ar', unless we are preferring prefix or suffix, we have
// to state that they have the same order. So is the case with 'foo{x}babar' and 'food{x}ar', which
// will lead us to claiming the 'foo{x}bar' and 'foo{x}babar' are from the same order, which they
// clearly are not.
// Taking other approaches to this problem results in similar cases. The only solution is preferring
// either the prefix or the suffix over the other; since we already preferred prefix over suffix
// implicitly (we preferred the prefix only class over the suffix only, we also prefared literal
// over variable, if in the same path segment) that still maintain consistency.
// Therefore:
// - 'food{var}' should be before 'foo{var}'; '{x}.{y}.{z}' should be before '{x}.{y}'.
// - the order between '{var}bar' and '{var}qux' is not important
// - '{x}.{y}' and '{x}_{y}' should have the same order
// - 'foo{x}bar' is less preferred than 'food{x}ar'
// In the above third case - if we are opening the table with allowDuplicate=false, we will throw;
// if we are opening it with allowDuplicate=true we will let it go and might match both templates
// for certain wire candidates.
int IComparable<UriTemplateCompoundPathSegment>.CompareTo(UriTemplateCompoundPathSegment other)
{
Fx.Assert(other != null, "We are only expected to get here for comparing real compound segments");
switch (this.csClass)
{
case CompoundSegmentClass.HasPrefixAndSuffix:
switch (other.csClass)
{
case CompoundSegmentClass.HasPrefixAndSuffix:
return CompareToOtherThatHasPrefixAndSuffix(other);
case CompoundSegmentClass.HasOnlyPrefix:
case CompoundSegmentClass.HasOnlySuffix:
case CompoundSegmentClass.HasNoPrefixNorSuffix:
return -1;
default:
Fx.Assert("Invalid other.CompoundSegmentClass");
return 0;
}
case CompoundSegmentClass.HasOnlyPrefix:
switch (other.csClass)
{
case CompoundSegmentClass.HasPrefixAndSuffix:
return 1;
case CompoundSegmentClass.HasOnlyPrefix:
return CompareToOtherThatHasOnlyPrefix(other);
case CompoundSegmentClass.HasOnlySuffix:
case CompoundSegmentClass.HasNoPrefixNorSuffix:
return -1;
default:
Fx.Assert("Invalid other.CompoundSegmentClass");
return 0;
}
case CompoundSegmentClass.HasOnlySuffix:
switch (other.csClass)
{
case CompoundSegmentClass.HasPrefixAndSuffix:
case CompoundSegmentClass.HasOnlyPrefix:
return 1;
case CompoundSegmentClass.HasOnlySuffix:
return CompareToOtherThatHasOnlySuffix(other);
case CompoundSegmentClass.HasNoPrefixNorSuffix:
return -1;
default:
Fx.Assert("Invalid other.CompoundSegmentClass");
return 0;
}
case CompoundSegmentClass.HasNoPrefixNorSuffix:
switch (other.csClass)
{
case CompoundSegmentClass.HasPrefixAndSuffix:
case CompoundSegmentClass.HasOnlyPrefix:
case CompoundSegmentClass.HasOnlySuffix:
return 1;
case CompoundSegmentClass.HasNoPrefixNorSuffix:
return CompareToOtherThatHasNoPrefixNorSuffix(other);
default:
Fx.Assert("Invalid other.CompoundSegmentClass");
return 0;
}
default:
Fx.Assert("Invalid this.CompoundSegmentClass");
return 0;
}
}
int CompareToOtherThatHasPrefixAndSuffix(UriTemplateCompoundPathSegment other)
{
Fx.Assert(this.csClass == CompoundSegmentClass.HasPrefixAndSuffix, "Otherwise, how did we got here?");
Fx.Assert(other.csClass == CompoundSegmentClass.HasPrefixAndSuffix, "Otherwise, how did we got here?");
// In this case we are determining the order based on the prefix of the two segments,
// then by their suffix and then based on the number of variables
int prefixOrder = ComparePrefixToOtherPrefix(other);
if (prefixOrder == 0)
{
int suffixOrder = CompareSuffixToOtherSuffix(other);
if (suffixOrder == 0)
{
return (other.varLitPairs.Count - this.varLitPairs.Count);
}
else
{
return suffixOrder;
}
}
else
{
return prefixOrder;
}
}
int CompareToOtherThatHasOnlyPrefix(UriTemplateCompoundPathSegment other)
{
Fx.Assert(this.csClass == CompoundSegmentClass.HasOnlyPrefix, "Otherwise, how did we got here?");
Fx.Assert(other.csClass == CompoundSegmentClass.HasOnlyPrefix, "Otherwise, how did we got here?");
// In this case we are determining the order based on the prefix of the two segments,
// then based on the number of variables
int prefixOrder = ComparePrefixToOtherPrefix(other);
if (prefixOrder == 0)
{
return (other.varLitPairs.Count - this.varLitPairs.Count);
}
else
{
return prefixOrder;
}
}
int CompareToOtherThatHasOnlySuffix(UriTemplateCompoundPathSegment other)
{
Fx.Assert(this.csClass == CompoundSegmentClass.HasOnlySuffix, "Otherwise, how did we got here?");
Fx.Assert(other.csClass == CompoundSegmentClass.HasOnlySuffix, "Otherwise, how did we got here?");
// In this case we are determining the order based on the suffix of the two segments,
// then based on the number of variables
int suffixOrder = CompareSuffixToOtherSuffix(other);
if (suffixOrder == 0)
{
return (other.varLitPairs.Count - this.varLitPairs.Count);
}
else
{
return suffixOrder;
}
}
int CompareToOtherThatHasNoPrefixNorSuffix(UriTemplateCompoundPathSegment other)
{
Fx.Assert(this.csClass == CompoundSegmentClass.HasNoPrefixNorSuffix, "Otherwise, how did we got here?");
Fx.Assert(other.csClass == CompoundSegmentClass.HasNoPrefixNorSuffix, "Otherwise, how did we got here?");
// In this case the order is determined by the number of variables
return (other.varLitPairs.Count - this.varLitPairs.Count);
}
int ComparePrefixToOtherPrefix(UriTemplateCompoundPathSegment other)
{
return string.Compare(other.firstLiteral, this.firstLiteral, StringComparison.OrdinalIgnoreCase);
}
int CompareSuffixToOtherSuffix(UriTemplateCompoundPathSegment other)
{
string reversedSuffix = ReverseString(this.varLitPairs[this.varLitPairs.Count - 1].Literal);
string reversedOtherSuffix = ReverseString(other.varLitPairs[other.varLitPairs.Count - 1].Literal);
return string.Compare(reversedOtherSuffix, reversedSuffix, StringComparison.OrdinalIgnoreCase);
}
static string ReverseString(string stringToReverse)
{
char[] reversedString = new char[stringToReverse.Length];
for (int i = 0; i < stringToReverse.Length; i++)
{
reversedString[i] = stringToReverse[stringToReverse.Length - i - 1];
}
return new string(reversedString);
}
enum CompoundSegmentClass
{
Undefined,
HasPrefixAndSuffix,
HasOnlyPrefix,
HasOnlySuffix,
HasNoPrefixNorSuffix
}
struct VarAndLitPair
{
readonly string literal;
readonly string varName;
public VarAndLitPair(string varName, string literal)
{
this.varName = varName;
this.literal = literal;
}
public string Literal
{
get
{
return this.literal;
}
}
public string VarName
{
get
{
return this.varName;
}
}
}
}
}
|