| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225 |
- // -----------------------------------------------------------------------
- // Copyright (c) Microsoft Corporation. All rights reserved.
- // -----------------------------------------------------------------------
- using System;
- using System.Collections.Generic;
- using System.Linq.Expressions;
- using System.Reflection;
- using Microsoft.Internal;
- using Microsoft.VisualStudio.TestTools.UnitTesting;
- using System.ComponentModel.Composition.Primitives;
- namespace System.ComponentModel.Composition
- {
- public class ContraintParser
- {
- private static readonly PropertyInfo _exportDefinitionContractNameProperty = typeof(ExportDefinition).GetProperty("ContractName");
- private static readonly PropertyInfo _exportDefinitionMetadataProperty = typeof(ExportDefinition).GetProperty("Metadata");
- private static readonly MethodInfo _metadataContainsKeyMethod = typeof(IDictionary<string, object>).GetMethod("ContainsKey");
- private static readonly MethodInfo _metadataItemMethod = typeof(IDictionary<string, object>).GetMethod("get_Item");
- private static readonly MethodInfo _typeIsInstanceOfTypeMethod = typeof(Type).GetMethod("IsInstanceOfType");
- public static bool TryParseConstraint(Expression<Func<ExportDefinition, bool>> constraint, out string contractName, out IEnumerable<KeyValuePair<string, Type>> requiredMetadata)
- {
- contractName = null;
- requiredMetadata = null;
- List<KeyValuePair<string, Type>> requiredMetadataList = new List<KeyValuePair<string, Type>>();
- foreach (Expression expression in SplitConstraintBody(constraint.Body))
- {
- // First try to parse as a contract, if we don't have one already
- if (contractName == null && TryParseExpressionAsContractConstraintBody(expression, constraint.Parameters[0], out contractName))
- {
- continue;
- }
- // Then try to parse as a required metadata item name
- string requiredMetadataItemName = null;
- Type requiredMetadataItemType = null;
- if (TryParseExpressionAsMetadataConstraintBody(expression, constraint.Parameters[0], out requiredMetadataItemName, out requiredMetadataItemType))
- {
- requiredMetadataList.Add(new KeyValuePair<string, Type>(requiredMetadataItemName, requiredMetadataItemType));
- }
- // Just skip the expressions we don't understand
- }
- // ContractName should have been set already, just need to set metadata
- requiredMetadata = requiredMetadataList;
- return true;
- }
- private static IEnumerable<Expression> SplitConstraintBody(Expression expression)
- {
- Assert.IsNotNull(expression);
- // The expression we know about should be a set of nested AndAlso's, we
- // need to flatten them into one list. we do this iteratively, as
- // recursion will create too much of a memory churn.
- Stack<Expression> expressions = new Stack<Expression>();
- expressions.Push(expression);
- while (expressions.Count > 0)
- {
- Expression current = expressions.Pop();
- if (current.NodeType == ExpressionType.AndAlso)
- {
- BinaryExpression andAlso = (BinaryExpression)current;
- // Push right first - this preserves the ordering of the expression, which will force
- // the contract constraint to come up first as the callers are optimized for this form
- expressions.Push(andAlso.Right);
- expressions.Push(andAlso.Left);
- continue;
- }
- yield return current;
- }
- }
- private static bool TryParseExpressionAsContractConstraintBody(Expression expression, Expression parameter, out string contractName)
- {
- contractName = null;
- // The expression should be an '==' expression
- if (expression.NodeType != ExpressionType.Equal)
- {
- return false;
- }
- BinaryExpression contractConstraintExpression = (BinaryExpression)expression;
- // First try item.ContractName == "Value"
- if (TryParseContractNameFromEqualsExpression(contractConstraintExpression.Left, contractConstraintExpression.Right, parameter, out contractName))
- {
- return true;
- }
- // Then try "Value == item.ContractName
- if (TryParseContractNameFromEqualsExpression(contractConstraintExpression.Right, contractConstraintExpression.Left, parameter, out contractName))
- {
- return true;
- }
- return false;
- }
- private static bool TryParseContractNameFromEqualsExpression(Expression left, Expression right, Expression parameter, out string contractName)
- {
- contractName = null;
- // The left should be access to property "Contract" applied to the parameter
- MemberExpression targetMember = left as MemberExpression;
- if (targetMember == null)
- {
- return false;
- }
- if ((targetMember.Member != _exportDefinitionContractNameProperty) || (targetMember.Expression != parameter))
- {
- return false;
- }
- // Right should a constant expression containing the contract name
- ConstantExpression contractNameConstant = right as ConstantExpression;
- if (contractNameConstant == null)
- {
- return false;
- }
- if (!TryParseConstant<string>(contractNameConstant, out contractName))
- {
- return false;
- }
- return true;
- }
- private static bool TryParseExpressionAsMetadataConstraintBody(Expression expression, Expression parameter, out string requiredMetadataKey, out Type requiredMetadataType)
- {
- Assumes.NotNull(expression, parameter);
- requiredMetadataKey = null;
- requiredMetadataType = null;
- // Should be a call to Type.IsInstanceofType on definition.Metadata[key]
- MethodCallExpression outerMethodCall = expression as MethodCallExpression;
- if (outerMethodCall == null)
- {
- return false;
- }
- // Make sure that the right method ie being called
- if (outerMethodCall.Method != _typeIsInstanceOfTypeMethod)
- {
- return false;
- }
- Assumes.IsTrue(outerMethodCall.Arguments.Count == 1);
- // 'this' should be a constant expression pointing at a Type object
- ConstantExpression targetType = outerMethodCall.Object as ConstantExpression;
- if(!TryParseConstant<Type>(targetType, out requiredMetadataType))
- {
- return false;
- }
- // SHould be a call to get_Item
- MethodCallExpression methodCall = outerMethodCall.Arguments[0] as MethodCallExpression;
- if (methodCall == null)
- {
- return false;
- }
- if (methodCall.Method != _metadataItemMethod)
- {
- return false;
- }
- // Make sure the method is being called on the right object
- MemberExpression targetMember = methodCall.Object as MemberExpression;
- if (targetMember == null)
- {
- return false;
- }
- if ((targetMember.Expression != parameter) || (targetMember.Member != _exportDefinitionMetadataProperty))
- {
- return false;
- }
- // There should only ever be one argument; otherwise,
- // we've got the wrong IDictionary.get_Item method.
- Assumes.IsTrue(methodCall.Arguments.Count == 1);
- // Argument should a constant expression containing the metadata key
- ConstantExpression requiredMetadataKeyConstant = methodCall.Arguments[0] as ConstantExpression;
- if (requiredMetadataKeyConstant == null)
- {
- return false;
- }
- if (!TryParseConstant<string>(requiredMetadataKeyConstant, out requiredMetadataKey))
- {
- return false;
- }
- return true;
- }
- private static bool TryParseConstant<T>(ConstantExpression constant, out T result)
- where T : class
- {
- Assumes.NotNull(constant);
- if (constant.Type == typeof(T) && constant.Value != null)
- {
- result = (T)constant.Value;
- return true;
- }
- result = default(T);
- return false;
- }
- }
- }
|