Browse Source

Started adding converters support

pull/46/head
Anairkoen Schno 5 years ago
parent
commit
6aafbc0ac9
4 changed files with 122 additions and 12 deletions
  1. +34
    -0
      IPA.Loader/Config/Stores/Attributes.cs
  2. +21
    -12
      IPA.Loader/Config/Stores/GeneratedStore.cs
  3. +66
    -0
      IPA.Loader/Config/Stores/ValueConverter.cs
  4. +1
    -0
      IPA.Loader/IPA.Loader.csproj

+ 34
- 0
IPA.Loader/Config/Stores/Attributes.cs View File

@ -21,6 +21,40 @@ namespace IPA.Config.Stores.Attributes
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = true)] [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public sealed class NonNullableAttribute : Attribute { } public sealed class NonNullableAttribute : Attribute { }
/// <summary>
/// Indicates that a given field or property in an object being wrapped by <see cref="GeneratedExtension.Generated{T}(Config, bool)"/>
/// should be serialized and deserialized using the provided converter instead of the default mechanism.
/// </summary>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public sealed class UseConverterAttribute : Attribute
{
/// <summary>
/// Gets the type of the converter to use.
/// </summary>
public Type ConverterType { get; private set; }
/// <summary>
/// Gets the target type of the converter if it is avaliable at instantiation time, otherwise
/// <see langword="null"/>.
/// </summary>
public Type ConverterTargetType => ConverterType.BaseType.IsGenericType ?
ConverterType.BaseType.GetGenericArguments()[0] :
null;
/// <summary>
/// Creates a new <see cref="UseConverterAttribute"/> with a given <see cref="ConverterType"/>.
/// </summary>
/// <param name="converterType">tpy type to assign to <see cref="ConverterType"/></param>
public UseConverterAttribute(Type converterType)
{
ConverterType = converterType;
var implInterface = ConverterType.GetInterfaces().Contains(typeof(IValueConverter));
var inheritGeneric = ConverterType.BaseType.IsGenericType &&
ConverterType.BaseType.GetGenericTypeDefinition() == typeof(ValueConverter<>);
if (!implInterface && !inheritGeneric) throw new ArgumentException("Type is not a value converter!");
}
}
/// <summary> /// <summary>
/// Specifies a name for the serialized field or property in an object being wrapped by /// Specifies a name for the serialized field or property in an object being wrapped by
/// <see cref="GeneratedExtension.Generated{T}(Config, bool)"/> that is different from the member name itself. /// <see cref="GeneratedExtension.Generated{T}(Config, bool)"/> that is different from the member name itself.


+ 21
- 12
IPA.Loader/Config/Stores/GeneratedStore.cs View File

@ -245,6 +245,10 @@ namespace IPA.Config.Stores
public bool IsField; public bool IsField;
public bool IsNullable; // signifies whether this is a Nullable<T> public bool IsNullable; // signifies whether this is a Nullable<T>
public bool IsGenericConverter; // used so we can call directly to the generic version if it is
public Type Converter;
public Type ConverterTarget;
// invalid for objects with IsNullabe false // invalid for objects with IsNullabe false
public Type NullableWrappedType => Nullable.GetUnderlyingType(Type); public Type NullableWrappedType => Nullable.GetUnderlyingType(Type);
// invalid for objects with IsNullabe false // invalid for objects with IsNullabe false
@ -281,28 +285,33 @@ namespace IPA.Config.Stores
// TODO: support converters // TODO: support converters
bool ProcessAttributesFor(MemberInfo member, Type memberType, out string name, out bool allowNull, out bool isNullable)
static bool ProcessAttributesFor(ref SerializedMemberInfo member)
{ {
var attrs = member.GetCustomAttributes(true);
var attrs = member.Member.GetCustomAttributes(true);
var ignores = attrs.Select(o => o as IgnoreAttribute).NonNull(); var ignores = attrs.Select(o => o as IgnoreAttribute).NonNull();
if (ignores.Any()) // we ignore if (ignores.Any()) // we ignore
{ {
name = null;
allowNull = false;
isNullable = false;
return false; return false;
} }
var nonNullables = attrs.Select(o => o as NonNullableAttribute).NonNull(); var nonNullables = attrs.Select(o => o as NonNullableAttribute).NonNull();
name = member.Name;
isNullable = memberType.IsGenericType
&& memberType.GetGenericTypeDefinition() == typeof(Nullable<>);
allowNull = !nonNullables.Any() && (!memberType.IsValueType || isNullable);
member.Name = member.Member.Name;
member.IsNullable = member.Type.IsGenericType
&& member.Type.GetGenericTypeDefinition() == typeof(Nullable<>);
member.AllowNull = !nonNullables.Any() && (!member.Type.IsValueType || member.IsNullable);
var nameAttr = attrs.Select(o => o as SerializedNameAttribute).NonNull().FirstOrDefault(); var nameAttr = attrs.Select(o => o as SerializedNameAttribute).NonNull().FirstOrDefault();
if (nameAttr != null) if (nameAttr != null)
name = nameAttr.Name;
member.Name = nameAttr.Name;
var converterAttr = attrs.Select(o => o as UseConverterAttribute).NonNull().FirstOrDefault();
if (converterAttr != null)
{
member.Converter = converterAttr.ConverterType;
member.ConverterTarget = converterAttr.ConverterTargetType;
member.IsGenericConverter = member.ConverterTarget != null;
}
return true; return true;
} }
@ -328,7 +337,7 @@ namespace IPA.Config.Stores
Type = prop.PropertyType Type = prop.PropertyType
}; };
if (!ProcessAttributesFor(smi.Member, smi.Type, out smi.Name, out smi.AllowNull, out smi.IsNullable)) continue;
if (!ProcessAttributesFor(ref smi)) continue;
structure.Add(smi); structure.Add(smi);
} }
@ -346,7 +355,7 @@ namespace IPA.Config.Stores
Type = field.FieldType Type = field.FieldType
}; };
if (!ProcessAttributesFor(smi.Member, smi.Type, out smi.Name, out smi.AllowNull, out smi.IsNullable)) continue;
if (!ProcessAttributesFor(ref smi)) continue;
structure.Add(smi); structure.Add(smi);
} }


+ 66
- 0
IPA.Loader/Config/Stores/ValueConverter.cs View File

@ -0,0 +1,66 @@
using IPA.Config.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace IPA.Config.Stores
{
/// <summary>
/// The base interface for a value converter for use by objects generated by
/// <see cref="GeneratedExtension.Generated{T}(Config, bool)"/>.
/// </summary>
/// <remarks>
/// The object returned from <see cref="FromValue(Value)"/>, if fed into <see cref="ToValue(object)"/>,
/// should return equivalent <see cref="Value"/> structures. Similarly, if the result of <see cref="ToValue(object)"/>
/// is fed into <see cref="FromValue(Value)"/>, the resulting object should be equivalent to the one passed to
/// <see cref="ToValue(object)"/>.
/// </remarks>
public interface IValueConverter
{
/// <summary>
/// Converts the given object to a <see cref="Value"/>.
/// </summary>
/// <param name="obj">the object to convert</param>
/// <returns>a representation of <paramref name="obj"/> as a <see cref="Value"/> structure</returns>
Value ToValue(object obj);
/// <summary>
/// Converts the given <see cref="Value"/> to the object type handled by this converter.
/// </summary>
/// <param name="value">the <see cref="Value"/> to deserialize</param>
/// <returns>the deserialized object</returns>
object FromValue(Value value);
/// <summary>
/// Gets the type that this <see cref="IValueConverter"/> handles.
/// </summary>
Type Type { get; }
}
/// <summary>
/// A strongly-typed <see cref="IValueConverter"/>.
/// </summary>
/// <typeparam name="T">the type of object to handle</typeparam>
/// <seealso cref="IValueConverter"/>
public abstract class ValueConverter<T> : IValueConverter
{
/// <summary>
/// Converts the given object to a <see cref="Value"/>.
/// </summary>
/// <param name="obj">the object to convert</param>
/// <returns>a representation of <paramref name="obj"/> as a <see cref="Value"/> structure</returns>
/// <seealso cref="IValueConverter.ToValue(object)"/>
public abstract Value ToValue(T obj);
/// <summary>
/// Converts the given <see cref="Value"/> to the object type handled by this converter.
/// </summary>
/// <param name="value">the <see cref="Value"/> to deserialize</param>
/// <returns>the deserialized object</returns>
/// <seealso cref="IValueConverter.FromValue(Value)"/>
public abstract T FromValue(Value value);
Value IValueConverter.ToValue(object obj) => ToValue((T)obj);
object IValueConverter.FromValue(Value value) => FromValue(value);
Type IValueConverter.Type => typeof(T);
}
}

+ 1
- 0
IPA.Loader/IPA.Loader.csproj View File

@ -97,6 +97,7 @@
<Compile Include="Config\Stores\Attributes.cs" /> <Compile Include="Config\Stores\Attributes.cs" />
<Compile Include="Config\Stores\GeneratedStore.cs" /> <Compile Include="Config\Stores\GeneratedStore.cs" />
<Compile Include="Config\Stores\GeneratedStoreCollections.cs" /> <Compile Include="Config\Stores\GeneratedStoreCollections.cs" />
<Compile Include="Config\Stores\ValueConverter.cs" />
<Compile Include="JsonConverters\AlmostVersionConverter.cs" /> <Compile Include="JsonConverters\AlmostVersionConverter.cs" />
<Compile Include="JsonConverters\MultilineStringConverter.cs" /> <Compile Include="JsonConverters\MultilineStringConverter.cs" />
<Compile Include="Loader\Composite\CompositeBSPlugin.cs" /> <Compile Include="Loader\Composite\CompositeBSPlugin.cs" />


Loading…
Cancel
Save