|
//----------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//----------------------------------------------------------------
namespace System
{
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Runtime;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.Text;
using System.Threading;
using System.Runtime.CompilerServices;
using System.Globalization;
[TypeForwardedFrom("System.ServiceModel.Web, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35")]
public class UriTemplate
{
internal readonly int firstOptionalSegment;
internal readonly string originalTemplate;
internal readonly Dictionary<string, UriTemplateQueryValue> queries; // keys are original case specified in UriTemplate constructor, dictionary ignores case
internal readonly List<UriTemplatePathSegment> segments;
internal const string WildcardPath = "*";
readonly Dictionary<string, string> additionalDefaults; // keys are original case specified in UriTemplate constructor, dictionary ignores case
readonly string fragment;
readonly bool ignoreTrailingSlash;
const string NullableDefault = "null";
readonly WildcardInfo wildcard;
IDictionary<string, string> defaults;
ConcurrentDictionary<string, string> unescapedDefaults;
VariablesCollection variables;
// constructors validates that template is well-formed
public UriTemplate(string template)
: this(template, false)
{
}
public UriTemplate(string template, bool ignoreTrailingSlash)
: this(template, ignoreTrailingSlash, null)
{
}
public UriTemplate(string template, IDictionary<string, string> additionalDefaults)
: this(template, false, additionalDefaults)
{
}
public UriTemplate(string template, bool ignoreTrailingSlash, IDictionary<string, string> additionalDefaults)
{
if (template == null)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("template");
}
this.originalTemplate = template;
this.ignoreTrailingSlash = ignoreTrailingSlash;
this.segments = new List<UriTemplatePathSegment>();
this.queries = new Dictionary<string, UriTemplateQueryValue>(StringComparer.OrdinalIgnoreCase);
// parse it
string pathTemplate;
string queryTemplate;
// ignore a leading slash
if (template.StartsWith("/", StringComparison.Ordinal))
{
template = template.Substring(1);
}
// pull out fragment
int fragmentStart = template.IndexOf('#');
if (fragmentStart == -1)
{
this.fragment = "";
}
else
{
this.fragment = template.Substring(fragmentStart + 1);
template = template.Substring(0, fragmentStart);
}
// pull out path and query
int queryStart = template.IndexOf('?');
if (queryStart == -1)
{
queryTemplate = string.Empty;
pathTemplate = template;
}
else
{
queryTemplate = template.Substring(queryStart + 1);
pathTemplate = template.Substring(0, queryStart);
}
template = null; // to ensure we don't accidentally reference this variable any more
// setup path template and validate
if (!string.IsNullOrEmpty(pathTemplate))
{
int startIndex = 0;
while (startIndex < pathTemplate.Length)
{
// Identify the next segment
int endIndex = pathTemplate.IndexOf('/', startIndex);
string segment;
if (endIndex != -1)
{
segment = pathTemplate.Substring(startIndex, endIndex + 1 - startIndex);
startIndex = endIndex + 1;
}
else
{
segment = pathTemplate.Substring(startIndex);
startIndex = pathTemplate.Length;
}
// Checking for wildcard segment ("*") or ("{*<var name>}")
UriTemplatePartType wildcardType;
if ((startIndex == pathTemplate.Length) &&
UriTemplateHelpers.IsWildcardSegment(segment, out wildcardType))
{
switch (wildcardType)
{
case UriTemplatePartType.Literal:
this.wildcard = new WildcardInfo(this);
break;
case UriTemplatePartType.Variable:
this.wildcard = new WildcardInfo(this, segment);
break;
default:
Fx.Assert("Error in identifying the type of the wildcard segment");
break;
}
}
else
{
this.segments.Add(UriTemplatePathSegment.CreateFromUriTemplate(segment, this));
}
}
}
// setup query template and validate
if (!string.IsNullOrEmpty(queryTemplate))
{
int startIndex = 0;
while (startIndex < queryTemplate.Length)
{
// Identify the next query part
int endIndex = queryTemplate.IndexOf('&', startIndex);
int queryPartStart = startIndex;
int queryPartEnd;
if (endIndex != -1)
{
queryPartEnd = endIndex;
startIndex = endIndex + 1;
if (startIndex >= queryTemplate.Length)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(
SR.UTQueryCannotEndInAmpersand, this.originalTemplate)));
}
}
else
{
queryPartEnd = queryTemplate.Length;
startIndex = queryTemplate.Length;
}
// Checking query part type; identifying key and value
int equalSignIndex = queryTemplate.IndexOf('=', queryPartStart, queryPartEnd - queryPartStart);
string key;
string value;
if (equalSignIndex >= 0)
{
key = queryTemplate.Substring(queryPartStart, equalSignIndex - queryPartStart);
value = queryTemplate.Substring(equalSignIndex + 1, queryPartEnd - equalSignIndex - 1);
}
else
{
key = queryTemplate.Substring(queryPartStart, queryPartEnd - queryPartStart);
value = null;
}
if (string.IsNullOrEmpty(key))
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(
SR.UTQueryCannotHaveEmptyName, this.originalTemplate)));
}
if (UriTemplateHelpers.IdentifyPartType(key) != UriTemplatePartType.Literal)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("template", SR.GetString(
SR.UTQueryMustHaveLiteralNames, this.originalTemplate));
}
// Adding a new entry to the queries dictionary
key = UrlUtility.UrlDecode(key, Encoding.UTF8);
if (this.queries.ContainsKey(key))
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(
SR.UTQueryNamesMustBeUnique, this.originalTemplate)));
}
this.queries.Add(key, UriTemplateQueryValue.CreateFromUriTemplate(value, this));
}
}
// Process additional defaults (if has some) :
if (additionalDefaults != null)
{
if (this.variables == null)
{
if (additionalDefaults.Count > 0)
{
this.additionalDefaults = new Dictionary<string, string>(additionalDefaults, StringComparer.OrdinalIgnoreCase);
}
}
else
{
foreach (KeyValuePair<string, string> kvp in additionalDefaults)
{
string uppercaseKey = kvp.Key.ToUpperInvariant();
if ((this.variables.DefaultValues != null) && this.variables.DefaultValues.ContainsKey(uppercaseKey))
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("additionalDefaults",
SR.GetString(SR.UTAdditionalDefaultIsInvalid, kvp.Key, this.originalTemplate));
}
if (this.variables.PathSegmentVariableNames.Contains(uppercaseKey))
{
this.variables.AddDefaultValue(uppercaseKey, kvp.Value);
}
else if (this.variables.QueryValueVariableNames.Contains(uppercaseKey))
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(
SR.GetString(SR.UTDefaultValueToQueryVarFromAdditionalDefaults, this.originalTemplate,
uppercaseKey)));
}
else if (string.Compare(kvp.Value, UriTemplate.NullableDefault, StringComparison.OrdinalIgnoreCase) == 0)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(
SR.GetString(SR.UTNullableDefaultAtAdditionalDefaults, this.originalTemplate,
uppercaseKey)));
}
else
{
if (this.additionalDefaults == null)
{
this.additionalDefaults = new Dictionary<string, string>(additionalDefaults.Count, StringComparer.OrdinalIgnoreCase);
}
this.additionalDefaults.Add(kvp.Key, kvp.Value);
}
}
}
}
// Validate defaults (if should)
if ((this.variables != null) && (this.variables.DefaultValues != null))
{
this.variables.ValidateDefaults(out this.firstOptionalSegment);
}
else
{
this.firstOptionalSegment = this.segments.Count;
}
}
public IDictionary<string, string> Defaults
{
get
{
if (this.defaults == null)
{
Interlocked.CompareExchange<IDictionary<string, string>>(ref this.defaults, new UriTemplateDefaults(this), null);
}
return this.defaults;
}
}
public bool IgnoreTrailingSlash
{
get
{
return this.ignoreTrailingSlash;
}
}
public ReadOnlyCollection<string> PathSegmentVariableNames
{
get
{
if (this.variables == null)
{
return VariablesCollection.EmptyCollection;
}
else
{
return this.variables.PathSegmentVariableNames;
}
}
}
public ReadOnlyCollection<string> QueryValueVariableNames
{
get
{
if (this.variables == null)
{
return VariablesCollection.EmptyCollection;
}
else
{
return this.variables.QueryValueVariableNames;
}
}
}
internal bool HasNoVariables
{
get
{
return (this.variables == null);
}
}
internal bool HasWildcard
{
get
{
return (this.wildcard != null);
}
}
// make a Uri by subbing in the values, throw on bad input
public Uri BindByName(Uri baseAddress, IDictionary<string, string> parameters)
{
return BindByName(baseAddress, parameters, false);
}
public Uri BindByName(Uri baseAddress, IDictionary<string, string> parameters, bool omitDefaults)
{
if (baseAddress == null)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("baseAddress");
}
if (!baseAddress.IsAbsoluteUri)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("baseAddress", SR.GetString(
SR.UTBadBaseAddress));
}
BindInformation bindInfo;
if (this.variables == null)
{
bindInfo = PrepareBindInformation(parameters, omitDefaults);
}
else
{
bindInfo = this.variables.PrepareBindInformation(parameters, omitDefaults);
}
return Bind(baseAddress, bindInfo, omitDefaults);
}
public Uri BindByName(Uri baseAddress, NameValueCollection parameters)
{
return BindByName(baseAddress, parameters, false);
}
public Uri BindByName(Uri baseAddress, NameValueCollection parameters, bool omitDefaults)
{
if (baseAddress == null)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("baseAddress");
}
if (!baseAddress.IsAbsoluteUri)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("baseAddress", SR.GetString(
SR.UTBadBaseAddress));
}
BindInformation bindInfo;
if (this.variables == null)
{
bindInfo = PrepareBindInformation(parameters, omitDefaults);
}
else
{
bindInfo = this.variables.PrepareBindInformation(parameters, omitDefaults);
}
return Bind(baseAddress, bindInfo, omitDefaults);
}
public Uri BindByPosition(Uri baseAddress, params string[] values)
{
if (baseAddress == null)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("baseAddress");
}
if (!baseAddress.IsAbsoluteUri)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("baseAddress", SR.GetString(
SR.UTBadBaseAddress));
}
BindInformation bindInfo;
if (this.variables == null)
{
if (values.Length > 0)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new FormatException(SR.GetString(
SR.UTBindByPositionNoVariables, this.originalTemplate, values.Length)));
}
bindInfo = new BindInformation(this.additionalDefaults);
}
else
{
bindInfo = this.variables.PrepareBindInformation(values);
}
return Bind(baseAddress, bindInfo, false);
}
// A note about UriTemplate equivalency:
// The introduction of defaults and, more over, terminal defaults, broke the simple
// intuative notion of equivalency between templates. We will define equivalent
// templates as such based on the structure of them and not based on the set of uri
// that are matched by them. The result is that, even though they do not match the
// same set of uri's, the following templates are equivalent:
// - "/foo/{bar}"
// - "/foo/{bar=xyz}"
// A direct result from the support for 'terminal defaults' is that the IsPathEquivalentTo
// method, which was used both to determine the equivalence between templates, as
// well as verify that all the templates, combined together in the same PathEquivalentSet,
// are equivalent in thier path is no longer valid for both purposes. We will break
// it to two distinct methods, each will be called in a different case.
public bool IsEquivalentTo(UriTemplate other)
{
if (other == null)
{
return false;
}
if (other.segments == null || other.queries == null)
{
// they never are null, but PreSharp is complaining,
// and warning suppression isn't working
return false;
}
if (!IsPathFullyEquivalent(other))
{
return false;
}
if (!IsQueryEquivalent(other))
{
return false;
}
Fx.Assert(UriTemplateEquivalenceComparer.Instance.GetHashCode(this) == UriTemplateEquivalenceComparer.Instance.GetHashCode(other), "bad GetHashCode impl");
return true;
}
public UriTemplateMatch Match(Uri baseAddress, Uri candidate)
{
if (baseAddress == null)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("baseAddress");
}
if (!baseAddress.IsAbsoluteUri)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("baseAddress", SR.GetString(
SR.UTBadBaseAddress));
}
if (candidate == null)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("candidate");
}
// ensure that the candidate is 'under' the base address
if (!candidate.IsAbsoluteUri)
{
return null;
}
string basePath = UriTemplateHelpers.GetUriPath(baseAddress);
string candidatePath = UriTemplateHelpers.GetUriPath(candidate);
if (candidatePath.Length < basePath.Length)
{
return null;
}
if (!candidatePath.StartsWith(basePath, StringComparison.OrdinalIgnoreCase))
{
return null;
}
// Identifying the relative segments \ checking matching to the path :
int numSegmentsInBaseAddress = baseAddress.Segments.Length;
string[] candidateSegments = candidate.Segments;
int numMatchedSegments;
Collection<string> relativeCandidateSegments;
if (!IsCandidatePathMatch(numSegmentsInBaseAddress, candidateSegments,
out numMatchedSegments, out relativeCandidateSegments))
{
return null;
}
// Checking matching to the query (if should) :
NameValueCollection candidateQuery = null;
if (!UriTemplateHelpers.CanMatchQueryTrivially(this))
{
candidateQuery = UriTemplateHelpers.ParseQueryString(candidate.Query);
if (!UriTemplateHelpers.CanMatchQueryInterestingly(this, candidateQuery, false))
{
return null;
}
}
// We matched; lets build the UriTemplateMatch
return CreateUriTemplateMatch(baseAddress, candidate, null, numMatchedSegments,
relativeCandidateSegments, candidateQuery);
}
public override string ToString()
{
return this.originalTemplate;
}
internal string AddPathVariable(UriTemplatePartType sourceNature, string varDeclaration)
{
bool hasDefaultValue;
return AddPathVariable(sourceNature, varDeclaration, out hasDefaultValue);
}
internal string AddPathVariable(UriTemplatePartType sourceNature, string varDeclaration,
out bool hasDefaultValue)
{
if (this.variables == null)
{
this.variables = new VariablesCollection(this);
}
return this.variables.AddPathVariable(sourceNature, varDeclaration, out hasDefaultValue);
}
internal string AddQueryVariable(string varDeclaration)
{
if (this.variables == null)
{
this.variables = new VariablesCollection(this);
}
return this.variables.AddQueryVariable(varDeclaration);
}
internal UriTemplateMatch CreateUriTemplateMatch(Uri baseUri, Uri uri, object data,
int numMatchedSegments, Collection<string> relativePathSegments, NameValueCollection uriQuery)
{
UriTemplateMatch result = new UriTemplateMatch();
result.RequestUri = uri;
result.BaseUri = baseUri;
if (uriQuery != null)
{
result.SetQueryParameters(uriQuery);
}
result.SetRelativePathSegments(relativePathSegments);
result.Data = data;
result.Template = this;
for (int i = 0; i < numMatchedSegments; i++)
{
this.segments[i].Lookup(result.RelativePathSegments[i], result.BoundVariables);
}
if (this.wildcard != null)
{
this.wildcard.Lookup(numMatchedSegments, result.RelativePathSegments,
result.BoundVariables);
}
else if (numMatchedSegments < this.segments.Count)
{
BindTerminalDefaults(numMatchedSegments, result.BoundVariables);
}
if (this.queries.Count > 0)
{
foreach (KeyValuePair<string, UriTemplateQueryValue> kvp in this.queries)
{
kvp.Value.Lookup(result.QueryParameters[kvp.Key], result.BoundVariables);
//UriTemplateHelpers.AssertCanonical(varName);
}
}
if (this.additionalDefaults != null)
{
foreach (KeyValuePair<string, string> kvp in this.additionalDefaults)
{
result.BoundVariables.Add(kvp.Key, UnescapeDefaultValue(kvp.Value));
}
}
Fx.Assert(result.RelativePathSegments.Count - numMatchedSegments >= 0, "bad segment computation");
result.SetWildcardPathSegmentsStart(numMatchedSegments);
return result;
}
internal bool IsPathPartiallyEquivalentAt(UriTemplate other, int segmentsCount)
{
// Refer to the note on template equivalency at IsEquivalentTo
// This method checks if any uri with given number of segments, which can be matched
// by this template, can be also matched by the other template.
Fx.Assert(segmentsCount >= this.firstOptionalSegment - 1, "How can that be? The Trie is constructed that way!");
Fx.Assert(segmentsCount <= this.segments.Count, "How can that be? The Trie is constructed that way!");
Fx.Assert(segmentsCount >= other.firstOptionalSegment - 1, "How can that be? The Trie is constructed that way!");
Fx.Assert(segmentsCount <= other.segments.Count, "How can that be? The Trie is constructed that way!");
for (int i = 0; i < segmentsCount; ++i)
{
if (!this.segments[i].IsEquivalentTo(other.segments[i],
((i == segmentsCount - 1) && (this.ignoreTrailingSlash || other.ignoreTrailingSlash))))
{
return false;
}
}
return true;
}
internal bool IsQueryEquivalent(UriTemplate other)
{
if (this.queries.Count != other.queries.Count)
{
return false;
}
foreach (string key in this.queries.Keys)
{
UriTemplateQueryValue utqv = this.queries[key];
UriTemplateQueryValue otherUtqv;
if (!other.queries.TryGetValue(key, out otherUtqv))
{
return false;
}
if (!utqv.IsEquivalentTo(otherUtqv))
{
return false;
}
}
return true;
}
internal static Uri RewriteUri(Uri uri, string host)
{
if (!string.IsNullOrEmpty(host))
{
string originalHostHeader = uri.Host + ((!uri.IsDefaultPort) ? ":" + uri.Port.ToString(CultureInfo.InvariantCulture) : string.Empty);
if (!String.Equals(originalHostHeader, host, StringComparison.OrdinalIgnoreCase))
{
Uri sourceUri = new Uri(String.Format(CultureInfo.InvariantCulture, "{0}://{1}", uri.Scheme, host));
return (new UriBuilder(uri) { Host = sourceUri.Host, Port = sourceUri.Port }).Uri;
}
}
return uri;
}
Uri Bind(Uri baseAddress, BindInformation bindInfo, bool omitDefaults)
{
UriBuilder result = new UriBuilder(baseAddress);
int parameterIndex = 0;
int lastPathParameter = ((this.variables == null) ? -1 : this.variables.PathSegmentVariableNames.Count - 1);
int lastPathParameterToBind;
if (lastPathParameter == -1)
{
lastPathParameterToBind = -1;
}
else if (omitDefaults)
{
lastPathParameterToBind = bindInfo.LastNonDefaultPathParameter;
}
else
{
lastPathParameterToBind = bindInfo.LastNonNullablePathParameter;
}
string[] parameters = bindInfo.NormalizedParameters;
IDictionary<string, string> extraQueryParameters = bindInfo.AdditionalParameters;
// Binding the path :
StringBuilder pathString = new StringBuilder(result.Path);
if (pathString[pathString.Length - 1] != '/')
{
pathString.Append('/');
}
if (lastPathParameterToBind < lastPathParameter)
{
// Binding all the parameters we need
int segmentIndex = 0;
while (parameterIndex <= lastPathParameterToBind)
{
Fx.Assert(segmentIndex < this.segments.Count,
"Calculation of LastNonDefaultPathParameter,lastPathParameter or parameterIndex failed");
this.segments[segmentIndex++].Bind(parameters, ref parameterIndex, pathString);
}
Fx.Assert(parameterIndex == lastPathParameterToBind + 1,
"That is the exit criteria from the loop");
// Maybe we have some literals yet to bind
Fx.Assert(segmentIndex < this.segments.Count,
"Calculation of LastNonDefaultPathParameter,lastPathParameter or parameterIndex failed");
while (this.segments[segmentIndex].Nature == UriTemplatePartType.Literal)
{
this.segments[segmentIndex++].Bind(parameters, ref parameterIndex, pathString);
Fx.Assert(parameterIndex == lastPathParameterToBind + 1,
"We have moved the parameter index in a literal binding");
Fx.Assert(segmentIndex < this.segments.Count,
"Calculation of LastNonDefaultPathParameter,lastPathParameter or parameterIndex failed");
}
// We're done; skip to the beggining of the query parameters
parameterIndex = lastPathParameter + 1;
}
else if (this.segments.Count > 0 || this.wildcard != null)
{
for (int i = 0; i < this.segments.Count; i++)
{
this.segments[i].Bind(parameters, ref parameterIndex, pathString);
}
if (this.wildcard != null)
{
this.wildcard.Bind(parameters, ref parameterIndex, pathString);
}
}
if (this.ignoreTrailingSlash && (pathString[pathString.Length - 1] == '/'))
{
pathString.Remove(pathString.Length - 1, 1);
}
result.Path = pathString.ToString();
// Binding the query :
if ((this.queries.Count != 0) || (extraQueryParameters != null))
{
StringBuilder query = new StringBuilder("");
foreach (string key in this.queries.Keys)
{
this.queries[key].Bind(key, parameters, ref parameterIndex, query);
}
if (extraQueryParameters != null)
{
foreach (string key in extraQueryParameters.Keys)
{
if (this.queries.ContainsKey(key.ToUpperInvariant()))
{
// This can only be if the key passed has the same name as some literal key
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("parameters", SR.GetString(
SR.UTBothLiteralAndNameValueCollectionKey, key));
}
string value = extraQueryParameters[key];
string escapedValue = (string.IsNullOrEmpty(value) ? string.Empty : UrlUtility.UrlEncode(value, Encoding.UTF8));
query.AppendFormat("&{0}={1}", UrlUtility.UrlEncode(key, Encoding.UTF8), escapedValue);
}
}
if (query.Length != 0)
{
query.Remove(0, 1); // remove extra leading '&'
}
result.Query = query.ToString();
}
// Adding the fragment (if needed)
if (this.fragment != null)
{
result.Fragment = this.fragment;
}
return result.Uri;
}
void BindTerminalDefaults(int numMatchedSegments, NameValueCollection boundParameters)
{
Fx.Assert(!this.HasWildcard, "There are no terminal default when ends with wildcard");
Fx.Assert(numMatchedSegments < this.segments.Count, "Otherwise - no defaults to bind");
Fx.Assert(this.variables != null, "Otherwise - no default values to bind");
Fx.Assert(this.variables.DefaultValues != null, "Otherwise - no default values to bind");
for (int i = numMatchedSegments; i < this.segments.Count; i++)
{
switch (this.segments[i].Nature)
{
case UriTemplatePartType.Variable:
{
UriTemplateVariablePathSegment vps = this.segments[i] as UriTemplateVariablePathSegment;
Fx.Assert(vps != null, "How can that be? That its nature");
this.variables.LookupDefault(vps.VarName, boundParameters);
}
break;
default:
Fx.Assert("We only support terminal defaults on Variable segments");
break;
}
}
}
bool IsCandidatePathMatch(int numSegmentsInBaseAddress, string[] candidateSegments,
out int numMatchedSegments, out Collection<string> relativeSegments)
{
int numRelativeSegments = candidateSegments.Length - numSegmentsInBaseAddress;
Fx.Assert(numRelativeSegments >= 0, "bad segments num");
relativeSegments = new Collection<string>();
bool isStillMatch = true;
int relativeSegmentsIndex = 0;
while (isStillMatch && (relativeSegmentsIndex < numRelativeSegments))
{
string segment = candidateSegments[relativeSegmentsIndex + numSegmentsInBaseAddress];
// Mathcing to next regular segment in the template (if there is one); building the wire segment representation
if (relativeSegmentsIndex < this.segments.Count)
{
bool ignoreSlash = (this.ignoreTrailingSlash && (relativeSegmentsIndex == numRelativeSegments - 1));
UriTemplateLiteralPathSegment lps = UriTemplateLiteralPathSegment.CreateFromWireData(segment);
if (!this.segments[relativeSegmentsIndex].IsMatch(lps, ignoreSlash))
{
isStillMatch = false;
break;
}
string relPathSeg = Uri.UnescapeDataString(segment);
if (lps.EndsWithSlash)
{
Fx.Assert(relPathSeg.EndsWith("/", StringComparison.Ordinal), "problem with relative path segment");
relPathSeg = relPathSeg.Substring(0, relPathSeg.Length - 1); // trim slash
}
relativeSegments.Add(relPathSeg);
}
// Checking if the template has a wild card ('*') or a final star var segment ("{*<var name>}"
else if (this.HasWildcard)
{
break;
}
else
{
isStillMatch = false;
break;
}
relativeSegmentsIndex++;
}
if (isStillMatch)
{
numMatchedSegments = relativeSegmentsIndex;
// building the wire representation to segments that were matched to a wild card
if (relativeSegmentsIndex < numRelativeSegments)
{
while (relativeSegmentsIndex < numRelativeSegments)
{
string relPathSeg = Uri.UnescapeDataString(candidateSegments[relativeSegmentsIndex + numSegmentsInBaseAddress]);
if (relPathSeg.EndsWith("/", StringComparison.Ordinal))
{
relPathSeg = relPathSeg.Substring(0, relPathSeg.Length - 1); // trim slash
}
relativeSegments.Add(relPathSeg);
relativeSegmentsIndex++;
}
}
// Checking if we matched all required segments already
else if (numMatchedSegments < this.firstOptionalSegment)
{
isStillMatch = false;
}
}
else
{
numMatchedSegments = 0;
}
return isStillMatch;
}
bool IsPathFullyEquivalent(UriTemplate other)
{
// Refer to the note on template equivalency at IsEquivalentTo
// This method checks if both templates has a fully equivalent path.
if (this.HasWildcard != other.HasWildcard)
{
return false;
}
if (this.segments.Count != other.segments.Count)
{
return false;
}
for (int i = 0; i < this.segments.Count; ++i)
{
if (!this.segments[i].IsEquivalentTo(other.segments[i],
(i == this.segments.Count - 1) && !this.HasWildcard && (this.ignoreTrailingSlash || other.ignoreTrailingSlash)))
{
return false;
}
}
return true;
}
BindInformation PrepareBindInformation(IDictionary<string, string> parameters, bool omitDefaults)
{
if (parameters == null)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("parameters");
}
IDictionary<string, string> extraParameters = new Dictionary<string, string>(UriTemplateHelpers.GetQueryKeyComparer());
foreach (KeyValuePair<string, string> kvp in parameters)
{
if (string.IsNullOrEmpty(kvp.Key))
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("parameters",
SR.GetString(SR.UTBindByNameCalledWithEmptyKey));
}
extraParameters.Add(kvp);
}
BindInformation bindInfo;
ProcessDefaultsAndCreateBindInfo(omitDefaults, extraParameters, out bindInfo);
return bindInfo;
}
BindInformation PrepareBindInformation(NameValueCollection parameters, bool omitDefaults)
{
if (parameters == null)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("parameters");
}
IDictionary<string, string> extraParameters = new Dictionary<string, string>(UriTemplateHelpers.GetQueryKeyComparer());
foreach (string key in parameters.AllKeys)
{
if (string.IsNullOrEmpty(key))
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("parameters",
SR.GetString(SR.UTBindByNameCalledWithEmptyKey));
}
extraParameters.Add(key, parameters[key]);
}
BindInformation bindInfo;
ProcessDefaultsAndCreateBindInfo(omitDefaults, extraParameters, out bindInfo);
return bindInfo;
}
void ProcessDefaultsAndCreateBindInfo(bool omitDefaults, IDictionary<string, string> extraParameters,
out BindInformation bindInfo)
{
Fx.Assert(extraParameters != null, "We are expected to create it at the calling PrepareBindInformation");
if (this.additionalDefaults != null)
{
if (omitDefaults)
{
foreach (KeyValuePair<string, string> kvp in this.additionalDefaults)
{
string extraParameter;
if (extraParameters.TryGetValue(kvp.Key, out extraParameter))
{
if (string.Compare(extraParameter, kvp.Value, StringComparison.Ordinal) == 0)
{
extraParameters.Remove(kvp.Key);
}
}
}
}
else
{
foreach (KeyValuePair<string, string> kvp in this.additionalDefaults)
{
if (!extraParameters.ContainsKey(kvp.Key))
{
extraParameters.Add(kvp.Key, kvp.Value);
}
}
}
}
if (extraParameters.Count == 0)
{
extraParameters = null;
}
bindInfo = new BindInformation(extraParameters);
}
string UnescapeDefaultValue(string escapedValue)
{
if (string.IsNullOrEmpty(escapedValue))
{
return escapedValue;
}
if (this.unescapedDefaults == null)
{
this.unescapedDefaults = new ConcurrentDictionary<string, string>(StringComparer.Ordinal);
}
return this.unescapedDefaults.GetOrAdd(escapedValue, Uri.UnescapeDataString);
}
struct BindInformation
{
IDictionary<string, string> additionalParameters;
int lastNonDefaultPathParameter;
int lastNonNullablePathParameter;
string[] normalizedParameters;
public BindInformation(string[] normalizedParameters, int lastNonDefaultPathParameter,
int lastNonNullablePathParameter, IDictionary<string, string> additionalParameters)
{
this.normalizedParameters = normalizedParameters;
this.lastNonDefaultPathParameter = lastNonDefaultPathParameter;
this.lastNonNullablePathParameter = lastNonNullablePathParameter;
this.additionalParameters = additionalParameters;
}
public BindInformation(IDictionary<string, string> additionalParameters)
{
this.normalizedParameters = null;
this.lastNonDefaultPathParameter = -1;
this.lastNonNullablePathParameter = -1;
this.additionalParameters = additionalParameters;
}
public IDictionary<string, string> AdditionalParameters
{
get
{
return this.additionalParameters;
}
}
public int LastNonDefaultPathParameter
{
get
{
return this.lastNonDefaultPathParameter;
}
}
public int LastNonNullablePathParameter
{
get
{
return this.lastNonNullablePathParameter;
}
}
public string[] NormalizedParameters
{
get
{
return this.normalizedParameters;
}
}
}
class UriTemplateDefaults : IDictionary<string, string>
{
Dictionary<string, string> defaults;
ReadOnlyCollection<string> keys;
ReadOnlyCollection<string> values;
public UriTemplateDefaults(UriTemplate template)
{
this.defaults = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
if ((template.variables != null) && (template.variables.DefaultValues != null))
{
foreach (KeyValuePair<string, string> kvp in template.variables.DefaultValues)
{
this.defaults.Add(kvp.Key, kvp.Value);
}
}
if (template.additionalDefaults != null)
{
foreach (KeyValuePair<string, string> kvp in template.additionalDefaults)
{
this.defaults.Add(kvp.Key.ToUpperInvariant(), kvp.Value);
}
}
this.keys = new ReadOnlyCollection<string>(new List<string>(this.defaults.Keys));
this.values = new ReadOnlyCollection<string>(new List<string>(this.defaults.Values));
}
// ICollection<KeyValuePair<string, string>> Members
public int Count
{
get
{
return this.defaults.Count;
}
}
public bool IsReadOnly
{
get
{
return true;
}
}
// IDictionary<string, string> Members
public ICollection<string> Keys
{
get
{
return this.keys;
}
}
public ICollection<string> Values
{
get
{
return this.values;
}
}
public string this[string key]
{
get
{
return this.defaults[key];
}
set
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(
SR.GetString(SR.UTDefaultValuesAreImmutable)));
}
}
public void Add(string key, string value)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(
SR.GetString(SR.UTDefaultValuesAreImmutable)));
}
public void Add(KeyValuePair<string, string> item)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(
SR.GetString(SR.UTDefaultValuesAreImmutable)));
}
public void Clear()
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(
SR.GetString(SR.UTDefaultValuesAreImmutable)));
}
public bool Contains(KeyValuePair<string, string> item)
{
return (this.defaults as ICollection<KeyValuePair<string, string>>).Contains(item);
}
public bool ContainsKey(string key)
{
return this.defaults.ContainsKey(key);
}
public void CopyTo(KeyValuePair<string, string>[] array, int arrayIndex)
{
(this.defaults as ICollection<KeyValuePair<string, string>>).CopyTo(array, arrayIndex);
}
// IEnumerable<KeyValuePair<string, string>> Members
public IEnumerator<KeyValuePair<string, string>> GetEnumerator()
{
return this.defaults.GetEnumerator();
}
public bool Remove(string key)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(
SR.GetString(SR.UTDefaultValuesAreImmutable)));
}
public bool Remove(KeyValuePair<string, string> item)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(
SR.GetString(SR.UTDefaultValuesAreImmutable)));
}
// IEnumerable Members
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return this.defaults.GetEnumerator();
}
public bool TryGetValue(string key, out string value)
{
return this.defaults.TryGetValue(key, out value);
}
}
class VariablesCollection
{
readonly UriTemplate owner;
static ReadOnlyCollection<string> emptyStringCollection = null;
Dictionary<string, string> defaultValues; // key is the variable name (in uppercase; as appear in the variable names lists)
int firstNullablePathVariable;
List<string> pathSegmentVariableNames; // ToUpperInvariant, in order they occur in the original template string
ReadOnlyCollection<string> pathSegmentVariableNamesSnapshot = null;
List<UriTemplatePartType> pathSegmentVariableNature;
List<string> queryValueVariableNames; // ToUpperInvariant, in order they occur in the original template string
ReadOnlyCollection<string> queryValueVariableNamesSnapshot = null;
public VariablesCollection(UriTemplate owner)
{
this.owner = owner;
this.pathSegmentVariableNames = new List<string>();
this.pathSegmentVariableNature = new List<UriTemplatePartType>();
this.queryValueVariableNames = new List<string>();
this.firstNullablePathVariable = -1;
}
public static ReadOnlyCollection<string> EmptyCollection
{
get
{
if (emptyStringCollection == null)
{
emptyStringCollection = new ReadOnlyCollection<string>(new List<string>());
}
return emptyStringCollection;
}
}
public Dictionary<string, string> DefaultValues
{
get
{
return this.defaultValues;
}
}
public ReadOnlyCollection<string> PathSegmentVariableNames
{
get
{
if (this.pathSegmentVariableNamesSnapshot == null)
{
Interlocked.CompareExchange<ReadOnlyCollection<string>>(ref this.pathSegmentVariableNamesSnapshot, new ReadOnlyCollection<string>(
this.pathSegmentVariableNames), null);
}
return this.pathSegmentVariableNamesSnapshot;
}
}
public ReadOnlyCollection<string> QueryValueVariableNames
{
get
{
if (this.queryValueVariableNamesSnapshot == null)
{
Interlocked.CompareExchange<ReadOnlyCollection<string>>(ref this.queryValueVariableNamesSnapshot, new ReadOnlyCollection<string>(
this.queryValueVariableNames), null);
}
return this.queryValueVariableNamesSnapshot;
}
}
public void AddDefaultValue(string varName, string value)
{
int varIndex = this.pathSegmentVariableNames.IndexOf(varName);
Fx.Assert(varIndex != -1, "Adding default value is restricted to path variables");
if ((this.owner.wildcard != null) && this.owner.wildcard.HasVariable &&
(varIndex == this.pathSegmentVariableNames.Count - 1))
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(
SR.GetString(SR.UTStarVariableWithDefaultsFromAdditionalDefaults,
this.owner.originalTemplate, varName)));
}
if (this.pathSegmentVariableNature[varIndex] != UriTemplatePartType.Variable)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(
SR.GetString(SR.UTDefaultValueToCompoundSegmentVarFromAdditionalDefaults,
this.owner.originalTemplate, varName)));
}
if (string.IsNullOrEmpty(value) ||
(string.Compare(value, UriTemplate.NullableDefault, StringComparison.OrdinalIgnoreCase) == 0))
{
value = null;
}
if (this.defaultValues == null)
{
this.defaultValues = new Dictionary<string, string>();
}
this.defaultValues.Add(varName, value);
}
public string AddPathVariable(UriTemplatePartType sourceNature, string varDeclaration, out bool hasDefaultValue)
{
Fx.Assert(sourceNature != UriTemplatePartType.Literal, "Literal path segments can't be the source for path variables");
string varName;
string defaultValue;
ParseVariableDeclaration(varDeclaration, out varName, out defaultValue);
hasDefaultValue = (defaultValue != null);
if (varName.IndexOf(UriTemplate.WildcardPath, StringComparison.Ordinal) != -1)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new FormatException(
SR.GetString(SR.UTInvalidWildcardInVariableOrLiteral, this.owner.originalTemplate, UriTemplate.WildcardPath)));
}
string uppercaseVarName = varName.ToUpperInvariant();
if (this.pathSegmentVariableNames.Contains(uppercaseVarName) ||
this.queryValueVariableNames.Contains(uppercaseVarName))
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(
SR.GetString(SR.UTVarNamesMustBeUnique, this.owner.originalTemplate, varName)));
}
this.pathSegmentVariableNames.Add(uppercaseVarName);
this.pathSegmentVariableNature.Add(sourceNature);
if (hasDefaultValue)
{
if (defaultValue == string.Empty)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(
SR.GetString(SR.UTInvalidDefaultPathValue, this.owner.originalTemplate,
varDeclaration, varName)));
}
if (string.Compare(defaultValue, UriTemplate.NullableDefault, StringComparison.OrdinalIgnoreCase) == 0)
{
defaultValue = null;
}
if (this.defaultValues == null)
{
this.defaultValues = new Dictionary<string, string>();
}
this.defaultValues.Add(uppercaseVarName, defaultValue);
}
return uppercaseVarName;
}
public string AddQueryVariable(string varDeclaration)
{
string varName;
string defaultValue;
ParseVariableDeclaration(varDeclaration, out varName, out defaultValue);
if (varName.IndexOf(UriTemplate.WildcardPath, StringComparison.Ordinal) != -1)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new FormatException(
SR.GetString(SR.UTInvalidWildcardInVariableOrLiteral, this.owner.originalTemplate, UriTemplate.WildcardPath)));
}
if (defaultValue != null)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(
SR.GetString(SR.UTDefaultValueToQueryVar, this.owner.originalTemplate,
varDeclaration, varName)));
}
string uppercaseVarName = varName.ToUpperInvariant();
if (this.pathSegmentVariableNames.Contains(uppercaseVarName) ||
this.queryValueVariableNames.Contains(uppercaseVarName))
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(
SR.GetString(SR.UTVarNamesMustBeUnique, this.owner.originalTemplate, varName)));
}
this.queryValueVariableNames.Add(uppercaseVarName);
return uppercaseVarName;
}
public void LookupDefault(string varName, NameValueCollection boundParameters)
{
Fx.Assert(this.defaultValues.ContainsKey(varName), "Otherwise, we don't have a value to bind");
boundParameters.Add(varName, owner.UnescapeDefaultValue(this.defaultValues[varName]));
}
public BindInformation PrepareBindInformation(IDictionary<string, string> parameters, bool omitDefaults)
{
if (parameters == null)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("parameters");
}
string[] normalizedParameters = PrepareNormalizedParameters();
IDictionary<string, string> extraParameters = null;
foreach (string key in parameters.Keys)
{
ProcessBindParameter(key, parameters[key], normalizedParameters, ref extraParameters);
}
BindInformation bindInfo;
ProcessDefaultsAndCreateBindInfo(omitDefaults, normalizedParameters, extraParameters, out bindInfo);
return bindInfo;
}
public BindInformation PrepareBindInformation(NameValueCollection parameters, bool omitDefaults)
{
if (parameters == null)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("parameters");
}
string[] normalizedParameters = PrepareNormalizedParameters();
IDictionary<string, string> extraParameters = null;
foreach (string key in parameters.AllKeys)
{
ProcessBindParameter(key, parameters[key], normalizedParameters, ref extraParameters);
}
BindInformation bindInfo;
ProcessDefaultsAndCreateBindInfo(omitDefaults, normalizedParameters, extraParameters, out bindInfo);
return bindInfo;
}
public BindInformation PrepareBindInformation(params string[] parameters)
{
if (parameters == null)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("values");
}
if ((parameters.Length < this.pathSegmentVariableNames.Count) ||
(parameters.Length > this.pathSegmentVariableNames.Count + this.queryValueVariableNames.Count))
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new FormatException(
SR.GetString(SR.UTBindByPositionWrongCount, this.owner.originalTemplate,
this.pathSegmentVariableNames.Count, this.queryValueVariableNames.Count,
parameters.Length)));
}
string[] normalizedParameters;
if (parameters.Length == this.pathSegmentVariableNames.Count + this.queryValueVariableNames.Count)
{
normalizedParameters = parameters;
}
else
{
normalizedParameters = new string[this.pathSegmentVariableNames.Count + this.queryValueVariableNames.Count];
parameters.CopyTo(normalizedParameters, 0);
for (int i = parameters.Length; i < normalizedParameters.Length; i++)
{
normalizedParameters[i] = null;
}
}
int lastNonDefaultPathParameter;
int lastNonNullablePathParameter;
LoadDefaultsAndValidate(normalizedParameters, out lastNonDefaultPathParameter,
out lastNonNullablePathParameter);
return new BindInformation(normalizedParameters, lastNonDefaultPathParameter,
lastNonNullablePathParameter, this.owner.additionalDefaults);
}
public void ValidateDefaults(out int firstOptionalSegment)
{
Fx.Assert(this.defaultValues != null, "We are checking this condition from the c'tor");
Fx.Assert(this.pathSegmentVariableNames.Count > 0, "Otherwise, how can we have default values");
// Finding the first valid nullable defaults
for (int i = this.pathSegmentVariableNames.Count - 1; (i >= 0) && (this.firstNullablePathVariable == -1); i--)
{
string varName = this.pathSegmentVariableNames[i];
string defaultValue;
if (!this.defaultValues.TryGetValue(varName, out defaultValue))
{
this.firstNullablePathVariable = i + 1;
}
else if (defaultValue != null)
{
this.firstNullablePathVariable = i + 1;
}
}
if (this.firstNullablePathVariable == -1)
{
this.firstNullablePathVariable = 0;
}
// Making sure that there are no nullables to the left of the first valid nullable
if (this.firstNullablePathVariable > 1)
{
for (int i = this.firstNullablePathVariable - 2; i >= 0; i--)
{
string varName = this.pathSegmentVariableNames[i];
string defaultValue;
if (this.defaultValues.TryGetValue(varName, out defaultValue))
{
if (defaultValue == null)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(
SR.GetString(SR.UTNullableDefaultMustBeFollowedWithNullables, this.owner.originalTemplate,
varName, this.pathSegmentVariableNames[i + 1])));
}
}
}
}
// Making sure that there are no Literals\WildCards to the right
// Based on the fact that only Variable Path Segments support default values,
// if firstNullablePathVariable=N and pathSegmentVariableNames.Count=M then
// the nature of the last M-N path segments should be StringNature.Variable; otherwise,
// there was a literal segment in between. Also, there shouldn't be a wildcard.
if (this.firstNullablePathVariable < this.pathSegmentVariableNames.Count)
{
if (this.owner.HasWildcard)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(
SR.GetString(SR.UTNullableDefaultMustNotBeFollowedWithWildcard,
this.owner.originalTemplate, this.pathSegmentVariableNames[this.firstNullablePathVariable])));
}
for (int i = this.pathSegmentVariableNames.Count - 1; i >= this.firstNullablePathVariable; i--)
{
int segmentIndex = this.owner.segments.Count - (this.pathSegmentVariableNames.Count - i);
if (this.owner.segments[segmentIndex].Nature != UriTemplatePartType.Variable)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(
SR.GetString(SR.UTNullableDefaultMustNotBeFollowedWithLiteral,
this.owner.originalTemplate, this.pathSegmentVariableNames[this.firstNullablePathVariable],
this.owner.segments[segmentIndex].OriginalSegment)));
}
}
}
// Now that we have the firstNullablePathVariable set, lets calculate the firstOptionalSegment.
// We already knows that the last M-N path segments (when M=pathSegmentVariableNames.Count and
// N=firstNullablePathVariable) are optional (see the previos comment). We will start there and
// move to the left, stopping at the first segment, which is not a variable or is a variable
// and doesn't have a default value.
int numNullablePathVariables = (this.pathSegmentVariableNames.Count - this.firstNullablePathVariable);
firstOptionalSegment = this.owner.segments.Count - numNullablePathVariables;
if (!this.owner.HasWildcard)
{
while (firstOptionalSegment > 0)
{
UriTemplatePathSegment ps = this.owner.segments[firstOptionalSegment - 1];
if (ps.Nature != UriTemplatePartType.Variable)
{
break;
}
UriTemplateVariablePathSegment vps = (ps as UriTemplateVariablePathSegment);
Fx.Assert(vps != null, "Should be; that's his nature");
if (!this.defaultValues.ContainsKey(vps.VarName))
{
break;
}
firstOptionalSegment--;
}
}
}
void AddAdditionalDefaults(ref IDictionary<string, string> extraParameters)
{
if (extraParameters == null)
{
extraParameters = this.owner.additionalDefaults;
}
else
{
foreach (KeyValuePair<string, string> kvp in this.owner.additionalDefaults)
{
if (!extraParameters.ContainsKey(kvp.Key))
{
extraParameters.Add(kvp.Key, kvp.Value);
}
}
}
}
void LoadDefaultsAndValidate(string[] normalizedParameters, out int lastNonDefaultPathParameter,
out int lastNonNullablePathParameter)
{
// First step - loading defaults
for (int i = 0; i < this.pathSegmentVariableNames.Count; i++)
{
if (string.IsNullOrEmpty(normalizedParameters[i]) && (this.defaultValues != null))
{
this.defaultValues.TryGetValue(this.pathSegmentVariableNames[i], out normalizedParameters[i]);
}
}
// Second step - calculating bind constrains
lastNonDefaultPathParameter = this.pathSegmentVariableNames.Count - 1;
if ((this.defaultValues != null) &&
(this.owner.segments[this.owner.segments.Count - 1].Nature != UriTemplatePartType.Literal))
{
bool foundNonDefaultPathParameter = false;
while (!foundNonDefaultPathParameter && (lastNonDefaultPathParameter >= 0))
{
string defaultValue;
if (this.defaultValues.TryGetValue(this.pathSegmentVariableNames[lastNonDefaultPathParameter],
out defaultValue))
{
if (string.Compare(normalizedParameters[lastNonDefaultPathParameter],
defaultValue, StringComparison.Ordinal) != 0)
{
foundNonDefaultPathParameter = true;
}
else
{
lastNonDefaultPathParameter--;
}
}
else
{
foundNonDefaultPathParameter = true;
}
}
}
if (this.firstNullablePathVariable > lastNonDefaultPathParameter)
{
lastNonNullablePathParameter = this.firstNullablePathVariable - 1;
}
else
{
lastNonNullablePathParameter = lastNonDefaultPathParameter;
}
// Third step - validate
for (int i = 0; i <= lastNonNullablePathParameter; i++)
{
// Skip validation for terminating star variable segment :
if (this.owner.HasWildcard && this.owner.wildcard.HasVariable &&
(i == this.pathSegmentVariableNames.Count - 1))
{
continue;
}
// Validate
if (string.IsNullOrEmpty(normalizedParameters[i]))
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("parameters",
SR.GetString(SR.BindUriTemplateToNullOrEmptyPathParam, this.pathSegmentVariableNames[i]));
}
}
}
void ParseVariableDeclaration(string varDeclaration, out string varName, out string defaultValue)
{
if ((varDeclaration.IndexOf('{') != -1) || (varDeclaration.IndexOf('}') != -1))
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new FormatException(
SR.GetString(SR.UTInvalidVarDeclaration, this.owner.originalTemplate, varDeclaration)));
}
int equalSignIndex = varDeclaration.IndexOf('=');
switch (equalSignIndex)
{
case -1:
varName = varDeclaration;
defaultValue = null;
break;
case 0:
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new FormatException(
SR.GetString(SR.UTInvalidVarDeclaration, this.owner.originalTemplate, varDeclaration)));
default:
varName = varDeclaration.Substring(0, equalSignIndex);
defaultValue = varDeclaration.Substring(equalSignIndex + 1);
if (defaultValue.IndexOf('=') != -1)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new FormatException(
SR.GetString(SR.UTInvalidVarDeclaration, this.owner.originalTemplate, varDeclaration)));
}
break;
}
}
string[] PrepareNormalizedParameters()
{
string[] normalizedParameters = new string[this.pathSegmentVariableNames.Count + this.queryValueVariableNames.Count];
for (int i = 0; i < normalizedParameters.Length; i++)
{
normalizedParameters[i] = null;
}
return normalizedParameters;
}
void ProcessBindParameter(string name, string value, string[] normalizedParameters,
ref IDictionary<string, string> extraParameters)
{
if (string.IsNullOrEmpty(name))
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("parameters",
SR.GetString(SR.UTBindByNameCalledWithEmptyKey));
}
string uppercaseVarName = name.ToUpperInvariant();
int pathVarIndex = this.pathSegmentVariableNames.IndexOf(uppercaseVarName);
if (pathVarIndex != -1)
{
normalizedParameters[pathVarIndex] = (string.IsNullOrEmpty(value) ? string.Empty : value);
return;
}
int queryVarIndex = this.queryValueVariableNames.IndexOf(uppercaseVarName);
if (queryVarIndex != -1)
{
normalizedParameters[this.pathSegmentVariableNames.Count + queryVarIndex] = (string.IsNullOrEmpty(value) ? string.Empty : value);
return;
}
if (extraParameters == null)
{
extraParameters = new Dictionary<string, string>(UriTemplateHelpers.GetQueryKeyComparer());
}
extraParameters.Add(name, value);
}
void ProcessDefaultsAndCreateBindInfo(bool omitDefaults, string[] normalizedParameters,
IDictionary<string, string> extraParameters, out BindInformation bindInfo)
{
int lastNonDefaultPathParameter;
int lastNonNullablePathParameter;
LoadDefaultsAndValidate(normalizedParameters, out lastNonDefaultPathParameter,
out lastNonNullablePathParameter);
if (this.owner.additionalDefaults != null)
{
if (omitDefaults)
{
RemoveAdditionalDefaults(ref extraParameters);
}
else
{
AddAdditionalDefaults(ref extraParameters);
}
}
bindInfo = new BindInformation(normalizedParameters, lastNonDefaultPathParameter,
lastNonNullablePathParameter, extraParameters);
}
void RemoveAdditionalDefaults(ref IDictionary<string, string> extraParameters)
{
if (extraParameters == null)
{
return;
}
foreach (KeyValuePair<string, string> kvp in this.owner.additionalDefaults)
{
string extraParameter;
if (extraParameters.TryGetValue(kvp.Key, out extraParameter))
{
if (string.Compare(extraParameter, kvp.Value, StringComparison.Ordinal) == 0)
{
extraParameters.Remove(kvp.Key);
}
}
}
if (extraParameters.Count == 0)
{
extraParameters = null;
}
}
}
class WildcardInfo
{
readonly UriTemplate owner;
readonly string varName;
public WildcardInfo(UriTemplate owner)
{
this.varName = null;
this.owner = owner;
}
public WildcardInfo(UriTemplate owner, string segment)
{
Fx.Assert(!segment.EndsWith("/", StringComparison.Ordinal), "We are expecting to check this earlier");
bool hasDefault;
this.varName = owner.AddPathVariable(UriTemplatePartType.Variable,
segment.Substring(1 + WildcardPath.Length, segment.Length - 2 - WildcardPath.Length),
out hasDefault);
// Since this is a terminating star segment there shouldn't be a default
if (hasDefault)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(
SR.GetString(SR.UTStarVariableWithDefaults, owner.originalTemplate,
segment, this.varName)));
}
this.owner = owner;
}
internal bool HasVariable
{
get
{
return (!string.IsNullOrEmpty(this.varName));
}
}
public void Bind(string[] values, ref int valueIndex, StringBuilder path)
{
if (HasVariable)
{
Fx.Assert(valueIndex < values.Length, "Not enough values to bind");
if (string.IsNullOrEmpty(values[valueIndex]))
{
valueIndex++;
}
else
{
path.Append(values[valueIndex++]);
}
}
}
public void Lookup(int numMatchedSegments, Collection<string> relativePathSegments,
NameValueCollection boundParameters)
{
Fx.Assert(numMatchedSegments == this.owner.segments.Count, "We should have matched the other segments");
if (HasVariable)
{
StringBuilder remainingPath = new StringBuilder();
for (int i = numMatchedSegments; i < relativePathSegments.Count; i++)
{
if (i < relativePathSegments.Count - 1)
{
remainingPath.AppendFormat("{0}/", relativePathSegments[i]);
}
else
{
remainingPath.Append(relativePathSegments[i]);
}
}
boundParameters.Add(this.varName, remainingPath.ToString());
}
}
}
}
}
|