Welcome to mirror list, hosted at ThFree Co, Russian Federation.

ConstraintParser.cs « Composition « ComponentModel « System « UnitTestFramework « Tests « System.ComponentModel.Composition « class « mcs - github.com/mono/mono.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 2a0a0d3e665f51be5db3a9f9da2117751adb11db (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
// -----------------------------------------------------------------------
// 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;
        }
    }
}