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 using System;
5 using System.Collections;
6 using System.Collections.Generic;
7 using System.Text;
8 using System.Linq;
9 using Microsoft.Build.Shared;
10 using System.Diagnostics;
11 using System.Threading;
12 using Microsoft.Build.Evaluation;
13 using Microsoft.Build.Framework;
14 using Microsoft.Build.Execution;
15 using Microsoft.Build.Collections;
16 
17 using ReservedPropertyNames = Microsoft.Build.Internal.ReservedPropertyNames;
18 
19 namespace Microsoft.Build.BackEnd
20 {
21     using ItemsMetadataUpdateDictionary = Dictionary<ProjectItemInstance, Lookup.MetadataModifications>;
22     using ItemTypeToItemsMetadataUpdateDictionary = Dictionary<string, Dictionary<ProjectItemInstance, Lookup.MetadataModifications>>;
23 
24     /// <summary>
25     /// Contains a list of item and property collections, optimized to allow
26     ///     - very fast "cloning"
27     ///     - quick lookups
28     ///     - scoping down of item subsets in nested scopes (useful for batches)
29     ///     - isolation of adds, removes, modifies, and property sets inside nested scopes
30     ///
31     /// When retrieving the item group for an item type, each table is consulted in turn,
32     /// starting with the primary table (the "top" or "innermost" table), until a table is found that has an entry for that type.
33     /// When an entry is found, it is returned without looking deeper.
34     /// This makes it possible to let callers see only a subset of items without affecting or cloning the original item groups,
35     /// by populating a scope with item groups that are empty or contain subsets of items in lower scopes.
36     ///
37     /// Instances of this class can be cloned with Clone() to share between batches.
38     ///
39     /// When EnterScope() is called, a fresh primary table is inserted, and all adds and removes will be invisible to
40     /// any clones made before the scope was entered and anyone who has access to item groups in lower tables.
41     ///
42     /// When LeaveScope() is called, the primary tables are merged into the secondary tables, and the primary tables are discarded.
43     /// This makes the adds and removes in the primary tables visible to clones made during the previous scope.
44     ///
45     /// Scopes can be populated (before Adds, Removes, and Lookups) using PopulateWithItem(). This reduces the set of items of a particular
46     /// type that are visible in a scope, because lookups of items of this type will stop at this level and see the subset, rather than the
47     /// larger set in a scope below.
48     ///
49     /// Items can be added or removed by calling AddNewItem() and RemoveItem(). Only the primary level is modified.
50     /// When items are added or removed they enter into a primary table exclusively for adds or removes, instead of the main primary table.
51     /// This allows the adds and removes to be applied to the scope below on LeaveScope(). Even when LeaveScope() is called, the adds and removes
52     /// stay in their separate add and remove tables: if they were applied to a main table, they could truncate the downward traversal performed by lookups
53     /// and hide items in a lower main table. Only on the final call of LeaveScope() can all adds and removes be applied to the outermost table, i.e., the project.
54     ///
55     /// Much the same applies to properties.
56     ///
57     /// For sensible semantics, only the current primary scope can be modified at any point.
58     /// </summary>
59     internal class Lookup : IPropertyProvider<ProjectPropertyInstance>, IItemProvider<ProjectItemInstance>
60     {
61         #region Fields
62 
63         /// <summary>
64         /// Ordered list of scope used for lookup.
65         /// Each scope contains multiple tables:
66         ///  - the main item table (populated with subsets of lists, in order to create batches)
67         ///  - the add table (items that have been added during execution)
68         ///  - the remove table (items that have been removed during execution)
69         ///  - the modify table (item metadata modifications)
70         ///  - the main property table (populated with properties that are visible in this scope)
71         ///  - the property set table (changes made to properties)
72         /// All have to be consulted to find the items and properties available in the current scope.
73         /// We have to keep them separate, because the adds and removes etc need to be applied to the table
74         /// below when we leave a scope.
75         /// </summary>
76         private LinkedList<Lookup.Scope> _lookupScopes = new LinkedList<Lookup.Scope>();
77 
78         /// <summary>
79         /// When we are asked for all the items of a certain type using the GetItems() method, we may have to handle items
80         /// that have been modified earlier with ModifyItems(). These pending modifications can't be applied immediately to
81         /// the item because that would affect other batches. Instead we clone the item, apply the modification, and hand that over.
82         /// The problem is that later we might get asked to remove or modify that item. We want to make sure that we record that as
83         /// a remove or modify of the real item, not the clone we handed over. So we keep a lookup of (clone, original) to consult.
84         /// </summary>
85         private Dictionary<ProjectItemInstance, ProjectItemInstance> _cloneTable;
86 
87         /// <summary>
88         /// A dictionary of named values for debugger display only. If
89         /// not debugging, this should be null.
90         /// </summary>
91         private IDictionary<string, object> _globalsForDebugging;
92 
93         #endregion
94 
95         #region Constructors
96 
97         /// <summary>
98         /// Construct a lookup over specified items and properties.
99         /// Accept a dictionary of named values for debugger display only. If
100         /// not debugging, this should be null.
101         /// </summary>
Lookup(ItemDictionary<ProjectItemInstance> projectItems, PropertyDictionary<ProjectPropertyInstance> properties, IDictionary<string, object> globalsForDebugging)102         internal Lookup(ItemDictionary<ProjectItemInstance> projectItems, PropertyDictionary<ProjectPropertyInstance> properties, IDictionary<string, object> globalsForDebugging)
103         {
104             ErrorUtilities.VerifyThrowInternalNull(projectItems, "projectItems");
105             ErrorUtilities.VerifyThrowInternalNull(properties, "properties");
106 
107             Lookup.Scope scope = new Lookup.Scope(this, "Lookup()", projectItems, properties);
108             _lookupScopes.AddFirst(scope);
109 
110             _globalsForDebugging = globalsForDebugging;
111         }
112 
113         /// <summary>
114         /// Copy constructor (called via Clone() - clearer semantics)
115         /// </summary>
Lookup(Lookup that)116         private Lookup(Lookup that)
117         {
118             // Add the same tables from the original
119             foreach (Lookup.Scope scope in that._lookupScopes)
120             {
121                 _lookupScopes.AddLast(scope);
122             }
123 
124             // Clones need to share an (item)clone table; the batching engine asks for items from the lookup,
125             // then populates buckets with them, which have clone lookups.
126             _cloneTable = that._cloneTable;
127 
128             _globalsForDebugging = that._globalsForDebugging;
129         }
130 
131         #endregion
132 
133         #region Properties
134 
135         // Convenience private properties
136         // "Primary" is the "top" or "innermost" scope
137         // "Secondary" is the next from the top.
138         private ItemDictionary<ProjectItemInstance> PrimaryTable
139         {
140             get { return _lookupScopes.First.Value.Items; }
141             set { _lookupScopes.First.Value.Items = value; }
142         }
143 
144         private ItemDictionary<ProjectItemInstance> PrimaryAddTable
145         {
146             get { return _lookupScopes.First.Value.Adds; }
147             set { _lookupScopes.First.Value.Adds = value; }
148         }
149 
150         private ItemDictionary<ProjectItemInstance> PrimaryRemoveTable
151         {
152             get { return _lookupScopes.First.Value.Removes; }
153             set { _lookupScopes.First.Value.Removes = value; }
154         }
155 
156         private ItemTypeToItemsMetadataUpdateDictionary PrimaryModifyTable
157         {
158             get { return _lookupScopes.First.Value.Modifies; }
159             set { _lookupScopes.First.Value.Modifies = value; }
160         }
161 
162         private PropertyDictionary<ProjectPropertyInstance> PrimaryPropertySets
163         {
164             get { return _lookupScopes.First.Value.PropertySets; }
165             set { _lookupScopes.First.Value.PropertySets = value; }
166         }
167 
168         private ItemDictionary<ProjectItemInstance> SecondaryTable
169         {
170             get { return _lookupScopes.First.Next.Value.Items; }
171             set { _lookupScopes.First.Next.Value.Items = value; }
172         }
173 
174         private ItemDictionary<ProjectItemInstance> SecondaryAddTable
175         {
176             get { return _lookupScopes.First.Next.Value.Adds; }
177             set { _lookupScopes.First.Next.Value.Adds = value; }
178         }
179 
180         private ItemDictionary<ProjectItemInstance> SecondaryRemoveTable
181         {
182             get { return _lookupScopes.First.Next.Value.Removes; }
183             set { _lookupScopes.First.Next.Value.Removes = value; }
184         }
185 
186         private ItemTypeToItemsMetadataUpdateDictionary SecondaryModifyTable
187         {
188             get { return _lookupScopes.First.Next.Value.Modifies; }
189             set { _lookupScopes.First.Next.Value.Modifies = value; }
190         }
191 
192         private PropertyDictionary<ProjectPropertyInstance> SecondaryProperties
193         {
194             get { return _lookupScopes.First.Next.Value.Properties; }
195             set { _lookupScopes.First.Next.Value.Properties = value; }
196         }
197 
198         private PropertyDictionary<ProjectPropertyInstance> SecondaryPropertySets
199         {
200             get { return _lookupScopes.First.Next.Value.PropertySets; }
201             set { _lookupScopes.First.Next.Value.PropertySets = value; }
202         }
203 
204         /// <summary>
205         /// A dictionary of named values for debugger display only. If
206         /// not debugging, this should be null.
207         /// </summary>
208         internal IDictionary<string, object> GlobalsForDebugging
209         {
210             get { return _globalsForDebugging; }
211         }
212 
213         #endregion
214 
215         #region Internal Methods
216 
217         /// <summary>
218         /// Compares the primary property sets of the passed in lookups to determine if there are properties which are shared between
219         /// the lookups. We find these shared property names because this indicates that the current Lookup is overriding the property value of another Lookup
220         /// When an override is detected a messages is generated to inform the users that the property is being changed between batches
221         /// </summary>
222         /// <returns>array or error messages to log </returns>
GetPropertyOverrideMessages(Dictionary<string, string> lookupHash)223         internal List<string> GetPropertyOverrideMessages(Dictionary<string, string> lookupHash)
224         {
225             List<string> errorMessages = null;
226             // For each batch lookup list we need to compare the property items to see if they have already been set
227             if (PrimaryPropertySets != null)
228             {
229                 foreach (ProjectPropertyInstance property in PrimaryPropertySets)
230                 {
231                     if (String.Equals(property.Name, ReservedPropertyNames.lastTaskResult, StringComparison.OrdinalIgnoreCase))
232                     {
233                         continue;
234                     }
235 
236                     string propertyName = property.Name;
237                     // If the hash contains the property name, output a messages that displays the previous property value and the new property value
238                     if (lookupHash.ContainsKey(propertyName))
239                     {
240                         if (errorMessages == null)
241                         {
242                             errorMessages = new List<string>();
243                         }
244                         errorMessages.Add(ResourceUtilities.FormatResourceString("PropertyOutputOverridden", propertyName, EscapingUtilities.UnescapeAll(lookupHash[propertyName]), property.EvaluatedValue));
245                     }
246 
247                     // Set the value of the hash to the new property value
248                     // PERF: we store the EvaluatedValueEscaped here to avoid unnecessary unescaping (the value is stored
249                     // escaped in the property)
250                     lookupHash[propertyName] = ((IProperty)property).EvaluatedValueEscaped;
251                 }
252             }
253 
254             return errorMessages;
255         }
256 
257         /// <summary>
258         /// Clones this object, to create another one with its own list, but the same contents.
259         /// Then the clone can enter scope and have its own fresh primary list without affecting the other object.
260         /// </summary>
Clone()261         internal Lookup Clone()
262         {
263             return new Lookup(this);
264         }
265 
266         /// <summary>
267         /// Enters the scope using the specified description.
268         /// Callers keep the scope in order to pass it to <see cref="LeaveScope">LeaveScope</see>.
269         /// </summary>
EnterScope(string description)270         internal Lookup.Scope EnterScope(string description)
271         {
272             // We don't create the tables unless we need them
273             Scope scope = new Scope(this, description, null, null);
274             _lookupScopes.AddFirst(scope);
275             return scope;
276         }
277 
278         /// <summary>
279         /// Leaves the specified scope, which must be the active one.
280         /// Moves all tables up one: the tertiary table becomes the secondary table, and so on. The primary
281         /// and secondary table are merged. This has the effect of "applying" the adds applied to the primary
282         /// table into the secondary table.
283         /// </summary>
LeaveScope(Lookup.Scope scopeToLeave)284         private void LeaveScope(Lookup.Scope scopeToLeave)
285         {
286             ErrorUtilities.VerifyThrow(_lookupScopes.Count >= 2, "Too many calls to Leave().");
287             ErrorUtilities.VerifyThrow(Object.ReferenceEquals(scopeToLeave, _lookupScopes.First.Value), "Attempting to leave with scope '{0}' but scope '{1}' is on top of the stack.", scopeToLeave.Description, _lookupScopes.First.Value.Description);
288 
289             // Our lookup works by stopping the first time it finds an item group of the appropriate type.
290             // So we can't apply an add directly into the table below because that could create a new group
291             // of that type, which would cause the next lookup to stop there and miss any existing items in a table below.
292             // Instead we keep adds stored separately until we're leaving the very last scope. Until then
293             // we only move adds down into the next add table below, and when we lookup we consider both tables.
294             // Same applies to removes.
295             if (_lookupScopes.Count == 2)
296             {
297                 MergeScopeIntoLastScope();
298             }
299             else
300             {
301                 MergeScopeIntoNotLastScope();
302             }
303 
304             // Let go of our pointer into the clone table; we assume we won't need it after leaving scope and want to save memory.
305             // This is an assumption on IntrinsicTask, that it won't ask to remove or modify a clone in a higher scope than it was handed out in.
306             // We mustn't call cloneTable.Clear() because other clones of this lookup may still be using it. When the last lookup clone leaves scope,
307             // the table will be collected.
308             _cloneTable = null;
309 
310             // Move all tables up one, discarding the primary tables
311             _lookupScopes.RemoveFirst();
312         }
313 
314         /// <summary>
315         /// Leaving an arbitrary scope, just merging all the adds, removes, modifies, and sets into the scope below.
316         /// </summary>
MergeScopeIntoNotLastScope()317         private void MergeScopeIntoNotLastScope()
318         {
319             // Move all the adds down
320             if (PrimaryAddTable != null)
321             {
322                 if (SecondaryAddTable == null)
323                 {
324                     SecondaryAddTable = PrimaryAddTable;
325                 }
326                 else
327                 {
328                     SecondaryAddTable.ImportItems(PrimaryAddTable);
329                 }
330             }
331 
332             // Move all the removes down
333             if (PrimaryRemoveTable != null)
334             {
335                 if (SecondaryRemoveTable == null)
336                 {
337                     SecondaryRemoveTable = PrimaryRemoveTable;
338                 }
339                 else
340                 {
341                     // When merging remove lists from two or more batches both tables (primary and secondary) may contain identical items. The reason is when removing the items we get the original items rather than a clone
342                     // so the same item may have already been added to the secondary table. If we then try and add the same item from the primary table we will get a duplicate key exception from the
343                     // dictionary. Therefore we must not merge in an item if it already is in the secondary remove table.
344                     foreach (ProjectItemInstance item in PrimaryRemoveTable)
345                     {
346                         if (!SecondaryRemoveTable.Contains(item))
347                         {
348                             SecondaryRemoveTable.Add(item);
349                         }
350                     }
351                 }
352             }
353 
354             // Move all the modifies down
355             if (PrimaryModifyTable != null)
356             {
357                 if (SecondaryModifyTable == null)
358                 {
359                     SecondaryModifyTable = PrimaryModifyTable;
360                 }
361                 else
362                 {
363                     foreach (KeyValuePair<string, Dictionary<ProjectItemInstance, MetadataModifications>> entry in PrimaryModifyTable)
364                     {
365                         Dictionary<ProjectItemInstance, MetadataModifications> modifiesOfType;
366                         if (SecondaryModifyTable.TryGetValue(entry.Key, out modifiesOfType))
367                         {
368                             // There are already modifies of this type: add to the existing table
369                             foreach (KeyValuePair<ProjectItemInstance, MetadataModifications> modify in entry.Value)
370                             {
371                                 MergeModificationsIntoModificationTable(modifiesOfType, modify, ModifyMergeType.SecondWins);
372                             }
373                         }
374                         else
375                         {
376                             SecondaryModifyTable.Add(entry.Key, entry.Value);
377                         }
378                     }
379                 }
380             }
381 
382             // Move all the sets down
383             if (PrimaryPropertySets != null)
384             {
385                 if (SecondaryPropertySets == null)
386                 {
387                     SecondaryPropertySets = PrimaryPropertySets;
388                 }
389                 else
390                 {
391                     SecondaryPropertySets.ImportProperties(PrimaryPropertySets);
392                 }
393             }
394         }
395 
396         /// <summary>
397         /// Merge the current scope down into the base scope. This means applying the adds, removes, modifies, and sets
398         /// directly into the item and property tables in this scope.
399         /// </summary>
MergeScopeIntoLastScope()400         private void MergeScopeIntoLastScope()
401         {
402             // End of the line for this object: we are done with add tables, and we want to expose our
403             // adds to the world
404             if (PrimaryAddTable != null)
405             {
406                 SecondaryTable = SecondaryTable ?? new ItemDictionary<ProjectItemInstance>();
407                 SecondaryTable.ImportItems(PrimaryAddTable);
408             }
409 
410             if (PrimaryRemoveTable != null)
411             {
412                 SecondaryTable = SecondaryTable ?? new ItemDictionary<ProjectItemInstance>();
413                 SecondaryTable.RemoveItems(PrimaryRemoveTable);
414             }
415 
416             if (PrimaryModifyTable != null)
417             {
418                 foreach (KeyValuePair<string, Dictionary<ProjectItemInstance, MetadataModifications>> entry in PrimaryModifyTable)
419                 {
420                     SecondaryTable = SecondaryTable ?? new ItemDictionary<ProjectItemInstance>();
421                     ApplyModificationsToTable(SecondaryTable, entry.Key, entry.Value);
422                 }
423             }
424 
425             if (PrimaryPropertySets != null)
426             {
427                 SecondaryProperties = SecondaryProperties ?? new PropertyDictionary<ProjectPropertyInstance>(PrimaryPropertySets.Count);
428                 SecondaryProperties.ImportProperties(PrimaryPropertySets);
429             }
430         }
431 
432         /// <summary>
433         /// Gets the effective property for the current scope.
434         /// taking the name from the provided string within the specified start and end indexes.
435         /// If no match is found, returns null.
436         /// Caller must not modify the property returned.
437         /// </summary>
GetProperty(string name, int startIndex, int endIndex)438         public ProjectPropertyInstance GetProperty(string name, int startIndex, int endIndex)
439         {
440             // Walk down the tables and stop when the first
441             // property with this name is found
442             foreach (Scope scope in _lookupScopes)
443             {
444                 if (scope.PropertySets != null)
445                 {
446                     ProjectPropertyInstance property = scope.PropertySets.GetProperty(name, startIndex, endIndex);
447                     if (property != null)
448                     {
449                         return property;
450                     }
451                 }
452 
453                 if (scope.Properties != null)
454                 {
455                     ProjectPropertyInstance property = scope.Properties.GetProperty(name, startIndex, endIndex);
456                     if (property != null)
457                     {
458                         return property;
459                     }
460                 }
461 
462                 if (scope.TruncateLookupsAtThisScope)
463                 {
464                     break;
465                 }
466             }
467 
468             return null;
469         }
470 
471         /// <summary>
472         /// Gets the effective property for the current scope.
473         /// If no match is found, returns null.
474         /// Caller must not modify the property returned.
475         /// </summary>
GetProperty(string name)476         public ProjectPropertyInstance GetProperty(string name)
477         {
478             ErrorUtilities.VerifyThrowInternalLength(name, "name");
479 
480             return GetProperty(name, 0, name.Length - 1);
481         }
482 
483         /// <summary>
484         /// Gets the items of the specified type that are visible in the current scope.
485         /// If no match is found, returns an empty list.
486         /// Caller must not modify the group returned.
487         /// </summary>
GetItems(string itemType)488         public ICollection<ProjectItemInstance> GetItems(string itemType)
489         {
490             // The visible items consist of the adds (accumulated as we go down)
491             // plus the first set of regular items we encounter
492             // minus any removes
493 
494             List<ProjectItemInstance> allAdds = null;
495             List<ProjectItemInstance> allRemoves = null;
496             Dictionary<ProjectItemInstance, MetadataModifications> allModifies = null;
497             ICollection<ProjectItemInstance> groupFound = null;
498 
499             foreach (Scope scope in _lookupScopes)
500             {
501                 // Accumulate adds while we look downwards
502                 if (scope.Adds != null)
503                 {
504                     ICollection<ProjectItemInstance> adds = scope.Adds[itemType];
505                     if (adds.Count != 0)
506                     {
507                         allAdds = allAdds ?? new List<ProjectItemInstance>(adds.Count);
508                         allAdds.AddRange(adds);
509                     }
510                 }
511 
512                 // Accumulate removes while we look downwards
513                 if (scope.Removes != null)
514                 {
515                     ICollection<ProjectItemInstance> removes = scope.Removes[itemType];
516                     if (removes.Count != 0)
517                     {
518                         allRemoves = allRemoves ?? new List<ProjectItemInstance>(removes.Count);
519                         allRemoves.AddRange(removes);
520                     }
521                 }
522 
523                 // Accumulate modifications as we look downwards
524                 if (scope.Modifies != null)
525                 {
526                     Dictionary<ProjectItemInstance, MetadataModifications> modifies;
527                     if (scope.Modifies.TryGetValue(itemType, out modifies))
528                     {
529                         if (modifies.Count != 0)
530                         {
531                             allModifies = allModifies ?? new Dictionary<ProjectItemInstance, MetadataModifications>(modifies.Count);
532 
533                             // We already have some modifies for this type
534                             foreach (KeyValuePair<ProjectItemInstance, MetadataModifications> modify in modifies)
535                             {
536                                 // If earlier scopes modify the same metadata on the same item,
537                                 // they have priority
538                                 MergeModificationsIntoModificationTable(allModifies, modify, ModifyMergeType.FirstWins);
539                             }
540                         }
541                     }
542                 }
543 
544                 if (scope.Items != null)
545                 {
546                     groupFound = scope.Items[itemType];
547                     if (groupFound.Count != 0 || scope.Items.HasEmptyMarker(itemType))
548                     {
549                         // Found a group: we go no further
550                         break;
551                     }
552                 }
553 
554                 if (scope.TruncateLookupsAtThisScope)
555                 {
556                     break;
557                 }
558             }
559 
560             if ((allAdds == null) &&
561                 (allRemoves == null) &&
562                 (allModifies == null))
563             {
564                 // We can just hand out this group verbatim -
565                 // that avoids any importing
566                 groupFound = groupFound ?? Array.Empty<ProjectItemInstance>();
567 
568                 return groupFound;
569             }
570 
571             // Set the initial sizes to avoid resizing during import
572             int itemsTypesCount = 1;    // We're only ever importing a single item type
573             int itemsCount = groupFound?.Count ?? 0;    // Start with initial set
574             itemsCount += allAdds?.Count ?? 0;          // Add all the additions
575             itemsCount -= allRemoves?.Count ?? 0;       // Remove the removals
576             if (itemsCount < 0)
577                 itemsCount = 0;
578 
579             // We have adds and/or removes and/or modifies to incorporate.
580             // We can't modify the group, because that might
581             // be visible to other batches; we have to create
582             // a new one.
583             ItemDictionary<ProjectItemInstance> result = new ItemDictionary<ProjectItemInstance>(itemsTypesCount, itemsCount);
584 
585             if (groupFound != null)
586             {
587                 result.ImportItemsOfType(itemType, groupFound);
588             }
589             // Removes are processed after adds; this means when we remove there's no need to concern ourselves
590             // with the case where the item being removed is in an add table somewhere. The converse case is not possible
591             // using a project file: a project file cannot create an item that was already removed, it can only create
592             // a unique new item.
593             if (allAdds != null)
594             {
595                 result.ImportItemsOfType(itemType, allAdds);
596             }
597 
598             if (allRemoves != null)
599             {
600                 result.RemoveItems(allRemoves);
601             }
602 
603             // Modifies can be processed last; if a modified item was removed, the modify will be ignored
604             if (allModifies != null)
605             {
606                 ApplyModifies(result, allModifies);
607             }
608 
609             return result[itemType];
610         }
611 
612         /// <summary>
613         /// Populates with an item group. This is done before the item lookup is used in this scope.
614         /// Assumes all the items in the group have the same, provided, type.
615         /// Assumes there is no item group of this type in the primary table already.
616         /// Should be used only by batching buckets, and if no items are passed,
617         /// explicitly stores a marker for this item type indicating this.
618         /// </summary>
PopulateWithItems(string itemType, ICollection<ProjectItemInstance> group)619         internal void PopulateWithItems(string itemType, ICollection<ProjectItemInstance> group)
620         {
621             PrimaryTable = PrimaryTable ?? new ItemDictionary<ProjectItemInstance>();
622             ICollection<ProjectItemInstance> existing = PrimaryTable[itemType];
623             ErrorUtilities.VerifyThrow(existing.Count == 0, "Cannot add an itemgroup of this type.");
624 
625             if (group.Count > 0)
626             {
627                 PrimaryTable.ImportItemsOfType(itemType, group);
628             }
629             else
630             {
631                 PrimaryTable.AddEmptyMarker(itemType);
632             }
633         }
634 
635         /// <summary>
636         /// Populates with an item. This is done before the item lookup is used in this scope.
637         /// There may or may not already be a group for it.
638         /// </summary>
PopulateWithItem(ProjectItemInstance item)639         internal void PopulateWithItem(ProjectItemInstance item)
640         {
641             PrimaryTable = PrimaryTable ?? new ItemDictionary<ProjectItemInstance>();
642             PrimaryTable.Add(item);
643         }
644 
645         /// <summary>
646         /// Apply a property to this scope.
647         /// </summary>
SetProperty(ProjectPropertyInstance property)648         internal void SetProperty(ProjectPropertyInstance property)
649         {
650             // Setting in outer scope could be easily implemented, but our code does not do it at present
651             MustNotBeOuterScope();
652 
653             // Put in the set table
654             PrimaryPropertySets = PrimaryPropertySets ?? new PropertyDictionary<ProjectPropertyInstance>();
655             PrimaryPropertySets.Set(property);
656         }
657 
658         /// <summary>
659         /// Implements a true add, an item that has been created in a batch.
660         /// </summary>
AddNewItemsOfItemType(string itemType, ICollection<ProjectItemInstance> group, bool doNotAddDuplicates = false)661         internal void AddNewItemsOfItemType(string itemType, ICollection<ProjectItemInstance> group, bool doNotAddDuplicates = false)
662         {
663             // Adding to outer scope could be easily implemented, but our code does not do it at present
664             MustNotBeOuterScope();
665 
666 #if DEBUG
667             foreach (ProjectItemInstance item in group)
668             {
669                 MustNotBeInAnyTables(item);
670             }
671 #endif
672 
673             if (group.Count == 0)
674             {
675                 return;
676             }
677 
678             // Put them in the add table
679             PrimaryAddTable = PrimaryAddTable ?? new ItemDictionary<ProjectItemInstance>();
680             IEnumerable<ProjectItemInstance> itemsToAdd = group;
681             if (doNotAddDuplicates)
682             {
683                 // Remove duplicates from the inputs.
684                 itemsToAdd = itemsToAdd.Distinct(ProjectItemInstance.EqualityComparer);
685 
686                 // Ensure we don't also add any that already exist.
687                 var existingItems = GetItems(itemType);
688                 if (existingItems.Count > 0)
689                 {
690                     itemsToAdd = itemsToAdd.Where(item => !(existingItems.Contains(item, ProjectItemInstance.EqualityComparer)));
691                 }
692             }
693 
694             PrimaryAddTable.ImportItemsOfType(itemType, itemsToAdd);
695         }
696 
697         /// <summary>
698         /// Implements a true add, an item that has been created in a batch.
699         /// </summary>
AddNewItem(ProjectItemInstance item)700         internal void AddNewItem(ProjectItemInstance item)
701         {
702             // Adding to outer scope could be easily implemented, but our code does not do it at present
703             MustNotBeOuterScope();
704 
705 #if DEBUG
706             // This item must not be in any table already; a project cannot create an item
707             // that already exists
708             MustNotBeInAnyTables(item);
709 #endif
710 
711             // Put in the add table
712             PrimaryAddTable = PrimaryAddTable ?? new ItemDictionary<ProjectItemInstance>();
713             PrimaryAddTable.Add(item);
714         }
715 
716         /// <summary>
717         /// Remove a bunch of items from this scope
718         /// </summary>
RemoveItems(IEnumerable<ProjectItemInstance> items)719         internal void RemoveItems(IEnumerable<ProjectItemInstance> items)
720         {
721             foreach (ProjectItemInstance item in items)
722             {
723                 RemoveItem(item);
724             }
725         }
726 
727         /// <summary>
728         /// Remove an item from this scope
729         /// </summary>
RemoveItem(ProjectItemInstance item)730         internal void RemoveItem(ProjectItemInstance item)
731         {
732             // Removing from outer scope could be easily implemented, but our code does not do it at present
733             MustNotBeOuterScope();
734 
735             item = RetrieveOriginalFromCloneTable(item);
736 
737             // Put in the remove table
738             PrimaryRemoveTable = PrimaryRemoveTable ?? new ItemDictionary<ProjectItemInstance>();
739             PrimaryRemoveTable.Add(item);
740 
741             // No need to remove this item from the primary add table if it's
742             // already there -- we always apply removes after adds, so that add
743             // will be reversed anyway.
744         }
745 
746         /// <summary>
747         /// Modifies items in this scope with the same set of metadata modifications.
748         /// Assumes all the items in the group have the same, provided, type.
749         /// </summary>
ModifyItems(string itemType, ICollection<ProjectItemInstance> group, MetadataModifications metadataChanges)750         internal void ModifyItems(string itemType, ICollection<ProjectItemInstance> group, MetadataModifications metadataChanges)
751         {
752             // Modifying in outer scope could be easily implemented, but our code does not do it at present
753             MustNotBeOuterScope();
754 
755 #if DEBUG
756             // This item should not already be in any remove table; there is no way a project can
757             // modify items that were already removed
758             // Obviously, do this only in debug, as it's a slow check for bugs.
759             LinkedListNode<Scope> node = _lookupScopes.First;
760             while (node != null)
761             {
762                 Scope scope = node.Value;
763                 foreach (ProjectItemInstance item in group)
764                 {
765                     ProjectItemInstance actualItem = RetrieveOriginalFromCloneTable(item);
766                     MustNotBeInTable(scope.Removes, actualItem);
767                 }
768                 node = node.Next;
769             }
770 #endif
771 
772             if (!metadataChanges.HasChanges)
773             {
774                 return;
775             }
776 
777             // Put in the modify table
778 
779             // We don't need to check whether the item is in the add table vs. the main table; either
780             // way the modification will be applied.
781             PrimaryModifyTable = PrimaryModifyTable ?? new ItemTypeToItemsMetadataUpdateDictionary(MSBuildNameIgnoreCaseComparer.Default);
782             Dictionary<ProjectItemInstance, MetadataModifications> modifiesOfType;
783             if (!PrimaryModifyTable.TryGetValue(itemType, out modifiesOfType))
784             {
785                 modifiesOfType = new Dictionary<ProjectItemInstance, MetadataModifications>();
786                 PrimaryModifyTable[itemType] = modifiesOfType;
787             }
788 
789             foreach (ProjectItemInstance item in group)
790             {
791                 // Each item needs its own collection for metadata changes, even if this particular change is the same
792                 // for more than one item, subsequent changes might not be.
793                 var metadataChangeCopy = metadataChanges.Clone();
794 
795                 // If we're asked to modify a clone we handed out, record it as a modify of the original
796                 // instead
797                 ProjectItemInstance actualItem = RetrieveOriginalFromCloneTable(item);
798                 var modify = new KeyValuePair<ProjectItemInstance, MetadataModifications>(actualItem, metadataChangeCopy);
799                 MergeModificationsIntoModificationTable(modifiesOfType, modify, ModifyMergeType.SecondWins);
800             }
801         }
802 
803         #endregion
804 
805         #region Private Methods
806 
807         /// <summary>
808         /// Apply modifies to a temporary result group.
809         /// Items to be modified are virtual-cloned so the original isn't changed.
810         /// </summary>
ApplyModifies(ItemDictionary<ProjectItemInstance> result, Dictionary<ProjectItemInstance, MetadataModifications> allModifies)811         private void ApplyModifies(ItemDictionary<ProjectItemInstance> result, Dictionary<ProjectItemInstance, MetadataModifications> allModifies)
812         {
813             // Clone, because we're modifying actual items, and this would otherwise be visible to other batches,
814             // and would be "published" even if a target fails.
815             // FUTURE - don't need to clone here for non intrinsic tasks, but at present, they don't do modifies
816 
817             // Store the clone, in case we're asked to modify or remove it later (we will record it against the real item)
818             _cloneTable = _cloneTable ?? new Dictionary<ProjectItemInstance, ProjectItemInstance>();
819 
820             foreach (var modify in allModifies)
821             {
822                 ProjectItemInstance originalItem = modify.Key;
823 
824                 if (result.Contains(originalItem))
825                 {
826                     var modificationsToApply = modify.Value;
827 
828                     // Modify the cloned item and replace the original with it.
829                     ProjectItemInstance cloneItem = modify.Key.DeepClone();
830 
831                     ApplyMetadataModificationsToItem(modificationsToApply, cloneItem);
832 
833                     result.Replace(originalItem, cloneItem);
834 
835                     // This will be null if the item wasn't in the result group, ie, it had been removed after being modified
836                     ErrorUtilities.VerifyThrow(!_cloneTable.ContainsKey(cloneItem), "Should be new, not already in table!");
837                     _cloneTable[cloneItem] = originalItem;
838                 }
839             }
840         }
841 
842         /// <summary>
843         /// Applies the specified modifications to the supplied item.
844         /// </summary>
ApplyMetadataModificationsToItem(MetadataModifications modificationsToApply, ProjectItemInstance itemToModify)845         private static void ApplyMetadataModificationsToItem(MetadataModifications modificationsToApply, ProjectItemInstance itemToModify)
846         {
847             // Remove any metadata from the item which is slated for removal.  The indexer in the modifications table will
848             // return a modification with Remove == true either if there is an explicit entry for that name in the modifications
849             // or if keepOnlySpecified == true and there is no entry for that name.
850             if (modificationsToApply.KeepOnlySpecified)
851             {
852                 List<string> metadataToRemove = new List<string>(itemToModify.Metadata.Where(m => modificationsToApply[m.Name].Remove).Select(m => m.Name));
853                 foreach (var metadataName in metadataToRemove)
854                 {
855                     itemToModify.RemoveMetadata(metadataName);
856                 }
857             }
858 
859             // Now make any additions or modifications
860             foreach (var modificationPair in modificationsToApply.ExplicitModifications)
861             {
862                 if (modificationPair.Value.Remove)
863                 {
864                     itemToModify.RemoveMetadata(modificationPair.Key);
865                 }
866                 else if (modificationPair.Value.NewValue != null)
867                 {
868                     itemToModify.SetMetadata(modificationPair.Key, modificationPair.Value.NewValue);
869                 }
870             }
871         }
872 
873         /// <summary>
874         /// Look up the "real" item by using its clone, and return the real item.
875         /// See <see cref="_cloneTable"/> for explanation of the clone table.
876         /// </summary>
RetrieveOriginalFromCloneTable(ProjectItemInstance item)877         private ProjectItemInstance RetrieveOriginalFromCloneTable(ProjectItemInstance item)
878         {
879             ProjectItemInstance original;
880             if (_cloneTable != null)
881             {
882                 if (_cloneTable.TryGetValue(item, out original))
883                 {
884                     item = original;
885                 }
886             }
887 
888             return item;
889         }
890 
891         /// <summary>
892         /// Applies a list of modifications to the appropriate <see cref="ItemDictionary{ProjectItemInstance}" /> in a main table.
893         /// If any modifications conflict, these modifications win.
894         /// </summary>
ApplyModificationsToTable(ItemDictionary<ProjectItemInstance> table, string itemType, ItemsMetadataUpdateDictionary modify)895         private void ApplyModificationsToTable(ItemDictionary<ProjectItemInstance> table, string itemType, ItemsMetadataUpdateDictionary modify)
896         {
897             ICollection<ProjectItemInstance> existing = table[itemType];
898             if (existing != null)
899             {
900                 foreach (var kvPair in modify)
901                 {
902                     if (table.Contains(kvPair.Key))
903                     {
904                         var itemToModify = kvPair.Key;
905                         var modificationsToApply = kvPair.Value;
906 
907                         ApplyMetadataModificationsToItem(modificationsToApply, itemToModify);
908                     }
909                 }
910             }
911         }
912 
913         /// <summary>
914         /// Applies a modification to an item in a table of modifications.
915         /// If the item already exists in the table, merges in the modifications; if there is a conflict
916         /// the mergeType indicates which should win.
917         /// </summary>
MergeModificationsIntoModificationTable(Dictionary<ProjectItemInstance, MetadataModifications> modifiesOfType, KeyValuePair<ProjectItemInstance, MetadataModifications> modify, ModifyMergeType mergeType)918         private void MergeModificationsIntoModificationTable(Dictionary<ProjectItemInstance, MetadataModifications> modifiesOfType,
919                                                              KeyValuePair<ProjectItemInstance, MetadataModifications> modify,
920                                                              ModifyMergeType mergeType)
921         {
922             MetadataModifications existingMetadataChanges;
923             if (modifiesOfType.TryGetValue(modify.Key, out existingMetadataChanges))
924             {
925                 // There's already modifications for this item; merge with those
926                 if (mergeType == ModifyMergeType.SecondWins)
927                 {
928                     // Merge the new modifications on top of the existing modifications.
929                     existingMetadataChanges.ApplyModifications(modify.Value);
930                 }
931                 else
932                 {
933                     // Only apply explicit modifications.
934                     foreach (var metadataChange in modify.Value.ExplicitModifications)
935                     {
936                         // If the existing metadata change list has an entry for this metadata, ignore this change.
937                         // We continue to allow changes made when KeepOnlySpecified is set because it is assumed that explicit metadata changes
938                         // always trump implicit ones.
939                         if (!existingMetadataChanges.ContainsExplicitModification(metadataChange.Key))
940                         {
941                             existingMetadataChanges[metadataChange.Key] = metadataChange.Value;
942                         }
943                     }
944                 }
945             }
946             else
947             {
948                 modifiesOfType.Add(modify.Key, modify.Value);
949             }
950         }
951 
952 #if DEBUG
953         /// <summary>
954         /// Verify item is not in the table
955         /// </summary>
MustNotBeInTable(ItemDictionary<ProjectItemInstance> table, ProjectItemInstance item)956         private void MustNotBeInTable(ItemDictionary<ProjectItemInstance> table, ProjectItemInstance item)
957         {
958             if (table != null && table.ItemTypes.Contains(item.ItemType))
959             {
960                 ICollection<ProjectItemInstance> tableOfItemsOfSameType = table[item.ItemType];
961                 if (tableOfItemsOfSameType != null)
962                 {
963                     ErrorUtilities.VerifyThrow(!tableOfItemsOfSameType.Contains(item), "Item should not be in table");
964                 }
965             }
966         }
967 
968         /// <summary>
969         /// Verify item is not in the modify table
970         /// </summary>
MustNotBeInTable(ItemTypeToItemsMetadataUpdateDictionary table, ProjectItemInstance item)971         private void MustNotBeInTable(ItemTypeToItemsMetadataUpdateDictionary table, ProjectItemInstance item)
972         {
973             if (table != null && table.ContainsKey(item.ItemType))
974             {
975                 ItemsMetadataUpdateDictionary tableOfItemsOfSameType = table[item.ItemType];
976                 if (tableOfItemsOfSameType != null)
977                 {
978                     ErrorUtilities.VerifyThrow(!tableOfItemsOfSameType.ContainsKey(item), "Item should not be in table");
979                 }
980             }
981         }
982 
983         /// <summary>
984         /// Verify item is not in any table in any scope
985         /// </summary>
MustNotBeInAnyTables(ProjectItemInstance item)986         private void MustNotBeInAnyTables(ProjectItemInstance item)
987         {
988             // This item should not already be in any table; there is no way a project can
989             // create items that already existed
990             // Obviously, do this only in debug, as it's a slow check for bugs.
991             LinkedListNode<Scope> node = _lookupScopes.First;
992             while (node != null)
993             {
994                 Scope scope = node.Value;
995                 MustNotBeInTable(scope.Adds, item);
996                 MustNotBeInTable(scope.Removes, item);
997                 MustNotBeInTable(scope.Modifies, item);
998                 node = node.Next;
999             }
1000         }
1001 
1002 #endif
1003 
1004         /// <summary>
1005         /// Add/remove/modify/set directly on an outer scope would need to be handled separately - it would apply
1006         /// directly to the main tables. Our code isn't expected to do this.
1007         /// </summary>
MustNotBeOuterScope()1008         private void MustNotBeOuterScope()
1009         {
1010             ErrorUtilities.VerifyThrow(_lookupScopes.Count > 1, "Operation in outer scope not supported");
1011         }
1012 
1013         #endregion
1014 
1015         /// <summary>
1016         /// When merging metadata, we can deal with a conflict two different ways:
1017         /// FirstWins = any previous metadata with the name takes precedence
1018         /// SecondWins = the new metadata with the name takes precedence
1019         /// </summary>
1020         private enum ModifyMergeType
1021         {
1022             FirstWins = 1,
1023             SecondWins = 2
1024         }
1025 
1026         /// <summary>
1027         /// A class representing a set of additions, modifications or removal of metadata from items.
1028         /// </summary>
1029         internal class MetadataModifications
1030         {
1031             /// <summary>
1032             /// Flag indicating if the modifications should be interpreted such that the lack of an explicit entry for a metadata name
1033             /// means that that metadata should be removed.
1034             /// </summary>
1035             private bool _keepOnlySpecified;
1036 
1037             /// <summary>
1038             /// A set of explicitly-specified modifications.
1039             /// </summary>
1040             private HybridDictionary<string, MetadataModification> _modifications;
1041 
1042             /// <summary>
1043             /// Constructor.
1044             /// </summary>
1045             /// <param name="keepOnlySpecified">When true, metadata which is not explicitly-specified here but which is present on the target
1046             /// item should be removed.  When false, only explicitly-specified modifications apply.</param>
MetadataModifications(bool keepOnlySpecified)1047             public MetadataModifications(bool keepOnlySpecified)
1048             {
1049                 _keepOnlySpecified = keepOnlySpecified;
1050                 _modifications = new HybridDictionary<string, MetadataModification>(MSBuildNameIgnoreCaseComparer.Default);
1051             }
1052 
1053             /// <summary>
1054             /// Cloning constructor.
1055             /// </summary>
1056             /// <param name="other">The metadata modifications to clone.</param>
MetadataModifications(MetadataModifications other)1057             private MetadataModifications(MetadataModifications other)
1058             {
1059                 _keepOnlySpecified = other._keepOnlySpecified;
1060                 _modifications = new HybridDictionary<string, MetadataModification>(other._modifications, MSBuildNameIgnoreCaseComparer.Default);
1061             }
1062 
1063             /// <summary>
1064             /// Clones this modification set.
1065             /// </summary>
1066             /// <returns>A copy of the modifications.</returns>
Clone()1067             public MetadataModifications Clone()
1068             {
1069                 return new MetadataModifications(this);
1070             }
1071 
1072             /// <summary>
1073             /// A flag indicating whether or not there are any changes which might apply.
1074             /// </summary>
1075             public bool HasChanges
1076             {
1077                 get { return _modifications.Count > 0 || _keepOnlySpecified; }
1078             }
1079 
1080             /// <summary>
1081             /// A flag indicating whether only those metadata explicitly-specified should be retained on a target item.
1082             /// </summary>
1083             public bool KeepOnlySpecified
1084             {
1085                 get { return _keepOnlySpecified; }
1086             }
1087 
1088             /// <summary>
1089             /// Applies the modifications from the specified modifications to this one, performing a proper merge.
1090             /// </summary>
1091             /// <param name="other">The set of metadata modifications to merge into this one.</param>
ApplyModifications(MetadataModifications other)1092             public void ApplyModifications(MetadataModifications other)
1093             {
1094                 // Apply implicit modifications
1095                 if (other._keepOnlySpecified)
1096                 {
1097                     // Any metadata not specified in other must be removed from this one.
1098                     var metadataToRemove = new List<string>(_modifications.Keys.Where(m => other[m].Remove));
1099                     foreach (var metadata in metadataToRemove)
1100                     {
1101                         _modifications.Remove(metadata);
1102                     }
1103                 }
1104 
1105                 _keepOnlySpecified |= other._keepOnlySpecified;
1106 
1107                 // Now apply the explicit modifications from the other table
1108                 foreach (var modificationPair in other.ExplicitModifications)
1109                 {
1110                     MetadataModification existingModification;
1111                     if (modificationPair.Value.KeepValue && _modifications.TryGetValue(modificationPair.Key, out existingModification))
1112                     {
1113                         // The incoming modification requests we maintain the "current value" of the metadata.  If we have
1114                         // an existing change, maintain that as-is.  Otherwise, fall through and apply our change directly.
1115                         if (existingModification.Remove || existingModification.NewValue != null)
1116                         {
1117                             continue;
1118                         }
1119                     }
1120 
1121                     // Just copy over the changes from the other table to this one.
1122                     _modifications[modificationPair.Key] = modificationPair.Value;
1123                 }
1124             }
1125 
1126             /// <summary>
1127             /// Returns true if this block contains an explicitly-specified modification for the provided metadata name.
1128             /// </summary>
1129             /// <param name="metadataName">The name of the metadata.</param>
1130             /// <returns>True if there is an explicit modification for this metadata, false otherwise.</returns>
1131             /// <remarks>The return value of this method is unaffected by the <see cref="KeepOnlySpecified"/> property.</remarks>
ContainsExplicitModification(string metadataName)1132             public bool ContainsExplicitModification(string metadataName)
1133             {
1134                 return _modifications.ContainsKey(metadataName);
1135             }
1136 
1137             /// <summary>
1138             /// Adds metadata to the modification table.
1139             /// </summary>
1140             /// <param name="metadataName">The name of the metadata to add (or change) in the target item.</param>
1141             /// <param name="metadataValue">The metadata value.</param>
Add(string metadataName, string metadataValue)1142             public void Add(string metadataName, string metadataValue)
1143             {
1144                 _modifications.Add(metadataName, MetadataModification.CreateFromNewValue(metadataValue));
1145             }
1146 
1147             /// <summary>
1148             /// Provides an enumeration of the explicit metadata modifications.
1149             /// </summary>
1150             public IEnumerable<KeyValuePair<string, MetadataModification>> ExplicitModifications
1151             {
1152                 get { return _modifications; }
1153             }
1154 
1155             /// <summary>
1156             /// Sets or retrieves a modification from the modifications table.
1157             /// </summary>
1158             /// <param name="metadataName">The metadata name.</param>
1159             /// <returns>If <see cref="KeepOnlySpecified"/> is true, this will return a modification with <see cref="MetadataModification.Remove"/>
1160             /// set to true if the metadata has no other explicitly-specified modification.  Otherwise it will return only the explicitly-specified
1161             /// modification if one exists.</returns>
1162             /// <exception cref="System.Collections.Generic.KeyNotFoundException">When <see cref="KeepOnlySpecified"/> if false, this is thrown if the metadata
1163             /// specified does not exist when attempting to retrieve a metadata modification.</exception>
1164             public MetadataModification this[string metadataName]
1165             {
1166                 get
1167                 {
1168                     MetadataModification modification = null;
1169                     if (!_modifications.TryGetValue(metadataName, out modification))
1170                     {
1171                         if (_keepOnlySpecified)
1172                         {
1173                             // This metadata was not specified and we are only keeping specified metadata, so remove it.
1174                             return MetadataModification.CreateFromRemove();
1175                         }
1176 
1177                         return MetadataModification.CreateFromNoChange();
1178                     }
1179 
1180                     return modification;
1181                 }
1182 
1183                 set
1184                 {
1185                     ErrorUtilities.VerifyThrowInternalNull(value, "value");
1186                     _modifications[metadataName] = value;
1187                 }
1188             }
1189         }
1190 
1191         /// <summary>
1192         /// A type of metadata modification.
1193         /// </summary>
1194         internal enum ModificationType
1195         {
1196             /// <summary>
1197             /// Indicates the metadata value should be kept unchanged.
1198             /// </summary>
1199             Keep,
1200 
1201             /// <summary>
1202             /// Indicates the metadata value should be changed.
1203             /// </summary>
1204             Update,
1205 
1206             /// <summary>
1207             /// Indicates the metadata value should be removed.
1208             /// </summary>
1209             Remove
1210         }
1211 
1212         /// <summary>
1213         /// Represents a modification for a single metadata.
1214         /// </summary>
1215         internal class MetadataModification
1216         {
1217             /// <summary>
1218             /// When true, indicates the metadata should be removed from the target item.
1219             /// </summary>
1220             private readonly bool _remove;
1221 
1222             /// <summary>
1223             /// The value to which the metadata should be set.  If null, the metadata value should be retained unmodified.
1224             /// </summary>
1225             private readonly string _newValue;
1226 
1227             /// <summary>
1228             /// A modification which indicates the metadata value should be retained without modification.
1229             /// </summary>
1230             private static readonly MetadataModification s_keepModification = new MetadataModification(ModificationType.Keep);
1231 
1232             /// <summary>
1233             /// A modification which indicates the metadata should be removed.
1234             /// </summary>
1235             private static readonly MetadataModification s_removeModification = new MetadataModification(ModificationType.Remove);
1236 
1237             /// <summary>
1238             /// Constructor for metadata modifications of type Keep or Remove.
1239             /// </summary>
1240             /// <param name="modificationType">The type of modification to make.</param>
MetadataModification(ModificationType modificationType)1241             private MetadataModification(ModificationType modificationType)
1242             {
1243                 ErrorUtilities.VerifyThrow(modificationType != ModificationType.Update, "Modification type may only be update when a value is specified.");
1244                 _remove = modificationType == ModificationType.Remove;
1245                 _newValue = null;
1246             }
1247 
1248             /// <summary>
1249             /// Constructor for metadata modifications of type Update.
1250             /// </summary>
1251             /// <param name="value">The new value for the metadata.</param>
MetadataModification(string value)1252             private MetadataModification(string value)
1253             {
1254                 _remove = false;
1255                 _newValue = value;
1256             }
1257 
1258             /// <summary>
1259             /// Creates a metadata modification of type Keep.
1260             /// </summary>
1261             /// <returns>The metadata modification.</returns>
CreateFromNoChange()1262             public static MetadataModification CreateFromNoChange()
1263             {
1264                 return s_keepModification;
1265             }
1266 
1267             /// <summary>
1268             /// Creates a metadata modification of type Update with the specified metadata value.
1269             /// </summary>
1270             /// <param name="newValue">The new metadata value.</param>
1271             /// <returns>The metadata modification.</returns>
CreateFromNewValue(string newValue)1272             public static MetadataModification CreateFromNewValue(string newValue)
1273             {
1274                 return new MetadataModification(newValue);
1275             }
1276 
1277             /// <summary>
1278             /// Creates a metadata modification of type Remove.
1279             /// </summary>
1280             /// <returns>The metadata modification.</returns>
CreateFromRemove()1281             public static MetadataModification CreateFromRemove()
1282             {
1283                 return s_removeModification;
1284             }
1285 
1286             /// <summary>
1287             /// When true, this modification indicates the associated metadata should be removed.
1288             /// </summary>
1289             public bool Remove
1290             {
1291                 get { return _remove; }
1292             }
1293 
1294             /// <summary>
1295             /// When true, this modification indicates the associated metadata should retain its existing value.
1296             /// </summary>
1297             public bool KeepValue
1298             {
1299                 get { return (_remove == false && _newValue == null); }
1300             }
1301 
1302             /// <summary>
1303             /// The new value of the metadata.  Only valid when <see cref="Remove"/> is false.
1304             /// </summary>
1305             public string NewValue
1306             {
1307                 get { return _newValue; }
1308             }
1309         }
1310 
1311         /// <summary>
1312         /// Represents an entry in the lookup list.
1313         /// Class rather than a struct so that it can be modified in the list.
1314         /// </summary>
1315         internal class Scope
1316         {
1317             /// <summary>
1318             /// Contains all of the original items at this level in the Lookup
1319             /// </summary>
1320             private ItemDictionary<ProjectItemInstance> _items;
1321 
1322             /// <summary>
1323             /// Contains all of the items which have been added at this level in the Lookup
1324             /// </summary>
1325             private ItemDictionary<ProjectItemInstance> _adds;
1326 
1327             /// <summary>
1328             /// Contails all of the items which have been removed at this level in the Lookup
1329             /// </summary>
1330             private ItemDictionary<ProjectItemInstance> _removes;
1331 
1332             /// <summary>
1333             /// Contains all of the metadata which has been changed for items at this level in the Lookup.
1334             /// Schema: { K=type, V= { K=item, V=table of { K=metadata name, V=metadata value }}}
1335             /// </summary>
1336             private ItemTypeToItemsMetadataUpdateDictionary _modifies;
1337 
1338             /// <summary>
1339             /// Contains all of the original properties at this level in the Lookup
1340             /// </summary>
1341             private PropertyDictionary<ProjectPropertyInstance> _properties;
1342 
1343             /// <summary>
1344             /// Contains all of the properties which have been set at this level or above in the Lookup
1345             /// </summary>
1346             private PropertyDictionary<ProjectPropertyInstance> _propertySets;
1347 
1348             /// <summary>
1349             /// The managed thread id which entered this scope.
1350             /// </summary>
1351             private int _threadIdThatEnteredScope;
1352 
1353             /// <summary>
1354             /// A description of this scope, for error checking
1355             /// </summary>
1356             private string _description;
1357 
1358             /// <summary>
1359             /// The lookup which owns this scope, for error checking.
1360             /// </summary>
1361             private Lookup _owningLookup;
1362 
1363             /// <summary>
1364             /// Indicates whether or not further levels in the Lookup should be consulted beyond this one
1365             /// to find the actual value for the desired item or property.
1366             /// </summary>
1367             private bool _truncateLookupsAtThisScope;
1368 
Scope(Lookup lookup, string description, ItemDictionary<ProjectItemInstance> items, PropertyDictionary<ProjectPropertyInstance> properties)1369             internal Scope(Lookup lookup, string description, ItemDictionary<ProjectItemInstance> items, PropertyDictionary<ProjectPropertyInstance> properties)
1370             {
1371                 _owningLookup = lookup;
1372                 _description = description;
1373                 _items = items;
1374                 _adds = null;
1375                 _removes = null;
1376                 _modifies = null;
1377                 _properties = properties;
1378                 _propertySets = null;
1379                 _threadIdThatEnteredScope = Thread.CurrentThread.ManagedThreadId;
1380                 _truncateLookupsAtThisScope = false;
1381             }
1382 
1383             /// <summary>
1384             /// The main table, populated with items that
1385             /// are initially visible in this scope. Does not
1386             /// include adds or removes unless it's the table in
1387             /// the outermost scope.
1388             /// </summary>
1389             internal ItemDictionary<ProjectItemInstance> Items
1390             {
1391                 get { return _items; }
1392                 set { _items = value; }
1393             }
1394             /// <summary>
1395             /// Adds made in this scope or above.
1396             /// </summary>
1397             internal ItemDictionary<ProjectItemInstance> Adds
1398             {
1399                 get { return _adds; }
1400                 set { _adds = value; }
1401             }
1402             /// <summary>
1403             /// Removes made in this scope or above.
1404             /// </summary>
1405             internal ItemDictionary<ProjectItemInstance> Removes
1406             {
1407                 get { return _removes; }
1408                 set { _removes = value; }
1409             }
1410             /// <summary>
1411             /// Modifications made in this scope or above.
1412             /// </summary>
1413             internal ItemTypeToItemsMetadataUpdateDictionary Modifies
1414             {
1415                 get { return _modifies; }
1416                 set { _modifies = value; }
1417             }
1418             /// <summary>
1419             /// The main property table, populated with properties
1420             /// that are initially visible in this scope. Does not
1421             /// include sets unless it's the table in the outermost scope.
1422             /// </summary>
1423             internal PropertyDictionary<ProjectPropertyInstance> Properties
1424             {
1425                 get { return _properties; }
1426                 set { _properties = value; }
1427             }
1428             /// <summary>
1429             /// Properties set in this scope or above.
1430             /// </summary>
1431             internal PropertyDictionary<ProjectPropertyInstance> PropertySets
1432             {
1433                 get { return _propertySets; }
1434                 set { _propertySets = value; }
1435             }
1436             /// <summary>
1437             /// ID of thread owning this scope
1438             /// </summary>
1439             internal int ThreadIdThatEnteredScope
1440             {
1441                 get { return _threadIdThatEnteredScope; }
1442             }
1443             /// <summary>
1444             /// Whether to stop lookups going beyond this scope downwards
1445             /// </summary>
1446             internal bool TruncateLookupsAtThisScope
1447             {
1448                 get { return _truncateLookupsAtThisScope; }
1449             }
1450 
1451             /// <summary>
1452             /// The description assigned to this scope.
1453             /// </summary>
1454             internal string Description
1455             {
1456                 get { return _description; }
1457             }
1458 
1459             /// <summary>
1460             /// Leaves the current lookup scope.
1461             /// </summary>
LeaveScope()1462             internal void LeaveScope()
1463             {
1464                 _owningLookup.LeaveScope(this);
1465             }
1466         }
1467     }
1468 }
1469