< Summary

Information
Class: Chronicler.ChronicleLinkRegistry
Assembly: Chronicler
File(s): /home/runner/work/Chronicler/Chronicler/src/Chronicler/Links/ChronicleLinkRegistry.cs
Line coverage
100%
Covered lines: 61
Uncovered lines: 0
Coverable lines: 61
Total lines: 175
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 System;
 2using System.Collections.Generic;
 3using System.Diagnostics.CodeAnalysis;
 4
 5namespace Chronicler;
 6
 7/// <summary>
 8/// Stores stable-link resolution strategies for external or runtime-owned objects.
 9/// </summary>
 10public sealed class ChronicleLinkRegistry
 11{
 11412    private readonly Dictionary<ChronicleLinkKey, object> _resolvers = new();
 11413    private readonly Dictionary<ChronicleLinkKey, object> _registeredInstances = new();
 14
 15    /// <summary>
 16    /// Registers a custom link resolver for the given type and optional slot.
 17    /// </summary>
 18    public void RegisterResolver<T>(IRecordLinkResolver<T> resolver, string? slot = null)
 19    {
 1320        _resolvers[new ChronicleLinkKey(typeof(T), slot)] = resolver ?? throw new ArgumentNullException(nameof(resolver)
 1221    }
 22
 23    /// <summary>
 24    /// Registers a concrete instance so the chronicler can save and load it through a stable identifier.
 25    /// </summary>
 26    public void RegisterInstance<T>(string id, T value, string? slot = null)
 27    {
 2528        if (string.IsNullOrWhiteSpace(id))
 329            throw new ArgumentException("A registered link id must not be null or empty.", nameof(id));
 30
 2231        RegisteredLinkTable<T> table = GetOrCreateInstanceTable<T>(slot);
 2232        table.Register(id, value);
 2233    }
 34
 35    /// <summary>
 36    /// Removes a previously registered concrete instance.
 37    /// </summary>
 38    public bool UnregisterInstance<T>(string id, string? slot = null)
 39    {
 640        if (string.IsNullOrWhiteSpace(id))
 341            return false;
 42
 343        ChronicleLinkKey key = new(typeof(T), slot);
 344        if (!_registeredInstances.TryGetValue(key, out object? tableObject))
 145            return false;
 46
 247        return ((RegisteredLinkTable<T>)tableObject!).Unregister(id);
 48    }
 49
 50    /// <summary>
 51    /// Attempts to resolve a stable identifier into an instance of the requested type.
 52    /// </summary>
 53    public bool TryResolve<T>(string id, [MaybeNullWhen(false)] out T value, string? slot = null)
 54    {
 2255        ChronicleLinkKey key = new(typeof(T), slot);
 2256        if (_resolvers.TryGetValue(key, out object? resolverObject)
 2257            && ((IRecordLinkResolver<T>)resolverObject!).TryResolveReference(id, out value))
 58        {
 459            return true;
 60        }
 61
 1862        if (_registeredInstances.TryGetValue(key, out object? tableObject)
 1863            && ((RegisteredLinkTable<T>)tableObject!).TryResolve(id, out value))
 64        {
 565            return true;
 66        }
 67
 1368        value = default!;
 1369        return false;
 70    }
 71
 72    /// <summary>
 73    /// Attempts to read a stable identifier from a concrete instance.
 74    /// </summary>
 75    public bool TryGetReferenceId<T>(T value, [NotNullWhen(true)] out string? id, string? slot = null)
 76    {
 2677        ChronicleLinkKey key = new(typeof(T), slot);
 2678        if (_resolvers.TryGetValue(key, out object? resolverObject)
 2679            && ((IRecordLinkResolver<T>)resolverObject!).TryGetReferenceId(value, out id))
 80        {
 881            return true;
 82        }
 83
 1884        if (_registeredInstances.TryGetValue(key, out object? tableObject)
 1885            && ((RegisteredLinkTable<T>)tableObject!).TryGetReferenceId(value, out id))
 86        {
 1587            return true;
 88        }
 89
 390        id = null;
 391        return false;
 92    }
 93
 94    private RegisteredLinkTable<T> GetOrCreateInstanceTable<T>(string? slot)
 95    {
 2296        ChronicleLinkKey key = new(typeof(T), slot);
 2297        if (_registeredInstances.TryGetValue(key, out object? tableObject))
 198            return (RegisteredLinkTable<T>)tableObject!;
 99
 21100        var table = new RegisteredLinkTable<T>();
 21101        _registeredInstances[key] = table;
 21102        return table;
 103    }
 104
 105    private readonly struct ChronicleLinkKey : IEquatable<ChronicleLinkKey>
 106    {
 107        private readonly Type _type;
 108        private readonly string _slot;
 109
 110        public ChronicleLinkKey(Type type, string? slot)
 111        {
 88112            _type = type ?? throw new ArgumentNullException(nameof(type));
 88113            _slot = slot ?? string.Empty;
 88114        }
 115
 116        public bool Equals(ChronicleLinkKey other)
 117        {
 37118            return _type == other._type
 37119                && string.Equals(_slot, other._slot, StringComparison.Ordinal);
 120        }
 121
 2122        public override bool Equals(object? obj) => obj is ChronicleLinkKey other && Equals(other);
 123
 124        public override int GetHashCode()
 125        {
 126            unchecked
 127            {
 69128                return (_type.GetHashCode() * 397) ^ _slot.GetHashCode();
 129            }
 130        }
 131    }
 132
 133    private sealed class RegisteredLinkTable<T>
 134    {
 21135        private readonly OrderedStringMap<T> _byId = new(8, StringComparer.Ordinal);
 136
 137        public void Register(string id, T value)
 138        {
 22139            _byId[id] = value;
 22140        }
 141
 142        public bool Unregister(string id)
 143        {
 2144            return _byId.Remove(id);
 145        }
 146
 147        public bool TryResolve(string id, [MaybeNullWhen(false)] out T value)
 148        {
 5149            return _byId.TryGetValue(id, out value);
 150        }
 151
 152        public bool TryGetReferenceId(T value, [NotNullWhen(true)] out string? id)
 153        {
 49154            foreach (KeyValuePair<string, T> pair in _byId)
 155            {
 16156                if (!ValuesMatch(pair.Value, value))
 157                    continue;
 158
 15159                id = pair.Key;
 15160                return true;
 161            }
 162
 1163            id = null;
 1164            return false;
 15165        }
 166
 167        private static bool ValuesMatch(T left, T right)
 168        {
 16169            if (typeof(T).IsValueType)
 2170                return EqualityComparer<T>.Default.Equals(left, right);
 171
 14172            return ReferenceEquals(left, right);
 173        }
 174    }
 175}