// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
namespace WixToolset.Versioning
{
using System;
using System.Collections.Generic;
///
/// WiX Toolset's representation of a version designed to support
/// a different version types including 4-part versions with very
/// large numbers and semantic versions with 4-part versions with
/// or without leading "v" indicators.
///
public class WixVersion : IComparable, IComparable, IEquatable
{
///
/// Gets the prefix of the version if present when parsed. Usually, 'v' or 'V'.
///
public char? Prefix { get; set; }
///
/// Gets or sets the major version.
///
public uint Major { get; set; }
///
/// Gets or sets the minor version.
///
public uint Minor { get; set; }
///
/// Gets or sets the patch version.
///
public uint Patch { get; set; }
///
/// Gets or sets the revision version.
///
public uint Revision { get; set; }
///
/// Gets or sets whether the version did not parse correctly.
///
public bool Invalid { get; set; }
///
/// Gets or sets whether the major version was defined.
///
public bool HasMajor { get; set; }
///
/// Gets or sets the whether the minor version was defined.
///
public bool HasMinor { get; set; }
///
/// Gets or sets the whether the patch version was defined.
///
public bool HasPatch { get; set; }
///
/// Gets or sets the whether the revision version was defined.
///
public bool HasRevision { get; set; }
///
/// Gets or sets the labels in the version.
///
public WixVersionLabel[] Labels { get; set; }
///
/// Gets or sets the metadata in the version.
///
public string Metadata { get; set; }
///
/// Compare to another WixVersion.
///
/// WixVersion to compare.
/// A comparison between versions.
public int CompareTo(WixVersion version)
{
return WixVersionComparer.Default.Compare(this, version);
}
///
/// Compare to another object.
///
/// Object to compare.
/// A comparison between objects.
public int CompareTo(object version)
{
return WixVersionComparer.Default.Compare(this, version as WixVersion);
}
///
/// Returns a value indicating whether the current System.Version object is equal to a specified object.
///
/// An WixVersion to compare with the current WixVersion object, or null.
///
/// true if the current WixVersion object and obj are both WixVersion objects,
/// and every component of the current System.Version object matches the corresponding
/// component of obj; otherwise, false.
///
public bool Equals(WixVersion version)
{
return WixVersionComparer.Default.Equals(this, version);
}
///
/// Returns a value indicating whether the current WixVersion object is equal to a specified object.
///
/// An object to compare with the current WixVersion object, or null.
///
/// true if the current WixVersion object and obj are both WixVersion objects,
/// and every component of the current System.Version object matches the corresponding
/// component of obj; otherwise, false.
///
public override bool Equals(object obj)
{
return WixVersionComparer.Default.Equals(this, obj as WixVersion);
}
///
/// Returns a hash code for the current WixVersion object.
///
/// A 32-bit signed integer hash code.
public override int GetHashCode()
{
return WixVersionComparer.Default.GetHashCode(this);
}
///
/// Parse a string value into a WixVersion. The returned version may be invalid.
///
/// String value to parse into a version.
/// Parsed version.
public static WixVersion Parse(string parse)
{
var version = new WixVersion();
var labels = new List();
var start = 0;
var end = parse.Length;
if (end > 0 && (parse[0] == 'v' || parse[0] == 'V'))
{
version.Prefix = parse[0];
++start;
}
var partBegin = start;
var partEnd = start;
var lastPart = false;
var trailingDot = false;
var invalid = false;
var currentPart = 0;
var parsedVersionNumber = false;
var expectedReleaseLabels = false;
// Parse version number
while (start < end)
{
trailingDot = false;
// Find end of part.
for (; ; )
{
if (partEnd >= end)
{
lastPart = true;
break;
}
var ch = parse[partEnd];
switch (ch)
{
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
++partEnd;
continue;
case '.':
trailingDot = true;
break;
case '-':
case '+':
lastPart = true;
break;
default:
invalid = true;
break;
}
break;
}
var partLength = partEnd - partBegin;
if (invalid || partLength <= 0)
{
invalid = true;
break;
}
// Parse version part.
var s = parse.Substring(partBegin, partLength);
if (!UInt32.TryParse(s, out var part))
{
invalid = true;
break;
}
switch (currentPart)
{
case 0:
version.Major = part;
version.HasMajor = true;
break;
case 1:
version.Minor = part;
version.HasMinor = true;
break;
case 2:
version.Patch = part;
version.HasPatch = true;
break;
case 3:
version.Revision = part;
version.HasRevision = true;
break;
}
if (trailingDot)
{
++partEnd;
}
partBegin = partEnd;
++currentPart;
if (4 <= currentPart || lastPart)
{
parsedVersionNumber = true;
break;
}
}
invalid |= !parsedVersionNumber || trailingDot;
if (!invalid && partBegin < end && parse[partBegin] == '-')
{
partBegin = partEnd = partBegin + 1;
expectedReleaseLabels = true;
lastPart = false;
}
while (expectedReleaseLabels && partBegin < end)
{
trailingDot = false;
// Find end of part.
for (; ; )
{
if (partEnd >= end)
{
lastPart = true;
break;
}
var ch = parse[partEnd];
if (ch >= '0' && ch <= '9' ||
ch >= 'A' && ch <= 'Z' ||
ch >= 'a' && ch <= 'z' ||
ch == '-')
{
++partEnd;
continue;
}
else if (ch == '+')
{
lastPart = true;
}
else if (ch == '.')
{
trailingDot = true;
}
else
{
invalid = true;
}
break;
}
var partLength = partEnd - partBegin;
if (invalid || partLength <= 0)
{
invalid = true;
break;
}
WixVersionLabel label;
var partString = parse.Substring(partBegin, partLength);
if (UInt32.TryParse(partString, out var numericPart))
{
label = new WixVersionLabel(partString, numericPart);
}
else
{
label = new WixVersionLabel(partString);
}
labels.Add(label);
if (trailingDot)
{
++partEnd;
}
partBegin = partEnd;
if (lastPart)
{
break;
}
}
invalid |= expectedReleaseLabels && (labels.Count == 0 || trailingDot);
if (!invalid && partBegin < end)
{
if (parse[partBegin] == '+')
{
version.Metadata = parse.Substring(partBegin + 1);
}
else
{
invalid = true;
}
}
version.Labels = labels.Count == 0 ? null : labels.ToArray();
if (invalid)
{
// If the prefix was parsed but the rest of the version was
// invalid, store the full invalid version in the Metadata
// and clear the prefix.
if (version.Prefix.HasValue && partBegin == 1)
{
version.Prefix = null;
version.Metadata = parse;
}
else // store the remaining invalid content in Metadata.
{
version.Metadata = (partBegin < end) ? parse.Substring(partBegin) : String.Empty;
}
version.Invalid = true;
}
return version;
}
///
/// Tries to parse a string value into a valid WixVersion.
///
/// String value to parse into a version.
/// Parsed version.
/// True if the version was successfully parsed, or false otherwise.
public static bool TryParse(string parse, out WixVersion version)
{
version = WixVersion.Parse(parse);
if (version.Invalid)
{
version = null;
return false;
}
return true;
}
///
/// Determines whether two specified WixVersion objects are equal.
///
/// The first WixVersion object.
/// The second WixVersion object.
/// true if v1 equals v2; otherwise, false.
public static bool operator ==(WixVersion v1, WixVersion v2)
{
return WixVersionComparer.Default.Equals(v1, v2);
}
///
/// Determines whether two specified System.Version objects are not equal.
///
/// The first WixVersion object.
/// The second WixVersion object.
/// true if v1 does not equal v2; otherwise, false.
public static bool operator !=(WixVersion v1, WixVersion v2)
{
return !WixVersionComparer.Default.Equals(v1, v2);
}
///
/// Determines whether the first specified System.Version object is less than the second specified System.Version object.
///
/// The first WixVersion object.
/// The second WixVersion object.
/// true if v1 is less than v2; otherwise, false.
/// v1 is null.
public static bool operator <(WixVersion v1, WixVersion v2)
{
return WixVersionComparer.Default.Compare(v1, v2) == -1;
}
///
/// Determines whether the first specified System.Version object is greater than the second specified System.Version object.
///
/// The first WixVersion object.
/// The second WixVersion object.
/// true if v1 is greater than v2; otherwise, false.
public static bool operator >(WixVersion v1, WixVersion v2)
{
return WixVersionComparer.Default.Compare(v1, v2) == 1;
}
///
/// Determines whether the first specified System.Version object is less than or equal to the second System.Version object.
///
/// The first WixVersion object.
/// The second WixVersion object.
/// true if v1 is less than or equal to v2; otherwise, false.
/// v1 is null.
public static bool operator <=(WixVersion v1, WixVersion v2)
{
var result = WixVersionComparer.Default.Compare(v1, v2);
return result == 0 || result == -1;
}
///
/// Determines whether the first specified System.Version object is greater than or equal to the second specified System.Version object.
///
/// The first WixVersion object.
/// The second WixVersion object.
/// true if v1 is greater than or equal to v2; otherwise, false.
public static bool operator >=(WixVersion v1, WixVersion v2)
{
var result = WixVersionComparer.Default.Compare(v1, v2);
return result == 0 || result == 1;
}
}
}