Comparaison de versions en Java

1. Vue d'ensemble

Avec l'avancement des technologies DevOps, il est courant de créer et de déployer une application plusieurs fois par jour.

Par conséquent, chaque build se voit attribuer un numéro de version unique afin que nous puissions faire la distinction entre les builds . Parfois, il est nécessaire de comparer les chaînes de version par programme.

Dans cet article, nous allons explorer quelques façons de comparer les chaînes de version en Java via diverses bibliothèques. Enfin, nous allons écrire un programme personnalisé pour gérer la comparaison générique de chaînes de version.

2. Utilisation de maven-artifact

Pour commencer, explorons comment Maven gère la comparaison de versions.

2.1. Dépendance de Maven

Tout d'abord, nous allons ajouter la dernière dépendance Maven -artifact Maven à notre pom.xml :

 org.apache.maven maven-artifact 3.6.3 

2.2. ComparableVersion

Explorons la classe ComparableVersion . Il fournit une implémentation générique de la comparaison de version avec un nombre illimité de composants de version .

Il contient une méthode compareTo , et le résultat de la comparaison sera supérieur ou inférieur à 0 lorsqu'une version est supérieure ou inférieure à l'autre, respectivement:

ComparableVersion version1_1 = new ComparableVersion("1.1"); ComparableVersion version1_2 = new ComparableVersion("1.2"); ComparableVersion version1_3 = new ComparableVersion("1.3"); assertTrue(version1_1.compareTo(version1_2)  0);

Ici, nous pouvons confirmer que la version 1.1 est inférieure à la version 1.2, et la version 1.3 est supérieure à la version 1.2.

Cependant, nous obtiendrons 0 à la suite de la comparaison des mêmes versions:

ComparableVersion version1_1_0 = new ComparableVersion("1.1.0"); assertEquals(0, version1_1.compareTo(version1_1_0));

2.3. Séparateurs et qualificatifs de version

De plus, la classe ComparableVersion respecte le point (.) Et le trait d'union (-) comme séparateurs, où le point sépare les versions majeures et mineures, et le trait d'union définit les qualificatifs :

ComparableVersion version1_1_alpha = new ComparableVersion("1.1-alpha"); assertTrue(version1_1.compareTo(version1_1_alpha) > 0);

Ici, nous pouvons confirmer que la version 1.1 est supérieure à la version 1.1-alpha.

Il existe quelques qualificatifs bien connus pris en charge par la ComparableVersion, tels que alpha , beta , milestone , RC et snapshot (dans l'ordre du plus bas au plus élevé):

ComparableVersion version1_1_beta = new ComparableVersion("1.1-beta"); ComparableVersion version1_1_milestone = new ComparableVersion("1.1-milestone"); ComparableVersion version1_1_rc = new ComparableVersion("1.1-rc"); ComparableVersion version1_1_snapshot = new ComparableVersion("1.1-snapshot"); assertTrue(version1_1_alpha.compareTo(version1_1_beta) < 0); assertTrue(version1_1_beta.compareTo(version1_1_milestone) < 0); assertTrue(version1_1_rc.compareTo(version1_1_snapshot) < 0); assertTrue(version1_1_snapshot.compareTo(version1_1) < 0);

De plus, cela nous permet de définir des qualificatifs inconnus et respecte leur ordre, après les qualificatifs connus déjà discutés, avec un ordre lexical insensible à la casse :

ComparableVersion version1_1_c = new ComparableVersion("1.1-c"); ComparableVersion version1_1_z = new ComparableVersion("1.1-z"); ComparableVersion version1_1_1 = new ComparableVersion("1.1.1"); assertTrue(version1_1_c.compareTo(version1_1_z) < 0); assertTrue(version1_1_z.compareTo(version1_1_1) < 0);

3. Utilisation de gradle-core

Comme Maven, Gradle a également la capacité intégrée de gérer la comparaison de versions.

3.1. Dépendance de Maven

Tout d'abord, ajoutons la dernière dépendance Maven gradle-core du repo Gradle Releases:

 org.gradle gradle-core 6.1.1 

3.2. Numéro de version

La classe VersionNumber fournie par Gradle compare deux versions, similaires à la classe ComparableVersion de Maven :

VersionNumber version1_1 = VersionNumber.parse("1.1"); VersionNumber version1_2 = VersionNumber.parse("1.2"); VersionNumber version1_3 = VersionNumber.parse("1.3"); assertTrue(version1_1.compareTo(version1_2)  0); VersionNumber version1_1_0 = VersionNumber.parse("1.1.0"); assertEquals(0, version1_1.compareTo(version1_1_0)); 

3.3. Composants de la version

Contrairement à la classe ComparableVersion , la classe VersionNumber ne prend en charge que cinq composants de version: Major , Minor , Micro , Patch et Qualifier :

VersionNumber version1_1_1_1_alpha = VersionNumber.parse("1.1.1.1-alpha"); assertTrue(version1_1.compareTo(version1_1_1_1_alpha) < 0); VersionNumber version1_1_beta = VersionNumber.parse("1.1.0.0-beta"); assertTrue(version1_1_beta.compareTo(version1_1_1_1_alpha) < 0);

3.4. Schémas de version

En outre, VersionNumber prend en charge quelques schémas de version différents tels que Major.Minor.Micro-Qualifier et Major.Minor.Micro.Patch-Qualifier :

VersionNumber version1_1_1_snapshot = VersionNumber.parse("1.1.1-snapshot"); assertTrue(version1_1_1_1_alpha.compareTo(version1_1_1_snapshot) < 0);

4. Utilisation de jackson-core

4.1. Dépendance de Maven

Semblable à d'autres dépendances, ajoutons la dernière dépendance Maven jackson-core à notre pom.xml :

 com.fasterxml.jackson.core jackson-core 2.11.1 

4.2. Version

Ensuite, nous pouvons examiner la classe Version de Jackson , qui peut contenir des informations de version d'un composant avec les valeurs facultatives groupId et artifactId .

Therefore, the constructor of the Version class allows us to define groupId and artifactId, along with components like Major, Minor, and Patch:

public Version (int major, int minor, int patchLevel, String snapshotInfo, String groupId, String artifactId) { //... }

So, let's compare a few versions using the Version class:

Version version1_1 = new Version(1, 1, 0, null, null, null); Version version1_2 = new Version(1, 2, 0, null, null, null); Version version1_3 = new Version(1, 3, 0, null, null, null); assertTrue(version1_1.compareTo(version1_2)  0); Version version1_1_1 = new Version(1, 1, 1, null, null, null); assertTrue(version1_1.compareTo(version1_1_1) < 0);

4.3. The snapshotInfo Component

The snapshotInfo component isn't used while comparing two versions:

Version version1_1_snapshot = new Version(1, 1, 0, "snapshot", null, null); assertEquals(0, version1_1.compareTo(version1_1_snapshot));

Additionally, the Version class provides the isSnapshot method to check if the version contains a snapshot component:

assertTrue(version1_1_snapshot.isSnapshot());

4.4. The groupId and artifactId Components

Also, this class compares the lexical order of the groupId and artifactId version components:

Version version1_1_maven = new Version(1, 1, 0, null, "org.apache.maven", null); Version version1_1_gradle = new Version(1, 1, 0, null, "org.gradle", null); assertTrue(version1_1_maven.compareTo(version1_1_gradle) < 0);

5. Using Semver4J

The Semver4j library allows us to follow the rules of the semantic versioning specification in Java.

5.1. Maven Dependency

First, we'll add the latest semver4j Maven dependency:

 com.vdurmont semver4j 3.1.0 

5.2. Semver

Then, we can use the Semver class to define a version:

Semver version1_1 = new Semver("1.1.0"); Semver version1_2 = new Semver("1.2.0"); Semver version1_3 = new Semver("1.3.0"); assertTrue(version1_1.compareTo(version1_2)  0); 

Internally, it parses a version into components like Major, Minor, and Patch.

5.3. Version Comparison

Also, the Semver class comes with various built-in methods like isGreaterThan, isLowerThan, and isEqualTo for version comparison:

Semver version1_1_alpha = new Semver("1.1.0-alpha"); assertTrue(version1_1.isGreaterThan(version1_1_alpha)); Semver version1_1_beta = new Semver("1.1.0-beta"); assertTrue(version1_1_alpha.isLowerThan(version1_1_beta)); assertTrue(version1_1.isEqualTo("1.1.0"));

Likewise, it provides the diff method that returns the main difference between the two versions:

assertEquals(VersionDiff.MAJOR, version1_1.diff("2.1.0")); assertEquals(VersionDiff.MINOR, version1_1.diff("1.2.3")); assertEquals(VersionDiff.PATCH, version1_1.diff("1.1.1"));

5.4. Version Stability

Also, the Semver class comes with the isStable method to check the stability of a version, determined by the presence or absence of a suffix:

assertTrue(version1_1.isStable()); assertFalse(version1_1_alpha.isStable());

6. Custom Solution

We've seen a few solutions to compare the version strings. If they don't work for a specific use-case, we might have to write a custom solution.

Here's a simple example that works for some basic cases — it can always be extended if we need something more.

The idea here is to tokenize the version strings using a dot delimiter, and then compare integer conversion of every String token, beginning from the left. If the token's integer value is the same, examine the next token, continuing this step until we find a difference (or until we reach the last token in either string):

public static int compareVersions(String version1, String version2) { int comparisonResult = 0; String[] version1Splits = version1.split("\\."); String[] version2Splits = version2.split("\\."); int maxLengthOfVersionSplits = Math.max(version1Splits.length, version2Splits.length); for (int i = 0; i < maxLengthOfVersionSplits; i++){ Integer v1 = i < version1Splits.length ? Integer.parseInt(version1Splits[i]) : 0; Integer v2 = i < version2Splits.length ? Integer.parseInt(version2Splits[i]) : 0; int compare = v1.compareTo(v2); if (compare != 0) { comparisonResult = compare; break; } } return comparisonResult; }

Let's verify our solution by comparing a few versions:

assertTrue(VersionCompare.compareVersions("1.0.1", "1.1.2") < 0); assertTrue(VersionCompare.compareVersions("1.0.1", "1.10")  0); assertTrue(VersionCompare.compareVersions("1.1.2", "1.2.0") < 0); assertEquals(0, VersionCompare.compareVersions("1.3.0", "1.3"));

This code has a limitation that it can only compare a version number made of integers delimited by dots.

Therefore, for comparing alphanumeric version strings, we can use a regular expression to segregate alphabets and compare the lexical order.

7. Conclusion

In this article, we looked into various ways to compare version strings in Java.

Dans un premier temps, nous avons examiné les solutions intégrées fournies par les frameworks de construction comme Maven et Gradle, en utilisant respectivement les dépendances maven- artifact et gradle-core . Ensuite, nous avons exploré les fonctionnalités de comparaison de versions des bibliothèques jackson-core et semver4j .

Enfin, nous avons écrit une solution personnalisée pour la comparaison de chaînes de version générique.

Comme d'habitude, toutes les implémentations de code sont disponibles sur sur GitHub.