< Summary

Information
Class: Chronicler.ChronicleLinkRegistry
Assembly: Chronicler
File(s): /home/runner/work/Chronicler/Chronicler/src/Chronicler/Links/ChronicleLinkRegistry.cs
Line coverage
100%
Covered lines: 91
Uncovered lines: 0
Coverable lines: 91
Total lines: 176
Line coverage: 100%
Branch coverage
95%
Covered branches: 38
Total branches: 40
Branch coverage: 95%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor()100%11100%
RegisterResolver(...)100%22100%
RegisterInstance(...)100%22100%
UnregisterInstance(...)100%44100%
TryResolve(...)100%88100%
TryGetReferenceId(...)100%88100%
GetOrCreateInstanceTable(...)100%22100%
.ctor(...)75%44100%
Equals(...)50%22100%
Equals(...)100%22100%
GetHashCode()100%11100%
.ctor()100%11100%
Register(...)100%11100%
Unregister(...)100%11100%
TryResolve(...)100%11100%
TryGetReferenceId(...)100%44100%
ValuesMatch(...)100%22100%

File(s)

/home/runner/work/Chronicler/Chronicler/src/Chronicler/Links/ChronicleLinkRegistry.cs

#LineLine coverage
 1using SwiftCollections;
 2using System;
 3using System.Collections.Generic;
 4using System.Diagnostics.CodeAnalysis;
 5
 6namespace Chronicler;
 7
 8/// <summary>
 9/// Stores stable-link resolution strategies for external or runtime-owned objects.
 10/// </summary>
 11public sealed class ChronicleLinkRegistry
 12{
 11313    private readonly SwiftDictionary<ChronicleLinkKey, object> _resolvers = new();
 11314    private readonly SwiftDictionary<ChronicleLinkKey, object> _registeredInstances = new();
 15
 16    /// <summary>
 17    /// Registers a custom link resolver for the given type and optional slot.
 18    /// </summary>
 19    public void RegisterResolver<T>(IRecordLinkResolver<T> resolver, string? slot = null)
 1320    {
 1321        _resolvers[new ChronicleLinkKey(typeof(T), slot)] = resolver ?? throw new ArgumentNullException(nameof(resolver)
 1222    }
 23
 24    /// <summary>
 25    /// Registers a concrete instance so the chronicler can save and load it through a stable identifier.
 26    /// </summary>
 27    public void RegisterInstance<T>(string id, T value, string? slot = null)
 2528    {
 2529        if (string.IsNullOrWhiteSpace(id))
 330            throw new ArgumentException("A registered link id must not be null or empty.", nameof(id));
 31
 2232        RegisteredLinkTable<T> table = GetOrCreateInstanceTable<T>(slot);
 2233        table.Register(id, value);
 2234    }
 35
 36    /// <summary>
 37    /// Removes a previously registered concrete instance.
 38    /// </summary>
 39    public bool UnregisterInstance<T>(string id, string? slot = null)
 640    {
 641        if (string.IsNullOrWhiteSpace(id))
 342            return false;
 43
 344        ChronicleLinkKey key = new(typeof(T), slot);
 345        if (!_registeredInstances.TryGetValue(key, out object tableObject))
 146            return false;
 47
 248        return ((RegisteredLinkTable<T>)tableObject).Unregister(id);
 649    }
 50
 51    /// <summary>
 52    /// Attempts to resolve a stable identifier into an instance of the requested type.
 53    /// </summary>
 54    public bool TryResolve<T>(string id, [MaybeNullWhen(false)] out T value, string? slot = null)
 2255    {
 2256        ChronicleLinkKey key = new(typeof(T), slot);
 2257        if (_resolvers.TryGetValue(key, out object resolverObject)
 2258            && ((IRecordLinkResolver<T>)resolverObject).TryResolveReference(id, out value))
 459        {
 460            return true;
 61        }
 62
 1863        if (_registeredInstances.TryGetValue(key, out object tableObject)
 1864            && ((RegisteredLinkTable<T>)tableObject).TryResolve(id, out value))
 565        {
 566            return true;
 67        }
 68
 1369        value = default!;
 1370        return false;
 2271    }
 72
 73    /// <summary>
 74    /// Attempts to read a stable identifier from a concrete instance.
 75    /// </summary>
 76    public bool TryGetReferenceId<T>(T value, [NotNullWhen(true)] out string? id, string? slot = null)
 2677    {
 2678        ChronicleLinkKey key = new(typeof(T), slot);
 2679        if (_resolvers.TryGetValue(key, out object resolverObject)
 2680            && ((IRecordLinkResolver<T>)resolverObject).TryGetReferenceId(value, out id))
 881        {
 882            return true;
 83        }
 84
 1885        if (_registeredInstances.TryGetValue(key, out object tableObject)
 1886            && ((RegisteredLinkTable<T>)tableObject).TryGetReferenceId(value, out id))
 1587        {
 1588            return true;
 89        }
 90
 391        id = null;
 392        return false;
 2693    }
 94
 95    private RegisteredLinkTable<T> GetOrCreateInstanceTable<T>(string? slot)
 2296    {
 2297        ChronicleLinkKey key = new(typeof(T), slot);
 2298        if (_registeredInstances.TryGetValue(key, out object tableObject))
 199            return (RegisteredLinkTable<T>)tableObject;
 100
 21101        var table = new RegisteredLinkTable<T>();
 21102        _registeredInstances[key] = table;
 21103        return table;
 22104    }
 105
 106    private readonly struct ChronicleLinkKey : IEquatable<ChronicleLinkKey>
 107    {
 108        private readonly Type _type;
 109        private readonly string _slot;
 110
 111        public ChronicleLinkKey(Type type, string? slot)
 88112        {
 88113            _type = type ?? throw new ArgumentNullException(nameof(type));
 88114            _slot = slot ?? string.Empty;
 88115        }
 116
 117        public bool Equals(ChronicleLinkKey other)
 37118        {
 37119            return _type == other._type
 37120                && string.Equals(_slot, other._slot, StringComparison.Ordinal);
 37121        }
 122
 2123        public override bool Equals(object? obj) => obj is ChronicleLinkKey other && Equals(other);
 124
 125        public override int GetHashCode()
 175126        {
 127            unchecked
 175128            {
 175129                return (_type.GetHashCode() * 397) ^ _slot.GetHashCode();
 130            }
 175131        }
 132    }
 133
 134    private sealed class RegisteredLinkTable<T>
 135    {
 21136        private readonly SwiftDictionary<string, T> _byId = new(8, StringComparer.Ordinal);
 137
 138        public void Register(string id, T value)
 22139        {
 22140            _byId[id] = value;
 22141        }
 142
 143        public bool Unregister(string id)
 2144        {
 2145            return _byId.Remove(id);
 2146        }
 147
 148        public bool TryResolve(string id, [MaybeNullWhen(false)] out T value)
 5149        {
 5150            return _byId.TryGetValue(id, out value);
 5151        }
 152
 153        public bool TryGetReferenceId(T value, [NotNullWhen(true)] out string? id)
 16154        {
 65155            foreach (KeyValuePair<string, T> pair in _byId)
 16156            {
 16157                if (!ValuesMatch(pair.Value, value))
 1158                    continue;
 159
 15160                id = pair.Key;
 15161                return true;
 162            }
 163
 1164            id = null;
 1165            return false;
 16166        }
 167
 168        private static bool ValuesMatch(T left, T right)
 16169        {
 16170            if (typeof(T).IsValueType)
 2171                return EqualityComparer<T>.Default.Equals(left, right);
 172
 14173            return ReferenceEquals(left, right);
 16174        }
 175    }
 176}