Files
ZuneModdingHelper/ZuneModCore/ReleaseVersion.cs
T

497 lines
20 KiB
C#
Raw Normal View History

2021-11-16 01:41:09 -06:00
using CommunityToolkit.Diagnostics;
using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
2022-03-01 14:14:11 -06:00
using System.Text.Json;
using System.Text.Json.Serialization;
2021-11-16 01:41:09 -06:00
2022-03-01 14:14:11 -06:00
namespace ZuneModCore
2021-11-16 01:41:09 -06:00
{
2022-03-01 14:14:11 -06:00
[JsonConverter(typeof(ReleaseVersionJsonConverter))]
2021-11-16 01:41:09 -06:00
public sealed class ReleaseVersion : ICloneable, IComparable, IComparable<ReleaseVersion?>, IEquatable<ReleaseVersion?>
{
/// <summary>
/// Gets the value of the major component of the version number for the current <see cref="ReleaseVersion"/> object.
/// </summary>
public int Major { get; set; }
/// <summary>
/// Gets the value of the minor component of the version number for the current <see cref="ReleaseVersion"/> object.
/// </summary>
public int Minor { get; set; }
/// <summary>
/// Gets the value of the build component of the version number for the current <see cref="ReleaseVersion"/> object.
/// </summary>
public int Build { get; set; }
/// <summary>
/// Gets the value of the revision component of the version number for the current <see cref="ReleaseVersion"/> object.
/// </summary>
public int Revision { get; set; }
private Phase _Phase = Phase.Unknown;
/// <summary>
/// Gets the value of the release phase component of the version number for the current <see cref="ReleaseVersion"/> object.
/// </summary>
public Phase Phase
{
set => _Phase = value;
get
{
if (_Phase == Phase.Unknown)
{
#if DEBUG
_Phase = Phase.Debug;
#else
_Phase = Phase.Production;
#endif
}
return _Phase;
}
}
/// <summary>
/// Initializes a new instance of the <see cref="ReleaseVersion"/> class.
/// </summary>
public ReleaseVersion()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ReleaseVersion"/> class with the specified major,
/// minor, build, revision, and release phase.
/// </summary>
/// <param name="major">
/// The major version number.
/// </param>
/// <param name="minor">
/// The minor version number.
/// </param>
/// <param name="build">
/// The build version number.
/// </param>
/// <param name="revision">
/// The revision version number.
/// </param>
/// <param name="phase">
/// The release phase.
/// </param>
/// <exception cref="ArgumentOutOfRangeException">
/// <paramref name="major"/>, <paramref name="minor"/>, <paramref name="build"/>, or <paramref name="revision"/> is less than zero.
/// </exception>
public ReleaseVersion(int major, int minor, int build = 0, int revision = 0, Phase phase = Phase.Unknown)
{
Guard.IsGreaterThanOrEqualTo(major, 0, nameof(major));
Guard.IsGreaterThanOrEqualTo(minor, 0, nameof(minor));
Guard.IsGreaterThanOrEqualTo(build, 0, nameof(build));
Guard.IsGreaterThanOrEqualTo(revision, 0, nameof(revision));
Major = major;
Minor = minor;
Build = build;
Revision = revision;
Phase = phase;
}
/// <summary>
/// Returns a new <see cref="ReleaseVersion"/> object whose value is the same as the current <see cref="ReleaseVersion"/>
/// object.
/// </summary>
/// <returns>
/// A new <see cref="object"/> whose values are a copy of the current <see cref="ReleaseVersion"/> object.
/// </returns>
public object Clone()
{
return new ReleaseVersion(Major, Minor, Build, Revision, Phase);
}
/// <summary>
/// Compares the current <see cref="ReleaseVersion"/> object to a specified object and returns
/// an indication of their relative values.
/// </summary>
/// <param name="version">
/// An object to compare, or null.
/// </param>
/// <returns>
/// A signed integer that indicates the relative values of the two objects, as shown
/// in the following table.
/// Return value – Meaning
/// Less than zero – The current <see cref="ReleaseVersion"/> object is a version before <paramref name="version"/>.
/// Zero – The current <see cref="ReleaseVersion"/> object is the same version as <paramref name="version"/>.
/// Greater than zero – The current <see cref="ReleaseVersion"/> object is a version subsequent
/// to <paramref name="version"/>, or <paramref name="version"/> is null.
/// </returns>
/// <exception cref="NullReferenceException">
/// <paramref name="version"/> is not of type <see cref="ReleaseVersion"/>.
/// </exception>
public int CompareTo(object? version)
{
if (version == null)
{
return 1;
}
if (version is ReleaseVersion v)
{
return CompareTo(v);
}
throw new ArgumentException(nameof(version) + " must be of type " + nameof(ReleaseVersion));
}
/// <summary>
/// Compares the current <see cref="ReleaseVersion"/> object to a specified <see cref="ReleaseVersion"/> object
/// and returns an indication of their relative values.
/// </summary>
/// <param name="value">
/// A <see cref="ReleaseVersion"/> object to compare to the current <see cref="ReleaseVersion"/> object, or null.
/// </param>
/// <returns>
/// A signed integer that indicates the relative values of the two objects, as shown
/// in the following table.
/// Return value – Meaning
/// Less than zero – The current <see cref="ReleaseVersion"/> object is a version before <paramref name="version"/>.
/// Zero – The current <see cref="ReleaseVersion"/> object is the same version as <paramref name="version"/>.
/// Greater than zero – The current <see cref="ReleaseVersion"/> object is a version subsequent
/// to <paramref name="version"/>, or <paramref name="version"/> is null.
/// </returns>
public int CompareTo(ReleaseVersion? value)
{
return
ReferenceEquals(value, this) ? 0 :
value is null ? 1 :
Major != value.Major ? (Major > value.Major ? 1 : -1) :
Minor != value.Minor ? (Minor > value.Minor ? 1 : -1) :
Build != value.Build ? (Build > value.Build ? 1 : -1) :
Revision != value.Revision ? (Revision > value.Revision ? 1 : -1) :
Phase != value.Phase? ((byte)Phase > (byte)value.Phase ? 1 : -1) :
0;
}
/// <summary>
/// Returns a value indicating whether the current <see cref="ReleaseVersion"/> object is equal
/// to a specified object.
/// </summary>
/// <param name="obj">
/// An object to compare with the current <see cref="ReleaseVersion"/> object, or null.
/// </param>
/// <returns>
/// <c>true</c> if the current <see cref="ReleaseVersion"/> object and <paramref name="obj"/>
/// are both <see cref="ReleaseVersion"/> objects,
/// and every component of the current <see cref="ReleaseVersion"/> object matches the corresponding
/// component of <paramref name="obj"/>; otherwise, <c>false</c>.
/// </returns>
public override bool Equals(object? obj)
{
return Equals(obj as ReleaseVersion);
}
/// <summary>
/// Returns a value indicating whether the current <see cref="ReleaseVersion"/> object and a specified
/// <see cref="ReleaseVersion"/> object represent the same value.
/// </summary>
/// <param name="obj">
/// A <see cref="ReleaseVersion"/> object to compare to the current <see cref="ReleaseVersion"/> object, or null.
/// </param>
/// <returns>
/// <c>true</c> if every component of the current <see cref="ReleaseVersion"/> object matches the corresponding
/// component of the <paramref name="obj"/> parameter; otherwise, <c>false</c>.
/// </returns>
public bool Equals(ReleaseVersion? obj)
{
return ReferenceEquals(obj, this) ||
(obj is not null &&
Major == obj.Major &&
Minor == obj.Minor &&
Build == obj.Build &&
Revision == obj.Revision &&
Phase == obj.Phase);
}
/// <summary>
/// Returns a hash code for the current <see cref="ReleaseVersion"/> object.
/// </summary>
/// <returns>
/// A 32-bit signed integer hash code.
/// </returns>
public override int GetHashCode()
{
return HashCode.Combine(Major, Minor, Build, Revision, Phase);
}
/// <summary>
/// Determines whether two specified <see cref="ReleaseVersion"/> objects are equal.
/// </summary>
/// <param name="v1">
/// The first <see cref="ReleaseVersion"/> object.
/// </param>
/// <param name="v2">
/// The second <see cref="ReleaseVersion"/> object.
/// </param>
/// <returns>
/// true if v1 equals v2; otherwise, false.
/// </returns>
// Force inline as the true/false ternary takes it above ALWAYS_INLINE size even though the asm ends up smaller
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(ReleaseVersion? v1, ReleaseVersion? v2)
{
// Test "right" first to allow branch elimination when inlined for null checks (== null)
// so it can become a simple test
if (v2 is null)
{
// return true/false not the test result https://github.com/dotnet/runtime/issues/4207
return (v1 is null) ? true : false;
}
// Quick reference equality test prior to calling the virtual Equality
return ReferenceEquals(v2, v1) ? true : v2.Equals(v1);
}
/// <summary>
/// Determines whether the first specified <see cref="ReleaseVersion"/> object is greater than
/// the second specified <see cref="ReleaseVersion"/> object.
/// </summary>
/// <param name="v1">
/// The first <see cref="ReleaseVersion"/> object.
/// </param>
/// <param name="v2">
/// The second <see cref="ReleaseVersion"/> object.
/// </param>
/// <returns>
/// true if v1 is greater than v2; otherwise, false.
/// </returns>
public static bool operator >(ReleaseVersion? v1, ReleaseVersion? v2)
=> v2 < v1;
/// <summary>
/// Determines whether the first specified <see cref="ReleaseVersion"/> object is greater than
/// or equal to the second specified <see cref="ReleaseVersion"/> object.
/// </summary>
/// <param name="v1">
/// The first <see cref="ReleaseVersion"/> object.
/// </param>
/// <param name="v2">
/// The second <see cref="ReleaseVersion"/> object.
/// </param>
/// <returns>
/// true if v1 is greater than or equal to v2; otherwise, false.
/// </returns>
public static bool operator >=(ReleaseVersion? v1, ReleaseVersion? v2)
=> v2 <= v1;
/// <summary>
/// Determines whether two specified <see cref="ReleaseVersion"/> objects are not equal.
/// </summary>
/// <param name="v1">
/// The first <see cref="ReleaseVersion"/> object.
/// </param>
/// <param name="v2">
/// The second <see cref="ReleaseVersion"/> object.
/// </param>
/// <returns>
/// true if v1 does not equal v2; otherwise, false.
/// </returns>
public static bool operator !=(ReleaseVersion? v1, ReleaseVersion? v2)
=> !(v1 == v2);
/// <summary>
/// Determines whether the first specified <see cref="ReleaseVersion"/> object is less than the
/// second specified <see cref="ReleaseVersion"/> object.
/// </summary>
/// <param name="v1">
/// The first <see cref="ReleaseVersion"/> object.
/// </param>
/// <param name="v2">
/// The second <see cref="ReleaseVersion"/> object.
/// </param>
/// <returns>
/// true if v1 is less than v2; otherwise, false.
/// </returns>
public static bool operator <(ReleaseVersion? v1, ReleaseVersion? v2)
{
if (v1 is null)
{
return !(v2 is null);
}
return v1.CompareTo(v2) < 0;
}
/// <summary>
/// Determines whether the first specified <see cref="ReleaseVersion"/> object is less than or
/// equal to the second <see cref="ReleaseVersion"/> object.
/// </summary>
/// <param name="v1">
/// The first <see cref="ReleaseVersion"/> object.
/// </param>
/// <param name="v2">
/// The second <see cref="ReleaseVersion"/> object.
/// </param>
/// <returns>
/// true if v1 is less than or equal to v2; otherwise, false.
/// </returns>
public static bool operator <=(ReleaseVersion? v1, ReleaseVersion? v2)
{
if (v1 is null)
{
return true;
}
return v1.CompareTo(v2) <= 0;
}
/// <summary>
/// Converts the string representation of a version number to an equivalent <see cref="ReleaseVersion"/>
/// object.
/// </summary>
/// <param name="input">
/// A string containing the major, minor, build, and revision numbers, where each
/// number is delimited with a period character ('.'), followed by a dash character
/// ('-') and the release phase.
/// </param>
/// <returns>
/// An object that is equivalent to the version number specified in the input parameter.
/// </returns>
public static ReleaseVersion Parse(string input)
{
ReleaseVersion version = new();
string versionPart = input;
int phaseIdx = input.IndexOf('-');
if (phaseIdx != -1)
{
versionPart = input[..phaseIdx];
string phaseComp = input[(phaseIdx + 1)..];
if (phaseComp.ToLowerInvariant() == "rc")
version.Phase = Phase.ReleaseCandidate;
else
version.Phase = Enum.Parse<Phase>(phaseComp, true);
}
string[] components = versionPart.Split('.');
Guard.IsGreaterThanOrEqualTo(components.Length, 2, nameof(components));
if (components.Length >= 2)
{
version.Major = int.Parse(components[0]);
version.Minor = int.Parse(components[1]);
if (components.Length >= 3)
{
version.Build = int.Parse(components[2]);
if (components.Length >= 4)
{
version.Revision = int.Parse(components[3]);
}
}
}
return version;
}
/// <summary>
/// Converts the value of the current <see cref="ReleaseVersion"/> object to its equivalent <see cref="string"/>
/// representation.
/// </summary>
/// <returns>
/// The string representation of the values of the major, minor, build, and
/// revision components of the current <see cref="ReleaseVersion"/> object, as depicted in the
/// following format. Each component is separated by a period character ('.'),
/// and the release phase is appended to the end after a dash character ('-'). Square
/// brackets ('[' and ']') indicate a component that will not appear in the return
/// value if the component is not defined: <c>major.minor[.build[.revision]][-phase]</c> For example,
/// if you create a <see cref="ReleaseVersion"/> object using the constructor <c>Version(1, 1)</c>, the
/// returned string is <c>"1.1"</c>. If you create a <see cref="ReleaseVersion"/> object using the constructor
/// <c>Version(1, 3, 4, 2, Phase.Beta)</c>, the returned string is <c>"1.3.4.2-beta"</c>.
/// </returns>
public override string ToString() => ToString(true);
/// <inheritdoc cref="ToString"/>
/// <param name="includeAllComponents">
/// When set to <c>true</c>, the string will be formatted as <c>major.minor.build.revision-phase</c>,
/// even if an optional component is not present.
/// </param>
public string ToString(bool includeAllComponents)
{
const char numDelim = '.';
const char phaseDelim = '-';
string output = Major.ToString() + numDelim + Minor.ToString();
if (Build > 0 || includeAllComponents)
{
output += numDelim + Build.ToString();
if (Revision > 0 || includeAllComponents)
{
output += numDelim + Revision.ToString();
}
}
if ((Phase != Phase.Unknown && Phase != Phase.Production) || includeAllComponents)
{
output += phaseDelim + (Phase == Phase.ReleaseCandidate ? "rc" : Phase.ToString().ToLowerInvariant());
}
return output;
}
/// <summary>
/// Tries to convert the string representation of a version number to an equivalent
/// <see cref="ReleaseVersion"/> object, and returns a value that indicates whether the conversion
/// succeeded.
/// </summary>
/// <param name="input">
/// A string that contains a version number to convert.
/// </param>
/// <param name="result">
/// When this method returns, contains the <see cref="ReleaseVersion"/> equivalent of the number
/// that is contained in input, if the conversion succeeded. If input is null, <see cref="String.Empty"/>,
/// or if the conversion fails, result is null when the method returns.
/// </param>
/// <returns>
/// true if the input parameter was converted successfully; otherwise, false.
/// </returns>
public static bool TryParse([NotNullWhen(true)] string? input, [NotNullWhen(true)] out ReleaseVersion? result)
{
try
{
Guard.IsNotNull(input, nameof(input));
result = Parse(input);
return true;
}
catch
{
result = null;
return false;
}
}
}
public enum Phase : byte
{
Unknown,
Debug,
Alpha,
Beta,
ReleaseCandidate,
Production
}
2022-03-01 14:14:11 -06:00
internal class ReleaseVersionJsonConverter : JsonConverter<ReleaseVersion>
{
public override ReleaseVersion Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return ReleaseVersion.Parse(reader.GetString()!);
}
public override void Write(Utf8JsonWriter writer, ReleaseVersion value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString());
}
}
2021-11-16 01:41:09 -06:00
}