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 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.Evaluation;
13 using Microsoft.Build.Execution;
14 using Microsoft.Build.Shared;
15 using System.Collections.Generic;
16 using System;
17 using Microsoft.Build.BackEnd;
18 
19 namespace Microsoft.Build.Execution
20 {
21     /// <summary>
22     /// An evaluated item definition for a particular item-type, divested of all references to XML.
23     /// Immutable.
24     /// </summary>
25     [DebuggerDisplay("{_itemType} #Metadata={MetadataCount}")]
26     public class ProjectItemDefinitionInstance : IKeyed, IMetadataTable, IItemDefinition<ProjectMetadataInstance>, INodePacketTranslatable
27     {
28         /// <summary>
29         /// Item type, for example "Compile", that this item definition applies to
30         /// </summary>
31         private string _itemType;
32 
33         /// <summary>
34         /// Collection of metadata that link the XML metadata and instance metadata
35         /// Since evaluation has occurred, this is an unordered collection.
36         /// Is never null or empty.
37         /// </summary>
38         private CopyOnWritePropertyDictionary<ProjectMetadataInstance> _metadata;
39 
40         /// <summary>
41         /// Constructs an empty project item definition instance.
42         /// </summary>
43         /// <param name="projectInstance">The project instance to which this item definition belongs.</param>
44         /// <param name="itemType">The type of item this definition object represents.</param>
ProjectItemDefinitionInstance(ProjectInstance projectInstance, string itemType)45         internal ProjectItemDefinitionInstance(ProjectInstance projectInstance, string itemType)
46         {
47             ErrorUtilities.VerifyThrowArgumentNull(itemType, "itemType");
48 
49             _itemType = itemType;
50         }
51 
52         /// <summary>
53         /// Called when a ProjectInstance is created.
54         /// </summary>
55         /// <remarks>
56         /// Assumes that the itemType string originated in a ProjectItemDefinitionElement and therefore
57         /// was already validated.
58         /// </remarks>
ProjectItemDefinitionInstance(ProjectInstance projectInstance, ProjectItemDefinition itemDefinition)59         internal ProjectItemDefinitionInstance(ProjectInstance projectInstance, ProjectItemDefinition itemDefinition)
60             : this(projectInstance, itemDefinition.ItemType)
61         {
62             if (itemDefinition.MetadataCount > 0)
63             {
64                 _metadata = new CopyOnWritePropertyDictionary<ProjectMetadataInstance>(itemDefinition.MetadataCount);
65             }
66 
67             foreach (ProjectMetadata originalMetadata in itemDefinition.Metadata)
68             {
69                 _metadata.Set(new ProjectMetadataInstance(originalMetadata));
70             }
71         }
72 
ProjectItemDefinitionInstance()73         private ProjectItemDefinitionInstance()
74         {
75         }
76 
77         /// <summary>
78         /// Type of this item definition.
79         /// </summary>
80         [DebuggerBrowsable(DebuggerBrowsableState.Never)]
81         public string ItemType
82         {
83             [DebuggerStepThrough]
84             get
85             { return _itemType; }
86         }
87 
88         /// <summary>
89         /// Metadata on the item definition.
90         /// If there is no metadata, returns empty collection.
91         /// This is a read-only collection.
92         /// </summary>
93         [SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods", Justification = "This is a reasonable choice. API review approved")]
94         public ICollection<ProjectMetadataInstance> Metadata
95         {
96             get
97             {
98                 if (_metadata == null)
99                 {
100                     return ReadOnlyEmptyCollection<ProjectMetadataInstance>.Instance;
101                 }
102 
103                 return new ReadOnlyCollection<ProjectMetadataInstance>(_metadata);
104             }
105         }
106 
107         /// <summary>
108         /// Number of pieces of metadata on this item definition.
109         /// </summary>
110         public int MetadataCount
111         {
112             get { return (_metadata == null) ? 0 : _metadata.Count; }
113         }
114 
115         /// <summary>
116         /// Names of all metadata on this item definition
117         /// </summary>
118         public IEnumerable<string> MetadataNames
119         {
120             get
121             {
122                 if (_metadata == null)
123                 {
124                     yield break;
125                 }
126 
127                 foreach (ProjectMetadataInstance metadatum in _metadata)
128                 {
129                     yield return metadatum.Name;
130                 }
131             }
132         }
133 
134         /// <summary>
135         /// Implementation of IKeyed exposing the item type, so these
136         /// can be put in a dictionary conveniently.
137         /// </summary>
138         [DebuggerBrowsable(DebuggerBrowsableState.Never)]
139         string IKeyed.Key
140         {
141             get { return ItemType; }
142         }
143 
144         /// <summary>
145         /// Get any metadata in the item that has the specified name,
146         /// otherwise returns null
147         /// </summary>
148         [DebuggerStepThrough]
GetMetadata(string name)149         public ProjectMetadataInstance GetMetadata(string name)
150         {
151             return (_metadata == null) ? null : _metadata[name];
152         }
153 
154         #region IMetadataTable Members
155 
156         /// <summary>
157         /// Returns the specified metadata.
158         /// </summary>
159         /// <param name="name">The metadata name.</param>
160         /// <returns>The metadata value, or an empty string if none exists.</returns>
IMetadataTable.GetEscapedValue(string name)161         string IMetadataTable.GetEscapedValue(string name)
162         {
163             return ((IMetadataTable)this).GetEscapedValue(null, name);
164         }
165 
166         /// <summary>
167         /// Returns the metadata for the specified item type.
168         /// </summary>
169         /// <param name="specifiedItemType">The item type.</param>
170         /// <param name="name">The metadata name.</param>
171         /// <returns>The metadata value, or an empty string if none exists.</returns>
IMetadataTable.GetEscapedValue(string specifiedItemType, string name)172         string IMetadataTable.GetEscapedValue(string specifiedItemType, string name)
173         {
174             return ((IMetadataTable)this).GetEscapedValueIfPresent(specifiedItemType, name) ?? String.Empty;
175         }
176 
177         /// <summary>
178         /// Returns the metadata for the specified item type.
179         /// </summary>
180         /// <param name="specifiedItemType">The item type.</param>
181         /// <param name="name">The metadata name.</param>
182         /// <returns>The metadata value, or an null if none exists.</returns>
IMetadataTable.GetEscapedValueIfPresent(string specifiedItemType, string name)183         string IMetadataTable.GetEscapedValueIfPresent(string specifiedItemType, string name)
184         {
185             if (specifiedItemType == null || String.Equals(_itemType, specifiedItemType, StringComparison.OrdinalIgnoreCase))
186             {
187                 ProjectMetadataInstance metadatum = GetMetadata(name);
188                 if (metadatum != null)
189                 {
190                     return metadatum.EvaluatedValueEscaped;
191                 }
192             }
193 
194             return null;
195         }
196 
197         #endregion
198 
199         /// <summary>
200         /// Sets a new metadata value.  Called by the evaluator only.
201         /// Discards predecessor as this information is only useful at design time.
202         /// </summary>
SetMetadata(ProjectMetadataElement xml, string evaluatedValue, ProjectMetadataInstance predecessor)203         ProjectMetadataInstance IItemDefinition<ProjectMetadataInstance>.SetMetadata(ProjectMetadataElement xml, string evaluatedValue, ProjectMetadataInstance predecessor)
204         {
205             // No mutability check as this is used during creation (evaluation)
206             _metadata = _metadata ?? new CopyOnWritePropertyDictionary<ProjectMetadataInstance>();
207 
208             ProjectMetadataInstance metadatum = new ProjectMetadataInstance(xml.Name, evaluatedValue);
209             _metadata[xml.Name] = metadatum;
210 
211             return metadatum;
212         }
213 
214         /// <summary>
215         /// Creates a ProjectItemDefinitionElement representing this instance.
216         /// </summary>
ToProjectItemDefinitionElement(ProjectElementContainer parent)217         internal ProjectItemDefinitionElement ToProjectItemDefinitionElement(ProjectElementContainer parent)
218         {
219             ProjectItemDefinitionElement element = parent.ContainingProject.CreateItemDefinitionElement(ItemType);
220             parent.AppendChild(element);
221             foreach (ProjectMetadataInstance metadataInstance in _metadata)
222             {
223                 element.AddMetadata(metadataInstance.Name, metadataInstance.EvaluatedValue);
224             }
225 
226             return element;
227         }
228 
INodePacketTranslatable.Translate(INodePacketTranslator translator)229         void INodePacketTranslatable.Translate(INodePacketTranslator translator)
230         {
231             translator.Translate(ref _itemType);
232             translator.TranslateDictionary(ref _metadata, ProjectMetadataInstance.FactoryForDeserialization);
233         }
234 
FactoryForDeserialization(INodePacketTranslator translator)235         internal static ProjectItemDefinitionInstance FactoryForDeserialization(INodePacketTranslator translator)
236         {
237             var instance = new ProjectItemDefinitionInstance();
238             ((INodePacketTranslatable) instance).Translate(translator);
239 
240             return instance;
241         }
242     }
243 }
244