IndexerAccessor.cs 5.8 KB

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