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.ComponentModel.Composition.Primitives;
7 using System.Diagnostics;
8 using System.Diagnostics.CodeAnalysis;
9 using System.Diagnostics.Contracts;
10 using System.Globalization;
11 using System.Reflection;
12 using System.Threading;
13 using Microsoft.Internal;
14 
15 namespace System.ComponentModel.Composition.Hosting
16 {
17     /// <summary>
18     ///     An immutable ComposablePartCatalog created from a managed code assembly.
19     /// </summary>
20     /// <remarks>
21     ///     This type is thread safe.
22     /// </remarks>
23     [DebuggerTypeProxy(typeof(AssemblyCatalogDebuggerProxy))]
24     public class AssemblyCatalog : ComposablePartCatalog, ICompositionElement
25     {
26         private readonly object _thisLock = new object();
27         private readonly ICompositionElement _definitionOrigin;
28         private volatile Assembly _assembly = null;
29         private volatile ComposablePartCatalog _innerCatalog = null;
30         private int _isDisposed = 0;
31 
32         private ReflectionContext _reflectionContext = default(ReflectionContext);
33 
34         /// <summary>
35         ///     Initializes a new instance of the <see cref="AssemblyCatalog"/> class
36         ///     with the specified code base.
37         /// </summary>
38         /// <param name="codeBase">
39         ///     A <see cref="String"/> containing the code base of the assembly containing the
40         ///     attributed <see cref="Type"/> objects to add to the <see cref="AssemblyCatalog"/>.
41         /// </param>
42         /// <exception cref="ArgumentNullException">
43         ///     <paramref name="codeBase"/> is <see langword="null"/>.
44         /// </exception>
45         /// <exception cref="ArgumentException">
46         ///     <paramref name="codeBase"/> is a zero-length string, contains only white space,
47         ///     or contains one or more invalid characters. />.
48         /// </exception>
49         /// <exception cref="PathTooLongException">
50         ///     The specified path, file name, or both exceed the system-defined maximum length.
51         /// </exception>
52         /// <exception cref="SecurityException">
53         ///     The caller does not have path discovery permission.
54         /// </exception>
55         /// <exception cref="FileNotFoundException">
56         ///     <paramref name="codeBase"/> is not found.
57         /// </exception>
58         /// <exception cref="FileLoadException ">
59         ///     <paramref name="codeBase"/> could not be loaded.
60         ///     <para>
61         ///         -or-
62         ///     </para>
63         ///     <paramref name="codeBase"/> specified a directory.
64         /// </exception>
65         /// <exception cref="BadImageFormatException">
66         ///     <paramref name="codeBase"/> is not a valid assembly
67         ///     -or-
68         ///     Version 2.0 or later of the common language runtime is currently loaded
69         ///     and <paramref name="codeBase"/> was compiled with a later version.
70         /// </exception>
71         /// <remarks>
72         ///     The assembly referenced by <paramref langword="codeBase"/> is loaded into the Load context.
73         /// </remarks>
AssemblyCatalog(string codeBase)74         public AssemblyCatalog(string codeBase)
75         {
76             Requires.NotNullOrEmpty(codeBase, "codeBase");
77 
78             InitializeAssemblyCatalog(LoadAssembly(codeBase));
79             _definitionOrigin = this;
80         }
81 
82         /// <summary>
83         ///     Initializes a new instance of the <see cref="AssemblyCatalog"/> class
84         ///     with the specified code base.
85         /// </summary>
86         /// <param name="codeBase">
87         ///     A <see cref="String"/> containing the code base of the assembly containing the
88         ///     attributed <see cref="Type"/> objects to add to the <see cref="AssemblyCatalog"/>.
89         /// </param>
90         /// <param name="reflectionContext">
91         ///     The <see cref="ReflectionContext"/> a context used by the catalog when
92         ///     interpreting the types to inject attributes into the type definition<see cref="AssemblyCatalog"/>.
93         /// </param>
94         /// <exception cref="ArgumentNullException">
95         ///     <paramref name="codeBase"/> is <see langword="null"/>.
96         ///     <para>
97         ///         -or-
98         ///     </para>
99         ///     <paramref name="reflectionContext"/> is <see langword="null"/>.
100         /// </exception>
101         /// <exception cref="ArgumentException">
102         ///     <paramref name="codeBase"/> is a zero-length string, contains only white space,
103         ///     or contains one or more invalid characters. />.
104         /// </exception>
105         /// <exception cref="PathTooLongException">
106         ///     The specified path, file name, or both exceed the system-defined maximum length.
107         /// </exception>
108         /// <exception cref="SecurityException">
109         ///     The caller does not have path discovery permission.
110         /// </exception>
111         /// <exception cref="FileNotFoundException">
112         ///     <paramref name="codeBase"/> is not found.
113         /// </exception>
114         /// <exception cref="FileLoadException ">
115         ///     <paramref name="codeBase"/> could not be loaded.
116         ///     <para>
117         ///         -or-
118         ///     </para>
119         ///     <paramref name="codeBase"/> specified a directory.
120         /// </exception>
121         /// <exception cref="BadImageFormatException">
122         ///     <paramref name="codeBase"/> is not a valid assembly
123         ///     -or-
124         ///     Version 2.0 or later of the common language runtime is currently loaded
125         ///     and <paramref name="codeBase"/> was compiled with a later version.
126         /// </exception>
127         /// <remarks>
128         ///     The assembly referenced by <paramref langword="codeBase"/> is loaded into the Load context.
129         /// </remarks>
AssemblyCatalog(string codeBase, ReflectionContext reflectionContext)130         public AssemblyCatalog(string codeBase, ReflectionContext reflectionContext)
131         {
132             Requires.NotNullOrEmpty(codeBase, "codeBase");
133             Requires.NotNull(reflectionContext, "reflectionContext");
134 
135             InitializeAssemblyCatalog(LoadAssembly(codeBase));
136             _reflectionContext = reflectionContext;
137             _definitionOrigin = this;
138         }
139 
140         /// <summary>
141         ///     Initializes a new instance of the <see cref="AssemblyCatalog"/> class
142         ///     with the specified code base.
143         /// </summary>
144         /// <param name="codeBase">
145         ///     A <see cref="String"/> containing the code base of the assembly containing the
146         ///     attributed <see cref="Type"/> objects to add to the <see cref="AssemblyCatalog"/>.
147         /// </param>
148         /// <param name="definitionOrigin">
149         ///     The <see cref="ICompositionElement"/> CompositionElement used by Diagnostics to identify the source for parts.
150         /// </param>
151         /// <exception cref="ArgumentNullException">
152         ///     <paramref name="codeBase"/> is <see langword="null"/>.
153         ///     <para>
154         ///         -or-
155         ///     </para>
156         ///     <paramref name="definitionOrigin"/> is <see langword="null"/>.
157         /// </exception>
158         /// <exception cref="ArgumentException">
159         ///     <paramref name="codeBase"/> is a zero-length string, contains only white space,
160         ///     or contains one or more invalid characters. />.
161         /// </exception>
162         /// <exception cref="PathTooLongException">
163         ///     The specified path, file name, or both exceed the system-defined maximum length.
164         /// </exception>
165         /// <exception cref="SecurityException">
166         ///     The caller does not have path discovery permission.
167         /// </exception>
168         /// <exception cref="FileNotFoundException">
169         ///     <paramref name="codeBase"/> is not found.
170         /// </exception>
171         /// <exception cref="FileLoadException ">
172         ///     <paramref name="codeBase"/> could not be loaded.
173         ///     <para>
174         ///         -or-
175         ///     </para>
176         ///     <paramref name="codeBase"/> specified a directory.
177         /// </exception>
178         /// <exception cref="BadImageFormatException">
179         ///     <paramref name="codeBase"/> is not a valid assembly
180         ///     -or-
181         ///     Version 2.0 or later of the common language runtime is currently loaded
182         ///     and <paramref name="codeBase"/> was compiled with a later version.
183         /// </exception>
184         /// <remarks>
185         ///     The assembly referenced by <paramref langword="codeBase"/> is loaded into the Load context.
186         /// </remarks>
AssemblyCatalog(string codeBase, ICompositionElement definitionOrigin)187         public AssemblyCatalog(string codeBase, ICompositionElement definitionOrigin)
188         {
189             Requires.NotNullOrEmpty(codeBase, "codeBase");
190             Requires.NotNull(definitionOrigin, nameof(definitionOrigin));
191 
192             InitializeAssemblyCatalog(LoadAssembly(codeBase));
193             _definitionOrigin = definitionOrigin;
194         }
195 
196         /// <summary>
197         ///     Initializes a new instance of the <see cref="AssemblyCatalog"/> class
198         ///     with the specified code base.
199         /// </summary>
200         /// <param name="codeBase">
201         ///     A <see cref="String"/> containing the code base of the assembly containing the
202         ///     attributed <see cref="Type"/> objects to add to the <see cref="AssemblyCatalog"/>.
203         /// </param>
204         /// <param name="reflectionContext">
205         ///     The <see cref="ReflectionContext"/> a context used by the catalog when
206         ///     interpreting the types to inject attributes into the type definition<see cref="AssemblyCatalog"/>.
207         /// </param>
208         /// <param name="definitionOrigin">
209         ///     The <see cref="ICompositionElement"/> CompositionElement used by Diagnostics to identify the source for parts.
210         /// </param>
211         /// <exception cref="ArgumentNullException">
212         ///     <paramref name="codeBase"/> is <see langword="null"/>.
213         ///     <para>
214         ///         -or-
215         ///     </para>
216         ///     <paramref name="reflectionContext"/> is <see langword="null"/>.
217         ///     <para>
218         ///         -or-
219         ///     </para>
220         ///     <paramref name="definitionOrigin"/> is <see langword="null"/>.
221         /// </exception>
222         /// <exception cref="ArgumentException">
223         ///     <paramref name="codeBase"/> is a zero-length string, contains only white space,
224         ///     or contains one or more invalid characters. />.
225         /// </exception>
226         /// <exception cref="PathTooLongException">
227         ///     The specified path, file name, or both exceed the system-defined maximum length.
228         /// </exception>
229         /// <exception cref="SecurityException">
230         ///     The caller does not have path discovery permission.
231         /// </exception>
232         /// <exception cref="FileNotFoundException">
233         ///     <paramref name="codeBase"/> is not found.
234         /// </exception>
235         /// <exception cref="FileLoadException ">
236         ///     <paramref name="codeBase"/> could not be loaded.
237         ///     <para>
238         ///         -or-
239         ///     </para>
240         ///     <paramref name="codeBase"/> specified a directory.
241         /// </exception>
242         /// <exception cref="BadImageFormatException">
243         ///     <paramref name="codeBase"/> is not a valid assembly
244         ///     -or-
245         ///     Version 2.0 or later of the common language runtime is currently loaded
246         ///     and <paramref name="codeBase"/> was compiled with a later version.
247         /// </exception>
248         /// <remarks>
249         ///     The assembly referenced by <paramref langword="codeBase"/> is loaded into the Load context.
250         /// </remarks>
AssemblyCatalog(string codeBase, ReflectionContext reflectionContext, ICompositionElement definitionOrigin)251         public AssemblyCatalog(string codeBase, ReflectionContext reflectionContext, ICompositionElement definitionOrigin)
252         {
253             Requires.NotNullOrEmpty(codeBase, "codeBase");
254             Requires.NotNull(reflectionContext, "reflectionContext");
255             Requires.NotNull(definitionOrigin, "definitionOrigin");
256 
257             InitializeAssemblyCatalog(LoadAssembly(codeBase));
258             _reflectionContext = reflectionContext;
259             _definitionOrigin = definitionOrigin;
260         }
261 
262         /// <summary>
263         ///     Initializes a new instance of the <see cref="AssemblyCatalog"/> class
264         ///     with the specified assembly and reflection context.
265         /// </summary>
266         /// <param name="assembly">
267         ///     The <see cref="Assembly"/> containing the attributed <see cref="Type"/> objects to
268         ///     add to the <see cref="AssemblyCatalog"/>.
269         /// </param>
270         /// <param name="reflectionContext">
271         ///     The <see cref="ReflectionContext"/> a context used by the catalog when
272         ///     interpreting the types to inject attributes into the type definition.
273         /// </param>
274         /// <exception cref="ArgumentException">
275         ///     <paramref name="assembly"/> is <see langword="null"/>.
276         ///     <para>
277         ///         -or-
278         ///     </para>
279         ///     <paramref name="assembly"/> was loaded in the reflection-only context.
280         ///     <para>
281         ///         -or-
282         ///     </para>
283         ///     <paramref name="reflectionContext"/> is <see langword="null"/>.
284         /// </exception>
AssemblyCatalog(Assembly assembly, ReflectionContext reflectionContext)285         public AssemblyCatalog(Assembly assembly, ReflectionContext reflectionContext)
286         {
287             Requires.NotNull(assembly, "assembly");
288             Requires.NotNull(reflectionContext, "reflectionContext");
289 
290             InitializeAssemblyCatalog(assembly);
291             _reflectionContext = reflectionContext;
292             _definitionOrigin = this;
293         }
294 
295         /// <summary>
296         ///     Initializes a new instance of the <see cref="AssemblyCatalog"/> class
297         ///     with the specified assembly, reflectionContext and definitionOrigin.
298         /// </summary>
299         /// <param name="assembly">
300         ///     The <see cref="Assembly"/> containing the attributed <see cref="Type"/> objects to
301         ///     add to the <see cref="AssemblyCatalog"/>.
302         /// </param>
303         /// <param name="reflectionContext">
304         ///     The <see cref="ReflectionContext"/> a context used by the catalog when
305         ///     interpreting the types to inject attributes into the type definition.
306         /// </param>
307         /// <param name="definitionOrigin">
308         ///     The <see cref="ICompositionElement"/> CompositionElement used by Diagnostics to identify the source for parts.
309         /// </param>
310         /// <exception cref="ArgumentException">
311         ///     <paramref name="assembly"/> is <see langword="null"/>.
312         ///     <para>
313         ///         -or-
314         ///     </para>
315         ///     <paramref name="assembly"/> was loaded in the reflection-only context.
316         ///     <para>
317         ///         -or-
318         ///     </para>
319         ///     <paramref name="reflectionContext"/> is <see langword="null"/>.
320         ///     <para>
321         ///         -or-
322         ///     </para>
323         ///     <paramref name="definitionOrigin"/> is <see langword="null"/>.
324         /// </exception>
AssemblyCatalog(Assembly assembly, ReflectionContext reflectionContext, ICompositionElement definitionOrigin)325         public AssemblyCatalog(Assembly assembly, ReflectionContext reflectionContext, ICompositionElement definitionOrigin)
326         {
327             Requires.NotNull(assembly, "assembly");
328             Requires.NotNull(reflectionContext, "reflectionContext");
329             Requires.NotNull(definitionOrigin, "definitionOrigin");
330 
331             InitializeAssemblyCatalog(assembly);
332             _reflectionContext = reflectionContext;
333             _definitionOrigin = definitionOrigin;
334         }
335 
336         /// <summary>
337         ///     Initializes a new instance of the <see cref="AssemblyCatalog"/> class
338         ///     with the specified assembly.
339         /// </summary>
340         /// <param name="assembly">
341         ///     The <see cref="Assembly"/> containing the attributed <see cref="Type"/> objects to
342         ///     add to the <see cref="AssemblyCatalog"/>.
343         /// </param>
344         /// <exception cref="ArgumentException">
345         ///     <paramref name="assembly"/> is <see langword="null"/>.
346         ///     <para>
347         ///         -or-
348         ///     </para>
349         ///     <paramref name="assembly"/> was loaded in the reflection-only context.
350         /// </exception>
AssemblyCatalog(Assembly assembly)351         public AssemblyCatalog(Assembly assembly)
352         {
353             Requires.NotNull(assembly, nameof(assembly));
354 
355             InitializeAssemblyCatalog(assembly);
356             _definitionOrigin = this;
357         }
358 
359         /// <summary>
360         ///     Initializes a new instance of the <see cref="AssemblyCatalog"/> class
361         ///     with the specified assembly.
362         /// </summary>
363         /// <param name="assembly">
364         ///     The <see cref="Assembly"/> containing the attributed <see cref="Type"/> objects to
365         ///     add to the <see cref="AssemblyCatalog"/>.
366         /// </param>
367         /// <param name="definitionOrigin">
368         ///     The <see cref="ICompositionElement"/> CompositionElement used by Diagnostics to identify the source for parts.
369         /// </param>
370         /// <exception cref="ArgumentException">
371         ///     <paramref name="assembly"/> is <see langword="null"/>.
372         ///     <para>
373         ///         -or-
374         ///     </para>
375         ///     <paramref name="assembly"/> was loaded in the reflection-only context.
376         ///     <para>
377         ///         -or-
378         ///     </para>
379         ///     <paramref name="definitionOrigin"/> is <see langword="null"/>.
380         /// </exception>
AssemblyCatalog(Assembly assembly, ICompositionElement definitionOrigin)381         public AssemblyCatalog(Assembly assembly, ICompositionElement definitionOrigin)
382         {
383             Requires.NotNull(assembly, nameof(assembly));
384             Requires.NotNull(definitionOrigin, nameof(definitionOrigin));
385 
386             InitializeAssemblyCatalog(assembly);
387             _definitionOrigin = definitionOrigin;
388         }
389 
InitializeAssemblyCatalog(Assembly assembly)390         private void InitializeAssemblyCatalog(Assembly assembly)
391         {
392             if (assembly.ReflectionOnly)
393             {
394                 throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, SR.Argument_AssemblyReflectionOnly, "assembly"), "assembly");
395             }
396             _assembly = assembly;
397         }
398 
399         /// <summary>
400         ///     Returns the export definitions that match the constraint defined by the specified definition.
401         /// </summary>
402         /// <param name="definition">
403         ///     The <see cref="ImportDefinition"/> that defines the conditions of the
404         ///     <see cref="ExportDefinition"/> objects to return.
405         /// </param>
406         /// <returns>
407         ///     An <see cref="IEnumerable{T}"/> of <see cref="Tuple{T1, T2}"/> containing the
408         ///     <see cref="ExportDefinition"/> objects and their associated
409         ///     <see cref="ComposablePartDefinition"/> for objects that match the constraint defined
410         ///     by <paramref name="definition"/>.
411         /// </returns>
412         /// <exception cref="ArgumentNullException">
413         ///     <paramref name="definition"/> is <see langword="null"/>.
414         /// </exception>
415         /// <exception cref="ObjectDisposedException">
416         ///     The <see cref="ComposablePartCatalog"/> has been disposed of.
417         /// </exception>
418         /// <remarks>
419         ///     <note type="inheritinfo">
420         ///         Overriders of this property should never return <see langword="null"/>, if no
421         ///         <see cref="ExportDefinition"/> match the conditions defined by
422         ///         <paramref name="definition"/>, return an empty <see cref="IEnumerable{T}"/>.
423         ///     </note>
424         /// </remarks>
GetExports(ImportDefinition definition)425         public override IEnumerable<Tuple<ComposablePartDefinition, ExportDefinition>> GetExports(ImportDefinition definition)
426         {
427             return InnerCatalog.GetExports(definition);
428         }
429 
430         private ComposablePartCatalog InnerCatalog
431         {
432             get
433             {
434                 ThrowIfDisposed();
435 
436                 if (_innerCatalog == null)
437                 {
438                     var catalogReflectionContextAttribute = _assembly.GetFirstAttribute<CatalogReflectionContextAttribute>();
439                     var assembly = (catalogReflectionContextAttribute != null)
440                         ? catalogReflectionContextAttribute.CreateReflectionContext().MapAssembly(_assembly)
441                         : _assembly;
442                     lock (_thisLock)
443                     {
444                         if (_innerCatalog == null)
445                         {
446                             var catalog = (_reflectionContext != null)
447                                 ? new TypeCatalog(assembly.GetTypes(), _reflectionContext, _definitionOrigin)
448                                 : new TypeCatalog(assembly.GetTypes(), _definitionOrigin);
449                             Thread.MemoryBarrier();
450                             _innerCatalog = catalog;
451                         }
452                     }
453                 }
454                 return _innerCatalog;
455             }
456         }
457 
458         /// <summary>
459         ///     Gets the assembly containing the attributed types contained within the assembly
460         ///     catalog.
461         /// </summary>
462         /// <value>
463         ///     The <see cref="Assembly"/> containing the attributed <see cref="Type"/> objects
464         ///     contained within the <see cref="AssemblyCatalog"/>.
465         /// </value>
466         public Assembly Assembly
467         {
468             get
469             {
470                 Contract.Ensures(Contract.Result<Assembly>() != null);
471 
472                 return _assembly;
473             }
474         }
475 
476         /// <summary>
477         ///     Gets the display name of the assembly catalog.
478         /// </summary>
479         /// <value>
480         ///     A <see cref="String"/> containing a human-readable display name of the <see cref="AssemblyCatalog"/>.
481         /// </value>
482         [SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes")]
483         string ICompositionElement.DisplayName
484         {
485             get { return GetDisplayName(); }
486         }
487 
488         /// <summary>
489         ///     Gets the composition element from which the assembly catalog originated.
490         /// </summary>
491         /// <value>
492         ///     This property always returns <see langword="null"/>.
493         /// </value>
494         [SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes")]
495         ICompositionElement ICompositionElement.Origin
496         {
497             get { return null; }
498         }
499 
500         /// <summary>
501         ///     Returns a string representation of the assembly catalog.
502         /// </summary>
503         /// <returns>
504         ///     A <see cref="String"/> containing the string representation of the <see cref="AssemblyCatalog"/>.
505         /// </returns>
ToString()506         public override string ToString()
507         {
508             return GetDisplayName();
509         }
510 
Dispose(bool disposing)511         protected override void Dispose(bool disposing)
512         {
513             try
514             {
515                 if (Interlocked.CompareExchange(ref _isDisposed, 1, 0) == 0)
516                 {
517                     if (disposing)
518                     {
519                         if (_innerCatalog != null)
520                         {
521                             _innerCatalog.Dispose();
522                         }
523                     }
524                 }
525             }
526             finally
527             {
528                 base.Dispose(disposing);
529             }
530         }
531 
GetEnumerator()532         public override IEnumerator<ComposablePartDefinition> GetEnumerator()
533         {
534             return InnerCatalog.GetEnumerator();
535         }
536 
ThrowIfDisposed()537         private void ThrowIfDisposed()
538         {
539             if (_isDisposed == 1)
540             {
541                 throw ExceptionBuilder.CreateObjectDisposed(this);
542             }
543         }
544 
GetDisplayName()545         private string GetDisplayName()
546         {
547             return string.Format(CultureInfo.CurrentCulture,
548                                 "{0} (Assembly=\"{1}\")",   // NOLOC
549                                 GetType().Name,
550                                 Assembly.FullName);
551         }
552 
LoadAssembly(string codeBase)553         private static Assembly LoadAssembly(string codeBase)
554         {
555             Requires.NotNullOrEmpty(codeBase, "codeBase");
556 
557             AssemblyName assemblyName;
558 
559             try
560             {
561                 assemblyName = AssemblyName.GetAssemblyName(codeBase);
562             }
563             catch (ArgumentException)
564             {
565                 assemblyName = new AssemblyName();
566                 assemblyName.CodeBase = codeBase;
567             }
568 
569             return Assembly.Load(assemblyName);
570         }
571     }
572 }
573