From e0ce1813a214456770a1d534d21ff1b18dfa102a Mon Sep 17 00:00:00 2001 From: RogueException Date: Wed, 9 Aug 2017 02:08:44 -0300 Subject: [PATCH] Cleaned up Serializers, removed SerializationFormat --- src/Discord.Net.Rest/DiscordRestApiClient.cs | 9 -- src/Discord.Net.Rest/DiscordRestClient.cs | 3 +- .../Net/Queue/RequestQueueBucket.cs | 6 +- .../Converters}/EntityOrIdPropertyConverter.cs | 0 .../Converters}/ImagePropertyConverter.cs | 0 .../Converters}/Int53PropertyConverter.cs | 0 .../Converters}/OptionalPropertyConverter.cs | 0 .../Converters}/UInt53PropertyConverter.cs | 0 .../Serialization/Json/DiscordJsonSerializer.cs | 23 +++++ .../ConverterCollection.cs | 60 ++++++++---- .../Json/Converters/Object.cs | 9 +- .../Json/{JsonFormat.cs => JsonSerializer.cs} | 36 +++---- .../SerializationFormat.cs | 55 ----------- src/Discord.Net.Serialization/Serializer.cs | 106 ++++++++++++++++++--- 14 files changed, 184 insertions(+), 123 deletions(-) rename src/Discord.Net.Rest/Serialization/{JsonConverters => Json/Converters}/EntityOrIdPropertyConverter.cs (100%) rename src/Discord.Net.Rest/Serialization/{JsonConverters => Json/Converters}/ImagePropertyConverter.cs (100%) rename src/Discord.Net.Rest/Serialization/{JsonConverters => Json/Converters}/Int53PropertyConverter.cs (100%) rename src/Discord.Net.Rest/Serialization/{JsonConverters => Json/Converters}/OptionalPropertyConverter.cs (100%) rename src/Discord.Net.Rest/Serialization/{JsonConverters => Json/Converters}/UInt53PropertyConverter.cs (100%) create mode 100644 src/Discord.Net.Rest/Serialization/Json/DiscordJsonSerializer.cs rename src/Discord.Net.Serialization/Json/{JsonFormat.cs => JsonSerializer.cs} (67%) delete mode 100644 src/Discord.Net.Serialization/SerializationFormat.cs diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs index 833ec7d6e..5574afaa8 100644 --- a/src/Discord.Net.Rest/DiscordRestApiClient.cs +++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs @@ -24,15 +24,6 @@ namespace Discord.API { internal class DiscordRestApiClient : IDisposable { - static DiscordRestApiClient() - { - SerializationFormat.Json.AddConverter(); - SerializationFormat.Json.AddConverter((type, prop) => prop?.GetCustomAttribute() != null); - SerializationFormat.Json.AddConverter((type, prop) => prop?.GetCustomAttribute() != null); - SerializationFormat.Json.AddGenericConverter(typeof(EntityOrId<>), typeof(EntityOrIdPropertyConverter<>)); - SerializationFormat.Json.AddGenericConverter(typeof(Optional<>), typeof(OptionalPropertyConverter<>)); - } - private static readonly ConcurrentDictionary> _bucketIdGenerators = new ConcurrentDictionary>(); public event Func SentRequest { add { _sentRequestEvent.Add(value); } remove { _sentRequestEvent.Remove(value); } } diff --git a/src/Discord.Net.Rest/DiscordRestClient.cs b/src/Discord.Net.Rest/DiscordRestClient.cs index b49303043..8c4e7cafa 100644 --- a/src/Discord.Net.Rest/DiscordRestClient.cs +++ b/src/Discord.Net.Rest/DiscordRestClient.cs @@ -1,4 +1,5 @@ using Discord.Serialization; +using Discord.Serialization.Json; using System.Collections.Generic; using System.Collections.Immutable; using System.IO; @@ -16,7 +17,7 @@ namespace Discord.Rest public DiscordRestClient() : this(new DiscordRestConfig()) { } public DiscordRestClient(DiscordRestConfig config) : base(config) { - _serializer = new Serializer(SerializationFormat.Json); + _serializer = DiscordJsonSerializer.Global.CreateScope(); _serializer.Error += ex => { _restLogger.WarningAsync("Serializer Error", ex).GetAwaiter().GetResult(); diff --git a/src/Discord.Net.Rest/Net/Queue/RequestQueueBucket.cs b/src/Discord.Net.Rest/Net/Queue/RequestQueueBucket.cs index 6fd212eed..bfd47796e 100644 --- a/src/Discord.Net.Rest/Net/Queue/RequestQueueBucket.cs +++ b/src/Discord.Net.Rest/Net/Queue/RequestQueueBucket.cs @@ -1,4 +1,6 @@ -using Discord.Serialization; +using Discord.Rest; +using Discord.Serialization; +using Discord.Serialization.Json; using System; #if DEBUG_LIMITS using System.Diagnostics; @@ -104,7 +106,7 @@ namespace Discord.Net.Queue { try { - var error = Serializer.Json.Read(response.Data); + var error = DiscordJsonSerializer.Global.Read(response.Data); code = error.Code; reason = error.Message; } diff --git a/src/Discord.Net.Rest/Serialization/JsonConverters/EntityOrIdPropertyConverter.cs b/src/Discord.Net.Rest/Serialization/Json/Converters/EntityOrIdPropertyConverter.cs similarity index 100% rename from src/Discord.Net.Rest/Serialization/JsonConverters/EntityOrIdPropertyConverter.cs rename to src/Discord.Net.Rest/Serialization/Json/Converters/EntityOrIdPropertyConverter.cs diff --git a/src/Discord.Net.Rest/Serialization/JsonConverters/ImagePropertyConverter.cs b/src/Discord.Net.Rest/Serialization/Json/Converters/ImagePropertyConverter.cs similarity index 100% rename from src/Discord.Net.Rest/Serialization/JsonConverters/ImagePropertyConverter.cs rename to src/Discord.Net.Rest/Serialization/Json/Converters/ImagePropertyConverter.cs diff --git a/src/Discord.Net.Rest/Serialization/JsonConverters/Int53PropertyConverter.cs b/src/Discord.Net.Rest/Serialization/Json/Converters/Int53PropertyConverter.cs similarity index 100% rename from src/Discord.Net.Rest/Serialization/JsonConverters/Int53PropertyConverter.cs rename to src/Discord.Net.Rest/Serialization/Json/Converters/Int53PropertyConverter.cs diff --git a/src/Discord.Net.Rest/Serialization/JsonConverters/OptionalPropertyConverter.cs b/src/Discord.Net.Rest/Serialization/Json/Converters/OptionalPropertyConverter.cs similarity index 100% rename from src/Discord.Net.Rest/Serialization/JsonConverters/OptionalPropertyConverter.cs rename to src/Discord.Net.Rest/Serialization/Json/Converters/OptionalPropertyConverter.cs diff --git a/src/Discord.Net.Rest/Serialization/JsonConverters/UInt53PropertyConverter.cs b/src/Discord.Net.Rest/Serialization/Json/Converters/UInt53PropertyConverter.cs similarity index 100% rename from src/Discord.Net.Rest/Serialization/JsonConverters/UInt53PropertyConverter.cs rename to src/Discord.Net.Rest/Serialization/Json/Converters/UInt53PropertyConverter.cs diff --git a/src/Discord.Net.Rest/Serialization/Json/DiscordJsonSerializer.cs b/src/Discord.Net.Rest/Serialization/Json/DiscordJsonSerializer.cs new file mode 100644 index 000000000..e2a4038c5 --- /dev/null +++ b/src/Discord.Net.Rest/Serialization/Json/DiscordJsonSerializer.cs @@ -0,0 +1,23 @@ +using Discord.Serialization.Json.Converters; +using System; +using System.Reflection; + +namespace Discord.Serialization.Json +{ + internal class DiscordJsonSerializer : JsonSerializer + { + private static readonly Lazy _singleton = new Lazy(); + public static new DiscordJsonSerializer Global => _singleton.Value; + + public DiscordJsonSerializer() + { + AddConverter(); + AddConverter((type, prop) => prop?.GetCustomAttribute() != null); + AddConverter((type, prop) => prop?.GetCustomAttribute() != null); + AddGenericConverter(typeof(API.EntityOrId<>), typeof(EntityOrIdPropertyConverter<>)); + AddGenericConverter(typeof(Optional<>), typeof(OptionalPropertyConverter<>)); + } + protected DiscordJsonSerializer(JsonSerializer parent) : base(parent) { } + public new DiscordJsonSerializer CreateScope() => new DiscordJsonSerializer(this); + } +} diff --git a/src/Discord.Net.Serialization/ConverterCollection.cs b/src/Discord.Net.Serialization/ConverterCollection.cs index eee25dfbe..e2d8f0eb7 100644 --- a/src/Discord.Net.Serialization/ConverterCollection.cs +++ b/src/Discord.Net.Serialization/ConverterCollection.cs @@ -1,11 +1,12 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Linq; using System.Reflection; namespace Discord.Serialization { - public class ConverterCollection + internal class ConverterCollection { private class ConverterTypeCollection { @@ -13,27 +14,30 @@ namespace Discord.Serialization public List<(Func Condition, Type ConverterType)> Conditionals = new List<(Func, Type)>(); } - private static readonly MethodInfo _getConverterMethod - = typeof(ConverterCollection).GetTypeInfo().GetDeclaredMethod(nameof(Get)); + private static readonly TypeInfo _serializerType = typeof(Serializer).GetTypeInfo(); + private readonly Serializer _serializer; private readonly ConcurrentDictionary _cache = new ConcurrentDictionary(); private readonly Dictionary _types = new Dictionary(); private readonly Dictionary _mappedGenericTypes = new Dictionary(); private readonly ConverterTypeCollection _genericTypes = new ConverterTypeCollection(); - internal ConverterCollection() { } + internal ConverterCollection(Serializer serializer) + { + _serializer = serializer; + } - public void Add() + public void Add(Type type, Type converterType) { - if (!_types.TryGetValue(typeof(TType), out var converters)) - _types.Add(typeof(TType), converters = new ConverterTypeCollection()); - converters.DefaultConverterType = typeof(TConverter); + if (!_types.TryGetValue(type, out var converters)) + _types.Add(type, converters = new ConverterTypeCollection()); + converters.DefaultConverterType = converterType; } - public void Add(Func condition) + public void Add(Type type, Type converterType, Func condition) { - if (!_types.TryGetValue(typeof(TType), out var converters)) - _types.Add(typeof(TType), converters = new ConverterTypeCollection()); - converters.Conditionals.Add((condition, typeof(TConverter))); + if (!_types.TryGetValue(type, out var converters)) + _types.Add(type, converters = new ConverterTypeCollection()); + converters.Conditionals.Add((condition, converterType)); } public void AddGeneric(Type openConverterType) @@ -63,12 +67,12 @@ namespace Discord.Serialization converters.Conditionals.Add((condition, openConverterType)); } - public object Get(PropertyInfo propInfo = null) + public object Get(Type type, PropertyInfo propInfo = null) { - if (!_cache.TryGetValue(typeof(TType), out var result)) + if (!_cache.TryGetValue(type, out var result)) { - object converter = Create(typeof(TType), propInfo); - result = _cache.GetOrAdd(typeof(TType), converter); + object converter = Create(type, propInfo); + result = _cache.GetOrAdd(type, converter); } return result; } @@ -84,7 +88,7 @@ namespace Discord.Serialization { var innerType = typeInfo.GenericTypeArguments[0]; converterType = converterType.MakeGenericType(innerType); - object innerConverter = GetInnerConverter(innerType, propInfo); + object innerConverter = Get(innerType, propInfo); return Activator.CreateInstance(converterType, innerConverter); } } @@ -102,14 +106,30 @@ namespace Discord.Serialization if (converterType != null) { converterType = converterType.MakeGenericType(type); - return Activator.CreateInstance(converterType); + var converterTypeInfo = converterType.GetTypeInfo(); + + var constructors = converterTypeInfo.DeclaredConstructors.ToArray(); + if (constructors.Length == 0) + throw new SerializationException($"{converterType.Name} is missing a constructor"); + if (constructors.Length != 1) + throw new SerializationException($"{converterType.Name} has multiple constructors"); + var constructor = constructors[0]; + var parameters = constructor.GetParameters(); + + if (parameters.Length == 0) + return constructor.Invoke(null); + else if (parameters.Length == 1) + { + var parameterType = parameters[0].ParameterType.GetTypeInfo(); + if (_serializerType.IsAssignableFrom(parameterType)) + return constructor.Invoke(new object[] { _serializer }); + } + throw new SerializationException($"{converterType.Name} has an unsupported constructor"); } } throw new InvalidOperationException($"Unsupported model type: {type.Name}"); } - private object GetInnerConverter(Type type, PropertyInfo propInfo) - => _getConverterMethod.MakeGenericMethod(type).Invoke(this, new object[] { propInfo }); private Type FindConverterType(Type type, Dictionary collection, TypeInfo typeInfo, PropertyInfo propInfo) { diff --git a/src/Discord.Net.Serialization/Json/Converters/Object.cs b/src/Discord.Net.Serialization/Json/Converters/Object.cs index 1e206fae3..115b369f1 100644 --- a/src/Discord.Net.Serialization/Json/Converters/Object.cs +++ b/src/Discord.Net.Serialization/Json/Converters/Object.cs @@ -5,8 +5,13 @@ namespace Discord.Serialization.Json.Converters internal class ObjectPropertyConverter : IJsonPropertyConverter where T : class, new() { - private static readonly ModelMap _map = SerializationFormat.Json.MapModel(); - + private readonly ModelMap _map; + + public ObjectPropertyConverter(Serializer serializer) + { + _map = serializer.MapModel(); + } + public T Read(PropertyMap map, ref JsonReader reader, bool isTopLevel) { var model = new T(); diff --git a/src/Discord.Net.Serialization/Json/JsonFormat.cs b/src/Discord.Net.Serialization/Json/JsonSerializer.cs similarity index 67% rename from src/Discord.Net.Serialization/Json/JsonFormat.cs rename to src/Discord.Net.Serialization/Json/JsonSerializer.cs index 074b39c7c..edf8f58c2 100644 --- a/src/Discord.Net.Serialization/Json/JsonFormat.cs +++ b/src/Discord.Net.Serialization/Json/JsonSerializer.cs @@ -1,5 +1,4 @@ -using Discord.Serialization.Json.Converters; -using System; +using System; using System.Collections.Generic; using System.Reflection; using System.Text; @@ -8,9 +7,12 @@ using System.Text.Json; namespace Discord.Serialization.Json { - public class JsonFormat : SerializationFormat + public class JsonSerializer : Serializer { - public JsonFormat() + private static readonly Lazy _singleton = new Lazy(); + public static JsonSerializer Global => _singleton.Value; + + public JsonSerializer() { AddConverter(); AddConverter(); @@ -41,42 +43,34 @@ namespace Discord.Serialization.Json AddGenericConverter(typeof(Converters.EnumPropertyConverter<>), (type, prop) => type.IsEnum); AddGenericConverter(typeof(Converters.ObjectPropertyConverter<>), (type, prop) => type.IsClass); } + protected JsonSerializer(JsonSerializer parent) : base(parent) { } + public JsonSerializer CreateScope() => new JsonSerializer(this); public void AddConverter() where TConverter : class, IJsonPropertyConverter - => _converters.Add(); + => AddConverter(typeof(TValue), typeof(TConverter)); public void AddConverter(Func condition) where TConverter : class, IJsonPropertyConverter - => _converters.Add(condition); - - public void AddGenericConverter(Type converter) - => _converters.AddGeneric(converter); - public void AddGenericConverter(Type converter, Func condition) - => _converters.AddGeneric(converter, condition); - public void AddGenericConverter(Type value, Type converter) - => _converters.AddGeneric(value, converter); - public void AddGenericConverter(Type value, Type converter, Func condition) - => _converters.AddGeneric(value, converter, condition); + => AddConverter(typeof(TValue), typeof(TConverter), condition); protected override PropertyMap CreatePropertyMap(PropertyInfo propInfo) { - var converter = (IJsonPropertyConverter)_converters.Get(propInfo); + var converter = (IJsonPropertyConverter)GetConverter(typeof(TValue), propInfo); return new JsonPropertyMap(propInfo, converter); } - protected internal override TModel Read(Serializer serializer, ReadOnlyBuffer data) + public override TModel Read(ReadOnlyBuffer data) { var reader = new JsonReader(data.Span, SymbolTable.InvariantUtf8); if (!reader.Read()) return null; - var converter = _converters.Get() as IJsonPropertyConverter; + var converter = GetConverter(typeof(TModel)) as IJsonPropertyConverter; return converter.Read(null, ref reader, false); } - - protected internal override void Write(Serializer serializer, ArrayFormatter stream, TModel model) + public override void Write(ArrayFormatter stream, TModel model) { var writer = new JsonWriter(stream); - var converter = _converters.Get() as IJsonPropertyConverter; + var converter = GetConverter(typeof(TModel)) as IJsonPropertyConverter; converter.Write(null, ref writer, model, false); } } diff --git a/src/Discord.Net.Serialization/SerializationFormat.cs b/src/Discord.Net.Serialization/SerializationFormat.cs deleted file mode 100644 index b04ac2011..000000000 --- a/src/Discord.Net.Serialization/SerializationFormat.cs +++ /dev/null @@ -1,55 +0,0 @@ -using Discord.Serialization.Json; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Text.Formatting; - -namespace Discord.Serialization -{ - public abstract class SerializationFormat - { - private static readonly MethodInfo _getConverterMethod - = typeof(SerializationFormat).GetTypeInfo().GetDeclaredMethod(nameof(CreatePropertyMap)); - - private static readonly Lazy _json = new Lazy(() => new JsonFormat()); - public static JsonFormat Json => _json.Value; - - protected readonly ConcurrentDictionary _maps = new ConcurrentDictionary(); - protected readonly ConverterCollection _converters = new ConverterCollection(); - - protected internal ModelMap MapModel() - where TModel : class, new() - { - return _maps.GetOrAdd(typeof(TModel), _ => - { - var type = typeof(TModel).GetTypeInfo(); - var propInfos = type.DeclaredProperties - .Where(x => x.CanRead && x.CanWrite) - .ToArray(); - - var properties = new List(); - for (int i = 0; i < propInfos.Length; i++) - { - var propMap = MapProperty(propInfos[i]); - properties.Add(propMap); - } - return new ModelMap(properties); - }) as ModelMap; - } - - private PropertyMap MapProperty(PropertyInfo propInfo) - where TModel : class, new() - => _getConverterMethod.MakeGenericMethod(typeof(TModel), propInfo.PropertyType).Invoke(this, new object[] { propInfo }) as PropertyMap; - - - protected internal abstract TModel Read(Serializer serializer, ReadOnlyBuffer data) - where TModel : class, new(); - protected internal abstract void Write(Serializer serializer, ArrayFormatter stream, TModel model) - where TModel : class, new(); - - protected abstract PropertyMap CreatePropertyMap(PropertyInfo propInfo) - where TModel : class, new(); - } -} diff --git a/src/Discord.Net.Serialization/Serializer.cs b/src/Discord.Net.Serialization/Serializer.cs index 7c08177e6..1b8bf7928 100644 --- a/src/Discord.Net.Serialization/Serializer.cs +++ b/src/Discord.Net.Serialization/Serializer.cs @@ -1,27 +1,107 @@ using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; using System.Text.Formatting; namespace Discord.Serialization { - public class Serializer + public abstract class Serializer { - private readonly static Lazy _json = new Lazy(() => new Serializer(SerializationFormat.Json)); - public static Serializer Json => _json.Value; - public event Action Error; //TODO: Impl - private readonly SerializationFormat _format; + private static readonly MethodInfo _createPropertyMapMethod + = typeof(Serializer).GetTypeInfo().GetDeclaredMethod(nameof(CreatePropertyMap)); + + private readonly ConcurrentDictionary _maps; + private readonly ConverterCollection _converters; + + public bool IsScoped { get; } + + protected Serializer() + { + _maps = new ConcurrentDictionary(); + _converters = new ConverterCollection(this); + IsScoped = false; + } + protected Serializer(Serializer parent) + { + _maps = parent._maps; + _converters = parent._converters; + IsScoped = true; + } + + protected object GetConverter(Type type, PropertyInfo propInfo = null) + => _converters.Get(type, propInfo); + + public void AddConverter(Type type, Type converter) + { + CheckScoped(); + _converters.Add(type, converter); + } + public void AddConverter(Type type, Type converter, Func condition) + { + CheckScoped(); + _converters.Add(type, converter, condition); + } + + public void AddGenericConverter(Type converter) + { + CheckScoped(); + _converters.AddGeneric(converter); + } + public void AddGenericConverter(Type converter, Func condition) + { + CheckScoped(); + _converters.AddGeneric(converter, condition); + } + public void AddGenericConverter(Type value, Type converter) + { + CheckScoped(); + _converters.AddGeneric(value, converter); + } + public void AddGenericConverter(Type value, Type converter, Func condition) + { + CheckScoped(); + _converters.AddGeneric(value, converter, condition); + } - public Serializer(SerializationFormat format) + protected internal ModelMap MapModel() + where TModel : class, new() { - _format = format; + return _maps.GetOrAdd(typeof(TModel), _ => + { + var type = typeof(TModel).GetTypeInfo(); + var propInfos = type.DeclaredProperties + .Where(x => x.CanRead && x.CanWrite) + .ToArray(); + + var properties = new List(); + for (int i = 0; i < propInfos.Length; i++) + { + var propMap = MapProperty(propInfos[i]); + properties.Add(propMap); + } + return new ModelMap(properties); + }) as ModelMap; } - public T Read(ReadOnlyBuffer data) - where T : class, new() - => _format.Read(this, data); - public void Write(ArrayFormatter data, T obj) - where T : class, new() - => _format.Write(this, data, obj); + private PropertyMap MapProperty(PropertyInfo propInfo) + where TModel : class, new() + => _createPropertyMapMethod.MakeGenericMethod(typeof(TModel), propInfo.PropertyType).Invoke(this, new object[] { propInfo }) as PropertyMap; + protected abstract PropertyMap CreatePropertyMap(PropertyInfo propInfo) + where TModel : class, new(); + + public abstract TModel Read(ReadOnlyBuffer data) + where TModel : class, new(); + public abstract void Write(ArrayFormatter stream, TModel model) + where TModel : class, new(); + + private void CheckScoped() + { + if (IsScoped) + throw new InvalidOperationException("Scoped serializers are read-only"); + } } }