2323import java .util .ArrayList ;
2424import java .util .Arrays ;
2525import java .util .Deque ;
26+ import java .util .HashMap ;
2627import java .util .Iterator ;
2728import java .util .List ;
2829import java .util .Locale ;
29- import java .util .Properties ;
30+ import java .util .Map ;
3031
3132/**
3233 * <p>
4041 * <code>1.0alpha1 => [1, 0, alpha, 1]</code></li>
4142 * <li>unlimited number of version components,</li>
4243 * <li>version components in the text can be digits or strings,</li>
43- * <li>strings are checked for well-known qualifiers and the qualifier ordering is used for version ordering.
44- * Well-known qualifiers (case insensitive) are:<ul>
45- * <li><code>alpha</code> or <code>a</code></li>
46- * <li><code>beta</code> or <code>b</code></li>
47- * <li><code>milestone</code> or <code>m</code></li>
48- * <li><code>rc</code> or <code>cr</code></li>
49- * <li><code>snapshot</code></li>
50- * <li><code>(the empty string)</code> or <code>ga</code> or <code>final</code></li>
51- * <li><code>sp</code></li>
52- * </ul>
53- * Unknown qualifiers are considered after known qualifiers, with lexical order (always case insensitive),
54- * </li>
55- * <li>a hyphen usually precedes a qualifier, and is always less important than something preceded with a dot.</li>
44+ * <li>
45+ * String qualifiers are ordered lexically (case insensitive), with the following exceptions:
46+ * <ul>
47+ * <li> 'snapshot' < '' < 'sp' </li>
48+ * </ul>
49+ * and alias -> replacement (all case insensitive):
50+ * <ul>
51+ * <li> 'a' -> 'alpha' </li>
52+ * <li> 'b' -> 'beta' </li>
53+ * <li> 'm' -> 'milestone' </li>
54+ * <li> 'cr' -> 'rc' </li>
55+ * <li> 'final' -> '' </li>
56+ * <li> 'final' -> '' </li>
57+ * <li> 'final' -> '' </li>
58+ * </ul>
59+ * </li>
60+ * <li>
61+ * Following semver rules is encouraged, and some qualifiers are discouraged (no matter the case):
62+ * <ul>
63+ * <li> The usage of 'CR' qualifier is discouraged. Use 'RC' instead. </li>
64+ * <li> The usage of 'final', 'ga', and 'release' qualifiers is discouraged. Use no qualifier instead. </li>
65+ * <li> The usage of 'SP' qualifier is discouraged. Increment the patch version instead. </li>
66+ * </ul>
67+ * For other qualifiers, natural ordering is used (case insensitive):
68+ * <ul>
69+ * <li> alpha = a < beta = b < milestone = m < rc = cr < snapshot < '' = final = ga = release < sp </li>
70+ * </ul>
71+ * </li>
72+ * <li>a hyphen usually precedes a qualifier, and is always less important than digits/number, for example
73+ * 1.0.RC2 < 1.0-RC3 < 1.0.1 ; but prefer '1.0.0-RC1' over '1.0.0.RC1' </li>
5674 * </ul>
5775 *
58- * @see <a href="https://cwiki .apache.org/confluence/display/MAVENOLD/Versioning">"Versioning" on Maven Wiki </a>
76+ * @see <a href="https://maven .apache.org/pom.html#Version_Order_Specification">Version Order Specification </a>
5977 * @author <a href="mailto:kenney@apache.org">Kenney Westerhof</a>
6078 * @author <a href="mailto:hboutemy@apache.org">Hervé Boutemy</a>
6179 */
@@ -119,7 +137,7 @@ public int compareTo(Item item) {
119137 switch (item .getType ()) {
120138 case INT_ITEM :
121139 int itemValue = ((IntItem ) item ).value ;
122- return (value < itemValue ) ? - 1 : (( value == itemValue ) ? 0 : 1 );
140+ return Integer . compare (value , itemValue );
123141 case LONG_ITEM :
124142 case BIGINTEGER_ITEM :
125143 return -1 ;
@@ -191,7 +209,7 @@ public int compareTo(Item item) {
191209 return 1 ;
192210 case LONG_ITEM :
193211 long itemValue = ((LongItem ) item ).value ;
194- return (value < itemValue ) ? - 1 : (( value == itemValue ) ? 0 : 1 );
212+ return Long . compare (value , itemValue );
195213 case BIGINTEGER_ITEM :
196214 return -1 ;
197215
@@ -304,23 +322,22 @@ public String toString() {
304322 * Represents a string in the version item list, usually a qualifier.
305323 */
306324 private static class StringItem implements Item {
307- private static final List <String > QUALIFIERS =
308- Arrays .asList ("alpha" , "beta" , "milestone" , "rc" , "snapshot" , "" , "sp" );
325+ private static final List <String > QUALIFIERS = Arrays .asList ("snapshot" , "" , "sp" );
309326
310- private static final Properties ALIASES = new Properties ( );
327+ private static final Map < String , String > ALIASES = new HashMap <>( 4 );
311328
312329 static {
313- ALIASES .put ("ga " , "" );
330+ ALIASES .put ("cr " , "rc " );
314331 ALIASES .put ("final" , "" );
332+ ALIASES .put ("ga" , "" );
315333 ALIASES .put ("release" , "" );
316- ALIASES .put ("cr" , "rc" );
317334 }
318335
319336 /**
320- * A comparable value for the empty-string qualifier. This one is used to determine if a given qualifier makes
337+ * An index value for the empty-string qualifier. This one is used to determine if a given qualifier makes
321338 * the version older than one without a qualifier, or more recent.
322339 */
323- private static final String RELEASE_VERSION_INDEX = String . valueOf ( QUALIFIERS .indexOf ("" ) );
340+ private static final int RELEASE_VERSION_INDEX = QUALIFIERS .indexOf ("" );
324341
325342 private final String value ;
326343
@@ -340,7 +357,7 @@ private static class StringItem implements Item {
340357 default :
341358 }
342359 }
343- this .value = ALIASES .getProperty (value , value );
360+ this .value = ALIASES .getOrDefault (value , value );
344361 }
345362
346363 @ Override
@@ -350,7 +367,7 @@ public int getType() {
350367
351368 @ Override
352369 public boolean isNull () {
353- return ( comparableQualifier ( value ). compareTo ( RELEASE_VERSION_INDEX ) == 0 ) ;
370+ return QUALIFIERS . indexOf ( value ) == RELEASE_VERSION_INDEX ;
354371 }
355372
356373 /**
@@ -365,18 +382,42 @@ public boolean isNull() {
365382 *
366383 * @param qualifier
367384 * @return an equivalent value that can be used with lexical comparison
385+ * @deprecated Use {@link #compareQualifiers(String, String)} instead
368386 */
387+ @ Deprecated
369388 public static String comparableQualifier (String qualifier ) {
370- int i = QUALIFIERS .indexOf (qualifier );
389+ int index = QUALIFIERS .indexOf (qualifier ) + 1 ;
371390
372- return i == -1 ? (QUALIFIERS .size () + "-" + qualifier ) : String .valueOf (i );
391+ return index == 0 ? ("0-" + qualifier ) : String .valueOf (index );
392+ }
393+
394+ /**
395+ * Compare the qualifiers of two artifact versions.
396+ *
397+ * @param qualifier1 qualifier of first artifact
398+ * @param qualifier2 qualifier of second artifact
399+ * @return a negative integer, zero, or a positive integer as the first argument is less than, equal to, or
400+ * greater than the second
401+ */
402+ public static int compareQualifiers (String qualifier1 , String qualifier2 ) {
403+ int i1 = QUALIFIERS .indexOf (qualifier1 );
404+ int i2 = QUALIFIERS .indexOf (qualifier2 );
405+
406+ // if both pre-release, then use natural lexical ordering
407+ if (i1 == -1 && i2 == -1 ) {
408+ // alpha < beta < ea < milestone < preview < rc
409+ return qualifier1 .compareTo (qualifier2 );
410+ }
411+
412+ // 'other qualifier' < 'snapshot' < '' < 'sp'
413+ return Integer .compare (i1 , i2 );
373414 }
374415
375416 @ Override
376417 public int compareTo (Item item ) {
377418 if (item == null ) {
378419 // 1-rc < 1, 1-ga > 1
379- return comparableQualifier ( value ). compareTo ( RELEASE_VERSION_INDEX );
420+ return Integer . compare ( QUALIFIERS . indexOf ( value ), RELEASE_VERSION_INDEX );
380421 }
381422 switch (item .getType ()) {
382423 case INT_ITEM :
@@ -385,7 +426,7 @@ public int compareTo(Item item) {
385426 return -1 ; // 1.any < 1.1 ?
386427
387428 case STRING_ITEM :
388- return comparableQualifier (value ). compareTo ( comparableQualifier ((( StringItem ) item ).value ) );
429+ return compareQualifiers (value , (( StringItem ) item ).value );
389430
390431 case LIST_ITEM :
391432 return -1 ; // 1.any < 1-1
@@ -570,6 +611,13 @@ public final void parseVersion(String version) {
570611 stack .push (list );
571612 } else if (Character .isDigit (c )) {
572613 if (!isDigit && i > startIndex ) {
614+ // 1.0.0.RC1 < 1.0.0-RC2
615+ // treat .RC as -RC
616+ if (!list .isEmpty ()) {
617+ list .add (list = new ListItem ());
618+ stack .push (list );
619+ }
620+
573621 list .add (new StringItem (version .substring (startIndex , i ), true ));
574622 startIndex = i ;
575623
@@ -592,6 +640,13 @@ public final void parseVersion(String version) {
592640 }
593641
594642 if (version .length () > startIndex ) {
643+ // 1.0.0.RC1 < 1.0.0-RC2
644+ // treat .RC as -RC
645+ if (!isDigit && !list .isEmpty ()) {
646+ list .add (list = new ListItem ());
647+ stack .push (list );
648+ }
649+
595650 list .add (parseItem (isDigit , version .substring (startIndex )));
596651 }
597652
0 commit comments