File: Evaluation\LazyItemEvaluator.OrderedItemDataCollection.cs
Web Access
Project: ..\..\..\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
 
                private ImmutableList<ItemData>.Enumerator GetEnumerator() => _listBuilder.GetEnumerator();
                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());
            }
        }
    }
}