IndexerAccessor.cs 5.2 KB

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