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.

192 lines
8.2 KiB

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