From 87c4da783e8acaec1447e56cbc463aac145f3a9e Mon Sep 17 00:00:00 2001 From: Michael Catanzariti Date: Mon, 19 Aug 2019 01:37:33 +0200 Subject: [PATCH] [feature request] add support for System.Collections.Immutable https://github.com/dahomey-technologies/Dahomey.Cbor/issues/4 - ImmutableArray - ImmutableList - ImmutableSortedSet - ImmutableHashSet - ImmutableDictionary - ImmutableSortedDictionary --- .../ImmutableCollectionsTests.cs | 126 ++++++++++++++++++ src/Dahomey.Cbor/Dahomey.Cbor.csproj | 1 + .../Converters/AbstractCollectionConverter.cs | 79 +++++++++++ .../Converters/AbstractDictionaryConverter.cs | 77 +++++++++++ .../Serialization/Converters/CborConverter.cs | 19 +++ .../Converters/CollectionConverter.cs | 67 +--------- .../Converters/DictionaryConverter.cs | 65 +-------- .../ImmutableCollectionConverter.cs | 36 +++++ .../ImmutableDictionaryConverter.cs | 36 +++++ 9 files changed, 384 insertions(+), 122 deletions(-) create mode 100644 src/Dahomey.Cbor.Tests/ImmutableCollectionsTests.cs create mode 100644 src/Dahomey.Cbor/Serialization/Converters/AbstractCollectionConverter.cs create mode 100644 src/Dahomey.Cbor/Serialization/Converters/AbstractDictionaryConverter.cs create mode 100644 src/Dahomey.Cbor/Serialization/Converters/ImmutableCollectionConverter.cs create mode 100644 src/Dahomey.Cbor/Serialization/Converters/ImmutableDictionaryConverter.cs diff --git a/src/Dahomey.Cbor.Tests/ImmutableCollectionsTests.cs b/src/Dahomey.Cbor.Tests/ImmutableCollectionsTests.cs new file mode 100644 index 0000000..045d656 --- /dev/null +++ b/src/Dahomey.Cbor.Tests/ImmutableCollectionsTests.cs @@ -0,0 +1,126 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Collections.Generic; +using System.Collections.Immutable; + +namespace Dahomey.Cbor.Tests +{ + [TestClass] + public class ImmutableCollectionsTests + { + [TestMethod] + public void WriteImmutableArrayOfInt32() + { + ImmutableArray value = ImmutableArray.CreateRange(new[] { 1, 2, 3 }); + const string expectedHexBuffer = "83010203"; + Helper.TestWrite(value, expectedHexBuffer); + } + + [TestMethod] + public void ReadImmutableArrayOfInt32() + { + ImmutableArray expectedValue = ImmutableArray.CreateRange(new[] { 1, 2, 3 }); + const string hexBuffer = "83010203"; + Helper.TestRead(hexBuffer, expectedValue); + } + + [TestMethod] + public void WriteImmutableListOfInt32() + { + ImmutableList value = ImmutableList.CreateRange(new[] { 1, 2, 3 }); + const string expectedHexBuffer = "83010203"; + Helper.TestWrite(value, expectedHexBuffer); + } + + [TestMethod] + public void ReadImmutableListOfInt32() + { + ImmutableList expectedValue = ImmutableList.CreateRange(new[] { 1, 2, 3 }); + const string hexBuffer = "83010203"; + Helper.TestRead(hexBuffer, expectedValue); + } + + [TestMethod] + public void WriteImmutableHashSetOfInt32() + { + ImmutableHashSet value = ImmutableHashSet.CreateRange(new[] { 1, 2, 3 }); + const string expectedHexBuffer = "83010203"; + Helper.TestWrite(value, expectedHexBuffer); + } + + [TestMethod] + public void ReadImmutableHashSetOfInt32() + { + ImmutableHashSet expectedValue = ImmutableHashSet.CreateRange(new[] { 1, 2, 3 }); + const string hexBuffer = "83010203"; + Helper.TestRead(hexBuffer, expectedValue); + } + + [TestMethod] + public void WriteImmutableSortedSetOfInt32() + { + ImmutableSortedSet value = ImmutableSortedSet.CreateRange(new[] { 1, 2, 3 }); + const string expectedHexBuffer = "83010203"; + Helper.TestWrite(value, expectedHexBuffer); + } + + [TestMethod] + public void ReadImmutableSortedSetOfInt32() + { + ImmutableSortedSet expectedValue = ImmutableSortedSet.CreateRange(new[] { 1, 2, 3 }); + const string hexBuffer = "83010203"; + Helper.TestRead(hexBuffer, expectedValue); + } + + [TestMethod] + public void WriteImmutableDictionaryOfInt32() + { + ImmutableDictionary value = ImmutableDictionary.CreateRange(new Dictionary + { + { 1, 1 }, + { 2, 2 }, + { 3, 3 } + }); + const string expectedHexBuffer = "A3010102020303"; + Helper.TestWrite(value, expectedHexBuffer); + } + + [TestMethod] + public void ReadImmutableDictionaryOfInt32() + { + ImmutableDictionary expectedValue = ImmutableDictionary.CreateRange(new Dictionary + { + { 1, 1 }, + { 2, 2 }, + { 3, 3 } + }); + const string hexBuffer = "A3010102020303"; + Helper.TestRead(hexBuffer, expectedValue); + } + + [TestMethod] + public void WriteImmutableSortedDictionaryOfInt32() + { + ImmutableSortedDictionary value = ImmutableSortedDictionary.CreateRange(new SortedDictionary + { + { 1, 1 }, + { 2, 2 }, + { 3, 3 } + }); + const string expectedHexBuffer = "A3010102020303"; + Helper.TestWrite(value, expectedHexBuffer); + } + + [TestMethod] + public void ReadImmutableSortedDictionaryOfInt32() + { + ImmutableSortedDictionary expectedValue = ImmutableSortedDictionary.CreateRange(new SortedDictionary + { + { 1, 1 }, + { 2, 2 }, + { 3, 3 } + }); + const string hexBuffer = "A3010102020303"; + Helper.TestRead(hexBuffer, expectedValue); + } + } +} diff --git a/src/Dahomey.Cbor/Dahomey.Cbor.csproj b/src/Dahomey.Cbor/Dahomey.Cbor.csproj index 17983e1..f1f78d2 100644 --- a/src/Dahomey.Cbor/Dahomey.Cbor.csproj +++ b/src/Dahomey.Cbor/Dahomey.Cbor.csproj @@ -26,6 +26,7 @@ + diff --git a/src/Dahomey.Cbor/Serialization/Converters/AbstractCollectionConverter.cs b/src/Dahomey.Cbor/Serialization/Converters/AbstractCollectionConverter.cs new file mode 100644 index 0000000..e4558d2 --- /dev/null +++ b/src/Dahomey.Cbor/Serialization/Converters/AbstractCollectionConverter.cs @@ -0,0 +1,79 @@ +using System.Collections.Generic; + +namespace Dahomey.Cbor.Serialization.Converters +{ + public abstract class AbstractCollectionConverter : + CborConverterBase, + ICborArrayReader.ReaderContext>, + ICborArrayWriter.WriterContext> + where TC : ICollection + { + public struct ReaderContext + { + public ICollection collection; + } + + public struct WriterContext + { + public int count; + public IEnumerator enumerator; + } + + private static readonly ICborConverter _itemConverter = CborConverter.Lookup(); + + protected abstract ICollection InstantiateTempCollection(); + protected abstract TC InstantiateCollection(ICollection tempCollection); + + public override TC Read(ref CborReader reader) + { + if (reader.ReadNull()) + { + return default; + } + + ReaderContext context = new ReaderContext(); + reader.ReadArray(this, ref context); + + return InstantiateCollection(context.collection); + } + + public override void Write(ref CborWriter writer, TC value) + { + WriterContext context = new WriterContext + { + count = value.Count, + enumerator = value.GetEnumerator() + }; + writer.WriteArray(this, ref context); + } + + public void ReadBeginArray(int size, ref ReaderContext context) + { + context.collection = InstantiateTempCollection(); + + if (size != -1 && context.collection is List list) + { + list.Capacity = size; + } + } + + public void ReadArrayItem(ref CborReader reader, ref ReaderContext context) + { + TI item = _itemConverter.Read(ref reader); + context.collection.Add(item); + } + + public int GetArraySize(ref WriterContext context) + { + return context.count; + } + + public void WriteArrayItem(ref CborWriter writer, ref WriterContext context) + { + if (context.enumerator.MoveNext()) + { + _itemConverter.Write(ref writer, context.enumerator.Current); + } + } + } +} diff --git a/src/Dahomey.Cbor/Serialization/Converters/AbstractDictionaryConverter.cs b/src/Dahomey.Cbor/Serialization/Converters/AbstractDictionaryConverter.cs new file mode 100644 index 0000000..aeb535d --- /dev/null +++ b/src/Dahomey.Cbor/Serialization/Converters/AbstractDictionaryConverter.cs @@ -0,0 +1,77 @@ +using System.Collections.Generic; + +namespace Dahomey.Cbor.Serialization.Converters +{ + public abstract class AbstractDictionaryConverter : + CborConverterBase, + ICborMapReader.ReaderContext>, + ICborMapWriter.WriterContext> + where TC : IDictionary + { + public struct ReaderContext + { + public IDictionary dict; + } + + public struct WriterContext + { + public int count; + public IEnumerator> enumerator; + } + + private static readonly ICborConverter _keyConverter = CborConverter.Lookup(); + private static readonly ICborConverter _valueConverter = CborConverter.Lookup(); + + protected abstract IDictionary InstantiateTempCollection(); + protected abstract TC InstantiateCollection(IDictionary tempCollection); + + public override TC Read(ref CborReader reader) + { + if (reader.ReadNull()) + { + return default; + } + + ReaderContext context = new ReaderContext(); + reader.ReadMap(this, ref context); + return InstantiateCollection(context.dict); + } + + public override void Write(ref CborWriter writer, TC value) + { + WriterContext context = new WriterContext + { + count = value.Count, + enumerator = value.GetEnumerator() + }; + writer.WriteMap(this, ref context); + } + + public void ReadBeginMap(int size, ref ReaderContext context) + { + context.dict = InstantiateTempCollection(); + } + + public void ReadMapItem(ref CborReader reader, ref ReaderContext context) + { + TK key = _keyConverter.Read(ref reader); + TV value = _valueConverter.Read(ref reader); + + context.dict.Add(key, value); + } + + public int GetMapSize(ref WriterContext context) + { + return context.count; + } + + public void WriteMapItem(ref CborWriter writer, ref WriterContext context) + { + if (context.enumerator.MoveNext()) + { + _keyConverter.Write(ref writer, context.enumerator.Current.Key); + _valueConverter.Write(ref writer, context.enumerator.Current.Value); + } + } + } +} diff --git a/src/Dahomey.Cbor/Serialization/Converters/CborConverter.cs b/src/Dahomey.Cbor/Serialization/Converters/CborConverter.cs index 73e1774..ebe4fbf 100644 --- a/src/Dahomey.Cbor/Serialization/Converters/CborConverter.cs +++ b/src/Dahomey.Cbor/Serialization/Converters/CborConverter.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using System.Reflection; @@ -139,6 +140,14 @@ private static ICborConverter Instantiate(Type type) } if (type.IsGenericType) { + if (type.GetGenericTypeDefinition() == typeof(ImmutableDictionary<,>) + || type.GetGenericTypeDefinition() == typeof(ImmutableSortedDictionary<,>)) + { + Type keyType = type.GetGenericArguments()[0]; + Type valueType = type.GetGenericArguments()[1]; + return (ICborConverter)Activator.CreateInstance(typeof(ImmutableDictionaryConverter<,,>).MakeGenericType(type, keyType, valueType)); + } + if (type.GetInterfaces() .Any(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IDictionary<,>))) { @@ -146,6 +155,16 @@ private static ICborConverter Instantiate(Type type) Type valueType = type.GetGenericArguments()[1]; return (ICborConverter)Activator.CreateInstance(typeof(DictionaryConverter<,,>).MakeGenericType(type, keyType, valueType)); } + + if (type.GetGenericTypeDefinition() == typeof(ImmutableArray<>) + || type.GetGenericTypeDefinition() == typeof(ImmutableList<>) + || type.GetGenericTypeDefinition() == typeof(ImmutableSortedSet<>) + || type.GetGenericTypeDefinition() == typeof(ImmutableHashSet<>)) + { + Type itemType = type.GetGenericArguments()[0]; + return (ICborConverter)Activator.CreateInstance(typeof(ImmutableCollectionConverter<,>).MakeGenericType(type, itemType)); + } + if (type.GetInterfaces() .Any(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(ICollection<>))) { diff --git a/src/Dahomey.Cbor/Serialization/Converters/CollectionConverter.cs b/src/Dahomey.Cbor/Serialization/Converters/CollectionConverter.cs index b9e11fa..c1af2bb 100644 --- a/src/Dahomey.Cbor/Serialization/Converters/CollectionConverter.cs +++ b/src/Dahomey.Cbor/Serialization/Converters/CollectionConverter.cs @@ -3,74 +3,17 @@ namespace Dahomey.Cbor.Serialization.Converters { public class CollectionConverter : - CborConverterBase, - ICborArrayReader.ReaderContext>, - ICborArrayWriter.WriterContext> + AbstractCollectionConverter where TC : class, ICollection, new() { - public struct ReaderContext + protected override TC InstantiateCollection(ICollection tempCollection) { - public TC collection; + return (TC)tempCollection; } - public struct WriterContext + protected override ICollection InstantiateTempCollection() { - public TC collection; - public IEnumerator enumerator; - } - - private static readonly ICborConverter _itemConverter = CborConverter.Lookup(); - - public override TC Read(ref CborReader reader) - { - if (reader.ReadNull()) - { - return null; - } - - ReaderContext context = new ReaderContext(); - reader.ReadArray(this, ref context); - - return context.collection; - } - - public override void Write(ref CborWriter writer, TC value) - { - WriterContext context = new WriterContext - { - collection = value, - enumerator = value.GetEnumerator() - }; - writer.WriteArray(this, ref context); - } - - public void ReadBeginArray(int size, ref ReaderContext context) - { - context.collection = new TC(); - - if (size != -1 && context.collection is List list) - { - list.Capacity = size; - } - } - - public void ReadArrayItem(ref CborReader reader, ref ReaderContext context) - { - TI item = _itemConverter.Read(ref reader); - context.collection.Add(item); - } - - public int GetArraySize(ref WriterContext context) - { - return context.collection.Count; - } - - public void WriteArrayItem(ref CborWriter writer, ref WriterContext context) - { - if (context.enumerator.MoveNext()) - { - _itemConverter.Write(ref writer, context.enumerator.Current); - } + return new TC(); } } } diff --git a/src/Dahomey.Cbor/Serialization/Converters/DictionaryConverter.cs b/src/Dahomey.Cbor/Serialization/Converters/DictionaryConverter.cs index b743917..7414672 100644 --- a/src/Dahomey.Cbor/Serialization/Converters/DictionaryConverter.cs +++ b/src/Dahomey.Cbor/Serialization/Converters/DictionaryConverter.cs @@ -3,72 +3,17 @@ namespace Dahomey.Cbor.Serialization.Converters { public class DictionaryConverter : - CborConverterBase, - ICborMapReader.ReaderContext>, - ICborMapWriter.WriterContext> + AbstractDictionaryConverter where TC : class, IDictionary, new() { - public struct ReaderContext + protected override TC InstantiateCollection(IDictionary tempCollection) { - public TC dict; + return (TC)tempCollection; } - public struct WriterContext + protected override IDictionary InstantiateTempCollection() { - public TC dict; - public IEnumerator> enumerator; - } - - private static readonly ICborConverter _keyConverter = CborConverter.Lookup(); - private static readonly ICborConverter _valueConverter = CborConverter.Lookup(); - - public override TC Read(ref CborReader reader) - { - if (reader.ReadNull()) - { - return null; - } - - ReaderContext context = new ReaderContext(); - reader.ReadMap(this, ref context); - return context.dict; - } - - public override void Write(ref CborWriter writer, TC value) - { - WriterContext context = new WriterContext - { - dict = value, - enumerator = value.GetEnumerator() - }; - writer.WriteMap(this, ref context); - } - - public void ReadBeginMap(int size, ref ReaderContext context) - { - context.dict = new TC(); - } - - public void ReadMapItem(ref CborReader reader, ref ReaderContext context) - { - TK key = _keyConverter.Read(ref reader); - TV value = _valueConverter.Read(ref reader); - - context.dict.Add(key, value); - } - - public int GetMapSize(ref WriterContext context) - { - return context.dict.Count; - } - - public void WriteMapItem(ref CborWriter writer, ref WriterContext context) - { - if (context.enumerator.MoveNext()) - { - _keyConverter.Write(ref writer, context.enumerator.Current.Key); - _valueConverter.Write(ref writer, context.enumerator.Current.Value); - } + return new TC(); } } } diff --git a/src/Dahomey.Cbor/Serialization/Converters/ImmutableCollectionConverter.cs b/src/Dahomey.Cbor/Serialization/Converters/ImmutableCollectionConverter.cs new file mode 100644 index 0000000..df37122 --- /dev/null +++ b/src/Dahomey.Cbor/Serialization/Converters/ImmutableCollectionConverter.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace Dahomey.Cbor.Serialization.Converters +{ + public class ImmutableCollectionConverter : + AbstractCollectionConverter + where TC : ICollection + { + private readonly Func, TC> _createRangeDelegate; + + public ImmutableCollectionConverter() + { + string typeFullName = typeof(TC).GetGenericTypeDefinition().FullName; + string staticTypeFullName = typeFullName.Substring(0, typeFullName.Length - 2); + Assembly assembly = typeof(TC).Assembly; + Type type = assembly.GetType(staticTypeFullName); + MethodInfo methodInfo = type.GetMethods(BindingFlags.Static | BindingFlags.Public) + .First(m => m.IsGenericMethod && m.GetGenericMethodDefinition().Name == "CreateRange") + .MakeGenericMethod(typeof(TI)); + _createRangeDelegate = (Func, TC>)Delegate.CreateDelegate(typeof(Func, TC>), methodInfo); + } + + protected override TC InstantiateCollection(ICollection tempCollection) + { + return _createRangeDelegate(tempCollection); + } + + protected override ICollection InstantiateTempCollection() + { + return new List(); + } + } +} diff --git a/src/Dahomey.Cbor/Serialization/Converters/ImmutableDictionaryConverter.cs b/src/Dahomey.Cbor/Serialization/Converters/ImmutableDictionaryConverter.cs new file mode 100644 index 0000000..c9ccdb5 --- /dev/null +++ b/src/Dahomey.Cbor/Serialization/Converters/ImmutableDictionaryConverter.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace Dahomey.Cbor.Serialization.Converters +{ + public class ImmutableDictionaryConverter : + AbstractDictionaryConverter + where TC : IDictionary + { + private readonly Func>, TC> _createRangeDelegate; + + public ImmutableDictionaryConverter() + { + string typeFullName = typeof(TC).GetGenericTypeDefinition().FullName; + string staticTypeFullName = typeFullName.Substring(0, typeFullName.Length - 2); + Assembly assembly = typeof(TC).Assembly; + Type type = assembly.GetType(staticTypeFullName); + MethodInfo methodInfo = type.GetMethods(BindingFlags.Static | BindingFlags.Public) + .First(m => m.IsGenericMethod && m.GetGenericMethodDefinition().Name == "CreateRange") + .MakeGenericMethod(typeof(TK), typeof(TV)); + _createRangeDelegate = (Func>, TC>)Delegate.CreateDelegate(typeof(Func>, TC>), methodInfo); + } + + protected override TC InstantiateCollection(IDictionary tempCollection) + { + return _createRangeDelegate(tempCollection); + } + + protected override IDictionary InstantiateTempCollection() + { + return new Dictionary(); + } + } +}