File: System\ComponentModel\Composition\Hosting\TypeCatalog.cs
Web Access
Project: src\src\libraries\System.ComponentModel.Composition\src\System.ComponentModel.Composition.csproj (System.ComponentModel.Composition)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Collections.Generic;
using System.ComponentModel.Composition.AttributedModel;
using System.ComponentModel.Composition.Primitives;
using System.ComponentModel.Composition.ReflectionModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using Microsoft.Internal;
using Microsoft.Internal.Collections;
 
namespace System.ComponentModel.Composition.Hosting
{
    /// <summary>
    ///     An immutable ComposablePartCatalog created from a type array or a list of managed types.  This class is threadsafe.
    ///     It is Disposable.
    /// </summary>
    [DebuggerTypeProxy(typeof(ComposablePartCatalogDebuggerProxy))]
    public class TypeCatalog : ComposablePartCatalog, ICompositionElement
    {
        private readonly object _thisLock = new object();
        private Type[]? _types;
        private volatile List<ComposablePartDefinition>? _parts;
        private volatile bool _isDisposed;
        private readonly ICompositionElement _definitionOrigin;
        private readonly Lazy<Dictionary<string, List<ComposablePartDefinition>>> _contractPartIndex;
 
        /// <summary>
        ///     Initializes a new instance of the <see cref="TypeCatalog"/> class
        ///     with the specified types.
        /// </summary>
        /// <param name="types">
        ///     An <see cref="Array"/> of attributed <see cref="Type"/> objects to add to the
        ///     <see cref="TypeCatalog"/>.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="types"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="ArgumentException">
        ///     <paramref name="types"/> contains an element that is <see langword="null"/>.
        ///     <para>
        ///         -or-
        ///     </para>
        ///     <paramref name="types"/> contains an element that was loaded in the Reflection-only context.
        /// </exception>
        public TypeCatalog(params Type[] types) : this((IEnumerable<Type>)types)
        {
        }
 
        /// <summary>
        ///     Initializes a new instance of the <see cref="TypeCatalog"/> class
        ///     with the specified types.
        /// </summary>
        /// <param name="types">
        ///     An <see cref="IEnumerable{T}"/> of attributed <see cref="Type"/> objects to add
        ///     to the <see cref="TypeCatalog"/>.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="types"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="ArgumentException">
        ///     <paramref name="types"/> contains an element that is <see langword="null"/>.
        ///     <para>
        ///         -or-
        ///     </para>
        ///     <paramref name="types"/> contains an element that was loaded in the reflection-only context.
        /// </exception>
        public TypeCatalog(IEnumerable<Type> types)
        {
            Requires.NotNull(types, nameof(types));
 
            InitializeTypeCatalog(types);
 
            _definitionOrigin = this;
            _contractPartIndex = new Lazy<Dictionary<string, List<ComposablePartDefinition>>>(CreateIndex, true);
        }
 
        /// <summary>
        ///     Initializes a new instance of the <see cref="TypeCatalog"/> class
        ///     with the specified types.
        /// </summary>
        /// <param name="types">
        ///     An <see cref="IEnumerable{T}"/> of attributed <see cref="Type"/> objects to add
        ///     to the <see cref="TypeCatalog"/>.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="types"/> is <see langword="null"/>.
        /// </exception>
        /// <param name="definitionOrigin">
        ///     The <see cref="ICompositionElement"/> CompositionElement used by Diagnostics to identify the source for parts.
        /// </param>
        /// <exception cref="ArgumentException">
        ///     <paramref name="types"/> contains an element that is <see langword="null"/>.
        /// </exception>
        public TypeCatalog(IEnumerable<Type> types, ICompositionElement definitionOrigin)
        {
            Requires.NotNull(types, nameof(types));
            Requires.NotNull(definitionOrigin, nameof(definitionOrigin));
 
            InitializeTypeCatalog(types);
 
            _definitionOrigin = definitionOrigin;
            _contractPartIndex = new Lazy<Dictionary<string, List<ComposablePartDefinition>>>(CreateIndex, true);
        }
 
        /// <summary>
        ///     Initializes a new instance of the <see cref="TypeCatalog"/> class
        ///     with the specified types.
        /// </summary>
        /// <param name="types">
        ///     An <see cref="IEnumerable{T}"/> of attributed <see cref="Type"/> objects to add
        ///     to the <see cref="TypeCatalog"/>.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="types"/> is <see langword="null"/>.
        /// </exception>
        /// <param name="reflectionContext">
        ///     The <see cref="ReflectionContext"/> a context used by the catalog when
        ///     interpreting the types to inject attributes into the type definition.
        /// </param>
        /// <exception cref="ArgumentException">
        ///     <paramref name="types"/> contains an element that is <see langword="null"/>.
        /// </exception>
        public TypeCatalog(IEnumerable<Type> types, ReflectionContext reflectionContext)
        {
            Requires.NotNull(types, nameof(types));
            Requires.NotNull(reflectionContext, nameof(reflectionContext));
 
            InitializeTypeCatalog(types, reflectionContext);
 
            _definitionOrigin = this;
            _contractPartIndex = new Lazy<Dictionary<string, List<ComposablePartDefinition>>>(CreateIndex, true);
        }
 
        /// <summary>
        ///     Initializes a new instance of the <see cref="TypeCatalog"/> class
        ///     with the specified types.
        /// </summary>
        /// <param name="types">
        ///     An <see cref="IEnumerable{T}"/> of attributed <see cref="Type"/> objects to add
        ///     to the <see cref="TypeCatalog"/>.
        /// </param>
        /// <param name="reflectionContext">
        ///     The <see cref="ReflectionContext"/> a context used by the catalog when
        ///     interpreting the types to inject attributes into the type definition.
        /// </param>
        /// <param name="definitionOrigin">
        ///     The <see cref="ICompositionElement"/> CompositionElement used by Diagnostics to identify the source for parts.
        /// </param>
        /// <exception cref="ArgumentNullException">
        ///     <paramref name="types"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="ArgumentException">
        ///     <paramref name="types"/> contains an element that is <see langword="null"/>.
        /// </exception>
        public TypeCatalog(IEnumerable<Type> types, ReflectionContext reflectionContext, ICompositionElement definitionOrigin)
        {
            Requires.NotNull(types, nameof(types));
            Requires.NotNull(reflectionContext, nameof(reflectionContext));
            Requires.NotNull(definitionOrigin, nameof(definitionOrigin));
 
            InitializeTypeCatalog(types, reflectionContext);
 
            _definitionOrigin = definitionOrigin;
            _contractPartIndex = new Lazy<Dictionary<string, List<ComposablePartDefinition>>>(CreateIndex, true);
        }
 
        private void InitializeTypeCatalog(IEnumerable<Type> types, ReflectionContext reflectionContext)
        {
            var typesList = new List<Type>();
            foreach (var type in types)
            {
                if (type == null)
                {
                    throw ExceptionBuilder.CreateContainsNullElement(nameof(types));
                }
                if (type.Assembly.ReflectionOnly)
                {
                    throw new ArgumentException(SR.Format(SR.Argument_ElementReflectionOnlyType, nameof(types)), nameof(types));
                }
                var typeInfo = type.GetTypeInfo();
                var lclType = (reflectionContext != null) ? reflectionContext.MapType(typeInfo) : typeInfo;
 
                // It is valid for the reflectionContext to delete types by mapping them to null
                if (lclType != null)
                {
                    // The final mapped type may be activated so we check to see if it is in a reflect only assembly
                    if (lclType.Assembly.ReflectionOnly)
                    {
                        throw new ArgumentException(SR.Format(SR.Argument_ReflectionContextReturnsReflectionOnlyType, nameof(reflectionContext)), nameof(reflectionContext));
                    }
                    typesList.Add(lclType);
                }
            }
            _types = typesList.ToArray();
        }
 
        private void InitializeTypeCatalog(IEnumerable<Type> types)
        {
            Type[] arr = types.ToArray();
            foreach (Type type in arr)
            {
                if (type == null)
                {
                    throw ExceptionBuilder.CreateContainsNullElement(nameof(types));
                }
 
                if (type.Assembly.ReflectionOnly)
                {
                    throw new ArgumentException(SR.Format(SR.Argument_ElementReflectionOnlyType, nameof(types)), nameof(types));
                }
            }
 
            _types = arr;
        }
 
        public override IEnumerator<ComposablePartDefinition> GetEnumerator()
        {
            ThrowIfDisposed();
            return PartsInternal.GetEnumerator();
        }
 
        /// <summary>
        ///     Gets the display name of the type catalog.
        /// </summary>
        /// <value>
        ///     A <see cref="string"/> containing a human-readable display name of the <see cref="TypeCatalog"/>.
        /// </value>
        string ICompositionElement.DisplayName
        {
            get { return GetDisplayName(); }
        }
 
        /// <summary>
        ///     Gets the composition element from which the type catalog originated.
        /// </summary>
        /// <value>
        ///     This property always returns <see langword="null"/>.
        /// </value>
        ICompositionElement? ICompositionElement.Origin
        {
            get { return null; }
        }
 
        private IEnumerable<ComposablePartDefinition> PartsInternal
        {
            get
            {
                if (_parts == null)
                {
                    lock (_thisLock)
                    {
                        if (_parts == null)
                        {
                            if (_types == null)
                            {
                                throw new Exception(SR.Diagnostic_InternalExceptionMessage);
                            }
 
                            var collection = new List<ComposablePartDefinition>();
                            foreach (Type type in _types)
                            {
                                var definition = AttributedModelDiscovery.CreatePartDefinitionIfDiscoverable(type, _definitionOrigin);
                                if (definition != null)
                                {
                                    collection.Add(definition);
                                }
                            }
                            Thread.MemoryBarrier();
 
                            _types = null;
                            _parts = collection;
                        }
                    }
                }
 
                return _parts;
            }
        }
 
        internal override IEnumerable<ComposablePartDefinition>? GetCandidateParts(ImportDefinition definition)
        {
            ArgumentNullException.ThrowIfNull(definition);
 
            string contractName = definition.ContractName;
            if (string.IsNullOrEmpty(contractName))
            {
                return PartsInternal;
            }
 
            string? genericContractName = definition.Metadata.GetValue<string>(CompositionConstants.GenericContractMetadataName);
 
            List<ComposablePartDefinition>? nonGenericMatches = GetCandidateParts(contractName);
            List<ComposablePartDefinition>? genericMatches = GetCandidateParts(genericContractName);
 
            return nonGenericMatches.ConcatAllowingNull(genericMatches);
        }
 
        private List<ComposablePartDefinition>? GetCandidateParts(string? contractName)
        {
            if (contractName == null)
            {
                return null;
            }
 
            _contractPartIndex.Value.TryGetValue(contractName, out List<ComposablePartDefinition>? contractCandidateParts);
            return contractCandidateParts;
        }
 
        private Dictionary<string, List<ComposablePartDefinition>> CreateIndex()
        {
            Dictionary<string, List<ComposablePartDefinition>> index = new Dictionary<string, List<ComposablePartDefinition>>(StringComparers.ContractName);
 
            foreach (var part in PartsInternal)
            {
                foreach (string contractName in part.ExportDefinitions.Select(export => export.ContractName).Distinct())
                {
                    List<ComposablePartDefinition>? contractParts = null;
                    if (!index.TryGetValue(contractName, out contractParts))
                    {
                        contractParts = new List<ComposablePartDefinition>();
                        index.Add(contractName, contractParts);
                    }
                    contractParts.Add(part);
                }
            }
            return index;
        }
 
        /// <summary>
        ///     Returns a string representation of the type catalog.
        /// </summary>
        /// <returns>
        ///     A <see cref="string"/> containing the string representation of the <see cref="TypeCatalog"/>.
        /// </returns>
        public override string ToString()
        {
            return GetDisplayName();
        }
 
        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                _isDisposed = true;
            }
 
            base.Dispose(disposing);
        }
 
        private string GetDisplayName()
        {
            return SR.Format(
                                SR.TypeCatalog_DisplayNameFormat,
                                GetType().Name,
                                GetTypesDisplay());
        }
 
        private string GetTypesDisplay()
        {
            int count = PartsInternal.Count();
            if (count == 0)
            {
                return SR.TypeCatalog_Empty;
            }
 
            const int displayCount = 2;
            StringBuilder builder = new StringBuilder();
            foreach (ReflectionComposablePartDefinition definition in PartsInternal.Take(displayCount))
            {
                if (builder.Length > 0)
                {
                    builder.Append(CultureInfo.CurrentCulture.TextInfo.ListSeparator);
                    builder.Append(' ');
                }
 
                builder.Append(definition.GetPartType().GetDisplayName());
            }
 
            if (count > displayCount)
            {   // Add an elipse to indicate that there
                // are more types than actually listed
                builder.Append(CultureInfo.CurrentCulture.TextInfo.ListSeparator);
                builder.Append(" ...");
            }
 
            return builder.ToString();
        }
 
        [DebuggerStepThrough]
        private void ThrowIfDisposed()
        {
            if (_isDisposed)
            {
                throw ExceptionBuilder.CreateObjectDisposed(this);
            }
        }
    }
}