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 = Activator.CreateInstance(typeof(ValConv<>).MakeGenericType(t)) as IValConv;
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 ValConvImpls();
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 = null;
///
/// 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)
=> Activator.CreateInstance(ty) as ValueConverter;
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 == 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 == 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 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 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 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());
}
}