File: System\Composition\Convention\PartConventionBuilderOfT.cs
Web Access
Project: src\src\libraries\System.Composition.Convention\src\System.Composition.Convention.csproj (System.Composition.Convention)
// 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.Linq.Expressions;
using System.Reflection;
 
namespace System.Composition.Convention
{
    /// <summary>
    /// Configures a type as a MEF part.
    /// </summary>
    /// <typeparam name="T">The type of the part, or a type to which the part is assignable.</typeparam>
    public class PartConventionBuilder<T> : PartConventionBuilder
    {
        private sealed class MethodExpressionAdapter
        {
            private readonly MethodInfo _methodInfo;
 
            public MethodExpressionAdapter(Expression<Action<T>> methodSelector)
            {
                _methodInfo = SelectMethods(methodSelector);
            }
 
            public bool VerifyMethodInfo(MethodInfo mi)
            {
                return mi == _methodInfo;
            }
 
            private static MethodInfo SelectMethods(Expression<Action<T>> methodSelector)
            {
                if (methodSelector is null)
                {
                    throw new ArgumentNullException(nameof(methodSelector));
                }
 
                Expression expr = Reduce(methodSelector).Body;
                if (expr.NodeType == ExpressionType.Call)
                {
                    var memberInfo = ((MethodCallExpression)expr).Method as MethodInfo;
                    if (memberInfo != null)
                    {
                        return memberInfo;
                    }
                }
 
                // An error occurred the expression must be a void Method() Member Expression
                throw new ArgumentException(SR.Format(SR.Argument_ExpressionMustBeVoidMethodWithNoArguments, nameof(methodSelector)), nameof(methodSelector));
            }
 
            private static Expression<Func<T, object>> Reduce(Expression<Func<T, object>> expr)
            {
                while (expr.CanReduce)
                {
                    expr = (Expression<Func<T, object>>)expr.Reduce();
                }
                return expr;
            }
 
            private static Expression<Action<T>> Reduce(Expression<Action<T>> expr)
            {
                while (expr.CanReduce)
                {
                    expr = (Expression<Action<T>>)expr.Reduce();
                }
                return expr;
            }
        }
 
        private sealed class PropertyExpressionAdapter
        {
            private readonly PropertyInfo _propertyInfo;
            private readonly Action<ImportConventionBuilder> _configureImport;
            private readonly Action<ExportConventionBuilder> _configureExport;
 
            public PropertyExpressionAdapter(
                Expression<Func<T, object>> propertySelector,
                Action<ImportConventionBuilder> configureImport = null,
                Action<ExportConventionBuilder> configureExport = null)
            {
                _propertyInfo = SelectProperties(propertySelector);
                _configureImport = configureImport;
                _configureExport = configureExport;
            }
 
            public bool VerifyPropertyInfo(PropertyInfo pi)
            {
                return pi == _propertyInfo;
            }
 
            public void ConfigureImport(PropertyInfo _, ImportConventionBuilder importBuilder)
            {
                _configureImport?.Invoke(importBuilder);
            }
 
            public void ConfigureExport(PropertyInfo _, ExportConventionBuilder exportBuilder)
            {
                _configureExport?.Invoke(exportBuilder);
            }
 
            private static PropertyInfo SelectProperties(Expression<Func<T, object>> propertySelector)
            {
                if (propertySelector is null)
                {
                    throw new ArgumentNullException(nameof(propertySelector));
                }
 
                Expression expr = Reduce(propertySelector).Body;
                if (expr.NodeType == ExpressionType.MemberAccess)
                {
                    var memberInfo = ((MemberExpression)expr).Member as PropertyInfo;
                    if (memberInfo != null)
                    {
                        return memberInfo;
                    }
                }
 
                // An error occurred the expression must be a Property Member Expression
                throw new ArgumentException(SR.Format(SR.Argument_ExpressionMustBePropertyMember, nameof(propertySelector)), nameof(propertySelector));
            }
 
            private static Expression<Func<T, object>> Reduce(Expression<Func<T, object>> expr)
            {
                while (expr.CanReduce)
                {
                    expr = (Expression<Func<T, object>>)expr.Reduce();
                }
                return expr;
            }
        }
 
        private sealed class ConstructorExpressionAdapter
        {
            private ConstructorInfo _constructorInfo;
            private Dictionary<ParameterInfo, Action<ImportConventionBuilder>> _importBuilders;
 
            public ConstructorExpressionAdapter(Expression<Func<ParameterImportConventionBuilder, T>> selectConstructor)
            {
                ParseSelectConstructor(selectConstructor);
            }
 
            public ConstructorInfo SelectConstructor(IEnumerable<ConstructorInfo> _)
            {
                return _constructorInfo;
            }
 
            public void ConfigureConstructorImports(ParameterInfo parameterInfo, ImportConventionBuilder importBuilder)
            {
                if (_importBuilders != null)
                {
                    if (_importBuilders.TryGetValue(parameterInfo, out Action<ImportConventionBuilder> parameterImportBuilder))
                    {
                        parameterImportBuilder(importBuilder);
                    }
                }
 
                return;
            }
 
            private void ParseSelectConstructor(Expression<Func<ParameterImportConventionBuilder, T>> constructorSelector)
            {
                if (constructorSelector is null)
                {
                    throw new ArgumentNullException(nameof(constructorSelector));
                }
 
                Expression expr = Reduce(constructorSelector).Body;
                if (expr.NodeType != ExpressionType.New)
                {
                    throw new ArgumentException(SR.Format(SR.Argument_ExpressionMustBeNew, nameof(constructorSelector)), nameof(constructorSelector));
                }
                var newExpression = (NewExpression)expr;
                _constructorInfo = newExpression.Constructor;
 
                int index = 0;
                ParameterInfo[] parameterInfos = _constructorInfo.GetParameters();
 
                foreach (Expression argument in newExpression.Arguments)
                {
                    if (argument.NodeType == ExpressionType.Call)
                    {
                        var methodCallExpression = (MethodCallExpression)argument;
                        if (methodCallExpression.Arguments.Count == 1)
                        {
                            Expression parameter = methodCallExpression.Arguments[0];
                            if (parameter.NodeType == ExpressionType.Lambda)
                            {
                                var lambdaExpression = (LambdaExpression)parameter;
                                Delegate importDelegate = lambdaExpression.Compile();
                                _importBuilders ??= new Dictionary<ParameterInfo, Action<ImportConventionBuilder>>();
                                _importBuilders.Add(parameterInfos[index], (Action<ImportConventionBuilder>)importDelegate);
                                ++index;
                            }
                        }
                    }
                }
            }
 
            private static Expression<Func<ParameterImportConventionBuilder, T>> Reduce(Expression<Func<ParameterImportConventionBuilder, T>> expr)
            {
                while (expr.CanReduce)
                {
                    expr.Reduce();
                }
                return expr;
            }
        }
 
        internal PartConventionBuilder(Predicate<Type> selectType) : base(selectType)
        {
        }
 
        /// <summary>
        /// Select which of the available constructors will be used to instantiate the part.
        /// </summary>
        /// <param name="constructorSelector">Expression that selects a single constructor.</param>
        /// <returns>A part builder allowing further configuration of the part.</returns>
        public PartConventionBuilder<T> SelectConstructor(Expression<Func<ParameterImportConventionBuilder, T>> constructorSelector)
        {
            if (constructorSelector is null)
            {
                throw new ArgumentNullException(nameof(constructorSelector));
            }
 
            var adapter = new ConstructorExpressionAdapter(constructorSelector);
            base.SelectConstructor(adapter.SelectConstructor, adapter.ConfigureConstructorImports);
            return this;
        }
 
        /// <summary>
        /// Select a property on the part to export.
        /// </summary>
        /// <param name="propertySelector">Expression that selects the exported property.</param>
        /// <returns>A part builder allowing further configuration of the part.</returns>
        public PartConventionBuilder<T> ExportProperty(Expression<Func<T, object>> propertySelector)
        {
            return ExportProperty(propertySelector, null);
        }
 
        /// <summary>
        /// Select a property on the part to export.
        /// </summary>
        /// <param name="propertySelector">Expression that selects the exported property.</param>
        /// <param name="exportConfiguration">Action to configure selected properties.</param>
        /// <returns>A part builder allowing further configuration of the part.</returns>
        public PartConventionBuilder<T> ExportProperty(
            Expression<Func<T, object>> propertySelector,
            Action<ExportConventionBuilder> exportConfiguration)
        {
            if (propertySelector is null)
            {
                throw new ArgumentNullException(nameof(propertySelector));
            }
 
            var adapter = new PropertyExpressionAdapter(propertySelector, null, exportConfiguration);
            base.ExportProperties(adapter.VerifyPropertyInfo, adapter.ConfigureExport);
            return this;
        }
 
 
        /// <summary>
        /// Select a property to export from the part.
        /// </summary>
        /// <typeparam name="TContract">Contract type to export.</typeparam>
        /// <param name="propertySelector">Expression to select the matching property.</param>
        /// <returns>A part builder allowing further configuration of the part.</returns>
        public PartConventionBuilder<T> ExportProperty<TContract>(Expression<Func<T, object>> propertySelector)
        {
            return ExportProperty<TContract>(propertySelector, null);
        }
 
        /// <summary>
        /// Select a property to export from the part.
        /// </summary>
        /// <typeparam name="TContract">Contract type to export.</typeparam>
        /// <param name="propertySelector">Expression to select the matching property.</param>
        /// <param name="exportConfiguration">Action to configure selected properties.</param>
        /// <returns>A part builder allowing further configuration of the part.</returns>
        public PartConventionBuilder<T> ExportProperty<TContract>(
            Expression<Func<T, object>> propertySelector,
            Action<ExportConventionBuilder> exportConfiguration)
        {
            if (propertySelector is null)
            {
                throw new ArgumentNullException(nameof(propertySelector));
            }
 
            var adapter = new PropertyExpressionAdapter(propertySelector, null, exportConfiguration);
            base.ExportProperties<TContract>(adapter.VerifyPropertyInfo, adapter.ConfigureExport);
            return this;
        }
 
        /// <summary>
        /// Select a property on the part to import.
        /// </summary>
        /// <param name="propertySelector">Expression selecting the property.</param>
        /// <returns>A part builder allowing further configuration of the part.</returns>
        public PartConventionBuilder<T> ImportProperty(Expression<Func<T, object>> propertySelector)
        {
            return ImportProperty(propertySelector, null);
        }
 
        /// <summary>
        /// Select a property on the part to import.
        /// </summary>
        /// <param name="propertySelector">Expression selecting the property.</param>
        /// <param name="importConfiguration">Action configuring the imported property.</param>
        /// <returns>A part builder allowing further configuration of the part.</returns>
        public PartConventionBuilder<T> ImportProperty(
            Expression<Func<T, object>> propertySelector,
            Action<ImportConventionBuilder> importConfiguration)
        {
            if (propertySelector is null)
            {
                throw new ArgumentNullException(nameof(propertySelector));
            }
 
            var adapter = new PropertyExpressionAdapter(propertySelector, importConfiguration, null);
            base.ImportProperties(adapter.VerifyPropertyInfo, adapter.ConfigureImport);
            return this;
        }
 
        /// <summary>
        /// Select a property on the part to import.
        /// </summary>
        /// <typeparam name="TContract">Contract type to import.</typeparam>
        /// <param name="propertySelector">Expression selecting the property.</param>
        /// <returns>A part builder allowing further configuration of the part.</returns>
        public PartConventionBuilder<T> ImportProperty<TContract>(Expression<Func<T, object>> propertySelector)
        {
            return ImportProperty<TContract>(propertySelector, null);
        }
 
        /// <summary>
        /// Select a property on the part to import.
        /// </summary>
        /// <typeparam name="TContract">Contract type to import.</typeparam>
        /// <param name="propertySelector">Expression selecting the property.</param>
        /// <param name="importConfiguration">Action configuring the imported property.</param>
        /// <returns>A part builder allowing further configuration of the part.</returns>
        public PartConventionBuilder<T> ImportProperty<TContract>(
            Expression<Func<T, object>> propertySelector,
            Action<ImportConventionBuilder> importConfiguration)
        {
            if (propertySelector is null)
            {
                throw new ArgumentNullException(nameof(propertySelector));
            }
 
            var adapter = new PropertyExpressionAdapter(propertySelector, importConfiguration, null);
            base.ImportProperties<TContract>(adapter.VerifyPropertyInfo, adapter.ConfigureImport);
            return this;
        }
 
        /// <summary>
        /// Mark the part as being shared within the entire composition.
        /// </summary>
        /// <returns>A part builder allowing further configuration of the part.</returns>
        public PartConventionBuilder<T> NotifyImportsSatisfied(Expression<Action<T>> methodSelector)
        {
            if (methodSelector is null)
            {
                throw new ArgumentNullException(nameof(methodSelector));
            }
 
            var adapter = new MethodExpressionAdapter(methodSelector);
            base.NotifyImportsSatisfied(adapter.VerifyMethodInfo);
            return this;
        }
    }
}