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.

190 lines
8.1 KiB

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