#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;
|
|
}
|
|
}
|
|
}
|