e79aa3c0ed
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
531 lines
18 KiB
C#
531 lines
18 KiB
C#
// ==++==
|
||
//
|
||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||
//
|
||
// ==--==
|
||
/*============================================================
|
||
**
|
||
** File: Version
|
||
**
|
||
**
|
||
** Purpose:
|
||
**
|
||
**
|
||
===========================================================*/
|
||
namespace System {
|
||
|
||
using System.Diagnostics.Contracts;
|
||
using System.Text;
|
||
using CultureInfo = System.Globalization.CultureInfo;
|
||
using NumberStyles = System.Globalization.NumberStyles;
|
||
|
||
// A Version object contains four hierarchical numeric components: major, minor,
|
||
// build and revision. Build and revision may be unspecified, which is represented
|
||
// internally as a -1. By definition, an unspecified component matches anything
|
||
// (both unspecified and specified), and an unspecified component is "less than" any
|
||
// specified component.
|
||
|
||
[Serializable]
|
||
[System.Runtime.InteropServices.ComVisible(true)]
|
||
public sealed class Version : ICloneable, IComparable
|
||
#if GENERICS_WORK
|
||
, IComparable<Version>, IEquatable<Version>
|
||
#endif
|
||
{
|
||
// AssemblyName depends on the order staying the same
|
||
private int _Major;
|
||
private int _Minor;
|
||
private int _Build = -1;
|
||
private int _Revision = -1;
|
||
private static readonly char[] SeparatorsArray = new char[] { '.' };
|
||
|
||
public Version(int major, int minor, int build, int revision) {
|
||
if (major < 0)
|
||
throw new ArgumentOutOfRangeException("major",Environment.GetResourceString("ArgumentOutOfRange_Version"));
|
||
|
||
if (minor < 0)
|
||
throw new ArgumentOutOfRangeException("minor",Environment.GetResourceString("ArgumentOutOfRange_Version"));
|
||
|
||
if (build < 0)
|
||
throw new ArgumentOutOfRangeException("build",Environment.GetResourceString("ArgumentOutOfRange_Version"));
|
||
|
||
if (revision < 0)
|
||
throw new ArgumentOutOfRangeException("revision",Environment.GetResourceString("ArgumentOutOfRange_Version"));
|
||
Contract.EndContractBlock();
|
||
|
||
_Major = major;
|
||
_Minor = minor;
|
||
_Build = build;
|
||
_Revision = revision;
|
||
}
|
||
|
||
public Version(int major, int minor, int build) {
|
||
if (major < 0)
|
||
throw new ArgumentOutOfRangeException("major",Environment.GetResourceString("ArgumentOutOfRange_Version"));
|
||
|
||
if (minor < 0)
|
||
throw new ArgumentOutOfRangeException("minor",Environment.GetResourceString("ArgumentOutOfRange_Version"));
|
||
|
||
if (build < 0)
|
||
throw new ArgumentOutOfRangeException("build",Environment.GetResourceString("ArgumentOutOfRange_Version"));
|
||
|
||
Contract.EndContractBlock();
|
||
|
||
_Major = major;
|
||
_Minor = minor;
|
||
_Build = build;
|
||
}
|
||
|
||
public Version(int major, int minor) {
|
||
if (major < 0)
|
||
throw new ArgumentOutOfRangeException("major",Environment.GetResourceString("ArgumentOutOfRange_Version"));
|
||
|
||
if (minor < 0)
|
||
throw new ArgumentOutOfRangeException("minor",Environment.GetResourceString("ArgumentOutOfRange_Version"));
|
||
Contract.EndContractBlock();
|
||
|
||
_Major = major;
|
||
_Minor = minor;
|
||
}
|
||
|
||
public Version(String version) {
|
||
Version v = Version.Parse(version);
|
||
_Major = v.Major;
|
||
_Minor = v.Minor;
|
||
_Build = v.Build;
|
||
_Revision = v.Revision;
|
||
}
|
||
|
||
#if FEATURE_LEGACYNETCF
|
||
//required for Mango AppCompat
|
||
[System.Runtime.CompilerServices.FriendAccessAllowed]
|
||
#endif
|
||
public Version()
|
||
{
|
||
_Major = 0;
|
||
_Minor = 0;
|
||
}
|
||
|
||
// Properties for setting and getting version numbers
|
||
public int Major {
|
||
get { return _Major; }
|
||
}
|
||
|
||
public int Minor {
|
||
get { return _Minor; }
|
||
}
|
||
|
||
public int Build {
|
||
get { return _Build; }
|
||
}
|
||
|
||
public int Revision {
|
||
get { return _Revision; }
|
||
}
|
||
|
||
public short MajorRevision {
|
||
get { return (short)(_Revision >> 16); }
|
||
}
|
||
|
||
public short MinorRevision {
|
||
get { return (short)(_Revision & 0xFFFF); }
|
||
}
|
||
|
||
public Object Clone() {
|
||
Version v = new Version();
|
||
v._Major = _Major;
|
||
v._Minor = _Minor;
|
||
v._Build = _Build;
|
||
v._Revision = _Revision;
|
||
return(v);
|
||
}
|
||
|
||
public int CompareTo(Object version)
|
||
{
|
||
if (version == null)
|
||
{
|
||
#if FEATURE_LEGACYNETCF
|
||
if (CompatibilitySwitches.IsAppEarlierThanWindowsPhone8) {
|
||
throw new ArgumentOutOfRangeException();
|
||
} else {
|
||
#endif
|
||
return 1;
|
||
#if FEATURE_LEGACYNETCF
|
||
}
|
||
#endif
|
||
}
|
||
|
||
Version v = version as Version;
|
||
if (v == null)
|
||
{
|
||
#if FEATURE_LEGACYNETCF
|
||
if (CompatibilitySwitches.IsAppEarlierThanWindowsPhone8) {
|
||
throw new InvalidCastException(Environment.GetResourceString("Arg_MustBeVersion"));
|
||
} else {
|
||
#endif
|
||
throw new ArgumentException(Environment.GetResourceString("Arg_MustBeVersion"));
|
||
#if FEATURE_LEGACYNETCF
|
||
}
|
||
#endif
|
||
}
|
||
|
||
if (this._Major != v._Major)
|
||
if (this._Major > v._Major)
|
||
return 1;
|
||
else
|
||
return -1;
|
||
|
||
if (this._Minor != v._Minor)
|
||
if (this._Minor > v._Minor)
|
||
return 1;
|
||
else
|
||
return -1;
|
||
|
||
if (this._Build != v._Build)
|
||
if (this._Build > v._Build)
|
||
return 1;
|
||
else
|
||
return -1;
|
||
|
||
if (this._Revision != v._Revision)
|
||
if (this._Revision > v._Revision)
|
||
return 1;
|
||
else
|
||
return -1;
|
||
|
||
return 0;
|
||
}
|
||
|
||
#if GENERICS_WORK
|
||
public int CompareTo(Version value)
|
||
{
|
||
if (value == null)
|
||
return 1;
|
||
|
||
if (this._Major != value._Major)
|
||
if (this._Major > value._Major)
|
||
return 1;
|
||
else
|
||
return -1;
|
||
|
||
if (this._Minor != value._Minor)
|
||
if (this._Minor > value._Minor)
|
||
return 1;
|
||
else
|
||
return -1;
|
||
|
||
if (this._Build != value._Build)
|
||
if (this._Build > value._Build)
|
||
return 1;
|
||
else
|
||
return -1;
|
||
|
||
if (this._Revision != value._Revision)
|
||
if (this._Revision > value._Revision)
|
||
return 1;
|
||
else
|
||
return -1;
|
||
|
||
return 0;
|
||
}
|
||
#endif
|
||
|
||
public override bool Equals(Object obj) {
|
||
Version v = obj as Version;
|
||
if (v == null)
|
||
return false;
|
||
|
||
// check that major, minor, build & revision numbers match
|
||
if ((this._Major != v._Major) ||
|
||
(this._Minor != v._Minor) ||
|
||
(this._Build != v._Build) ||
|
||
(this._Revision != v._Revision))
|
||
return false;
|
||
|
||
return true;
|
||
}
|
||
|
||
public bool Equals(Version obj)
|
||
{
|
||
if (obj == null)
|
||
return false;
|
||
|
||
// check that major, minor, build & revision numbers match
|
||
if ((this._Major != obj._Major) ||
|
||
(this._Minor != obj._Minor) ||
|
||
(this._Build != obj._Build) ||
|
||
(this._Revision != obj._Revision))
|
||
return false;
|
||
|
||
return true;
|
||
}
|
||
|
||
public override int GetHashCode()
|
||
{
|
||
// Let's assume that most version numbers will be pretty small and just
|
||
// OR some lower order bits together.
|
||
|
||
int accumulator = 0;
|
||
|
||
accumulator |= (this._Major & 0x0000000F) << 28;
|
||
accumulator |= (this._Minor & 0x000000FF) << 20;
|
||
accumulator |= (this._Build & 0x000000FF) << 12;
|
||
accumulator |= (this._Revision & 0x00000FFF);
|
||
|
||
return accumulator;
|
||
}
|
||
|
||
public override String ToString() {
|
||
if (_Build == -1) return(ToString(2));
|
||
if (_Revision == -1) return(ToString(3));
|
||
return(ToString(4));
|
||
}
|
||
|
||
public String ToString(int fieldCount) {
|
||
StringBuilder sb;
|
||
switch (fieldCount) {
|
||
case 0:
|
||
return(String.Empty);
|
||
case 1:
|
||
return(_Major.ToString());
|
||
case 2:
|
||
sb = StringBuilderCache.Acquire();
|
||
AppendPositiveNumber(_Major, sb);
|
||
sb.Append('.');
|
||
AppendPositiveNumber(_Minor, sb);
|
||
return StringBuilderCache.GetStringAndRelease(sb);
|
||
default:
|
||
if (_Build == -1)
|
||
throw new ArgumentException(Environment.GetResourceString("ArgumentOutOfRange_Bounds_Lower_Upper", "0", "2"), "fieldCount");
|
||
|
||
if (fieldCount == 3)
|
||
{
|
||
sb = StringBuilderCache.Acquire();
|
||
AppendPositiveNumber(_Major, sb);
|
||
sb.Append('.');
|
||
AppendPositiveNumber(_Minor, sb);
|
||
sb.Append('.');
|
||
AppendPositiveNumber(_Build, sb);
|
||
return StringBuilderCache.GetStringAndRelease(sb);
|
||
}
|
||
|
||
if (_Revision == -1)
|
||
throw new ArgumentException(Environment.GetResourceString("ArgumentOutOfRange_Bounds_Lower_Upper", "0", "3"), "fieldCount");
|
||
|
||
if (fieldCount == 4)
|
||
{
|
||
sb = StringBuilderCache.Acquire();
|
||
AppendPositiveNumber(_Major, sb);
|
||
sb.Append('.');
|
||
AppendPositiveNumber(_Minor, sb);
|
||
sb.Append('.');
|
||
AppendPositiveNumber(_Build, sb);
|
||
sb.Append('.');
|
||
AppendPositiveNumber(_Revision, sb);
|
||
return StringBuilderCache.GetStringAndRelease(sb);
|
||
}
|
||
|
||
throw new ArgumentException(Environment.GetResourceString("ArgumentOutOfRange_Bounds_Lower_Upper", "0", "4"), "fieldCount");
|
||
}
|
||
}
|
||
|
||
//
|
||
// AppendPositiveNumber is an optimization to append a number to a StringBuilder object without
|
||
// doing any boxing and not even creating intermediate string.
|
||
// Note: as we always have positive numbers then it is safe to convert the number to string
|
||
// regardless of the current culture as we<77>ll not have any punctuation marks in the number
|
||
//
|
||
private const int ZERO_CHAR_VALUE = (int) '0';
|
||
private static void AppendPositiveNumber(int num, StringBuilder sb)
|
||
{
|
||
Contract.Assert(num >= 0, "AppendPositiveNumber expect positive numbers");
|
||
|
||
int index = sb.Length;
|
||
int reminder;
|
||
|
||
do
|
||
{
|
||
reminder = num % 10;
|
||
num = num / 10;
|
||
sb.Insert(index, (char)(ZERO_CHAR_VALUE + reminder));
|
||
} while (num > 0);
|
||
}
|
||
|
||
public static Version Parse(string input) {
|
||
if (input == null) {
|
||
throw new ArgumentNullException("input");
|
||
}
|
||
Contract.EndContractBlock();
|
||
|
||
VersionResult r = new VersionResult();
|
||
r.Init("input", true);
|
||
if (!TryParseVersion(input, ref r)) {
|
||
throw r.GetVersionParseException();
|
||
}
|
||
return r.m_parsedVersion;
|
||
}
|
||
|
||
public static bool TryParse(string input, out Version result) {
|
||
VersionResult r = new VersionResult();
|
||
r.Init("input", false);
|
||
bool b = TryParseVersion(input, ref r);
|
||
result = r.m_parsedVersion;
|
||
return b;
|
||
}
|
||
|
||
private static bool TryParseVersion(string version, ref VersionResult result) {
|
||
int major, minor, build, revision;
|
||
|
||
if ((Object)version == null) {
|
||
result.SetFailure(ParseFailureKind.ArgumentNullException);
|
||
return false;
|
||
}
|
||
|
||
String[] parsedComponents = version.Split(SeparatorsArray);
|
||
int parsedComponentsLength = parsedComponents.Length;
|
||
if ((parsedComponentsLength < 2) || (parsedComponentsLength > 4)) {
|
||
result.SetFailure(ParseFailureKind.ArgumentException);
|
||
return false;
|
||
}
|
||
|
||
if (!TryParseComponent(parsedComponents[0], "version", ref result, out major)) {
|
||
return false;
|
||
}
|
||
|
||
if (!TryParseComponent(parsedComponents[1], "version", ref result, out minor)) {
|
||
return false;
|
||
}
|
||
|
||
parsedComponentsLength -= 2;
|
||
|
||
if (parsedComponentsLength > 0) {
|
||
if (!TryParseComponent(parsedComponents[2], "build", ref result, out build)) {
|
||
return false;
|
||
}
|
||
|
||
parsedComponentsLength--;
|
||
|
||
if (parsedComponentsLength > 0) {
|
||
if (!TryParseComponent(parsedComponents[3], "revision", ref result, out revision)) {
|
||
return false;
|
||
} else {
|
||
result.m_parsedVersion = new Version(major, minor, build, revision);
|
||
}
|
||
} else {
|
||
result.m_parsedVersion = new Version(major, minor, build);
|
||
}
|
||
} else {
|
||
result.m_parsedVersion = new Version(major, minor);
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
private static bool TryParseComponent(string component, string componentName, ref VersionResult result, out int parsedComponent) {
|
||
if (!Int32.TryParse(component, NumberStyles.Integer, CultureInfo.InvariantCulture, out parsedComponent)) {
|
||
result.SetFailure(ParseFailureKind.FormatException, component);
|
||
return false;
|
||
}
|
||
|
||
if (parsedComponent < 0) {
|
||
result.SetFailure(ParseFailureKind.ArgumentOutOfRangeException, componentName);
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
public static bool operator ==(Version v1, Version v2) {
|
||
if (Object.ReferenceEquals(v1, null)) {
|
||
return Object.ReferenceEquals(v2, null);
|
||
}
|
||
|
||
return v1.Equals(v2);
|
||
}
|
||
|
||
public static bool operator !=(Version v1, Version v2) {
|
||
return !(v1 == v2);
|
||
}
|
||
|
||
public static bool operator <(Version v1, Version v2) {
|
||
if ((Object) v1 == null)
|
||
throw new ArgumentNullException("v1");
|
||
Contract.EndContractBlock();
|
||
return (v1.CompareTo(v2) < 0);
|
||
}
|
||
|
||
public static bool operator <=(Version v1, Version v2) {
|
||
if ((Object) v1 == null)
|
||
throw new ArgumentNullException("v1");
|
||
Contract.EndContractBlock();
|
||
return (v1.CompareTo(v2) <= 0);
|
||
}
|
||
|
||
public static bool operator >(Version v1, Version v2) {
|
||
return (v2 < v1);
|
||
}
|
||
|
||
public static bool operator >=(Version v1, Version v2) {
|
||
return (v2 <= v1);
|
||
}
|
||
|
||
internal enum ParseFailureKind {
|
||
ArgumentNullException,
|
||
ArgumentException,
|
||
ArgumentOutOfRangeException,
|
||
FormatException
|
||
}
|
||
|
||
internal struct VersionResult {
|
||
internal Version m_parsedVersion;
|
||
internal ParseFailureKind m_failure;
|
||
internal string m_exceptionArgument;
|
||
internal string m_argumentName;
|
||
internal bool m_canThrow;
|
||
|
||
internal void Init(string argumentName, bool canThrow) {
|
||
m_canThrow = canThrow;
|
||
m_argumentName = argumentName;
|
||
}
|
||
|
||
internal void SetFailure(ParseFailureKind failure) {
|
||
SetFailure(failure, String.Empty);
|
||
}
|
||
|
||
internal void SetFailure(ParseFailureKind failure, string argument) {
|
||
m_failure = failure;
|
||
m_exceptionArgument = argument;
|
||
if (m_canThrow) {
|
||
throw GetVersionParseException();
|
||
}
|
||
}
|
||
|
||
internal Exception GetVersionParseException() {
|
||
switch (m_failure) {
|
||
case ParseFailureKind.ArgumentNullException:
|
||
return new ArgumentNullException(m_argumentName);
|
||
case ParseFailureKind.ArgumentException:
|
||
return new ArgumentException(Environment.GetResourceString("Arg_VersionString"));
|
||
case ParseFailureKind.ArgumentOutOfRangeException:
|
||
return new ArgumentOutOfRangeException(m_exceptionArgument, Environment.GetResourceString("ArgumentOutOfRange_Version"));
|
||
case ParseFailureKind.FormatException:
|
||
// Regenerate the FormatException as would be thrown by Int32.Parse()
|
||
try {
|
||
Int32.Parse(m_exceptionArgument, CultureInfo.InvariantCulture);
|
||
} catch (FormatException e) {
|
||
return e;
|
||
} catch (OverflowException e) {
|
||
return e;
|
||
}
|
||
Contract.Assert(false, "Int32.Parse() did not throw exception but TryParse failed: " + m_exceptionArgument);
|
||
return new FormatException(Environment.GetResourceString("Format_InvalidString"));
|
||
default:
|
||
Contract.Assert(false, "Unmatched case in Version.GetVersionParseException() for value: " + m_failure);
|
||
return new ArgumentException(Environment.GetResourceString("Arg_VersionString"));
|
||
}
|
||
}
|
||
|
||
}
|
||
}
|
||
}
|