From 71365339338b8d6a3037b3a6447e9e22679d2166 Mon Sep 17 00:00:00 2001 From: "Matteo Franci a.k.a. Fugerit" Date: Sun, 6 Jul 2025 13:00:36 +0200 Subject: [PATCH] Create version compare utility #99 --- CHANGELOG.md | 4 + .../fugerit/java/core/cfg/VersionCompare.java | 76 +++++++++++++++++++ .../java/core/cfg/TestVersionCompare.java | 72 ++++++++++++++++++ 3 files changed, 152 insertions(+) create mode 100644 fj-core/src/main/java/org/fugerit/java/core/cfg/VersionCompare.java create mode 100644 fj-core/src/test/java/test/org/fugerit/java/core/cfg/TestVersionCompare.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a0a363c..e85cc241 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Create version compare utility + ### Deprecated - MavenProps.getPropery() method. (flagged for removal in future versions) diff --git a/fj-core/src/main/java/org/fugerit/java/core/cfg/VersionCompare.java b/fj-core/src/main/java/org/fugerit/java/core/cfg/VersionCompare.java new file mode 100644 index 00000000..aca77097 --- /dev/null +++ b/fj-core/src/main/java/org/fugerit/java/core/cfg/VersionCompare.java @@ -0,0 +1,76 @@ +package org.fugerit.java.core.cfg; + +/** + * Utility class for comparing version strings in the format "MAJOR.MINOR.PATCH", + * optionally including qualifiers (e.g. "-SNAPSHOT"). Provides a compare method + * that handles versions with different lengths and a convenience method for + * checking if one version is greater than another. + * + *

This implementation follows standard Semantic Versioning comparison rules: + * numeric comparison of components, missing components treated as zero.

+ * + * @author Your Name + * @since 1.0 + */ +public class VersionCompare { + + private VersionCompare() { + // Prevent instantiation + } + + /** + * Extracts the numeric portion of a version string component, ignoring any + * qualifier suffix that begins with a hyphen. + * + * @param part the version component, e.g. "1", "2-SNAPSHOT" + * @return the integer value of the numeric portion + * @throws NumberFormatException if the numeric portion is not a valid integer + */ + private static int convertVersionPart(String part) { + String numeric = part.split("-", 2)[0]; + return Integer.parseInt(numeric); + } + + /** + * Compares two version strings in "MAJOR.MINOR.PATCH(...)" format. + * Missing components are treated as zero. + * + * @param v1 the first version string to compare + * @param v2 the second version string to compare + * @return a negative integer if {@code v1 < v2}, zero if equal, or a positive + * integer if {@code v1 > v2} + * @throws NumberFormatException if any component is not a valid integer + * + * @since 1.0 + */ + public static int compare(String v1, String v2) { + String[] parts1 = v1.split("\\."); + String[] parts2 = v2.split("\\."); + int length = Math.max(parts1.length, parts2.length); + + for (int i = 0; i < length; i++) { + int p1 = i < parts1.length ? convertVersionPart(parts1[i]) : 0; + int p2 = i < parts2.length ? convertVersionPart(parts2[i]) : 0; + if (p1 != p2) { + return Integer.compare(p1, p2); + } + } + return 0; + } + + /** + * Determines if the first version string represents a version strictly greater + * than the second. + * + * @param v1 the first version string + * @param v2 the second version string + * @return {@code true} if {@code v1} is greater than {@code v2}, {@code false} + * otherwise (including equality) + * + * @since 1.0 + */ + public static boolean isGreaterThan(String v1, String v2) { + return compare(v1, v2) > 0; + } + +} diff --git a/fj-core/src/test/java/test/org/fugerit/java/core/cfg/TestVersionCompare.java b/fj-core/src/test/java/test/org/fugerit/java/core/cfg/TestVersionCompare.java new file mode 100644 index 00000000..9aaff098 --- /dev/null +++ b/fj-core/src/test/java/test/org/fugerit/java/core/cfg/TestVersionCompare.java @@ -0,0 +1,72 @@ +package test.org.fugerit.java.core.cfg; + +import org.fugerit.java.core.cfg.VersionCompare; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class TestVersionCompare { + + // Version constants + private static final String V_1_0_0 = "1.0.0"; + private static final String V_2_0_0 = "2.0.0"; + private static final String V_1_9_9 = "1.9.9"; + private static final String V_3_0_0 = "3.0.0"; + private static final String V_2_5_6 = "2.5.6"; + private static final String V_1_2_0 = "1.2.0"; + private static final String V_1_1_9 = "1.1.9"; + private static final String V_1_3 = "1.3"; + private static final String V_1_2_9 = "1.2.9"; + private static final String V_1_0_1 = "1.0.1"; + private static final String V_1_0_5 = "1.0.5"; + private static final String V_1_0_4 = "1.0.4"; + private static final String V_1_0 = "1.0"; + private static final String V_1_0_0_SNAPSHOT = "1.0.0-SNAPSHOT"; + private static final String V_1_0_1_RC1 = "1.0.1-RC1"; + private static final String V_1_A_0 = "1.a.0"; + private static final String V_X_Y_Z = "x.y.z"; + + @Test + void testEqualVersions() { + assertEquals(0, VersionCompare.compare(V_1_0_0, V_1_0_0)); + assertFalse(VersionCompare.isGreaterThan(V_1_0_0, V_1_0_0)); + } + + @Test + void testGreaterMajorVersion() { + assertTrue(VersionCompare.isGreaterThan(V_2_0_0, V_1_9_9)); + assertEquals(1, VersionCompare.compare(V_3_0_0, V_2_5_6)); + } + + @Test + void testGreaterMinorVersion() { + assertTrue(VersionCompare.isGreaterThan(V_1_2_0, V_1_1_9)); + assertEquals(1, VersionCompare.compare(V_1_3, V_1_2_9)); + } + + @Test + void testGreaterPatchVersion() { + assertTrue(VersionCompare.isGreaterThan(V_1_0_1, V_1_0_0)); + assertEquals(1, VersionCompare.compare(V_1_0_5, V_1_0_4)); + } + + @Test + void testVersionWithDifferentLength() { + assertEquals(0, VersionCompare.compare(V_1_0, V_1_0_0)); + assertTrue(VersionCompare.isGreaterThan(V_1_0_1, V_1_0)); + assertFalse(VersionCompare.isGreaterThan(V_1_0, V_1_0_1)); + } + + @Test + void testVersionWithQualifierSuffix() { + assertEquals(0, VersionCompare.compare(V_1_0_0_SNAPSHOT, V_1_0_0)); + assertTrue(VersionCompare.isGreaterThan(V_1_0_1_RC1, V_1_0_0)); + } + + @Test + void testInvalidVersionShouldThrowException() { + assertThrows(NumberFormatException.class, () -> VersionCompare.compare(V_1_A_0, V_1_0_0)); + assertThrows(NumberFormatException.class, () -> VersionCompare.compare(V_1_0, V_X_Y_Z)); + } + +}