IndexerAccessor.cs 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. using System.Collections;
  2. using System.Diagnostics.CodeAnalysis;
  3. using System.Globalization;
  4. using System.Reflection;
  5. using Jint.Native;
  6. using Jint.Runtime.Descriptors;
  7. using Jint.Runtime.Descriptors.Specialized;
  8. namespace Jint.Runtime.Interop.Reflection
  9. {
  10. internal sealed class IndexerAccessor : ReflectionAccessor
  11. {
  12. private readonly object _key;
  13. internal readonly PropertyInfo _indexer;
  14. private readonly MethodInfo? _getter;
  15. private readonly MethodInfo? _setter;
  16. private readonly MethodInfo? _containsKey;
  17. private static readonly PropertyInfo _iListIndexer = typeof(IList).GetProperty("Item")!;
  18. private IndexerAccessor(PropertyInfo indexer, MethodInfo? containsKey, object key)
  19. : base(indexer.PropertyType, key)
  20. {
  21. _indexer = indexer;
  22. _containsKey = containsKey;
  23. _key = key;
  24. _getter = indexer.GetGetMethod();
  25. _setter = indexer.GetSetMethod();
  26. }
  27. internal static bool TryFindIndexer(
  28. Engine engine,
  29. Type targetType,
  30. string propertyName,
  31. [NotNullWhen(true)] out IndexerAccessor? indexerAccessor,
  32. [NotNullWhen(true)] out PropertyInfo? indexer)
  33. {
  34. indexerAccessor = null;
  35. indexer = null;
  36. var paramTypeArray = new Type[1];
  37. // integer keys can be ambiguous as we only know string keys
  38. int? integerKey = null;
  39. if (int.TryParse(propertyName, NumberStyles.Integer, CultureInfo.InvariantCulture, out var intKeyTemp))
  40. {
  41. integerKey = intKeyTemp;
  42. }
  43. IndexerAccessor? ComposeIndexerFactory(PropertyInfo candidate, Type paramType)
  44. {
  45. object? key = null;
  46. // int key is quite common case
  47. if (paramType == typeof(int) && integerKey is not null)
  48. {
  49. key = integerKey;
  50. }
  51. else
  52. {
  53. engine.TypeConverter.TryConvert(propertyName, paramType, CultureInfo.InvariantCulture, out key);
  54. }
  55. if (key is not null)
  56. {
  57. // the key can be converted for this indexer
  58. var indexerProperty = candidate;
  59. // get contains key method to avoid index exception being thrown in dictionaries
  60. paramTypeArray[0] = paramType;
  61. var containsKeyMethod = targetType.GetMethod(nameof(IDictionary<string, string>.ContainsKey), paramTypeArray);
  62. if (containsKeyMethod is null && targetType.IsAssignableFrom(typeof(IDictionary)))
  63. {
  64. paramTypeArray[0] = typeof(object);
  65. containsKeyMethod = targetType.GetMethod(nameof(IDictionary.Contains), paramTypeArray);
  66. }
  67. return new IndexerAccessor(indexerProperty, containsKeyMethod, key);
  68. }
  69. // the key type doesn't work for this indexer
  70. return null;
  71. }
  72. var filter = new Func<MemberInfo, bool>(m => engine.Options.Interop.TypeResolver.Filter(engine, m));
  73. // default indexer wins
  74. if (typeof(IList).IsAssignableFrom(targetType) && filter(_iListIndexer))
  75. {
  76. indexerAccessor = ComposeIndexerFactory(_iListIndexer, typeof(int));
  77. if (indexerAccessor != null)
  78. {
  79. indexer = _iListIndexer;
  80. return true;
  81. }
  82. }
  83. // try to find first indexer having either public getter or setter with matching argument type
  84. PropertyInfo? fallbackIndexer = null;
  85. foreach (var candidate in targetType.GetProperties())
  86. {
  87. if (!filter(candidate))
  88. {
  89. continue;
  90. }
  91. var indexParameters = candidate.GetIndexParameters();
  92. if (indexParameters.Length != 1)
  93. {
  94. continue;
  95. }
  96. if (candidate.GetGetMethod() != null || candidate.GetSetMethod() != null)
  97. {
  98. var paramType = indexParameters[0].ParameterType;
  99. indexerAccessor = ComposeIndexerFactory(candidate, paramType);
  100. if (indexerAccessor != null)
  101. {
  102. if (paramType != typeof(string) || integerKey is null)
  103. {
  104. // exact match, we don't need to check for integer key
  105. indexer = candidate;
  106. return true;
  107. }
  108. if (fallbackIndexer is null)
  109. {
  110. // our fallback
  111. fallbackIndexer = candidate;
  112. }
  113. }
  114. }
  115. }
  116. if (fallbackIndexer is not null)
  117. {
  118. indexer = fallbackIndexer;
  119. // just to keep compiler happy, we know we have a value
  120. indexerAccessor = indexerAccessor ?? new IndexerAccessor(indexer, null, null!);
  121. return true;
  122. }
  123. indexerAccessor = default;
  124. indexer = default;
  125. return false;
  126. }
  127. public override bool Readable => _indexer.CanRead;
  128. public override bool Writable => _indexer.CanWrite;
  129. protected override object? DoGetValue(object target)
  130. {
  131. if (_getter is null)
  132. {
  133. ExceptionHelper.ThrowInvalidOperationException("Indexer has no public getter.");
  134. return null;
  135. }
  136. object[] parameters = { _key };
  137. if (_containsKey != null)
  138. {
  139. if (_containsKey.Invoke(target, parameters) as bool? != true)
  140. {
  141. return JsValue.Undefined;
  142. }
  143. }
  144. try
  145. {
  146. return _getter.Invoke(target, parameters);
  147. }
  148. catch (TargetInvocationException tie) when (tie.InnerException is KeyNotFoundException)
  149. {
  150. return JsValue.Undefined;
  151. }
  152. }
  153. protected override void DoSetValue(object target, object? value)
  154. {
  155. if (_setter is null)
  156. {
  157. ExceptionHelper.ThrowInvalidOperationException("Indexer has no public setter.");
  158. }
  159. object?[] parameters = { _key, value };
  160. _setter.Invoke(target, parameters);
  161. }
  162. public override PropertyDescriptor CreatePropertyDescriptor(Engine engine, object target, bool enumerable = true)
  163. {
  164. if (_containsKey != null)
  165. {
  166. if (_containsKey.Invoke(target, new[] { _key }) as bool? != true)
  167. {
  168. return PropertyDescriptor.Undefined;
  169. }
  170. }
  171. return new ReflectionDescriptor(engine, this, target, enumerable: true);
  172. }
  173. }
  174. }