<PackageReference Include="Castle.Core" Version="3.1.0" />

XmlReferenceManager

public class XmlReferenceManager
using Castle.Core; using Castle.Core.Internal; using System; using System.Collections.Generic; namespace Castle.Components.DictionaryAdapter.Xml { public class XmlReferenceManager { private class Entry { public int Id; public IXmlNode Node; private List<IXmlNode> references; private List<EntryValue> values; public List<IXmlNode> References => references; public List<EntryValue> Values => values; public Entry(IXmlNode node) { Node = node.Save(); } public Entry(int id, IXmlNode node) : this(node) { Id = id; } public void AddReference(IXmlNode node) { if (references == null) references = new List<IXmlNode>(); references.Add(node); } public IXmlNode RemoveReference(IXmlNode node) { for (int i = 0; i < references.Count; i++) { if (references[i].PositionEquals(node)) return RemoveReference(i); } return node; } public IXmlNode RemoveReference(int index) { IXmlNode result = references[index]; references.RemoveAt(index); if (references.Count == 0) references = null; return result; } public void AddValue(Type type, object value, bool isInGraph) { if (values == null) values = new List<EntryValue>(); values.Add(new EntryValue(type, value, isInGraph)); } } private struct Reference { public readonly int Id; public readonly IXmlNode Node; public Reference(int id, IXmlNode node) { Id = id; Node = node; } } private struct EntryValue { public readonly Type Type; public readonly WeakReference Value; public readonly bool IsInGraph; public EntryValue(Type type, object value, bool isInGraph) { this = new EntryValue(type, new WeakReference(value), isInGraph); } public EntryValue(Type type, WeakReference value, bool isInGraph) { Type = type; Value = value; IsInGraph = isInGraph; } } private readonly Dictionary<int, Entry> entriesById; private readonly WeakKeyDictionary<object, Entry> entriesByValue; private readonly IXmlReferenceFormat format; private int nextId; private static readonly Type StringType = typeof(string); private static readonly object CreateEntryToken = new object(); public XmlReferenceManager(IXmlNode root, IXmlReferenceFormat format) { entriesById = new Dictionary<int, Entry>(); entriesByValue = new WeakKeyDictionary<object, Entry>(ReferenceEqualityComparer<object>.Instance); this.format = format; nextId = 1; Populate(root); } private void Populate(IXmlNode node) { List<Reference> references = new List<Reference>(); IXmlIterator xmlIterator = node.SelectSubtree(); while (xmlIterator.MoveNext()) { PopulateFromNode(xmlIterator, references); } PopulateDeferredReferences(references); } private void PopulateFromNode(IXmlIterator node, ICollection<Reference> references) { if (format.TryGetIdentity(node, out int id)) PopulateIdentity(id, node.Save()); else if (format.TryGetReference(node, out id)) { PopulateReference(id, node.Save(), references); } } private void PopulateIdentity(int id, IXmlNode node) { if (!entriesById.TryGetValue(id, out Entry _)) entriesById.Add(id, new Entry(id, node)); if (nextId <= id) nextId = ++id; } private void PopulateReference(int id, IXmlNode node, ICollection<Reference> references) { if (entriesById.TryGetValue(id, out Entry value)) value.AddReference(node); else references.Add(new Reference(id, node)); } private void PopulateDeferredReferences(ICollection<Reference> references) { foreach (Reference reference in references) { Reference current = reference; if (entriesById.TryGetValue(current.Id, out Entry value)) value.AddReference(current.Node); } } public bool TryGet(object keyObject, out object inGraphObject) { if (entriesByValue.TryGetValue(keyObject, out Entry value)) { inGraphObject = keyObject; TryGetCompatibleValue(value, keyObject.GetComponentType(), ref inGraphObject); return true; } inGraphObject = null; return false; } public void Add(IXmlNode node, object keyValue, object newValue, bool isInGraph) { if (keyValue == null) throw Error.ArgumentNull("keyValue"); if (newValue == null) throw Error.ArgumentNull("newValue"); Type componentType = newValue.GetComponentType(); if (!ShouldExclude(componentType) && !entriesByValue.ContainsKey(newValue)) { if (entriesByValue.TryGetValue(keyValue, out Entry value)) { if (newValue == keyValue) return; } else { if (node == null) return; if (!TryGetEntry(node, out value, out bool _)) value = new Entry(node); } AddValueCore(value, componentType, newValue, isInGraph); } } public bool OnGetStarting(ref IXmlNode node, ref object value, out object token) { Type clrType = node.ClrType; if (ShouldExclude(clrType)) { token = null; return true; } if (!TryGetEntry(node, out Entry entry, out bool reference)) { token = CreateEntryToken; return true; } if (reference) RedirectNode(ref node, entry); bool flag = !TryGetCompatibleValue(entry, node.ClrType, ref value); token = (flag ? entry : null); return flag; } public void OnGetCompleted(IXmlNode node, object value, object token) { if (value != null) { Type clrType = node.ClrType; if (!ShouldExclude(clrType) && !entriesByValue.ContainsKey(value)) { Entry entry = (token == CreateEntryToken) ? new Entry(node) : (token as Entry); if (entry != null) AddValue(entry, clrType, value, null); } } } public bool OnAssigningNull(IXmlNode node, object oldValue) { object newValue = null; object token; return OnAssigningValue(node, oldValue, ref newValue, out token); } public bool OnAssigningValue(IXmlNode node, object oldValue, ref object newValue, out object token) { if (newValue == oldValue && newValue != null) { token = null; return false; } Entry entry = OnReplacingValue(node, oldValue); if (newValue == null) return ShouldAssignmentProceed(entry, null, token = null); Type componentType = newValue.GetComponentType(); if (ShouldExclude(componentType)) return ShouldAssignmentProceed(entry, null, token = null); XmlAdapter xmlAdapter = XmlAdapter.For(newValue, false); if (entriesByValue.TryGetValue(xmlAdapter ?? newValue, out Entry value)) { TryGetCompatibleValue(value, componentType, ref newValue); AddReference(node, value); token = null; } else { value = (entry ?? new Entry(node)); AddValue(value, componentType, newValue, xmlAdapter); format.ClearIdentity(node); format.ClearReference(node); token = value; } return ShouldAssignmentProceed(entry, value, token); } private bool ShouldAssignmentProceed(Entry oldEntry, Entry newEntry, object token) { if (oldEntry != null && oldEntry != newEntry && oldEntry.Id > 0) entriesById.Remove(oldEntry.Id); if (token == null) return newEntry == null; return true; } private Entry OnReplacingValue(IXmlNode node, object oldValue) { Entry entry; bool reference; if (oldValue == null) { if (!TryGetEntry(node, out entry, out reference)) return null; } else { if (!entriesByValue.TryGetValue(oldValue, out entry)) return null; reference = !entry.Node.PositionEquals(node); } if (reference) { entry.RemoveReference(node); ClearReference(entry, node); return null; } if (entry.References != null) { node = entry.RemoveReference(0); ClearReference(entry, node); entry.Node.CopyTo(node); entry.Node.Clear(); entry.Node = node; return null; } PrepareForReuse(entry); return entry; } public void OnAssignedValue(IXmlNode node, object givenValue, object storedValue, object token) { Entry entry = token as Entry; if (entry != null && !object.ReferenceEquals(givenValue, storedValue)) { SetNotInGraph(entry, givenValue); if (!entriesByValue.ContainsKey(storedValue)) AddValue(entry, node.ClrType, storedValue, null); } } private void AddReference(IXmlNode node, Entry entry) { if (!entry.Node.PositionEquals(node)) { if (entry.References == null) { GenerateId(entry); format.SetIdentity(entry.Node, entry.Id); } node.Clear(); entry.AddReference(node); format.SetReference(node, entry.Id); } } private void GenerateId(Entry entry) { if (entry.Id == 0) { entry.Id = nextId++; entriesById.Add(entry.Id, entry); } } private void AddValue(Entry entry, Type type, object value, XmlAdapter xmlAdapter) { if (xmlAdapter == null) xmlAdapter = XmlAdapter.For(value, false); AddValueCore(entry, type, value, true); if (xmlAdapter != null) AddValueCore(entry, typeof(XmlAdapter), xmlAdapter, true); } private void AddValueCore(Entry entry, Type type, object value, bool isInGraph) { entry.AddValue(type, value, isInGraph); entriesByValue.Add(value, entry); } private void ClearReference(Entry entry, IXmlNode node) { format.ClearReference(node); if (entry.References == null) format.ClearIdentity(entry.Node); } private void PrepareForReuse(Entry entry) { foreach (EntryValue value in entry.Values) { EntryValue current = value; object target = current.Value.Target; if (target != null) entriesByValue.Remove(target); } entry.Values.Clear(); format.ClearIdentity(entry.Node); } private bool TryGetEntry(IXmlNode node, out Entry entry, out bool reference) { if (format.TryGetIdentity(node, out int id)) reference = false; else { if (!format.TryGetReference(node, out id)) { reference = false; entry = null; return false; } reference = true; } if (!entriesById.TryGetValue(id, out entry)) throw IdNotFoundError(id); return true; } private bool TryGetCompatibleValue(Entry entry, Type type, ref object value) { List<EntryValue> values = entry.Values; if (values == null) return false; IDictionaryAdapter dictionaryAdapter = null; foreach (EntryValue item in values) { EntryValue current = item; if (current.IsInGraph) { object target = current.Value.Target; if (target != null) { if (type.IsAssignableFrom(current.Type) && target != null) return Try.Success(out value, target); if (dictionaryAdapter == null) dictionaryAdapter = (target as IDictionaryAdapter); } } } if (dictionaryAdapter != null) { value = dictionaryAdapter.Coerce(type); entry.AddValue(type, value, true); return true; } return false; } private static void SetNotInGraph(Entry entry, object value) { XmlAdapter xmlAdapter = XmlAdapter.For(value, false); SetNotInGraphCore(entry, value); if (xmlAdapter != null) SetNotInGraphCore(entry, xmlAdapter); } private static bool ShouldExclude(Type type) { if (!type.IsValueType) return (object)type == StringType; return true; } private static void SetNotInGraphCore(Entry entry, object value) { List<EntryValue> values = entry.Values; int num = 0; EntryValue entryValue; while (true) { if (num >= values.Count) return; entryValue = values[num]; object target = entryValue.Value.Target; if (object.ReferenceEquals(target, value)) break; num++; } entryValue = (values[num] = new EntryValue(entryValue.Type, entryValue.Value, false)); } private static IXmlNode RedirectNode(ref IXmlNode node, Entry entry) { IXmlCursor xmlCursor = entry.Node.SelectSelf(node.ClrType); xmlCursor.MoveNext(); return node = xmlCursor; } public void UnionWith(XmlReferenceManager other) { HashSet<Entry> hashSet = null; foreach (KeyValuePair<object, Entry> item in other.entriesByValue) { if (entriesByValue.TryGetValue(item.Key, out Entry value)) { if (hashSet == null) hashSet = new HashSet<Entry>(ReferenceEqualityComparer<Entry>.Instance); else if (hashSet.Contains(value)) { continue; } hashSet.Add(value); foreach (EntryValue value2 in item.Value.Values) { EntryValue current2 = value2; object target = current2.Value.Target; if (target != null && target != item.Key && !entriesByValue.ContainsKey(target)) AddValueCore(value, current2.Type, target, false); } } } } private static Exception IdNotFoundError(int id) { string message = $"""{id}"""; return new KeyNotFoundException(message); } } }