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.

345 lines
13 KiB

  1. #region License
  2. // Copyright (c) 2007 James Newton-King
  3. //
  4. // Permission is hereby granted, free of charge, to any person
  5. // obtaining a copy of this software and associated documentation
  6. // files (the "Software"), to deal in the Software without
  7. // restriction, including without limitation the rights to use,
  8. // copy, modify, merge, publish, distribute, sublicense, and/or sell
  9. // copies of the Software, and to permit persons to whom the
  10. // Software is furnished to do so, subject to the following
  11. // conditions:
  12. //
  13. // The above copyright notice and this permission notice shall be
  14. // included in all copies or substantial portions of the Software.
  15. //
  16. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  17. // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
  18. // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  19. // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
  20. // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
  21. // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  22. // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
  23. // OTHER DEALINGS IN THE SOFTWARE.
  24. #endregion
  25. using System;
  26. using System.Collections.Generic;
  27. #if NET20
  28. using Newtonsoft.Json.Utilities.LinqBridge;
  29. #else
  30. using System.Linq;
  31. #endif
  32. using System.Reflection;
  33. namespace Newtonsoft.Json.Utilities
  34. {
  35. #if PORTABLE
  36. internal static class MethodBinder
  37. {
  38. /// <summary>
  39. /// List of primitive types which can be widened.
  40. /// </summary>
  41. private static readonly Type[] PrimitiveTypes = new Type[]
  42. {
  43. typeof(bool), typeof(char), typeof(sbyte), typeof(byte),
  44. typeof(short), typeof(ushort), typeof(int), typeof(uint),
  45. typeof(long), typeof(ulong), typeof(float), typeof(double)
  46. };
  47. /// <summary>
  48. /// Widening masks for primitive types above.
  49. /// Index of the value in this array defines a type we're widening,
  50. /// while the bits in mask define types it can be widened to (including itself).
  51. ///
  52. /// For example, value at index 0 defines a bool type, and it only has bit 0 set,
  53. /// i.e. bool values can be assigned only to bool.
  54. /// </summary>
  55. private static readonly int[] WideningMasks = new int[]
  56. {
  57. 0x0001, 0x0FE2, 0x0D54, 0x0FFA,
  58. 0x0D50, 0x0FE2, 0x0D40, 0x0F80,
  59. 0x0D00, 0x0E00, 0x0C00, 0x0800
  60. };
  61. /// <summary>
  62. /// Checks if value of primitive type <paramref name="from"/> can be
  63. /// assigned to parameter of primitive type <paramref name="to"/>.
  64. /// </summary>
  65. /// <param name="from">Source primitive type.</param>
  66. /// <param name="to">Target primitive type.</param>
  67. /// <returns><c>true</c> if source type can be widened to target type, <c>false</c> otherwise.</returns>
  68. private static bool CanConvertPrimitive(Type from, Type to)
  69. {
  70. if (from == to)
  71. {
  72. // same type
  73. return true;
  74. }
  75. int fromMask = 0;
  76. int toMask = 0;
  77. for (int i = 0; i < PrimitiveTypes.Length; i++)
  78. {
  79. if (PrimitiveTypes[i] == from)
  80. {
  81. fromMask = WideningMasks[i];
  82. }
  83. else if (PrimitiveTypes[i] == to)
  84. {
  85. toMask = 1 << i;
  86. }
  87. if (fromMask != 0 && toMask != 0)
  88. {
  89. break;
  90. }
  91. }
  92. return (fromMask & toMask) != 0;
  93. }
  94. /// <summary>
  95. /// Checks if a set of values with given <paramref name="types"/> can be used
  96. /// to invoke a method with specified <paramref name="parameters"/>.
  97. /// </summary>
  98. /// <param name="parameters">Method parameters.</param>
  99. /// <param name="types">Argument types.</param>
  100. /// <param name="enableParamArray">Try to pack extra arguments into the last parameter when it is marked up with <see cref="ParamArrayAttribute"/>.</param>
  101. /// <returns><c>true</c> if method can be called with given arguments, <c>false</c> otherwise.</returns>
  102. private static bool FilterParameters(ParameterInfo[] parameters, IList<Type> types, bool enableParamArray)
  103. {
  104. ValidationUtils.ArgumentNotNull(parameters, nameof(parameters));
  105. ValidationUtils.ArgumentNotNull(types, nameof(types));
  106. if (parameters.Length == 0)
  107. {
  108. // fast check for parameterless methods
  109. return types.Count == 0;
  110. }
  111. if (parameters.Length > types.Count)
  112. {
  113. // not all declared parameters were specified (optional parameters are not supported)
  114. return false;
  115. }
  116. // check if the last parameter is ParamArray
  117. Type paramArrayType = null;
  118. if (enableParamArray)
  119. {
  120. ParameterInfo lastParam = parameters[parameters.Length - 1];
  121. if (lastParam.ParameterType.IsArray && lastParam.IsDefined(typeof(ParamArrayAttribute)))
  122. {
  123. paramArrayType = lastParam.ParameterType.GetElementType();
  124. }
  125. }
  126. if (paramArrayType == null && parameters.Length != types.Count)
  127. {
  128. // when there's no ParamArray, number of parameters should match
  129. return false;
  130. }
  131. for (int i = 0; i < types.Count; i++)
  132. {
  133. Type paramType = (paramArrayType != null && i >= parameters.Length - 1) ? paramArrayType : parameters[i].ParameterType;
  134. if (paramType == types[i])
  135. {
  136. // exact match with provided type
  137. continue;
  138. }
  139. if (paramType == typeof(object))
  140. {
  141. // parameter of type object matches anything
  142. continue;
  143. }
  144. if (paramType.IsPrimitive())
  145. {
  146. if (!types[i].IsPrimitive() || !CanConvertPrimitive(types[i], paramType))
  147. {
  148. // primitive parameter can only be assigned from compatible primitive type
  149. return false;
  150. }
  151. }
  152. else
  153. {
  154. if (!paramType.IsAssignableFrom(types[i]))
  155. {
  156. return false;
  157. }
  158. }
  159. }
  160. return true;
  161. }
  162. /// <summary>
  163. /// Compares two sets of parameters to determine
  164. /// which one suits better for given argument types.
  165. /// </summary>
  166. private class ParametersMatchComparer : IComparer<ParameterInfo[]>
  167. {
  168. private readonly IList<Type> _types;
  169. private readonly bool _enableParamArray;
  170. public ParametersMatchComparer(IList<Type> types, bool enableParamArray)
  171. {
  172. ValidationUtils.ArgumentNotNull(types, nameof(types));
  173. _types = types;
  174. _enableParamArray = enableParamArray;
  175. }
  176. public int Compare(ParameterInfo[] parameters1, ParameterInfo[] parameters2)
  177. {
  178. ValidationUtils.ArgumentNotNull(parameters1, nameof(parameters1));
  179. ValidationUtils.ArgumentNotNull(parameters2, nameof(parameters2));
  180. // parameterless method wins
  181. if (parameters1.Length == 0)
  182. {
  183. return -1;
  184. }
  185. if (parameters2.Length == 0)
  186. {
  187. return 1;
  188. }
  189. Type paramArrayType1 = null, paramArrayType2 = null;
  190. if (_enableParamArray)
  191. {
  192. ParameterInfo lastParam1 = parameters1[parameters1.Length - 1];
  193. if (lastParam1.ParameterType.IsArray && lastParam1.IsDefined(typeof(ParamArrayAttribute)))
  194. {
  195. paramArrayType1 = lastParam1.ParameterType.GetElementType();
  196. }
  197. ParameterInfo lastParam2 = parameters2[parameters2.Length - 1];
  198. if (lastParam2.ParameterType.IsArray && lastParam2.IsDefined(typeof(ParamArrayAttribute)))
  199. {
  200. paramArrayType2 = lastParam2.ParameterType.GetElementType();
  201. }
  202. // A method using params always loses to one not using params
  203. if (paramArrayType1 != null && paramArrayType2 == null)
  204. {
  205. return 1;
  206. }
  207. if (paramArrayType2 != null && paramArrayType1 == null)
  208. {
  209. return -1;
  210. }
  211. }
  212. for (int i = 0; i < _types.Count; i++)
  213. {
  214. Type type1 = (paramArrayType1 != null && i >= parameters1.Length - 1) ? paramArrayType1 : parameters1[i].ParameterType;
  215. Type type2 = (paramArrayType2 != null && i >= parameters2.Length - 1) ? paramArrayType2 : parameters2[i].ParameterType;
  216. if (type1 == type2)
  217. {
  218. // exact match between parameter types doesn't change score
  219. continue;
  220. }
  221. // exact match with source type decides winner immediately
  222. if (type1 == _types[i])
  223. {
  224. return -1;
  225. }
  226. if (type2 == _types[i])
  227. {
  228. return 1;
  229. }
  230. int r = ChooseMorePreciseType(type1, type2);
  231. if (r != 0)
  232. {
  233. // winner decided
  234. return r;
  235. }
  236. }
  237. return 0;
  238. }
  239. private static int ChooseMorePreciseType(Type type1, Type type2)
  240. {
  241. if (type1.IsByRef || type2.IsByRef)
  242. {
  243. if (type1.IsByRef && type2.IsByRef)
  244. {
  245. type1 = type1.GetElementType();
  246. type2 = type2.GetElementType();
  247. }
  248. else if (type1.IsByRef)
  249. {
  250. type1 = type1.GetElementType();
  251. if (type1 == type2)
  252. {
  253. return 1;
  254. }
  255. }
  256. else
  257. {
  258. type2 = type2.GetElementType();
  259. if (type2 == type1)
  260. {
  261. return -1;
  262. }
  263. }
  264. }
  265. bool c1FromC2, c2FromC1;
  266. if (type1.IsPrimitive() && type2.IsPrimitive())
  267. {
  268. c1FromC2 = CanConvertPrimitive(type2, type1);
  269. c2FromC1 = CanConvertPrimitive(type1, type2);
  270. }
  271. else
  272. {
  273. c1FromC2 = type1.IsAssignableFrom(type2);
  274. c2FromC1 = type2.IsAssignableFrom(type1);
  275. }
  276. if (c1FromC2 == c2FromC1)
  277. {
  278. return 0;
  279. }
  280. return c1FromC2 ? 1 : -1;
  281. }
  282. }
  283. /// <summary>
  284. /// Returns a best method overload for given argument <paramref name="types"/>.
  285. /// </summary>
  286. /// <param name="candidates">List of method candidates.</param>
  287. /// <param name="types">Argument types.</param>
  288. /// <returns>Best method overload, or <c>null</c> if none matched.</returns>
  289. public static TMethod SelectMethod<TMethod>(IEnumerable<TMethod> candidates, IList<Type> types) where TMethod : MethodBase
  290. {
  291. ValidationUtils.ArgumentNotNull(candidates, nameof(candidates));
  292. ValidationUtils.ArgumentNotNull(types, nameof(types));
  293. // ParamArrays are not supported by ReflectionDelegateFactory
  294. // They will be treated like ordinary array arguments
  295. const bool enableParamArray = false;
  296. return candidates
  297. .Where(m => FilterParameters(m.GetParameters(), types, enableParamArray))
  298. .OrderBy(m => m.GetParameters(), new ParametersMatchComparer(types, enableParamArray))
  299. .FirstOrDefault();
  300. }
  301. }
  302. #endif
  303. }