You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

183 lines
7.9 KiB

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 readonly Dictionary<Type, SerializedMemberInfo[]> objectStructureCache = new Dictionary<Type, SerializedMemberInfo[]>();
private static IEnumerable<SerializedMemberInfo> ReadObjectMembers(Type type)
{
if (!objectStructureCache.TryGetValue(type, out var structure))
objectStructureCache.Add(type, structure = ReadObjectMembersInternal(type).ToArray());
return structure;
}
private static IEnumerable<SerializedMemberInfo> ReadObjectMembersInternal(Type type)
{
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
}
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)
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;
}
}
}