UriTemplateTable.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 / UriTemplateTable.cs / 2 / UriTemplateTable.cs

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

namespace System 
{
    using System.Collections.Generic; 
    using System.Collections.ObjectModel; 
    using System.Collections.Specialized;
    using System.Diagnostics; 
    using System.Diagnostics.CodeAnalysis;
    using System.ServiceModel;
    using System.ServiceModel.Web;
    using System.Web; 

    // this class is thread-safe 
    public class UriTemplateTable 
    {
        Uri baseAddress; 
        string basePath;
        Dictionary fastPathTable; // key is uri.PathAndQuery, fastPathTable may be null
        bool noTemplateHasQueryPart;
        int numSegmentsInBaseAddress; 
        Uri originalUncanonicalizedBaseAddress;
        UriTemplateTrieNode rootNode; 
        UriTemplatesCollection templates; 
        object thisLock;
 
        public UriTemplateTable()
            : this(null, null)
        {
        } 
        public UriTemplateTable(IEnumerable> keyValuePairs)
            : this(null, keyValuePairs) 
        { 
        }
        public UriTemplateTable(Uri baseAddress) 
            : this(baseAddress, null)
        {
        }
        public UriTemplateTable(Uri baseAddress, IEnumerable> keyValuePairs) 
        {
            if (baseAddress != null && !baseAddress.IsAbsoluteUri) 
            { 
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("baseAddress", SR2.GetString(
                    SR2.UTTMustBeAbsolute)); 
            }
            this.originalUncanonicalizedBaseAddress = baseAddress;
            if (keyValuePairs != null)
            { 
                this.templates = new UriTemplatesCollection(keyValuePairs);
            } 
            else 
            {
                this.templates = new UriTemplatesCollection(); 
            }
            this.thisLock = new object();
            this.baseAddress = baseAddress;
            NormalizeBaseAddress(); 
        }
 
        public Uri BaseAddress 
        {
            get 
            {
                return this.baseAddress;
            }
            set 
            {
                if (value == null) 
                { 
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("value");
                } 
                lock (this.thisLock)
                {
                    if (this.IsReadOnly)
                    { 
                        throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(
                            SR2.GetString(SR2.UTTCannotChangeBaseAddress))); 
                    } 
                    else
                    { 
                        if (!value.IsAbsoluteUri)
                        {
                            throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("value", SR2.GetString(
                                SR2.UTTBaseAddressMustBeAbsolute)); 
                        }
                        else 
                        { 
                            this.originalUncanonicalizedBaseAddress = value;
                            this.baseAddress = value; 
                            NormalizeBaseAddress();
                        }
                    }
                } 
            }
        } 
        public bool IsReadOnly 
        {
            get 
            {
                return this.templates.IsFrozen;
            }
        } 
        public IList> KeyValuePairs
        { 
            get 
            {
                return this.templates; 
            }
        }

        public void MakeReadOnly(bool allowDuplicateEquivalentUriTemplates) 
        {
            // idempotent 
            lock (this.thisLock) 
            {
                if (!this.IsReadOnly) 
                {
                    this.templates.Freeze();
                    Validate(allowDuplicateEquivalentUriTemplates);
                    ConstructFastPathTable(); 
                }
            } 
        } 
        public Collection Match(Uri uri)
        { 
            if (uri == null)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("uri");
            } 
            if (!uri.IsAbsoluteUri)
            { 
                return None(); 
            }
 
            this.MakeReadOnly(true);

            // Matching path :
            Collection relativeSegments; 
            IList candidates;
            if (!FastComputeRelativeSegmentsAndLookup(uri, out relativeSegments, out candidates)) 
            { 
                return None();
            } 
            // Matching query :
            NameValueCollection queryParameters = null;
            if (!this.noTemplateHasQueryPart && AtLeastOneCandidateHasQueryPart(candidates))
            { 
                Collection nextCandidates = new Collection();
                Fx.Assert(nextCandidates.Count == 0, "nextCandidates should be empty"); 
 
                // then deal with query
                queryParameters = UriTemplateHelpers.ParseQueryString(uri.Query); 
                bool mustBeEspeciallyInteresting = NoCandidateHasQueryLiteralRequirementsAndThereIsAnEmptyFallback(candidates);
                for (int i = 0; i < candidates.Count; i++)
                {
                    if (UriTemplateHelpers.CanMatchQueryInterestingly(candidates[i].Template, queryParameters, mustBeEspeciallyInteresting)) 
                    {
                        nextCandidates.Add(candidates[i]); 
                    } 
                }
                if (nextCandidates.Count > 1) 
                {
                    Fx.Assert(AllEquivalent(nextCandidates, 0, nextCandidates.Count), "demux algorithm problem, multiple non-equivalent matches");
                }
 
                if (nextCandidates.Count == 0)
                { 
                    for (int i = 0; i < candidates.Count; i++) 
                    {
                        if (UriTemplateHelpers.CanMatchQueryTrivially(candidates[i].Template)) 
                        {
                            nextCandidates.Add(candidates[i]);
                        }
                    } 
                }
                if (nextCandidates.Count == 0) 
                { 
                    return None();
                } 
                if (nextCandidates.Count > 1)
                {
                    Fx.Assert(AllEquivalent(nextCandidates, 0, nextCandidates.Count), "demux algorithm problem, multiple non-equivalent matches");
                } 

                candidates = nextCandidates; 
            } 
            // Verifying that we have not broken the allowDuplicates settings because of terminal defaults
            //  This situation can be caused when we are hosting ".../" and ".../{foo=xyz}" in the same 
            //  table. They are not equivalent; yet they reside together in the same path partially-equivalent
            //  set. If we hit a uri that ends up in that particular end-of-path set, we want to provide the
            //  user only the 'best' match and not both; thus preventing inconsistancy between the MakeReadonly
            //  settings and the matching results. We will assume that the 'best' matches will be the ones with 
            //  the smallest number of segments - this will prefer ".../" over ".../{x=1}[/...]".
            if (NotAllCandidatesArePathFullyEquivalent(candidates)) 
            { 
                Collection nextCandidates = new Collection();
                int minSegmentsCount = -1; 
                for (int i = 0; i < candidates.Count; i++)
                {
                    UriTemplateTableMatchCandidate candidate = candidates[i];
                    if (minSegmentsCount == -1) 
                    {
                        minSegmentsCount = candidate.Template.segments.Count; 
                        nextCandidates.Add(candidate); 
                    }
                    else if (candidate.Template.segments.Count < minSegmentsCount) 
                    {
                        minSegmentsCount = candidate.Template.segments.Count;
                        nextCandidates.Clear();
                        nextCandidates.Add(candidate); 
                    }
                    else if (candidate.Template.segments.Count == minSegmentsCount) 
                    { 
                        nextCandidates.Add(candidate);
                    } 
                }
                Fx.Assert(minSegmentsCount != -1, "At least the first entry in the list should be kept");
                Fx.Assert(nextCandidates.Count >= 1, "At least the first entry in the list should be kept");
                Fx.Assert(nextCandidates[0].Template.segments.Count == minSegmentsCount, "Trivial"); 
                candidates = nextCandidates;
            } 
 
            // Building the actual result
            Collection actualResults = new Collection(); 
            for (int i = 0; i < candidates.Count; i++)
            {
                UriTemplateTableMatchCandidate candidate = candidates[i];
                UriTemplateMatch match = candidate.Template.CreateUriTemplateMatch(this.originalUncanonicalizedBaseAddress, 
                    uri, candidate.Data, candidate.SegmentsCount, relativeSegments, queryParameters);
                actualResults.Add(match); 
            } 
            return actualResults;
        } 
        public UriTemplateMatch MatchSingle(Uri uri)
        {
            Collection c = this.Match(uri);
            if (c.Count == 0) 
            {
                return null; 
            } 
            if (c.Count == 1)
            { 
                return c[0];
            }
            throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new UriTemplateMatchException(SR2.GetString(
                SR2.UTTMultipleMatches))); 
        }
 
        [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "This method is called within a Debug assert")] 
        static bool AllEquivalent(IList list, int a, int b)
        { 
            for (int i = a; i < b - 1; ++i)
            {
                if (!list[i].Template.IsPathPartiallyEquivalentAt(list[i + 1].Template, list[i].SegmentsCount))
                { 
                    return false;
                } 
                if (!list[i].Template.IsQueryEquivalent(list[i + 1].Template)) 
                {
                    return false; 
                }
            }
            return true;
        } 

        static bool AtLeastOneCandidateHasQueryPart(IList candidates) 
        { 
            for (int i = 0; i < candidates.Count; i++)
            { 
                if (!UriTemplateHelpers.CanMatchQueryTrivially(candidates[i].Template))
                {
                    return true;
                } 
            }
            return false; 
        } 
        static bool NoCandidateHasQueryLiteralRequirementsAndThereIsAnEmptyFallback(
            IList candidates) 
        {
            bool thereIsAmEmptyFallback = false;
            for (int i = 0; i < candidates.Count; i++)
            { 
                if (UriTemplateHelpers.HasQueryLiteralRequirements(candidates[i].Template))
                { 
                    return false; 
                }
                if (candidates[i].Template.queries.Count == 0) 
                {
                    thereIsAmEmptyFallback = true;
                }
            } 
            return thereIsAmEmptyFallback;
        } 
 
        static Collection None()
        { 
            return new Collection();
        }
        static bool NotAllCandidatesArePathFullyEquivalent(IList candidates)
        { 
            if (candidates.Count <= 1)
            { 
                return false; 
            }
 
            int segmentsCount = -1;
            for (int i = 0; i < candidates.Count; i++)
            {
                if (segmentsCount == -1) 
                {
                    segmentsCount = candidates[i].Template.segments.Count; 
                } 
                else if (segmentsCount != candidates[i].Template.segments.Count)
                { 
                    return true;
                }
            }
            return false; 
        }
 
        bool ComputeRelativeSegmentsAndLookup(Uri uri, 
            ICollection relativePathSegments, // add to this
            ICollection candidates) // matched candidates 
        {
            string[] uriSegments = uri.Segments;
            int numRelativeSegments = uriSegments.Length - this.numSegmentsInBaseAddress;
            Fx.Assert(numRelativeSegments >= 0, "bad num segments"); 
            UriTemplateLiteralPathSegment[] uSegments = new UriTemplateLiteralPathSegment[numRelativeSegments];
            for (int i = 0; i < numRelativeSegments; ++i) 
            { 
                string seg = uriSegments[i + this.numSegmentsInBaseAddress];
                // compute representation for matching 
                UriTemplateLiteralPathSegment lps = UriTemplateLiteralPathSegment.CreateFromWireData(seg);
                uSegments[i] = lps;
                // compute representation to project out into results
                string relPathSeg = Uri.UnescapeDataString(seg); 
                if (lps.EndsWithSlash)
                { 
                    Fx.Assert(relPathSeg.EndsWith("/", StringComparison.Ordinal), "problem with relative path segment"); 
                    relPathSeg = relPathSeg.Substring(0, relPathSeg.Length - 1); // trim slash
                } 
                relativePathSegments.Add(relPathSeg);
            }
            return rootNode.Match(uSegments, candidates);
        } 
        void ConstructFastPathTable()
        { 
            this.noTemplateHasQueryPart = true; 
            foreach (KeyValuePair kvp in this.templates)
            { 
                UriTemplate ut = kvp.Key;
                if (!UriTemplateHelpers.CanMatchQueryTrivially(ut))
                {
                    this.noTemplateHasQueryPart = false; 
                }
                if (ut.HasNoVariables && !ut.HasWildcard) 
                { 
                    // eligible for fast path
                    if (this.fastPathTable == null) 
                    {
                        this.fastPathTable = new Dictionary();
                    }
                    Uri uri = ut.BindByPosition(this.originalUncanonicalizedBaseAddress); 
                    string uriPath = UriTemplateHelpers.GetUriPath(uri);
                    if (this.fastPathTable.ContainsKey(uriPath)) 
                    { 
                        // nothing to do, we've already seen it
                    } 
                    else
                    {
                        FastPathInfo fpInfo = new FastPathInfo();
                        if (ComputeRelativeSegmentsAndLookup(uri, fpInfo.RelativePathSegments, 
                            fpInfo.Candidates))
                        { 
                            fpInfo.Freeze(); 
                            this.fastPathTable.Add(uriPath, fpInfo);
                        } 
                    }
                }
            }
        } 
        // this method checks the literal cache for a match if none, goes through the slower path of cracking the segments
        bool FastComputeRelativeSegmentsAndLookup(Uri uri, out Collection relativePathSegments, 
            out IList candidates) 
        {
            // Consider fast-path and lookup 
            // return false if not under base uri
            string uriPath = UriTemplateHelpers.GetUriPath(uri);
            FastPathInfo fpInfo = null;
            if ((this.fastPathTable != null) && this.fastPathTable.TryGetValue(uriPath, out fpInfo)) 
            {
                relativePathSegments = fpInfo.RelativePathSegments; 
                candidates = fpInfo.Candidates; 
                VerifyThatFastPathAndSlowPathHaveSameResults(uri, relativePathSegments, candidates);
                return true; 
            }
            else
            {
                relativePathSegments = new Collection(); 
                candidates = new Collection();
                return SlowComputeRelativeSegmentsAndLookup(uri, uriPath, relativePathSegments, candidates); 
            } 
        }
 
        void NormalizeBaseAddress()
        {
            if (this.baseAddress != null)
            { 
                // ensure trailing slash on baseAddress, so that IsBaseOf will work later
                UriBuilder ub = new UriBuilder(this.baseAddress); 
                if (!ub.Path.EndsWith("/", StringComparison.Ordinal)) 
                {
                    ub.Path = ub.Path + "/"; 
                }
                ub.Host = "localhost"; // always normalize to localhost
                ub.Port = -1;
                ub.UserName = null; 
                ub.Password = null;
                ub.Path = ub.Path.ToUpperInvariant(); 
                ub.Scheme = Uri.UriSchemeHttp; 
                this.baseAddress = ub.Uri;
                basePath = UriTemplateHelpers.GetUriPath(this.baseAddress); 
            }
        }
        bool SlowComputeRelativeSegmentsAndLookup(Uri uri, string uriPath, Collection relativePathSegments,
            ICollection candidates) 
        {
            // ensure 'under' the base address 
            if (uriPath.Length < basePath.Length) 
            {
                return false; 
            }
            if (!uriPath.StartsWith(basePath, StringComparison.OrdinalIgnoreCase))
            {
                return false; 
            }
            return ComputeRelativeSegmentsAndLookup(uri, relativePathSegments, candidates); 
        } 

        void Validate(bool allowDuplicateEquivalentUriTemplates) 
        {
            if (this.baseAddress == null)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR2.GetString( 
                    SR2.UTTBaseAddressNotSet)));
            } 
            this.numSegmentsInBaseAddress = this.baseAddress.Segments.Length; 
            if (this.templates.Count == 0)
            { 
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR2.GetString(
                    SR2.UTTEmptyKeyValuePairs)));
            }
            // build the trie and 
            // validate that forall Uri u, at most one UriTemplate is a best match for u
            rootNode = UriTemplateTrieNode.Make(this.templates, allowDuplicateEquivalentUriTemplates); 
        } 
        [Conditional("DEBUG")]
        void VerifyThatFastPathAndSlowPathHaveSameResults(Uri uri, Collection fastPathRelativePathSegments, 
            IList fastPathCandidates)
        {
            Collection slowPathRelativePathSegments = new Collection();
            List slowPathCandidates = new List(); 
            if (!SlowComputeRelativeSegmentsAndLookup(uri, UriTemplateHelpers.GetUriPath(uri),
                slowPathRelativePathSegments, slowPathCandidates)) 
            { 
                Fx.Assert("fast path yielded a result but slow path yielded no result");
            } 
            // compare results
            if (fastPathRelativePathSegments.Count != slowPathRelativePathSegments.Count)
            {
                Fx.Assert("fast path yielded different number of segments from slow path"); 
            }
            for (int i = 0; i < fastPathRelativePathSegments.Count; ++i) 
            { 
                if (fastPathRelativePathSegments[i] != slowPathRelativePathSegments[i])
                { 
                    Fx.Assert("fast path yielded different segments from slow path");
                }
            }
            if (fastPathCandidates.Count != slowPathCandidates.Count) 
            {
                Fx.Assert("fast path yielded different number of candidates from slow path"); 
            } 
            for (int i = 0; i < fastPathCandidates.Count; i++)
            { 
                if (!slowPathCandidates.Contains(fastPathCandidates[i]))
                {
                    Fx.Assert("fast path yielded different candidates from slow path");
                } 
            }
        } 
 
        class FastPathInfo
        { 
            FreezableCollection candidates;
            FreezableCollection relativePathSegments;

            public FastPathInfo() 
            {
                this.relativePathSegments = new FreezableCollection(); 
                this.candidates = new FreezableCollection(); 
            }
            public Collection Candidates 
            {
                get
                {
                    return this.candidates; 
                }
            } 
 
            public Collection RelativePathSegments
            { 
                get
                {
                    return this.relativePathSegments;
                } 
            }
 
            public void Freeze() 
            {
                this.relativePathSegments.Freeze(); 
                this.candidates.Freeze();
            }
        }
 
        class UriTemplatesCollection : FreezableCollection>
        { 
            public UriTemplatesCollection() 
                : base()
            { 
            }
            [SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors", Justification = "This is a private class; virtual methods cannot be overriden")]
            public UriTemplatesCollection(IEnumerable> keyValuePairs)
                : base() 
            {
                foreach (KeyValuePair kvp in keyValuePairs) 
                { 
                    ThrowIfInvalid(kvp.Key, "keyValuePairs");
                    base.Add(kvp); 
                }
            }

            protected override void InsertItem(int index, KeyValuePair item) 
            {
                ThrowIfInvalid(item.Key, "item"); 
                base.InsertItem(index, item); 
            }
            protected override void SetItem(int index, KeyValuePair item) 
            {
                ThrowIfInvalid(item.Key, "item");
                base.SetItem(index, item);
            } 

            static void ThrowIfInvalid(UriTemplate template, string argName) 
            { 
                if (template == null)
                { 
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument(argName,
                        SR2.GetString(SR2.UTTNullTemplateKey));
                }
                if (template.IgnoreTrailingSlash) 
                {
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument(argName, 
                        SR2.GetString(SR2.UTTInvalidTemplateKey, template)); 
                }
            } 
        }
    }
}

// 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