File: RuntimeModel\RuntimeDescription.cs
Web Access
Project: src\src\nuget-client\src\NuGet.Core\NuGet.Packaging\NuGet.Packaging.csproj (NuGet.Packaging)
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Linq;
using NuGet.Shared;

namespace NuGet.RuntimeModel
{
    /// <remarks>
    /// Immutable.
    /// </remarks>
    public sealed class RuntimeDescription : IEquatable<RuntimeDescription>
    {
        private static readonly IReadOnlyDictionary<string, RuntimeDependencySet> EmptyRuntimeDependencySets = new Dictionary<string, RuntimeDependencySet>();

        public string RuntimeIdentifier { get; }

        public IReadOnlyList<string> InheritedRuntimes { get; }

        /// <summary>
        /// RID specific package dependencies, keyed by <see cref="RuntimeDependencySet.Id"/>.
        /// </summary>
        public IReadOnlyDictionary<string, RuntimeDependencySet> RuntimeDependencySets { get; }

        public RuntimeDescription(string runtimeIdentifier)
            : this(runtimeIdentifier, null, null)
        {
        }

        public RuntimeDescription(string runtimeIdentifier, IEnumerable<string>? inheritedRuntimes)
            : this(runtimeIdentifier, inheritedRuntimes, null)
        {
        }

        public RuntimeDescription(string runtimeIdentifier, IEnumerable<RuntimeDependencySet>? runtimeDependencySets)
            : this(runtimeIdentifier, null, runtimeDependencySets)
        {
        }

        public RuntimeDescription(string runtimeIdentifier, IEnumerable<string>? inheritedRuntimes, IEnumerable<RuntimeDependencySet>? runtimeDependencySets)
            : this(
                runtimeIdentifier,
                inheritedRuntimes?.ToList(),
                runtimeDependencySets?.ToDictionary(d => d.Id, StringComparer.OrdinalIgnoreCase))
        {
        }

        private RuntimeDescription(string runtimeIdentifier, IReadOnlyList<string>? inheritedRuntimes, IReadOnlyDictionary<string, RuntimeDependencySet>? runtimeDependencySets)
        {
            RuntimeIdentifier = runtimeIdentifier;
            InheritedRuntimes = inheritedRuntimes is null or { Count: 0 } ? Array.Empty<string>() : inheritedRuntimes;
            RuntimeDependencySets = runtimeDependencySets is null or { Count: 0 } ? EmptyRuntimeDependencySets : runtimeDependencySets;
        }

        public bool Equals(RuntimeDescription? other)
        {
            if (ReferenceEquals(this, other))
            {
                return true;
            }

            if (other == null)
            {
                return false;
            }

            return string.Equals(other.RuntimeIdentifier, RuntimeIdentifier, StringComparison.Ordinal)
                && InheritedRuntimes.OrderedEquals(other.InheritedRuntimes, s => s, StringComparer.Ordinal, StringComparer.Ordinal)
                && RuntimeDependencySets.OrderedEquals(other.RuntimeDependencySets, p => p.Key, StringComparer.OrdinalIgnoreCase);
        }

        /// <summary>
        /// Merges the content of the other runtime description in to this runtime description
        /// </summary>
        /// <param name="other">The other description to merge in to this description</param>
        public static RuntimeDescription Merge(RuntimeDescription left, RuntimeDescription right)
        {
            if (!string.Equals(left.RuntimeIdentifier, right.RuntimeIdentifier, StringComparison.Ordinal))
            {
                throw new InvalidOperationException("TODO: Unable to merge runtimes, they do not have the same identifier");
            }

            // Merge #imports
            List<string> inheritedRuntimes;
            if (right.InheritedRuntimes.Count != 0 && left.InheritedRuntimes.Count == 0)
            {
                // Copy #imports from right
                inheritedRuntimes = new List<string>(right.InheritedRuntimes);
            }
            else
            {
                // Ignore the inherited runtimes from the right if there are inherited runtimes on the left.

                // Copy #imports from left (if any)
                inheritedRuntimes = new List<string>(left.InheritedRuntimes);
            }

            // Merge dependency sets
            var newSets = new Dictionary<string, RuntimeDependencySet>(StringComparer.OrdinalIgnoreCase);
            foreach (var dependencySet in left.RuntimeDependencySets.Values)
            {
                newSets[dependencySet.Id] = dependencySet;
            }

            // Overwrite with things from the right
            foreach (var dependencySet in right.RuntimeDependencySets.Values)
            {
                newSets[dependencySet.Id] = dependencySet;
            }

            return new RuntimeDescription(
                left.RuntimeIdentifier,
                // If collections are empty, pass null to avoid allocations.
                inheritedRuntimes.Count == 0 ? null : inheritedRuntimes,
                newSets.Count == 0 ? null : newSets);
        }

        public override bool Equals(object? obj)
        {
            return Equals(obj as RuntimeDescription);
        }

        public override int GetHashCode()
        {
            var combiner = new HashCodeCombiner();

            combiner.AddObject(RuntimeIdentifier);
            combiner.AddSequence(InheritedRuntimes);
            combiner.AddDictionary(RuntimeDependencySets);

            return combiner.CombinedHash;
        }

        public override string ToString()
        {
            return $"({RuntimeIdentifier}: (#imports: {string.Join(",", InheritedRuntimes)}); {string.Join(",", RuntimeDependencySets)})";
        }
    }
}