using System; using System.Globalization; using Developpez.Dotnet.Properties; namespace Developpez.Dotnet { /// /// Représente le numéro de version d'un composant. /// /// Cette classe implémente les spécifications /// du versionnement sémantique (http://semver.org). /// Pour une version française des spécifications voir : /// https://github.com/DudePascalou/semver/blob/master/semver.fr.md #if SILVERLIGHT || NETFX_CORE public class VersionNumber : IComparable, IComparable, IEquatable #else public class VersionNumber : ICloneable, IComparable, IComparable, IEquatable #endif { #region Properties private readonly int _major; private readonly int _minor; private readonly int _patch; private readonly string _preRelease; private readonly string _build; /// /// Version majeure. /// /// SemVer.Spec.2 : Un numéro de version standard DOIT prendre la forme X.Y.Z où X, Y et Z sont des entiers non négatifs. /// X est la version majeure, Y est la version mineure, et Z est la version de patch. public int Major { get { return _major; } } /// /// Version mmineure. /// /// SemVer.Spec.2 : Un numéro de version standard DOIT prendre la forme X.Y.Z où X, Y et Z sont des entiers non négatifs. /// X est la version majeure, Y est la version mineure, et Z est la version de patch. public int Minor { get { return _minor; } } /// /// Verison de patch. /// /// SemVer.Spec.2 : Un numéro de version standard DOIT prendre la forme X.Y.Z où X, Y et Z sont des entiers non négatifs. /// X est la version majeure, Y est la version mineure, et Z est la version de patch. public int Patch { get { return _patch; } } /// /// Version de pré-publication. /// /// SemVer.Spec.10 : Une version de prépublication PEUT être notée par l'ajout d'un tiret /// et une série d'identifiants séparés par des points suivant immédiatement la version de patch. /// Les identifiants DOIVENT être composés uniquement de caractères alphanumériques ASCII et de tirets [0-9A-Za-z-]. public string PreRelease { get { return _preRelease; } } /// /// Version de construction. /// /// SemVer.Spec.11 : Une version de construction PEUT être représentée par l'ajout d'un signe "plus" /// et d'une série d'identifiants séparés par des points suivant immédiatement la version de patch ou de prépublication. /// Les identifiants DOIVENT être composés uniquement de caractères alphanumériques ASCII et de tirets [0-9A-Za-z-]. public string Build { get { return _build; } } #endregion #region Constructors /// /// Intialise une nouvelle instance de la classe . /// public VersionNumber() : this(0, 0, 0, string.Empty, string.Empty) { } /// /// Intialise une nouvelle instance de la classe . /// /// Version majeure. /// Version mineure. /// Version de patch. public VersionNumber(int major, int minor, int patch) : this(major, minor, patch, string.Empty, string.Empty) { } /// /// Intialise une nouvelle instance de la classe . /// /// Version majeure. /// Version mineure. /// Version de patch. /// Version de pré-publication. /// Version de construction. public VersionNumber(int major, int minor, int patch, string preRelease, string build) { _major = major; _minor = minor; _patch = patch; _preRelease = preRelease; _build = build; } /// /// Intialise une nouvelle instance de la classe . /// /// Chaîne de caractères représentant une instance de . public VersionNumber(string versionNumber) { if (string.IsNullOrEmpty(versionNumber)) throw new ArgumentNullException("versionNumber", ExceptionMessages.StringNullOrEmpty); int indexOfMinus = versionNumber.IndexOf('-'); int indexOfPlus = versionNumber.IndexOf('+'); if (indexOfMinus > -1 && indexOfPlus > -1 && indexOfMinus > indexOfPlus) throw new FormatException(ExceptionMessages.PreReleasePartMustBeBeforeBuildPart); // X.Y.Z : string xyzPart; if (indexOfMinus > -1) xyzPart = versionNumber.Substring(0, indexOfMinus); else if (indexOfPlus > -1) xyzPart = versionNumber.Substring(0, indexOfPlus); else xyzPart = versionNumber; string[] identifiers = xyzPart.Split(new[] { '.' }, StringSplitOptions.None); if (identifiers.Length < 3) throw new FormatException(ExceptionMessages.MajorMinorPatchIdentifiersAreMandatory); // Major : _major = int.Parse(identifiers[0], CultureInfo.InvariantCulture); if (this.Major < 0) throw new ArgumentOutOfRangeException("versionNumber", string.Format(ExceptionMessages.NumberMustBePositiveOrZero, "Major number")); // Minor : _minor = int.Parse(identifiers[1], CultureInfo.InvariantCulture); if (this.Minor < 0) throw new ArgumentOutOfRangeException("versionNumber", string.Format(ExceptionMessages.NumberMustBePositiveOrZero, "Minor number")); // Patch : _patch = int.Parse(identifiers[2], CultureInfo.InvariantCulture); if (this.Patch < 0) throw new ArgumentOutOfRangeException("versionNumber", string.Format(ExceptionMessages.NumberMustBePositiveOrZero, "Patch number")); // PreRelease : _preRelease = string.Empty; if (indexOfMinus > -1) { if (indexOfPlus > -1) _preRelease = versionNumber.Substring(indexOfMinus + 1, indexOfPlus - (indexOfMinus + 1)); else _preRelease = versionNumber.Substring(indexOfMinus + 1, versionNumber.Length - (indexOfMinus + 1)); } // Build : _build = string.Empty; if (indexOfPlus > -1) _build = versionNumber.Substring(indexOfPlus + 1, versionNumber.Length - (indexOfPlus + 1)); } #endregion /// /// Retourne le code de hachage pour cette instance. /// /// Le code de hachage pour cette instance. public override int GetHashCode() { // D'après Jon Skeet, sur Stackoverflow.com : // http://stackoverflow.com/questions/263400/what-is-the-best-algorithm-for-an-overridden-system-object-gethashcode // Les nombres primaires ont changé par rapport à la réponse initiale pour générer des codes de hashage plus "uniques". unchecked { int hash = 89; hash = hash * 197 + this.Major.GetHashCode(); hash = hash * 293 + this.Minor.GetHashCode(); hash = hash * 397 + this.Patch.GetHashCode(); hash = hash * 499 + (this.PreRelease != null ? this.PreRelease.GetHashCode() : 0); hash = hash * 593 + (this.Build != null ? this.Build.GetHashCode() : 0); return hash; } } /// /// Indique si l'instance de l'objet donné est identique à l'instance courante. /// /// L'instance de l'objet à comparer avec l'instance courante. /// Vrai, si l'instance de l'objet donné est identique à l'instance courante, faux sinon. public override bool Equals(object obj) { if (obj == null) return false; VersionNumber instance = obj as VersionNumber; if (instance == null) return false; return this.Equals(instance); } /// /// Indique si l'instance de donnée est identique à l'instance courante. /// /// L'instance de à comparer avec l'instance courante. /// Vrai, si l'instance de donnée est identique à l'instance courante, faux sinon. public bool Equals(VersionNumber other) { if (other == null) return false; return ( this.Major.Equals(other.Major) && this.Minor.Equals(other.Minor) && this.Patch.Equals(other.Patch) && string.Equals(this.PreRelease, other.PreRelease) && string.Equals(this.Build, other.Build) ); } /// /// Retourne un entier indiquant l'ordre relatif de l'instance donnée par rapport à l'instance courante. /// /// L'instance de l'objet à comparer avec l'instance courante. /// Un entier inférieur à zéro si l'instance donnée précède l'instance courante, /// un entier égal à zéro si les instances ont la même position, /// un entier supérieur à zéro si l'instance donnée suit l'instance courante. public int CompareTo(object obj) { if (obj == null) return 1; VersionNumber instance = obj as VersionNumber; if (instance == null) throw new ArgumentException(string.Format("L'argument doit être du type {0}.", typeof(VersionNumber).Name)); return this.CompareTo(instance); } /// /// Retourne un entier indiquant l'ordre relatif de l'instance donnée par rapport à l'instance courante. /// /// L'instance à comparer avec l'instance courante. /// Un entier inférieur à zéro si l'instance précède l'instance courante, /// un entier égal à zéro si les instances ont la même position, /// un entier supérieur à zéro si l'instance suit l'instance courante. /// SemVer.Spec.12 : La priorité DOIT être calculée en séparant les numéros de version en identifiants /// majeur, mineur, patch, prépublication et de construction dans cet ordre. /// Les numéros de version majeur, mineur, et de patch sont toujours comparés numériquement. /// Pour les versions de prépublication et de construction, la priorité DOIT être déterminée /// en comparant chaque identifiant séparé par un point comme suit : les identifiants composés /// de chiffres seulement sont comparés numériquement et les identifiants composés de lettres ou /// de tirets sont comparés dans l'ordre de tri ASCII. /// Les identifiants numériques ont toujours priorité inférieure par rapport aux identifiants non numériques. public int CompareTo(VersionNumber other) { if (other == null) return 1; if (this.Major != other.Major) return this.Major.CompareTo(other.Major); if (this.Minor != other.Minor) return this.Minor.CompareTo(other.Minor); if (this.Patch != other.Patch) return this.Patch.CompareTo(other.Patch); if (string.Compare(this.PreRelease, other.PreRelease) != 0) return this.ComparePreReleaseTo(other.PreRelease); if (string.Compare(this.Build, other.Build) != 0) return this.CompareBuildTo(other.Build); return 0; } /// /// Retourne un entier indiquant l'ordre relatif de la version de pré-publication donnée /// par rapport à la version de pré-publication de l'instance courante. /// /// Version de pré-publication à comparer avec celle de l'instance courante. /// Un entier inférieur à zéro si la version de pré-publication donnée précède celle de l'instance courante, /// un entier égal à zéro si les versions ont la même position, /// un entier supérieur à zéro si la version de pré-publication donnée suit celle de l'instance courante. /// SemVer.Spec.10 : Des versions de prépublication sont utilisables /// et précèdent la version normale associée (version de prépublication < version normale). private int ComparePreReleaseTo(string preRelease) { if (this.PreRelease == preRelease) return 0; if (!string.IsNullOrEmpty(this.PreRelease) && string.IsNullOrEmpty(preRelease)) return -1; if (string.IsNullOrEmpty(this.PreRelease) && !string.IsNullOrEmpty(preRelease)) return 1; return CompareIdentifiers(this.PreRelease, preRelease); } /// /// Retourne un entier indiquant l'ordre relatif de la version de construction donnée /// par rapport à la version de construction de l'instance courante. /// /// Version de construction à comparer avec celle de l'instance courante. /// Un entier inférieur à zéro si la version de construction donnée précède celle de l'instance courante, /// un entier égal à zéro si les versions ont la même position, /// un entier supérieur à zéro si la version de construction donnée suit celle de l'instance courante. /// SemVer.Spec.11 : Les versions de construction sont utilisables /// et suivent la version normale associée (version de construction > version normale). private int CompareBuildTo(string build) { if (this.Build == build) return 0; if (!string.IsNullOrEmpty(this.Build) && string.IsNullOrEmpty(build)) return 1; if (string.IsNullOrEmpty(this.Build) && !string.IsNullOrEmpty(build)) return -1; return CompareIdentifiers(this.Build, build); } /// /// Retourne un entier indiquant l'ordre relatif des identifiants donnés. /// /// Identifiant 1. /// Identifiant 2. /// Un entier inférieur à zéro si l'identifiant 1 précède l'identifiant 2, /// un entier égal à zéro si les identifiants ont la même position, /// un entier supérieur à zéro si l'identifiant 1 suit l'identifiant 2. /// SemVer.Spec.12 : Pour les versions de prépublication et de construction, /// la priorité DOIT être déterminée en comparant chaque identifiant séparé par un point comme suit : /// les identifiants composés de chiffres seulement sont comparés numériquement et les identifiants /// composés de lettres ou de tirets sont comparés dans l'ordre de tri ASCII. /// Les identifiants numériques précèdent les identifiants non numériques (identifiants numériques < identifiants non-numériques). private static int CompareIdentifiers(string identifiers1, string identifiers2) { // null et string.Empty ont déjà été vérifiés dans les méthodes appelantes. string[] ids1 = identifiers1.Split('.'); string[] ids2 = identifiers2.Split('.'); for (int i = 0; i < Math.Max(ids1.Length, ids2.Length); i++) { if (i >= ids1.Length) return -1; if (i >= ids2.Length) return 1; if (ids1[i] != ids2[i]) { int intThis; int intOther; if (int.TryParse(ids1[i], out intThis) && int.TryParse(ids2[i], out intOther)) return intThis.CompareTo(intOther); return string.Compare(ids1[i], ids2[i]); } } return 0; } #if !SILVERLIGHT && !NETFX_CORE /// /// Retourne une instance de qui est la copie de l'instance courante. /// /// Une instance de qui est la copie de l'instance courante. object ICloneable.Clone() { return Clone(); } #endif /// /// Retourne une instance de qui est la copie de l'instance courante. /// /// Une instance de qui est la copie de l'instance courante. public VersionNumber Clone() { VersionNumber clone = new VersionNumber(Major, Minor, Patch, PreRelease, Build); return clone; } /// /// Retourne une chaîne de caractères représentant l'instance courante. /// /// Une chaîne de caractères représentant l'instance courante. public override string ToString() { return string.Format ( "{0}.{1}.{2}{3}{4}", this.Major, this.Minor, this.Patch, (string.IsNullOrEmpty(this.PreRelease) ? string.Empty : "-" + this.PreRelease), (string.IsNullOrEmpty(this.Build) ? string.Empty : "+" + this.Build) ); } /// /// Retourne une instance de obtenue /// à partir de la chaîne de caractères données. /// /// Chaîne de caractères représentant une instance de . /// L'instance de obtenue /// à partir de la chaîne de caractères. /// versionNumber est null. /// versionNumber n'est pas une représentation valide de . public static VersionNumber Parse(string versionNumber) { return new VersionNumber(versionNumber); } #region Operators /// /// Indique si les 2 instances de données sont identiques. /// /// Instance de . /// Instance de . /// Vrai si les 2 instances de données sont identiques, faux sinon. public static bool operator ==(VersionNumber vn1, VersionNumber vn2) { if (ReferenceEquals(vn1, null)) return ReferenceEquals(vn2, null); return vn1.Equals(vn2); } /// /// Indique si les 2 instances de données sont différentes. /// /// Instance de . /// Instance de . /// Vrai si les 2 instances de données sont différentes, faux sinon. public static bool operator !=(VersionNumber vn1, VersionNumber vn2) { if (ReferenceEquals(vn1, null)) return !ReferenceEquals(vn2, null); return !vn1.Equals(vn2); } /// /// Indique si vn1 est supérieur à vn2. /// /// Instance de . /// Instance de . /// Vrai, si vn1 est supérieur à vn2, faux sinon. public static bool operator >(VersionNumber vn1, VersionNumber vn2) { if (ReferenceEquals(vn1, null)) throw new ArgumentNullException("vn1"); return vn1.CompareTo(vn2) > 0; } /// /// Indique si vn1 est supérieur ou égal à vn2. /// /// Instance de . /// Instance de . /// Vrai, si vn1 est supérieur ou égal à vn2, faux sinon. public static bool operator >=(VersionNumber vn1, VersionNumber vn2) { if (ReferenceEquals(vn1, null)) throw new ArgumentNullException("vn1"); return vn1.CompareTo(vn2) >= 0; } /// /// Indique si vn1 est inférieur à vn2. /// /// Instance de . /// Instance de . /// Vrai, si vn1 est inférieur à vn2, faux sinon. public static bool operator <(VersionNumber vn1, VersionNumber vn2) { if (ReferenceEquals(vn1, null)) throw new ArgumentNullException("vn1"); return vn1.CompareTo(vn2) < 0; } /// /// Indique si vn1 est inférieur ou égal à vn2. /// /// Instance de . /// Instance de . /// Vrai, si vn1 est inférieur ou égal à vn2, faux sinon. public static bool operator <=(VersionNumber vn1, VersionNumber vn2) { if (ReferenceEquals(vn1, null)) throw new ArgumentNullException("vn1"); return vn1.CompareTo(vn2) <= 0; } #endregion } }