1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4 
5 using System.Collections.Generic;
6 using System.Composition.Convention;
7 using System.Composition.Hosting;
8 using System.Composition.Hosting.Core;
9 using System.Composition.TypedParts.ActivationFeatures;
10 using System.Linq;
11 using System.Reflection;
12 
13 namespace System.Composition.TypedParts.Discovery
14 {
15     internal class TypeInspector
16     {
17         private static readonly IDictionary<string, object> s_noMetadata = new Dictionary<string, object>();
18 
19         private readonly ActivationFeature[] _activationFeatures;
20         private readonly AttributedModelProvider _attributeContext;
21 
TypeInspector(AttributedModelProvider attributeContext, ActivationFeature[] activationFeatures)22         public TypeInspector(AttributedModelProvider attributeContext, ActivationFeature[] activationFeatures)
23         {
24             _attributeContext = attributeContext;
25             _activationFeatures = activationFeatures;
26         }
27 
InspectTypeForPart(TypeInfo type, out DiscoveredPart part)28         public bool InspectTypeForPart(TypeInfo type, out DiscoveredPart part)
29         {
30             part = null;
31 
32             if (type.IsAbstract || !type.IsClass || _attributeContext.GetDeclaredAttribute<PartNotDiscoverableAttribute>(type.AsType(), type) != null)
33                 return false;
34 
35             foreach (var export in DiscoverExports(type))
36             {
37                 part = part ?? new DiscoveredPart(type, _attributeContext, _activationFeatures);
38                 part.AddDiscoveredExport(export);
39             }
40 
41             return part != null;
42         }
43 
DiscoverExports(TypeInfo partType)44         private IEnumerable<DiscoveredExport> DiscoverExports(TypeInfo partType)
45         {
46             foreach (var export in DiscoverInstanceExports(partType))
47                 yield return export;
48 
49             foreach (var export in DiscoverPropertyExports(partType))
50                 yield return export;
51         }
52 
DiscoverInstanceExports(TypeInfo partType)53         private IEnumerable<DiscoveredExport> DiscoverInstanceExports(TypeInfo partType)
54         {
55             var partTypeAsType = partType.AsType();
56             foreach (var export in _attributeContext.GetDeclaredAttributes<ExportAttribute>(partTypeAsType, partType))
57             {
58                 IDictionary<string, object> metadata = new Dictionary<string, object>();
59                 ReadMetadataAttribute(export, metadata);
60 
61                 var applied = _attributeContext.GetDeclaredAttributes(partTypeAsType, partType);
62                 ReadLooseMetadata(applied, metadata);
63 
64                 var contractType = export.ContractType ?? partTypeAsType;
65                 CheckInstanceExportCompatibility(partType, contractType.GetTypeInfo());
66 
67                 var exportKey = new CompositionContract(contractType, export.ContractName);
68 
69                 if (metadata.Count == 0)
70                     metadata = s_noMetadata;
71 
72                 yield return new DiscoveredInstanceExport(exportKey, metadata);
73             }
74         }
75 
DiscoverPropertyExports(TypeInfo partType)76         private IEnumerable<DiscoveredExport> DiscoverPropertyExports(TypeInfo partType)
77         {
78             var partTypeAsType = partType.AsType();
79             foreach (var property in partTypeAsType.GetRuntimeProperties()
80                 .Where(pi => pi.CanRead && pi.GetMethod.IsPublic && !pi.GetMethod.IsStatic))
81             {
82                 foreach (var export in _attributeContext.GetDeclaredAttributes<ExportAttribute>(partTypeAsType, property))
83                 {
84                     IDictionary<string, object> metadata = new Dictionary<string, object>();
85                     ReadMetadataAttribute(export, metadata);
86 
87                     var applied = _attributeContext.GetDeclaredAttributes(partTypeAsType, property);
88                     ReadLooseMetadata(applied, metadata);
89 
90                     var contractType = export.ContractType ?? property.PropertyType;
91                     CheckPropertyExportCompatibility(partType, property, contractType.GetTypeInfo());
92 
93                     var exportKey = new CompositionContract(export.ContractType ?? property.PropertyType, export.ContractName);
94 
95                     if (metadata.Count == 0)
96                         metadata = s_noMetadata;
97 
98                     yield return new DiscoveredPropertyExport(exportKey, metadata, property);
99                 }
100             }
101         }
102 
ReadLooseMetadata(object[] appliedAttributes, IDictionary<string, object> metadata)103         private void ReadLooseMetadata(object[] appliedAttributes, IDictionary<string, object> metadata)
104         {
105             foreach (var attribute in appliedAttributes)
106             {
107                 if (attribute is ExportAttribute)
108                     continue;
109 
110                 var ema = attribute as ExportMetadataAttribute;
111                 if (ema != null)
112                 {
113                     var valueType = ema.Value?.GetType() ?? typeof(object);
114                     AddMetadata(metadata, ema.Name, valueType, ema.Value);
115                 }
116                 else
117                 {
118                     ReadMetadataAttribute((Attribute)attribute, metadata);
119                 }
120             }
121         }
122 
AddMetadata(IDictionary<string, object> metadata, string name, Type valueType, object value)123         private void AddMetadata(IDictionary<string, object> metadata, string name, Type valueType, object value)
124         {
125             object existingValue;
126             if (!metadata.TryGetValue(name, out existingValue))
127             {
128                 metadata.Add(name, value);
129                 return;
130             }
131 
132             var existingArray = existingValue as Array;
133             if (existingArray != null)
134             {
135                 var newArray = Array.CreateInstance(valueType, existingArray.Length + 1);
136                 Array.Copy(existingArray, newArray, existingArray.Length);
137                 newArray.SetValue(value, existingArray.Length);
138                 metadata[name] = newArray;
139             }
140             else
141             {
142                 var newArray = Array.CreateInstance(valueType, 2);
143                 newArray.SetValue(existingValue, 0);
144                 newArray.SetValue(value, 1);
145                 metadata[name] = newArray;
146             }
147         }
148 
ReadMetadataAttribute(Attribute attribute, IDictionary<string, object> metadata)149         private void ReadMetadataAttribute(Attribute attribute, IDictionary<string, object> metadata)
150         {
151             var attrType = attribute.GetType();
152 
153             // Note, we don't support ReflectionContext in this scenario as
154             if (attrType.GetTypeInfo().GetCustomAttribute<MetadataAttributeAttribute>(true) == null)
155                 return;
156 
157             foreach (var prop in attrType
158                 .GetRuntimeProperties()
159                 .Where(p => p.DeclaringType == attrType && p.CanRead))
160             {
161                 AddMetadata(metadata, prop.Name, prop.PropertyType, prop.GetValue(attribute, null));
162             }
163         }
164 
CheckPropertyExportCompatibility(TypeInfo partType, PropertyInfo property, TypeInfo contractType)165         private static void CheckPropertyExportCompatibility(TypeInfo partType, PropertyInfo property, TypeInfo contractType)
166         {
167             if (partType.IsGenericTypeDefinition)
168             {
169                 CheckGenericContractCompatibility(partType, property.PropertyType.GetTypeInfo(), contractType);
170             }
171             else if (!contractType.IsAssignableFrom(property.PropertyType.GetTypeInfo()))
172             {
173                 string message = SR.Format(SR.TypeInspector_ExportedContractTypeNotAssignable,
174                                                 contractType.Name, property.Name, partType.Name);
175                 throw new CompositionFailedException(message);
176             }
177         }
178 
CheckGenericContractCompatibility(TypeInfo partType, TypeInfo exportingMemberType, TypeInfo contractType)179         private static void CheckGenericContractCompatibility(TypeInfo partType, TypeInfo exportingMemberType, TypeInfo contractType)
180         {
181             if (!contractType.IsGenericTypeDefinition)
182             {
183                 string message = SR.Format(SR.TypeInspector_NoExportNonGenericContract, partType.Name, contractType.Name);
184                 throw new CompositionFailedException(message);
185             }
186 
187             var compatible = false;
188 
189             foreach (var ifce in GetAssignableTypes(exportingMemberType))
190             {
191                 if (ifce == contractType || (ifce.IsGenericType && ifce.GetGenericTypeDefinition() == contractType.AsType()))
192                 {
193                     var mappedType = ifce;
194                     if (!(mappedType == partType || mappedType.GenericTypeArguments.SequenceEqual(partType.GenericTypeParameters)))
195                     {
196                         string message = SR.Format(SR.TypeInspector_ArgumentMissmatch, contractType.Name, partType.Name);
197                         throw new CompositionFailedException(message);
198                     }
199 
200                     compatible = true;
201                     break;
202                 }
203             }
204 
205             if (!compatible)
206             {
207                 string message = SR.Format(SR.TypeInspector_ExportNotCompatible, exportingMemberType.Name, partType.Name, contractType.Name);
208                 throw new CompositionFailedException(message);
209             }
210         }
211 
GetAssignableTypes(TypeInfo exportingMemberType)212         private static IEnumerable<TypeInfo> GetAssignableTypes(TypeInfo exportingMemberType)
213         {
214             foreach (var ifce in exportingMemberType.ImplementedInterfaces)
215                 yield return ifce.GetTypeInfo();
216 
217             var b = exportingMemberType;
218             while (b != null)
219             {
220                 yield return b;
221                 b = b.BaseType?.GetTypeInfo();
222             }
223         }
224 
CheckInstanceExportCompatibility(TypeInfo partType, TypeInfo contractType)225         private static void CheckInstanceExportCompatibility(TypeInfo partType, TypeInfo contractType)
226         {
227             if (partType.IsGenericTypeDefinition)
228             {
229                 CheckGenericContractCompatibility(partType, partType, contractType);
230             }
231             else if (!contractType.IsAssignableFrom(partType))
232             {
233                 string message = string.Format(SR.TypeInspector_ContractNotAssignable, contractType.Name, partType.Name);
234                 throw new CompositionFailedException(message);
235             }
236         }
237     }
238 }
239