ConstraintParser.cs 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. // -----------------------------------------------------------------------
  2. // Copyright (c) Microsoft Corporation. All rights reserved.
  3. // -----------------------------------------------------------------------
  4. using System;
  5. using System.Collections.Generic;
  6. using System.Linq.Expressions;
  7. using System.Reflection;
  8. using Microsoft.Internal;
  9. using Microsoft.VisualStudio.TestTools.UnitTesting;
  10. using System.ComponentModel.Composition.Primitives;
  11. namespace System.ComponentModel.Composition
  12. {
  13. public class ContraintParser
  14. {
  15. private static readonly PropertyInfo _exportDefinitionContractNameProperty = typeof(ExportDefinition).GetProperty("ContractName");
  16. private static readonly PropertyInfo _exportDefinitionMetadataProperty = typeof(ExportDefinition).GetProperty("Metadata");
  17. private static readonly MethodInfo _metadataContainsKeyMethod = typeof(IDictionary<string, object>).GetMethod("ContainsKey");
  18. private static readonly MethodInfo _metadataItemMethod = typeof(IDictionary<string, object>).GetMethod("get_Item");
  19. private static readonly MethodInfo _typeIsInstanceOfTypeMethod = typeof(Type).GetMethod("IsInstanceOfType");
  20. public static bool TryParseConstraint(Expression<Func<ExportDefinition, bool>> constraint, out string contractName, out IEnumerable<KeyValuePair<string, Type>> requiredMetadata)
  21. {
  22. contractName = null;
  23. requiredMetadata = null;
  24. List<KeyValuePair<string, Type>> requiredMetadataList = new List<KeyValuePair<string, Type>>();
  25. foreach (Expression expression in SplitConstraintBody(constraint.Body))
  26. {
  27. // First try to parse as a contract, if we don't have one already
  28. if (contractName == null && TryParseExpressionAsContractConstraintBody(expression, constraint.Parameters[0], out contractName))
  29. {
  30. continue;
  31. }
  32. // Then try to parse as a required metadata item name
  33. string requiredMetadataItemName = null;
  34. Type requiredMetadataItemType = null;
  35. if (TryParseExpressionAsMetadataConstraintBody(expression, constraint.Parameters[0], out requiredMetadataItemName, out requiredMetadataItemType))
  36. {
  37. requiredMetadataList.Add(new KeyValuePair<string, Type>(requiredMetadataItemName, requiredMetadataItemType));
  38. }
  39. // Just skip the expressions we don't understand
  40. }
  41. // ContractName should have been set already, just need to set metadata
  42. requiredMetadata = requiredMetadataList;
  43. return true;
  44. }
  45. private static IEnumerable<Expression> SplitConstraintBody(Expression expression)
  46. {
  47. Assert.IsNotNull(expression);
  48. // The expression we know about should be a set of nested AndAlso's, we
  49. // need to flatten them into one list. we do this iteratively, as
  50. // recursion will create too much of a memory churn.
  51. Stack<Expression> expressions = new Stack<Expression>();
  52. expressions.Push(expression);
  53. while (expressions.Count > 0)
  54. {
  55. Expression current = expressions.Pop();
  56. if (current.NodeType == ExpressionType.AndAlso)
  57. {
  58. BinaryExpression andAlso = (BinaryExpression)current;
  59. // Push right first - this preserves the ordering of the expression, which will force
  60. // the contract constraint to come up first as the callers are optimized for this form
  61. expressions.Push(andAlso.Right);
  62. expressions.Push(andAlso.Left);
  63. continue;
  64. }
  65. yield return current;
  66. }
  67. }
  68. private static bool TryParseExpressionAsContractConstraintBody(Expression expression, Expression parameter, out string contractName)
  69. {
  70. contractName = null;
  71. // The expression should be an '==' expression
  72. if (expression.NodeType != ExpressionType.Equal)
  73. {
  74. return false;
  75. }
  76. BinaryExpression contractConstraintExpression = (BinaryExpression)expression;
  77. // First try item.ContractName == "Value"
  78. if (TryParseContractNameFromEqualsExpression(contractConstraintExpression.Left, contractConstraintExpression.Right, parameter, out contractName))
  79. {
  80. return true;
  81. }
  82. // Then try "Value == item.ContractName
  83. if (TryParseContractNameFromEqualsExpression(contractConstraintExpression.Right, contractConstraintExpression.Left, parameter, out contractName))
  84. {
  85. return true;
  86. }
  87. return false;
  88. }
  89. private static bool TryParseContractNameFromEqualsExpression(Expression left, Expression right, Expression parameter, out string contractName)
  90. {
  91. contractName = null;
  92. // The left should be access to property "Contract" applied to the parameter
  93. MemberExpression targetMember = left as MemberExpression;
  94. if (targetMember == null)
  95. {
  96. return false;
  97. }
  98. if ((targetMember.Member != _exportDefinitionContractNameProperty) || (targetMember.Expression != parameter))
  99. {
  100. return false;
  101. }
  102. // Right should a constant expression containing the contract name
  103. ConstantExpression contractNameConstant = right as ConstantExpression;
  104. if (contractNameConstant == null)
  105. {
  106. return false;
  107. }
  108. if (!TryParseConstant<string>(contractNameConstant, out contractName))
  109. {
  110. return false;
  111. }
  112. return true;
  113. }
  114. private static bool TryParseExpressionAsMetadataConstraintBody(Expression expression, Expression parameter, out string requiredMetadataKey, out Type requiredMetadataType)
  115. {
  116. Assumes.NotNull(expression, parameter);
  117. requiredMetadataKey = null;
  118. requiredMetadataType = null;
  119. // Should be a call to Type.IsInstanceofType on definition.Metadata[key]
  120. MethodCallExpression outerMethodCall = expression as MethodCallExpression;
  121. if (outerMethodCall == null)
  122. {
  123. return false;
  124. }
  125. // Make sure that the right method ie being called
  126. if (outerMethodCall.Method != _typeIsInstanceOfTypeMethod)
  127. {
  128. return false;
  129. }
  130. Assumes.IsTrue(outerMethodCall.Arguments.Count == 1);
  131. // 'this' should be a constant expression pointing at a Type object
  132. ConstantExpression targetType = outerMethodCall.Object as ConstantExpression;
  133. if(!TryParseConstant<Type>(targetType, out requiredMetadataType))
  134. {
  135. return false;
  136. }
  137. // SHould be a call to get_Item
  138. MethodCallExpression methodCall = outerMethodCall.Arguments[0] as MethodCallExpression;
  139. if (methodCall == null)
  140. {
  141. return false;
  142. }
  143. if (methodCall.Method != _metadataItemMethod)
  144. {
  145. return false;
  146. }
  147. // Make sure the method is being called on the right object
  148. MemberExpression targetMember = methodCall.Object as MemberExpression;
  149. if (targetMember == null)
  150. {
  151. return false;
  152. }
  153. if ((targetMember.Expression != parameter) || (targetMember.Member != _exportDefinitionMetadataProperty))
  154. {
  155. return false;
  156. }
  157. // There should only ever be one argument; otherwise,
  158. // we've got the wrong IDictionary.get_Item method.
  159. Assumes.IsTrue(methodCall.Arguments.Count == 1);
  160. // Argument should a constant expression containing the metadata key
  161. ConstantExpression requiredMetadataKeyConstant = methodCall.Arguments[0] as ConstantExpression;
  162. if (requiredMetadataKeyConstant == null)
  163. {
  164. return false;
  165. }
  166. if (!TryParseConstant<string>(requiredMetadataKeyConstant, out requiredMetadataKey))
  167. {
  168. return false;
  169. }
  170. return true;
  171. }
  172. private static bool TryParseConstant<T>(ConstantExpression constant, out T result)
  173. where T : class
  174. {
  175. Assumes.NotNull(constant);
  176. if (constant.Type == typeof(T) && constant.Value != null)
  177. {
  178. result = (T)constant.Value;
  179. return true;
  180. }
  181. result = default(T);
  182. return false;
  183. }
  184. }
  185. }