UriTemplate.cs source code in C# .NET

Source code for the .NET framework in C#

                        

Code:

/ WCF / WCF / 3.5.30729.1 / untmp / Orcas / SP / ndp / cdf / src / NetFx35 / System.ServiceModel.Web / System / UriTemplate.cs / 4 / UriTemplate.cs

                            //---------------------------------------------------------------- 
// Copyright (c) Microsoft Corporation.  All rights reserved.
//---------------------------------------------------------------

namespace System 
{
    using System.Collections.Generic; 
    using System.Collections.ObjectModel; 
    using System.Collections.Specialized;
    using System.ServiceModel; 
    using System.ServiceModel.Channels;
    using System.ServiceModel.Web;
    using System.Text;
    using System.Web; 

    public class UriTemplate 
    { 
        internal readonly int firstOptionalSegment;
 
        internal readonly string originalTemplate;
        internal readonly Dictionary queries; // keys are original case specified in UriTemplate constructor, dictionary ignores case
        internal readonly List segments;
        internal const string WildcardPath = "*"; 
        readonly Dictionary 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 defaults;
        Dictionary 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 additionalDefaults)
            : this(template, false, additionalDefaults) 
        {
        }
        public UriTemplate(string template, bool ignoreTrailingSlash, IDictionary additionalDefaults)
        { 
            if (template == null)
            { 
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("template"); 
            }
            this.originalTemplate = template; 
            this.ignoreTrailingSlash = ignoreTrailingSlash;
            this.segments = new List();
            this.queries = new Dictionary(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 ("{*}")
                    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(SR2.GetString(
                                SR2.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(SR2.GetString(
                            SR2.UTQueryCannotHaveEmptyName, this.originalTemplate))); 
                    }
                    if (UriTemplateHelpers.IdentifyPartType(key) != UriTemplatePartType.Literal)
                    {
                        throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("template", SR2.GetString( 
                            SR2.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(SR2.GetString(
                            SR2.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(additionalDefaults, StringComparer.OrdinalIgnoreCase); 
                    }
                } 
                else
                {
                    foreach (KeyValuePair kvp in additionalDefaults)
                    { 
                        string uppercaseKey = kvp.Key.ToUpperInvariant();
                        if ((this.variables.DefaultValues != null) && this.variables.DefaultValues.ContainsKey(uppercaseKey)) 
                        { 
                            throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("additionalDefaults",
                                SR2.GetString(SR2.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(
                                SR2.GetString(SR2.UTDefaultValueToQueryVarFromAdditionalDefaults, this.originalTemplate, 
                                uppercaseKey)));
                        }
                        else if (string.Compare(kvp.Value, UriTemplate.NullableDefault, StringComparison.OrdinalIgnoreCase) == 0)
                        { 
                            throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(
                                SR2.GetString(SR2.UTNullableDefaultAtAdditionalDefaults, this.originalTemplate, 
                                uppercaseKey))); 
                        }
                        else 
                        {
                            if (this.additionalDefaults == null)
                            {
                                this.additionalDefaults = new Dictionary(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 Defaults
        { 
            get 
            {
                if (this.defaults == null) 
                {
                    this.defaults = new UriTemplateDefaults(this);
                }
                return this.defaults; 
            }
        } 
        public bool IgnoreTrailingSlash 
        {
            get 
            {
                return this.ignoreTrailingSlash;
            }
        } 
        public ReadOnlyCollection PathSegmentVariableNames
        { 
            get 
            {
                if (this.variables == null) 
                {
                    return VariablesCollection.EmptyCollection;
                }
                else 
                {
                    return this.variables.PathSegmentVariableNames; 
                } 
            }
        } 
        public ReadOnlyCollection 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 parameters)
        { 
            return BindByName(baseAddress, parameters, false); 
        }
        public Uri BindByName(Uri baseAddress, IDictionary parameters, bool omitDefaults) 
        {
            if (baseAddress == null)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("baseAddress"); 
            }
            if (!baseAddress.IsAbsoluteUri) 
            { 
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("baseAddress", SR2.GetString(
                    SR2.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", SR2.GetString(
                    SR2.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", SR2.GetString( 
                    SR2.UTBadBaseAddress));
            } 
 
            BindInformation bindInfo;
            if (this.variables == null) 
            {
                if (values.Length > 0)
                {
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new FormatException(SR2.GetString( 
                        SR2.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", SR2.GetString( 
                    SR2.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 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 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 kvp in this.queries)
                { 
                    kvp.Value.Lookup(result.QueryParameters[kvp.Key], result.BoundVariables); 
                    //UriTemplateHelpers.AssertCanonical(varName);
                } 
            }
            if (this.additionalDefaults != null)
            {
                foreach (KeyValuePair 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;
        } 

        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 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) 
            {
                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", SR2.GetString( 
                                SR2.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 relativeSegments) 
        {
            int numRelativeSegments = candidateSegments.Length - numSegmentsInBaseAddress; 
            Fx.Assert(numRelativeSegments >= 0, "bad segments num");
            relativeSegments = new Collection();
            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 ("{*}"
                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 parameters, bool omitDefaults)
        {
            if (parameters == null) 
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("parameters"); 
            } 

            IDictionary extraParameters = new Dictionary(UriTemplateHelpers.GetQueryKeyComparer()); 
            foreach (KeyValuePair kvp in parameters)
            {
                if (string.IsNullOrEmpty(kvp.Key))
                { 
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("parameters",
                        SR2.GetString(SR2.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 extraParameters = new Dictionary(UriTemplateHelpers.GetQueryKeyComparer()); 
            foreach (string key in parameters.AllKeys)
            { 
                if (string.IsNullOrEmpty(key)) 
                {
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("parameters", 
                        SR2.GetString(SR2.UTBindByNameCalledWithEmptyKey));
                }

                extraParameters.Add(key, parameters[key]); 
            }
            BindInformation bindInfo; 
            ProcessDefaultsAndCreateBindInfo(omitDefaults, extraParameters, out bindInfo); 
            return bindInfo;
        } 
        void ProcessDefaultsAndCreateBindInfo(bool omitDefaults, IDictionary 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 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 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 Dictionary(StringComparer.Ordinal);
            }
            string unescapedValue; 
            if (!this.unescapedDefaults.TryGetValue(escapedValue, out unescapedValue))
            { 
                unescapedValue = Uri.UnescapeDataString(escapedValue); 
                this.unescapedDefaults.Add(escapedValue, unescapedValue);
            } 

            return unescapedValue;
        }
 
        struct BindInformation
        { 
            IDictionary additionalParameters; 
            int lastNonDefaultPathParameter;
            int lastNonNullablePathParameter; 
            string[] normalizedParameters;

            public BindInformation(string[] normalizedParameters, int lastNonDefaultPathParameter,
                int lastNonNullablePathParameter, IDictionary additionalParameters) 
            {
                this.normalizedParameters = normalizedParameters; 
                this.lastNonDefaultPathParameter = lastNonDefaultPathParameter; 
                this.lastNonNullablePathParameter = lastNonNullablePathParameter;
                this.additionalParameters = additionalParameters; 
            }
            public BindInformation(IDictionary additionalParameters)
            {
                this.normalizedParameters = null; 
                this.lastNonDefaultPathParameter = -1;
                this.lastNonNullablePathParameter = -1; 
                this.additionalParameters = additionalParameters; 
            }
 
            public IDictionary 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 
        {
            Dictionary defaults; 
            ReadOnlyCollection keys; 
            ReadOnlyCollection values;
 
            public UriTemplateDefaults(UriTemplate template)
            {
                this.defaults = new Dictionary(StringComparer.OrdinalIgnoreCase);
                if ((template.variables != null) && (template.variables.DefaultValues != null)) 
                {
                    foreach (KeyValuePair kvp in template.variables.DefaultValues) 
                    { 
                        this.defaults.Add(kvp.Key, kvp.Value);
                    } 
                }
                if (template.additionalDefaults != null)
                {
                    foreach (KeyValuePair kvp in template.additionalDefaults) 
                    {
                        this.defaults.Add(kvp.Key.ToUpperInvariant(), kvp.Value); 
                    } 
                }
                this.keys = new ReadOnlyCollection(new List(this.defaults.Keys)); 
                this.values = new ReadOnlyCollection(new List(this.defaults.Values));
            }

            // ICollection> Members 
            public int Count
            { 
                get 
                {
                    return this.defaults.Count; 
                }
            }
            public bool IsReadOnly
            { 
                get
                { 
                    return true; 
                }
            } 

            // IDictionary Members
            public ICollection Keys
            { 
                get
                { 
                    return this.keys; 
                }
            } 
            public ICollection Values
            {
                get
                { 
                    return this.values;
                } 
            } 
            public string this[string key]
            { 
                get
                {
                    return this.defaults[key];
                } 
                set
                { 
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException( 
                        SR2.GetString(SR2.UTDefaultValuesAreImmutable)));
                } 
            }

            public void Add(string key, string value)
            { 
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(
                    SR2.GetString(SR2.UTDefaultValuesAreImmutable))); 
            } 

            public void Add(KeyValuePair item) 
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(
                    SR2.GetString(SR2.UTDefaultValuesAreImmutable)));
            } 
            public void Clear()
            { 
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException( 
                    SR2.GetString(SR2.UTDefaultValuesAreImmutable)));
            } 
            public bool Contains(KeyValuePair item)
            {
                return (this.defaults as ICollection>).Contains(item);
            } 
            public bool ContainsKey(string key)
            { 
                return this.defaults.ContainsKey(key); 
            }
            public void CopyTo(KeyValuePair[] array, int arrayIndex) 
            {
                (this.defaults as ICollection>).CopyTo(array, arrayIndex);
            }
 
            // IEnumerable> Members
            public IEnumerator> GetEnumerator() 
            { 
                return this.defaults.GetEnumerator();
            } 
            public bool Remove(string key)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(
                    SR2.GetString(SR2.UTDefaultValuesAreImmutable))); 
            }
            public bool Remove(KeyValuePair item) 
            { 
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(
                    SR2.GetString(SR2.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 emptyStringCollection = null;
            Dictionary defaultValues; // key is the variable name (in uppercase; as appear in the variable names lists) 
            int firstNullablePathVariable;
            List pathSegmentVariableNames; // ToUpperInvariant, in order they occur in the original template string
            ReadOnlyCollection pathSegmentVariableNamesSnapshot = null;
            List pathSegmentVariableNature; 
            List queryValueVariableNames; // ToUpperInvariant, in order they occur in the original template string
            ReadOnlyCollection queryValueVariableNamesSnapshot = null; 
 
            public VariablesCollection(UriTemplate owner)
            { 
                this.owner = owner;
                this.pathSegmentVariableNames = new List();
                this.pathSegmentVariableNature = new List();
                this.queryValueVariableNames = new List(); 
                this.firstNullablePathVariable = -1;
            } 
 
            public static ReadOnlyCollection EmptyCollection
            { 
                get
                {
                    if (emptyStringCollection == null)
                    { 
                        emptyStringCollection = new ReadOnlyCollection(new List());
                    } 
                    return emptyStringCollection; 
                }
            } 

            public Dictionary DefaultValues
            {
                get 
                {
                    return this.defaultValues; 
                } 
            }
            public ReadOnlyCollection PathSegmentVariableNames 
            {
                get
                {
                    if (this.pathSegmentVariableNamesSnapshot == null) 
                    {
                        this.pathSegmentVariableNamesSnapshot = new ReadOnlyCollection( 
                            this.pathSegmentVariableNames); 
                    }
                    return this.pathSegmentVariableNamesSnapshot; 
                }
            }
            public ReadOnlyCollection QueryValueVariableNames
            { 
                get
                { 
                    if (this.queryValueVariableNamesSnapshot == null) 
                    {
                        this.queryValueVariableNamesSnapshot = new ReadOnlyCollection( 
                            this.queryValueVariableNames);
                    }
                    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(
                        SR2.GetString(SR2.UTStarVariableWithDefaultsFromAdditionalDefaults, 
                        this.owner.originalTemplate, varName))); 
                }
                if (this.pathSegmentVariableNature[varIndex] != UriTemplatePartType.Variable) 
                {
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(
                        SR2.GetString(SR2.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(); 
                }
                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( 
                        SR2.GetString(SR2.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( 
                        SR2.GetString(SR2.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( 
                            SR2.GetString(SR2.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();
                    } 
                    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(
                        SR2.GetString(SR2.UTInvalidWildcardInVariableOrLiteral, this.owner.originalTemplate, UriTemplate.WildcardPath))); 
                }
                if (defaultValue != null) 
                { 
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(
                        SR2.GetString(SR2.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( 
                        SR2.GetString(SR2.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 parameters, bool omitDefaults)
            {
                if (parameters == null) 
                {
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("parameters"); 
                } 

                string[] normalizedParameters = PrepareNormalizedParameters(); 
                IDictionary 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 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(SR2.GetString( 
                        SR2.GetString(SR2.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( 
                                    SR2.GetString(SR2.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( 
                            SR2.GetString(SR2.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( 
                                SR2.GetString(SR2.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 extraParameters)
            {
                if (extraParameters == null) 
                {
                    extraParameters = this.owner.additionalDefaults; 
                } 
                else
                { 
                    foreach (KeyValuePair 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", 
                            SR2.GetString(SR2.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( 
                        SR2.GetString(SR2.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( 
                            SR2.GetString(SR2.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(
                                SR2.GetString(SR2.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 extraParameters) 
            { 
                if (string.IsNullOrEmpty(name))
                { 
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("parameters",
                        SR2.GetString(SR2.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(UriTemplateHelpers.GetQueryKeyComparer());
                }
                extraParameters.Add(name, value); 
            }
            void ProcessDefaultsAndCreateBindInfo(bool omitDefaults, string[] normalizedParameters, 
                IDictionary 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 extraParameters) 
            {
                if (extraParameters == null)
                {
                    return; 
                }
 
                foreach (KeyValuePair 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(
                        SR2.GetString(SR2.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 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()); 
                }
            } 
        } 
    }
} 

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
// Copyright (c) Microsoft Corporation. All rights reserved.
                        

Link Menu

Network programming in C#, Network Programming in VB.NET, Network Programming in .NET
This book is available now!
Buy at Amazon US or
Buy at Amazon UK