File: Instance\ImmutableProjectCollections\ImmutableProjectMetadataCollectionConverter.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;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using Microsoft.Build.Collections;
using Microsoft.Build.Evaluation;
using Microsoft.Build.Shared;

namespace Microsoft.Build.Instance
{
    internal class ImmutableProjectMetadataCollectionConverter : IReadOnlyDictionary<string, string>
    {
        /// <summary>
        /// Immutable project item.
        /// </summary>
        private readonly ProjectItem _linkedProjectItem;

        /// <summary>
        /// Properties in the underlying dictionary.
        /// This dictionary contains all properties that are directly defined in the project item.
        /// </summary>
        private readonly IDictionary<string, ProjectMetadata> _properties;

        /// <summary>
        /// A cached immutable dictionary containing all direct properties.
        /// </summary>
        private ImmutableDictionary<string, string>? _convertedPropertiesDictionary;

        public ImmutableProjectMetadataCollectionConverter(
            ProjectItem linkedProjectItem,
            IDictionary<string, ProjectMetadata> properties)
        {
            _linkedProjectItem = linkedProjectItem ?? throw new ArgumentNullException(nameof(linkedProjectItem));
            _properties = properties ?? throw new ArgumentNullException(nameof(properties));
        }

        public IEnumerable<string> Keys => _properties.Keys;

        public IEnumerable<string> Values => _properties.Values.Select(m => m.EvaluatedValueEscaped);

        public int Count => _properties.Count;

        public string this[string key]
        {
            get
            {
                if (_properties.ContainsKey(key))
                {
                    return EscapingUtilities.Escape(_linkedProjectItem.GetMetadataValue(key));
                }

                throw new KeyNotFoundException($"The metadata '{key}' does not exist in the project item '{_linkedProjectItem.ItemType}'.");
            }

            set => throw new NotSupportedException("Cannot set value in an immutable collection.");
        }

        public bool ContainsKey(string key) => _properties.ContainsKey(key);

        public bool TryGetValue(string key, out string value)
        {
            if (ContainsKey(key))
            {
                value = EscapingUtilities.Escape(_linkedProjectItem.GetMetadataValue(key));
                return true;
            }

            value = null!;
            return false;
        }

        public IEnumerator<KeyValuePair<string, string>> GetEnumerator()
        {
            foreach (string name in _properties.Keys)
            {
                yield return new KeyValuePair<string, string>(
                    name,
                    EscapingUtilities.Escape(_linkedProjectItem.GetMetadataValue(name)));
            }
        }

        IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

        /// <summary>
        /// Gets value of any property in the project item. (including direct properties, properties from item type definitions and built-in properties.)
        /// </summary>
        /// <param name="name">The name of the property.</param>
        /// <returns>Unescaped value of the property, or an empty string.</returns>
        public string GetExtendedPropertyValue(string name) => _linkedProjectItem.GetMetadataValue(name);

        /// <summary>
        /// Converts the collection to an immutable dictionary.
        /// </summary>
        /// <returns>An immutable dictionary containing all direct properties.</returns>
        public ImmutableDictionary<string, string> ToImmutableDictionary()
        {
            if (_convertedPropertiesDictionary is null)
            {
                var newDictionary = _properties.ToImmutableDictionary(
                    kvp => kvp.Key,
                    kvp => EscapingUtilities.Escape(_linkedProjectItem.GetMetadataValue(kvp.Key)),
                    MSBuildNameIgnoreCaseComparer.Default);

                _ = Interlocked.CompareExchange(ref _convertedPropertiesDictionary, newDictionary, null);
            }

            return _convertedPropertiesDictionary;
        }
    }
}