Confronto delle versioni in Java

1. Panoramica

Con il progresso delle tecnologie DevOps, è comune creare e distribuire un'applicazione più volte in un giorno.

Pertanto, a ogni build viene assegnato un numero di versione univoco in modo da poter distinguere tra build . A volte, sorge la necessità di confrontare le stringhe di versione a livello di codice.

In questo articolo, esploreremo alcuni modi per confrontare le stringhe di versione in Java attraverso varie librerie. Infine, scriveremo un programma personalizzato per gestire il confronto generico della stringa di versione.

2. Utilizzo di maven-artefatto

Per cominciare, esploriamo come Maven gestisce il confronto delle versioni.

2.1. Dipendenza da Maven

Innanzitutto, aggiungeremo l'ultima dipendenza Maven di maven -artifact al nostro pom.xml :

 org.apache.maven maven-artifact 3.6.3 

2.2. ComparableVersion

Esploriamo la classe ComparableVersion . Fornisce un'implementazione generica del confronto delle versioni con un numero illimitato di componenti della versione .

Contiene un metodo compareTo e il risultato del confronto sarà maggiore o minore di 0 quando una versione è rispettivamente maggiore o minore dell'altra:

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);

Qui, possiamo confermare che la versione 1.1 è inferiore alla versione 1.2 e la versione 1.3 è maggiore della versione 1.2.

Tuttavia, otterremo 0 come risultato quando confrontiamo le stesse versioni:

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

2.3. Separatori di versione e qualificatori

Inoltre, la classe ComparableVersion rispetta il punto (.) E il trattino (-) come separatori, dove il punto separa le versioni principali e secondarie e il trattino definisce i qualificatori :

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

Qui possiamo confermare che la versione 1.1 è maggiore della versione 1.1-alpha.

Esistono alcuni qualificatori ben noti supportati da ComparableVersion come alpha , beta , milestone , RC e snapshot (nell'ordine dal più basso al più alto):

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);

Inoltre, ci consente di definire qualificatori sconosciuti e rispetta il loro ordine, dopo i qualificatori noti già discussi, con ordine lessicale senza distinzione tra maiuscole e minuscole :

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. Utilizzando gradle-core

Come Maven, anche Gradle ha la capacità integrata di gestire il confronto delle versioni.

3.1. Dipendenza da Maven

Innanzitutto, aggiungiamo l'ultima dipendenza Maven gradle-core dal repository Gradle Releases:

 org.gradle gradle-core 6.1.1 

3.2. Numero della versione

La classe VersionNumber fornita da Gradle confronta due versioni, simili alla classe ComparableVersion di 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. Componenti della versione

A differenza della classe ComparableVersion , la classe VersionNumber supporta solo cinque componenti della versione: Major , Minor , Micro , Patch e 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. Schemi di versione

Inoltre, VersionNumber supporta un paio di diversi schemi di versione come Major.Minor.Micro-Qualifier e 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. Utilizzando jackson-core

4.1. Dipendenza da Maven

Simile ad altre dipendenze, aggiungiamo l'ultima dipendenza Maven jackson-core al nostro pom.xml :

 com.fasterxml.jackson.core jackson-core 2.11.1 

4.2. Versione

Quindi, possiamo esaminare la classe Version di Jackson , che può contenere informazioni sul controllo delle versioni di un componente insieme ai valori groupId e artifactId facoltativi .

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.

All'inizio, abbiamo esaminato le soluzioni integrate fornite da framework di compilazione come Maven e Gradle, utilizzando rispettivamente le dipendenze maven- artifact e gradle-core . Quindi, abbiamo esplorato le funzionalità di confronto delle versioni delle librerie jackson-core e semver4j .

Infine, abbiamo scritto una soluzione personalizzata per il confronto di stringhe di versioni generiche.

Come al solito, tutte le implementazioni del codice sono disponibili su GitHub.