PageRenderTime 35ms CodeModel.GetById 1ms RepoModel.GetById 0ms app.codeStats 0ms

/WCFWebApi/src/Microsoft.Json/System/Json/JsonValueDynamicMetaObject.cs

#
C# | 1067 lines | 693 code | 150 blank | 224 comment | 149 complexity | 859cd634438c5865541e6eafd23aeef8 MD5 | raw file
Possible License(s): CC-BY-SA-3.0, Apache-2.0
  1. // <copyright file="JsonValueDynamicMetaObject.cs" company="Microsoft Corporation">
  2. // Copyright (c) Microsoft Corporation. All rights reserved.
  3. // </copyright>
  4. // TODO: Remove CODEPLEX define once CSDMain 234546 has been resolved
  5. #define CODEPLEX
  6. namespace System.Json
  7. {
  8. using System;
  9. using System.Collections.Generic;
  10. using System.Diagnostics.CodeAnalysis;
  11. using System.Dynamic;
  12. using System.Linq.Expressions;
  13. using System.Reflection;
  14. using System.Runtime.Serialization.Json;
  15. using Microsoft.Server.Common;
  16. /// <summary>
  17. /// This class provides dynamic behavior support for the JsonValue types.
  18. /// </summary>
  19. internal class JsonValueDynamicMetaObject : DynamicMetaObject
  20. {
  21. private static readonly MethodInfo GetValueByIndexMethodInfo = typeof(JsonValue).GetMethod("GetValue", new Type[] { typeof(int) });
  22. private static readonly MethodInfo GetValueByKeyMethodInfo = typeof(JsonValue).GetMethod("GetValue", new Type[] { typeof(string) });
  23. private static readonly MethodInfo SetValueByIndexMethodInfo = typeof(JsonValue).GetMethod("SetValue", new Type[] { typeof(int), typeof(object) });
  24. private static readonly MethodInfo SetValueByKeyMethodInfo = typeof(JsonValue).GetMethod("SetValue", new Type[] { typeof(string), typeof(object) });
  25. private static readonly MethodInfo CastValueMethodInfo = typeof(JsonValue).GetMethod("CastValue", new Type[] { typeof(JsonValue) });
  26. private static readonly MethodInfo ChangeTypeMethodInfo = typeof(Convert).GetMethod("ChangeType", new Type[] { typeof(object), typeof(Type) });
  27. #if CODEPLEX
  28. private static readonly MethodInfo ReadAsMethodInfo = typeof(JsonValue).GetMethod("ReadAs", new Type[] { typeof(Type) });
  29. #endif
  30. /// <summary>
  31. /// Class constructor.
  32. /// </summary>
  33. /// <param name="parameter">The expression representing this <see cref="DynamicMetaObject"/> during the dynamic binding process.</param>
  34. /// <param name="value">The runtime value represented by the <see cref="DynamicMetaObject"/>.</param>
  35. internal JsonValueDynamicMetaObject(Expression parameter, JsonValue value)
  36. : base(parameter, BindingRestrictions.Empty, value)
  37. {
  38. }
  39. #if CODEPLEX
  40. /// <summary>
  41. /// Represents the level of support for operations.
  42. /// </summary>
  43. private enum OperationSupport
  44. {
  45. /// <summary>
  46. /// Operation fully supported on operands.
  47. /// </summary>
  48. Supported,
  49. /// <summary>
  50. /// Operation not supported on operand.
  51. /// </summary>
  52. NotSupported,
  53. /// <summary>
  54. /// Operation not supported on a <see cref="JsonValue "/> instance of certain <see cref="JsonType"/> type.
  55. /// </summary>
  56. NotSupportedOnJsonType,
  57. /// <summary>
  58. /// Operation not supported on second operand type.
  59. /// </summary>
  60. NotSupportedOnOperand,
  61. /// <summary>
  62. /// Operation not supported on second operand's value type.
  63. /// </summary>
  64. NotSupportedOnValueType
  65. }
  66. #endif
  67. /// <summary>
  68. /// Gets the default binding restrictions for this type.
  69. /// </summary>
  70. private BindingRestrictions DefaultRestrictions
  71. {
  72. get { return BindingRestrictions.GetTypeRestriction(this.Expression, this.LimitType); }
  73. }
  74. #if CODEPLEX
  75. /// <summary>
  76. /// Performs the binding of the dynamic unary operation.
  77. /// </summary>
  78. /// <param name="binder">An instance of the <see cref="UnaryOperationBinder"/> that represents the details of the dynamic operation.</param>
  79. /// <returns>The new <see cref="DynamicMetaObject"/> representing the result of the binding.</returns>
  80. public override DynamicMetaObject BindUnaryOperation(UnaryOperationBinder binder)
  81. {
  82. if (binder == null)
  83. {
  84. throw Fx.Exception.ArgumentNull("binder");
  85. }
  86. Expression operExpression = null;
  87. JsonValue jsonValue = this.Value as JsonValue;
  88. if (jsonValue is JsonPrimitive)
  89. {
  90. OperationSupport supportValue = GetUnaryOperationSupport(binder.Operation, jsonValue);
  91. if (supportValue == OperationSupport.Supported)
  92. {
  93. Type operationReturnType = this.GetUnaryOperationReturnType(binder);
  94. Expression instance = Expression.Convert(this.Expression, this.LimitType);
  95. Expression thisExpression = Expression.Convert(Expression.Call(instance, ReadAsMethodInfo, new Expression[] { Expression.Constant(operationReturnType) }), operationReturnType);
  96. operExpression = JsonValueDynamicMetaObject.GetUnaryOperationExpression(binder.Operation, thisExpression);
  97. }
  98. }
  99. if (operExpression == null)
  100. {
  101. operExpression = JsonValueDynamicMetaObject.GetOperationErrorExpression(OperationSupport.NotSupportedOnJsonType, binder.Operation, jsonValue, null);
  102. }
  103. operExpression = Expression.Convert(operExpression, binder.ReturnType);
  104. return new DynamicMetaObject(operExpression, this.DefaultRestrictions);
  105. }
  106. /// <summary>
  107. /// Performs the binding of the dynamic binary operation.
  108. /// </summary>
  109. /// <param name="binder">An instance of the <see cref="BinaryOperationBinder"/> that represents the details of the dynamic operation.</param>
  110. /// <param name="arg">An instance of the <see cref="DynamicMetaObject"/> representing the right hand side of the binary operation.</param>
  111. /// <returns>The new <see cref="DynamicMetaObject"/> representing the result of the binding.</returns>
  112. public override DynamicMetaObject BindBinaryOperation(BinaryOperationBinder binder, DynamicMetaObject arg)
  113. {
  114. if (binder == null)
  115. {
  116. throw Fx.Exception.ArgumentNull("binder");
  117. }
  118. if (arg == null)
  119. {
  120. throw Fx.Exception.ArgumentNull("arg");
  121. }
  122. Expression thisExpression = this.Expression;
  123. Expression otherExpression = arg.Expression;
  124. Expression operExpression = null;
  125. JsonValue otherValue = arg.Value as JsonValue;
  126. JsonValue thisValue = this.Value as JsonValue;
  127. OperationSupport supportValue = JsonValueDynamicMetaObject.GetBinaryOperationSupport(binder.Operation, thisValue, arg.Value);
  128. if (supportValue == OperationSupport.Supported)
  129. {
  130. if (otherValue != null)
  131. {
  132. if (thisValue is JsonPrimitive && otherValue is JsonPrimitive)
  133. {
  134. //// operation on primitive types.
  135. JsonValueDynamicMetaObject.GetBinaryOperandExpressions(binder.Operation, this, arg, ref thisExpression, ref otherExpression);
  136. }
  137. else
  138. {
  139. //// operation on JsonValue types.
  140. thisExpression = Expression.Convert(thisExpression, typeof(JsonValue));
  141. otherExpression = Expression.Convert(otherExpression, typeof(JsonValue));
  142. }
  143. }
  144. else
  145. {
  146. if (arg.Value != null)
  147. {
  148. //// operation on JSON primitive and CLR primitive
  149. JsonValueDynamicMetaObject.GetBinaryOperandExpressions(binder.Operation, this, arg, ref thisExpression, ref otherExpression);
  150. }
  151. else
  152. {
  153. //// operation on JsonValue and null.
  154. thisExpression = Expression.Convert(thisExpression, typeof(JsonValue));
  155. if (thisValue.JsonType == JsonType.Default)
  156. {
  157. thisExpression = Expression.Constant(null);
  158. }
  159. }
  160. }
  161. operExpression = JsonValueDynamicMetaObject.GetBinaryOperationExpression(binder.Operation, thisExpression, otherExpression);
  162. }
  163. if (operExpression == null)
  164. {
  165. operExpression = JsonValueDynamicMetaObject.GetOperationErrorExpression(supportValue, binder.Operation, thisValue, arg.Value);
  166. }
  167. operExpression = Expression.Convert(operExpression, typeof(object));
  168. return new DynamicMetaObject(operExpression, this.DefaultRestrictions);
  169. }
  170. #endif
  171. /// <summary>
  172. /// Implements dynamic cast for JsonValue types.
  173. /// </summary>
  174. /// <param name="binder">An instance of the <see cref="ConvertBinder"/> that represents the details of the dynamic operation.</param>
  175. /// <returns>The new <see cref="DynamicMetaObject"/> representing the result of the binding.</returns>
  176. public override DynamicMetaObject BindConvert(ConvertBinder binder)
  177. {
  178. if (binder == null)
  179. {
  180. throw Fx.Exception.ArgumentNull("binder");
  181. }
  182. Expression expression = this.Expression;
  183. bool implicitCastSupported =
  184. binder.Type.IsAssignableFrom(this.LimitType) ||
  185. binder.Type == typeof(IEnumerable<KeyValuePair<string, JsonValue>>) ||
  186. binder.Type == typeof(IDynamicMetaObjectProvider) ||
  187. binder.Type == typeof(object);
  188. if (!implicitCastSupported)
  189. {
  190. if (JsonValue.IsSupportedExplicitCastType(binder.Type))
  191. {
  192. Expression instance = Expression.Convert(this.Expression, this.LimitType);
  193. expression = Expression.Call(CastValueMethodInfo.MakeGenericMethod(binder.Type), new Expression[] { instance });
  194. }
  195. else
  196. {
  197. string exceptionMessage = SR.CannotCastJsonValue(this.LimitType.FullName, binder.Type.FullName);
  198. expression = Expression.Throw(Expression.Constant(new InvalidCastException(exceptionMessage)), typeof(object));
  199. }
  200. }
  201. expression = Expression.Convert(expression, binder.Type);
  202. return new DynamicMetaObject(expression, this.DefaultRestrictions);
  203. }
  204. /// <summary>
  205. /// Implements setter for dynamic indexer by index (JsonArray)
  206. /// </summary>
  207. /// <param name="binder">An instance of the <see cref="GetIndexBinder"/> that represents the details of the dynamic operation.</param>
  208. /// <param name="indexes">An array of <see cref="DynamicMetaObject"/> instances - indexes for the get index operation.</param>
  209. /// <returns>The new <see cref="DynamicMetaObject"/> representing the result of the binding.</returns>
  210. public override DynamicMetaObject BindGetIndex(GetIndexBinder binder, DynamicMetaObject[] indexes)
  211. {
  212. if (binder == null)
  213. {
  214. throw Fx.Exception.ArgumentNull("binder");
  215. }
  216. if (indexes == null)
  217. {
  218. throw Fx.Exception.ArgumentNull("indexes");
  219. }
  220. Expression indexExpression;
  221. if (!JsonValueDynamicMetaObject.TryGetIndexExpression(indexes, out indexExpression))
  222. {
  223. return new DynamicMetaObject(indexExpression, this.DefaultRestrictions);
  224. }
  225. MethodInfo methodInfo = indexExpression.Type == typeof(string) ? GetValueByKeyMethodInfo : GetValueByIndexMethodInfo;
  226. Expression[] args = new Expression[] { indexExpression };
  227. return this.GetMethodMetaObject(methodInfo, args);
  228. }
  229. /// <summary>
  230. /// Implements getter for dynamic indexer by index (JsonArray).
  231. /// </summary>
  232. /// <param name="binder">An instance of the <see cref="SetIndexBinder"/> that represents the details of the dynamic operation.</param>
  233. /// <param name="indexes">An array of <see cref="DynamicMetaObject"/> instances - indexes for the set index operation.</param>
  234. /// <param name="value">The <see cref="DynamicMetaObject"/> representing the value for the set index operation.</param>
  235. /// <returns>The new <see cref="DynamicMetaObject"/> representing the result of the binding.</returns>
  236. public override DynamicMetaObject BindSetIndex(SetIndexBinder binder, DynamicMetaObject[] indexes, DynamicMetaObject value)
  237. {
  238. if (binder == null)
  239. {
  240. throw Fx.Exception.ArgumentNull("binder");
  241. }
  242. if (indexes == null)
  243. {
  244. throw Fx.Exception.ArgumentNull("indexes");
  245. }
  246. if (value == null)
  247. {
  248. throw Fx.Exception.ArgumentNull("value");
  249. }
  250. Expression indexExpression;
  251. if (!JsonValueDynamicMetaObject.TryGetIndexExpression(indexes, out indexExpression))
  252. {
  253. return new DynamicMetaObject(indexExpression, this.DefaultRestrictions);
  254. }
  255. MethodInfo methodInfo = indexExpression.Type == typeof(string) ? SetValueByKeyMethodInfo : SetValueByIndexMethodInfo;
  256. Expression[] args = new Expression[] { indexExpression, Expression.Convert(value.Expression, typeof(object)) };
  257. return this.GetMethodMetaObject(methodInfo, args);
  258. }
  259. /// <summary>
  260. /// Implements getter for dynamic indexer by key (JsonObject).
  261. /// </summary>
  262. /// <param name="binder">An instance of the <see cref="GetMemberBinder"/> that represents the details of the dynamic operation.</param>
  263. /// <returns>The new <see cref="DynamicMetaObject"/> representing the result of the binding.</returns>
  264. public override DynamicMetaObject BindGetMember(GetMemberBinder binder)
  265. {
  266. if (binder == null)
  267. {
  268. throw Fx.Exception.ArgumentNull("binder");
  269. }
  270. PropertyInfo propInfo = this.LimitType.GetProperty(binder.Name, BindingFlags.Instance | BindingFlags.Public);
  271. if (propInfo != null)
  272. {
  273. return base.BindGetMember(binder);
  274. }
  275. Expression[] args = new Expression[] { Expression.Constant(binder.Name) };
  276. return this.GetMethodMetaObject(GetValueByKeyMethodInfo, args);
  277. }
  278. /// <summary>
  279. /// Implements setter for dynamic indexer by key (JsonObject).
  280. /// </summary>
  281. /// <param name="binder">An instance of the <see cref="SetMemberBinder"/> that represents the details of the dynamic operation.</param>
  282. /// <param name="value">The <see cref="DynamicMetaObject"/> representing the value for the set member operation.</param>
  283. /// <returns>The new <see cref="DynamicMetaObject"/> representing the result of the binding.</returns>
  284. public override DynamicMetaObject BindSetMember(SetMemberBinder binder, DynamicMetaObject value)
  285. {
  286. if (binder == null)
  287. {
  288. throw Fx.Exception.ArgumentNull("binder");
  289. }
  290. if (value == null)
  291. {
  292. throw Fx.Exception.ArgumentNull("value");
  293. }
  294. Expression[] args = new Expression[] { Expression.Constant(binder.Name), Expression.Convert(value.Expression, typeof(object)) };
  295. return this.GetMethodMetaObject(SetValueByKeyMethodInfo, args);
  296. }
  297. /// <summary>
  298. /// Performs the binding of the dynamic invoke member operation.
  299. /// Implemented to support extension methods defined in <see cref="JsonValueExtensions"/> type.
  300. /// </summary>
  301. /// <param name="binder">An instance of the InvokeMemberBinder that represents the details of the dynamic operation.</param>
  302. /// <param name="args">An array of DynamicMetaObject instances - arguments to the invoke member operation.</param>
  303. /// <returns>The new DynamicMetaObject representing the result of the binding.</returns>
  304. public override DynamicMetaObject BindInvokeMember(InvokeMemberBinder binder, DynamicMetaObject[] args)
  305. {
  306. if (binder == null)
  307. {
  308. throw Fx.Exception.ArgumentNull("binder");
  309. }
  310. if (args == null)
  311. {
  312. throw Fx.Exception.ArgumentNull("args");
  313. }
  314. List<Type> argTypeList = new List<Type>();
  315. for (int idx = 0; idx < args.Length; idx++)
  316. {
  317. argTypeList.Add(args[idx].LimitType);
  318. }
  319. MethodInfo methodInfo = this.Value.GetType().GetMethod(binder.Name, argTypeList.ToArray());
  320. if (methodInfo == null)
  321. {
  322. argTypeList.Insert(0, typeof(JsonValue));
  323. Type[] argTypes = argTypeList.ToArray();
  324. methodInfo = JsonValueDynamicMetaObject.GetExtensionMethod(typeof(JsonValueExtensions), binder.Name, argTypes);
  325. if (methodInfo != null)
  326. {
  327. Expression thisInstance = Expression.Convert(this.Expression, this.LimitType);
  328. Expression[] argsExpression = new Expression[argTypes.Length];
  329. argsExpression[0] = thisInstance;
  330. for (int i = 0; i < args.Length; i++)
  331. {
  332. argsExpression[i + 1] = args[i].Expression;
  333. }
  334. Expression callExpression = Expression.Call(methodInfo, argsExpression);
  335. if (methodInfo.ReturnType == typeof(void))
  336. {
  337. callExpression = Expression.Block(callExpression, Expression.Default(binder.ReturnType));
  338. }
  339. else
  340. {
  341. callExpression = Expression.Convert(Expression.Call(methodInfo, argsExpression), binder.ReturnType);
  342. }
  343. return new DynamicMetaObject(callExpression, this.DefaultRestrictions);
  344. }
  345. }
  346. return base.BindInvokeMember(binder, args);
  347. }
  348. /// <summary>
  349. /// Returns the enumeration of all dynamic member names.
  350. /// </summary>
  351. /// <returns>An <see cref="IEnumerable{T}"/> of string reprenseting the dynamic member names.</returns>
  352. public override IEnumerable<string> GetDynamicMemberNames()
  353. {
  354. JsonValue jsonValue = this.Value as JsonValue;
  355. if (jsonValue != null)
  356. {
  357. List<string> names = new List<string>();
  358. foreach (KeyValuePair<string, JsonValue> pair in jsonValue)
  359. {
  360. names.Add(pair.Key);
  361. }
  362. return names;
  363. }
  364. return base.GetDynamicMemberNames();
  365. }
  366. #if CODEPLEX
  367. /// <summary>
  368. /// Gets the operation support value for the specified operation on the specified operand.
  369. /// </summary>
  370. /// <param name="operation">The operation type.</param>
  371. /// <param name="thisValue">The JsonValue instance to check operation for.</param>
  372. /// <returns>An <see cref="OperationSupport"/> value.</returns>
  373. private static OperationSupport GetUnaryOperationSupport(ExpressionType operation, JsonValue thisValue)
  374. {
  375. //// Unary operators: +, -, !, ~, false (&&), true (||)
  376. //// unsupported: ++, --
  377. switch (operation)
  378. {
  379. case ExpressionType.UnaryPlus:
  380. case ExpressionType.Negate:
  381. case ExpressionType.OnesComplement:
  382. case ExpressionType.IsFalse:
  383. case ExpressionType.IsTrue:
  384. break;
  385. case ExpressionType.Not:
  386. //// The DLR converts the 'Not' operation into a 'OnesComplement' operation for integer numbers, need to block that scenario.
  387. bool boolVal;
  388. if (!thisValue.TryReadAs<bool>(out boolVal))
  389. {
  390. return OperationSupport.NotSupportedOnOperand;
  391. }
  392. break;
  393. default:
  394. return OperationSupport.NotSupported;
  395. }
  396. return OperationSupport.Supported;
  397. }
  398. /// <summary>
  399. /// Gets the operation support value for the specified operation and operands.
  400. /// </summary>
  401. /// <param name="operation">The operation type.</param>
  402. /// <param name="thisValue">The JsonValue instance to check operation for.</param>
  403. /// <param name="operand">The second operand instance.</param>
  404. /// <returns>An <see cref="OperationSupport"/> value.</returns>
  405. [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity",
  406. Justification = "It doesn't make sense to break up this method.")]
  407. private static OperationSupport GetBinaryOperationSupport(ExpressionType operation, JsonValue thisValue, object operand)
  408. {
  409. //// Supported binary operators: +, -, *, /, %, &, |, ^, <<, >>, ==, !=, >, <, >=, <=
  410. bool isCompareOperation = false;
  411. JsonValue otherValue = operand as JsonValue;
  412. switch (operation)
  413. {
  414. //// supported binary operations
  415. case ExpressionType.Add:
  416. case ExpressionType.Subtract:
  417. case ExpressionType.Multiply:
  418. case ExpressionType.Divide:
  419. case ExpressionType.Modulo:
  420. case ExpressionType.And:
  421. case ExpressionType.Or:
  422. case ExpressionType.ExclusiveOr:
  423. case ExpressionType.LeftShift:
  424. case ExpressionType.RightShift:
  425. case ExpressionType.GreaterThan:
  426. case ExpressionType.GreaterThanOrEqual:
  427. case ExpressionType.LessThan:
  428. case ExpressionType.LessThanOrEqual:
  429. break;
  430. //// compare operations:
  431. case ExpressionType.Equal:
  432. case ExpressionType.NotEqual:
  433. isCompareOperation = true;
  434. break;
  435. default:
  436. return OperationSupport.NotSupported;
  437. }
  438. if (operand != null)
  439. {
  440. bool thisIsPrimitive = thisValue is JsonPrimitive;
  441. if (otherValue != null)
  442. {
  443. if (!(otherValue is JsonPrimitive) || !thisIsPrimitive)
  444. {
  445. //// When either value is non-primitive it must be a compare operation.
  446. if (!isCompareOperation)
  447. {
  448. return OperationSupport.NotSupportedOnJsonType;
  449. }
  450. }
  451. }
  452. else
  453. {
  454. //// if operand is not a JsonValue it must be a primitive CLR type and first operand must be a JsonPrimitive.
  455. if (!thisIsPrimitive)
  456. {
  457. return OperationSupport.NotSupportedOnJsonType;
  458. }
  459. JsonPrimitive primitiveValue = null;
  460. if (!JsonPrimitive.TryCreate(operand, out primitiveValue))
  461. {
  462. return OperationSupport.NotSupportedOnValueType;
  463. }
  464. }
  465. }
  466. else
  467. {
  468. //// when operand is null only compare operations are valid.
  469. if (!isCompareOperation)
  470. {
  471. return OperationSupport.NotSupportedOnOperand;
  472. }
  473. }
  474. return OperationSupport.Supported;
  475. }
  476. /// <summary>
  477. /// Returns an expression representing a unary operation based on the specified operation type.
  478. /// </summary>
  479. /// <param name="operation">The operation type.</param>
  480. /// <param name="thisExpression">The operand.</param>
  481. /// <returns>The expression representing the unary operation.</returns>
  482. private static Expression GetUnaryOperationExpression(ExpressionType operation, Expression thisExpression)
  483. {
  484. //// Unary operators: +, -, !, ~, false (&&), true (||)
  485. //// unsupported: ++, --
  486. Expression operExpression = null;
  487. try
  488. {
  489. switch (operation)
  490. {
  491. case ExpressionType.UnaryPlus:
  492. operExpression = Expression.UnaryPlus(thisExpression);
  493. break;
  494. case ExpressionType.Negate:
  495. operExpression = Expression.Negate(thisExpression);
  496. break;
  497. case ExpressionType.Not:
  498. operExpression = Expression.Not(thisExpression);
  499. break;
  500. case ExpressionType.OnesComplement:
  501. operExpression = Expression.OnesComplement(thisExpression);
  502. break;
  503. case ExpressionType.IsFalse:
  504. operExpression = Expression.IsFalse(thisExpression);
  505. break;
  506. case ExpressionType.IsTrue:
  507. operExpression = Expression.IsTrue(thisExpression);
  508. break;
  509. }
  510. }
  511. catch (InvalidOperationException ex)
  512. {
  513. operExpression = Expression.Throw(Expression.Constant(ex), typeof(object));
  514. }
  515. return operExpression;
  516. }
  517. /// <summary>
  518. /// Updates the <see cref="Expression"/> tree for the operands of the specified operation.
  519. /// </summary>
  520. /// <param name="operation">The operation to evalutes.</param>
  521. /// <param name="thisOperand">The first operand.</param>
  522. /// <param name="otherOperand">The second operand.</param>
  523. /// <param name="thisExpression">The <see cref="Expression"/> for the first operand.</param>
  524. /// <param name="otherExpression">The <see cref="Expression"/> for the second operand.</param>
  525. private static void GetBinaryOperandExpressions(ExpressionType operation, DynamicMetaObject thisOperand, DynamicMetaObject otherOperand, ref Expression thisExpression, ref Expression otherExpression)
  526. {
  527. JsonValue thisValue = thisOperand.Value as JsonValue;
  528. JsonValue otherValue = otherOperand.Value as JsonValue;
  529. Type thisType = thisValue.Read().GetType();
  530. Type otherType = otherValue != null ? otherValue.Read().GetType() : otherOperand.Value.GetType();
  531. Type coercedType;
  532. if (JsonValueDynamicMetaObject.TryCoerceType(operation, thisType, otherType, out coercedType))
  533. {
  534. thisType = otherType = coercedType;
  535. }
  536. else if (JsonValueDynamicMetaObject.TryCoerceSpecialTypes(thisOperand, otherOperand, out coercedType))
  537. {
  538. thisType = otherType = coercedType;
  539. }
  540. thisExpression = Expression.Convert(thisExpression, thisOperand.LimitType);
  541. thisExpression = Expression.Convert(Expression.Call(thisExpression, ReadAsMethodInfo, new Expression[] { Expression.Constant(thisType) }), thisType);
  542. otherExpression = Expression.Convert(otherExpression, otherOperand.LimitType);
  543. if (otherValue != null)
  544. {
  545. otherExpression = Expression.Convert(Expression.Call(otherExpression, ReadAsMethodInfo, new Expression[] { Expression.Constant(otherType) }), otherType);
  546. }
  547. else if (otherOperand.LimitType != otherType)
  548. {
  549. otherExpression = Expression.Convert(otherExpression, otherType);
  550. }
  551. }
  552. /// <summary>
  553. /// Returns an Expression representing a binary operation based on the specified operation type.
  554. /// </summary>
  555. /// <param name="operation">The operation type.</param>
  556. /// <param name="thisExpression">An expression representing the left operand.</param>
  557. /// <param name="otherExpression">An expression representing the right operand.</param>
  558. /// <returns>The expression representing the binary operation.</returns>
  559. private static Expression GetBinaryOperationExpression(ExpressionType operation, Expression thisExpression, Expression otherExpression)
  560. {
  561. //// Binary operators: +, -, *, /, %, &, |, ^, <<, >>, ==, !=, >, <, >=, <=
  562. //// The '&&' and '||' operators are conditional versions of the '&' and '|' operators.
  563. //// Unsupported: Compound assignment operators.
  564. Expression operExpression = null;
  565. try
  566. {
  567. switch (operation)
  568. {
  569. case ExpressionType.Equal:
  570. operExpression = Expression.Equal(thisExpression, otherExpression);
  571. break;
  572. case ExpressionType.NotEqual:
  573. operExpression = Expression.NotEqual(thisExpression, otherExpression);
  574. break;
  575. case ExpressionType.GreaterThan:
  576. operExpression = Expression.GreaterThan(thisExpression, otherExpression);
  577. break;
  578. case ExpressionType.GreaterThanOrEqual:
  579. operExpression = Expression.GreaterThanOrEqual(thisExpression, otherExpression);
  580. break;
  581. case ExpressionType.LessThan:
  582. operExpression = Expression.LessThan(thisExpression, otherExpression);
  583. break;
  584. case ExpressionType.LessThanOrEqual:
  585. operExpression = Expression.LessThanOrEqual(thisExpression, otherExpression);
  586. break;
  587. case ExpressionType.LeftShift:
  588. operExpression = Expression.LeftShift(thisExpression, otherExpression);
  589. break;
  590. case ExpressionType.RightShift:
  591. operExpression = Expression.RightShift(thisExpression, otherExpression);
  592. break;
  593. case ExpressionType.And:
  594. operExpression = Expression.And(thisExpression, otherExpression);
  595. break;
  596. case ExpressionType.Or:
  597. operExpression = Expression.Or(thisExpression, otherExpression);
  598. break;
  599. case ExpressionType.ExclusiveOr:
  600. operExpression = Expression.ExclusiveOr(thisExpression, otherExpression);
  601. break;
  602. case ExpressionType.Add:
  603. operExpression = Expression.Add(thisExpression, otherExpression);
  604. break;
  605. case ExpressionType.Subtract:
  606. operExpression = Expression.Subtract(thisExpression, otherExpression);
  607. break;
  608. case ExpressionType.Multiply:
  609. operExpression = Expression.Multiply(thisExpression, otherExpression);
  610. break;
  611. case ExpressionType.Divide:
  612. operExpression = Expression.Divide(thisExpression, otherExpression);
  613. break;
  614. case ExpressionType.Modulo:
  615. operExpression = Expression.Modulo(thisExpression, otherExpression);
  616. break;
  617. }
  618. }
  619. catch (InvalidOperationException ex)
  620. {
  621. operExpression = Expression.Throw(Expression.Constant(ex), typeof(object));
  622. }
  623. return operExpression;
  624. }
  625. /// <summary>
  626. /// Returns an expression representing a 'throw' instruction based on the specified <see cref="OperationSupport"/> value.
  627. /// </summary>
  628. /// <param name="supportValue">The <see cref="OperationSupport"/> value.</param>
  629. /// <param name="operation">The operation type.</param>
  630. /// <param name="thisValue">The operation left operand.</param>
  631. /// <param name="operand">The operation right operand.</param>
  632. /// <returns>A <see cref="Expression"/> representing a 'throw' instruction.</returns>
  633. private static Expression GetOperationErrorExpression(OperationSupport supportValue, ExpressionType operation, JsonValue thisValue, object operand)
  634. {
  635. string exceptionMessage;
  636. string operandTypeName = operand != null ? operand.GetType().FullName : "<null>";
  637. switch (supportValue)
  638. {
  639. default:
  640. case OperationSupport.NotSupported:
  641. case OperationSupport.NotSupportedOnJsonType:
  642. case OperationSupport.NotSupportedOnValueType:
  643. exceptionMessage = SR.OperatorNotDefinedForJsonType(operation, thisValue.JsonType);
  644. break;
  645. case OperationSupport.NotSupportedOnOperand:
  646. exceptionMessage = SR.OperatorNotAllowedOnOperands(operation, thisValue.GetType().FullName, operandTypeName);
  647. break;
  648. }
  649. return Expression.Throw(Expression.Constant(new InvalidOperationException(exceptionMessage)), typeof(object));
  650. }
  651. #endif
  652. /// <summary>
  653. /// Gets a <see cref="MethodInfo"/> instance for the specified method name in the specified type.
  654. /// </summary>
  655. /// <param name="extensionProviderType">The extension provider type.</param>
  656. /// <param name="methodName">The name of the method to get the info for.</param>
  657. /// <param name="argTypes">The types of the method arguments.</param>
  658. /// <returns>A <see cref="MethodInfo"/>instance or null if the method cannot be resolved.</returns>
  659. private static MethodInfo GetExtensionMethod(Type extensionProviderType, string methodName, Type[] argTypes)
  660. {
  661. MethodInfo methodInfo = null;
  662. MethodInfo[] methods = extensionProviderType.GetMethods();
  663. foreach (MethodInfo info in methods)
  664. {
  665. if (info.Name == methodName)
  666. {
  667. methodInfo = info;
  668. if (!info.IsGenericMethodDefinition)
  669. {
  670. bool paramsMatch = true;
  671. ParameterInfo[] args = methodInfo.GetParameters();
  672. if (args.Length == argTypes.Length)
  673. {
  674. for (int idx = 0; idx < args.Length; idx++)
  675. {
  676. if (!args[idx].ParameterType.IsAssignableFrom(argTypes[idx]))
  677. {
  678. paramsMatch = false;
  679. break;
  680. }
  681. }
  682. if (paramsMatch)
  683. {
  684. break;
  685. }
  686. }
  687. }
  688. }
  689. }
  690. return methodInfo;
  691. }
  692. /// <summary>
  693. /// Attempts to get an expression for an index parameter.
  694. /// </summary>
  695. /// <param name="indexes">The operation indexes parameter.</param>
  696. /// <param name="expression">A <see cref="Expression"/> to be initialized to the index expression if the operation is successful, otherwise an error expression.</param>
  697. /// <returns>true the operation is successful, false otherwise.</returns>
  698. private static bool TryGetIndexExpression(DynamicMetaObject[] indexes, out Expression expression)
  699. {
  700. if (indexes.Length == 1 && indexes[0] != null && indexes[0].Value != null)
  701. {
  702. DynamicMetaObject index = indexes[0];
  703. Type indexType = indexes[0].Value.GetType();
  704. switch (Type.GetTypeCode(indexType))
  705. {
  706. case TypeCode.Char:
  707. case TypeCode.Int16:
  708. case TypeCode.UInt16:
  709. case TypeCode.Byte:
  710. case TypeCode.SByte:
  711. Expression argExp = Expression.Convert(index.Expression, typeof(object));
  712. Expression typeExp = Expression.Constant(typeof(int));
  713. expression = Expression.Convert(Expression.Call(ChangeTypeMethodInfo, new Expression[] { argExp, typeExp }), typeof(int));
  714. return true;
  715. case TypeCode.Int32:
  716. case TypeCode.String:
  717. expression = index.Expression;
  718. return true;
  719. }
  720. expression = Expression.Throw(Expression.Constant(new ArgumentException(SR.InvalidIndexType(indexType))), typeof(object));
  721. return false;
  722. }
  723. expression = Expression.Throw(Expression.Constant(new ArgumentException(SR.NonSingleNonNullIndexNotSupported)), typeof(object));
  724. return false;
  725. }
  726. #if CODEPLEX
  727. /// <summary>
  728. /// Attempts to coerce the operand types on a binary operation for some special type and value cases as treated by JsonValue:
  729. /// "true" and "false" can be converted to boolean.
  730. /// Guid, DateTime and other types can be converted to string.
  731. /// </summary>
  732. /// <param name="thisOperand">The first operand.</param>
  733. /// <param name="otherOperand">The second operand</param>
  734. /// <param name="coercedType">On success, this parameter contains the coerced type.</param>
  735. /// <returns>true if the coercion is performed, false otherwise.</returns>
  736. private static bool TryCoerceSpecialTypes(DynamicMetaObject thisOperand, DynamicMetaObject otherOperand, out Type coercedType)
  737. {
  738. JsonValue thisValue = thisOperand.Value as JsonValue;
  739. JsonValue otherValue = otherOperand.Value as JsonValue;
  740. if (thisValue is JsonPrimitive)
  741. {
  742. Type thisType = thisValue.Read().GetType();
  743. if (thisType != otherOperand.LimitType)
  744. {
  745. if (otherOperand.LimitType == typeof(string) || (thisType == typeof(string) && otherValue == null))
  746. {
  747. object value;
  748. if (thisValue.TryReadAs(otherOperand.LimitType, out value))
  749. {
  750. coercedType = otherOperand.LimitType;
  751. return true;
  752. }
  753. }
  754. }
  755. }
  756. coercedType = default(Type);
  757. return false;
  758. }
  759. /// <summary>
  760. /// Attempts to coerce one of the specified types to the other if needed and if possible.
  761. /// </summary>
  762. /// <param name="operation">The operation for the type coercion.</param>
  763. /// <param name="thisType">The type of the first operand.</param>
  764. /// <param name="otherType">The type of the second operand.</param>
  765. /// <param name="coercedType">The coerced type.</param>
  766. /// <returns>true if the type is coerced, false otherwise.</returns>
  767. private static bool TryCoerceType(ExpressionType operation, Type thisType, Type otherType, out Type coercedType)
  768. {
  769. //// Supported coercion operators: +, -, *, /, %, ==, !=, >, <, >=, <=
  770. if (thisType != otherType)
  771. {
  772. switch (operation)
  773. {
  774. case ExpressionType.Add:
  775. case ExpressionType.Subtract:
  776. case ExpressionType.Multiply:
  777. case ExpressionType.Divide:
  778. case ExpressionType.Modulo:
  779. case ExpressionType.Equal:
  780. case ExpressionType.NotEqual:
  781. case ExpressionType.GreaterThan:
  782. case ExpressionType.GreaterThanOrEqual:
  783. case ExpressionType.LessThan:
  784. case ExpressionType.LessThanOrEqual:
  785. if (ImplicitNumericTypeConverter.TryGetCoercionType(thisType, otherType, out coercedType))
  786. {
  787. return true;
  788. }
  789. break;
  790. }
  791. }
  792. coercedType = default(Type);
  793. return false;
  794. }
  795. /// <summary>
  796. /// Gets the return type for unary operations.
  797. /// </summary>
  798. /// <param name="binder">The unary operation binder.</param>
  799. /// <returns>The type representing the operation return type.</returns>
  800. private Type GetUnaryOperationReturnType(UnaryOperationBinder binder)
  801. {
  802. JsonValue thisValue = this.Value as JsonValue;
  803. Type returnType = binder.ReturnType == typeof(object) ? thisValue.Read().GetType() : binder.ReturnType;
  804. //// The DLR sets the binder.ReturnType for the unary 'Not' operation as 'object' as opposed to 'bool',
  805. //// we need to detect this case and fix up the type to enable boolean conversions from strings.
  806. if (returnType == typeof(string) && binder.Operation == ExpressionType.Not)
  807. {
  808. bool boolVal;
  809. if (thisValue.TryReadAs<bool>(out boolVal))
  810. {
  811. returnType = typeof(bool);
  812. }
  813. }
  814. return returnType;
  815. }
  816. #endif
  817. /// <summary>
  818. /// Gets a <see cref="DynamicMetaObject"/> for a method call.
  819. /// </summary>
  820. /// <param name="methodInfo">Info for the method to be performed.</param>
  821. /// <param name="args">expression array representing the method arguments</param>
  822. /// <returns>A meta object for the method call.</returns>
  823. private DynamicMetaObject GetMethodMetaObject(MethodInfo methodInfo, Expression[] args)
  824. {
  825. Expression instance = Expression.Convert(this.Expression, this.LimitType);
  826. Expression methodCall = Expression.Call(instance, methodInfo, args);
  827. BindingRestrictions restrictions = this.DefaultRestrictions;
  828. DynamicMetaObject metaObj = new DynamicMetaObject(methodCall, restrictions);
  829. return metaObj;
  830. }
  831. #if CODEPLEX
  832. /// <summary>
  833. /// Helper class for numeric type coercion support.
  834. /// </summary>
  835. private static class ImplicitNumericTypeConverter
  836. {
  837. /// <summary>
  838. /// Table of implicit conversion types, the 'values' in the dictionary represent the type values the 'key' type
  839. /// can be converted to.
  840. /// </summary>
  841. /// <remarks>
  842. /// Observe that this table is not the full conversion table, for storage optimization some types have been factored
  843. /// out into a list; the algorithm that uses this table knows about this optimization.
  844. /// For reference see Implicit Conversion table in the MSDN http://msdn.microsoft.com/en-us/library/y5b434w4.aspx
  845. /// sbyte -> short, int, long, float, double, or decimal
  846. /// byte -> short, ushort, int, uint, long, ulong, float, double, or decimal
  847. /// short -> int, long, float, double, or decimal
  848. /// ushort -> int, uint, long, ulong, float, double, or decimal
  849. /// int -> long, float, double, or decimal
  850. /// uint -> long, ulong, float, double, or decimal
  851. /// long -> float, double, or decimal
  852. /// char -> ushort, int, uint, long, ulong, float, double, or decimal
  853. /// float -> double
  854. /// ulong -> float, double, or decimal
  855. /// </remarks>
  856. private static Dictionary<string, List<string>> partialConversionTable = new Dictionary<string, List<string>>
  857. {
  858. { typeof(sbyte).Name, new List<string>() { typeof(short).Name, typeof(int).Name } },
  859. { typeof(byte).Name, new List<string>() { typeof(short).Name, typeof(ushort).Name, typeof(int).Name, typeof(uint).Name, typeof(ulong).Name } },
  860. { typeof(short).Name, new List<string>() { typeof(int).Name } },
  861. { typeof(ushort).Name, new List<string>() { typeof(int).Name, typeof(uint).Name, typeof(ulong).Name } },
  862. { typeof(int).Name, new List<string>() { } },
  863. { typeof(uint).Name, new List<string>() { typeof(ulong).Name } },
  864. { typeof(long).Name, new List<string>() { } },
  865. { typeof(char).Name, new List<string>() { typeof(ushort).Name, typeof(int).Name, typeof(uint).Name, typeof(ulong).Name } },
  866. { typeof(ulong).Name, new List<string>() { } },
  867. ////{ typeof(float).Name, new List<string>() { typeof(double).Name }},
  868. };
  869. /// <summary>
  870. /// List of types most other types can be coerced to.
  871. /// </summary>
  872. private static List<string> universalCoercionTypes = new List<string> { typeof(long).Name, typeof(float).Name, typeof(double).Name, typeof(decimal).Name };
  873. /// <summary>
  874. /// Attempts to coerce one type to another.
  875. /// </summary>
  876. /// <param name="thisType">The first type to be evaluated.</param>
  877. /// <param name="otherType">The second type to be evaluated.</param>
  878. /// <param name="coercedType">The coerced resulting type to be used.</param>
  879. /// <returns>true if the coercion exists, false otherwise.</returns>
  880. public static bool TryGetCoercionType(Type thisType, Type otherType, out Type coercedType)
  881. {
  882. //// checks covering for storage optimizations in the implicit numeric converstion table
  883. Type typeofLong = typeof(long);
  884. Type typeofULong = typeof(ulong);
  885. // special-case ulong type since it cannot be coerced to long which is part of the universal coercion list.
  886. if ((thisType == typeofULong && otherType == typeofLong) || (thisType == typeofLong && otherType == typeofULong))
  887. {
  888. coercedType = default(Type);
  889. return false;
  890. }
  891. Type typeofFloat = typeof(float);
  892. Type typeofDouble = typeof(double);
  893. // special-case float since it can be coerced to double only.
  894. if ((thisType == typeofFloat && otherType == typeofDouble) || (otherType == typeofFloat && thisType == typeofDouble))
  895. {
  896. coercedType = typeofDouble;
  897. return true;
  898. }
  899. if (partialConversionTable.ContainsKey(thisType.Name) &&
  900. (partialConversionTable[thisType.Name].Contains(otherType.Name) || universalCoercionTypes.Contains(otherType.Name)))
  901. {
  902. coercedType = otherType;
  903. return true;
  904. }
  905. if (partialConversionTable.ContainsKey(otherType.Name) &&
  906. (partialConversionTable[otherType.Name].Contains(thisType.Name) || universalCoercionTypes.Contains(thisType.Name)))
  907. {
  908. coercedType = thisType;
  909. return true;
  910. }
  911. coercedType = default(Type);
  912. return false;
  913. }
  914. }
  915. #endif
  916. }
  917. }