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
}
}