| File: Evaluation\LazyItemEvaluator.OrderedItemDataCollection.cs | Web Access |
| Project: src\msbuild\src\Build\Microsoft.Build.csproj (Microsoft.Build) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; using System.Collections.Generic; using System.Collections.Immutable; #nullable disable namespace Microsoft.Build.Evaluation { internal partial class LazyItemEvaluator<P, I, M, D> { /// <summary> /// A collection of ItemData that maintains insertion order and internally optimizes some access patterns, e.g. bulk removal /// based on normalized item values. /// </summary> internal sealed class OrderedItemDataCollection { #region Inner types /// <summary> /// A mutable and enumerable version of <see cref="OrderedItemDataCollection"/>. /// </summary> internal sealed class Builder : IEnumerable<ItemData> { /// <summary> /// The list of items in the collection. Defines the enumeration order. /// </summary> private ImmutableList<ItemData>.Builder _listBuilder; /// <summary> /// A dictionary of items keyed by their normalized value. /// </summary> private Dictionary<string, ItemDataCollectionValue<I>> _dictionaryBuilder; internal Builder(ImmutableList<ItemData>.Builder listBuilder) { _listBuilder = listBuilder; } #region IEnumerable implementation IEnumerator<ItemData> IEnumerable<ItemData>.GetEnumerator() => _listBuilder.GetEnumerator(); System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => _listBuilder.GetEnumerator(); #endregion public int Count => _listBuilder.Count; public ItemData this[int index] { get { return _listBuilder[index]; } set { // Update the dictionary if it exists. if (_dictionaryBuilder is not null) { ItemData oldItemData = _listBuilder[index]; string oldNormalizedValue = oldItemData.NormalizedItemValue; string newNormalizedValue = value.NormalizedItemValue; if (!string.Equals(oldNormalizedValue, newNormalizedValue, StringComparison.OrdinalIgnoreCase)) { // Normalized values are different - delete from the old entry and add to the new entry. ItemDataCollectionValue<I> oldDictionaryEntry = _dictionaryBuilder[oldNormalizedValue]; oldDictionaryEntry.Delete(oldItemData.Item); if (oldDictionaryEntry.IsEmpty) { _dictionaryBuilder.Remove(oldNormalizedValue); } else { _dictionaryBuilder[oldNormalizedValue] = oldDictionaryEntry; } ItemDataCollectionValue<I> newDictionaryEntry = _dictionaryBuilder[newNormalizedValue]; newDictionaryEntry.Add(value.Item); _dictionaryBuilder[newNormalizedValue] = newDictionaryEntry; } else { // Normalized values are the same - replace the item in the entry. ItemDataCollectionValue<I> dictionaryEntry = _dictionaryBuilder[newNormalizedValue]; dictionaryEntry.Replace(oldItemData.Item, value.Item); _dictionaryBuilder[newNormalizedValue] = dictionaryEntry; } } _listBuilder[index] = value; } } /// <summary> /// Gets or creates a dictionary keyed by normalized values. /// </summary> public Dictionary<string, ItemDataCollectionValue<I>> Dictionary { get { if (_dictionaryBuilder == null) { _dictionaryBuilder = new Dictionary<string, ItemDataCollectionValue<I>>(StringComparer.OrdinalIgnoreCase); for (int i = 0; i < _listBuilder.Count; i++) { ItemData itemData = _listBuilder[i]; AddToDictionary(ref itemData); _listBuilder[i] = itemData; } } return _dictionaryBuilder; } } public void Add(ItemData data) { if (_dictionaryBuilder is not null) { AddToDictionary(ref data); } _listBuilder.Add(data); } public void Clear() { _listBuilder.Clear(); _dictionaryBuilder?.Clear(); } /// <summary> /// Removes all items passed in a collection. /// </summary> public void RemoveAll(ICollection<I> itemsToRemove) { _listBuilder.RemoveAll(item => itemsToRemove.Contains(item.Item)); // This is a rare operation, don't bother updating the dictionary for now. It will be recreated as needed. _dictionaryBuilder = null; } /// <summary> /// Removes all items whose normalized path is passed in a collection. /// </summary> public void RemoveAll(ICollection<string> itemPathsToRemove) { var dictionary = Dictionary; HashSet<I> itemsToRemove = null; foreach (string itemValue in itemPathsToRemove) { if (dictionary.TryGetValue(itemValue, out var multiItem)) { foreach (I item in multiItem) { itemsToRemove ??= new HashSet<I>(); itemsToRemove.Add(item); } _dictionaryBuilder.Remove(itemValue); } } if (itemsToRemove is not null) { _listBuilder.RemoveAll(item => itemsToRemove.Contains(item.Item)); } } /// <summary> /// Creates an immutable view of this collection. /// </summary> public OrderedItemDataCollection ToImmutable() { return new OrderedItemDataCollection(_listBuilder.ToImmutable()); } private void AddToDictionary(ref ItemData itemData) { string key = itemData.NormalizedItemValue; if (!_dictionaryBuilder.TryGetValue(key, out var dictionaryValue)) { dictionaryValue = new ItemDataCollectionValue<I>(itemData.Item); } else { dictionaryValue.Add(itemData.Item); } _dictionaryBuilder[key] = dictionaryValue; } } #endregion /// <summary> /// The list of items in the collection. Defines the enumeration order. /// </summary> private ImmutableList<ItemData> _list; private OrderedItemDataCollection(ImmutableList<ItemData> list) { _list = list; } /// <summary> /// Creates a new mutable collection. /// </summary> public static Builder CreateBuilder() { return new Builder(ImmutableList.CreateBuilder<ItemData>()); } /// <summary> /// Creates a mutable view of this collection. Changes made to the returned builder are not reflected in this collection. /// </summary> public Builder ToBuilder() { return new Builder(_list.ToBuilder()); } } } }