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 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.HasValue)); // invalid for objects with IsNullabe false public PropertyInfo Nullable_Value => Type.GetProperty(nameof(Nullable.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 objectStructureCache = new Dictionary(); private static IEnumerable ReadObjectMembers(Type type) { if (!objectStructureCache.TryGetValue(type, out var structure)) objectStructureCache.Add(type, structure = ReadObjectMembersInternal(type).ToArray()); return structure; } private static IEnumerable ReadObjectMembersInternal(Type type) { var structure = new List(); // 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; } } }