«1. Обзор

С развитием технологий DevOps стало обычным создавать и развертывать приложение несколько раз в день.

Поэтому каждой сборке присваивается уникальный номер версии, чтобы мы могли различать сборки. Иногда возникает необходимость сравнить строки версий программно.

В этой статье мы рассмотрим несколько способов сравнения строк версий в Java с помощью различных библиотек. Наконец, мы напишем пользовательскую программу для обработки общего сравнения версий и строк.

2. Использование maven-artifact

Для начала давайте рассмотрим, как Maven обрабатывает сравнение версий.

2.1. Зависимость Maven

Во-первых, мы добавим последнюю зависимость Maven от maven-artifact в наш pom.xml:

<dependency>
    <groupId>org.apache.maven</groupId>
    <artifactId>maven-artifact</artifactId>
    <version>3.6.3</version>
</dependency>

2.2. ComparableVersion

Давайте рассмотрим класс ComparableVersion. Он обеспечивает общую реализацию сравнения версий с неограниченным количеством компонентов версий.

Он содержит метод compareTo, и результат сравнения будет больше или меньше 0, когда одна версия больше или меньше другой, соответственно:

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);
assertTrue(version1_3.compareTo(version1_2) > 0);

Здесь мы можем подтвердить, что Версия 1.1 меньше версии 1.2, а версия 1.3 больше версии 1.2.

Однако при сравнении одинаковых версий в результате мы получим 0:

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

2.3. Разделители и квалификаторы версий

Кроме того, класс ComparableVersion учитывает точку (.) и дефис (-) в качестве разделителей, где точка разделяет основную и дополнительную версии, а дефис определяет квалификаторы:

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

Здесь мы могу подтвердить, что версия 1.1 лучше, чем альфа-версия 1.1.

Есть несколько известных квалификаторов, поддерживаемых ComparableVersion, таких как альфа, бета, веха, RC и моментальный снимок (в порядке от низшего к высшему):

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

Кроме того, это позволяет нам определить неизвестные квалификаторы и учитывает их порядок, после уже обсуждавшихся известных квалификаторов, с лексическим порядком без учета регистра:

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. Использование gradle-core

Как и Maven, Gradle также имеет встроенную возможность обработки версии сравнение.

3.1. Зависимость Maven

Во-первых, давайте добавим последнюю версию зависимости Maven от ядра gradle из репозитория Gradle Releases:

<dependency>
    <groupId>org.gradle</groupId>
    <artifactId>gradle-core</artifactId>
    <version>6.1.1</version>
</dependency>

3.2. VersionNumber

Класс VersionNumber, предоставленный Gradle, сравнивает две версии, подобно классу Maven ComparableVersion:

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);
assertTrue(version1_3.compareTo(version1_2) > 0);

VersionNumber version1_1_0 = VersionNumber.parse("1.1.0");
assertEquals(0, version1_1.compareTo(version1_1_0));

3.3. Компоненты версии

В отличие от класса ComparableVersion, класс VersionNumber поддерживает только пять компонентов версии — Major, Minor, Micro, Patch и 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. Схемы версий

Кроме того, VersionNumber поддерживает несколько различных схем версий, таких как Major.Minor.Micro-Qualifier и 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. Использование jackson-core

4.1 . Зависимость Maven

Подобно другим зависимостям, давайте добавим последнюю зависимость Maven от jackson-core в наш pom.xml:

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
    <version>2.11.1</version>
</dependency>

4.2. Version

Затем мы можем изучить класс Version Джексона, который может содержать информацию о версиях компонента вместе с необязательными значениями groupId и ArtiftId.

Таким образом, конструктор класса Version позволяет нам определять groupId и ArtiftId, а также такие компоненты, как Major, Minor и Patch:

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

Итак, давайте сравним несколько версий, используя класс Version: ~~ ~

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);
assertTrue(version1_3.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. Компонент snapshotInfo

Компонент snapshotInfo не используется при сравнении двух версий:

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

Кроме того, класс Version предоставляет метод isSnapshot для проверки наличия в версии компонента моментального снимка:

assertTrue(version1_1_snapshot.isSnapshot());

4.4 . Компоненты groupId и ArtiftId

Кроме того, этот класс сравнивает лексический порядок компонентов версии groupId и ArtiftId:

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. Использование Semver4J

Библиотека Semver4j позволяет нам следовать правилам семантического управления версиями. Спецификация на Java.

5.1. Зависимость Maven

Сначала мы добавим последнюю зависимость semver4j Maven:

<dependency>
    <groupId>com.vdurmont</groupId>
    <artifactId>semver4j</artifactId>
    <version>3.1.0</version>
</dependency>

5.2. Semver

Затем мы можем использовать класс Semver для определения версии:

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);
assertTrue(version1_3.compareTo(version1_2) > 0);

«

«Внутри он анализирует версию на такие компоненты, как Major, Minor и Patch.

5.3. Сравнение версий

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

Кроме того, класс Semver поставляется с различными встроенными методами, такими как isGreaterThan, isLowerThan и isEqualTo, для сравнения версий:

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

Кроме того, он предоставляет метод diff, который возвращает основное различие между двумя версиями. :

5.4. Стабильность версии

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

Кроме того, класс Semver поставляется с методом isStable для проверки стабильности версии, определяемой наличием или отсутствием суффикса:

6. Пользовательское решение

Мы видели несколько решений для сравнения строк версии. Если они не работают для конкретного варианта использования, возможно, нам придется написать собственное решение.

Вот простой пример, который работает для некоторых основных случаев — его всегда можно расширить, если нам нужно что-то еще.

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

Идея здесь состоит в том, чтобы разбить строки версий с помощью разделителя точек, а затем сравнить целочисленное преобразование каждого токена String, начиная слева. Если целочисленное значение токена такое же, проверяем следующий токен, продолжая этот шаг, пока не найдем разницу (или пока не достигнем последнего токена в любой строке):

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.0.1") > 0);
assertTrue(VersionCompare.compareVersions("1.1.2", "1.2.0") < 0);
assertEquals(0, VersionCompare.compareVersions("1.3.0", "1.3"));

Давайте проверим наше решение, сравнив несколько версий :

Этот код имеет ограничение, заключающееся в том, что он может сравнивать только номер версии, составленный из целых чисел, разделенных точками.

Таким образом, для сравнения буквенно-цифровых строк версий мы можем использовать регулярное выражение для разделения алфавитов и сравнения лексического порядка.

7. Заключение

В этой статье мы рассмотрели различные способы сравнения строк версий в Java.

Сначала мы рассмотрели встроенные решения, предоставляемые фреймворками сборки, такими как Maven и Gradle, с использованием зависимостей maven-artifact и gradle-core соответственно. Затем мы изучили функции сравнения версий библиотек jackson-core и semver4j.

Наконец, мы написали собственное решение для сравнения строки универсальной версии.