ElasticVersion.cs

// A simple version implementation based on
// https://github.com/maxhauser/semver/blob/master/src/Semver/SemVersion.cs
// MIT License
// Copyright (c) 2013 Max Hauser
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
 
using System;
using System.Globalization;
using System.Runtime.Serialization;
using System.Security.Permissions;
using System.Text.RegularExpressions;
 
namespace Elastic
{
    /// <summary>
    /// An Elastic product version
    /// </summary>
    public sealed class ElasticVersion : IEquatable<ElasticVersion>, IComparable<ElasticVersion>, IComparable
    {
        private static Regex VersionRegex = new Regex(
            @"^(?<major>\d+)(\.(?<minor>\d+))?(\.(?<patch>\d+))?(\-(?<pre>[0-9A-Za-z]+))?$",
            RegexOptions.CultureInvariant | RegexOptions.ExplicitCapture);
 
        public ElasticVersion(object version) : this(version.ToString())
        {
        }
 
        public ElasticVersion(string version)
        {
            var match = VersionRegex.Match(version);
            if (!match.Success)
                throw new ArgumentException(string.Format("Invalid version '{0}'", version), "version");
 
            var major = int.Parse(match.Groups["major"].Value, CultureInfo.InvariantCulture);
 
            var minorMatch = match.Groups["minor"];
            int minor = 0;
            if (minorMatch.Success)
                minor = int.Parse(minorMatch.Value, CultureInfo.InvariantCulture);
 
            var patchMatch = match.Groups["patch"];
            int patch = 0;
            if (patchMatch.Success)
                patch = int.Parse(patchMatch.Value, CultureInfo.InvariantCulture);
 
            var prerelease = match.Groups["pre"].Value;
             
            this.Major = major;
            this.Minor = minor;
            this.Patch = patch;
            this.Prerelease = prerelease;
        }
 
        public ElasticVersion(int major, int minor, int patch, string prerelease)
        {
            this.Major = major;
            this.Minor = minor;
            this.Patch = patch;
            this.Prerelease = prerelease;
        }
 
        public static bool TryParse(string version, out ElasticVersion ElasticVersion)
        {
            try
            {
                ElasticVersion = new ElasticVersion(version);
                return true;
            }
            catch (Exception)
            {
                ElasticVersion = null;
                return false;
            }
        }
 
        public static bool Equals(ElasticVersion versionA, ElasticVersion versionB)
        {
            if (ReferenceEquals(versionA, null))
                return ReferenceEquals(versionB, null);
            return versionA.Equals(versionB);
        }
 
        public static int Compare(ElasticVersion versionA, ElasticVersion versionB)
        {
            if (ReferenceEquals(versionA, null))
                return ReferenceEquals(versionB, null) ? 0 : -1;
            return versionA.CompareTo(versionB);
        }
 
        public ElasticVersion Change(int? major = null, int? minor = null, int? patch = null, string prerelease = null)
        {
            return new ElasticVersion(
                major ?? this.Major,
                minor ?? this.Minor,
                patch ?? this.Patch,
                prerelease ?? this.Prerelease);
        }
 
        public int Major { get; private set; }
 
        public int Minor { get; private set; }
 
        public int Patch { get; private set; }
 
        public string Prerelease { get; private set; }
 
        public override string ToString()
        {
            var version = "" + Major + "." + Minor + "." + Patch;
            if (!String.IsNullOrEmpty(Prerelease))
                version += "-" + Prerelease;
            return version;
        }
 
        public int CompareTo(object obj)
        {
            return CompareTo((ElasticVersion)obj);
        }
 
        public int CompareTo(ElasticVersion other)
        {
            if (ReferenceEquals(other, null))
                return 1;
 
            var r = this.CompareByPrecedence(other);
            return r;
        }
 
        public bool PrecedenceMatches(ElasticVersion other)
        {
            return CompareByPrecedence(other) == 0;
        }
 
        public int CompareByPrecedence(ElasticVersion other)
        {
            if (ReferenceEquals(other, null))
                return 1;
 
            var r = this.Major.CompareTo(other.Major);
            if (r != 0) return r;
 
            r = this.Minor.CompareTo(other.Minor);
            if (r != 0) return r;
 
            r = this.Patch.CompareTo(other.Patch);
            if (r != 0) return r;
 
            r = CompareComponent(this.Prerelease, other.Prerelease, true);
            return r;
        }
 
        static int CompareComponent(string a, string b, bool lower = false)
        {
            var aEmpty = String.IsNullOrEmpty(a);
            var bEmpty = String.IsNullOrEmpty(b);
            if (aEmpty && bEmpty)
                return 0;
 
            if (aEmpty)
                return lower ? 1 : -1;
            if (bEmpty)
                return lower ? -1 : 1;
 
            var aComps = a.Split('.');
            var bComps = b.Split('.');
 
            var minLen = Math.Min(aComps.Length, bComps.Length);
            for (int i = 0; i < minLen; i++)
            {
                var ac = aComps[i];
                var bc = bComps[i];
                int anum, bnum;
                var isanum = Int32.TryParse(ac, out anum);
                var isbnum = Int32.TryParse(bc, out bnum);
                int r;
                if (isanum && isbnum)
                {
                    r = anum.CompareTo(bnum);
                    if (r != 0) return anum.CompareTo(bnum);
                }
                else
                {
                    if (isanum)
                        return -1;
                    if (isbnum)
                        return 1;
                    r = String.CompareOrdinal(ac, bc);
                    if (r != 0)
                        return r;
                }
            }
 
            return aComps.Length.CompareTo(bComps.Length);
        }
 
        public override bool Equals(object obj)
        {
            if (ReferenceEquals(obj, null))
                return false;
 
            if (ReferenceEquals(this, obj))
                return true;
 
            var other = (ElasticVersion)obj;
 
            return Equals(other);
        }
 
        public bool Equals(ElasticVersion other)
        {
            if (other == null)
                return false;
 
            return this.Major == other.Major &&
                this.Minor == other.Minor &&
                this.Patch == other.Patch &&
                string.Equals(this.Prerelease, other.Prerelease, StringComparison.Ordinal);
        }
 
        public override int GetHashCode()
        {
            unchecked
            {
                int result = this.Major.GetHashCode();
                result = result * 31 + this.Minor.GetHashCode();
                result = result * 31 + this.Patch.GetHashCode();
                result = result * 31 + this.Prerelease.GetHashCode();
                return result;
            }
        }
 
        public static bool operator ==(ElasticVersion left, ElasticVersion right)
        {
            return ElasticVersion.Equals(left, right);
        }
 
        public static bool operator !=(ElasticVersion left, ElasticVersion right)
        {
            return !ElasticVersion.Equals(left, right);
        }
 
        public static bool operator >(ElasticVersion left, ElasticVersion right)
        {
            return ElasticVersion.Compare(left, right) > 0;
        }
 
        public static bool operator >=(ElasticVersion left, ElasticVersion right)
        {
            return left == right || left > right;
        }
 
        public static bool operator <(ElasticVersion left, ElasticVersion right)
        {
            return ElasticVersion.Compare(left, right) < 0;
        }
 
        public static bool operator <=(ElasticVersion left, ElasticVersion right)
        {
            return left == right || left < right;
        }
    }
}