From 450b8df2283bcee65f9980ff769a2c2d75462abc Mon Sep 17 00:00:00 2001 From: Anairkoen Schno Date: Sun, 15 Dec 2019 14:45:21 -0600 Subject: [PATCH] Added CollectionConverter and some derivatives --- IPA.Loader/Config/Stores/Attributes.cs | 7 +- .../Config/Stores/CollectionConverter.cs | 129 ++++++++++++++++++ IPA.Loader/Config/Stores/Converters.cs | 19 +++ IPA.Loader/Config/Stores/GeneratedStore.cs | 4 +- IPA.Loader/Config/Stores/ValueConverter.cs | 3 + IPA.Loader/IPA.Loader.csproj | 1 + IPA.Loader/Loader/DisabledConfig.cs | 3 + 7 files changed, 163 insertions(+), 3 deletions(-) create mode 100644 IPA.Loader/Config/Stores/CollectionConverter.cs diff --git a/IPA.Loader/Config/Stores/Attributes.cs b/IPA.Loader/Config/Stores/Attributes.cs index def1d292..d2708b30 100644 --- a/IPA.Loader/Config/Stores/Attributes.cs +++ b/IPA.Loader/Config/Stores/Attributes.cs @@ -23,7 +23,7 @@ namespace IPA.Config.Stores.Attributes /// /// Indicates that a given field or property in an object being wrapped by - /// should be serialized and deserialized using the provided converter instead of the default mechanism. + /// should be serialized and deserialized using the provided converter instead of the default mechanism. /// [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = true)] public sealed class UseConverterAttribute : Attribute @@ -40,6 +40,11 @@ namespace IPA.Config.Stores.Attributes ConverterType.BaseType.GetGenericArguments()[0] : null; + /// + /// Gets whether or not this converter is a generic . + /// + public bool IsGenericConverter => ConverterTargetType != null; + /// /// Creates a new with a given . /// diff --git a/IPA.Loader/Config/Stores/CollectionConverter.cs b/IPA.Loader/Config/Stores/CollectionConverter.cs new file mode 100644 index 00000000..29a47794 --- /dev/null +++ b/IPA.Loader/Config/Stores/CollectionConverter.cs @@ -0,0 +1,129 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using IPA.Config.Data; + +namespace IPA.Config.Stores.Converters +{ + /// + /// A base class for all type converters, providing most of the functionality. + /// + /// the type of the items in the collection + /// the instantiated type of collection + public class CollectionConverter : ValueConverter + where TCollection : ICollection + { + /// + /// Creates a using the default converter for the + /// element type. Equivalent to calling + /// with . + /// + /// + public CollectionConverter() : this(Converter.Default) { } + /// + /// Creates a using the specified underlying converter. + /// + /// the to use to convert the values + public CollectionConverter(ValueConverter underlying) + => BaseConverter = underlying; + + /// + /// Gets the converter for the collection's value type. + /// + protected ValueConverter BaseConverter { get; } + /// + /// Creates a collection of type using the and + /// . + /// + /// the initial size of the collecion + /// the object that will own the new collection + /// a new instance of + /// + protected virtual TCollection Create(int size, object parent) + => Activator.CreateInstance(); + /// + /// Populates the colleciton with the deserialized values from + /// with the parent . + /// + /// the collection to populate + /// the values to populate it with + /// the object that will own the new objects + /// + protected void PopulateFromValue(TCollection col, List list, object parent) + { + foreach (var it in list) + col.Add(BaseConverter.FromValue(it, parent)); + } + /// + /// Deserializes a in into a new + /// owned by . + /// + /// the to convert to a + /// the object that will own the resulting + /// a new holding the deserialized content of + /// + public override TCollection FromValue(Value value, object parent) + { + if (!(value is List list)) throw new ArgumentException("Argument not a List", nameof(value)); + + var col = Create(list.Count, parent); + PopulateFromValue(col, list, parent); + return col; + } + /// + /// Serializes a into a . + /// + /// the to serialize + /// the object owning + /// the that was serialized into + /// + public override Value ToValue(TCollection obj, object parent) + => Value.From(obj.Select(t => BaseConverter.ToValue(t, parent))); + } + /// + /// A which default constructs a converter for use as the value converter. + /// + /// the value type of the collection + /// the type of the colleciton + /// the type of the converter to use for + /// + public class CollectionConverter : CollectionConverter + where TCollection : ICollection + where TConverter : ValueConverter, new() + { + /// + /// Creates a using the default converter for the + /// element type. Equivalent to calling + /// with a default-constructed . + /// + /// + public CollectionConverter() : base(new TConverter()) { } + } + + public class ISetConverter : CollectionConverter> + { + public ISetConverter() : base() { } + public ISetConverter(ValueConverter underlying) : base(underlying) { } + protected override ISet Create(int size, object parent) + => new HashSet(); + } + + public class ListConverter : CollectionConverter> + { + public ListConverter() : base() { } + public ListConverter(ValueConverter underlying) : base(underlying) { } + protected override List Create(int size, object parent) + => new List(size); + } + + public class IListConverter : CollectionConverter> + { + public IListConverter() : base() { } + public IListConverter(ValueConverter underlying) : base(underlying) { } + protected override IList Create(int size, object parent) + => new List(size); + } + +} diff --git a/IPA.Loader/Config/Stores/Converters.cs b/IPA.Loader/Config/Stores/Converters.cs index 605bd08d..5f5a0a0b 100644 --- a/IPA.Loader/Config/Stores/Converters.cs +++ b/IPA.Loader/Config/Stores/Converters.cs @@ -1,4 +1,5 @@ using IPA.Config.Data; +using IPA.Config.Stores.Attributes; using System; using System.Collections.Generic; using System.Linq; @@ -171,6 +172,24 @@ namespace IPA.Config.Stores.Converters => obj == null ? null : baseConverter.ToValue(obj.Value, parent); } + /// + /// A converter for a that default-constructs a converter of type + /// to use as the underlying converter. Use this in the . + /// + /// the underlying type of the + /// the type to use as an underlying converter + /// + public class NullableConverter : NullableConverter + where T : struct + where TConverter : ValueConverter, new() + { + /// + /// Creates a converter with a new as the underlying converter. + /// + /// + public NullableConverter() : base(new TConverter()) { } + } + internal class StringConverter : ValueConverter { public override string FromValue(Value value, object parent) diff --git a/IPA.Loader/Config/Stores/GeneratedStore.cs b/IPA.Loader/Config/Stores/GeneratedStore.cs index 83903abf..67114b74 100644 --- a/IPA.Loader/Config/Stores/GeneratedStore.cs +++ b/IPA.Loader/Config/Stores/GeneratedStore.cs @@ -325,10 +325,10 @@ namespace IPA.Config.Stores var converterAttr = attrs.Select(o => o as UseConverterAttribute).NonNull().FirstOrDefault(); if (converterAttr != null) - { + { // TODO: figure out how to represent chaining member.Converter = converterAttr.ConverterType; member.ConverterTarget = converterAttr.ConverterTargetType; - member.IsGenericConverter = member.ConverterTarget != null; + member.IsGenericConverter = converterAttr.IsGenericConverter; } return true; diff --git a/IPA.Loader/Config/Stores/ValueConverter.cs b/IPA.Loader/Config/Stores/ValueConverter.cs index ec5b9628..bd3dd555 100644 --- a/IPA.Loader/Config/Stores/ValueConverter.cs +++ b/IPA.Loader/Config/Stores/ValueConverter.cs @@ -19,6 +19,9 @@ namespace IPA.Config.Stores /// be (ideally) the the top of the serialization tree, or some other generated object in that tree, rather than some arbitrary /// object in the middle that is not managed by the generatd config system. /// + /// + /// Converters do not need to perform null checks, as the serializer and deserializer will do that automatically. + /// /// public interface IValueConverter { diff --git a/IPA.Loader/IPA.Loader.csproj b/IPA.Loader/IPA.Loader.csproj index 5a97c74b..a4dec5e0 100644 --- a/IPA.Loader/IPA.Loader.csproj +++ b/IPA.Loader/IPA.Loader.csproj @@ -97,6 +97,7 @@ + diff --git a/IPA.Loader/Loader/DisabledConfig.cs b/IPA.Loader/Loader/DisabledConfig.cs index b27b3261..eb8a9473 100644 --- a/IPA.Loader/Loader/DisabledConfig.cs +++ b/IPA.Loader/Loader/DisabledConfig.cs @@ -1,6 +1,7 @@ using IPA.Config; using IPA.Config.Stores; using IPA.Config.Stores.Attributes; +using IPA.Config.Stores.Converters; using IPA.Utilities; using System; using System.Collections.Generic; @@ -24,6 +25,8 @@ namespace IPA.Loader public virtual bool Reset { get; set; } = true; + [NonNullable] + [UseConverter(typeof(CollectionConverter>))] public virtual HashSet DisabledModIds { get; set; } = new HashSet(); protected virtual void OnReload()