// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Text;
namespace EpicGames.Core
{
///
/// Stores a numeric version consisting of any number of components.
///
[Serializable]
public class VersionNumber : IComparable
{
///
/// Set of delimiters for version numbers
///
static readonly char[] s_delimiters = ['.', ','];
///
/// The individual version components
///
public IReadOnlyList Components => _components;
readonly int[] _components;
///
/// Constructor
///
/// The individual version components. At least one value must be given.
public VersionNumber(params int[] components)
{
if (components.Length == 0)
{
throw new InvalidOperationException("Version number must have at least one component");
}
_components = components;
}
///
/// Returns the component at the given index
///
/// The zero-based component index to return
/// The component at the given index
public int GetComponent(int idx)
{
return _components[idx];
}
///
/// Tests two objects for equality. VersionNumber behaves like a value type.
///
/// Object to compare against
/// True if the objects are equal, false otherwise.
public override bool Equals(object? obj)
{
VersionNumber? version = obj as VersionNumber;
return version is not null && this == version;
}
///
/// Returns a hash of the version number.
///
/// A hash value for the version number.
public override int GetHashCode()
{
int result = 5831;
for (int idx = 0; idx < _components.Length; idx++)
{
result = (result * 33) + _components[idx];
}
return result;
}
///
/// Compares whether two versions are equal.
///
/// The first version number
/// The second version number
/// True if the versions are equal.
public static bool operator ==(VersionNumber? lhs, VersionNumber? rhs)
{
return lhs is null ? rhs is null : rhs is not null && Compare(lhs, rhs) == 0;
}
///
/// Compares whether two versions are not equal.
///
/// The first version number
/// The second version number
/// True if the versions are not equal.
public static bool operator !=(VersionNumber? lhs, VersionNumber? rhs)
{
return !(lhs == rhs);
}
///
/// Compares whether one version is less than another.
///
/// The first version number
/// The second version number
/// True if the first version is less than the second.
public static bool operator <(VersionNumber lhs, VersionNumber rhs)
{
return Compare(lhs, rhs) < 0;
}
///
/// Compares whether one version is less or equal to another.
///
/// The first version number
/// The second version number
/// True if the first version is less or equal to the second.
public static bool operator <=(VersionNumber lhs, VersionNumber rhs)
{
return Compare(lhs, rhs) <= 0;
}
///
/// Compares whether one version is greater than another.
///
/// The first version number
/// The second version number
/// True if the first version is greater than the second.
public static bool operator >(VersionNumber lhs, VersionNumber rhs)
{
return Compare(lhs, rhs) > 0;
}
///
/// Compares whether one version is greater or equal to another.
///
/// The first version number
/// The second version number
/// True if the first version is greater or equal to the second.
public static bool operator >=(VersionNumber lhs, VersionNumber rhs)
{
return Compare(lhs, rhs) >= 0;
}
///
/// Comparison function for IComparable
///
/// Other version number to compare to
/// A negative value if this version is before Other, a positive value if this version is after Other, and zero otherwise.
public int CompareTo(VersionNumber? other)
{
return other is null ? 1 : Compare(this, other);
}
///
/// Compares two version numbers and returns an integer indicating their order
///
/// The first version to check
/// The second version to check
/// A negative value if Lhs is before Rhs, a positive value if Lhs is after Rhs, and zero otherwise.
public static int Compare(VersionNumber lhs, VersionNumber rhs)
{
for (int idx = 0; ; idx++)
{
if (idx == lhs._components.Length)
{
if (idx == rhs._components.Length)
{
return 0;
}
else
{
return -1;
}
}
else
{
if (idx == rhs._components.Length)
{
return +1;
}
else if (lhs._components[idx] != rhs._components[idx])
{
return lhs._components[idx] - rhs._components[idx];
}
}
}
}
///
/// Parses the version number from a string
///
/// The string to parse
/// A version number object
public static VersionNumber Parse(string text)
{
List components = [];
foreach (string textElement in text.Split(s_delimiters))
{
components.Add(Int32.Parse(textElement));
}
return new VersionNumber([.. components]);
}
///
/// Parses the version number from a string
///
/// The string to parse
/// Variable to receive the parsed version number
/// A version number object
public static bool TryParse(string text, [NotNullWhen(true)] out VersionNumber? outNumber)
{
List components = [];
foreach (string textElement in text.Split(s_delimiters))
{
int component;
if (!Int32.TryParse(textElement, out component))
{
outNumber = null;
return false;
}
components.Add(component);
}
outNumber = new VersionNumber([.. components]);
return true;
}
///
/// Returns a string version number, eg. 1.4
///
/// The stringized version number
public override string ToString()
{
StringBuilder result = new StringBuilder();
if (_components.Length > 0)
{
result.Append(_components[0]);
for (int idx = 1; idx < _components.Length; idx++)
{
result.Append('.');
result.Append(_components[idx]);
}
}
return result.ToString();
}
}
}