mirror of https://github.com/apache/maven.git
fix version comparison (#845)
This commit is contained in:
parent
69ef6a61ba
commit
97e8bf6711
|
@ -23,10 +23,11 @@ import java.util.ArrayDeque;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Deque;
|
import java.util.Deque;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Properties;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
|
@ -40,22 +41,39 @@ import java.util.Properties;
|
||||||
* <code>1.0alpha1 => [1, 0, alpha, 1]</code></li>
|
* <code>1.0alpha1 => [1, 0, alpha, 1]</code></li>
|
||||||
* <li>unlimited number of version components,</li>
|
* <li>unlimited number of version components,</li>
|
||||||
* <li>version components in the text can be digits or strings,</li>
|
* <li>version components in the text can be digits or strings,</li>
|
||||||
* <li>strings are checked for well-known qualifiers and the qualifier ordering is used for version ordering.
|
* <li>
|
||||||
* Well-known qualifiers (case insensitive) are:<ul>
|
* String qualifiers are ordered lexically (case insensitive), with the following exceptions:
|
||||||
* <li><code>alpha</code> or <code>a</code></li>
|
* <ul>
|
||||||
* <li><code>beta</code> or <code>b</code></li>
|
* <li> 'snapshot' < '' < 'sp' </li>
|
||||||
* <li><code>milestone</code> or <code>m</code></li>
|
* </ul>
|
||||||
* <li><code>rc</code> or <code>cr</code></li>
|
* and alias -> replacement (all case insensitive):
|
||||||
* <li><code>snapshot</code></li>
|
* <ul>
|
||||||
* <li><code>(the empty string)</code> or <code>ga</code> or <code>final</code></li>
|
* <li> 'a' -> 'alpha' </li>
|
||||||
* <li><code>sp</code></li>
|
* <li> 'b' -> 'beta' </li>
|
||||||
* </ul>
|
* <li> 'm' -> 'milestone' </li>
|
||||||
* Unknown qualifiers are considered after known qualifiers, with lexical order (always case insensitive),
|
* <li> 'cr' -> 'rc' </li>
|
||||||
* </li>
|
* <li> 'final' -> '' </li>
|
||||||
* <li>a hyphen usually precedes a qualifier, and is always less important than something preceded with a dot.</li>
|
* <li> 'final' -> '' </li>
|
||||||
|
* <li> 'final' -> '' </li>
|
||||||
|
* </ul>
|
||||||
|
* </li>
|
||||||
|
* <li>
|
||||||
|
* Following semver rules is encouraged, and some qualifiers are discouraged (no matter the case):
|
||||||
|
* <ul>
|
||||||
|
* <li> The usage of 'CR' qualifier is discouraged. Use 'RC' instead. </li>
|
||||||
|
* <li> The usage of 'final', 'ga', and 'release' qualifiers is discouraged. Use no qualifier instead. </li>
|
||||||
|
* <li> The usage of 'SP' qualifier is discouraged. Increment the patch version instead. </li>
|
||||||
|
* </ul>
|
||||||
|
* For other qualifiers, natural ordering is used (case insensitive):
|
||||||
|
* <ul>
|
||||||
|
* <li> alpha = a < beta = b < milestone = m < rc = cr < snapshot < '' = final = ga = release < sp </li>
|
||||||
|
* </ul>
|
||||||
|
* </li>
|
||||||
|
* <li>a hyphen usually precedes a qualifier, and is always less important than digits/number, for example
|
||||||
|
* 1.0.RC2 < 1.0-RC3 < 1.0.1 ; but prefer '1.0.0-RC1' over '1.0.0.RC1' </li>
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* @see <a href="https://cwiki.apache.org/confluence/display/MAVENOLD/Versioning">"Versioning" on Maven Wiki</a>
|
* @see <a href="https://maven.apache.org/pom.html#Version_Order_Specification">Version Order Specification</a>
|
||||||
* @author <a href="mailto:kenney@apache.org">Kenney Westerhof</a>
|
* @author <a href="mailto:kenney@apache.org">Kenney Westerhof</a>
|
||||||
* @author <a href="mailto:hboutemy@apache.org">Hervé Boutemy</a>
|
* @author <a href="mailto:hboutemy@apache.org">Hervé Boutemy</a>
|
||||||
*/
|
*/
|
||||||
|
@ -304,23 +322,22 @@ public class ComparableVersion implements Comparable<ComparableVersion> {
|
||||||
* Represents a string in the version item list, usually a qualifier.
|
* Represents a string in the version item list, usually a qualifier.
|
||||||
*/
|
*/
|
||||||
private static class StringItem implements Item {
|
private static class StringItem implements Item {
|
||||||
private static final List<String> QUALIFIERS =
|
private static final List<String> QUALIFIERS = Arrays.asList("snapshot", "", "sp");
|
||||||
Arrays.asList("alpha", "beta", "milestone", "rc", "snapshot", "", "sp");
|
|
||||||
|
|
||||||
private static final Properties ALIASES = new Properties();
|
private static final Map<String, String> ALIASES = new HashMap<>(4);
|
||||||
|
|
||||||
static {
|
static {
|
||||||
ALIASES.put("ga", "");
|
|
||||||
ALIASES.put("final", "");
|
|
||||||
ALIASES.put("release", "");
|
|
||||||
ALIASES.put("cr", "rc");
|
ALIASES.put("cr", "rc");
|
||||||
|
ALIASES.put("final", "");
|
||||||
|
ALIASES.put("ga", "");
|
||||||
|
ALIASES.put("release", "");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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.
|
* 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;
|
private final String value;
|
||||||
|
|
||||||
|
@ -340,7 +357,7 @@ public class ComparableVersion implements Comparable<ComparableVersion> {
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.value = ALIASES.getProperty(value, value);
|
this.value = ALIASES.getOrDefault(value, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -350,7 +367,7 @@ public class ComparableVersion implements Comparable<ComparableVersion> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isNull() {
|
public boolean isNull() {
|
||||||
return (comparableQualifier(value).compareTo(RELEASE_VERSION_INDEX) == 0);
|
return QUALIFIERS.indexOf(value) == RELEASE_VERSION_INDEX;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -365,18 +382,42 @@ public class ComparableVersion implements Comparable<ComparableVersion> {
|
||||||
*
|
*
|
||||||
* @param qualifier
|
* @param qualifier
|
||||||
* @return an equivalent value that can be used with lexical comparison
|
* @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) {
|
public static String comparableQualifier(String qualifier) {
|
||||||
int i = QUALIFIERS.indexOf(qualifier);
|
int index = QUALIFIERS.indexOf(qualifier) + 1;
|
||||||
|
|
||||||
return i == -1 ? (QUALIFIERS.size() + "-" + qualifier) : String.valueOf(i);
|
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) {
|
||||||
|
// alpha < beta < ea < milestone < preview < rc
|
||||||
|
return qualifier1.compareTo(qualifier2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 'other qualifier' < 'snapshot' < '' < 'sp'
|
||||||
|
return Integer.compare(i1, i2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int compareTo(Item item) {
|
public int compareTo(Item item) {
|
||||||
if (item == null) {
|
if (item == null) {
|
||||||
// 1-rc < 1, 1-ga > 1
|
// 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()) {
|
switch (item.getType()) {
|
||||||
case INT_ITEM:
|
case INT_ITEM:
|
||||||
|
@ -385,7 +426,7 @@ public class ComparableVersion implements Comparable<ComparableVersion> {
|
||||||
return -1; // 1.any < 1.1 ?
|
return -1; // 1.any < 1.1 ?
|
||||||
|
|
||||||
case STRING_ITEM:
|
case STRING_ITEM:
|
||||||
return comparableQualifier(value).compareTo(comparableQualifier(((StringItem) item).value));
|
return compareQualifiers(value, ((StringItem) item).value);
|
||||||
|
|
||||||
case LIST_ITEM:
|
case LIST_ITEM:
|
||||||
return -1; // 1.any < 1-1
|
return -1; // 1.any < 1-1
|
||||||
|
@ -570,6 +611,13 @@ public class ComparableVersion implements Comparable<ComparableVersion> {
|
||||||
stack.push(list);
|
stack.push(list);
|
||||||
} else if (Character.isDigit(c)) {
|
} else if (Character.isDigit(c)) {
|
||||||
if (!isDigit && i > startIndex) {
|
if (!isDigit && i > startIndex) {
|
||||||
|
// 1.0.0.RC1 < 1.0.0-RC2
|
||||||
|
// treat .RC as -RC
|
||||||
|
if (!list.isEmpty()) {
|
||||||
|
list.add(list = new ListItem());
|
||||||
|
stack.push(list);
|
||||||
|
}
|
||||||
|
|
||||||
list.add(new StringItem(version.substring(startIndex, i), true));
|
list.add(new StringItem(version.substring(startIndex, i), true));
|
||||||
startIndex = i;
|
startIndex = i;
|
||||||
|
|
||||||
|
@ -592,6 +640,13 @@ public class ComparableVersion implements Comparable<ComparableVersion> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (version.length() > startIndex) {
|
if (version.length() > startIndex) {
|
||||||
|
// 1.0.0.RC1 < 1.0.0-RC2
|
||||||
|
// treat .RC as -RC
|
||||||
|
if (!isDigit && !list.isEmpty()) {
|
||||||
|
list.add(list = new ListItem());
|
||||||
|
stack.push(list);
|
||||||
|
}
|
||||||
|
|
||||||
list.add(parseItem(isDigit, version.substring(startIndex)));
|
list.add(parseItem(isDigit, version.substring(startIndex)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -46,13 +46,16 @@ public class ComparableVersionTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final String[] VERSIONS_QUALIFIER = {
|
private static final String[] VERSIONS_QUALIFIER = {
|
||||||
|
"1-abc",
|
||||||
"1-alpha2snapshot",
|
"1-alpha2snapshot",
|
||||||
"1-alpha2",
|
"1-alpha2",
|
||||||
"1-alpha-123",
|
"1-alpha-123",
|
||||||
"1-beta-2",
|
"1-beta-2",
|
||||||
"1-beta123",
|
"1-beta123",
|
||||||
|
"1-def",
|
||||||
"1-m2",
|
"1-m2",
|
||||||
"1-m11",
|
"1-m11",
|
||||||
|
"1-pom-1",
|
||||||
"1-rc",
|
"1-rc",
|
||||||
"1-cr2",
|
"1-cr2",
|
||||||
"1-rc123",
|
"1-rc123",
|
||||||
|
@ -61,9 +64,6 @@ public class ComparableVersionTest {
|
||||||
"1-sp",
|
"1-sp",
|
||||||
"1-sp2",
|
"1-sp2",
|
||||||
"1-sp123",
|
"1-sp123",
|
||||||
"1-abc",
|
|
||||||
"1-def",
|
|
||||||
"1-pom-1",
|
|
||||||
"1-1-snapshot",
|
"1-1-snapshot",
|
||||||
"1-1",
|
"1-1",
|
||||||
"1-2",
|
"1-2",
|
||||||
|
@ -71,8 +71,8 @@ public class ComparableVersionTest {
|
||||||
};
|
};
|
||||||
|
|
||||||
private static final String[] VERSIONS_NUMBER = {
|
private static final String[] VERSIONS_NUMBER = {
|
||||||
"2.0", "2-1", "2.0.a", "2.0.0.a", "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.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.2", "2.123", "11.a2", "11.a11", "11.b2", "11.b11", "11.m2", "11.m11", "11", "11.a", "11b", "11c", "11m"
|
"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) {
|
private void checkVersionsOrder(String[] versions) {
|
||||||
|
@ -202,7 +202,7 @@ public class ComparableVersionTest {
|
||||||
|
|
||||||
checkVersionsOrder("2.0-1", "2.0.1");
|
checkVersionsOrder("2.0-1", "2.0.1");
|
||||||
checkVersionsOrder("2.0.1-klm", "2.0.1-lmn");
|
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", "2.0.1-123");
|
||||||
checkVersionsOrder("2.0.1-xyz", "2.0.1-123");
|
checkVersionsOrder("2.0.1-xyz", "2.0.1-123");
|
||||||
|
@ -217,13 +217,9 @@ public class ComparableVersionTest {
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
public void testMng5568() {
|
public void testMng5568() {
|
||||||
String a = "6.1.0";
|
checkVersionsOrder("6.1H.5-beta", "6.1.0rc3"); // now H < RC as of MNG-7559
|
||||||
String b = "6.1.0rc3";
|
checkVersionsOrder("6.1.0rc3", "6.1.0"); // classical
|
||||||
String c = "6.1H.5-beta"; // this is the unusual version string, with 'H' in the middle
|
checkVersionsOrder("6.1H.5-beta", "6.1.0"); // transitivity
|
||||||
|
|
||||||
checkVersionsOrder(b, a); // classical
|
|
||||||
checkVersionsOrder(b, c); // now b < c, but before MNG-5568, we had b > c
|
|
||||||
checkVersionsOrder(a, c);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -346,4 +342,22 @@ public class ComparableVersionTest {
|
||||||
|
|
||||||
assertEquals(c1, c2, "reused instance should be equivalent to new instance");
|
assertEquals(c1, c2, "reused instance should be equivalent to new instance");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test <a href="https://issues.apache.org/jira/browse/MNG-7559">MNG-7559</a> edge cases
|
||||||
|
* 1.0.0.RC1 < 1.0.0-RC2
|
||||||
|
* -pfd < final, ga, release
|
||||||
|
* 2.0.1.MR < 2.0.1
|
||||||
|
* 9.4.1.jre16 > 9.4.1.jre16-preview
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testMng7559() {
|
||||||
|
checkVersionsOrder("1.0.0.RC1", "1.0.0-RC2");
|
||||||
|
checkVersionsOrder("4.0.0.Beta3", "4.0.0-RC2");
|
||||||
|
checkVersionsOrder("2.3-pfd", "2.3");
|
||||||
|
checkVersionsOrder("2.0.1.MR", "2.0.1");
|
||||||
|
checkVersionsOrder("9.4.1.jre16-preview", "9.4.1.jre16");
|
||||||
|
checkVersionsEqual("2.0.a", "2.0.0.a"); // previously ordered, now equals
|
||||||
|
checkVersionsOrder("1-sp-1", "1-ga-1"); // proving website documentation right.
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -119,7 +119,7 @@ public class DefaultArtifactVersionTest {
|
||||||
assertVersionOlder("2.0-1", "2.0.1");
|
assertVersionOlder("2.0-1", "2.0.1");
|
||||||
|
|
||||||
assertVersionOlder("2.0.1-klm", "2.0.1-lmn");
|
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-xyz-1", "2.0.1-1-xyz");
|
||||||
|
|
||||||
assertVersionOlder("2.0.1", "2.0.1-123");
|
assertVersionOlder("2.0.1", "2.0.1-123");
|
||||||
|
|
Loading…
Reference in New Issue