diff options
Diffstat (limited to 'src/mscorlib/shared/System/Version.cs')
-rw-r--r-- | src/mscorlib/shared/System/Version.cs | 495 |
1 files changed, 495 insertions, 0 deletions
diff --git a/src/mscorlib/shared/System/Version.cs b/src/mscorlib/shared/System/Version.cs new file mode 100644 index 0000000000..54b2052ddb --- /dev/null +++ b/src/mscorlib/shared/System/Version.cs @@ -0,0 +1,495 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Globalization; +using System.Diagnostics; +using System.Diagnostics.Contracts; +using System.Text; + +namespace System +{ + // 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] + public sealed class Version : ICloneable, IComparable + , IComparable<Version>, IEquatable<Version> + { + // AssemblyName depends on the order staying the same + private readonly int _Major; + private readonly int _Minor; + private readonly int _Build = -1; + private readonly int _Revision = -1; + + public Version(int major, int minor, int build, int revision) + { + if (major < 0) + throw new ArgumentOutOfRangeException(nameof(major), SR.ArgumentOutOfRange_Version); + + if (minor < 0) + throw new ArgumentOutOfRangeException(nameof(minor), SR.ArgumentOutOfRange_Version); + + if (build < 0) + throw new ArgumentOutOfRangeException(nameof(build), SR.ArgumentOutOfRange_Version); + + if (revision < 0) + throw new ArgumentOutOfRangeException(nameof(revision), SR.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(nameof(major), SR.ArgumentOutOfRange_Version); + + if (minor < 0) + throw new ArgumentOutOfRangeException(nameof(minor), SR.ArgumentOutOfRange_Version); + + if (build < 0) + throw new ArgumentOutOfRangeException(nameof(build), SR.ArgumentOutOfRange_Version); + + Contract.EndContractBlock(); + + _Major = major; + _Minor = minor; + _Build = build; + } + + public Version(int major, int minor) + { + if (major < 0) + throw new ArgumentOutOfRangeException(nameof(major), SR.ArgumentOutOfRange_Version); + + if (minor < 0) + throw new ArgumentOutOfRangeException(nameof(minor), SR.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; + } + + public Version() + { + _Major = 0; + _Minor = 0; + } + + private Version(Version version) + { + Debug.Assert(version != null); + + _Major = version._Major; + _Minor = version._Minor; + _Build = version._Build; + _Revision = version._Revision; + } + + public object Clone() + { + return new Version(this); + } + + // 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 int CompareTo(Object version) + { + if (version == null) + { + return 1; + } + + Version v = version as Version; + if (v == null) + { + throw new ArgumentException(SR.Arg_MustBeVersion); + } + + return CompareTo(v); + } + + public int CompareTo(Version value) + { + return + object.ReferenceEquals(value, this) ? 0 : + object.ReferenceEquals(value, 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) : + 0; + } + + public override bool Equals(Object obj) + { + return Equals(obj as Version); + } + + public bool Equals(Version obj) + { + return object.ReferenceEquals(obj, this) || + (!object.ReferenceEquals(obj, null) && + _Major == obj._Major && + _Minor == obj._Minor && + _Build == obj._Build && + _Revision == obj._Revision); + } + + 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 |= (_Major & 0x0000000F) << 28; + accumulator |= (_Minor & 0x000000FF) << 20; + accumulator |= (_Build & 0x000000FF) << 12; + accumulator |= (_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(SR.Format(SR.ArgumentOutOfRange_Bounds_Lower_Upper, "0", "2"), nameof(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(SR.Format(SR.ArgumentOutOfRange_Bounds_Lower_Upper, "0", "3"), nameof(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(SR.Format(SR.ArgumentOutOfRange_Bounds_Lower_Upper, "0", "4"), nameof(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'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) + { + Debug.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(nameof(input)); + } + Contract.EndContractBlock(); + + VersionResult r = new VersionResult(); + r.Init(nameof(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(nameof(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('.'); + int parsedComponentsLength = parsedComponents.Length; + if ((parsedComponentsLength < 2) || (parsedComponentsLength > 4)) + { + result.SetFailure(ParseFailureKind.ArgumentException); + return false; + } + + if (!TryParseComponent(parsedComponents[0], nameof(version), ref result, out major)) + { + return false; + } + + if (!TryParseComponent(parsedComponents[1], nameof(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(nameof(v1)); + Contract.EndContractBlock(); + return (v1.CompareTo(v2) < 0); + } + + public static bool operator <=(Version v1, Version v2) + { + if ((Object)v1 == null) + throw new ArgumentNullException(nameof(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(SR.Arg_VersionString); + case ParseFailureKind.ArgumentOutOfRangeException: + return new ArgumentOutOfRangeException(m_exceptionArgument, SR.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; + } + Debug.Assert(false, "Int32.Parse() did not throw exception but TryParse failed: " + m_exceptionArgument); + return new FormatException(SR.Format_InvalidString); + default: + Debug.Assert(false, "Unmatched case in Version.GetVersionParseException() for value: " + m_failure); + return new ArgumentException(SR.Arg_VersionString); + } + } + } + } +} |