diff --git a/maven-artifact/src/main/java/org/apache/maven/artifact/versioning/ComparableVersion.java b/maven-artifact/src/main/java/org/apache/maven/artifact/versioning/ComparableVersion.java index 17aa8cc7a703..bf29c2102ab3 100644 --- a/maven-artifact/src/main/java/org/apache/maven/artifact/versioning/ComparableVersion.java +++ b/maven-artifact/src/main/java/org/apache/maven/artifact/versioning/ComparableVersion.java @@ -41,25 +41,28 @@ * 1.0alpha1 => [1, 0, alpha, 1] *
  • unlimited number of version components,
  • *
  • version components in the text can be digits or strings,
  • - *
  • strings are checked for well-known qualifiers and the qualifier ordering is used for version ordering. - * Well-known qualifiers (case insensitive) are: - * Unknown qualifiers are considered after known qualifiers, with lexical order (always case insensitive), - *
  • + *
  • + * Following semver rules is encouraged, and some qualifiers are discouraged (no matter the case): + * + * String qualifiers are ordered lexically (case insensitive), with the following exceptions: + * + *
  • *
  • a hyphen usually precedes a qualifier, and is always less important than digits/number, for example * {@code 1.0.RC2 < 1.0-RC3 < 1.0.1}; but prefer {@code 1.0.0-RC1} over {@code 1.0.0.RC1}, and more * generally: {@code 1.0.X2 < 1.0-X3 < 1.0.1} for any string {@code X}; but prefer {@code 1.0.0-X1} * over {@code 1.0.0.X1}.
  • * * - * @see "Versioning" on Maven Wiki + * @see Version Order Specification * @author Kenney Westerhof * @author HervĂ© Boutemy */ @@ -135,7 +138,7 @@ public int compareTo( Item item ) { case INT_ITEM: int itemValue = ( (IntItem) item ).value; - return ( value < itemValue ) ? -1 : ( ( value == itemValue ) ? 0 : 1 ); + return Integer.compare( value, itemValue ); case LONG_ITEM: case BIGINTEGER_ITEM: return -1; @@ -221,7 +224,7 @@ public int compareTo( Item item ) return 1; case LONG_ITEM: long itemValue = ( (LongItem) item ).value; - return ( value < itemValue ) ? -1 : ( ( value == itemValue ) ? 0 : 1 ); + return Long.compare( value, itemValue ); case BIGINTEGER_ITEM: return -1; @@ -356,23 +359,23 @@ public String toString() private static class StringItem implements Item { - private static final List QUALIFIERS = - Arrays.asList( "alpha", "beta", "milestone", "rc", "snapshot", "", "sp" ); + private static final List QUALIFIERS = Arrays.asList( "snapshot", "", "sp" ); private static final Properties ALIASES = new Properties(); + static { - ALIASES.put( "ga", "" ); + ALIASES.put( "cr", "rc" ); ALIASES.put( "final", "" ); + ALIASES.put( "ga", "" ); ALIASES.put( "release", "" ); - ALIASES.put( "cr", "rc" ); } /** - * A comparable value for the empty-string qualifier. This one is used to determine if a given qualifier makes + * An index value for the empty-string qualifier. This one is used to determine if a given qualifier makes * the version older than one without a qualifier, or more recent. */ - private static final String RELEASE_VERSION_INDEX = String.valueOf( QUALIFIERS.indexOf( "" ) ); + private static final int RELEASE_VERSION_INDEX = QUALIFIERS.indexOf( "" ); private final String value; @@ -407,7 +410,7 @@ public int getType() @Override public boolean isNull() { - return ( comparableQualifier( value ).compareTo( RELEASE_VERSION_INDEX ) == 0 ); + return QUALIFIERS.indexOf( value ) == RELEASE_VERSION_INDEX; } /** @@ -422,12 +425,37 @@ public boolean isNull() * * @param qualifier * @return an equivalent value that can be used with lexical comparison + * @deprecated Use {@link #compareQualifiers(String, String)} instead */ + @Deprecated public static String comparableQualifier( String qualifier ) { - int i = QUALIFIERS.indexOf( qualifier ); + int index = QUALIFIERS.indexOf( qualifier ) + 1; + + return index == 0 ? ( "0-" + qualifier ) : String.valueOf( index ); + } + + /** + * Compare the qualifiers of two artifact versions. + * + * @param qualifier1 qualifier of first artifact + * @param qualifier2 qualifier of second artifact + * @return a negative integer, zero, or a positive integer as the first argument is less than, equal to, or + * greater than the second + */ + public static int compareQualifiers( String qualifier1, String qualifier2 ) + { + int i1 = QUALIFIERS.indexOf( qualifier1 ); + int i2 = QUALIFIERS.indexOf( qualifier2 ); + + // if both pre-release, then use natural lexical ordering + if ( i1 == -1 && i2 == -1 ) + { + return qualifier1.compareTo( qualifier2 ); + } - return i == -1 ? ( QUALIFIERS.size() + "-" + qualifier ) : String.valueOf( i ); + // 'other qualifier' < 'snapshot' < '' < 'sp' + return Integer.compare( i1, i2 ); } @Override @@ -436,7 +464,7 @@ public int compareTo( Item item ) if ( item == null ) { // 1-rc < 1, 1-ga > 1 - return comparableQualifier( value ).compareTo( RELEASE_VERSION_INDEX ); + return Integer.compare( QUALIFIERS.indexOf( value ), RELEASE_VERSION_INDEX ); } switch ( item.getType() ) { @@ -446,7 +474,7 @@ public int compareTo( Item item ) return -1; // 1.any < 1.1 ? case STRING_ITEM: - return comparableQualifier( value ).compareTo( comparableQualifier( ( (StringItem) item ).value ) ); + return compareQualifiers( value, ( ( StringItem ) item ).value ); case LIST_ITEM: return -1; // 1.any < 1-1 diff --git a/maven-artifact/src/test/java/org/apache/maven/artifact/versioning/ComparableVersionTest.java b/maven-artifact/src/test/java/org/apache/maven/artifact/versioning/ComparableVersionTest.java index 832ab179b83f..67e6d43b6596 100644 --- a/maven-artifact/src/test/java/org/apache/maven/artifact/versioning/ComparableVersionTest.java +++ b/maven-artifact/src/test/java/org/apache/maven/artifact/versioning/ComparableVersionTest.java @@ -46,13 +46,13 @@ private Comparable newComparable( String version ) } private static final String[] VERSIONS_QUALIFIER = - { "1-alpha2snapshot", "1-alpha2", "1-alpha-123", "1-beta-2", "1-beta123", "1-m2", "1-m11", "1-rc", "1-cr2", - "1-rc123", "1-SNAPSHOT", "1", "1-sp", "1-sp2", "1-sp123", "1-abc", "1-def", "1-pom-1", "1-1-snapshot", + { "1-abc", "1-alpha2snapshot", "1-alpha2", "1-alpha-123", "1-beta-2", "1-beta123", "1-def", "1-m2", "1-m11", + "1-pom-1", "1-rc", "1-cr2", "1-rc123", "1-SNAPSHOT", "1", "1-sp", "1-sp2", "1-sp123", "1-1-snapshot", "1-1", "1-2", "1-123" }; private static final String[] VERSIONS_NUMBER = - { "2.0", "2.0.a", "2-1", "2.0.2", "2.0.123", "2.1.0", "2.1-a", "2.1b", "2.1-c", "2.1-1", "2.1.0.1", "2.2", - "2.123", "11.a2", "11.a11", "11.b2", "11.b11", "11.m2", "11.m11", "11", "11.a", "11b", "11c", "11m" }; + { "2.0.a", "2.0", "2-1", "2.0.2", "2.0.123", "2.1-a", "2.1b", "2.1-c", "2.1.0", "2.1-1", "2.1.0.1", "2.2", + "2.123", "11.a", "11.a2", "11.a11", "11b", "11.b2", "11.b11", "11c", "11m", "11.m2", "11.m11", "11" }; private void checkVersionsOrder( String[] versions ) { @@ -189,7 +189,7 @@ public void testVersionComparing() checkVersionsOrder( "2.0-1", "2.0.1" ); checkVersionsOrder( "2.0.1-klm", "2.0.1-lmn" ); - checkVersionsOrder( "2.0.1", "2.0.1-xyz" ); + checkVersionsOrder( "2.0.1-xyz", "2.0.1" ); // now 2.0.1-xyz < 2.0.1 as of MNG-7559 checkVersionsOrder( "2.0.1", "2.0.1-123" ); checkVersionsOrder( "2.0.1-xyz", "2.0.1-123" ); @@ -204,13 +204,9 @@ public void testVersionComparing() */ public void testMng5568() { - String a = "6.1.0"; - String b = "6.1.0rc3"; - String c = "6.1H.5-beta"; // this is the unusual version string, with 'H' in the middle - - checkVersionsOrder( b, a ); // classical - checkVersionsOrder( b, c ); // now b < c, but before MNG-5568, we had b > c - checkVersionsOrder( a, c ); + checkVersionsOrder( "6.1H.5-beta", "6.1.0rc3" ); // now H < RC as of MNG-7559 + checkVersionsOrder( "6.1.0rc3", "6.1.0" ); // classical + checkVersionsOrder( "6.1H.5-beta", "6.1.0" ); // transitivity } /** @@ -338,6 +334,24 @@ public void testReuse() assertEquals( "reused instance should be equivalent to new instance", c1, c2 ); } + /** + * Test MNG-7559 edge cases + * -pfd < final, ga, release + * 2.0.1.MR < 2.0.1 + * 9.4.1.jre16 > 9.4.1.jre16-preview + */ + public void testMng7559() + { + // checking general cases + checkVersionsOrder( + new String[]{ "ab", "alpha", "beta", "cd", "ea", "milestone", "mr", "pfd", "preview", "RC" } ); + // checking identified issues respect the general case + checkVersionsOrder( "2.3-pfd", "2.3" ); + checkVersionsOrder( "2.0.1.MR", "2.0.1" ); + checkVersionsOrder( "9.4.1.jre16-preview", "9.4.1.jre16" ); + checkVersionsOrder( "1-sp-1", "1-ga-1" ); // proving website documentation right. + } + /** * Test MNG-7644 edge cases * 1.0.0.RC1 < 1.0.0-RC2 and more generally: @@ -345,7 +359,8 @@ public void testReuse() */ public void testMng7644() { - for ( String x : new String[]{ "abc", "alpha", "a", "beta", "b", "def", "milestone", "m", "RC" } ) { + for ( String x : new String[]{ "abc", "alpha", "a", "beta", "b", "def", "milestone", "m", "RC" } ) + { // 1.0.0.X1 < 1.0.0-X2 for any string x checkVersionsOrder( "1.0.0." + x + "1", "1.0.0-" + x + "2" ); // 2.0.X == 2-X == 2.0.0.X for any string x diff --git a/maven-artifact/src/test/java/org/apache/maven/artifact/versioning/DefaultArtifactVersionTest.java b/maven-artifact/src/test/java/org/apache/maven/artifact/versioning/DefaultArtifactVersionTest.java index fc5a2742edf3..01e473693f9b 100644 --- a/maven-artifact/src/test/java/org/apache/maven/artifact/versioning/DefaultArtifactVersionTest.java +++ b/maven-artifact/src/test/java/org/apache/maven/artifact/versioning/DefaultArtifactVersionTest.java @@ -121,7 +121,7 @@ public void testVersionComparing() assertVersionOlder( "2.0-1", "2.0.1" ); assertVersionOlder( "2.0.1-klm", "2.0.1-lmn" ); - assertVersionOlder( "2.0.1", "2.0.1-xyz" ); + assertVersionOlder( "2.0.1-xyz", "2.0.1" ); // now 2.0.1-xyz < 2.0.1 as of MNG-7559 assertVersionOlder( "2.0.1-xyz-1", "2.0.1-1-xyz" ); assertVersionOlder( "2.0.1", "2.0.1-123" );