File: System\ComponentModel\Composition\Hosting\CompositionServices.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.Collections.ObjectModel;
using System.ComponentModel.Composition.Primitives;
using System.ComponentModel.Composition.ReflectionModel;
using System.Linq;
using System.Reflection;
using Microsoft.Internal;
 
namespace System.ComponentModel.Composition.Hosting
{
    internal static class CompositionServices
    {
        internal static readonly Type InheritedExportAttributeType = typeof(InheritedExportAttribute);
        internal static readonly Type ExportAttributeType = typeof(ExportAttribute);
        internal static readonly Type AttributeType = typeof(Attribute);
        internal static readonly Type ObjectType = typeof(object);
 
        private static readonly string[] reservedMetadataNames = new string[]
        {
            CompositionConstants.PartCreationPolicyMetadataName
        };
 
        internal static Type GetDefaultTypeFromMember(this MemberInfo member)
        {
            ArgumentNullException.ThrowIfNull(member);
 
            switch (member.MemberType)
            {
                case MemberTypes.Property:
                    return ((PropertyInfo)member).PropertyType;
 
                case MemberTypes.NestedType:
                case MemberTypes.TypeInfo:
                    return ((Type)member);
 
                case MemberTypes.Field:
                default:
                    if (member.MemberType != MemberTypes.Field)
                    {
                        throw new Exception(SR.Diagnostic_InternalExceptionMessage);
                    }
                    return ((FieldInfo)member).FieldType;
            }
        }
 
        internal static Type AdjustSpecifiedTypeIdentityType(this Type specifiedContractType, MemberInfo member)
        {
            if (member.MemberType == MemberTypes.Method)
            {
                return specifiedContractType;
            }
            else
            {
                return specifiedContractType.AdjustSpecifiedTypeIdentityType(member.GetDefaultTypeFromMember());
            }
        }
 
        internal static Type AdjustSpecifiedTypeIdentityType(this Type specifiedContractType, Type? memberType)
        {
            ArgumentNullException.ThrowIfNull(specifiedContractType);
 
            if ((memberType != null) && memberType.IsGenericType && specifiedContractType.IsGenericType)
            {
                // if the member type is closed and the specified contract type is open and they have exatly the same number of parameters
                // we will close the specified contract type
                if (specifiedContractType.ContainsGenericParameters && !memberType.ContainsGenericParameters)
                {
                    var typeGenericArguments = memberType.GetGenericArguments();
                    var metadataTypeGenericArguments = specifiedContractType.GetGenericArguments();
 
                    if (typeGenericArguments.Length == metadataTypeGenericArguments.Length)
                    {
                        return specifiedContractType.MakeGenericType(typeGenericArguments);
                    }
                }
                // if both member type and the contract type are open generic types, make sure that their parameters are ordered the same way
                else if (specifiedContractType.ContainsGenericParameters && memberType.ContainsGenericParameters)
                {
                    var memberGenericParameters = memberType.GetPureGenericParameters();
                    if (specifiedContractType.GetPureGenericArity() == memberGenericParameters.Count)
                    {
                        return specifiedContractType.GetGenericTypeDefinition().MakeGenericType(memberGenericParameters.ToArray());
                    }
                }
            }
 
            return specifiedContractType;
        }
 
        private static string AdjustTypeIdentity(string originalTypeIdentity, Type typeIdentityType)
        {
            return GenericServices.GetGenericName(originalTypeIdentity, GenericServices.GetGenericParametersOrder(typeIdentityType), GenericServices.GetPureGenericArity(typeIdentityType));
        }
 
        internal static void GetContractInfoFromExport(this MemberInfo member, ExportAttribute export, out Type? typeIdentityType, out string contractName)
        {
            typeIdentityType = member.GetTypeIdentityTypeFromExport(export);
            if (!string.IsNullOrEmpty(export.ContractName))
            {
                contractName = export.ContractName;
            }
            else
            {
                contractName = member.GetTypeIdentityFromExport(typeIdentityType);
            }
        }
 
        internal static string GetTypeIdentityFromExport(this MemberInfo member, Type? typeIdentityType)
        {
            if (typeIdentityType != null)
            {
                string typeIdentity = AttributedModelServices.GetTypeIdentity(typeIdentityType);
                if (typeIdentityType.ContainsGenericParameters)
                {
                    typeIdentity = AdjustTypeIdentity(typeIdentity, typeIdentityType);
                }
                return typeIdentity;
            }
            else
            {
                MethodInfo? method = member as MethodInfo;
                if (method == null)
                {
                    throw new Exception(SR.Diagnostic_InternalExceptionMessage);
                }
                return AttributedModelServices.GetTypeIdentity(method);
            }
        }
 
        private static Type? GetTypeIdentityTypeFromExport(this MemberInfo member, ExportAttribute export)
        {
            if (export.ContractType != null)
            {
                return export.ContractType.AdjustSpecifiedTypeIdentityType(member);
            }
            else
            {
                return (member.MemberType != MemberTypes.Method) ? member.GetDefaultTypeFromMember() : null;
            }
        }
 
        internal static bool IsContractNameSameAsTypeIdentity(this ExportAttribute export)
        {
            return string.IsNullOrEmpty(export.ContractName);
        }
 
        internal static Type GetContractTypeFromImport(this IAttributedImport import, ImportType importType)
        {
            if (import.ContractType != null)
            {
                return import.ContractType.AdjustSpecifiedTypeIdentityType(importType.ContractType);
            }
 
            return importType.ContractType;
        }
 
        internal static string GetContractNameFromImport(this IAttributedImport import, ImportType importType)
        {
            if (!string.IsNullOrEmpty(import.ContractName))
            {
                return import.ContractName;
            }
 
            Type contractType = import.GetContractTypeFromImport(importType);
 
            return AttributedModelServices.GetContractName(contractType);
        }
 
        internal static string? GetTypeIdentityFromImport(this IAttributedImport import, ImportType importType)
        {
            Type contractType = import.GetContractTypeFromImport(importType);
 
            // For our importers we treat object as not having a type identity
            if (contractType == CompositionServices.ObjectType)
            {
                return null;
            }
 
            return AttributedModelServices.GetTypeIdentity(contractType);
        }
 
        internal static IDictionary<string, object?> GetPartMetadataForType(this Type type, CreationPolicy creationPolicy)
        {
            var dictionary = new Dictionary<string, object?>(StringComparers.MetadataKeyNames);
 
            if (creationPolicy != CreationPolicy.Any)
            {
                dictionary.Add(CompositionConstants.PartCreationPolicyMetadataName, creationPolicy);
            }
 
            foreach (PartMetadataAttribute partMetadata in type.GetAttributes<PartMetadataAttribute>())
            {
                if (reservedMetadataNames.Contains(partMetadata.Name, StringComparers.MetadataKeyNames)
                    || dictionary.ContainsKey(partMetadata.Name))
                {
                    // Perhaps we should log an error here so that people know this value is being ignored.
                    continue;
                }
 
                dictionary.Add(partMetadata.Name, partMetadata.Value);
            }
 
            // metadata for generic types
            if (type.ContainsGenericParameters)
            {
                // Register the part as generic
                dictionary.Add(CompositionConstants.IsGenericPartMetadataName, true);
 
                // Add arity
                Type[] genericArguments = type.GetGenericArguments();
                dictionary.Add(CompositionConstants.GenericPartArityMetadataName, genericArguments.Length);
 
                // add constraints
                bool hasConstraints = false;
                object?[] genericParameterConstraints = new object?[genericArguments.Length];
                GenericParameterAttributes[] genericParameterAttributes = new GenericParameterAttributes[genericArguments.Length];
                for (int i = 0; i < genericArguments.Length; i++)
                {
                    Type genericArgument = genericArguments[i];
 
                    Type[]? constraints = genericArgument.GetGenericParameterConstraints();
                    if (constraints.Length == 0)
                    {
                        constraints = null;
                    }
 
                    GenericParameterAttributes attributes = genericArgument.GenericParameterAttributes;
 
                    if ((constraints != null) || (attributes != GenericParameterAttributes.None))
                    {
                        genericParameterConstraints[i] = constraints;
                        genericParameterAttributes[i] = attributes;
                        hasConstraints = true;
                    }
                }
 
                if (hasConstraints)
                {
                    dictionary.Add(CompositionConstants.GenericParameterConstraintsMetadataName, genericParameterConstraints);
                    dictionary.Add(CompositionConstants.GenericParameterAttributesMetadataName, genericParameterAttributes);
                }
            }
 
            if (dictionary.Count == 0)
            {
                return MetadataServices.EmptyMetadata;
            }
            else
            {
                return dictionary;
            }
        }
 
        internal static void TryExportMetadataForMember(this MemberInfo member, out IDictionary<string, object?> dictionary)
        {
            dictionary = new Dictionary<string, object?>();
 
            foreach (var attr in member.GetAttributes<Attribute>())
            {
                var provider = attr as ExportMetadataAttribute;
 
                if (provider != null)
                {
                    if (reservedMetadataNames.Contains(provider.Name, StringComparers.MetadataKeyNames))
                    {
                        throw ExceptionBuilder.CreateDiscoveryException(SR.Discovery_ReservedMetadataNameUsed, member.GetDisplayName(), provider.Name);
                    }
 
                    // we pass "null" for valueType which would make it inferred. We don;t have additional type information when metadata
                    // goes through the ExportMetadataAttribute path
                    if (!dictionary.TryContributeMetadataValue(provider.Name, provider.Value, null, provider.IsMultiple))
                    {
                        throw ExceptionBuilder.CreateDiscoveryException(SR.Discovery_DuplicateMetadataNameValues, member.GetDisplayName(), provider.Name);
                    }
                }
                else
                {
                    Type attrType = attr.GetType();
                    // Perf optimization, relies on short circuit evaluation, often a property attribute is an ExportAttribute
                    if ((attrType != CompositionServices.ExportAttributeType) && attrType.IsAttributeDefined<MetadataAttributeAttribute>(true))
                    {
                        bool allowsMultiple = false;
                        AttributeUsageAttribute? usage = attrType.GetFirstAttribute<AttributeUsageAttribute>(true);
 
                        if (usage != null)
                        {
                            allowsMultiple = usage.AllowMultiple;
                        }
 
                        foreach (PropertyInfo pi in attrType.GetProperties())
                        {
                            if (pi.DeclaringType == CompositionServices.ExportAttributeType || pi.DeclaringType == CompositionServices.AttributeType)
                            {
                                // Don't contribute metadata properies from the base attribute types.
                                continue;
                            }
 
                            if (reservedMetadataNames.Contains(pi.Name, StringComparers.MetadataKeyNames))
                            {
                                throw ExceptionBuilder.CreateDiscoveryException(SR.Discovery_ReservedMetadataNameUsed, pi.GetDisplayName(), pi.Name);
                            }
 
                            object? value = pi.GetValue(attr, null);
 
                            if (value != null && !IsValidAttributeType(value.GetType()))
                            {
                                throw ExceptionBuilder.CreateDiscoveryException(SR.Discovery_MetadataContainsValueWithInvalidType, pi.GetDisplayName(), value.GetType().GetDisplayName());
                            }
 
                            if (!dictionary.TryContributeMetadataValue(pi.Name, value, pi.PropertyType, allowsMultiple))
                            {
                                throw ExceptionBuilder.CreateDiscoveryException(SR.Discovery_DuplicateMetadataNameValues, member.GetDisplayName(), pi.Name);
                            }
                        }
                    }
                }
            }
 
            // Need Keys.ToArray because we alter the dictionary in the loop
            foreach (var key in dictionary.Keys.ToArray())
            {
                var list = dictionary[key] as MetadataList;
                if (list != null)
                {
                    dictionary[key] = list.ToArray();
                }
            }
 
            return;
        }
 
        private static bool TryContributeMetadataValue(this IDictionary<string, object?> dictionary, string name, object? value, Type? valueType, bool allowsMultiple)
        {
            if (!dictionary.TryGetValue(name, out object? metadataValue))
            {
                if (allowsMultiple)
                {
                    var list = new MetadataList();
                    list.Add(value, valueType);
                    value = list;
                }
 
                dictionary.Add(name, value);
            }
            else
            {
                var list = metadataValue as MetadataList;
                if (!allowsMultiple || list == null)
                {
                    // Either single value already found when should be multiple
                    // or a duplicate name already exists
                    dictionary.Remove(name);
                    return false;
                }
 
                list.Add(value, valueType);
            }
            return true;
        }
 
        private sealed class MetadataList
        {
            private Type? _arrayType;
            private bool _containsNulls;
            private static readonly Type ObjectType = typeof(object);
            private static readonly Type TypeType = typeof(Type);
            private readonly Collection<object?> _innerList = new Collection<object?>();
 
            public void Add(object? item, Type? itemType)
            {
                _containsNulls |= (item == null);
 
                // if we've been passed typeof(object), we basically have no type inmformation
                if (itemType == ObjectType)
                {
                    itemType = null;
                }
 
                // if we have no type information, get it from the item, if we can
                if ((itemType == null) && (item != null))
                {
                    itemType = item.GetType();
                }
 
                // Types are special, because the are abstract classes, so if the item casts to Type, we assume System.Type
                if (item is Type)
                {
                    itemType = TypeType;
                }
 
                // only try to call this if we got a meaningful type
                if (itemType != null)
                {
                    InferArrayType(itemType);
                }
 
                _innerList.Add(item);
            }
 
            private void InferArrayType(Type itemType)
            {
                ArgumentNullException.ThrowIfNull(itemType);
 
                if (_arrayType == null)
                {
                    // this is the first typed element we've been given, it sets the type of the array
                    _arrayType = itemType;
                }
                else
                {
                    // if there's a disagreement on the array type, we flip to Object
                    // NOTE : we can try to do better in the future to find common base class, but given that we support very limited set of types
                    // in metadata right now, it's a moot point
                    if (_arrayType != itemType)
                    {
                        _arrayType = ObjectType;
                    }
                }
            }
 
            public Array ToArray()
            {
                if (_arrayType == null)
                {
                    // if the array type has not been set, assume Object
                    _arrayType = ObjectType;
                }
                else if (_containsNulls && _arrayType.IsValueType)
                {
                    // if the array type is a value type and we have seen nulls, then assume Object
                    _arrayType = ObjectType;
                }
 
                Array array = Array.CreateInstance(_arrayType, _innerList.Count);
 
                for (int i = 0; i < array.Length; i++)
                {
                    array.SetValue(_innerList[i], i);
                }
                return array;
            }
        }
 
        //UNDONE: Need to add these warnings somewhere...Dev10:472538 should address
        //internal static CompositionResult MatchRequiredMetadata(this IDictionary<string, object> metadata, IEnumerable<string> requiredMetadata, string contractName)
        //{
        //    Assumes.IsTrue(metadata != null);
 
        //    var result = CompositionResult.SucceededResult;
 
        //    var missingMetadata = (requiredMetadata == null) ? null : requiredMetadata.Except<string>(metadata.Keys);
        //    if (missingMetadata != null && missingMetadata.Any())
        //    {
        //        result = result.MergeIssue(
        //            CompositionError.CreateIssueAsWarning(CompositionErrorId.RequiredMetadataNotFound,
        //            SR.RequiredMetadataNotFound,
        //            contractName,
        //            string.Join(", ", missingMetadata.ToArray())));
 
        //        return new CompositionResult(false, result.Issues);
        //    }
 
        //    return result;
        //}
 
        internal static IEnumerable<KeyValuePair<string, Type>> GetRequiredMetadata(Type? metadataViewType)
        {
            if ((metadataViewType == null) ||
                ExportServices.IsDefaultMetadataViewType(metadataViewType) ||
                ExportServices.IsDictionaryConstructorViewType(metadataViewType) ||
                !metadataViewType.IsInterface)
            {
                return Enumerable.Empty<KeyValuePair<string, Type>>();
            }
 
            // A metadata view is required to be an Interface, and therefore only properties are allowed
            List<PropertyInfo> properties = metadataViewType.GetAllProperties().
                Where(property => property.GetFirstAttribute<DefaultValueAttribute>() == null).
                ToList();
 
            // NOTE : this is a carefully found balance between eager and delay-evaluation - the properties are filtered once and upfront
            // whereas the key/Type pairs are created every time. The latter is fine as KVPs are structs and as such copied on access regardless.
            // This also allows us to avoid creation of List<KVP> which - at least according to FxCop - leads to isues with NGEN
            return properties.Select(property => new KeyValuePair<string, Type>(property.Name, property.PropertyType));
        }
 
        internal static IDictionary<string, object?> GetImportMetadata(ImportType importType, IAttributedImport attributedImport)
        {
            return GetImportMetadata(importType.ContractType, attributedImport);
        }
 
        internal static IDictionary<string, object?> GetImportMetadata(Type type, IAttributedImport? attributedImport)
        {
            Dictionary<string, object?>? metadata = null;
 
            //Prior to V4.5 MEF did not support ImportMetadata
            if (type.IsGenericType)
            {
                metadata = new Dictionary<string, object?>();
 
                if (type.ContainsGenericParameters)
                {
                    metadata[CompositionConstants.GenericImportParametersOrderMetadataName] = GenericServices.GetGenericParametersOrder(type);
                }
                else
                {
                    metadata[CompositionConstants.GenericContractMetadataName] = ContractNameServices.GetTypeIdentity(type.GetGenericTypeDefinition());
                    metadata[CompositionConstants.GenericParametersMetadataName] = type.GetGenericArguments();
                }
            }
 
            // Default value is ImportSource.Any
            if (attributedImport != null && attributedImport.Source != ImportSource.Any)
            {
                metadata ??= new Dictionary<string, object?>();
                metadata[CompositionConstants.ImportSourceMetadataName] = attributedImport.Source;
            }
 
            if (metadata != null)
            {
                return metadata.AsReadOnly();
            }
            else
            {
                return MetadataServices.EmptyMetadata;
            }
        }
 
        internal static object? GetExportedValueFromComposedPart(ImportEngine? engine, ComposablePart part, ExportDefinition definition)
        {
            if (engine != null)
            {
                try
                {
                    engine.SatisfyImports(part);
                }
                catch (CompositionException ex)
                {
                    throw ExceptionBuilder.CreateCannotGetExportedValue(part, definition, ex);
                }
            }
 
            try
            {
                return part.GetExportedValue(definition);
            }
            catch (ComposablePartException ex)
            {
                throw ExceptionBuilder.CreateCannotGetExportedValue(part, definition, ex);
            }
        }
 
        internal static bool IsRecomposable(this ComposablePart part)
        {
            return part.ImportDefinitions.Any(import => import.IsRecomposable);
        }
 
        internal static CompositionResult TryInvoke(Action action)
        {
            try
            {
                action();
                return CompositionResult.SucceededResult;
            }
            catch (CompositionException ex)
            {
                return new CompositionResult(ex.Errors);
            }
        }
 
        internal static CompositionResult TryFire<TEventArgs>(EventHandler<TEventArgs> _delegate, object? sender, TEventArgs e)
            where TEventArgs : EventArgs
        {
            CompositionResult result = CompositionResult.SucceededResult;
            foreach (EventHandler<TEventArgs> _subscriber in _delegate.GetInvocationList())
            {
                try
                {
                    _subscriber.Invoke(sender, e);
                }
                catch (CompositionException ex)
                {
                    result = result.MergeErrors(ex.Errors);
                }
            }
 
            return result;
        }
 
        internal static CreationPolicy GetRequiredCreationPolicy(this ImportDefinition definition)
        {
            if (definition is ContractBasedImportDefinition contractDefinition)
            {
                return contractDefinition.RequiredCreationPolicy;
            }
 
            return CreationPolicy.Any;
        }
 
        /// <summary>
        ///     Returns a value indicating whether cardinality is
        ///     <see cref="ImportCardinality.ZeroOrOne"/> or
        ///     <see cref="ImportCardinality.ExactlyOne"/>.
        /// </summary>
        internal static bool IsAtMostOne(this ImportCardinality cardinality)
        {
            return cardinality == ImportCardinality.ZeroOrOne || cardinality == ImportCardinality.ExactlyOne;
        }
 
        private static bool IsValidAttributeType(Type type)
        {
            return IsValidAttributeType(type, true);
        }
 
        private static bool IsValidAttributeType(Type type, bool arrayAllowed)
        {
            ArgumentNullException.ThrowIfNull(type);
 
            // Definitions of valid attribute type taken from C# 3.0 Specification section 17.1.3.
 
            // One of the following types: bool, byte, char, double, float, int, long, sbyte, short, string, uint, ulong, ushort.
            if (type.IsPrimitive)
            {
                return true;
            }
 
            if (type == typeof(string))
            {
                return true;
            }
 
            // An enum type, provided it has public accessibility and the types in which it is nested (if any) also have public accessibility
            if (type.IsEnum && type.IsVisible)
            {
                return true;
            }
 
            if (typeof(Type).IsAssignableFrom(type))
            {
                return true;
            }
 
            // Single-dimensional arrays of the above types.
            if (arrayAllowed && type.IsArray &&
                type.GetArrayRank() == 1 &&
                IsValidAttributeType(type.GetElementType()!, false))
            {
                return true;
            }
 
            return false;
        }
    }
}