< Summary - CoreWCF Coverage — PR #1766

Information
Class: CoreWCF.Channels.UriPrefixTable<T>
Assembly: CoreWCF.NetFramingBase
File(s): /home/runner/work/CoreWCF/CoreWCF/src/Common/src/CoreWCF/Channels/UriPrefixTable.cs
Line coverage
78%
Covered lines: 125
Uncovered lines: 35
Coverable lines: 160
Total lines: 523
Line coverage: 78.1%
Branch coverage
74%
Covered branches: 50
Total branches: 67
Branch coverage: 74.6%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.ctor()100%11100%
.ctor(...)100%11100%
.ctor(...)100%11100%
.ctor(...)0%440%
IsRegistered(...)0%440%
GetAll()100%110%
TryCacheLookup(...)50%22100%
AddToCache(...)100%22100%
ClearCache()100%11100%
TryLookupUri(...)100%44100%
RegisterUri(...)50%2281.81%
UnregisterUri(...)0%220%
FindDataNode(...)100%66100%
FindOrCreateNode(...)100%44100%
GetEnumerator()100%110%
System.Collections.IEnumerable.GetEnumerator()100%110%
ToPath(...)50%2275%
.ctor(...)100%11100%
ClearSegment()100%11100%
GetSegments(...)100%1414100%
Next()69.23%131381.48%
NextPathSegment()100%88100%
SetSegment(...)100%11100%

File(s)

/home/runner/work/CoreWCF/CoreWCF/src/Common/src/CoreWCF/Channels/UriPrefixTable.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System;
 5using System.Collections;
 6using System.Collections.Generic;
 7using System.Globalization;
 8using System.Linq;
 9using CoreWCF.Runtime;
 10using CoreWCF.Runtime.Collections;
 11
 12namespace CoreWCF.Channels
 13{
 14    internal sealed class UriPrefixTable<TItem> : IEnumerable<KeyValuePair<BaseUriWithWildcard, TItem>>
 15        where TItem : class
 16    {
 17        private const int HopperSize = 128;
 18        private volatile HopperCache _lookupCache; // cache matches, for lookup speed
 19        private readonly SegmentHierarchyNode<TItem> _root;
 20        private readonly bool _useWeakReferences;
 21        private readonly bool _includePortInComparison;
 22
 23        public UriPrefixTable()
 8324            : this(false)
 25        {
 8326        }
 27
 28        public UriPrefixTable(bool includePortInComparison)
 8329            : this(includePortInComparison, false)
 30        {
 8331        }
 32
 8333        public UriPrefixTable(bool includePortInComparison, bool useWeakReferences)
 34        {
 8335            _includePortInComparison = includePortInComparison;
 8336            _useWeakReferences = useWeakReferences;
 8337            _root = new SegmentHierarchyNode<TItem>(null, useWeakReferences);
 8338            _lookupCache = new HopperCache(HopperSize, useWeakReferences);
 8339        }
 40
 41        internal UriPrefixTable(UriPrefixTable<TItem> objectToClone)
 042            : this(objectToClone._includePortInComparison, objectToClone._useWeakReferences)
 43        {
 044            if (objectToClone.Count > 0)
 45            {
 046                foreach (KeyValuePair<BaseUriWithWildcard, TItem> current in objectToClone.GetAll())
 47                {
 048                    RegisterUri(current.Key.BaseAddress, current.Key.HostNameComparisonMode, current.Value);
 49                }
 50            }
 051        }
 52
 53        private object ThisLock
 54        {
 55            get
 56            {
 57                // The UriPrefixTable instance itself is used as a
 58                // synchronization primitive in the TransportManagers and the
 59                // TransportManagerContainers so we return 'this' to keep them in sync.
 11760                return this;
 61            }
 62        }
 63
 21664        public int Count { get; private set; }
 65
 27166        public AsyncLock AsyncLock { get; } = new AsyncLock();
 67
 68        public bool IsRegistered(BaseUriWithWildcard key)
 69        {
 070            Uri uri = key.BaseAddress;
 71
 72            // don't need to normalize path since SegmentHierarchyNode is
 73            // already OrdinalIgnoreCase
 074            string[] paths = UriSegmenter.ToPath(uri, key.HostNameComparisonMode, _includePortInComparison);
 75            bool exactMatch;
 76            SegmentHierarchyNode<TItem> node;
 077            using (AsyncLock.TakeLock())
 78            {
 079                node = FindDataNode(paths, out exactMatch);
 080            }
 081            return exactMatch && node != null && node.Data != null;
 82        }
 83
 84        public IEnumerable<KeyValuePair<BaseUriWithWildcard, TItem>> GetAll()
 85        {
 086            using (AsyncLock.TakeLock())
 87            {
 088                List<KeyValuePair<BaseUriWithWildcard, TItem>> result = new List<KeyValuePair<BaseUriWithWildcard, TItem
 089                _root.Collect(result);
 090                return result;
 91            }
 092        }
 93
 94        private bool TryCacheLookup(BaseUriWithWildcard key, out TItem item)
 95        {
 11796            object value = _lookupCache.GetValue(ThisLock, key);
 97
 98            // We might return null and true in the case of DBNull (cached negative result).
 99            // When TItem is object, the cast isn't sufficient to weed out DBNulls, so we need an explicit check.
 117100            item = value == DBNull.Value ? null : (TItem)value;
 117101            return value != null;
 102        }
 103
 104        private void AddToCache(BaseUriWithWildcard key, TItem item)
 105        {
 106            // Don't allow explicitly adding DBNulls.
 107            Fx.Assert(item != DBNull.Value, "Can't add DBNull to UriPrefixTable.");
 108
 109            // HopperCache uses null as 'doesn't exist', so use DBNull as a stand-in for null.
 80110            _lookupCache.Add(key, item ?? (object)DBNull.Value);
 80111        }
 112
 113        private void ClearCache()
 114        {
 108115            _lookupCache = new HopperCache(HopperSize, _useWeakReferences);
 108116        }
 117
 118        public bool TryLookupUri(Uri uri, HostNameComparisonMode hostNameComparisonMode, out TItem item)
 119        {
 117120            BaseUriWithWildcard key = new BaseUriWithWildcard(uri, hostNameComparisonMode);
 117121            if (TryCacheLookup(key, out item))
 122            {
 37123                return item != null;
 124            }
 125
 80126            using (AsyncLock.TakeLock())
 127            {
 128                // exact match failed, perform the full lookup (which will also
 129                // catch case-insensitive variations that aren't yet in our cache)
 80130                SegmentHierarchyNode<TItem> node = FindDataNode(
 80131                    UriSegmenter.ToPath(key.BaseAddress, hostNameComparisonMode, _includePortInComparison), out bool dum
 80132                if (node != null)
 133                {
 77134                    item = node.Data;
 135                }
 136                // We want to cache both positive AND negative results
 80137                AddToCache(key, item);
 80138                return (item != null);
 139            }
 80140        }
 141
 142        public void RegisterUri(Uri uri, HostNameComparisonMode hostNameComparisonMode, TItem item)
 143        {
 144            Fx.Assert(HostNameComparisonModeHelper.IsDefined(hostNameComparisonMode), "RegisterUri: Invalid HostNameComp
 145
 108146            using (AsyncLock.TakeLock())
 147            {
 148                // Since every newly registered Uri could alter what Prefixes should have matched, we
 149                // should clear the cache of any existing results and start over
 108150                ClearCache();
 108151                BaseUriWithWildcard key = new BaseUriWithWildcard(uri, hostNameComparisonMode);
 108152                SegmentHierarchyNode<TItem> node = FindOrCreateNode(key);
 108153                if (node.Data != null)
 154                {
 0155                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SRCommon.For
 0156                        SRCommon.DuplicateRegistration, uri)));
 157                }
 108158                node.SetData(item, key);
 108159                Count++;
 108160            }
 108161        }
 162
 163        public void UnregisterUri(Uri uri, HostNameComparisonMode hostNameComparisonMode)
 164        {
 0165            using (AsyncLock.TakeLock())
 166            {
 167                // Since every removed Uri could alter what Prefixes should have matched, we
 168                // should clear the cache of any existing results and start over
 0169                ClearCache();
 0170                string[] path = UriSegmenter.ToPath(uri, hostNameComparisonMode, _includePortInComparison);
 171                // Never remove the root
 0172                if (path.Length == 0)
 173                {
 0174                    _root.RemoveData();
 175                }
 176                else
 177                {
 0178                    _root.RemovePath(path, 0);
 179                }
 0180                Count--;
 0181            }
 0182        }
 183
 184        private SegmentHierarchyNode<TItem> FindDataNode(string[] path, out bool exactMatch)
 185        {
 186            Fx.Assert(path != null, "FindDataNode: path is null");
 187
 80188            exactMatch = false;
 80189            SegmentHierarchyNode<TItem> current = _root;
 80190            SegmentHierarchyNode<TItem> result = null;
 718191            for (int i = 0; i < path.Length; ++i)
 192            {
 282193                if (!current.TryGetChild(path[i], out SegmentHierarchyNode<TItem> next))
 194                {
 195                    break;
 196                }
 279197                else if (next.Data != null)
 198                {
 77199                    result = next;
 77200                    exactMatch = (i == path.Length - 1);
 201                }
 279202                current = next;
 203            }
 80204            return result;
 205        }
 206
 207        private SegmentHierarchyNode<TItem> FindOrCreateNode(BaseUriWithWildcard baseUri)
 208        {
 209            Fx.Assert(baseUri != null, "FindOrCreateNode: baseUri is null");
 210
 108211            string[] path = UriSegmenter.ToPath(baseUri.BaseAddress, baseUri.HostNameComparisonMode, _includePortInCompa
 108212            SegmentHierarchyNode<TItem> current = _root;
 1008213            for (int i = 0; i < path.Length; ++i)
 214            {
 396215                if (!current.TryGetChild(path[i], out SegmentHierarchyNode<TItem> next))
 216                {
 321217                    next = new SegmentHierarchyNode<TItem>(path[i], _useWeakReferences);
 321218                    current.SetChildNode(path[i], next);
 219                }
 396220                current = next;
 221            }
 108222            return current;
 223        }
 224
 0225        public IEnumerator<KeyValuePair<BaseUriWithWildcard, TItem>> GetEnumerator() => GetAll().GetEnumerator();
 0226        IEnumerator IEnumerable.GetEnumerator() => GetAll().GetEnumerator();
 227
 228        private static class UriSegmenter
 229        {
 230            internal static string[] ToPath(Uri uriPath, HostNameComparisonMode hostNameComparisonMode,
 231                bool includePortInComparison)
 232            {
 188233                if (null == uriPath)
 234                {
 0235                    return Array.Empty<string>();
 236                }
 188237                UriSegmentEnum segmentEnum = new UriSegmentEnum(uriPath); // struct
 188238                return segmentEnum.GetSegments(hostNameComparisonMode, includePortInComparison);
 239            }
 240
 241            private struct UriSegmentEnum
 242            {
 243                private string _segment;
 244                private int _segmentStartAt;
 245                private int _segmentLength;
 246                private UriSegmentType _type;
 247                private readonly Uri _uri;
 248
 249                internal UriSegmentEnum(Uri uri)
 250                {
 251                    Fx.Assert(null != uri, "UreSegmentEnum: null uri");
 188252                    _uri = uri;
 188253                    _type = UriSegmentType.Unknown;
 188254                    _segment = null;
 188255                    _segmentStartAt = 0;
 188256                    _segmentLength = 0;
 188257                }
 258
 259                private void ClearSegment()
 260                {
 188261                    _type = UriSegmentType.None;
 188262                    _segment = string.Empty;
 188263                    _segmentStartAt = 0;
 188264                    _segmentLength = 0;
 188265                }
 266
 267                public string[] GetSegments(HostNameComparisonMode hostNameComparisonMode,
 268                    bool includePortInComparison)
 269                {
 188270                    List<string> segments = new List<string>();
 1058271                    while (Next())
 272                    {
 870273                        switch (_type)
 274                        {
 275                            case UriSegmentType.Path:
 306276                                segments.Add(_segment.Substring(_segmentStartAt, _segmentLength));
 306277                                break;
 278
 279                            case UriSegmentType.Host:
 188280                                if (hostNameComparisonMode == HostNameComparisonMode.StrongWildcard)
 281                                {
 186282                                    segments.Add("+");
 283                                }
 2284                                else if (hostNameComparisonMode == HostNameComparisonMode.Exact)
 285                                {
 1286                                    segments.Add(_segment);
 287                                }
 288                                else
 289                                {
 1290                                    segments.Add("*");
 291                                }
 1292                                break;
 293
 294                            case UriSegmentType.Port:
 188295                                if (includePortInComparison || hostNameComparisonMode == HostNameComparisonMode.Exact)
 296                                {
 1297                                    segments.Add(_segment);
 298                                }
 1299                                break;
 300
 301                            default:
 188302                                segments.Add(_segment);
 303                                break;
 304                        }
 305                    }
 188306                    return segments.ToArray();
 307                }
 308
 309                public bool Next()
 310                {
 311                    while (true)
 312                    {
 1058313                        switch (_type)
 314                        {
 315                            case UriSegmentType.Unknown:
 188316                                _type = UriSegmentType.Scheme;
 188317                                SetSegment(_uri.Scheme);
 188318                                return true;
 319
 320                            case UriSegmentType.Scheme:
 188321                                _type = UriSegmentType.Host;
 188322                                string host = _uri.Host;
 323                                // The userName+password also accompany...
 188324                                string userInfo = _uri.UserInfo;
 188325                                if (null != userInfo && userInfo.Length > 0)
 326                                {
 0327                                    host = userInfo + '@' + host;
 328                                }
 188329                                SetSegment(host);
 188330                                return true;
 331
 332                            case UriSegmentType.Host:
 188333                                _type = UriSegmentType.Port;
 188334                                int port = _uri.Port;
 188335                                SetSegment(port.ToString(CultureInfo.InvariantCulture));
 188336                                return true;
 337
 338                            case UriSegmentType.Port:
 188339                                _type = UriSegmentType.Path;
 188340                                string absPath = _uri.AbsolutePath;
 341                                Fx.Assert(null != absPath, "Next: nill absPath");
 188342                                if (0 == absPath.Length)
 343                                {
 0344                                    ClearSegment();
 0345                                    return false;
 346                                }
 188347                                _segment = absPath;
 188348                                _segmentStartAt = 0;
 188349                                _segmentLength = 0;
 188350                                return NextPathSegment();
 351
 352                            case UriSegmentType.Path:
 306353                                return NextPathSegment();
 354
 355                            case UriSegmentType.None:
 0356                                return false;
 357
 358                            default:
 359                                Fx.Assert("Next: unknown enum value");
 0360                                return false;
 361                        }
 362                    }
 363                }
 364
 365                public bool NextPathSegment()
 366                {
 494367                    _segmentStartAt += _segmentLength;
 810368                    while (_segmentStartAt < _segment.Length && _segment[_segmentStartAt] == '/')
 369                    {
 316370                        _segmentStartAt++;
 371                    }
 372
 494373                    if (_segmentStartAt < _segment.Length)
 374                    {
 306375                        int next = _segment.IndexOf('/', _segmentStartAt);
 306376                        if (-1 == next)
 377                        {
 178378                            _segmentLength = _segment.Length - _segmentStartAt;
 379                        }
 380                        else
 381                        {
 128382                            _segmentLength = next - _segmentStartAt;
 383                        }
 306384                        return true;
 385                    }
 188386                    ClearSegment();
 188387                    return false;
 388                }
 389
 390                private void SetSegment(string segment)
 391                {
 564392                    _segment = segment;
 564393                    _segmentStartAt = 0;
 564394                    _segmentLength = segment.Length;
 564395                }
 396
 397                private enum UriSegmentType
 398                {
 399                    Unknown,
 400                    Scheme,
 401                    Host,
 402                    Port,
 403                    Path,
 404                    None
 405                }
 406            }
 407        }
 408    }
 409
 410    internal class SegmentHierarchyNode<TData>
 411        where TData : class
 412    {
 413        private BaseUriWithWildcard _path;
 414        private TData _data;
 415        private readonly string _name;
 416        private readonly Dictionary<string, SegmentHierarchyNode<TData>> _children;
 417        private WeakReference _weakData;
 418        private readonly bool _useWeakReferences;
 419
 420        public SegmentHierarchyNode(string name, bool useWeakReferences)
 421        {
 422            _name = name;
 423            _useWeakReferences = useWeakReferences;
 424            _children = new Dictionary<string, SegmentHierarchyNode<TData>>(StringComparer.OrdinalIgnoreCase);
 425        }
 426
 427        public TData Data
 428        {
 429            get
 430            {
 431                if (_useWeakReferences)
 432                {
 433                    if (_weakData == null)
 434                    {
 435                        return null;
 436                    }
 437                    else
 438                    {
 439                        return _weakData.Target as TData;
 440                    }
 441                }
 442                else
 443                {
 444                    return _data;
 445                }
 446            }
 447        }
 448
 449        public void SetData(TData data, BaseUriWithWildcard path)
 450        {
 451            _path = path;
 452            if (_useWeakReferences)
 453            {
 454                if (data == null)
 455                {
 456                    _weakData = null;
 457                }
 458                else
 459                {
 460                    _weakData = new WeakReference(data);
 461                }
 462            }
 463            else
 464            {
 465                _data = data;
 466            }
 467        }
 468
 469        public void SetChildNode(string name, SegmentHierarchyNode<TData> node)
 470        {
 471            _children[name] = node;
 472        }
 473
 474        public void Collect(List<KeyValuePair<BaseUriWithWildcard, TData>> result)
 475        {
 476            TData localData = Data;
 477            if (localData != null)
 478            {
 479                result.Add(new KeyValuePair<BaseUriWithWildcard, TData>(_path, localData));
 480            }
 481
 482            foreach (SegmentHierarchyNode<TData> child in _children.Values)
 483            {
 484                child.Collect(result);
 485            }
 486        }
 487
 488        public bool TryGetChild(string segment, out SegmentHierarchyNode<TData> value)
 489        {
 490            return _children.TryGetValue(segment, out value);
 491        }
 492
 493        public void RemoveData()
 494        {
 495            SetData(null, null);
 496        }
 497
 498        // bool is whether to remove this node
 499        public bool RemovePath(string[] path, int seg)
 500        {
 501            if (seg == path.Length)
 502            {
 503                RemoveData();
 504                return _children.Count == 0;
 505            }
 506
 507            if (!TryGetChild(path[seg], out SegmentHierarchyNode<TData> node))
 508            {
 509                return (_children.Count == 0 && Data == null);
 510            }
 511
 512            if (node.RemovePath(path, seg + 1))
 513            {
 514                _children.Remove(path[seg]);
 515                return (_children.Count == 0 && Data == null);
 516            }
 517            else
 518            {
 519                return false;
 520            }
 521        }
 522    }
 523}