|
// 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.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Microsoft.Build.BackEnd;
using Microsoft.Build.Collections;
using Microsoft.Build.Construction;
using Microsoft.Build.Evaluation;
using Microsoft.Build.Instance;
using Microsoft.Build.Shared;
#nullable disable
namespace Microsoft.Build.Execution
{
/// <summary>
/// An evaluated item definition for a particular item-type, divested of all references to XML.
/// Immutable.
/// </summary>
[DebuggerDisplay("{_itemType} #Metadata={MetadataCount}")]
public class ProjectItemDefinitionInstance : IKeyed, IMetadataTable, IItemDefinition<ProjectMetadataInstance>, ITranslatable, IItemTypeDefinition
{
/// <summary>
/// Item type, for example "Compile", that this item definition applies to
/// </summary>
private string _itemType;
/// <summary>
/// Collection of metadata that link the XML metadata and instance metadata
/// Since evaluation has occurred, this is an unordered collection.
/// </summary>
private IDictionary<string, ProjectMetadataInstance> _metadata;
/// <summary>
/// Constructs an empty project item definition instance.
/// </summary>
/// <param name="itemType">The type of item this definition object represents.</param>
internal ProjectItemDefinitionInstance(string itemType)
{
ErrorUtilities.VerifyThrowArgumentNull(itemType);
_itemType = itemType;
}
/// <summary>
/// Called when a ProjectInstance is created.
/// </summary>
/// <remarks>
/// Assumes that the itemType string originated in a ProjectItemDefinitionElement and therefore
/// was already validated.
/// </remarks>
internal ProjectItemDefinitionInstance(ProjectItemDefinition itemDefinition)
: this(itemDefinition.ItemType)
{
if (itemDefinition.MetadataCount > 0)
{
var copyOnWriteMetadataDictionary = new CopyOnWritePropertyDictionary<ProjectMetadataInstance>();
IEnumerable<ProjectMetadataInstance> projectMetadataInstances = itemDefinition.Metadata.Select(originalMetadata => new ProjectMetadataInstance(originalMetadata));
copyOnWriteMetadataDictionary.ImportProperties(projectMetadataInstances);
_metadata = copyOnWriteMetadataDictionary;
}
}
/// <summary>
/// Initializes a new instance of the <see cref="ProjectItemDefinitionInstance"/> class.
/// </summary>
/// <param name="itemType">The type of item this definition object represents.</param>
/// <param name="metadata">A (possibly null) collection of the metadata associated with this item definition.</param>
internal ProjectItemDefinitionInstance(string itemType, IDictionary<string, ProjectMetadataInstance> metadata)
: this(itemType)
{
_metadata = metadata;
}
private ProjectItemDefinitionInstance()
{
}
/// <summary>
/// Type of this item definition.
/// </summary>
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
public string ItemType
{
[DebuggerStepThrough]
get
{ return _itemType; }
}
/// <summary>
/// Metadata on the item definition.
/// If there is no metadata, returns empty collection.
/// This is a read-only collection.
/// </summary>
[SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods", Justification = "This is a reasonable choice. API review approved")]
public ICollection<ProjectMetadataInstance> Metadata
{
get
{
if (_metadata == null)
{
return ReadOnlyEmptyCollection<ProjectMetadataInstance>.Instance;
}
return new ReadOnlyCollection<ProjectMetadataInstance>(_metadata.Values);
}
}
/// <summary>
/// Number of pieces of metadata on this item definition.
/// </summary>
public int MetadataCount
{
get { return (_metadata == null) ? 0 : _metadata.Count; }
}
/// <summary>
/// Names of all metadata on this item definition
/// </summary>
public IEnumerable<string> MetadataNames => _metadata == null ? [] : _metadata.Keys;
/// <summary>
/// Implementation of IKeyed exposing the item type, so these
/// can be put in a dictionary conveniently.
/// </summary>
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
string IKeyed.Key
{
get { return ItemType; }
}
/// <summary>
/// Get any metadata in the item that has the specified name,
/// otherwise returns null
/// </summary>
[DebuggerStepThrough]
public ProjectMetadataInstance GetMetadata(string name)
{
return _metadata?[name];
}
#region IMetadataTable Members
/// <summary>
/// Returns the specified metadata.
/// </summary>
/// <param name="name">The metadata name.</param>
/// <returns>The metadata value, or an empty string if none exists.</returns>
string IMetadataTable.GetEscapedValue(string name)
{
return ((IMetadataTable)this).GetEscapedValue(null, name);
}
/// <summary>
/// Returns the metadata for the specified item type.
/// </summary>
/// <param name="specifiedItemType">The item type.</param>
/// <param name="name">The metadata name.</param>
/// <returns>The metadata value, or an empty string if none exists.</returns>
string IMetadataTable.GetEscapedValue(string specifiedItemType, string name)
{
return ((IMetadataTable)this).GetEscapedValueIfPresent(specifiedItemType, name) ?? String.Empty;
}
/// <summary>
/// Returns the metadata for the specified item type.
/// </summary>
/// <param name="specifiedItemType">The item type.</param>
/// <param name="name">The metadata name.</param>
/// <returns>The metadata value, or an null if none exists.</returns>
string IMetadataTable.GetEscapedValueIfPresent(string specifiedItemType, string name)
{
if (specifiedItemType == null || String.Equals(_itemType, specifiedItemType, StringComparison.OrdinalIgnoreCase))
{
ProjectMetadataInstance metadatum = GetMetadata(name);
if (metadatum != null)
{
return metadatum.EvaluatedValueEscaped;
}
}
return null;
}
#endregion
/// <summary>
/// Sets a new metadata value. Called by the evaluator only.
/// Discards predecessor as this information is only useful at design time.
/// </summary>
ProjectMetadataInstance IItemDefinition<ProjectMetadataInstance>.SetMetadata(ProjectMetadataElement xml, string evaluatedValue, ProjectMetadataInstance predecessor)
{
// No mutability check as this is used during creation (evaluation)
_metadata ??= new CopyOnWritePropertyDictionary<ProjectMetadataInstance>();
ProjectMetadataInstance metadatum = new ProjectMetadataInstance(xml.Name, evaluatedValue);
_metadata[xml.Name] = metadatum;
return metadatum;
}
/// <summary>
/// Creates a ProjectItemDefinitionElement representing this instance.
/// </summary>
internal ProjectItemDefinitionElement ToProjectItemDefinitionElement(ProjectElementContainer parent)
{
ProjectItemDefinitionElement element = parent.ContainingProject.CreateItemDefinitionElement(ItemType);
parent.AppendChild(element);
foreach (var kvp in _metadata)
{
ProjectMetadataInstance metadataInstance = kvp.Value;
element.AddMetadata(metadataInstance.Name, metadataInstance.EvaluatedValue);
}
return element;
}
void ITranslatable.Translate(ITranslator translator)
{
translator.Translate(ref _itemType);
translator.TranslateDictionary(ref _metadata, ProjectMetadataInstance.FactoryForDeserialization, CreateMetadataCollection);
}
internal static ProjectItemDefinitionInstance FactoryForDeserialization(ITranslator translator)
{
var instance = new ProjectItemDefinitionInstance();
((ITranslatable)instance).Translate(translator);
return instance;
}
string IItemTypeDefinition.ItemType => _itemType;
private static IDictionary<string, ProjectMetadataInstance> CreateMetadataCollection(int capacity)
{
return new CopyOnWritePropertyDictionary<ProjectMetadataInstance>();
}
}
}
|