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