|
// 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.Collections;
using Microsoft.Build.Construction;
using Microsoft.Build.ObjectModelRemoting;
using Microsoft.Build.Shared;
#nullable disable
namespace Microsoft.Build.Evaluation
{
/// <summary>
/// An evaluated item definition for a particular item-type.
/// </summary>
/// <remarks>
/// Note that these are somewhat different to items. Like items, they can have metadata; like properties, the metadata
/// can override each other. So during evaluation all the item definitions for a type are rolled together (assuming
/// their conditions are true) to create one ProjectItemDefinition for each type. For this reason, the ProjectItemDefinition
/// often will not point to a single ProjectItemDefinitionElement. The metadata within, however, will each point to a single
/// ProjectMetadataElement, and these can be added, removed, and modified.
/// </remarks>
[DebuggerDisplay("{_itemType} #Metadata={MetadataCount}")]
public class ProjectItemDefinition : IKeyed, IMetadataTable, IItemDefinition<ProjectMetadata>, IProjectMetadataParent, IItemTypeDefinition
{
/// <summary>
/// Project that this item definition lives in.
/// ProjectItemDefinitions always live in a project.
/// Used to evaluate any updates to child metadata.
/// </summary>
private readonly Project _project;
/// <summary>
/// Item type, for example "Compile", that this item definition applies to
/// </summary>
private readonly 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 PropertyDictionary<ProjectMetadata> _metadata;
/// <summary>
/// Called by the Evaluator during project evaluation.
/// </summary>
/// <remarks>
/// Assumes that the itemType string originated in a ProjectItemDefinitionElement and therefore
/// was already validated.
/// </remarks>
internal ProjectItemDefinition(Project project, string itemType)
{
ErrorUtilities.VerifyThrowInternalNull(project);
ErrorUtilities.VerifyThrowArgumentLength(itemType);
_project = project;
_itemType = itemType;
_metadata = null;
}
internal virtual ProjectItemDefinitionLink Link => null;
/// <summary>
/// Project that this item lives in.
/// ProjectDefinitions always live in a project.
/// </summary>
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
public Project Project
{
[DebuggerStepThrough]
get
{ return _project; }
}
/// <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 IEnumerable<ProjectMetadata> Metadata => Link != null ? Link.Metadata : _metadata ?? [];
/// <summary>
/// Count of metadata on the item definition.
/// </summary>
public int MetadataCount
{
get { return Link != null ? Link.Metadata.Count : (_metadata == null) ? 0 : _metadata.Count; }
}
/// <summary>
/// Implementation of IKeyed exposing the item type, so these
/// can be put in a dictionary conveniently.
/// </summary>
string IKeyed.Key
{
get { return ItemType; }
}
/// <summary>
/// Get any metadata in the item that has the specified name,
/// otherwise returns null
/// </summary>
[DebuggerStepThrough]
public ProjectMetadata GetMetadata(string name)
{
return Link != null ? Link.GetMetadata(name) : _metadata?[name];
}
/// <summary>
/// Get the value of any metadata in the item that has the specified
/// name, otherwise returns null
/// </summary>
public string GetMetadataValue(string name)
{
if (Link != null)
{
return Link.GetMetadataValue(name);
}
string escapedValue = (this as IMetadataTable).GetEscapedValue(name);
return (escapedValue == null) ? null : EscapingUtilities.UnescapeAll(escapedValue);
}
/// <summary>
/// Sets a new metadata value on the ItemDefinition.
/// </summary>
/// <remarks>Unevaluated value is assumed to be escaped as necessary</remarks>
public ProjectMetadata SetMetadataValue(string name, string unevaluatedValue)
{
if (Link != null)
{
return Link.SetMetadataValue(name, unevaluatedValue);
}
XmlUtilities.VerifyThrowArgumentValidElementName(name);
ErrorUtilities.VerifyThrowArgument(!FileUtilities.ItemSpecModifiers.IsItemSpecModifier(name), "ItemSpecModifierCannotBeCustomMetadata", name);
ErrorUtilities.VerifyThrowInvalidOperation(!XMakeElements.ReservedItemNames.Contains(name), "CannotModifyReservedItemMetadata", name);
ProjectMetadata metadatum;
if (_metadata != null)
{
metadatum = _metadata[name];
if (metadatum != null)
{
Project.VerifyThrowInvalidOperationNotImported(metadatum.Xml.ContainingProject);
metadatum.UnevaluatedValue = unevaluatedValue;
return metadatum;
}
}
// We can't use the item definition that this object came from as a root, as it doesn't map directly
// to a single XML element. Instead, add a new one to the project. Best we can do.
ProjectItemDefinitionElement itemDefinition = _project.Xml.AddItemDefinition(_itemType);
ProjectMetadataElement metadatumXml = itemDefinition.AddMetadata(name, unevaluatedValue);
_metadata ??= new PropertyDictionary<ProjectMetadata>();
string evaluatedValueEscaped = _project.ExpandMetadataValueBestEffortLeaveEscaped(this, unevaluatedValue, metadatumXml.Location);
metadatum = new ProjectMetadata(this, metadatumXml, evaluatedValueEscaped, null /* predecessor unknown */);
_metadata.Set(metadatum);
return metadatum;
}
#region IItemDefinition Members
/// <summary>
/// Sets a new metadata value on the ItemDefinition.
/// This is ONLY called during evaluation and does not affect the XML.
/// </summary>
ProjectMetadata IItemDefinition<ProjectMetadata>.SetMetadata(ProjectMetadataElement metadataElement, string evaluatedValue, ProjectMetadata predecessor)
{
_metadata ??= new PropertyDictionary<ProjectMetadata>();
ProjectMetadata metadatum = new ProjectMetadata(this, metadataElement, evaluatedValue, predecessor);
_metadata.Set(metadatum);
return metadatum;
}
#endregion
#region IMetadataTable Members
/// <summary>
/// Retrieves the value of the named metadatum.
/// </summary>
/// <param name="name">The metadatum to retrieve.</param>
/// <returns>The value, or an empty string if there is none by that name.</returns>
string IMetadataTable.GetEscapedValue(string name)
{
return ((IMetadataTable)this).GetEscapedValue(null, name);
}
/// <summary>
/// Retrieves the value of the named metadatum.
/// </summary>
/// <param name="specifiedItemType">The type of item.</param>
/// <param name="name">The metadatum to retrieve.</param>
/// <returns>The value, or an empty string if there is none by that name.</returns>
string IMetadataTable.GetEscapedValue(string specifiedItemType, string name)
{
return ((IMetadataTable)this).GetEscapedValueIfPresent(specifiedItemType, name) ?? String.Empty;
}
/// <summary>
/// Retrieves the value of the named metadatum, or null if it doesn't exist
/// </summary>
/// <param name="specifiedItemType">The type of item.</param>
/// <param name="name">The metadatum to retrieve.</param>
/// <returns>The value, or null if there is none by that name.</returns>
string IMetadataTable.GetEscapedValueIfPresent(string specifiedItemType, string name)
{
if (_metadata == null)
{
return null;
}
if (specifiedItemType == null || String.Equals(_itemType, specifiedItemType, StringComparison.OrdinalIgnoreCase))
{
ProjectMetadata metadatum = GetMetadata(name);
if (metadatum != null)
{
return metadatum.EvaluatedValueEscaped;
}
}
return null;
}
#endregion
}
}
|