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.

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