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.Core;
8 using System.Composition.TypedParts.ActivationFeatures;
9 using System.Composition.TypedParts.Discovery;
10 using System.Linq;
11 using System.Reflection;
12 
13 namespace System.Composition.TypedParts
14 {
15     internal class TypedPartExportDescriptorProvider : ExportDescriptorProvider
16     {
17         private readonly IDictionary<CompositionContract, ICollection<DiscoveredExport>> _discoveredParts = new Dictionary<CompositionContract, ICollection<DiscoveredExport>>();
18 
TypedPartExportDescriptorProvider(IEnumerable<Type> types, AttributedModelProvider attributeContext)19         public TypedPartExportDescriptorProvider(IEnumerable<Type> types, AttributedModelProvider attributeContext)
20         {
21             var activationFeatures = CreateActivationFeatures(attributeContext);
22             var typeInspector = new TypeInspector(attributeContext, activationFeatures);
23 
24             foreach (var type in types)
25             {
26                 DiscoveredPart part;
27                 if (typeInspector.InspectTypeForPart(type.GetTypeInfo(), out part))
28                 {
29                     AddDiscoveredPart(part);
30                 }
31             }
32         }
33 
AddDiscoveredPart(DiscoveredPart part)34         private void AddDiscoveredPart(DiscoveredPart part)
35         {
36             foreach (var export in part.DiscoveredExports)
37             {
38                 AddDiscoveredExport(export);
39             }
40         }
41 
AddDiscoveredExport(DiscoveredExport export, CompositionContract contract = null)42         private void AddDiscoveredExport(DiscoveredExport export, CompositionContract contract = null)
43         {
44             var actualContract = contract ?? export.Contract;
45 
46             ICollection<DiscoveredExport> forKey;
47             if (!_discoveredParts.TryGetValue(actualContract, out forKey))
48             {
49                 forKey = new List<DiscoveredExport>();
50                 _discoveredParts.Add(actualContract, forKey);
51             }
52 
53             forKey.Add(export);
54         }
55 
GetExportDescriptors(CompositionContract contract, DependencyAccessor definitionAccessor)56         public override IEnumerable<ExportDescriptorPromise> GetExportDescriptors(CompositionContract contract, DependencyAccessor definitionAccessor)
57         {
58             DiscoverGenericParts(contract);
59             DiscoverConstrainedParts(contract);
60 
61             ICollection<DiscoveredExport> forKey;
62             if (!_discoveredParts.TryGetValue(contract, out forKey))
63                 return NoExportDescriptors;
64 
65             // Exports with metadata may be matched via metadata constraints.
66             // It should be possible to do this more aggressively by changing the way
67             // exports are stored.
68             if (!forKey.Any(x => x.Metadata.Any()))
69             {
70                 // Allow some garbage to be collected
71                 _discoveredParts.Remove(contract);
72             }
73 
74             return forKey.Select(de => de.GetExportDescriptorPromise(contract, definitionAccessor)).ToArray();
75         }
76 
77         // If the contract has metadata constraints, look for exports with matching metadata.
DiscoverConstrainedParts(CompositionContract contract)78         private void DiscoverConstrainedParts(CompositionContract contract)
79         {
80             if (contract.MetadataConstraints != null)
81             {
82                 var unconstrained = new CompositionContract(contract.ContractType, contract.ContractName);
83                 DiscoverGenericParts(unconstrained);
84 
85                 ICollection<DiscoveredExport> forKey;
86                 if (_discoveredParts.TryGetValue(unconstrained, out forKey))
87                 {
88                     foreach (var export in forKey)
89                     {
90                         var subsettedConstraints = contract.MetadataConstraints.Where(c => export.Metadata.ContainsKey(c.Key)).ToDictionary(c => c.Key, c => export.Metadata[c.Key]);
91                         if (subsettedConstraints.Count != 0)
92                         {
93                             var constrainedSubset = new CompositionContract(unconstrained.ContractType, unconstrained.ContractName, subsettedConstraints);
94 
95                             if (constrainedSubset.Equals(contract))
96                                 AddDiscoveredExport(export, contract);
97                         }
98                     }
99                 }
100             }
101         }
102 
103         // If the contract is a closed generic, look for open generics
104         // that close it.
DiscoverGenericParts(CompositionContract contract)105         private void DiscoverGenericParts(CompositionContract contract)
106         {
107             if (!contract.ContractType.IsConstructedGenericType)
108                 return;
109 
110             var gtd = contract.ContractType.GetGenericTypeDefinition();
111             var openGenericContract = contract.ChangeType(gtd);
112             ICollection<DiscoveredExport> openGenericParts;
113             if (!_discoveredParts.TryGetValue(openGenericContract, out openGenericParts))
114                 return;
115 
116             var typeArguments = contract.ContractType.GenericTypeArguments;
117             foreach (var open in openGenericParts)
118             {
119                 DiscoveredPart closed;
120                 if (open.Part.TryCloseGenericPart(typeArguments, out closed))
121                     AddDiscoveredPart(closed);
122             }
123         }
124 
CreateActivationFeatures(AttributedModelProvider attributeContext)125         private static ActivationFeature[] CreateActivationFeatures(AttributedModelProvider attributeContext)
126         {
127             return new ActivationFeature[] {
128                 new DisposalFeature(),
129                 new PropertyInjectionFeature(attributeContext),
130                 new OnImportsSatisfiedFeature(attributeContext),
131                 new LifetimeFeature(),
132             };
133         }
134 
DebugGetActivationFeatures(AttributedModelProvider attributeContext)135         internal static ActivationFeature[] DebugGetActivationFeatures(AttributedModelProvider attributeContext)
136         {
137             return CreateActivationFeatures(attributeContext);
138         }
139     }
140 }
141