| File: Instance\ProjectMetadataInstance.cs | Web Access |
| Project: src\msbuild\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.Diagnostics; using Microsoft.Build.BackEnd; using Microsoft.Build.Collections; using Microsoft.Build.Evaluation; using Microsoft.Build.Framework; using Microsoft.Build.Shared; #nullable disable namespace Microsoft.Build.Execution { /// <summary> /// Wraps an evaluated piece of metadata for build purposes /// Added and removed via methods on the ProjectItemInstance object. /// IMMUTABLE OBJECT. /// </summary> [DebuggerDisplay("{_name}={EvaluatedValue}")] public class ProjectMetadataInstance : IEquatable<ProjectMetadataInstance>, ITranslatable, IMetadatum, IImmutable { /// <summary> /// Name of the metadatum /// </summary> private readonly string _name; /// <summary> /// Evaluated value /// Never null. /// </summary> private readonly string _escapedValue; /// <summary> /// Constructor for metadata. /// Does not allow item spec modifiers. /// Discards the location of the original element. This is not interesting in the Execution world /// as it should never be needed for any subsequent messages, and is just extra bulk. /// IMMUTABLE OBJECT. /// </summary> /// <remarks> /// Not public since the only creation scenario is setting on an item /// </remarks> internal ProjectMetadataInstance(string name, string escapedValue) : this(name, escapedValue, false) { } /// <summary> /// Constructor for metadata. /// Called when a ProjectInstance is created, before the build /// when virtual items are added, and during the build when tasks /// emit items. /// Discards the location of the original element. This is not interesting in the Execution world /// as it should never be needed for any subsequent messages, and is just extra bulk. /// IMMUTABLE OBJECT. /// If the value passed in is null, will be changed to String.Empty. /// </summary> /// <remarks> /// Not public since the only creation scenario is setting on an item /// </remarks> internal ProjectMetadataInstance(string name, string escapedValue, bool allowItemSpecModifiers) { ArgumentException.ThrowIfNullOrEmpty(name); if (allowItemSpecModifiers) { VerifyThrowReservedNameAllowItemSpecModifiers(name); } else { VerifyThrowReservedName(name); } _name = name; _escapedValue = escapedValue ?? String.Empty; } /// <summary> /// Constructor for metadata from a ProjectMetadata. /// Called when a ProjectInstance is created. /// IMMUTABLE OBJECT. /// </summary> internal ProjectMetadataInstance(ProjectMetadata metadatum) : this(metadatum.Name, metadatum.EvaluatedValueEscaped, false) { } /// <summary> /// Private constructor used for serialization /// </summary> private ProjectMetadataInstance(ITranslator translator) { translator.Translate(ref _name); translator.Translate(ref _escapedValue); } /// <summary> /// Name of the metadata /// </summary> /// <remarks> /// This cannot be set, as it is used as the key into /// the item's metadata table. /// </remarks> [DebuggerBrowsable(DebuggerBrowsableState.Never)] public string Name { [DebuggerStepThrough] get { return _name; } } /// <summary> /// Evaluated value of the metadatum. /// Never null. /// </summary> public string EvaluatedValue { [DebuggerStepThrough] get { return EscapingUtilities.UnescapeAll(_escapedValue); } } /// <summary> /// Implementation of IKeyed exposing the metadatum name /// </summary> [DebuggerBrowsable(DebuggerBrowsableState.Never)] string IKeyed.Key { [DebuggerStepThrough] get { return Name; } } /// <summary> /// Implementation of IValued /// </summary> [DebuggerBrowsable(DebuggerBrowsableState.Never)] string IValued.EscapedValue { [DebuggerStepThrough] get { return EvaluatedValueEscaped; } } /// <summary> /// Evaluated and escaped value of the metadata. /// Never null. /// </summary> [DebuggerBrowsable(DebuggerBrowsableState.Never)] internal string EvaluatedValueEscaped { [DebuggerStepThrough] get { return _escapedValue; } } /// <summary> /// String representation handy for tracing /// </summary> public override string ToString() { return $"{_name}={_escapedValue}"; } #region INodePacketTranslatable Members /// <summary> /// Reads or writes the packet to the serializer. /// </summary> void ITranslatable.Translate(ITranslator translator) { // Read implementation is directly in the constructor so that fields can be read-only Assumed.Equal(translator.Mode, TranslationDirection.WriteToStream, "write only"); string mutableName = _name; string mutableValue = _escapedValue; translator.Translate(ref mutableName); translator.Translate(ref mutableValue); } #endregion #region IEquatable<ProjectMetadataInstance> Members /// <summary> /// Compares this metadata to another for equivalence. /// </summary> /// <param name="other">The other metadata</param> /// <returns>True if they are equivalent, false otherwise.</returns> bool IEquatable<ProjectMetadataInstance>.Equals(ProjectMetadataInstance other) { if (Object.ReferenceEquals(this, other)) { return true; } if (other == null) { return false; } return _escapedValue == other._escapedValue && String.Equals(_name, other._name, StringComparison.OrdinalIgnoreCase); } #endregion /// <summary> /// Deep clone the metadata /// Strings are immutable (copy on write) so there is no work to do. /// Allows built-in metadata names, as they are still valid on the new metadatum. /// </summary> /// <returns>A new metadata instance.</returns> public ProjectMetadataInstance DeepClone() { return new ProjectMetadataInstance(_name, _escapedValue, true /* allow built-in metadata names */); } /// <summary> /// Factory for serialization. /// </summary> internal static ProjectMetadataInstance FactoryForDeserialization(ITranslator translator) { return new ProjectMetadataInstance(translator); } /// <summary> /// Verifies that the name is not a reserved name. /// </summary> /// <remarks> /// Exposed so project items can validate metadata names without allocating ProjectMetadataInstance. /// </remarks> internal static void VerifyThrowReservedName(string name) { // PERF: This sequence of checks is faster than a full HashSet lookup since finding a match is an error case. // Otherwise, many keys would still match to a bucket and begin a string comparison. VerifyThrowReservedNameAllowItemSpecModifiers(name); if (ItemSpecModifiers.IsItemSpecModifier(name)) { ErrorUtilities.ThrowArgument("OM_ReservedName", name); } } /// <summary> /// Verifies that the name is not a reserved name, while allowing item spec modifiers. /// </summary> /// <remarks> /// Exposed so project items can validate metadata names without allocating ProjectMetadataInstance. /// </remarks> internal static void VerifyThrowReservedNameAllowItemSpecModifiers(string name) { ArgumentException.ThrowIfNullOrEmpty(name); foreach (string reservedName in XMakeElements.ReservedItemNames) { if (reservedName.Length == name.Length && reservedName[0] == name[0]) { ErrorUtilities.VerifyThrowArgument(!StringComparer.Ordinal.Equals(reservedName, name), "OM_ReservedName", name); } } } } }