File: Instance\TaskFactoryWrapper.cs
Web Access
Project: ..\..\..\src\Build\Microsoft.Build.csproj (Microsoft.Build)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Collections.Generic;
using System.Reflection;
using Microsoft.Build.Collections;
using Microsoft.Build.Framework;
using Microsoft.Build.Shared;
 
namespace Microsoft.Build.Execution
{
    /// <summary>
    /// This class packages information about task which has been loaded from a task factory.
    /// </summary>
    internal sealed class TaskFactoryWrapper
    {
        #region Data
 
        private struct PropertyData
        {
            /// <summary>
            /// Cache of names of required properties on this type
            /// </summary>
            public readonly IReadOnlyDictionary<string, string> NamesOfPropertiesWithRequiredAttribute;
 
            /// <summary>
            /// Cache of names of output properties on this type
            /// </summary>
            public readonly IReadOnlyDictionary<string, string> NamesOfPropertiesWithOutputAttribute;
 
            /// <summary>
            /// Cache of names of properties on this type whose names are ambiguous
            /// </summary>
            public readonly IReadOnlyDictionary<string, string> NamesOfPropertiesWithAmbiguousMatches;
 
            /// <summary>
            /// Cache of PropertyInfos for this type
            /// </summary>
            public readonly IReadOnlyDictionary<string, TaskPropertyInfo> PropertyInfoCache;
 
            public PropertyData(
                IReadOnlyDictionary<string, string> namesOfPropertiesWithRequiredAttribute,
                IReadOnlyDictionary<string, string> namesOfPropertiesWithOutputAttribute,
                IReadOnlyDictionary<string, string> namesOfPropertiesWithAmbiguousMatches,
                IReadOnlyDictionary<string, TaskPropertyInfo> propertyInfoCache)
            {
                NamesOfPropertiesWithRequiredAttribute = namesOfPropertiesWithRequiredAttribute;
                NamesOfPropertiesWithOutputAttribute = namesOfPropertiesWithOutputAttribute;
                NamesOfPropertiesWithAmbiguousMatches = namesOfPropertiesWithAmbiguousMatches;
                PropertyInfoCache = propertyInfoCache;
            }
        }
 
        /// <summary>
        /// Factory which is wrapped by the wrapper
        /// </summary>
        private ITaskFactory _taskFactory;
 
        /// <summary>
        /// Wrapper of lazy initializable property data.
        /// </summary>
        private Lazy<PropertyData> _propertyData;
 
        /// <summary>
        /// The name of the task this factory can create.
        /// </summary>
        private string _taskName;
 
        /// <summary>
        /// The set of special parameters that, along with the name, contribute to the identity of
        /// this factory.
        /// </summary>
        private IDictionary<string, string> _factoryIdentityParameters;
 
        #endregion
 
        #region Constructors
 
        /// <summary>
        /// Creates an instance of this class for the given type.
        /// </summary>
        internal TaskFactoryWrapper(ITaskFactory taskFactory, LoadedType taskFactoryLoadInfo, string taskName, IDictionary<string, string> factoryIdentityParameters)
        {
            ErrorUtilities.VerifyThrowArgumentNull(taskFactory);
            ErrorUtilities.VerifyThrowArgumentLength(taskName);
            _taskFactory = taskFactory;
            _taskName = taskName;
            TaskFactoryLoadedType = taskFactoryLoadInfo;
            _factoryIdentityParameters = factoryIdentityParameters;
            _propertyData = new Lazy<PropertyData>(PopulatePropertyInfo);
        }
 
        #endregion
 
        #region Properties
 
        /// <summary>
        /// Load information about the task factory itself
        /// </summary>
        public LoadedType TaskFactoryLoadedType
        {
            get;
            private set;
        }
 
        /// <summary>
        /// The task factory wrapped by the wrapper
        /// </summary>
        public ITaskFactory TaskFactory
        {
            get
            {
                return _taskFactory;
            }
        }
 
        /// <summary>
        /// Gets the list of names of public instance properties that have the required attribute applied.
        /// Caches the result - since it can't change during the build.
        /// </summary>
        /// <returns></returns>
        public IReadOnlyDictionary<string, string> GetNamesOfPropertiesWithRequiredAttribute
        {
            get
            {
                return _propertyData.Value.NamesOfPropertiesWithRequiredAttribute;
            }
        }
 
        /// <summary>
        /// Gets the list of names of public instance properties that have the output attribute applied.
        /// Caches the result - since it can't change during the build.
        /// </summary>
        /// <returns></returns>
        public IReadOnlyDictionary<string, string> GetNamesOfPropertiesWithOutputAttribute
        {
            get
            {
                return _propertyData.Value.NamesOfPropertiesWithOutputAttribute;
            }
        }
 
        /// <summary>
        /// Get the name of the factory wrapped by the wrapper
        /// </summary>
        public string Name
        {
            get
            {
                return _taskFactory.FactoryName;
            }
        }
 
        /// <summary>
        /// The set of task identity parameters that were set on
        /// this particular factory's UsingTask statement.
        /// </summary>
        public IDictionary<string, string> FactoryIdentityParameters
        {
            get
            {
                return _factoryIdentityParameters;
            }
        }
 
        #endregion
 
        #region Methods.
 
        /// <summary>
        /// Get the cached propertyinfo of the given name
        /// </summary>
        /// <param name="propertyName">property name</param>
        /// <returns>PropertyInfo</returns>
        public TaskPropertyInfo? GetProperty(string propertyName)
        {
            TaskPropertyInfo? propertyInfo;
            if (!_propertyData.Value.PropertyInfoCache.TryGetValue(propertyName, out propertyInfo))
            {
                return null;
            }
            else
            {
                if (_propertyData.Value.NamesOfPropertiesWithAmbiguousMatches.ContainsKey(propertyName))
                {
                    // See comment in PopulatePropertyInfoCache
                    throw new AmbiguousMatchException();
                }
 
                return propertyInfo;
            }
        }
 
        /// <summary>
        /// Sets the given property on the task.
        /// </summary>
        internal void SetPropertyValue(ITask task, TaskPropertyInfo property, object value)
        {
            ErrorUtilities.VerifyThrowArgumentNull(task);
            ErrorUtilities.VerifyThrowArgumentNull(property);
 
            IGeneratedTask? generatedTask = task as IGeneratedTask;
            if (generatedTask != null)
            {
                generatedTask.SetPropertyValue(property, value);
            }
            else
            {
                ReflectableTaskPropertyInfo propertyInfo = (ReflectableTaskPropertyInfo)property;
                propertyInfo.Reflection?.SetValue(task, value, null);
            }
        }
 
        /// <summary>
        /// Gets the value of a given property on the given task.
        /// </summary>
        internal object? GetPropertyValue(ITask task, TaskPropertyInfo property)
        {
            ErrorUtilities.VerifyThrowArgumentNull(task);
            ErrorUtilities.VerifyThrowArgumentNull(property);
 
            IGeneratedTask? generatedTask = task as IGeneratedTask;
            if (generatedTask != null)
            {
                return generatedTask.GetPropertyValue(property);
            }
            else
            {
                ReflectableTaskPropertyInfo? propertyInfo = property as ReflectableTaskPropertyInfo;
                if (propertyInfo != null)
                {
                    return propertyInfo.Reflection?.GetValue(task, null);
                }
                else
                {
                    ErrorUtilities.ThrowInternalError("Task does not implement IGeneratedTask and we don't have {0} either.", typeof(ReflectableTaskPropertyInfo).Name);
                    throw new InternalErrorException(); // unreachable
                }
            }
        }
 
        /// <summary>
        /// Determines whether a task with the given name is instantiable by this factory.
        /// </summary>
        /// <param name="taskName">Name of the task.</param>
        /// <returns>
        /// <c>true</c> if this factory can instantiate such a task; otherwise, <c>false</c>.
        /// </returns>
        internal bool IsCreatableByFactory(string taskName)
        {
            return String.Equals(_taskName, taskName, StringComparison.OrdinalIgnoreCase);
        }
 
        /// <summary>
        /// Populate the cache of PropertyInfos for this type
        /// </summary>
        private PropertyData PopulatePropertyInfo()
        {
            Dictionary<string, TaskPropertyInfo>? propertyInfoCache = null;
            Dictionary<string, string>? namesOfPropertiesWithRequiredAttribute = null;
            Dictionary<string, string>? namesOfPropertiesWithOutputAttribute = null;
            Dictionary<string, string>? namesOfPropertiesWithAmbiguousMatches = null;
 
            bool taskTypeImplementsIGeneratedTask = typeof(IGeneratedTask).IsAssignableFrom(_taskFactory.TaskType);
            TaskPropertyInfo[] propertyInfos = _taskFactory.GetTaskParameters();
 
            for (int i = 0; i < propertyInfos.Length; i++)
            {
                // If the task implements IGeneratedTask, we must use the TaskPropertyInfo the factory gives us.
                // Otherwise, we never have to hand the TaskPropertyInfo back to the task or factory, so we replace
                // theirs with one of our own that will allow us to cache reflection data per-property.
                TaskPropertyInfo propertyInfo = propertyInfos[i];
                if (!taskTypeImplementsIGeneratedTask)
                {
                    propertyInfo = new ReflectableTaskPropertyInfo(propertyInfo, _taskFactory.TaskType);
                }
 
                try
                {
                    if (propertyInfoCache == null)
                    {
                        propertyInfoCache = new Dictionary<string, TaskPropertyInfo>(StringComparer.OrdinalIgnoreCase);
                    }
 
                    propertyInfoCache.Add(propertyInfo.Name, propertyInfo);
                }
                catch (ArgumentException)
                {
                    // We have encountered a duplicate entry in our hashtable; if we had used BindingFlags.IgnoreCase this
                    // would have produced an AmbiguousMatchException. In the old code, before this cache existed,
                    // that wouldn't have been thrown unless and until the project actually tried to set this ambiguous parameter.
                    // So rather than fail here, we store a list of ambiguous names and throw later, when one of them
                    // is requested.
                    if (namesOfPropertiesWithAmbiguousMatches == null)
                    {
                        namesOfPropertiesWithAmbiguousMatches = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
                    }
 
                    namesOfPropertiesWithAmbiguousMatches[propertyInfo.Name] = String.Empty;
                }
 
                if (propertyInfos[i].Required)
                {
                    if (namesOfPropertiesWithRequiredAttribute == null)
                    {
                        namesOfPropertiesWithRequiredAttribute = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
                    }
 
                    // we have a require attribute defined, keep a record of that
                    namesOfPropertiesWithRequiredAttribute[propertyInfo.Name] = String.Empty;
                }
 
                if (propertyInfos[i].Output)
                {
                    if (namesOfPropertiesWithOutputAttribute == null)
                    {
                        namesOfPropertiesWithOutputAttribute = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
                    }
 
                    // we have a output attribute defined, keep a record of that
                    namesOfPropertiesWithOutputAttribute[propertyInfo.Name] = String.Empty;
                }
            }
 
            return new PropertyData(
                (IReadOnlyDictionary<string, string>?)namesOfPropertiesWithRequiredAttribute ?? ReadOnlyEmptyDictionary<string, string>.Instance,
                (IReadOnlyDictionary<string, string>?)namesOfPropertiesWithOutputAttribute ?? ReadOnlyEmptyDictionary<string, string>.Instance,
                (IReadOnlyDictionary<string, string>?)namesOfPropertiesWithAmbiguousMatches ?? ReadOnlyEmptyDictionary<string, string>.Instance,
                (IReadOnlyDictionary<string, TaskPropertyInfo>?)propertyInfoCache ?? ReadOnlyEmptyDictionary<string, TaskPropertyInfo>.Instance);
        }
        #endregion
    }
}