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;
6 using System.Collections.Generic;
7 using System.Composition.Hosting;
8 using System.Composition.Hosting.Core;
9 using System.Linq;
10 using System.Reflection;
11 using Microsoft.Composition.Demos.ExtendedCollectionImports.Util;
12 
13 namespace Microsoft.Composition.Demos.ExtendedCollectionImports.Dictionaries
14 {
15     public class DictionaryExportDescriptorProvider : ExportDescriptorProvider
16     {
17         /// <summary>
18         /// Identifies the metadata used as key for a dictionary import.
19         /// </summary>
20         private const string KeyByMetadataImportMetadataConstraintName = "KeyMetadataName";
21 
22         private static readonly MethodInfo s_getDictionaryDefinitionsMethod = typeof(DictionaryExportDescriptorProvider).GetTypeInfo().GetDeclaredMethod("GetDictionaryDefinition");
23 
GetExportDescriptors(CompositionContract contract, DependencyAccessor descriptorAccessor)24         public override IEnumerable<ExportDescriptorPromise> GetExportDescriptors(CompositionContract contract, DependencyAccessor descriptorAccessor)
25         {
26             if (!(contract.ContractType.IsConstructedGenericType && contract.ContractType.GetGenericTypeDefinition() == typeof(IDictionary<,>)))
27                 return NoExportDescriptors;
28 
29             CompositionContract unwrapped;
30             string keyByMetadataName;
31             if (!contract.TryUnwrapMetadataConstraint(KeyByMetadataImportMetadataConstraintName, out keyByMetadataName, out unwrapped))
32                 return NoExportDescriptors;
33 
34             var args = contract.ContractType.GenericTypeArguments;
35             var keyType = args[0];
36             var valueType = args[1];
37 
38             var valueContract = unwrapped.ChangeType(valueType);
39 
40             var gdd = s_getDictionaryDefinitionsMethod.MakeGenericMethod(keyType, valueType);
41 
42             return new[] { (ExportDescriptorPromise)gdd.Invoke(null, new object[] { contract, valueContract, descriptorAccessor, keyByMetadataName }) };
43         }
44 
GetDictionaryDefinition(CompositionContract dictionaryContract, CompositionContract valueContract, DependencyAccessor definitionAccessor, string keyByMetadataName)45         private static ExportDescriptorPromise GetDictionaryDefinition<TKey, TValue>(CompositionContract dictionaryContract, CompositionContract valueContract, DependencyAccessor definitionAccessor, string keyByMetadataName)
46         {
47             return new ExportDescriptorPromise(
48                 dictionaryContract,
49                 typeof(IDictionary<TKey, TValue>).Name,
50                 false,
51                 () => definitionAccessor.ResolveDependencies("value", valueContract, true),
52                 deps =>
53                 {
54                     var items = deps.Select(d => Tuple.Create(d.Target.Origin, d.Target.GetDescriptor())).ToArray();
55                     var isValidated = false;
56                     return ExportDescriptor.Create((c, o) =>
57                     {
58                         if (!isValidated)
59                         {
60                             Validate<TKey>(items, keyByMetadataName);
61                             isValidated = true;
62                         }
63 
64                         return items.ToDictionary(
65                             item => (TKey)item.Item2.Metadata[keyByMetadataName],
66                             item => (TValue)item.Item2.Activator(c, o));
67                     },
68                     NoMetadata);
69                 });
70         }
71 
Validate(Tuple<string, ExportDescriptor>[] partsWithMatchedDescriptors, string keyByMetadataName)72         private static void Validate<TKey>(Tuple<string, ExportDescriptor>[] partsWithMatchedDescriptors, string keyByMetadataName)
73         {
74             var missing = partsWithMatchedDescriptors.Where(p => !p.Item2.Metadata.ContainsKey(keyByMetadataName)).ToArray();
75             if (missing.Length != 0)
76             {
77                 var problems = Formatters.ReadableQuotedList(missing.Select(p => p.Item1));
78                 var message = string.Format("The metadata '{0}' cannot be used as a dictionary import key because it is missing from exports on part(s) {1}.", keyByMetadataName, problems);
79                 throw new CompositionFailedException(message);
80             }
81 
82             var wrongType = partsWithMatchedDescriptors.Where(p => !(p.Item2.Metadata[keyByMetadataName] is TKey)).ToArray();
83             if (wrongType.Length != 0)
84             {
85                 var problems = Formatters.ReadableQuotedList(wrongType.Select(p => p.Item1));
86                 var message = string.Format("The metadata '{0}' cannot be used as a dictionary import key of type '{1}' because the value(s) supplied by {2} are of the wrong type.", keyByMetadataName, typeof(TKey).Name, problems);
87                 throw new CompositionFailedException(message);
88             }
89 
90             var firstDuplicated = partsWithMatchedDescriptors.GroupBy(p => (TKey)p.Item2.Metadata[keyByMetadataName]).Where(g => g.Count() > 1).FirstOrDefault();
91             if (firstDuplicated != null)
92             {
93                 var problems = Formatters.ReadableQuotedList(firstDuplicated.Select(p => p.Item1));
94                 var message = string.Format("The metadata '{0}' cannot be used as a dictionary import key because the value '{1}' is associated with exports from parts {2}.", keyByMetadataName, firstDuplicated.Key, problems);
95                 throw new CompositionFailedException(message);
96             }
97         }
98     }
99 }
100