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

  1. #nullable enable
  2. using IPA.Config.Stores.Attributes;
  3. using IPA.Config.Stores.Converters;
  4. using IPA.Logging;
  5. using IPA.Utilities;
  6. using IPA.Utilities.Async;
  7. using System;
  8. using System.Collections.Generic;
  9. using System.Diagnostics.CodeAnalysis;
  10. using System.Linq;
  11. using System.Reflection;
  12. using System.Text;
  13. using System.Threading.Tasks;
  14. #if NET3
  15. using Net3_Proxy;
  16. using Array = Net3_Proxy.Array;
  17. #endif
  18. namespace IPA.Config.Stores
  19. {
  20. internal static partial class GeneratedStoreImpl
  21. {
  22. private class SerializedMemberInfo
  23. {
  24. public string Name = null!;
  25. public MemberInfo Member = null!;
  26. public Type Type = null!;
  27. public bool AllowNull;
  28. public bool IsVirtual;
  29. public bool IsField;
  30. [MemberNotNullWhen(true, nameof(NullableWrappedType))]
  31. public bool IsNullable { get; set; } // signifies whether this is a Nullable<T>
  32. [MemberNotNullWhen(true, nameof(Converter), nameof(ConverterBase))]
  33. public bool HasConverter { get; set; }
  34. public bool IsGenericConverter { get; set; } // used so we can call directly to the generic version if it is
  35. public Type? Converter;
  36. public Type? ConverterBase;
  37. public Type? ConverterTarget;
  38. public FieldInfo? ConverterField;
  39. // invalid for objects with IsNullable false
  40. public Type? NullableWrappedType => Nullable.GetUnderlyingType(Type);
  41. // invalid for objects with IsNullable false
  42. public PropertyInfo Nullable_HasValue => Type.GetProperty(nameof(Nullable<int>.HasValue));
  43. // invalid for objects with IsNullable false
  44. public PropertyInfo Nullable_Value => Type.GetProperty(nameof(Nullable<int>.Value));
  45. // invalid for objects with IsNullable false
  46. public ConstructorInfo Nullable_Construct => Type.GetConstructor(new[] { NullableWrappedType });
  47. public Type ConversionType => IsNullable ? NullableWrappedType : Type;
  48. }
  49. private static bool IsMethodInvalid(MethodInfo m, Type ret) => !m.IsVirtual || m.ReturnType != ret;
  50. private static bool ProcessAttributesFor(Type type, ref SerializedMemberInfo member)
  51. {
  52. var attrs = member.Member.GetCustomAttributes(true);
  53. var ignores = attrs.Select(o => o as IgnoreAttribute).NonNull();
  54. if (ignores.Any() || typeof(Delegate).IsAssignableFrom(member.Type))
  55. { // we ignore delegates completely because there is no a good way to serialize them
  56. return false;
  57. }
  58. var nonNullables = attrs.Select(o => o as NonNullableAttribute).NonNull();
  59. member.Name = member.Member.Name;
  60. member.IsNullable = member.Type.IsGenericType
  61. && member.Type.GetGenericTypeDefinition() == typeof(Nullable<>);
  62. member.AllowNull = !nonNullables.Any() && (!member.Type.IsValueType || member.IsNullable);
  63. var nameAttr = attrs.Select(o => o as SerializedNameAttribute).NonNull().FirstOrDefault();
  64. if (nameAttr != null)
  65. member.Name = nameAttr.Name;
  66. member.HasConverter = false;
  67. var converterAttr = attrs.Select(o => o as UseConverterAttribute).NonNull().FirstOrDefault();
  68. if (converterAttr != null)
  69. {
  70. if (converterAttr.UseDefaultConverterForType)
  71. converterAttr = new UseConverterAttribute(Converter.GetDefaultConverterType(member.Type));
  72. if (converterAttr.UseDefaultConverterForType)
  73. throw new InvalidOperationException("How did we get here?");
  74. member.Converter = converterAttr.ConverterType;
  75. member.IsGenericConverter = converterAttr.IsGenericConverter;
  76. if (member.Converter.GetConstructor(Type.EmptyTypes) == null)
  77. {
  78. Logger.config.Warn($"{type.FullName}'s member {member.Member.Name} requests a converter that is not default-constructible");
  79. goto endConverterAttr; // is there a better control flow structure to do this?
  80. }
  81. if (member.Converter.ContainsGenericParameters)
  82. {
  83. Logger.config.Warn($"{type.FullName}'s member {member.Member.Name} requests a converter that has unfilled type parameters");
  84. goto endConverterAttr;
  85. }
  86. if (member.Converter.IsInterface || member.Converter.IsAbstract)
  87. {
  88. Logger.config.Warn($"{type.FullName}'s member {member.Member.Name} requests a converter that is not constructible");
  89. goto endConverterAttr;
  90. }
  91. var targetType = converterAttr.ConverterTargetType;
  92. if (!member.IsGenericConverter)
  93. {
  94. try
  95. {
  96. var conv = (IValueConverter)Activator.CreateInstance(converterAttr.ConverterType);
  97. targetType = conv.Type;
  98. }
  99. catch
  100. {
  101. Logger.config.Warn($"{type.FullName}'s member {member.Member.Name} requests a converter who's target type could not be determined");
  102. goto endConverterAttr;
  103. }
  104. }
  105. if (targetType != member.Type)
  106. {
  107. Logger.config.Warn($"{type.FullName}'s member {member.Member.Name} requests a converter that is not of the member's type");
  108. goto endConverterAttr;
  109. }
  110. member.ConverterTarget = targetType;
  111. if (member.IsGenericConverter)
  112. member.ConverterBase = typeof(ValueConverter<>).MakeGenericType(targetType);
  113. else
  114. member.ConverterBase = typeof(IValueConverter);
  115. member.HasConverter = true;
  116. }
  117. endConverterAttr:
  118. return true;
  119. }
  120. private static readonly SingleCreationValueCache<Type, SerializedMemberInfo[]> objectStructureCache = new();
  121. private static IEnumerable<SerializedMemberInfo> ReadObjectMembers(Type type)
  122. => objectStructureCache.GetOrAdd(type, t => ReadObjectMembersInternal(type).ToArray());
  123. private static IEnumerable<SerializedMemberInfo> ReadObjectMembersInternal(Type type)
  124. {
  125. var structure = new List<SerializedMemberInfo>();
  126. // only looks at public/protected properties
  127. foreach (var prop in type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
  128. {
  129. if (prop.GetIndexParameters().Length > 0)
  130. continue; // we skip anything with index parameters
  131. if (prop.GetSetMethod(true)?.IsPrivate ?? true)
  132. { // we enter this block if the setter is inacessible or doesn't exist
  133. continue; // ignore props without setter
  134. }
  135. if (prop.GetGetMethod(true)?.IsPrivate ?? true)
  136. { // we enter this block if the getter is inacessible or doesn't exist
  137. continue; // ignore props without getter
  138. }
  139. var smi = new SerializedMemberInfo
  140. {
  141. Member = prop,
  142. IsVirtual = (prop.GetGetMethod(true)?.IsVirtual ?? false) ||
  143. (prop.GetSetMethod(true)?.IsVirtual ?? false),
  144. IsField = false,
  145. Type = prop.PropertyType
  146. };
  147. if (!ProcessAttributesFor(type, ref smi)) continue;
  148. structure.Add(smi);
  149. }
  150. // only look at public/protected fields
  151. foreach (var field in type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
  152. {
  153. if (field.IsPrivate)
  154. continue;
  155. var smi = new SerializedMemberInfo
  156. {
  157. Member = field,
  158. IsVirtual = false,
  159. IsField = true,
  160. Type = field.FieldType
  161. };
  162. if (!ProcessAttributesFor(type, ref smi)) continue;
  163. structure.Add(smi);
  164. }
  165. CreateAndInitializeConvertersFor(type, structure);
  166. return structure;
  167. }
  168. }
  169. }