using IPA.Config.Stores.Attributes;
|
|
using IPA.Logging;
|
|
using IPA.Utilities;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace IPA.Config.Stores
|
|
{
|
|
internal static partial class GeneratedStoreImpl
|
|
{
|
|
private class SerializedMemberInfo
|
|
{
|
|
public string Name;
|
|
public MemberInfo Member;
|
|
public Type Type;
|
|
public bool AllowNull;
|
|
public bool IsVirtual;
|
|
public bool IsField;
|
|
public bool IsNullable; // signifies whether this is a Nullable<T>
|
|
|
|
public bool HasConverter;
|
|
public bool IsGenericConverter; // used so we can call directly to the generic version if it is
|
|
public Type Converter;
|
|
public Type ConverterBase;
|
|
public Type ConverterTarget;
|
|
public FieldInfo ConverterField;
|
|
|
|
// invalid for objects with IsNullabe false
|
|
public Type NullableWrappedType => Nullable.GetUnderlyingType(Type);
|
|
// invalid for objects with IsNullabe false
|
|
public PropertyInfo Nullable_HasValue => Type.GetProperty(nameof(Nullable<int>.HasValue));
|
|
// invalid for objects with IsNullabe false
|
|
public PropertyInfo Nullable_Value => Type.GetProperty(nameof(Nullable<int>.Value));
|
|
// invalid for objects with IsNullabe false
|
|
public ConstructorInfo Nullable_Construct => Type.GetConstructor(new[] { NullableWrappedType });
|
|
|
|
public Type ConversionType => IsNullable ? NullableWrappedType : Type;
|
|
}
|
|
private static bool IsMethodInvalid(MethodInfo m, Type ret) => !m.IsVirtual || m.ReturnType != ret;
|
|
private static bool ProcessAttributesFor(Type type, ref SerializedMemberInfo member)
|
|
{
|
|
var attrs = member.Member.GetCustomAttributes(true);
|
|
var ignores = attrs.Select(o => o as IgnoreAttribute).NonNull();
|
|
if (ignores.Any() || typeof(Delegate).IsAssignableFrom(member.Type))
|
|
{ // we ignore delegates completely because there is no a good way to serialize them
|
|
return false;
|
|
}
|
|
|
|
var nonNullables = attrs.Select(o => o as NonNullableAttribute).NonNull();
|
|
|
|
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();
|
|
if (nameAttr != null)
|
|
member.Name = nameAttr.Name;
|
|
|
|
member.HasConverter = false;
|
|
var converterAttr = attrs.Select(o => o as UseConverterAttribute).NonNull().FirstOrDefault();
|
|
if (converterAttr != null)
|
|
{
|
|
member.Converter = converterAttr.ConverterType;
|
|
member.IsGenericConverter = converterAttr.IsGenericConverter;
|
|
|
|
if (member.Converter.GetConstructor(Type.EmptyTypes) == null)
|
|
{
|
|
Logger.config.Warn($"{type.FullName}'s member {member.Member.Name} requests a converter that is not default-constructible");
|
|
goto endConverterAttr; // is there a better control flow structure to do this?
|
|
}
|
|
|
|
if (member.Converter.ContainsGenericParameters)
|
|
{
|
|
Logger.config.Warn($"{type.FullName}'s member {member.Member.Name} requests a converter that has unfilled type parameters");
|
|
goto endConverterAttr;
|
|
}
|
|
|
|
if (member.Converter.IsInterface || member.Converter.IsAbstract)
|
|
{
|
|
Logger.config.Warn($"{type.FullName}'s member {member.Member.Name} requests a converter that is not constructible");
|
|
goto endConverterAttr;
|
|
}
|
|
|
|
var targetType = converterAttr.ConverterTargetType;
|
|
if (!member.IsGenericConverter)
|
|
{
|
|
try
|
|
{
|
|
var conv = Activator.CreateInstance(converterAttr.ConverterType) as IValueConverter;
|
|
targetType = conv.Type;
|
|
}
|
|
catch
|
|
{
|
|
Logger.config.Warn($"{type.FullName}'s member {member.Member.Name} requests a converter who's target type could not be determined");
|
|
goto endConverterAttr;
|
|
}
|
|
}
|
|
if (targetType != member.Type)
|
|
{
|
|
Logger.config.Warn($"{type.FullName}'s member {member.Member.Name} requests a converter that is not of the member's type");
|
|
goto endConverterAttr;
|
|
}
|
|
|
|
member.ConverterTarget = targetType;
|
|
if (member.IsGenericConverter)
|
|
member.ConverterBase = typeof(ValueConverter<>).MakeGenericType(targetType);
|
|
else
|
|
member.ConverterBase = typeof(IValueConverter);
|
|
|
|
member.HasConverter = true;
|
|
}
|
|
endConverterAttr:
|
|
|
|
return true;
|
|
}
|
|
|
|
private static IEnumerable<SerializedMemberInfo> ReadObjectMembers(Type type, bool throwOnPrivateField = false, bool throwOnPrivateProperty = false)
|
|
{
|
|
var structure = new List<SerializedMemberInfo>();
|
|
|
|
// only looks at public/protected properties
|
|
foreach (var prop in type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
|
|
{
|
|
if (prop.GetSetMethod(true)?.IsPrivate ?? true)
|
|
{ // we enter this block if the setter is inacessible or doesn't exist
|
|
continue; // ignore props without setter
|
|
}
|
|
if (prop.GetGetMethod(true)?.IsPrivate ?? true)
|
|
{ // we enter this block if the getter is inacessible or doesn't exist
|
|
//continue; // ignore props without getter
|
|
// TODO: is this what I want to do for private properties?
|
|
if (throwOnPrivateProperty)
|
|
throw new InvalidOperationException($"Cannot compile serializer for type {type} with private property {prop.Name}");
|
|
else
|
|
continue;
|
|
}
|
|
|
|
var smi = new SerializedMemberInfo
|
|
{
|
|
Member = prop,
|
|
IsVirtual = (prop.GetGetMethod(true)?.IsVirtual ?? false) ||
|
|
(prop.GetSetMethod(true)?.IsVirtual ?? false),
|
|
IsField = false,
|
|
Type = prop.PropertyType
|
|
};
|
|
|
|
if (!ProcessAttributesFor(type, ref smi)) continue;
|
|
|
|
structure.Add(smi);
|
|
}
|
|
|
|
// only look at public/protected fields
|
|
foreach (var field in type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
|
|
{
|
|
if (field.IsPrivate)
|
|
{
|
|
if (throwOnPrivateField && !field.Name.Contains(">k__BackingField")) // don't throw for private fields that are just backing fields
|
|
throw new InvalidOperationException($"Cannot compile serializer for type {type} with private field {field.Name}");
|
|
else
|
|
continue;
|
|
}
|
|
|
|
var smi = new SerializedMemberInfo
|
|
{
|
|
Member = field,
|
|
IsVirtual = false,
|
|
IsField = true,
|
|
Type = field.FieldType
|
|
};
|
|
|
|
if (!ProcessAttributesFor(type, ref smi)) continue;
|
|
|
|
structure.Add(smi);
|
|
}
|
|
|
|
return structure;
|
|
}
|
|
}
|
|
}
|