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.

187 lines
7.9 KiB

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