Browse Source

Added ChildrenDictionary collection

vpenades 2 years ago
parent
commit
88eaf523fd

+ 1 - 1
src/SharpGLTF.Core/Collections/ChildSetter.cs

@@ -31,7 +31,7 @@ namespace SharpGLTF.Collections
 
         #region API        
 
-        public void SetProperty<T>(ref T target, T value)
+        public void SetListProperty<T>(ref T target, T value)
             where T : class, IChildOfList<TParent>
         {
             if (value == target) return;

+ 174 - 0
src/SharpGLTF.Core/Collections/ChildrenDictionary.cs

@@ -0,0 +1,174 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+
+namespace SharpGLTF.Collections
+{
+    /// <summary>
+    /// An Specialisation of <see cref="Dictionary{TKey, TValue}"/>, which interconnects the dictionary items with the parent of the collection.
+    /// </summary>    
+    [System.Diagnostics.DebuggerDisplay("{Count}")]
+    public sealed class ChildrenDictionary<T, TParent> : IReadOnlyDictionary<string, T> , IDictionary<string, T>
+        where T : class, IChildOfDictionary<TParent>
+        where TParent : class
+    {
+        #region lifecycle
+
+        public ChildrenDictionary(TParent parent)
+        {
+            Guard.NotNull(parent, nameof(parent));
+            _Parent = parent;
+        }
+
+        #endregion
+
+        #region data
+
+        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
+        private readonly TParent _Parent;
+
+        [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.RootHidden)]
+        private Dictionary<string, T> _Collection;
+
+        #endregion
+
+        #region Properties
+
+        IEnumerable<string> IReadOnlyDictionary<string, T>.Keys => this.Keys;
+        public ICollection<string> Keys => _Collection == null ? Array.Empty<string>() : (ICollection<string>)_Collection.Keys;
+
+        IEnumerable<T> IReadOnlyDictionary<string, T>.Values => this.Values;
+        public ICollection<T> Values => _Collection == null ? Array.Empty<T>() : (ICollection<T>)_Collection.Values;        
+
+        public int Count => _Collection == null ? 0 : _Collection.Count;        
+
+        public bool IsReadOnly => false;
+
+        #endregion
+
+        #region API
+
+        public T this[string key]
+        {
+            get => TryGetValue(key, out var value) ? value : throw new KeyNotFoundException();
+            set => Add(key, value);
+        }
+
+        public void Clear()
+        {
+            if (_Collection == null) return;
+
+            foreach (var item in _Collection) // orphan all children
+            {
+                item.Value.SetLogicalParent(null, null);
+                _AssertItem(item.Value, null);
+            }
+
+            _Collection = null;
+        }
+        
+        public void Add(string key, T value)
+        {
+            _VerifyIsOrphan(value);
+
+            _Collection ??= new Dictionary<string, T>();
+
+            Remove(key);
+
+            if (value == null) return;
+
+            value.SetLogicalParent(_Parent, key);
+            _AssertItem(value, key);
+
+            _Collection[key] = value;            
+        }
+
+        public bool Remove(string key)
+        {
+            if (_Collection == null) return false;
+
+            if (!_Collection.TryGetValue(key, out var oldValue)) return false;            
+
+            // orphan the current child
+            oldValue?.SetLogicalParent(null, null);
+
+            var r = _Collection.Remove(key);
+
+            if (_Collection.Count == 0) _Collection = null;
+
+            return r;
+        }        
+
+        public bool ContainsKey(string key)
+        {
+            if (_Collection == null) return false;
+            return _Collection.ContainsKey(key);
+        }
+
+        public bool TryGetValue(string key, out T value)
+        {
+            if (_Collection == null) { value = default; return false; }
+            return _Collection.TryGetValue(key, out value);
+        }
+
+        public IEnumerator<KeyValuePair<string, T>> GetEnumerator()
+        {
+            var c = _Collection ?? Enumerable.Empty<KeyValuePair<string, T>>();
+            return c.GetEnumerator();
+        }
+
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            var c = _Collection ?? Enumerable.Empty<KeyValuePair<string, T>>();
+            return c.GetEnumerator();
+        }
+
+        private static void _VerifyIsOrphan(T item)
+        {
+            Guard.NotNull(item, nameof(item));
+            Guard.MustBeNull(item.LogicalParent, nameof(item.LogicalParent));
+            Guard.MustBeNull(item.LogicalKey, nameof(item.LogicalKey));
+        }
+
+        [Conditional("DEBUG")]
+        private void _AssertItem(T item, string key)
+        {
+            System.Diagnostics.Debug.Assert(item.LogicalKey == key);
+
+            var parent = key != null ? _Parent : null;
+
+            System.Diagnostics.Debug.Assert(item.LogicalParent == parent);
+        }        
+
+        public void Add(KeyValuePair<string, T> item)
+        {
+            Add(item.Key, item.Value);
+        }
+
+        public bool Contains(KeyValuePair<string, T> item)
+        {
+            return ContainsKey(item.Key);
+        }
+
+        public bool Remove(KeyValuePair<string, T> item)
+        {
+            return Remove(item.Key);
+        }
+
+        public void CopyTo(KeyValuePair<string, T>[] array, int arrayIndex)
+        {
+            if (_Collection == null) return;
+            foreach(var kvp in _Collection)
+            {
+                array[arrayIndex++] = kvp;
+            }
+        }
+
+        #endregion
+    }
+}

+ 35 - 0
src/SharpGLTF.Core/Collections/IChildOfDictionary.cs

@@ -0,0 +1,35 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace SharpGLTF.Collections
+{
+    /// <summary>
+    /// Implemented by children of <see cref="ChildrenDictionary{T, TParent}"/>
+    /// </summary>
+    /// <typeparam name="TParent">The type of the parent class containing the collection.</typeparam>
+    public interface IChildOfDictionary<TParent>
+        where TParent : class
+    {
+        /// <summary>
+        /// Gets the logical parent that owns the collection containing this object.
+        /// </summary>
+        TParent LogicalParent { get; }
+
+        /// <summary>
+        /// Gets the logical key of this item within the parent's collection.
+        /// </summary>
+        string LogicalKey { get; }
+
+        /// <summary>
+        /// Assigns a parent and index to this object.
+        /// </summary>
+        /// <param name="parent">The new parent, or null</param>
+        /// <param name="key">The new key, or null</param>
+        /// <remarks>
+        /// For internal use of the collection.<br/>
+        /// ALWAYS IMPLEMENT EXPLICITLY!
+        /// </remarks>
+        void SetLogicalParent(TParent parent, string key);
+    }
+}

+ 1 - 1
src/SharpGLTF.Core/Schema2/gltf.Asset.cs

@@ -100,7 +100,7 @@ namespace SharpGLTF.Schema2
         public Asset Asset
         {
             get => _asset;
-            set => GetChildSetter(this).SetProperty(ref _asset, value);
+            set => GetChildSetter(this).SetListProperty(ref _asset, value);
         }
     }
 }

+ 63 - 0
tests/SharpGLTF.Core.Tests/Collections/ChildrenDictionaryTests.cs

@@ -0,0 +1,63 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+using NUnit.Framework;
+
+namespace SharpGLTF.Collections
+{
+    [TestFixture]
+    [Category("Core")]
+    public class ChildrenDictionaryTests
+    {
+        class TestChild : IChildOfDictionary<ChildrenDictionaryTests>
+        {
+            public ChildrenDictionaryTests LogicalParent { get; private set; }
+
+            public string LogicalKey { get; private set; }
+
+            void IChildOfDictionary<ChildrenDictionaryTests>.SetLogicalParent(ChildrenDictionaryTests parent, string key)
+            {
+                LogicalParent = parent;
+                LogicalKey = key;
+            }
+        }
+
+        [Test]
+        public void TestChildCollectionDictionary1()
+        {
+            var dict = new ChildrenDictionary<TestChild, ChildrenDictionaryTests>(this);
+
+            Assert.That(() => dict.Add("key", null), Throws.ArgumentNullException);
+
+            var item1 = new TestChild();
+            Assert.That(item1.LogicalParent, Is.Null);
+            Assert.That(item1.LogicalKey, Is.Null);
+
+            var item2 = new TestChild();
+            Assert.That(item2.LogicalParent, Is.Null);
+            Assert.That(item2.LogicalKey, Is.Null);
+
+            dict["0"] = item1;
+            Assert.That(item1.LogicalParent, Is.SameAs(this));
+            Assert.That(item1.LogicalKey, Is.EqualTo("0"));
+
+            Assert.That(() => dict.Add("1", item1), Throws.ArgumentException);
+
+            dict.Remove("0");
+            Assert.That(item1.LogicalParent, Is.Null);
+            Assert.That(item1.LogicalKey, Is.Null);
+
+            dict["0"] = item1;
+            Assert.That(item1.LogicalParent, Is.SameAs(this));
+            Assert.That(item1.LogicalKey, Is.EqualTo("0"));
+
+            dict["1"] = item2;
+            Assert.That(item1.LogicalParent, Is.SameAs(this));
+            Assert.That(item1.LogicalKey, Is.EqualTo("0"));
+            Assert.That(item2.LogicalParent, Is.SameAs(this));
+            Assert.That(item2.LogicalKey, Is.EqualTo("1"));
+        }
+
+    }
+}

+ 2 - 4
tests/SharpGLTF.Core.Tests/Collections/ChildrenListTests.cs

@@ -26,11 +26,9 @@ namespace SharpGLTF.Collections
         [Test]
         public void TestChildCollectionList1()
         {
-            if (System.Diagnostics.Debugger.IsAttached) return;
-
             var list = new ChildrenList<TestChild, ChildrenListTests>(this);
             
-            Assert.Throws<ArgumentNullException>(() => list.Add(null));
+            Assert.That(() => list.Add(null), Throws.ArgumentNullException);
 
             var item1 = new TestChild();
             Assert.That(item1.LogicalParent, Is.Null);
@@ -44,7 +42,7 @@ namespace SharpGLTF.Collections
             Assert.That(item1.LogicalParent, Is.SameAs(this));
             Assert.That(item1.LogicalIndex, Is.EqualTo(0));
 
-            Assert.Throws<ArgumentException>(() => list.Add(item1));
+            Assert.That(() => list.Add(item1), Throws.ArgumentException);
 
             list.Remove(item1);
             Assert.That(item1.LogicalParent, Is.Null);