File: System\ServiceModel\Channels\UriPrefixTable.cs
Project: ndp\cdf\src\WCF\ServiceModel\System.ServiceModel.csproj (System.ServiceModel)
//----------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
//----------------------------------------------------------------------------
namespace System.ServiceModel.Channels
{
    using System.Collections.Generic;
    using System.Globalization;
    using System.Runtime;
    using System.Runtime.Collections;
    using System.ServiceModel;
 
    sealed class UriPrefixTable<TItem>
        where TItem : class
    {
        int count;
        const int HopperSize = 128;
        volatile HopperCache lookupCache; // cache matches, for lookup speed
 
        SegmentHierarchyNode<TItem> root;
        bool useWeakReferences;
        bool includePortInComparison;
 
        public UriPrefixTable()
            : this(false)
        {
        }
 
        public UriPrefixTable(bool includePortInComparison)
            : this(includePortInComparison, false)
        {
        }
 
        public UriPrefixTable(bool includePortInComparison, bool useWeakReferences)
        {
            this.includePortInComparison = includePortInComparison;
            this.useWeakReferences = useWeakReferences;
            this.root = new SegmentHierarchyNode<TItem>(null, useWeakReferences);
            this.lookupCache = new HopperCache(HopperSize, useWeakReferences);
        }
 
        internal UriPrefixTable(UriPrefixTable<TItem> objectToClone)
            : this(objectToClone.includePortInComparison, objectToClone.useWeakReferences)
        {
            if (objectToClone.Count > 0)
            {
                foreach (KeyValuePair<BaseUriWithWildcard, TItem> current in objectToClone.GetAll())
                {
                    this.RegisterUri(current.Key.BaseAddress, current.Key.HostNameComparisonMode, current.Value);
                }
            }
        }
 
        object ThisLock
        {
            get
            {
                // The UriPrefixTable instance itself is used as a 
                // synchronization primitive in the TransportManagers and the 
                // TransportManagerContainers so we return 'this' to keep them in sync.                 
                return this;
            }
        }
 
        public int Count
        {
            get
            {
                return this.count;
            }
        }
 
        public bool IsRegistered(BaseUriWithWildcard key)
        {
            Uri uri = key.BaseAddress;
 
            // don't need to normalize path since SegmentHierarchyNode is 
            // already OrdinalIgnoreCase
            string[] paths = UriSegmenter.ToPath(uri, key.HostNameComparisonMode, this.includePortInComparison);
            bool exactMatch;
            SegmentHierarchyNode<TItem> node;
            lock (ThisLock)
            {
                node = FindDataNode(paths, out exactMatch);
            }
            return exactMatch && node != null && node.Data != null;
        }
 
        public IEnumerable<KeyValuePair<BaseUriWithWildcard, TItem>> GetAll()
        {
            lock (ThisLock)
            {
                List<KeyValuePair<BaseUriWithWildcard, TItem>> result = new List<KeyValuePair<BaseUriWithWildcard, TItem>>();
                this.root.Collect(result);
                return result;
            }
        }
 
        bool TryCacheLookup(BaseUriWithWildcard key, out TItem item)
        {
            object value = this.lookupCache.GetValue(ThisLock, key);
 
            // We might return null and true in the case of DBNull (cached negative result).
            // When TItem is object, the cast isn't sufficient to ---- out DBNulls, so we need an explicit check.
            item = value == DBNull.Value ? null : (TItem)value;
            return value != null;
        }
 
        void AddToCache(BaseUriWithWildcard key, TItem item)
        {
            // Don't allow explicitly adding DBNulls.
            Fx.Assert(item != DBNull.Value, "Can't add DBNull to UriPrefixTable.");
 
            // HopperCache uses null as 'doesn't exist', so use DBNull as a stand-in for null.
            this.lookupCache.Add(key, item ?? (object)DBNull.Value);
        }
 
        void ClearCache()
        {
            this.lookupCache = new HopperCache(HopperSize, this.useWeakReferences);
        }
 
        public bool TryLookupUri(Uri uri, HostNameComparisonMode hostNameComparisonMode, out TItem item)
        {
            BaseUriWithWildcard key = new BaseUriWithWildcard(uri, hostNameComparisonMode);
            if (TryCacheLookup(key, out item))
            {
                return item != null;
            }
 
            lock (ThisLock)
            {
                // exact match failed, perform the full lookup (which will also
                // catch case-insensitive variations that aren't yet in our cache)
                bool dummy;
                SegmentHierarchyNode<TItem> node = FindDataNode(
                    UriSegmenter.ToPath(key.BaseAddress, hostNameComparisonMode, this.includePortInComparison), out dummy);
                if (node != null)
                {
                    item = node.Data;
                }
                // We want to cache both positive AND negative results
                AddToCache(key, item);
                return (item != null);
            }
        }
 
        public void RegisterUri(Uri uri, HostNameComparisonMode hostNameComparisonMode, TItem item)
        {
            Fx.Assert(HostNameComparisonModeHelper.IsDefined(hostNameComparisonMode), "RegisterUri: Invalid HostNameComparisonMode value passed in.");
 
            lock (ThisLock)
            {
                // Since every newly registered Uri could alter what Prefixes should have matched, we
                // should clear the cache of any existing results and start over
                ClearCache();
                BaseUriWithWildcard key = new BaseUriWithWildcard(uri, hostNameComparisonMode);
                SegmentHierarchyNode<TItem> node = FindOrCreateNode(key);
                if (node.Data != null)
                {
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(
                        SR.DuplicateRegistration, uri)));
                }
                node.SetData(item, key);
                count++;
            }
        }
 
        public void UnregisterUri(Uri uri, HostNameComparisonMode hostNameComparisonMode)
        {
            lock (ThisLock)
            {
                // Since every removed Uri could alter what Prefixes should have matched, we
                // should clear the cache of any existing results and start over
                ClearCache();
                string[] path = UriSegmenter.ToPath(uri, hostNameComparisonMode, this.includePortInComparison);
                // Never remove the root
                if (path.Length == 0)
                {
                    this.root.RemoveData();
                }
                else
                {
                    this.root.RemovePath(path, 0);
                }
                count--;
            }
        }
 
        SegmentHierarchyNode<TItem> FindDataNode(string[] path, out bool exactMatch)
        {
            Fx.Assert(path != null, "FindDataNode: path is null");
 
            exactMatch = false;
            SegmentHierarchyNode<TItem> current = this.root;
            SegmentHierarchyNode<TItem> result = null;
            for (int i = 0; i < path.Length; ++i)
            {
                SegmentHierarchyNode<TItem> next;
                if (!current.TryGetChild(path[i], out next))
                {
                    break;
                }
                else if (next.Data != null)
                {
                    result = next;
                    exactMatch = (i == path.Length - 1);
                }
                current = next;
            }
            return result;
        }
 
        SegmentHierarchyNode<TItem> FindOrCreateNode(BaseUriWithWildcard baseUri)
        {
            Fx.Assert(baseUri != null, "FindOrCreateNode: baseUri is null");
 
            string[] path = UriSegmenter.ToPath(baseUri.BaseAddress, baseUri.HostNameComparisonMode, this.includePortInComparison);
            SegmentHierarchyNode<TItem> current = this.root;
            for (int i = 0; i < path.Length; ++i)
            {
                SegmentHierarchyNode<TItem> next;
                if (!current.TryGetChild(path[i], out next))
                {
                    next = new SegmentHierarchyNode<TItem>(path[i], useWeakReferences);
                    current.SetChildNode(path[i], next);
                }
                current = next;
            }
            return current;
        }
 
        static class UriSegmenter
        {
            internal static string[] ToPath(Uri uriPath, HostNameComparisonMode hostNameComparisonMode,
                bool includePortInComparison)
            {
                if (null == uriPath)
                {
                    return new string[0];
                }
                UriSegmentEnum segmentEnum = new UriSegmentEnum(uriPath); // struct
                return segmentEnum.GetSegments(hostNameComparisonMode, includePortInComparison);
            }
 
            struct UriSegmentEnum
            {
                string segment;
                int segmentStartAt;
                int segmentLength;
                UriSegmentType type;
                Uri uri;
 
                internal UriSegmentEnum(Uri uri)
                {
                    Fx.Assert(null != uri, "UreSegmentEnum: null uri");
                    this.uri = uri;
                    this.type = UriSegmentType.Unknown;
                    this.segment = null;
                    this.segmentStartAt = 0;
                    this.segmentLength = 0;
                }
 
                void ClearSegment()
                {
                    this.type = UriSegmentType.None;
                    this.segment = string.Empty;
                    this.segmentStartAt = 0;
                    this.segmentLength = 0;
                }
 
                public string[] GetSegments(HostNameComparisonMode hostNameComparisonMode,
                    bool includePortInComparison)
                {
                    List<string> segments = new List<string>();
                    while (this.Next())
                    {
                        switch (this.type)
                        {
                            case UriSegmentType.Path:
                                segments.Add(this.segment.Substring(this.segmentStartAt, this.segmentLength));
                                break;
 
                            case UriSegmentType.Host:
                                if (hostNameComparisonMode == HostNameComparisonMode.StrongWildcard)
                                {
                                    segments.Add("+");
                                }
                                else if (hostNameComparisonMode == HostNameComparisonMode.Exact)
                                {
                                    segments.Add(this.segment);
                                }
                                else
                                {
                                    segments.Add("*");
                                }
                                break;
 
                            case UriSegmentType.Port:
                                if (includePortInComparison || hostNameComparisonMode == HostNameComparisonMode.Exact)
                                {
                                    segments.Add(this.segment);
                                }
                                break;
 
                            default:
                                segments.Add(this.segment);
                                break;
                        }
                    }
                    return segments.ToArray();
                }
 
                public bool Next()
                {
                    while (true)
                    {
                        switch (this.type)
                        {
                            case UriSegmentType.Unknown:
                                this.type = UriSegmentType.Scheme;
                                this.SetSegment(this.uri.Scheme);
                                return true;
 
                            case UriSegmentType.Scheme:
                                this.type = UriSegmentType.Host;
                                string host = this.uri.Host;
                                // The userName+password also accompany...
                                string userInfo = this.uri.UserInfo;
                                if (null != userInfo && userInfo.Length > 0)
                                {
                                    host = userInfo + '@' + host;
                                }
                                this.SetSegment(host);
                                return true;
 
                            case UriSegmentType.Host:
                                this.type = UriSegmentType.Port;
                                int port = this.uri.Port;
                                this.SetSegment(port.ToString(CultureInfo.InvariantCulture));
                                return true;
 
                            case UriSegmentType.Port:
                                this.type = UriSegmentType.Path;
                                string absPath = this.uri.AbsolutePath;
                                Fx.Assert(null != absPath, "Next: nill absPath");
                                if (0 == absPath.Length)
                                {
                                    this.ClearSegment();
                                    return false;
                                }
                                this.segment = absPath;
                                this.segmentStartAt = 0;
                                this.segmentLength = 0;
                                return this.NextPathSegment();
 
                            case UriSegmentType.Path:
                                return this.NextPathSegment();
 
                            case UriSegmentType.None:
                                return false;
 
                            default:
                                Fx.Assert("Next: unknown enum value");
                                return false;
                        }
                    }
                }
 
                public bool NextPathSegment()
                {
                    this.segmentStartAt += this.segmentLength;
                    while (this.segmentStartAt < this.segment.Length && this.segment[this.segmentStartAt] == '/')
                    {
                        this.segmentStartAt++;
                    }
 
                    if (this.segmentStartAt < this.segment.Length)
                    {
                        int next = this.segment.IndexOf('/', this.segmentStartAt);
                        if (-1 == next)
                        {
                            this.segmentLength = this.segment.Length - this.segmentStartAt;
                        }
                        else
                        {
                            this.segmentLength = next - this.segmentStartAt;
                        }
                        return true;
                    }
                    this.ClearSegment();
                    return false;
                }
 
                void SetSegment(string segment)
                {
                    this.segment = segment;
                    this.segmentStartAt = 0;
                    this.segmentLength = segment.Length;
                }
 
                enum UriSegmentType
                {
                    Unknown,
                    Scheme,
                    Host,
                    Port,
                    Path,
                    None
                }
            }
        }
    }
 
    class SegmentHierarchyNode<TData>
        where TData : class
    {
        BaseUriWithWildcard path;
        TData data;
        string name;
        Dictionary<string, SegmentHierarchyNode<TData>> children;
        WeakReference weakData;
        bool useWeakReferences;
 
        public SegmentHierarchyNode(string name, bool useWeakReferences)
        {
            this.name = name;
            this.useWeakReferences = useWeakReferences;
            this.children = new Dictionary<string, SegmentHierarchyNode<TData>>(StringComparer.OrdinalIgnoreCase);
        }
 
        public TData Data
        {
            get
            {
                if (useWeakReferences)
                {
                    if (this.weakData == null)
                    {
                        return null;
                    }
                    else
                    {
                        return this.weakData.Target as TData;
                    }
                }
                else
                {
                    return this.data;
                }
            }
        }
 
        public void SetData(TData data, BaseUriWithWildcard path)
        {
            this.path = path;
            if (useWeakReferences)
            {
                if (data == null)
                {
                    this.weakData = null;
                }
                else
                {
                    this.weakData = new WeakReference(data);
                }
            }
            else
            {
                this.data = data;
            }
        }
 
        public void SetChildNode(string name, SegmentHierarchyNode<TData> node)
        {
            this.children[name] = node;
        }
 
        public void Collect(List<KeyValuePair<BaseUriWithWildcard, TData>> result)
        {
            TData localData = this.Data;
            if (localData != null)
            {
                result.Add(new KeyValuePair<BaseUriWithWildcard, TData>(path, localData));
            }
 
            foreach (SegmentHierarchyNode<TData> child in this.children.Values)
            {
                child.Collect(result);
            }
        }
 
        public bool TryGetChild(string segment, out SegmentHierarchyNode<TData> value)
        {
            return children.TryGetValue(segment, out value);
        }
 
        public void RemoveData()
        {
            SetData(null, null);
        }
 
        // bool is whether to remove this node
        public bool RemovePath(string[] path, int seg)
        {
            if (seg == path.Length)
            {
                RemoveData();
                return this.children.Count == 0;
            }
 
            SegmentHierarchyNode<TData> node;
            if (!TryGetChild(path[seg], out node))
            {
                return (this.children.Count == 0 && Data == null);
            }
 
            if (node.RemovePath(path, seg + 1))
            {
                this.children.Remove(path[seg]);
                return (this.children.Count == 0 && Data == null);
            }
            else
            {
                return false;
            }
        }
    }
}