File: VersionRangeComparer.cs
Web Access
Project: src\src\nuget-client\src\NuGet.Core\NuGet.Versioning\NuGet.Versioning.csproj (NuGet.Versioning)
// 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 NuGet.Shared;

namespace NuGet.Versioning
{
    /// <summary>
    /// A version range comparer capable of using different VersionComparers to check if ranges are equivalent.
    /// </summary>
    public class VersionRangeComparer : IVersionRangeComparer
    {
        private readonly IVersionComparer _versionComparer;

        /// <summary>
        /// Default version range comparer.
        /// </summary>
        public VersionRangeComparer()
            : this(VersionComparer.Default)
        {
        }

        /// <summary>
        /// Compare versions with a specific VersionComparison
        /// </summary>
        public VersionRangeComparer(VersionComparison versionComparison)
            : this(VersionComparer.Get(versionComparison))
        {
        }

        /// <summary>
        /// Compare versions with a specific IVersionComparer
        /// </summary>
        public VersionRangeComparer(IVersionComparer versionComparer)
        {
            if (versionComparer == null)
            {
                throw new ArgumentNullException(nameof(versionComparer));
            }

            _versionComparer = versionComparer;
        }

        /// <summary>
        /// Default Version comparer
        /// </summary>
        public static IVersionRangeComparer Default { get; } = new VersionRangeComparer(VersionComparer.Default);

        internal static IVersionRangeComparer Version { get; } = new VersionRangeComparer(VersionComparer.Version);

        /// <summary>
        /// Compare versions using the Version and Release
        /// </summary>
        public static IVersionRangeComparer VersionRelease { get; } = new VersionRangeComparer(VersionComparer.VersionRelease);

        internal static IVersionRangeComparer VersionReleaseMetadata { get; } = new VersionRangeComparer(VersionComparer.VersionReleaseMetadata);

        /// <summary>
        /// Gets the appropriate version range comparer based on the specified version comparison.
        /// </summary>
        /// <param name="versionComparison">The version comparison to use.</param>
        /// <returns>An instance of <see cref="IVersionRangeComparer"/>.</returns>
        public static IVersionRangeComparer Get(VersionComparison versionComparison)
        {
            return versionComparison switch
            {
                VersionComparison.Default => Default,
                VersionComparison.Version => Version,
                VersionComparison.VersionRelease => VersionRelease,
                VersionComparison.VersionReleaseMetadata => VersionReleaseMetadata,
                _ => new VersionRangeComparer(versionComparison)
            };
        }

        /// <summary>
        /// Checks if two version ranges are equivalent. This follows the rules of the version comparer
        /// when checking the bounds.
        /// </summary>
        public bool Equals(VersionRangeBase? x, VersionRangeBase? y)
        {
            if (ReferenceEquals(x, y))
            {
                return true;
            }

            if (ReferenceEquals(y, null)
                || ReferenceEquals(x, null))
            {
                return false;
            }

            return x.IsMinInclusive == y.IsMinInclusive
                && y.IsMaxInclusive == x.IsMaxInclusive
#pragma warning disable CS8604 // Possible null reference argument.
                // BCL missing nullable annotations on IEqualityComparer<T> before .NET 5
                && _versionComparer.Equals(y.MinVersion, x.MinVersion)
                && _versionComparer.Equals(y.MaxVersion, x.MaxVersion);
#pragma warning restore CS8604 // Possible null reference argument.
        }

        /// <summary>
        /// Creates a hash code based on all properties of the range. This follows the rules of the
        /// version comparer when comparing the version bounds.
        /// </summary>
        public int GetHashCode(VersionRangeBase obj)
        {
            if (ReferenceEquals(obj, null))
            {
                return 0;
            }

            var combiner = new HashCodeCombiner();

            combiner.AddObject(obj.IsMinInclusive);
            combiner.AddObject(obj.IsMaxInclusive);
            if (obj.HasLowerBound)
            {
                combiner.AddObject(_versionComparer.GetHashCode(obj.MinVersion));
            }
            if (obj.HasUpperBound)
            {
                combiner.AddObject(_versionComparer.GetHashCode(obj.MaxVersion));
            }

            return combiner.CombinedHash;
        }
    }
}