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.

390 lines
16 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. #if !NET20
  26. using System;
  27. using System.Collections.Generic;
  28. using System.Dynamic;
  29. using System.Linq;
  30. using System.Linq.Expressions;
  31. namespace Newtonsoft.Json.Utilities
  32. {
  33. internal sealed class DynamicProxyMetaObject<T> : DynamicMetaObject
  34. {
  35. private readonly DynamicProxy<T> _proxy;
  36. internal DynamicProxyMetaObject(Expression expression, T value, DynamicProxy<T> proxy)
  37. : base(expression, BindingRestrictions.Empty, value)
  38. {
  39. _proxy = proxy;
  40. }
  41. private bool IsOverridden(string method)
  42. {
  43. return ReflectionUtils.IsMethodOverridden(_proxy.GetType(), typeof(DynamicProxy<T>), method);
  44. }
  45. public override DynamicMetaObject BindGetMember(GetMemberBinder binder)
  46. {
  47. return IsOverridden(nameof(DynamicProxy<T>.TryGetMember))
  48. ? CallMethodWithResult(nameof(DynamicProxy<T>.TryGetMember), binder, NoArgs, e => binder.FallbackGetMember(this, e))
  49. : base.BindGetMember(binder);
  50. }
  51. public override DynamicMetaObject BindSetMember(SetMemberBinder binder, DynamicMetaObject value)
  52. {
  53. return IsOverridden(nameof(DynamicProxy<T>.TrySetMember))
  54. ? CallMethodReturnLast(nameof(DynamicProxy<T>.TrySetMember), binder, GetArgs(value), e => binder.FallbackSetMember(this, value, e))
  55. : base.BindSetMember(binder, value);
  56. }
  57. public override DynamicMetaObject BindDeleteMember(DeleteMemberBinder binder)
  58. {
  59. return IsOverridden(nameof(DynamicProxy<T>.TryDeleteMember))
  60. ? CallMethodNoResult(nameof(DynamicProxy<T>.TryDeleteMember), binder, NoArgs, e => binder.FallbackDeleteMember(this, e))
  61. : base.BindDeleteMember(binder);
  62. }
  63. public override DynamicMetaObject BindConvert(ConvertBinder binder)
  64. {
  65. return IsOverridden(nameof(DynamicProxy<T>.TryConvert))
  66. ? CallMethodWithResult(nameof(DynamicProxy<T>.TryConvert), binder, NoArgs, e => binder.FallbackConvert(this, e))
  67. : base.BindConvert(binder);
  68. }
  69. public override DynamicMetaObject BindInvokeMember(InvokeMemberBinder binder, DynamicMetaObject[] args)
  70. {
  71. if (!IsOverridden(nameof(DynamicProxy<T>.TryInvokeMember)))
  72. {
  73. return base.BindInvokeMember(binder, args);
  74. }
  75. //
  76. // Generate a tree like:
  77. //
  78. // {
  79. // object result;
  80. // TryInvokeMember(payload, out result)
  81. // ? result
  82. // : TryGetMember(payload, out result)
  83. // ? FallbackInvoke(result)
  84. // : fallbackResult
  85. // }
  86. //
  87. // Then it calls FallbackInvokeMember with this tree as the
  88. // "error", giving the language the option of using this
  89. // tree or doing .NET binding.
  90. //
  91. Fallback fallback = e => binder.FallbackInvokeMember(this, args, e);
  92. return BuildCallMethodWithResult(
  93. nameof(DynamicProxy<T>.TryInvokeMember),
  94. binder,
  95. GetArgArray(args),
  96. BuildCallMethodWithResult(
  97. nameof(DynamicProxy<T>.TryGetMember),
  98. new GetBinderAdapter(binder),
  99. NoArgs,
  100. fallback(null),
  101. e => binder.FallbackInvoke(e, args, null)
  102. ),
  103. null
  104. );
  105. }
  106. public override DynamicMetaObject BindCreateInstance(CreateInstanceBinder binder, DynamicMetaObject[] args)
  107. {
  108. return IsOverridden(nameof(DynamicProxy<T>.TryCreateInstance))
  109. ? CallMethodWithResult(nameof(DynamicProxy<T>.TryCreateInstance), binder, GetArgArray(args), e => binder.FallbackCreateInstance(this, args, e))
  110. : base.BindCreateInstance(binder, args);
  111. }
  112. public override DynamicMetaObject BindInvoke(InvokeBinder binder, DynamicMetaObject[] args)
  113. {
  114. return IsOverridden(nameof(DynamicProxy<T>.TryInvoke))
  115. ? CallMethodWithResult(nameof(DynamicProxy<T>.TryInvoke), binder, GetArgArray(args), e => binder.FallbackInvoke(this, args, e))
  116. : base.BindInvoke(binder, args);
  117. }
  118. public override DynamicMetaObject BindBinaryOperation(BinaryOperationBinder binder, DynamicMetaObject arg)
  119. {
  120. return IsOverridden(nameof(DynamicProxy<T>.TryBinaryOperation))
  121. ? CallMethodWithResult(nameof(DynamicProxy<T>.TryBinaryOperation), binder, GetArgs(arg), e => binder.FallbackBinaryOperation(this, arg, e))
  122. : base.BindBinaryOperation(binder, arg);
  123. }
  124. public override DynamicMetaObject BindUnaryOperation(UnaryOperationBinder binder)
  125. {
  126. return IsOverridden(nameof(DynamicProxy<T>.TryUnaryOperation))
  127. ? CallMethodWithResult(nameof(DynamicProxy<T>.TryUnaryOperation), binder, NoArgs, e => binder.FallbackUnaryOperation(this, e))
  128. : base.BindUnaryOperation(binder);
  129. }
  130. public override DynamicMetaObject BindGetIndex(GetIndexBinder binder, DynamicMetaObject[] indexes)
  131. {
  132. return IsOverridden(nameof(DynamicProxy<T>.TryGetIndex))
  133. ? CallMethodWithResult(nameof(DynamicProxy<T>.TryGetIndex), binder, GetArgArray(indexes), e => binder.FallbackGetIndex(this, indexes, e))
  134. : base.BindGetIndex(binder, indexes);
  135. }
  136. public override DynamicMetaObject BindSetIndex(SetIndexBinder binder, DynamicMetaObject[] indexes, DynamicMetaObject value)
  137. {
  138. return IsOverridden(nameof(DynamicProxy<T>.TrySetIndex))
  139. ? CallMethodReturnLast(nameof(DynamicProxy<T>.TrySetIndex), binder, GetArgArray(indexes, value), e => binder.FallbackSetIndex(this, indexes, value, e))
  140. : base.BindSetIndex(binder, indexes, value);
  141. }
  142. public override DynamicMetaObject BindDeleteIndex(DeleteIndexBinder binder, DynamicMetaObject[] indexes)
  143. {
  144. return IsOverridden(nameof(DynamicProxy<T>.TryDeleteIndex))
  145. ? CallMethodNoResult(nameof(DynamicProxy<T>.TryDeleteIndex), binder, GetArgArray(indexes), e => binder.FallbackDeleteIndex(this, indexes, e))
  146. : base.BindDeleteIndex(binder, indexes);
  147. }
  148. private delegate DynamicMetaObject Fallback(DynamicMetaObject errorSuggestion);
  149. private static Expression[] NoArgs => CollectionUtils.ArrayEmpty<Expression>();
  150. private static IEnumerable<Expression> GetArgs(params DynamicMetaObject[] args)
  151. {
  152. return args.Select(arg =>
  153. {
  154. Expression exp = arg.Expression;
  155. return exp.Type.IsValueType() ? Expression.Convert(exp, typeof(object)) : exp;
  156. });
  157. }
  158. private static Expression[] GetArgArray(DynamicMetaObject[] args)
  159. {
  160. return new[] { Expression.NewArrayInit(typeof(object), GetArgs(args)) };
  161. }
  162. private static Expression[] GetArgArray(DynamicMetaObject[] args, DynamicMetaObject value)
  163. {
  164. Expression exp = value.Expression;
  165. return new[]
  166. {
  167. Expression.NewArrayInit(typeof(object), GetArgs(args)),
  168. exp.Type.IsValueType() ? Expression.Convert(exp, typeof(object)) : exp
  169. };
  170. }
  171. private static ConstantExpression Constant(DynamicMetaObjectBinder binder)
  172. {
  173. Type t = binder.GetType();
  174. while (!t.IsVisible())
  175. {
  176. t = t.BaseType();
  177. }
  178. return Expression.Constant(binder, t);
  179. }
  180. /// <summary>
  181. /// Helper method for generating a MetaObject which calls a
  182. /// specific method on Dynamic that returns a result
  183. /// </summary>
  184. private DynamicMetaObject CallMethodWithResult(string methodName, DynamicMetaObjectBinder binder, IEnumerable<Expression> args, Fallback fallback, Fallback fallbackInvoke = null)
  185. {
  186. //
  187. // First, call fallback to do default binding
  188. // This produces either an error or a call to a .NET member
  189. //
  190. DynamicMetaObject fallbackResult = fallback(null);
  191. return BuildCallMethodWithResult(methodName, binder, args, fallbackResult, fallbackInvoke);
  192. }
  193. private DynamicMetaObject BuildCallMethodWithResult(string methodName, DynamicMetaObjectBinder binder, IEnumerable<Expression> args, DynamicMetaObject fallbackResult, Fallback fallbackInvoke)
  194. {
  195. //
  196. // Build a new expression like:
  197. // {
  198. // object result;
  199. // TryGetMember(payload, out result) ? fallbackInvoke(result) : fallbackResult
  200. // }
  201. //
  202. ParameterExpression result = Expression.Parameter(typeof(object), null);
  203. IList<Expression> callArgs = new List<Expression>();
  204. callArgs.Add(Expression.Convert(Expression, typeof(T)));
  205. callArgs.Add(Constant(binder));
  206. callArgs.AddRange(args);
  207. callArgs.Add(result);
  208. DynamicMetaObject resultMetaObject = new DynamicMetaObject(result, BindingRestrictions.Empty);
  209. // Need to add a conversion if calling TryConvert
  210. if (binder.ReturnType != typeof(object))
  211. {
  212. UnaryExpression convert = Expression.Convert(resultMetaObject.Expression, binder.ReturnType);
  213. // will always be a cast or unbox
  214. resultMetaObject = new DynamicMetaObject(convert, resultMetaObject.Restrictions);
  215. }
  216. if (fallbackInvoke != null)
  217. {
  218. resultMetaObject = fallbackInvoke(resultMetaObject);
  219. }
  220. DynamicMetaObject callDynamic = new DynamicMetaObject(
  221. Expression.Block(
  222. new[] { result },
  223. Expression.Condition(
  224. Expression.Call(
  225. Expression.Constant(_proxy),
  226. typeof(DynamicProxy<T>).GetMethod(methodName),
  227. callArgs
  228. ),
  229. resultMetaObject.Expression,
  230. fallbackResult.Expression,
  231. binder.ReturnType
  232. )
  233. ),
  234. GetRestrictions().Merge(resultMetaObject.Restrictions).Merge(fallbackResult.Restrictions)
  235. );
  236. return callDynamic;
  237. }
  238. /// <summary>
  239. /// Helper method for generating a MetaObject which calls a
  240. /// specific method on Dynamic, but uses one of the arguments for
  241. /// the result.
  242. /// </summary>
  243. private DynamicMetaObject CallMethodReturnLast(string methodName, DynamicMetaObjectBinder binder, IEnumerable<Expression> args, Fallback fallback)
  244. {
  245. //
  246. // First, call fallback to do default binding
  247. // This produces either an error or a call to a .NET member
  248. //
  249. DynamicMetaObject fallbackResult = fallback(null);
  250. //
  251. // Build a new expression like:
  252. // {
  253. // object result;
  254. // TrySetMember(payload, result = value) ? result : fallbackResult
  255. // }
  256. //
  257. ParameterExpression result = Expression.Parameter(typeof(object), null);
  258. IList<Expression> callArgs = new List<Expression>();
  259. callArgs.Add(Expression.Convert(Expression, typeof(T)));
  260. callArgs.Add(Constant(binder));
  261. callArgs.AddRange(args);
  262. callArgs[callArgs.Count - 1] = Expression.Assign(result, callArgs[callArgs.Count - 1]);
  263. return new DynamicMetaObject(
  264. Expression.Block(
  265. new[] { result },
  266. Expression.Condition(
  267. Expression.Call(
  268. Expression.Constant(_proxy),
  269. typeof(DynamicProxy<T>).GetMethod(methodName),
  270. callArgs
  271. ),
  272. result,
  273. fallbackResult.Expression,
  274. typeof(object)
  275. )
  276. ),
  277. GetRestrictions().Merge(fallbackResult.Restrictions)
  278. );
  279. }
  280. /// <summary>
  281. /// Helper method for generating a MetaObject which calls a
  282. /// specific method on Dynamic, but uses one of the arguments for
  283. /// the result.
  284. /// </summary>
  285. private DynamicMetaObject CallMethodNoResult(string methodName, DynamicMetaObjectBinder binder, Expression[] args, Fallback fallback)
  286. {
  287. //
  288. // First, call fallback to do default binding
  289. // This produces either an error or a call to a .NET member
  290. //
  291. DynamicMetaObject fallbackResult = fallback(null);
  292. IList<Expression> callArgs = new List<Expression>();
  293. callArgs.Add(Expression.Convert(Expression, typeof(T)));
  294. callArgs.Add(Constant(binder));
  295. callArgs.AddRange(args);
  296. //
  297. // Build a new expression like:
  298. // if (TryDeleteMember(payload)) { } else { fallbackResult }
  299. //
  300. return new DynamicMetaObject(
  301. Expression.Condition(
  302. Expression.Call(
  303. Expression.Constant(_proxy),
  304. typeof(DynamicProxy<T>).GetMethod(methodName),
  305. callArgs
  306. ),
  307. Expression.Empty(),
  308. fallbackResult.Expression,
  309. typeof(void)
  310. ),
  311. GetRestrictions().Merge(fallbackResult.Restrictions)
  312. );
  313. }
  314. /// <summary>
  315. /// Returns a Restrictions object which includes our current restrictions merged
  316. /// with a restriction limiting our type
  317. /// </summary>
  318. private BindingRestrictions GetRestrictions()
  319. {
  320. return (Value == null && HasValue)
  321. ? BindingRestrictions.GetInstanceRestriction(Expression, null)
  322. : BindingRestrictions.GetTypeRestriction(Expression, LimitType);
  323. }
  324. public override IEnumerable<string> GetDynamicMemberNames()
  325. {
  326. return _proxy.GetDynamicMemberNames((T)Value);
  327. }
  328. // It is okay to throw NotSupported from this binder. This object
  329. // is only used by DynamicObject.GetMember--it is not expected to
  330. // (and cannot) implement binding semantics. It is just so the DO
  331. // can use the Name and IgnoreCase properties.
  332. private sealed class GetBinderAdapter : GetMemberBinder
  333. {
  334. internal GetBinderAdapter(InvokeMemberBinder binder) :
  335. base(binder.Name, binder.IgnoreCase)
  336. {
  337. }
  338. public override DynamicMetaObject FallbackGetMember(DynamicMetaObject target, DynamicMetaObject errorSuggestion)
  339. {
  340. throw new NotSupportedException();
  341. }
  342. }
  343. }
  344. }
  345. #endif