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