#nullable enable using IPA.Config.Data; using IPA.Config.Stores.Attributes; using IPA.Logging; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using UnityEngine; using Boolean = IPA.Config.Data.Boolean; namespace IPA.Config.Stores.Converters { /// /// Provides utility functions for custom converters. /// public static class Converter { /// /// Gets the integral value of a , coercing a if necessary, /// or if is not an or . /// /// the to get the integral value of /// the integral value of , or public static long? IntValue(Value? val) => val is Integer inte ? inte.Value : val is FloatingPoint fp ? fp.AsInteger()?.Value : null; /// /// Gets the floaing point value of a , coercing an if necessary, /// or if is not an or . /// /// the to get the floaing point value of /// the floaing point value of , or public static decimal? FloatValue(Value? val) => val is FloatingPoint fp ? fp.Value : val is Integer inte ? inte.AsFloat()?.Value : null; internal static Type GetDefaultConverterType(Type t) { if (t.IsEnum) { return typeof(CaseInsensitiveEnumConverter<>).MakeGenericType(t); } if (t.IsGenericType) { var generic = t.GetGenericTypeDefinition(); var args = t.GetGenericArguments(); if (generic == typeof(List<>)) return (typeof(ListConverter<>).MakeGenericType(args)); else if (generic == typeof(IList<>)) return (typeof(IListConverter<>).MakeGenericType(args)); else if (generic == typeof(Dictionary<,>) && args[0] == typeof(string)) return (typeof(DictionaryConverter<>).MakeGenericType(args[1])); else if (generic == typeof(IDictionary<,>) && args[0] == typeof(string)) return (typeof(IDictionaryConverter<>).MakeGenericType(args[1])); #if NET4 else if (generic == typeof(ISet<>)) return (typeof(ISetConverter<>).MakeGenericType(args)); else if (generic == typeof(IReadOnlyDictionary<,>) && args[0] == typeof(string)) return (typeof(IReadOnlyDictionaryConverter<>).MakeGenericType(args[1])); #endif } var iCollBase = t.GetInterfaces() .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ICollection<>)); if (iCollBase != null && t.GetConstructor(Type.EmptyTypes) != null) { // if it implements ICollection and has a default constructor var valueType = iCollBase.GetGenericArguments().First(); return (typeof(CollectionConverter<,>).MakeGenericType(valueType, t)); } if (t == typeof(string)) { //Logger.log.Debug($"gives StringConverter"); return typeof(StringConverter); } if (t.IsValueType) { // we have to do this garbo to make it accept the thing that we know is a value type at instantiation time if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>)) { // this is a Nullable //Logger.log.Debug($"gives NullableConverter<{Nullable.GetUnderlyingType(t)}>"); return (typeof(NullableConverter<>).MakeGenericType(Nullable.GetUnderlyingType(t))); } //Logger.log.Debug($"gives converter for value type {t}"); var valConv = (IValConv)Activator.CreateInstance(typeof(ValConv<>).MakeGenericType(t)); return valConv.Get(); } //Logger.log.Debug($"gives CustomObjectConverter<{t}>"); return (typeof(CustomObjectConverter<>).MakeGenericType(t)); } internal interface IValConv { Type Get(); } internal interface IValConv { Type Get(); } internal class ValConv : IValConv, IValConv where T : struct { private static readonly IValConv Impl = ValConvImpls.Impl as IValConv ?? new ValConv(); public Type Get() => Impl.Get(); Type IValConv.Get() => typeof(CustomValueTypeConverter); } private class ValConvImpls : IValConv, IValConv, IValConv, IValConv, IValConv, IValConv, IValConv, IValConv, IValConv, IValConv, IValConv, IValConv, IValConv, IValConv, IValConv, IValConv, IValConv, IValConv { internal static readonly ValConvImpls Impl = new(); Type IValConv.Get() => typeof(CharConverter); Type IValConv.Get() => typeof(LongConverter); Type IValConv.Get() => typeof(ULongConverter); Type IValConv.Get() => typeof(IntPtrConverter); Type IValConv.Get() => typeof(UIntPtrConverter); Type IValConv.Get() => typeof(IntConverter); Type IValConv.Get() => typeof(UIntConverter); Type IValConv.Get() => typeof(ShortConverter); Type IValConv.Get() => typeof(UShortConverter); Type IValConv.Get() => typeof(ByteConverter); Type IValConv.Get() => typeof(SByteConverter); Type IValConv.Get() => typeof(FloatConverter); Type IValConv.Get() => typeof(DoubleConverter); Type IValConv.Get() => typeof(DecimalConverter); Type IValConv.Get() => typeof(BooleanConverter); Type IValConv.Get() => typeof(DateTimeConverter); Type IValConv.Get() => typeof(DateTimeOffsetConverter); Type IValConv.Get() => typeof(TimeSpanConverter); } } /// /// Provides generic utilities for converters for certain types. /// /// the type of the that this works on public static class Converter { private static ValueConverter? defaultConverter; /// /// Gets the default for the current type. /// public static ValueConverter Default => defaultConverter ??= MakeDefault(); internal static ValueConverter MakeDefault() { var t = typeof(T); //Logger.log.Debug($"Converter<{t}>.MakeDefault()"); static ValueConverter MakeInstOf(Type ty) => (ValueConverter)Activator.CreateInstance(ty); return MakeInstOf(Converter.GetDefaultConverterType(t)); } } /// /// A converter for a . /// /// the underlying type of the public class NullableConverter : ValueConverter where T : struct { private readonly ValueConverter baseConverter; /// /// Creates a converter with the default converter for the base type. /// Equivalent to /// /// new NullableConverter(Converter<T>.Default) /// /// /// /// public NullableConverter() : this(Converter.Default) { } /// /// Creates a converter with the given underlying . /// /// the undlerlying to use public NullableConverter(ValueConverter underlying) => baseConverter = underlying; /// /// Converts a tree to a value. /// /// the tree to convert /// the object which will own the created object /// the object represented by public override T? FromValue(Value? value, object parent) => value is null ? null : new T?(baseConverter.FromValue(value, parent)); /// /// Converts a nullable to a tree. /// /// the value to serialize /// the object which owns /// a tree representing . public override Value? ToValue(T? obj, object parent) => obj is 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 sealed 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()) { } } /// /// A converter for an enum of type , that converts the enum to its string representation and back. /// /// the enum type public sealed class EnumConverter : ValueConverter where T : Enum { /// /// Converts a that is a node to the corresponding enum value. /// /// the to convert /// the object which will own the created object /// the deserialized enum value /// if is not a node public override T FromValue(Value? value, object parent) => value is Text t ? (T)Enum.Parse(typeof(T), t.Value) : throw new ArgumentException("Value not a string", nameof(value)); /// /// Converts an enum of type to a node corresponding to its value. /// /// the value to serialize /// the object which owns /// a node representing public override Value? ToValue(T? obj, object parent) => Value.Text(obj?.ToString()); } /// /// A converter for an enum of type , that converts the enum to its string representation and back, /// ignoring the case of the serialized value for deseiralization. /// /// the enum type public sealed class CaseInsensitiveEnumConverter : ValueConverter where T : Enum { /// /// Converts a that is a node to the corresponding enum value. /// /// the to convert /// the object which will own the created object /// the deserialized enum value /// if is not a node public override T FromValue(Value? value, object parent) => value is Text t ? (T)Enum.Parse(typeof(T), t.Value, true) : throw new ArgumentException("Value not a string", nameof(value)); /// /// Converts an enum of type to a node corresponding to its value. /// /// the value to serialize /// the object which owns /// a node representing public override Value? ToValue(T? obj, object parent) => Value.Text(obj?.ToString()); } /// /// A converter for an enum of type , that converts the enum to its underlying value for serialization. /// /// the enum type public sealed class NumericEnumConverter : ValueConverter where T : Enum { /// /// Converts a that is a numeric node to the corresponding enum value. /// /// the to convert /// the object which will own the created object /// the deserialized enum value /// if is not a numeric node public override T FromValue(Value? value, object parent) => (T)Enum.ToObject(typeof(T), Converter.IntValue(value) ?? throw new ArgumentException("Value not a numeric node", nameof(value))); /// /// Converts an enum of type to a node corresponding to its value. /// /// the value to serialize /// the object which owns /// an node representing public override Value ToValue(T? obj, object parent) => Value.Integer(Convert.ToInt64(obj)); } /// /// A converter for instances of . /// /// the value type of the dictionary public class IDictionaryConverter : ValueConverter> { /// /// Gets the converter for the dictionary's value type. /// protected ValueConverter BaseConverter { get; } /// /// Constructs an using the default converter for the value type. /// public IDictionaryConverter() : this(Converter.Default) { } /// /// Constructs an using the specified converter for the value. /// /// the converter for the value public IDictionaryConverter(ValueConverter converter) => BaseConverter = converter; /// /// Converts a to an that is represented by it. /// /// the to convert /// the parent that will own the resulting object /// the deserialized dictionary public override IDictionary FromValue(Value? value, object parent) => ((value as Map)?.Select(kvp => (kvp.Key, val: BaseConverter.FromValue(kvp.Value, parent))) ?.ToDictionary(p => p.Key, p => p.val)) ?? throw new ArgumentException("Value not a map", nameof(value)); /// /// Serializes an into a containing its values. /// /// the dictionary to serialize /// the object that owns the dictionary /// the dictionary serialized as a public override Value? ToValue(IDictionary? obj, object parent) => Value.From(obj.Select(p => new KeyValuePair(p.Key, BaseConverter.ToValue(p.Value, parent)))); } /// /// A converter for instances of , specifying a value converter as a type parameter. /// /// the value type of the dictionary /// the converter type for values public sealed class IDictionaryConverter : IDictionaryConverter where TConverter : ValueConverter, new() { /// /// Constructs a new with a new instance of /// as the value converter. /// public IDictionaryConverter() : base(new TConverter()) { } } /// /// A converter for instances of . /// /// the value type of the dictionary public class DictionaryConverter : ValueConverter> { /// /// Gets the converter for the dictionary's value type. /// protected ValueConverter BaseConverter { get; } /// /// Constructs an using the default converter for the value type. /// public DictionaryConverter() : this(Converter.Default) { } /// /// Constructs an using the specified converter for the value. /// /// the converter for the value public DictionaryConverter(ValueConverter converter) => BaseConverter = converter; /// /// Converts a to a that is represented by it. /// /// the to convert /// the parent that will own the resulting object /// the deserialized dictionary public override Dictionary FromValue(Value? value, object parent) => (value as Map)?.Select(kvp => (kvp.Key, val: BaseConverter.FromValue(kvp.Value, parent))) ?.ToDictionary(p => p.Key, p => p.val) ?? throw new ArgumentException("Value not a map", nameof(value)); /// /// Serializes a into a containing its values. /// /// the dictionary to serialize /// the object that owns the dictionary /// the dictionary serialized as a public override Value? ToValue(Dictionary? obj, object parent) => Value.From(obj?.Select(p => new KeyValuePair(p.Key, BaseConverter.ToValue(p.Value, parent)))); } /// /// A converter for instances of , specifying a value converter as a type parameter. /// /// the value type of the dictionary /// the converter type for values public sealed class DictionaryConverter : DictionaryConverter where TConverter : ValueConverter, new() { /// /// Constructs a new with a new instance of /// as the value converter. /// public DictionaryConverter() : base(new TConverter()) { } } #if NET4 /// /// A converter for instances of . /// /// the value type of the dictionary public class IReadOnlyDictionaryConverter : ValueConverter> { /// /// Gets the converter for the dictionary's value type. /// protected ValueConverter BaseConverter { get; } /// /// Constructs an using the default converter for the value type. /// public IReadOnlyDictionaryConverter() : this(Converter.Default) { } /// /// Constructs an using the specified converter for the value. /// /// the converter for the value public IReadOnlyDictionaryConverter(ValueConverter converter) => BaseConverter = converter; /// /// Converts a to an that is represented by it. /// /// the to convert /// the parent that will own the resulting object /// the deserialized dictionary public override IReadOnlyDictionary FromValue(Value? value, object parent) => (value as Map)?.Select(kvp => (kvp.Key, val: BaseConverter.FromValue(kvp.Value, parent))) ?.ToDictionary(p => p.Key, p => p.val) ?? throw new ArgumentException("Value not a map", nameof(value)); /// /// Serializes an into a containing its values. /// /// the dictionary to serialize /// the object that owns the dictionary /// the dictionary serialized as a public override Value? ToValue(IReadOnlyDictionary? obj, object parent) => Value.From(obj?.Select(p => new KeyValuePair(p.Key, BaseConverter.ToValue(p.Value, parent)))); } /// /// A converter for instances of , specifying a value converter as a type parameter. /// /// the value type of the dictionary /// the converter type for values public sealed class IReadOnlyDictionaryConverter : IReadOnlyDictionaryConverter where TConverter : ValueConverter, new() { /// /// Constructs a new with a new instance of /// as the value converter. /// public IReadOnlyDictionaryConverter() : base(new TConverter()) { } } #endif /// /// A converter for objects. /// public sealed class HexColorConverter : ValueConverter { /// /// Converts a that is a node to the corresponding object. /// /// the to convert /// the object which will own the created object /// the deserialized Color object /// if is not a node or couldn't be parsed into a Color object public override Color FromValue(Value? value, object parent) { if (value is Text t) { if (ColorUtility.TryParseHtmlString(t.Value, out Color color)) { return color; } throw new ArgumentException("Value cannot be parsed into a Color.", nameof(value)); } throw new ArgumentException("Value not a string", nameof(value)); } /// /// Converts color of type to a node. /// /// the object to serialize /// the object which owns /// a node representing public override Value ToValue(Color obj, object parent) => Value.Text($"#{ColorUtility.ToHtmlStringRGB(obj)}"); } internal class StringConverter : ValueConverter { public override string? FromValue(Value? value, object parent) => (value as Text)?.Value; public override Value? ToValue(string? obj, object parent) => Value.From(obj); } internal class CharConverter : ValueConverter { public override char FromValue(Value? value, object parent) => (value as Text)?.Value[0] ?? throw new ArgumentException("Value not a text node", nameof(value)); // can throw nullptr public override Value? ToValue(char obj, object parent) => Value.From(char.ToString(obj)); } internal class LongConverter : ValueConverter { public override long FromValue(Value? value, object parent) => Converter.IntValue(value) ?? throw new ArgumentException("Value not a numeric value", nameof(value)); public override Value? ToValue(long obj, object parent) => Value.From(obj); } internal class ULongConverter : ValueConverter { public override ulong FromValue(Value? value, object parent) => (ulong)(Converter.FloatValue(value) ?? throw new ArgumentException("Value not a numeric value", nameof(value))); public override Value? ToValue(ulong obj, object parent) => Value.From(obj); } internal class IntPtrConverter : ValueConverter { public override IntPtr FromValue(Value? value, object parent) => (IntPtr)Converter.Default.FromValue(value, parent); public override Value? ToValue(IntPtr obj, object parent) => Value.From((long)obj); } internal class UIntPtrConverter : ValueConverter { public override UIntPtr FromValue(Value? value, object parent) => (UIntPtr)Converter.Default.FromValue(value, parent); public override Value? ToValue(UIntPtr obj, object parent) => Value.From((decimal)obj); } internal class IntConverter : ValueConverter { public override int FromValue(Value? value, object parent) => (int)Converter.Default.FromValue(value, parent); public override Value? ToValue(int obj, object parent) => Value.From(obj); } internal class UIntConverter : ValueConverter { public override uint FromValue(Value? value, object parent) => (uint)Converter.Default.FromValue(value, parent); public override Value? ToValue(uint obj, object parent) => Value.From(obj); } internal class ShortConverter : ValueConverter { public override short FromValue(Value? value, object parent) => (short)Converter.Default.FromValue(value, parent); public override Value? ToValue(short obj, object parent) => Value.From(obj); } internal class UShortConverter : ValueConverter { public override ushort FromValue(Value? value, object parent) => (ushort)Converter.Default.FromValue(value, parent); public override Value? ToValue(ushort obj, object parent) => Value.From(obj); } internal class ByteConverter : ValueConverter { public override byte FromValue(Value? value, object parent) => (byte)Converter.Default.FromValue(value, parent); public override Value? ToValue(byte obj, object parent) => Value.From(obj); } internal class SByteConverter : ValueConverter { public override sbyte FromValue(Value? value, object parent) => (sbyte)Converter.Default.FromValue(value, parent); public override Value? ToValue(sbyte obj, object parent) => Value.From(obj); } internal class DecimalConverter : ValueConverter { public override decimal FromValue(Value? value, object parent) => Converter.FloatValue(value) ?? throw new ArgumentException("Value not a numeric value", nameof(value)); public override Value? ToValue(decimal obj, object parent) => Value.From(obj); } internal class FloatConverter : ValueConverter { public override float FromValue(Value? value, object parent) => (float)Converter.Default.FromValue(value, parent); public override Value? ToValue(float obj, object parent) => Value.From((decimal)obj); } internal class DoubleConverter : ValueConverter { public override double FromValue(Value? value, object parent) => (double)Converter.Default.FromValue(value, parent); public override Value? ToValue(double obj, object parent) => Value.From((decimal)obj); } internal class BooleanConverter : ValueConverter { public override bool FromValue(Value? value, object parent) => (value as Boolean)?.Value ?? throw new ArgumentException("Value not a Boolean", nameof(value)); public override Value? ToValue(bool obj, object parent) => Value.From(obj); } internal class DateTimeConverter : ValueConverter { public override DateTime FromValue(Value? value, object parent) { if (value is not Text text) { throw new ArgumentException("Value is not of type Text", nameof(value)); } if (DateTime.TryParse(text.Value, out var dateTime)) { return dateTime; } throw new ArgumentException($"Parsing failed, {text.Value}"); } public override Value? ToValue(DateTime obj, object parent) => Value.Text(obj.ToString("O")); } internal class DateTimeOffsetConverter : ValueConverter { public override DateTimeOffset FromValue(Value? value, object parent) { if (value is not Text text) { throw new ArgumentException("Value is not of type Text", nameof(value)); } if (DateTimeOffset.TryParse(text.Value, out var dateTime)) { return dateTime; } throw new ArgumentException($"Parsing failed, {text.Value}"); } public override Value ToValue(DateTimeOffset obj, object parent) => Value.Text(obj.ToString("O")); } internal class TimeSpanConverter : ValueConverter { public override TimeSpan FromValue(Value? value, object parent) { if (value is not Text text) { throw new ArgumentException("Value is not of type Text", nameof(value)); } if (TimeSpan.TryParse(text.Value, out var dateTime)) { return dateTime; } throw new ArgumentException($"Parsing failed, {text.Value}"); } public override Value? ToValue(TimeSpan obj, object parent) => Value.Text(obj.ToString()); } }