< Summary - CoreWCF Coverage — PR #1766

Information
Class: CoreWCF.Channels.SegmentHierarchyNode<T>
Assembly: CoreWCF.RabbitMQ
File(s): /home/runner/work/CoreWCF/CoreWCF/src/Common/src/CoreWCF/Channels/UriPrefixTable.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 37
Coverable lines: 37
Total lines: 523
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 22
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%
SetData(...)0%440%
SetChildNode(...)100%110%
Collect(...)0%440%
TryGetChild(...)100%110%
RemoveData()100%110%
RemovePath(...)0%10100%

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()
 24            : this(false)
 25        {
 26        }
 27
 28        public UriPrefixTable(bool includePortInComparison)
 29            : this(includePortInComparison, false)
 30        {
 31        }
 32
 33        public UriPrefixTable(bool includePortInComparison, bool useWeakReferences)
 34        {
 35            _includePortInComparison = includePortInComparison;
 36            _useWeakReferences = useWeakReferences;
 37            _root = new SegmentHierarchyNode<TItem>(null, useWeakReferences);
 38            _lookupCache = new HopperCache(HopperSize, useWeakReferences);
 39        }
 40
 41        internal UriPrefixTable(UriPrefixTable<TItem> objectToClone)
 42            : this(objectToClone._includePortInComparison, objectToClone._useWeakReferences)
 43        {
 44            if (objectToClone.Count > 0)
 45            {
 46                foreach (KeyValuePair<BaseUriWithWildcard, TItem> current in objectToClone.GetAll())
 47                {
 48                    RegisterUri(current.Key.BaseAddress, current.Key.HostNameComparisonMode, current.Value);
 49                }
 50            }
 51        }
 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.
 60                return this;
 61            }
 62        }
 63
 64        public int Count { get; private set; }
 65
 66        public AsyncLock AsyncLock { get; } = new AsyncLock();
 67
 68        public bool IsRegistered(BaseUriWithWildcard key)
 69        {
 70            Uri uri = key.BaseAddress;
 71
 72            // don't need to normalize path since SegmentHierarchyNode is
 73            // already OrdinalIgnoreCase
 74            string[] paths = UriSegmenter.ToPath(uri, key.HostNameComparisonMode, _includePortInComparison);
 75            bool exactMatch;
 76            SegmentHierarchyNode<TItem> node;
 77            using (AsyncLock.TakeLock())
 78            {
 79                node = FindDataNode(paths, out exactMatch);
 80            }
 81            return exactMatch && node != null && node.Data != null;
 82        }
 83
 84        public IEnumerable<KeyValuePair<BaseUriWithWildcard, TItem>> GetAll()
 85        {
 86            using (AsyncLock.TakeLock())
 87            {
 88                List<KeyValuePair<BaseUriWithWildcard, TItem>> result = new List<KeyValuePair<BaseUriWithWildcard, TItem
 89                _root.Collect(result);
 90                return result;
 91            }
 92        }
 93
 94        private bool TryCacheLookup(BaseUriWithWildcard key, out TItem item)
 95        {
 96            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.
 100            item = value == DBNull.Value ? null : (TItem)value;
 101            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.
 110            _lookupCache.Add(key, item ?? (object)DBNull.Value);
 111        }
 112
 113        private void ClearCache()
 114        {
 115            _lookupCache = new HopperCache(HopperSize, _useWeakReferences);
 116        }
 117
 118        public bool TryLookupUri(Uri uri, HostNameComparisonMode hostNameComparisonMode, out TItem item)
 119        {
 120            BaseUriWithWildcard key = new BaseUriWithWildcard(uri, hostNameComparisonMode);
 121            if (TryCacheLookup(key, out item))
 122            {
 123                return item != null;
 124            }
 125
 126            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)
 130                SegmentHierarchyNode<TItem> node = FindDataNode(
 131                    UriSegmenter.ToPath(key.BaseAddress, hostNameComparisonMode, _includePortInComparison), out bool dum
 132                if (node != null)
 133                {
 134                    item = node.Data;
 135                }
 136                // We want to cache both positive AND negative results
 137                AddToCache(key, item);
 138                return (item != null);
 139            }
 140        }
 141
 142        public void RegisterUri(Uri uri, HostNameComparisonMode hostNameComparisonMode, TItem item)
 143        {
 144            Fx.Assert(HostNameComparisonModeHelper.IsDefined(hostNameComparisonMode), "RegisterUri: Invalid HostNameComp
 145
 146            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
 150                ClearCache();
 151                BaseUriWithWildcard key = new BaseUriWithWildcard(uri, hostNameComparisonMode);
 152                SegmentHierarchyNode<TItem> node = FindOrCreateNode(key);
 153                if (node.Data != null)
 154                {
 155                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SRCommon.For
 156                        SRCommon.DuplicateRegistration, uri)));
 157                }
 158                node.SetData(item, key);
 159                Count++;
 160            }
 161        }
 162
 163        public void UnregisterUri(Uri uri, HostNameComparisonMode hostNameComparisonMode)
 164        {
 165            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
 169                ClearCache();
 170                string[] path = UriSegmenter.ToPath(uri, hostNameComparisonMode, _includePortInComparison);
 171                // Never remove the root
 172                if (path.Length == 0)
 173                {
 174                    _root.RemoveData();
 175                }
 176                else
 177                {
 178                    _root.RemovePath(path, 0);
 179                }
 180                Count--;
 181            }
 182        }
 183
 184        private SegmentHierarchyNode<TItem> FindDataNode(string[] path, out bool exactMatch)
 185        {
 186            Fx.Assert(path != null, "FindDataNode: path is null");
 187
 188            exactMatch = false;
 189            SegmentHierarchyNode<TItem> current = _root;
 190            SegmentHierarchyNode<TItem> result = null;
 191            for (int i = 0; i < path.Length; ++i)
 192            {
 193                if (!current.TryGetChild(path[i], out SegmentHierarchyNode<TItem> next))
 194                {
 195                    break;
 196                }
 197                else if (next.Data != null)
 198                {
 199                    result = next;
 200                    exactMatch = (i == path.Length - 1);
 201                }
 202                current = next;
 203            }
 204            return result;
 205        }
 206
 207        private SegmentHierarchyNode<TItem> FindOrCreateNode(BaseUriWithWildcard baseUri)
 208        {
 209            Fx.Assert(baseUri != null, "FindOrCreateNode: baseUri is null");
 210
 211            string[] path = UriSegmenter.ToPath(baseUri.BaseAddress, baseUri.HostNameComparisonMode, _includePortInCompa
 212            SegmentHierarchyNode<TItem> current = _root;
 213            for (int i = 0; i < path.Length; ++i)
 214            {
 215                if (!current.TryGetChild(path[i], out SegmentHierarchyNode<TItem> next))
 216                {
 217                    next = new SegmentHierarchyNode<TItem>(path[i], _useWeakReferences);
 218                    current.SetChildNode(path[i], next);
 219                }
 220                current = next;
 221            }
 222            return current;
 223        }
 224
 225        public IEnumerator<KeyValuePair<BaseUriWithWildcard, TItem>> GetEnumerator() => GetAll().GetEnumerator();
 226        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            {
 233                if (null == uriPath)
 234                {
 235                    return Array.Empty<string>();
 236                }
 237                UriSegmentEnum segmentEnum = new UriSegmentEnum(uriPath); // struct
 238                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");
 252                    _uri = uri;
 253                    _type = UriSegmentType.Unknown;
 254                    _segment = null;
 255                    _segmentStartAt = 0;
 256                    _segmentLength = 0;
 257                }
 258
 259                private void ClearSegment()
 260                {
 261                    _type = UriSegmentType.None;
 262                    _segment = string.Empty;
 263                    _segmentStartAt = 0;
 264                    _segmentLength = 0;
 265                }
 266
 267                public string[] GetSegments(HostNameComparisonMode hostNameComparisonMode,
 268                    bool includePortInComparison)
 269                {
 270                    List<string> segments = new List<string>();
 271                    while (Next())
 272                    {
 273                        switch (_type)
 274                        {
 275                            case UriSegmentType.Path:
 276                                segments.Add(_segment.Substring(_segmentStartAt, _segmentLength));
 277                                break;
 278
 279                            case UriSegmentType.Host:
 280                                if (hostNameComparisonMode == HostNameComparisonMode.StrongWildcard)
 281                                {
 282                                    segments.Add("+");
 283                                }
 284                                else if (hostNameComparisonMode == HostNameComparisonMode.Exact)
 285                                {
 286                                    segments.Add(_segment);
 287                                }
 288                                else
 289                                {
 290                                    segments.Add("*");
 291                                }
 292                                break;
 293
 294                            case UriSegmentType.Port:
 295                                if (includePortInComparison || hostNameComparisonMode == HostNameComparisonMode.Exact)
 296                                {
 297                                    segments.Add(_segment);
 298                                }
 299                                break;
 300
 301                            default:
 302                                segments.Add(_segment);
 303                                break;
 304                        }
 305                    }
 306                    return segments.ToArray();
 307                }
 308
 309                public bool Next()
 310                {
 311                    while (true)
 312                    {
 313                        switch (_type)
 314                        {
 315                            case UriSegmentType.Unknown:
 316                                _type = UriSegmentType.Scheme;
 317                                SetSegment(_uri.Scheme);
 318                                return true;
 319
 320                            case UriSegmentType.Scheme:
 321                                _type = UriSegmentType.Host;
 322                                string host = _uri.Host;
 323                                // The userName+password also accompany...
 324                                string userInfo = _uri.UserInfo;
 325                                if (null != userInfo && userInfo.Length > 0)
 326                                {
 327                                    host = userInfo + '@' + host;
 328                                }
 329                                SetSegment(host);
 330                                return true;
 331
 332                            case UriSegmentType.Host:
 333                                _type = UriSegmentType.Port;
 334                                int port = _uri.Port;
 335                                SetSegment(port.ToString(CultureInfo.InvariantCulture));
 336                                return true;
 337
 338                            case UriSegmentType.Port:
 339                                _type = UriSegmentType.Path;
 340                                string absPath = _uri.AbsolutePath;
 341                                Fx.Assert(null != absPath, "Next: nill absPath");
 342                                if (0 == absPath.Length)
 343                                {
 344                                    ClearSegment();
 345                                    return false;
 346                                }
 347                                _segment = absPath;
 348                                _segmentStartAt = 0;
 349                                _segmentLength = 0;
 350                                return NextPathSegment();
 351
 352                            case UriSegmentType.Path:
 353                                return NextPathSegment();
 354
 355                            case UriSegmentType.None:
 356                                return false;
 357
 358                            default:
 359                                Fx.Assert("Next: unknown enum value");
 360                                return false;
 361                        }
 362                    }
 363                }
 364
 365                public bool NextPathSegment()
 366                {
 367                    _segmentStartAt += _segmentLength;
 368                    while (_segmentStartAt < _segment.Length && _segment[_segmentStartAt] == '/')
 369                    {
 370                        _segmentStartAt++;
 371                    }
 372
 373                    if (_segmentStartAt < _segment.Length)
 374                    {
 375                        int next = _segment.IndexOf('/', _segmentStartAt);
 376                        if (-1 == next)
 377                        {
 378                            _segmentLength = _segment.Length - _segmentStartAt;
 379                        }
 380                        else
 381                        {
 382                            _segmentLength = next - _segmentStartAt;
 383                        }
 384                        return true;
 385                    }
 386                    ClearSegment();
 387                    return false;
 388                }
 389
 390                private void SetSegment(string segment)
 391                {
 392                    _segment = segment;
 393                    _segmentStartAt = 0;
 394                    _segmentLength = segment.Length;
 395                }
 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
 0420        public SegmentHierarchyNode(string name, bool useWeakReferences)
 421        {
 0422            _name = name;
 0423            _useWeakReferences = useWeakReferences;
 0424            _children = new Dictionary<string, SegmentHierarchyNode<TData>>(StringComparer.OrdinalIgnoreCase);
 0425        }
 426
 427        public TData Data
 428        {
 429            get
 430            {
 0431                if (_useWeakReferences)
 432                {
 0433                    if (_weakData == null)
 434                    {
 0435                        return null;
 436                    }
 437                    else
 438                    {
 0439                        return _weakData.Target as TData;
 440                    }
 441                }
 442                else
 443                {
 0444                    return _data;
 445                }
 446            }
 447        }
 448
 449        public void SetData(TData data, BaseUriWithWildcard path)
 450        {
 0451            _path = path;
 0452            if (_useWeakReferences)
 453            {
 0454                if (data == null)
 455                {
 0456                    _weakData = null;
 457                }
 458                else
 459                {
 0460                    _weakData = new WeakReference(data);
 461                }
 462            }
 463            else
 464            {
 0465                _data = data;
 466            }
 0467        }
 468
 469        public void SetChildNode(string name, SegmentHierarchyNode<TData> node)
 470        {
 0471            _children[name] = node;
 0472        }
 473
 474        public void Collect(List<KeyValuePair<BaseUriWithWildcard, TData>> result)
 475        {
 0476            TData localData = Data;
 0477            if (localData != null)
 478            {
 0479                result.Add(new KeyValuePair<BaseUriWithWildcard, TData>(_path, localData));
 480            }
 481
 0482            foreach (SegmentHierarchyNode<TData> child in _children.Values)
 483            {
 0484                child.Collect(result);
 485            }
 0486        }
 487
 488        public bool TryGetChild(string segment, out SegmentHierarchyNode<TData> value)
 489        {
 0490            return _children.TryGetValue(segment, out value);
 491        }
 492
 493        public void RemoveData()
 494        {
 0495            SetData(null, null);
 0496        }
 497
 498        // bool is whether to remove this node
 499        public bool RemovePath(string[] path, int seg)
 500        {
 0501            if (seg == path.Length)
 502            {
 0503                RemoveData();
 0504                return _children.Count == 0;
 505            }
 506
 0507            if (!TryGetChild(path[seg], out SegmentHierarchyNode<TData> node))
 508            {
 0509                return (_children.Count == 0 && Data == null);
 510            }
 511
 0512            if (node.RemovePath(path, seg + 1))
 513            {
 0514                _children.Remove(path[seg]);
 0515                return (_children.Count == 0 && Data == null);
 516            }
 517            else
 518            {
 0519                return false;
 520            }
 521        }
 522    }
 523}