// 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(); } } }