org.nuiton.util.version.SemVer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of nuiton-utils Show documentation
Show all versions of nuiton-utils Show documentation
Library of usefull classes to be used in any project.
package org.nuiton.util.version;
/*
* #%L
* Nuiton Utils
* %%
* Copyright (C) 2004 - 2013 CodeLutin
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* .
* #L%
*/
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Implantation de http://semver.org/.
*
* Cette objet est immutable, Il faut utiliser {@link #getCreator()}
* pour modifier un de ses elements
*
* Quelques changement par rapport au site:
*
* - le numero de version peut avoir entre 1 et N element, et non pas 3 obligatoirement
*
- on peut avoir un -SNAPSHOT ajoute en toute fin de la version
*
* Un SemVer est en plusieurs elements dont chaque element peut avoir plusieurs
* composant. Les composants sont utilise le meme separateur '.'.
*
* - version: 1 à N composant numerique, les 3 premiers sont nomme
*
* - major
*
- minor
*
- patch
*
* - prerelease: 0 à N composant alphanumerique, le prefix est '-'
*
- build: 0 à N composant alphanumerique, le prefix est '+'
*
- SNAPSHOT: 0 ou 1 composant dont le nom est fixe le prefix est '-'
*
* Le mieux pour construire ou modifier un SemVer est d'utilise une methode creator:
*
* - SemVer.creator(1,2,3).done() → "1.2.3"
*
- SemVer.creator("1.2.3","rc2").done() → "1.2.3-rc2"
*
- SemVer.creator().setVersion(2.3.4).setBuild("r223").done() → "2.3.4+r223"
*
- SemVer.creator().setVersion(2.3.4).setSnapshot(true).done() → "2.3.4-SNAPSHOT"
*
- SemVer.creator(new SemVer("1.2.3")).incMajor().done() → "2.2.2"
*
- new SemVer("1.2.3").getCreator().incMinor().setPrerelease(beta).done() → "1.3.3-beta"
*
*
* @author Benjamin Poussin - [email protected]
* @since 2.6.7
* @deprecated since 3.0, use now Nuiton version
*/
@Deprecated
public class SemVer implements Comparable {
/** Logger. */
private static final Log log = LogFactory.getLog(SemVer.class);
/** Separateur utiliser entre chaque element d'une partie */
final static public String SERIES_SEPARATOR = "."; // le separateur
/** Seperateur entre la version et l'element prerelease */
final static public String PRERELEASE_SEPARATOR = "-"; // le separateur de Prerelease
/** Separateur utiliser devant l'element build */
final static public String BUILD_SEPARATOR = "+"; // le separateur de build
/** pattern pour une partie de l'element version */
final static public String PATTERN_VERSION = "[0-9]+"; // le version ne sont que des nombres
/** pattern pour une partie de l'element prerelease */
final static public String PATTERN_PRERELEASE = "[0-9A-Za-z-]+"; // les Prerelease sont des chiffre, lettre ou '-'
/** pattern pour une partir de l'element build */
final static public String PATTERN_BUILD = "[0-9A-Za-z-]+";// les Build sont des chiffre, lettre ou '-'
/** Separateur de SNAPSHOT */
final static public String SNAPSHOT_SEPARATOR = "-";
/** la chaine de caractere representant le SNAPSHOT */
final static public String SNAPSHOT = "SNAPSHOT";
// "(1.20.300)(-alpha-1.20)?(+r123.20130126)?"
// version prerelease build
/** pattern qui permet de separer les 3 constituante d'une version, ne
* supporte pas le SNAPSHOT, il faut donc que le SNAPSHOT ait ete retirer
* avant d'appliquer le pattern
*/
final static Pattern PATTERN_ALL = Pattern.compile(
"("+PATTERN_VERSION+"(?:"+Pattern.quote(SERIES_SEPARATOR)+PATTERN_VERSION+")*)" +
"("+Pattern.quote(PRERELEASE_SEPARATOR)+ PATTERN_PRERELEASE+"(?:"+Pattern.quote(SERIES_SEPARATOR)+PATTERN_PRERELEASE+")*)?" +
"("+Pattern.quote(BUILD_SEPARATOR)+ PATTERN_BUILD+"(?:"+Pattern.quote(SERIES_SEPARATOR)+PATTERN_BUILD+")*)?");
protected String version;
protected String prerelease;
protected String build;
protected String snapshot;
/**
* Create new Version object, strip is done on argument to remove extra space
* @param versionString FIXME
* @exception IllegalArgumentException if argument isn't valid version string
*/
public SemVer(String versionString) {
String v = StringUtils.strip(versionString); // on conserve versionString intact pour le message d'erreur
if (StringUtils.endsWithIgnoreCase(v, SNAPSHOT_SEPARATOR + SNAPSHOT)) {
snapshot = SNAPSHOT;
v = StringUtils.substringBeforeLast(v, SNAPSHOT_SEPARATOR);
}
Matcher matcher = PATTERN_ALL.matcher(v);
boolean match = matcher.matches();
if (match) {
version = matcher.group(1);
prerelease = StringUtils.removeStart(matcher.group(2), PRERELEASE_SEPARATOR);
build = StringUtils.removeStart(matcher.group(3), BUILD_SEPARATOR);
} else {
throw new IllegalArgumentException(String.format("Bad version string: '%s'", versionString));
}
}
/**
* @param o the other version to test
* @return {@code true} if current version is before the given one
*/
public boolean before(SemVer o) {
int result = compareTo(o);
return result < 0;
}
/**
* @param o the other version to test
* @return {@code true} if current version is after the given one
*/
public boolean after(SemVer o) {
int result = compareTo(o);
return result > 0;
}
public int compareTo(SemVer other) {
int result = compare(this.getVersion(), other.getVersion(), false);
if (result == 0) {
result = compare(this.getPrerelease(), other.getPrerelease(), true);
if (result == 0) {
result = compare(this.getBuild(), other.getBuild(), false);
if (result == 0) {
result = compare(this.getSnapshot(), other.getSnapshot(), true);
}
}
}
return result;
}
/**
* Compare deux elements de meme semantique (version, prerelease, build, snapshot)
* sinon le comportement est non predictible
* @param a FIXME
* @param b FIXME
* @param nullIsHigh indique si un des elements est null, s'il est plus
* grand ou plus petit que l'autre
* @return negatif si a inferieur b, 0 si a == b, positif si a superieur a b
*/
protected int compare(String a, String b, boolean nullIsHigh) {
int result;
if (nullIsHigh) {
result = nullIsHigh(a, b);
} else {
result = nullIsLow(a, b);
}
if (result == 0 && a != null && b != null) {
// on decoupe suivant le '.'
String[] aSeries = StringUtils.split(a, SERIES_SEPARATOR);
String[] bSeries = StringUtils.split(b, SERIES_SEPARATOR);
int length = Math.max(aSeries.length, bSeries.length);
for (int i=0; result==0 & i
* 1.2.3 retourne 3
* 99.100 retourne 2
*
*
* @param element FIXME
* @return FIXME
*/
protected int getComposantCount(String element) {
int result = 0;
if (StringUtils.isNotBlank(element)) {
result = 1 + StringUtils.countMatches(element, SERIES_SEPARATOR);
}
return result;
}
/**
* Retourne un des composants de l'element version
* @param element FIXME
* @param i FIXME
* @return FIXME
*/
protected String getComposant(String element, int i) {
String[] v = StringUtils.split(element, SERIES_SEPARATOR);
Validate.validIndex(v, i,
"Ask '%s', element '%s' don't have enought composant", i, element);
String s = v[i];
return s;
}
public String getVersion() {
return version;
}
/**
* Donne le nombre de composante de la version
*
* - 1.2.3 retourne 3
* - 99.100 retourne 2
*
*
* @return FIXME
*/
public int getVersionCount() {
int result = getComposantCount(getVersion());
return result;
}
/**
* Retourne un des composants de l'element version
* @param i FIXME
* @return FIXME
*/
public String getVersion(int i) {
String s = getComposant(getVersion(), i);
return s;
}
public String getMajor() {
String result = getVersion(0);
return result;
}
public String getMinor() {
String result = getVersion(1);
return result;
}
public String getPatch() {
String result = getVersion(2);
return result;
}
public String getPrerelease() {
return prerelease;
}
/**
* Donne le nombre de composante de la Prerelease
*
* - 1.2.3 retourne 3
* - 99.100 retourne 2
*
*
* @return FIXME
*/
public int getPrereleaseCount() {
int result = getComposantCount(getPrerelease());
return result;
}
/**
* Retourne un des composants de l'element version
* @param i FIXME
* @return FIXME
*/
public String getPrerelease(int i) {
String s = getComposant(getPrerelease(), i);
return s;
}
public String getBuild() {
return build;
}
/**
* Donne le nombre de composante de la Prerelease
*
* - 1.2.3 retourne 3
* - 99.100 retourne 2
*
*
* @return FIXME
*/
public int getBuildCount() {
int result = getComposantCount(getBuild());
return result;
}
/**
* Retourne un des composants de l'element version
* @param i FIXME
* @return FIXME
*/
public String getBuild(int i) {
String s = getComposant(getBuild(), i);
return s;
}
public String getSnapshot() {
return snapshot;
}
public boolean isSnapshot() {
return StringUtils.isNotBlank(snapshot);
}
static protected String ifNotNull(String prefix, String s) {
String result = "";
if (StringUtils.isNotBlank(s)) {
result = prefix + s;
}
return result;
}
@Override
public boolean equals(Object o) {
boolean result = o instanceof SemVer && this.compareTo((SemVer)o) == 0;
return result;
}
@Override
public int hashCode() {
return toString().hashCode();
}
/**
* Convertit la representation textuelle de la version en identifiant java valide :
* - en java : "-" "." "+" interdit
*
* @return la valeur ou les carateres interdits sont remplaces par '_'
*/
public String toJavaIdentifier() {
String result = toString();
// attention dans les crochets le '-' a une signification, il faut donc le mettre en 1er
result = result.replaceAll("[-+.]", "_");
return result;
}
@Override
public String toString() {
return getVersion()
+ ifNotNull(PRERELEASE_SEPARATOR, getPrerelease())
+ ifNotNull(BUILD_SEPARATOR, getBuild())
+ ifNotNull(SNAPSHOT_SEPARATOR, getSnapshot());
}
/**
* Retourne un objet creator initialise avec les donnees de ce SemVer
* ce qui permet de creer un nouveau SemVer en modifiant un des elements
* @return FIXME
*/
public SemVerCreator getCreator() {
return creator(this);
}
/**
* Indique si la chaine represente bien une version au format SemVer
* @param version FIXME
* @return FIXME
*/
static public boolean isSemVer(String version) {
if (StringUtils.endsWithIgnoreCase(version, SNAPSHOT)) {
version = StringUtils.substringBeforeLast(version, SNAPSHOT);
}
Matcher matcher = PATTERN_ALL.matcher(version);
boolean result = matcher.matches();
return result;
}
/**
* Permet de creer un objet version. Si des arguments sont passer en parametre
* ils sont pris dans l'ordre pour: la version, la prerelease, le build, le snapshot
*
* SemVer v = SemVer.creator().setVersion("1.2").setPrerelease("beta.1").setBuild("r123").done();
* SemVer v = SemVer.creator("1.2", "beta.1","r123", SemVer.SNAPSHOT).done();
*
* @param v FIXME
* @return FIXME
*/
static public SemVerCreator creator(String ... v) {
SemVerCreator result = new SemVerCreator();
if (v != null) {
if (v.length > 0) {
result.setVersion(v[0]);
if (v.length > 1) {
result.setPrerelease(v[1]);
if (v.length > 2) {
result.setBuild(v[2]);
if (v.length > 3) {
if (v[3] == null) {
result.setSnapshot(false);
} else if (StringUtils.equalsIgnoreCase(SNAPSHOT, v[3])) {
result.setSnapshot(true);
} else {
throw new IllegalArgumentException(String.format(
"Illegal SNAPSHOT string '%s'",
v[3]));
}
if (v.length > 4) {
throw new IllegalArgumentException(String.format(
"too many string arguments '%s' maximum 4",
v.length));
}
}
}
}
}
}
return result;
}
/**
* Createur qui permet de passer en parametre les different composante de
* l'element version
* @param v FIXME
* @return FIXME
*/
static public SemVerCreator creator(int ... v) {
SemVerCreator result = creator(StringUtils.join(v, SERIES_SEPARATOR));
return result;
}
/**
* Create creator initialized with SemVer value, you can change some Element
* by puting null value.
*
* SemVer n = SemVer.creator(v).setBuild("r123").setSnapshot(false).done();
*
* @param v FIXME
* @return FIXME
*/
static public SemVerCreator creator(SemVer v) {
SemVerCreator result = creator(
v.getVersion(), v.getPrerelease(), v.getBuild(), v.getSnapshot());
return result;
}
/**
* Retourne un Createur sans aucune information. Cette methode permet
* aussi de desambiguise les deux autres methode creator qui si elle
* n'ont pas de parametre sont semblable
*
* @return FIXME
*/
static public SemVerCreator creator() {
SemVerCreator result = new SemVerCreator();
return result;
}
/**
* Construit un objet version, la verification de la coherence est faite lors
* de l'appel du create.
*/
static public class SemVerCreator {
protected String version;
protected String prerelease;
protected String build;
protected String snapshot;
public SemVerCreator setVersion(Integer... v) {
// l'argument est bien Integer, car join ne travail qu'avec des objets :(
version = StringUtils.join(v, SERIES_SEPARATOR);
return this;
}
public SemVerCreator setVersion(String v) {
version = v;
return this;
}
public SemVerCreator setPrerelease(String v) {
prerelease = v;
return this;
}
public SemVerCreator setBuild(String v) {
build = v;
return this;
}
public SemVerCreator setSnapshot(boolean b) {
if (b) {
snapshot = SNAPSHOT;
} else {
snapshot = null;
}
return this;
}
/**
* Incremente la composante 'indice' de l'element version de 'inc', la composante
* doit representer un entier sinon une exception est leve
*
* @param indice la composante de la version a incrementer
* @param inc le nombre a lui ajouter
* @return new instance, this SemVer is not modified
*/
public SemVerCreator incVersion(int indice, int inc) {
String[] v = StringUtils.split(version, SERIES_SEPARATOR);
Validate.validIndex(v, indice, "Version don't have enought composant");
String s = v[indice];
Validate.isTrue(StringUtils.isNumeric(s), "String '%s' must be numeric", s);
int i = Integer.parseInt(s);
i += inc;
s = String.valueOf(i);
v[indice] = s;
version = StringUtils.join(v, SERIES_SEPARATOR);
return this;
}
/**
* Return new instance, this SemVer is not modified
* @return new instance where major version number is incremented by 1
*/
public SemVerCreator incMajor() {
return incVersion(0, 1);
}
/**
* Return new instance, this SemVer is not modified
* @return new instance where major version number is incremented by 1
*/
public SemVerCreator incMinor() {
return incVersion(1, 1);
}
/**
* Return new instance, this SemVer is not modified
* @return new instance where major version number is incremented by 1
*/
public SemVerCreator incPatch() {
return incVersion(2, 1);
}
public SemVer done() {
SemVer result = new SemVer(
StringUtils.defaultString(version) +
ifNotNull(PRERELEASE_SEPARATOR, prerelease) +
ifNotNull(BUILD_SEPARATOR, build) +
ifNotNull(SNAPSHOT_SEPARATOR, snapshot)
);
return result;
}
}
}