File: Instance\ImmutableProjectCollections\ImmutableItemDictionary.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;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Build.Collections;
using Microsoft.Build.Evaluation;
using Microsoft.Build.Execution;
using Microsoft.Build.Shared;
 
namespace Microsoft.Build.Instance
{
    /// <summary>
    /// A specialized collection used when item data originates in an immutable Project.
    /// </summary>
    internal sealed class ImmutableItemDictionary<TCached, T> : IItemDictionary<T>
        where T : class, IKeyed, IItem
        where TCached : IKeyed, IItem
    {
        private readonly IDictionary<string, ICollection<TCached>> _itemsByType;
        private readonly ICollection<TCached> _allCachedItems;
        private readonly Func<TCached, T?> _getInstance;
        private readonly Func<T, string?> _getItemType;
 
        public ImmutableItemDictionary(
            ICollection<TCached> allItems,
            IDictionary<string, ICollection<TCached>> itemsByType,
            Func<TCached, T?> getInstance,
            Func<T, string?> getItemType)
        {
            if (allItems == null)
            {
                throw new ArgumentNullException(nameof(allItems));
            }
 
            _allCachedItems = allItems;
            _itemsByType = itemsByType ?? throw new ArgumentNullException(nameof(itemsByType));
            _getInstance = getInstance;
            _getItemType = getItemType;
        }
 
        /// <inheritdoc />
        public ICollection<T> this[string itemType]
        {
            get
            {
                if (!_itemsByType.TryGetValue(itemType, out ICollection<TCached>? list))
                {
                    return Array.Empty<T>();
                }
 
                return new ListConverter(itemType, list, _getInstance);
            }
        }
 
        /// <inheritdoc />
        public int Count => _allCachedItems.Count;
 
        /// <inheritdoc />
        public ICollection<string> ItemTypes => _itemsByType.Keys;
 
        /// <inheritdoc />
        public void Add(T projectItem) => throw new NotSupportedException();
 
        /// <inheritdoc />
        public void AddEmptyMarker(string itemType) => throw new NotSupportedException();
 
        /// <inheritdoc />
        public void AddRange(IEnumerable<T> projectItems) => throw new NotSupportedException();
 
        /// <inheritdoc />
        public void Clear() => throw new NotSupportedException();
 
        /// <inheritdoc />
        public bool Contains(T projectItem)
        {
            if (projectItem == null)
            {
                return false;
            }
 
            string? itemType = _getItemType(projectItem);
            if (itemType == null)
            {
                return false;
            }
 
            ICollection<T> items = GetItems(itemType);
            return items.Contains(projectItem);
        }
 
        /// <inheritdoc />
        public void EnumerateItemsPerType(Action<string, IEnumerable<T>> itemTypeCallback)
        {
            foreach (var kvp in _itemsByType)
            {
                if (kvp.Value == null || kvp.Value.Count == 0)
                {
                    // skip empty markers
                    continue;
                }
 
                itemTypeCallback(kvp.Key, new ListConverter(kvp.Key, kvp.Value, _getInstance));
            }
        }
 
        /// <inheritdoc />
        public IEnumerable<TResult> GetCopyOnReadEnumerable<TResult>(Func<T, TResult> selector)
        {
            foreach (var cachedItem in _allCachedItems)
            {
                T? item = _getInstance(cachedItem);
                if (item is not null)
                {
                    yield return selector(item);
                }
            }
        }
 
        /// <inheritdoc />
        public IEnumerator<T> GetEnumerator()
        {
            foreach (var cachedItem in _allCachedItems)
            {
                T? item = _getInstance(cachedItem);
                if (item is not null)
                {
                    yield return item;
                }
            }
        }
 
        /// <inheritdoc />
        IEnumerator IEnumerable.GetEnumerator()
        {
            foreach (var cachedItem in _allCachedItems)
            {
                T? item = _getInstance(cachedItem);
                if (item is not null)
                {
                    yield return item;
                }
            }
        }
 
        /// <inheritdoc />
        public ICollection<T> GetItems(string itemType)
        {
            if (_itemsByType.TryGetValue(itemType, out ICollection<TCached>? items))
            {
                return new ListConverter(itemType, items, _getInstance);
            }
 
            return Array.Empty<T>();
        }
 
        /// <inheritdoc />
        public bool HasEmptyMarker(string itemType) => _itemsByType.Values.Any(list => list.Count == 0);
 
        /// <inheritdoc />
        public void ImportItems(IEnumerable<T> other) => throw new NotSupportedException();
 
        /// <inheritdoc />
        public void ImportItemsOfType(string itemType, IEnumerable<T> items) => throw new NotSupportedException();
 
        /// <inheritdoc />
        public bool Remove(T projectItem) => throw new NotSupportedException();
 
        /// <inheritdoc />
        public void RemoveItems(IEnumerable<T> other) => throw new NotSupportedException();
 
        /// <inheritdoc />
        public void Replace(T existingItem, T newItem) => throw new NotSupportedException();
 
        private sealed class ListConverter : ICollection<T>
        {
            private readonly string _itemType;
            private readonly ICollection<TCached> _list;
            private readonly Func<TCached, T?> _getInstance;
 
            public ListConverter(string itemType, ICollection<TCached> list, Func<TCached, T?> getInstance)
            {
                _itemType = itemType;
                _list = list;
                _getInstance = getInstance;
            }
 
            public int Count => _list.Count;
 
            public bool IsReadOnly => true;
 
            public void Add(T item) => throw new NotSupportedException();
 
            public void Clear() => throw new NotSupportedException();
 
            public bool Remove(T item) => throw new NotSupportedException();
 
            public bool Contains(T item)
            {
                return _list.Any(
                    cachedItem =>
                    {
                        if (MSBuildNameIgnoreCaseComparer.Default.Equals(cachedItem.EvaluatedIncludeEscaped, item.EvaluatedIncludeEscaped))
                        {
                            T? foundItem = _getInstance(cachedItem);
                            if (foundItem is not null && foundItem.Equals(item))
                            {
                                return true;
                            }
                        }
 
                        return false;
                    });
            }
 
            public void CopyTo(T[] array, int arrayIndex)
            {
                ErrorUtilities.VerifyCollectionCopyToArguments(array, nameof(array), arrayIndex, nameof(arrayIndex), _list.Count);
 
                int currentIndex = arrayIndex;
                foreach (var item in _list)
                {
                    T? instance = _getInstance(item);
                    if (instance != null)
                    {
                        array[currentIndex] = instance;
                        ++currentIndex;
                    }
                }
            }
 
            public IEnumerator<T> GetEnumerator()
            {
                foreach (var item in _list)
                {
                    T? instance = _getInstance(item);
                    if (instance != null)
                    {
                        yield return instance;
                    }
                }
            }
 
            IEnumerator IEnumerable.GetEnumerator()
            {
                foreach (var item in _list)
                {
                    T? instance = _getInstance(item);
                    if (instance != null)
                    {
                        yield return instance;
                    }
                }
            }
        }
    }
}