diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f33b06..b44197c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - New `JavaVersion` and `JavaVersionScheme` classes to represent and interact with Java language versions (e.g. 11, 1.4, 17.0.11+34-cthing, 8u17) -- A `JavaVersionExample` class has been added to the examples sub-project +- A `JavaVersionExample` class has been added to the examples subproject - New `VersionConstraint.complement()` method to obtain a constraint representing all versions not in a constraint. For example, the complement of the constraint `[1.5,2.0)` is `(,1.5),[2.0,)`. - New `VersionConstraint.isNotEmpty()` method to indicate that a version constraint contains version diff --git a/examples/src/main/java/org/cthing/versionparser/examples/JavaVersionExample.java b/examples/src/main/java/org/cthing/versionparser/examples/JavaVersionExample.java index 1d423e7..9860fe6 100644 --- a/examples/src/main/java/org/cthing/versionparser/examples/JavaVersionExample.java +++ b/examples/src/main/java/org/cthing/versionparser/examples/JavaVersionExample.java @@ -63,6 +63,12 @@ public static void main(final String[] args) throws VersionParsingException { assertThat(JavaVersionScheme.isVersion(JavaVersionScheme.JAVA_17, "17.0.11")).isTrue(); assertThat(JavaVersionScheme.isVersion(JavaVersionScheme.JAVA_17, "21")).isFalse(); + assertThat(JavaVersionScheme.isVersion(JavaVersionScheme.JAVA_17_PLUS, "11")).isFalse(); + assertThat(JavaVersionScheme.isVersion(JavaVersionScheme.JAVA_17_PLUS, "16")).isFalse(); + assertThat(JavaVersionScheme.isVersion(JavaVersionScheme.JAVA_17_PLUS, "17")).isTrue(); + assertThat(JavaVersionScheme.isVersion(JavaVersionScheme.JAVA_17_PLUS, "17.0.11")).isTrue(); + assertThat(JavaVersionScheme.isVersion(JavaVersionScheme.JAVA_17_PLUS, "21")).isTrue(); + // Runtime Java version assertThat(JavaVersion.RUNTIME_VERSION.getFeature()).isGreaterThanOrEqualTo(17); } diff --git a/src/main/java/org/cthing/versionparser/java/JavaVersion.java b/src/main/java/org/cthing/versionparser/java/JavaVersion.java index 0c87b15..79adf40 100644 --- a/src/main/java/org/cthing/versionparser/java/JavaVersion.java +++ b/src/main/java/org/cthing/versionparser/java/JavaVersion.java @@ -88,63 +88,70 @@ public boolean isPreRelease() { } /** - * Obtains the build number, if present. See {@link Runtime.Version#build()}. + * Obtains the build number, if present. * * @return Build number + * @see Runtime.Version#build() */ public Optional getBuild() { return this.javaVersion.build(); } /** - * Obtains the feature component of the version. See {@link Runtime.Version#feature()}. + * Obtains the feature component of the version. * * @return Feature component of the version. + * @see Runtime.Version#feature() */ public int getFeature() { return this.javaVersion.feature(); } /** - * Obtains the interim component of the version. See {@link Runtime.Version#interim()}. + * Obtains the interim component of the version. * * @return Interim component of the version. + * @see Runtime.Version#interim() */ public int getInterim() { return this.javaVersion.interim(); } /** - * Obtains the optional portion of the version, if present. See {@link Runtime.Version#optional()}. + * Obtains the optional portion of the version, if present. * * @return Optional portion of the version. + * @see Runtime.Version#optional() */ public Optional getOptional() { return this.javaVersion.optional(); } /** - * Obtains the patch portion of the version. See {@link Runtime.Version#patch()}. + * Obtains the patch portion of the version. * * @return Patch portion of the version. + * @see Runtime.Version#patch() */ public int getPatch() { return this.javaVersion.patch(); } /** - * Obtains the pre-release portion of the version, if present. See {@link Runtime.Version#pre()}. + * Obtains the pre-release portion of the version, if present. * * @return Pre-release portion of the version. + * @see Runtime.Version#pre() */ public Optional getPre() { return this.javaVersion.pre(); } /** - * Obtains the update portion of the version. See {@link Runtime.Version#update()}. + * Obtains the update portion of the version. * * @return Update portion of the version. + * @see Runtime.Version#update() */ public int getUpdate() { return this.javaVersion.update(); diff --git a/src/main/java/org/cthing/versionparser/java/JavaVersionScheme.java b/src/main/java/org/cthing/versionparser/java/JavaVersionScheme.java index d5c6c7e..ab5cb21 100644 --- a/src/main/java/org/cthing/versionparser/java/JavaVersionScheme.java +++ b/src/main/java/org/cthing/versionparser/java/JavaVersionScheme.java @@ -17,124 +17,169 @@ */ public final class JavaVersionScheme { - /** Represents the range of versions corresponding to Java 1.0. */ - public static final VersionConstraint JAVA_1_0; + /** Any release of Java 1.0. */ + public static final VersionConstraint JAVA_1_0 = safeParseRange("1.0"); - /** Represents the range of versions corresponding to Java 1.1. */ - public static final VersionConstraint JAVA_1_1; + /** Any release of Java 1.1. */ + public static final VersionConstraint JAVA_1_1 = safeParseRange("1.1"); - /** Represents the range of versions corresponding to Java 1.2. */ - public static final VersionConstraint JAVA_1_2; + /** Any release of Java 1.2. */ + public static final VersionConstraint JAVA_1_2 = safeParseRange("1.2"); - /** Represents the range of versions corresponding to Java 1.3. */ - public static final VersionConstraint JAVA_1_3; + /** Any release of Java 1.3. */ + public static final VersionConstraint JAVA_1_3 = safeParseRange("1.3"); - /** Represents the range of versions corresponding to Java 1.4. */ - public static final VersionConstraint JAVA_1_4; + /** Any release of Java 1.4. */ + public static final VersionConstraint JAVA_1_4 = safeParseRange("1.4"); - /** Represents the range of versions corresponding to Java 1.5. */ - public static final VersionConstraint JAVA_1_5; + /** Any release of Java 1.5. */ + public static final VersionConstraint JAVA_1_5 = safeParseRange("1.5"); - /** Represents the range of versions corresponding to Java 1.6. */ - public static final VersionConstraint JAVA_1_6; + /** Any release of Java 1.6. */ + public static final VersionConstraint JAVA_1_6 = safeParseRange("1.6"); - /** Represents the range of versions corresponding to Java 1.7. */ - public static final VersionConstraint JAVA_1_7; + /** Any release of Java 1.7. */ + public static final VersionConstraint JAVA_1_7 = safeParseRange("1.7"); - /** Represents the range of versions corresponding to Java 1.8. */ - public static final VersionConstraint JAVA_1_8; + /** Any release of Java 1.8. */ + public static final VersionConstraint JAVA_1_8 = safeParseRange("1.8"); - /** Represents the range of versions corresponding to Java 9. */ - public static final VersionConstraint JAVA_9; + /** Any release of Java 9. */ + public static final VersionConstraint JAVA_9 = safeParseRange("9"); - /** Represents the range of versions corresponding to Java 10. */ - public static final VersionConstraint JAVA_10; + /** Any release of Java 10. */ + public static final VersionConstraint JAVA_10 = safeParseRange("10"); - /** Represents the range of versions corresponding to Java 11. */ - public static final VersionConstraint JAVA_11; + /** Any release of Java 11. */ + public static final VersionConstraint JAVA_11 = safeParseRange("11"); - /** Represents the range of versions corresponding to Java 12. */ - public static final VersionConstraint JAVA_12; + /** Any release of Java 12. */ + public static final VersionConstraint JAVA_12 = safeParseRange("12"); - /** Represents the range of versions corresponding to Java 13. */ - public static final VersionConstraint JAVA_13; + /** Any release of Java 13. */ + public static final VersionConstraint JAVA_13 = safeParseRange("13"); - /** Represents the range of versions corresponding to Java 14. */ - public static final VersionConstraint JAVA_14; + /** Any release of Java 14. */ + public static final VersionConstraint JAVA_14 = safeParseRange("14"); - /** Represents the range of versions corresponding to Java 15. */ - public static final VersionConstraint JAVA_15; + /** Any release of Java 15. */ + public static final VersionConstraint JAVA_15 = safeParseRange("15"); - /** Represents the range of versions corresponding to Java 16. */ - public static final VersionConstraint JAVA_16; + /** Any release of Java 16. */ + public static final VersionConstraint JAVA_16 = safeParseRange("16"); - /** Represents the range of versions corresponding to Java 17. */ - public static final VersionConstraint JAVA_17; + /** Any release of Java 17. */ + public static final VersionConstraint JAVA_17 = safeParseRange("17"); - /** Represents the range of versions corresponding to Java 18. */ - public static final VersionConstraint JAVA_18; + /** Any release of Java 18. */ + public static final VersionConstraint JAVA_18 = safeParseRange("18"); - /** Represents the range of versions corresponding to Java 19. */ - public static final VersionConstraint JAVA_19; + /** Any release of Java 19. */ + public static final VersionConstraint JAVA_19 = safeParseRange("19"); - /** Represents the range of versions corresponding to Java 20. */ - public static final VersionConstraint JAVA_20; + /** Any release of Java 20. */ + public static final VersionConstraint JAVA_20 = safeParseRange("20"); - /** Represents the range of versions corresponding to Java 21. */ - public static final VersionConstraint JAVA_21; + /** Any release of Java 21. */ + public static final VersionConstraint JAVA_21 = safeParseRange("21"); - /** Represents the range of versions corresponding to Java 22. */ - public static final VersionConstraint JAVA_22; + /** Any release of Java 22. */ + public static final VersionConstraint JAVA_22 = safeParseRange("22"); - /** Represents the range of versions corresponding to Java 23. */ - public static final VersionConstraint JAVA_23; + /** Any release of Java 23. */ + public static final VersionConstraint JAVA_23 = safeParseRange("23"); - /** Represents the range of versions corresponding to Java 24. */ - public static final VersionConstraint JAVA_24; + /** Any release of Java 24. */ + public static final VersionConstraint JAVA_24 = safeParseRange("24"); - /** Represents the range of versions corresponding to Java 25. */ - public static final VersionConstraint JAVA_25; + /** Any release of Java 25. */ + public static final VersionConstraint JAVA_25 = safeParseRange("25"); - /** Represents the range of versions corresponding to Java 26. */ - public static final VersionConstraint JAVA_26; + /** Any release of Java 26. */ + public static final VersionConstraint JAVA_26 = safeParseRange("26"); - /** Represents the range of versions corresponding to Java 27. */ - public static final VersionConstraint JAVA_27; + /** Any release of Java 27. */ + public static final VersionConstraint JAVA_27 = safeParseRange("27"); + + + /** Any release of Java 1.2 or greater. */ + public static final VersionConstraint JAVA_1_2_PLUS = safeParseRange("[1.2,)"); + + /** Any release of Java 1.3 or greater. */ + public static final VersionConstraint JAVA_1_3_PLUS = safeParseRange("[1.3,)"); + + /** Any release of Java 1.4 or greater. */ + public static final VersionConstraint JAVA_1_4_PLUS = safeParseRange("[1.4,)"); + + /** Any release of Java 1.5 or greater. */ + public static final VersionConstraint JAVA_1_5_PLUS = safeParseRange("[1.5,)"); + + /** Any release of Java 1.6 or greater. */ + public static final VersionConstraint JAVA_1_6_PLUS = safeParseRange("[1.6,)"); + + /** Any release of Java 1.7 or greater. */ + public static final VersionConstraint JAVA_1_7_PLUS = safeParseRange("[1.7,)"); + + /** Any release of Java 1.8 or greater. */ + public static final VersionConstraint JAVA_1_8_PLUS = safeParseRange("[1.8,)"); + + /** Any release of Java 9 or greater. */ + public static final VersionConstraint JAVA_9_PLUS = safeParseRange("[9,)"); + + /** Any release of Java 10 or greater. */ + public static final VersionConstraint JAVA_10_PLUS = safeParseRange("[10,)"); + + /** Any release of Java 11 or greater. */ + public static final VersionConstraint JAVA_11_PLUS = safeParseRange("[11,)"); + + /** Any release of Java 12 or greater. */ + public static final VersionConstraint JAVA_12_PLUS = safeParseRange("[12,)"); + + /** Any release of Java 13 or greater. */ + public static final VersionConstraint JAVA_13_PLUS = safeParseRange("[13,)"); + + /** Any release of Java 14 or greater. */ + public static final VersionConstraint JAVA_14_PLUS = safeParseRange("[14,)"); + + /** Any release of Java 15 or greater. */ + public static final VersionConstraint JAVA_15_PLUS = safeParseRange("[15,)"); + + /** Any release of Java 16 or greater. */ + public static final VersionConstraint JAVA_16_PLUS = safeParseRange("[16,)"); + + /** Any release of Java 17 or greater. */ + public static final VersionConstraint JAVA_17_PLUS = safeParseRange("[17,)"); + + /** Any release of Java 18 or greater. */ + public static final VersionConstraint JAVA_18_PLUS = safeParseRange("[18,)"); + + /** Any release of Java 19 or greater. */ + public static final VersionConstraint JAVA_19_PLUS = safeParseRange("[19,)"); + + /** Any release of Java 20 or greater. */ + public static final VersionConstraint JAVA_20_PLUS = safeParseRange("[20,)"); + + /** Any release of Java 21 or greater. */ + public static final VersionConstraint JAVA_21_PLUS = safeParseRange("[21,)"); + + /** Any release of Java 22 or greater. */ + public static final VersionConstraint JAVA_22_PLUS = safeParseRange("[22,)"); + + /** Any release of Java 23 or greater. */ + public static final VersionConstraint JAVA_23_PLUS = safeParseRange("[23,)"); + + /** Any release of Java 24 or greater. */ + public static final VersionConstraint JAVA_24_PLUS = safeParseRange("[24,)"); + + /** Any release of Java 25 or greater. */ + public static final VersionConstraint JAVA_25_PLUS = safeParseRange("[25,)"); + + /** Any release of Java 26 or greater. */ + public static final VersionConstraint JAVA_26_PLUS = safeParseRange("[26,)"); + + /** Any release of Java 27 or greater. */ + public static final VersionConstraint JAVA_27_PLUS = safeParseRange("[27,)"); - static { - try { - JAVA_1_0 = parseRange("1.0"); - JAVA_1_1 = parseRange("1.1"); - JAVA_1_2 = parseRange("1.2"); - JAVA_1_3 = parseRange("1.3"); - JAVA_1_4 = parseRange("1.4"); - JAVA_1_5 = parseRange("1.5"); - JAVA_1_6 = parseRange("1.6"); - JAVA_1_7 = parseRange("1.7"); - JAVA_1_8 = parseRange("1.8"); - JAVA_9 = parseRange("9"); - JAVA_10 = parseRange("10"); - JAVA_11 = parseRange("11"); - JAVA_12 = parseRange("12"); - JAVA_13 = parseRange("13"); - JAVA_14 = parseRange("14"); - JAVA_15 = parseRange("15"); - JAVA_16 = parseRange("16"); - JAVA_17 = parseRange("17"); - JAVA_18 = parseRange("18"); - JAVA_19 = parseRange("19"); - JAVA_20 = parseRange("20"); - JAVA_21 = parseRange("21"); - JAVA_22 = parseRange("22"); - JAVA_23 = parseRange("23"); - JAVA_24 = parseRange("24"); - JAVA_25 = parseRange("25"); - JAVA_26 = parseRange("26"); - JAVA_27 = parseRange("27"); - } catch (final VersionParsingException ex) { - throw new IllegalStateException(ex); - } - } @NoCoverageGenerated private JavaVersionScheme() { @@ -188,19 +233,17 @@ public static VersionConstraint parseRange(final String versionRange) throws Ver // A standalone version is treated as a range from that version inclusive to the // next feature version exclusive. Accommodations are made for old versions. if (!trimmedRange.startsWith("[") && !trimmedRange.startsWith("(")) { - final JavaVersion min = parseVersion(trimmedRange); + final JavaVersion min = JavaVersion.parse(trimmedRange); if (trimmedRange.startsWith("1.0")) { - return new VersionConstraint(min, parseVersion("1.1"), true, false); + return new VersionConstraint(min, JavaVersion.parse("1.1"), true, false); } final String maxFeature = Integer.toString(min.getFeature() + 1, 10); - final JavaVersion max = parseVersion(trimmedRange.startsWith("1.") ? "1." + maxFeature : maxFeature); + final JavaVersion max = JavaVersion.parse(trimmedRange.startsWith("1.") ? "1." + maxFeature : maxFeature); return new VersionConstraint(min, max, true, false); } - String rangeBuffer = trimmedRange; - final boolean minIncluded; final boolean maxIncluded; @@ -218,7 +261,7 @@ public static VersionConstraint parseRange(final String versionRange) throws Ver final JavaVersion minVersion; final JavaVersion maxVersion; - rangeBuffer = rangeBuffer.substring(1, rangeBuffer.length() - 1); + final String rangeBuffer = trimmedRange.substring(1, trimmedRange.length() - 1); final int index = rangeBuffer.indexOf(','); if (index < 0) { @@ -227,8 +270,7 @@ public static VersionConstraint parseRange(final String versionRange) throws Ver + "', single version must be surrounded by []"); } - final String version = rangeBuffer.trim(); - minVersion = JavaVersion.parse(version); + minVersion = JavaVersion.parse(rangeBuffer); maxVersion = minVersion; } else { final String minVersionStr = rangeBuffer.substring(0, index).trim(); @@ -243,17 +285,30 @@ public static VersionConstraint parseRange(final String versionRange) throws Ver minVersion = minVersionStr.isEmpty() ? null : JavaVersion.parse(minVersionStr); maxVersion = maxVersionStr.isEmpty() ? null : JavaVersion.parse(maxVersionStr); - if (maxVersion != null && minVersion != null) { - if (maxVersion.compareTo(minVersion) < 0) { - throw new VersionParsingException("Invalid version range '" + trimmedRange - + "', lower bound must not be greater than upper bound"); - } + if (maxVersion != null && minVersion != null && maxVersion.compareTo(minVersion) < 0) { + throw new VersionParsingException("Invalid version range '" + trimmedRange + + "', lower bound must not be greater than upper bound"); } } return new VersionConstraint(minVersion, maxVersion, minIncluded, maxIncluded); } + /** + * Creates a version constraint without throwing a checked exception. For use by the constant ranges (e.g. JAVA_9). + * + * @param versionRange Version range + * @return Version range object + * @see #parseRange(String) + */ + private static VersionConstraint safeParseRange(final String versionRange) { + try { + return parseRange(versionRange); + } catch (final VersionParsingException ex) { + throw new IllegalStateException(ex); + } + } + /** * Indicates whether the specified version corresponds to the specified Java language version. For example, *
@@ -269,7 +324,7 @@ public static VersionConstraint parseRange(final String versionRange) throws Ver
      */
     public static boolean isVersion(final VersionConstraint expectedVersion, final String version)
             throws VersionParsingException {
-        return isVersion(expectedVersion, parseVersion(version));
+        return isVersion(expectedVersion, JavaVersion.parse(version));
     }
 
     /**
diff --git a/src/main/java/org/cthing/versionparser/maven/MvnVersionScheme.java b/src/main/java/org/cthing/versionparser/maven/MvnVersionScheme.java
index 17df0d7..cf078d0 100644
--- a/src/main/java/org/cthing/versionparser/maven/MvnVersionScheme.java
+++ b/src/main/java/org/cthing/versionparser/maven/MvnVersionScheme.java
@@ -178,11 +178,9 @@ private static VersionRange parseRange(final String range) throws VersionParsing
             minVersion = minVersionStr.isEmpty() ? null : MvnVersion.parse(minVersionStr);
             maxVersion = maxVersionStr.isEmpty() ? null : MvnVersion.parse(maxVersionStr);
 
-            if (maxVersion != null && minVersion != null) {
-                if (maxVersion.compareTo(minVersion) < 0) {
-                    throw new VersionParsingException("Invalid version range '" + range
-                                                              + "', lower bound must not be greater than upper bound");
-                }
+            if (maxVersion != null && minVersion != null && maxVersion.compareTo(minVersion) < 0) {
+                throw new VersionParsingException("Invalid version range '" + range
+                                                          + "', lower bound must not be greater than upper bound");
             }
         }
 
diff --git a/src/test/java/org/cthing/versionparser/java/JavaVersionSchemeTest.java b/src/test/java/org/cthing/versionparser/java/JavaVersionSchemeTest.java
index c9915c1..cd33187 100644
--- a/src/test/java/org/cthing/versionparser/java/JavaVersionSchemeTest.java
+++ b/src/test/java/org/cthing/versionparser/java/JavaVersionSchemeTest.java
@@ -15,8 +15,10 @@
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.Arguments;
 import org.junit.jupiter.params.provider.MethodSource;
+import org.junit.jupiter.params.provider.ValueSource;
 
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
 import static org.junit.jupiter.params.provider.Arguments.arguments;
 
 
@@ -98,9 +100,9 @@ static Stream rangeProvider() {
 
     @ParameterizedTest
     @MethodSource("rangeProvider")
-    public void testParseConstraintRange(final String range, final String rangeRep, @Nullable final String minRep,
-                                         @Nullable final String maxRep, final boolean minIncluded,
-                                         final boolean maxIncluded) throws VersionParsingException {
+    public void testParseRange(final String range, final String rangeRep, @Nullable final String minRep,
+                               @Nullable final String maxRep, final boolean minIncluded,
+                               final boolean maxIncluded) throws VersionParsingException {
         final VersionConstraint constraint = JavaVersionScheme.parseRange(range);
         assertThat(constraint).hasToString(rangeRep);
         assertThat(constraint.isWeak()).isFalse();
@@ -119,6 +121,32 @@ public void testParseConstraintRange(final String range, final String rangeRep,
         assertThat(versionRange.isMaxIncluded()).isEqualTo(maxIncluded);
     }
 
+    @ParameterizedTest
+    @ValueSource(strings = {
+            "",
+            "[)",
+            "(]",
+            "()",
+            "[,",
+            "(,",
+            "[",
+            "(",
+            "[11",
+            "[11.0.7,",
+            "(11",
+            "(11,",
+            "(17)",
+            "[1.4,1.5,1.6)",
+            "[17,11]",
+            "[17,11)",
+            "(17,11]",
+            "(17,11)"
+    })
+    public void testParseRangeBad(final String range) {
+        assertThatExceptionOfType(VersionParsingException.class)
+                .isThrownBy(() -> JavaVersionScheme.parseRange(range));
+    }
+
     static Stream allowsProvider() {
         return Stream.of(
                 arguments("[1.4, 9)", "1.4", true),
@@ -184,6 +212,49 @@ public void testReleases(final VersionConstraint javaRelease, final String minRe
         assertThat(versionRange.isMaxIncluded()).isFalse();
     }
 
+    static Stream releasesPlusProvider() {
+        return Stream.of(
+                arguments(JavaVersionScheme.JAVA_1_2_PLUS, "1.2"),
+                arguments(JavaVersionScheme.JAVA_1_3_PLUS, "1.3"),
+                arguments(JavaVersionScheme.JAVA_1_4_PLUS, "1.4"),
+                arguments(JavaVersionScheme.JAVA_1_5_PLUS, "1.5"),
+                arguments(JavaVersionScheme.JAVA_1_6_PLUS, "1.6"),
+                arguments(JavaVersionScheme.JAVA_1_7_PLUS, "1.7"),
+                arguments(JavaVersionScheme.JAVA_1_8_PLUS, "1.8"),
+                arguments(JavaVersionScheme.JAVA_9_PLUS,   "9"),
+                arguments(JavaVersionScheme.JAVA_10_PLUS,  "10"),
+                arguments(JavaVersionScheme.JAVA_11_PLUS,  "11"),
+                arguments(JavaVersionScheme.JAVA_12_PLUS,  "12"),
+                arguments(JavaVersionScheme.JAVA_13_PLUS,  "13"),
+                arguments(JavaVersionScheme.JAVA_14_PLUS,  "14"),
+                arguments(JavaVersionScheme.JAVA_15_PLUS,  "15"),
+                arguments(JavaVersionScheme.JAVA_16_PLUS,  "16"),
+                arguments(JavaVersionScheme.JAVA_17_PLUS,  "17"),
+                arguments(JavaVersionScheme.JAVA_18_PLUS,  "18"),
+                arguments(JavaVersionScheme.JAVA_19_PLUS,  "19"),
+                arguments(JavaVersionScheme.JAVA_20_PLUS,  "20"),
+                arguments(JavaVersionScheme.JAVA_21_PLUS,  "21"),
+                arguments(JavaVersionScheme.JAVA_22_PLUS,  "22"),
+                arguments(JavaVersionScheme.JAVA_23_PLUS,  "23"),
+                arguments(JavaVersionScheme.JAVA_24_PLUS,  "24"),
+                arguments(JavaVersionScheme.JAVA_25_PLUS,  "25"),
+                arguments(JavaVersionScheme.JAVA_26_PLUS,  "26"),
+                arguments(JavaVersionScheme.JAVA_27_PLUS,  "27")
+        );
+    }
+
+    @ParameterizedTest
+    @MethodSource("releasesPlusProvider")
+    public void testReleasesPlus(final VersionConstraint javaRelease, final String minRep) {
+        assertThat(javaRelease).hasToString("[" + minRep + ",)");
+        assertThat(javaRelease.isWeak()).isFalse();
+        final VersionRange versionRange = javaRelease.getRanges().get(0);
+        assertThat(versionRange.getMinVersion()).hasToString(minRep);
+        assertThat(versionRange.getMaxVersion()).isNull();
+        assertThat(versionRange.isMinIncluded()).isTrue();
+        assertThat(versionRange.isMaxIncluded()).isFalse();
+    }
+
     static Stream javaProvider() {
         return Stream.of(
                 arguments(JavaVersionScheme.JAVA_1_4, "1.4", true),