< Summary - CoreWCF Coverage — PR #1766

Information
Class: CoreWCF.Channels.UriPrefixTable<T>
Assembly: CoreWCF.WebHttp
File(s): /home/runner/work/CoreWCF/CoreWCF/src/Common/src/CoreWCF/Channels/UriPrefixTable.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 160
Coverable lines: 160
Total lines: 523
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 67
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.ctor()100%110%
.ctor(...)100%110%
.ctor(...)100%110%
.ctor(...)0%440%
IsRegistered(...)0%440%
GetAll()100%110%
TryCacheLookup(...)0%220%
AddToCache(...)0%220%
ClearCache()100%110%
TryLookupUri(...)0%440%
RegisterUri(...)0%220%
UnregisterUri(...)0%220%
FindDataNode(...)0%660%
FindOrCreateNode(...)0%440%
GetEnumerator()100%110%
System.Collections.IEnumerable.GetEnumerator()100%110%
ToPath(...)0%220%
.ctor(...)100%110%
ClearSegment()100%110%
GetSegments(...)0%14140%
Next()0%13130%
NextPathSegment()0%880%
SetSegment(...)100%110%

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()
 024            : this(false)
 25        {
 026        }
 27
 28        public UriPrefixTable(bool includePortInComparison)
 029            : this(includePortInComparison, false)
 30        {
 031        }
 32
 033        public UriPrefixTable(bool includePortInComparison, bool useWeakReferences)
 34        {
 035            _includePortInComparison = includePortInComparison;
 036            _useWeakReferences = useWeakReferences;
 037            _root = new SegmentHierarchyNode<TItem>(null, useWeakReferences);
 038            _lookupCache = new HopperCache(HopperSize, useWeakReferences);
 039        }
 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.
 060                return this;
 61            }
 62        }
 63
 064        public int Count { get; private set; }
 65
 066        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        {
 096            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.
 0100            item = value == DBNull.Value ? null : (TItem)value;
 0101            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.
 0110            _lookupCache.Add(key, item ?? (object)DBNull.Value);
 0111        }
 112
 113        private void ClearCache()
 114        {
 0115            _lookupCache = new HopperCache(HopperSize, _useWeakReferences);
 0116        }
 117
 118        public bool TryLookupUri(Uri uri, HostNameComparisonMode hostNameComparisonMode, out TItem item)
 119        {
 0120            BaseUriWithWildcard key = new BaseUriWithWildcard(uri, hostNameComparisonMode);
 0121            if (TryCacheLookup(key, out item))
 122            {
 0123                return item != null;
 124            }
 125
 0126            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)
 0130                SegmentHierarchyNode<TItem> node = FindDataNode(
 0131                    UriSegmenter.ToPath(key.BaseAddress, hostNameComparisonMode, _includePortInComparison), out bool dum
 0132                if (node != null)
 133                {
 0134                    item = node.Data;
 135                }
 136                // We want to cache both positive AND negative results
 0137                AddToCache(key, item);
 0138                return (item != null);
 139            }
 0140        }
 141
 142        public void RegisterUri(Uri uri, HostNameComparisonMode hostNameComparisonMode, TItem item)
 143        {
 144            Fx.Assert(HostNameComparisonModeHelper.IsDefined(hostNameComparisonMode), "RegisterUri: Invalid HostNameComp
 145
 0146            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
 0150                ClearCache();
 0151                BaseUriWithWildcard key = new BaseUriWithWildcard(uri, hostNameComparisonMode);
 0152                SegmentHierarchyNode<TItem> node = FindOrCreateNode(key);
 0153                if (node.Data != null)
 154                {
 0155                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SRCommon.For
 0156                        SRCommon.DuplicateRegistration, uri)));
 157                }
 0158                node.SetData(item, key);
 0159                Count++;
 0160            }
 0161        }
 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
 0188            exactMatch = false;
 0189            SegmentHierarchyNode<TItem> current = _root;
 0190            SegmentHierarchyNode<TItem> result = null;
 0191            for (int i = 0; i < path.Length; ++i)
 192            {
 0193                if (!current.TryGetChild(path[i], out SegmentHierarchyNode<TItem> next))
 194                {
 195                    break;
 196                }
 0197                else if (next.Data != null)
 198                {
 0199                    result = next;
 0200                    exactMatch = (i == path.Length - 1);
 201                }
 0202                current = next;
 203            }
 0204            return result;
 205        }
 206
 207        private SegmentHierarchyNode<TItem> FindOrCreateNode(BaseUriWithWildcard baseUri)
 208        {
 209            Fx.Assert(baseUri != null, "FindOrCreateNode: baseUri is null");
 210
 0211            string[] path = UriSegmenter.ToPath(baseUri.BaseAddress, baseUri.HostNameComparisonMode, _includePortInCompa
 0212            SegmentHierarchyNode<TItem> current = _root;
 0213            for (int i = 0; i < path.Length; ++i)
 214            {
 0215                if (!current.TryGetChild(path[i], out SegmentHierarchyNode<TItem> next))
 216                {
 0217                    next = new SegmentHierarchyNode<TItem>(path[i], _useWeakReferences);
 0218                    current.SetChildNode(path[i], next);
 219                }
 0220                current = next;
 221            }
 0222            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            {
 0233                if (null == uriPath)
 234                {
 0235                    return Array.Empty<string>();
 236                }
 0237                UriSegmentEnum segmentEnum = new UriSegmentEnum(uriPath); // struct
 0238                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");
 0252                    _uri = uri;
 0253                    _type = UriSegmentType.Unknown;
 0254                    _segment = null;
 0255                    _segmentStartAt = 0;
 0256                    _segmentLength = 0;
 0257                }
 258
 259                private void ClearSegment()
 260                {
 0261                    _type = UriSegmentType.None;
 0262                    _segment = string.Empty;
 0263                    _segmentStartAt = 0;
 0264                    _segmentLength = 0;
 0265                }
 266
 267                public string[] GetSegments(HostNameComparisonMode hostNameComparisonMode,
 268                    bool includePortInComparison)
 269                {
 0270                    List<string> segments = new List<string>();
 0271                    while (Next())
 272                    {
 0273                        switch (_type)
 274                        {
 275                            case UriSegmentType.Path:
 0276                                segments.Add(_segment.Substring(_segmentStartAt, _segmentLength));
 0277                                break;
 278
 279                            case UriSegmentType.Host:
 0280                                if (hostNameComparisonMode == HostNameComparisonMode.StrongWildcard)
 281                                {
 0282                                    segments.Add("+");
 283                                }
 0284                                else if (hostNameComparisonMode == HostNameComparisonMode.Exact)
 285                                {
 0286                                    segments.Add(_segment);
 287                                }
 288                                else
 289                                {
 0290                                    segments.Add("*");
 291                                }
 0292                                break;
 293
 294                            case UriSegmentType.Port:
 0295                                if (includePortInComparison || hostNameComparisonMode == HostNameComparisonMode.Exact)
 296                                {
 0297                                    segments.Add(_segment);
 298                                }
 0299                                break;
 300
 301                            default:
 0302                                segments.Add(_segment);
 303                                break;
 304                        }
 305                    }
 0306                    return segments.ToArray();
 307                }
 308
 309                public bool Next()
 310                {
 311                    while (true)
 312                    {
 0313                        switch (_type)
 314                        {
 315                            case UriSegmentType.Unknown:
 0316                                _type = UriSegmentType.Scheme;
 0317                                SetSegment(_uri.Scheme);
 0318                                return true;
 319
 320                            case UriSegmentType.Scheme:
 0321                                _type = UriSegmentType.Host;
 0322                                string host = _uri.Host;
 323                                // The userName+password also accompany...
 0324                                string userInfo = _uri.UserInfo;
 0325                                if (null != userInfo && userInfo.Length > 0)
 326                                {
 0327                                    host = userInfo + '@' + host;
 328                                }
 0329                                SetSegment(host);
 0330                                return true;
 331
 332                            case UriSegmentType.Host:
 0333                                _type = UriSegmentType.Port;
 0334                                int port = _uri.Port;
 0335                                SetSegment(port.ToString(CultureInfo.InvariantCulture));
 0336                                return true;
 337
 338                            case UriSegmentType.Port:
 0339                                _type = UriSegmentType.Path;
 0340                                string absPath = _uri.AbsolutePath;
 341                                Fx.Assert(null != absPath, "Next: nill absPath");
 0342                                if (0 == absPath.Length)
 343                                {
 0344                                    ClearSegment();
 0345                                    return false;
 346                                }
 0347                                _segment = absPath;
 0348                                _segmentStartAt = 0;
 0349                                _segmentLength = 0;
 0350                                return NextPathSegment();
 351
 352                            case UriSegmentType.Path:
 0353                                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                {
 0367                    _segmentStartAt += _segmentLength;
 0368                    while (_segmentStartAt < _segment.Length && _segment[_segmentStartAt] == '/')
 369                    {
 0370                        _segmentStartAt++;
 371                    }
 372
 0373                    if (_segmentStartAt < _segment.Length)
 374                    {
 0375                        int next = _segment.IndexOf('/', _segmentStartAt);
 0376                        if (-1 == next)
 377                        {
 0378                            _segmentLength = _segment.Length - _segmentStartAt;
 379                        }
 380                        else
 381                        {
 0382                            _segmentLength = next - _segmentStartAt;
 383                        }
 0384                        return true;
 385                    }
 0386                    ClearSegment();
 0387                    return false;
 388                }
 389
 390                private void SetSegment(string segment)
 391                {
 0392                    _segment = segment;
 0393                    _segmentStartAt = 0;
 0394                    _segmentLength = segment.Length;
 0395                }
 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}