IndexerAccessor.cs 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  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. private 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. var paramTypeArray = new Type[1];
  35. IndexerAccessor? ComposeIndexerFactory(PropertyInfo candidate, Type paramType)
  36. {
  37. object? key = null;
  38. // int key is quite common case
  39. if (paramType == typeof(int))
  40. {
  41. if (int.TryParse(propertyName, out var intValue))
  42. {
  43. key = intValue;
  44. }
  45. }
  46. else
  47. {
  48. engine.ClrTypeConverter.TryConvert(propertyName, paramType, CultureInfo.InvariantCulture, out key);
  49. }
  50. if (key is not null)
  51. {
  52. // the key can be converted for this indexer
  53. var indexerProperty = candidate;
  54. // get contains key method to avoid index exception being thrown in dictionaries
  55. paramTypeArray[0] = paramType;
  56. var containsKeyMethod = targetType.GetMethod(nameof(IDictionary<string, string>.ContainsKey), paramTypeArray);
  57. if (containsKeyMethod is null && targetType.IsAssignableFrom(typeof(IDictionary)))
  58. {
  59. paramTypeArray[0] = typeof(object);
  60. containsKeyMethod = targetType.GetMethod(nameof(IDictionary.Contains), paramTypeArray);
  61. }
  62. return new IndexerAccessor(indexerProperty, containsKeyMethod, key);
  63. }
  64. // the key type doesn't work for this indexer
  65. return null;
  66. }
  67. var filter = new Func<MemberInfo, bool>(m => engine.Options.Interop.TypeResolver.Filter(engine, m));
  68. // default indexer wins
  69. if (typeof(IList).IsAssignableFrom(targetType) && filter(_iListIndexer))
  70. {
  71. indexerAccessor = ComposeIndexerFactory(_iListIndexer, typeof(int));
  72. if (indexerAccessor != null)
  73. {
  74. indexer = _iListIndexer;
  75. return true;
  76. }
  77. }
  78. // try to find first indexer having either public getter or setter with matching argument type
  79. foreach (var candidate in targetType.GetProperties())
  80. {
  81. if (!filter(candidate))
  82. {
  83. continue;
  84. }
  85. var indexParameters = candidate.GetIndexParameters();
  86. if (indexParameters.Length != 1)
  87. {
  88. continue;
  89. }
  90. if (candidate.GetGetMethod() != null || candidate.GetSetMethod() != null)
  91. {
  92. var paramType = indexParameters[0].ParameterType;
  93. indexerAccessor = ComposeIndexerFactory(candidate, paramType);
  94. if (indexerAccessor != null)
  95. {
  96. indexer = candidate;
  97. return true;
  98. }
  99. }
  100. }
  101. indexerAccessor = default;
  102. indexer = default;
  103. return false;
  104. }
  105. public override bool Writable => _indexer.CanWrite;
  106. protected override object? DoGetValue(object target)
  107. {
  108. if (_getter is null)
  109. {
  110. ExceptionHelper.ThrowInvalidOperationException("Indexer has no public getter.");
  111. return null;
  112. }
  113. object[] parameters = { _key };
  114. if (_containsKey != null)
  115. {
  116. if (_containsKey.Invoke(target, parameters) as bool? != true)
  117. {
  118. return JsValue.Undefined;
  119. }
  120. }
  121. try
  122. {
  123. return _getter.Invoke(target, parameters);
  124. }
  125. catch (TargetInvocationException tie) when (tie.InnerException is KeyNotFoundException)
  126. {
  127. return JsValue.Undefined;
  128. }
  129. }
  130. protected override void DoSetValue(object target, object? value)
  131. {
  132. if (_setter is null)
  133. {
  134. ExceptionHelper.ThrowInvalidOperationException("Indexer has no public setter.");
  135. }
  136. object?[] parameters = { _key, value };
  137. _setter.Invoke(target, parameters);
  138. }
  139. public override PropertyDescriptor CreatePropertyDescriptor(Engine engine, object target, bool enumerable = true)
  140. {
  141. if (_containsKey != null)
  142. {
  143. if (_containsKey.Invoke(target, new[] { _key }) as bool? != true)
  144. {
  145. return PropertyDescriptor.Undefined;
  146. }
  147. }
  148. return new ReflectionDescriptor(engine, this, target, enumerable: true);
  149. }
  150. }
  151. }