1 // Copyright (c) Microsoft. All rights reserved. 2 // Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 //----------------------------------------------------------------------- 4 // </copyright> 5 // <summary>Represents a set of evaluated item definitions all applying to the same item-type.</summary> 6 //----------------------------------------------------------------------- 7 8 using System.Diagnostics; 9 using System.Diagnostics.CodeAnalysis; 10 using Microsoft.Build.Collections; 11 using Microsoft.Build.Construction; 12 using Microsoft.Build.Execution; 13 using Microsoft.Build.Shared; 14 using System.Collections.Generic; 15 using System; 16 using System.Linq; 17 18 namespace Microsoft.Build.Evaluation 19 { 20 /// <summary> 21 /// An evaluated item definition for a particular item-type. 22 /// </summary> 23 /// <remarks> 24 /// Note that these are somewhat different to items. Like items, they can have metadata; like properties, the metadata 25 /// can override each other. So during evaluation all the item definitions for a type are rolled together (assuming 26 /// their conditions are true) to create one ProjectItemDefinition for each type. For this reason, the ProjectItemDefinition 27 /// often will not point to a single ProjectItemDefinitionElement. The metadata within, however, will each point to a single 28 /// ProjectMetadataElement, and these can be added, removed, and modified. 29 /// </remarks> 30 [DebuggerDisplay("{_itemType} #Metadata={MetadataCount}")] 31 public class ProjectItemDefinition : IKeyed, IMetadataTable, IItemDefinition<ProjectMetadata>, IProjectMetadataParent 32 { 33 /// <summary> 34 /// Project that this item definition lives in. 35 /// ProjectItemDefinitions always live in a project. 36 /// Used to evaluate any updates to child metadata. 37 /// </summary> 38 private readonly Project _project; 39 40 /// <summary> 41 /// Item type, for example "Compile", that this item definition applies to 42 /// </summary> 43 private readonly string _itemType; 44 45 /// <summary> 46 /// Collection of metadata that link the XML metadata and instance metadata 47 /// Since evaluation has occurred, this is an unordered collection. 48 /// </summary> 49 private PropertyDictionary<ProjectMetadata> _metadata; 50 51 /// <summary> 52 /// Called by the Evaluator during project evaluation. 53 /// </summary> 54 /// <remarks> 55 /// Assumes that the itemType string originated in a ProjectItemDefinitionElement and therefore 56 /// was already validated. 57 /// </remarks> ProjectItemDefinition(Project project, string itemType)58 internal ProjectItemDefinition(Project project, string itemType) 59 { 60 ErrorUtilities.VerifyThrowInternalNull(project, "project"); 61 ErrorUtilities.VerifyThrowArgumentLength(itemType, "itemType"); 62 63 _project = project; 64 _itemType = itemType; 65 _metadata = null; 66 } 67 68 /// <summary> 69 /// Project that this item lives in. 70 /// ProjectDefinitions always live in a project. 71 /// </summary> 72 [DebuggerBrowsable(DebuggerBrowsableState.Never)] 73 public Project Project 74 { 75 [DebuggerStepThrough] 76 get 77 { return _project; } 78 } 79 80 /// <summary> 81 /// Type of this item definition. 82 /// </summary> 83 [DebuggerBrowsable(DebuggerBrowsableState.Never)] 84 public string ItemType 85 { 86 [DebuggerStepThrough] 87 get 88 { return _itemType; } 89 } 90 91 /// <summary> 92 /// Metadata on the item definition. 93 /// If there is no metadata, returns empty collection. 94 /// This is a read-only collection. 95 /// </summary> 96 [SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods", Justification = "This is a reasonable choice. API review approved")] 97 public IEnumerable<ProjectMetadata> Metadata => _metadata ?? Enumerable.Empty<ProjectMetadata>(); 98 99 /// <summary> 100 /// Count of metadata on the item definition. 101 /// </summary> 102 public int MetadataCount 103 { 104 get { return (_metadata == null) ? 0 : _metadata.Count; } 105 } 106 107 /// <summary> 108 /// Implementation of IKeyed exposing the item type, so these 109 /// can be put in a dictionary conveniently. 110 /// </summary> 111 string IKeyed.Key 112 { 113 get { return ItemType; } 114 } 115 116 /// <summary> 117 /// Get any metadata in the item that has the specified name, 118 /// otherwise returns null 119 /// </summary> 120 [DebuggerStepThrough] GetMetadata(string name)121 public ProjectMetadata GetMetadata(string name) 122 { 123 return (_metadata == null) ? null : _metadata[name]; 124 } 125 126 /// <summary> 127 /// Get the value of any metadata in the item that has the specified 128 /// name, otherwise returns null 129 /// </summary> GetMetadataValue(string name)130 public string GetMetadataValue(string name) 131 { 132 string escapedValue = (this as IMetadataTable).GetEscapedValue(name); 133 134 return (escapedValue == null) ? null : EscapingUtilities.UnescapeAll(escapedValue); 135 } 136 137 /// <summary> 138 /// Sets a new metadata value on the ItemDefinition. 139 /// </summary> 140 /// <remarks>Unevaluated value is assumed to be escaped as necessary</remarks> SetMetadataValue(string name, string unevaluatedValue)141 public ProjectMetadata SetMetadataValue(string name, string unevaluatedValue) 142 { 143 XmlUtilities.VerifyThrowArgumentValidElementName(name); 144 ErrorUtilities.VerifyThrowArgument(!FileUtilities.ItemSpecModifiers.IsItemSpecModifier(name), "ItemSpecModifierCannotBeCustomMetadata", name); 145 ErrorUtilities.VerifyThrowInvalidOperation(XMakeElements.IllegalItemPropertyNames[name] == null, "CannotModifyReservedItemMetadata", name); 146 147 ProjectMetadata metadatum; 148 149 if (_metadata != null) 150 { 151 metadatum = _metadata[name]; 152 153 if (metadatum != null) 154 { 155 Project.VerifyThrowInvalidOperationNotImported(metadatum.Xml.ContainingProject); 156 metadatum.UnevaluatedValue = unevaluatedValue; 157 return metadatum; 158 } 159 } 160 161 // We can't use the item definition that this object came from as a root, as it doesn't map directly 162 // to a single XML element. Instead, add a new one to the project. Best we can do. 163 ProjectItemDefinitionElement itemDefinition = _project.Xml.AddItemDefinition(_itemType); 164 165 ProjectMetadataElement metadatumXml = itemDefinition.AddMetadata(name, unevaluatedValue); 166 167 _metadata = _metadata ?? new PropertyDictionary<ProjectMetadata>(); 168 169 string evaluatedValueEscaped = _project.ExpandMetadataValueBestEffortLeaveEscaped(this, unevaluatedValue, metadatumXml.Location); 170 171 metadatum = new ProjectMetadata(this, metadatumXml, evaluatedValueEscaped, null /* predecessor unknown */); 172 173 _metadata.Set(metadatum); 174 175 return metadatum; 176 } 177 178 #region IItemDefinition Members 179 180 /// <summary> 181 /// Sets a new metadata value on the ItemDefinition. 182 /// This is ONLY called during evaluation and does not affect the XML. 183 /// </summary> SetMetadata(ProjectMetadataElement metadataElement, string evaluatedValue, ProjectMetadata predecessor)184 ProjectMetadata IItemDefinition<ProjectMetadata>.SetMetadata(ProjectMetadataElement metadataElement, string evaluatedValue, ProjectMetadata predecessor) 185 { 186 _metadata = _metadata ?? new PropertyDictionary<ProjectMetadata>(); 187 188 ProjectMetadata metadatum = new ProjectMetadata(this, metadataElement, evaluatedValue, predecessor); 189 _metadata.Set(metadatum); 190 191 return metadatum; 192 } 193 194 #endregion 195 196 #region IMetadataTable Members 197 198 /// <summary> 199 /// Retrieves the value of the named metadatum. 200 /// </summary> 201 /// <param name="name">The metadatum to retrieve.</param> 202 /// <returns>The value, or an empty string if there is none by that name.</returns> IMetadataTable.GetEscapedValue(string name)203 string IMetadataTable.GetEscapedValue(string name) 204 { 205 return ((IMetadataTable)this).GetEscapedValue(null, name); 206 } 207 208 /// <summary> 209 /// Retrieves the value of the named metadatum. 210 /// </summary> 211 /// <param name="specifiedItemType">The type of item.</param> 212 /// <param name="name">The metadatum to retrieve.</param> 213 /// <returns>The value, or an empty string if there is none by that name.</returns> IMetadataTable.GetEscapedValue(string specifiedItemType, string name)214 string IMetadataTable.GetEscapedValue(string specifiedItemType, string name) 215 { 216 return ((IMetadataTable)this).GetEscapedValueIfPresent(specifiedItemType, name) ?? String.Empty; 217 } 218 219 /// <summary> 220 /// Retrieves the value of the named metadatum, or null if it doesn't exist 221 /// </summary> 222 /// <param name="specifiedItemType">The type of item.</param> 223 /// <param name="name">The metadatum to retrieve.</param> 224 /// <returns>The value, or null if there is none by that name.</returns> IMetadataTable.GetEscapedValueIfPresent(string specifiedItemType, string name)225 string IMetadataTable.GetEscapedValueIfPresent(string specifiedItemType, string name) 226 { 227 if (_metadata == null) 228 { 229 return null; 230 } 231 232 if (specifiedItemType == null || String.Equals(_itemType, specifiedItemType, StringComparison.OrdinalIgnoreCase)) 233 { 234 ProjectMetadata metadatum = GetMetadata(name); 235 if (metadatum != null) 236 { 237 return metadatum.EvaluatedValueEscaped; 238 } 239 } 240 241 return null; 242 } 243 244 #endregion 245 } 246 } 247