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.
 
 
 
 

198 lines
8.6 KiB

#nullable enable
using IPA.Config.Stores.Attributes;
using IPA.Config.Stores.Converters;
using IPA.Logging;
using IPA.Utilities;
using IPA.Utilities.Async;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
#if NET3
using Net3_Proxy;
using Array = Net3_Proxy.Array;
#endif
namespace IPA.Config.Stores
{
internal static partial class GeneratedStoreImpl
{
private class SerializedMemberInfo
{
public string Name = null!;
public MemberInfo Member = null!;
public Type Type = null!;
public bool AllowNull;
public bool IsVirtual;
public bool IsField;
[MemberNotNullWhen(true, nameof(NullableWrappedType))]
public bool IsNullable { get; set; } // signifies whether this is a Nullable<T>
[MemberNotNullWhen(true, nameof(Converter), nameof(ConverterBase))]
public bool HasConverter { get; set; }
public bool IsGenericConverter { get; set; } // 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 IsNullable false
public Type? NullableWrappedType => Nullable.GetUnderlyingType(Type);
// invalid for objects with IsNullable false
public PropertyInfo Nullable_HasValue => Type.GetProperty(nameof(Nullable<int>.HasValue));
// invalid for objects with IsNullable false
public PropertyInfo Nullable_Value => Type.GetProperty(nameof(Nullable<int>.Value));
// invalid for objects with IsNullable 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)
{
if (converterAttr.UseDefaultConverterForType)
converterAttr = new UseConverterAttribute(Converter.GetDefaultConverterType(member.Type));
if (converterAttr.UseDefaultConverterForType)
throw new InvalidOperationException("How did we get here?");
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 = (IValueConverter)Activator.CreateInstance(converterAttr.ConverterType);
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 SingleCreationValueCache<Type, SerializedMemberInfo[]> objectStructureCache = new();
private static IEnumerable<SerializedMemberInfo> ReadObjectMembers(Type type)
=> objectStructureCache.GetOrAdd(type, t => ReadObjectMembersInternal(type).ToArray());
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.GetIndexParameters().Length > 0)
continue; // we skip anything with index parameters
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);
}
CreateAndInitializeConvertersFor(type, structure);
return structure;
}
}
}