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><code> 'snapshot' < '' < 'sp' </code></li>
48+ * </ul>
49+ * and <code>'alias' -> 'replacement'</code> (all case insensitive):
50+ * <ul>
51+ * <li><code> 'a' -> 'alpha' </code></li>
52+ * <li><code> 'b' -> 'beta' </code></li>
53+ * <li><code> 'm' -> 'milestone' </code></li>
54+ * <li><code> 'cr' -> 'rc' </code></li>
55+ * <li><code> 'final' -> '' </code></li>
56+ * <li><code> 'ga' -> '' </code></li>
57+ * <li><code> 'release' -> '' </code></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><code> alpha = a < beta = b < milestone = m < rc = cr
70+ * < snapshot < '' = final = ga = release < sp </code></li>
71+ * </ul>
72+ * </li>
73+ * <li>a hyphen usually precedes a qualifier, and is always less important than digits/number, for example
74+ * 1.0.RC2 < 1.0-RC3 < 1.0.1 ; but prefer '1.0.0-RC1' over '1.0.0.RC1' </li>
5675 * </ul>
5776 *
58- * @see <a href="https://cwiki .apache.org/confluence/display/MAVENOLD/Versioning">"Versioning" on Maven Wiki </a>
77+ * @see <a href="https://maven .apache.org/pom.html#Version_Order_Specification">Version Order Specification </a>
5978 * @author <a href="mailto:kenney@apache.org">Kenney Westerhof</a>
6079 * @author <a href="mailto:hboutemy@apache.org">Hervé Boutemy</a>
6180 */
@@ -304,23 +323,22 @@ public String toString() {
304323 * Represents a string in the version item list, usually a qualifier.
305324 */
306325 private static class StringItem implements Item {
307- private static final List <String > QUALIFIERS =
308- Arrays .asList ("alpha" , "beta" , "milestone" , "rc" , "snapshot" , "" , "sp" );
326+ private static final List <String > QUALIFIERS = Arrays .asList ("snapshot" , "" , "sp" );
309327
310- private static final Properties ALIASES = new Properties ( );
328+ private static final Map < String , String > ALIASES = new HashMap <>( 4 );
311329
312330 static {
313- ALIASES .put ("ga " , "" );
331+ ALIASES .put ("cr " , "rc " );
314332 ALIASES .put ("final" , "" );
333+ ALIASES .put ("ga" , "" );
315334 ALIASES .put ("release" , "" );
316- ALIASES .put ("cr" , "rc" );
317335 }
318336
319337 /**
320- * A comparable value for the empty-string qualifier. This one is used to determine if a given qualifier makes
338+ * An index value for the empty-string qualifier. This one is used to determine if a given qualifier makes
321339 * the version older than one without a qualifier, or more recent.
322340 */
323- private static final String RELEASE_VERSION_INDEX = String . valueOf ( QUALIFIERS .indexOf ("" ) );
341+ private static final int RELEASE_VERSION_INDEX = QUALIFIERS .indexOf ("" );
324342
325343 private final String value ;
326344
@@ -340,7 +358,7 @@ private static class StringItem implements Item {
340358 default :
341359 }
342360 }
343- this .value = ALIASES .getProperty (value , value );
361+ this .value = ALIASES .getOrDefault (value , value );
344362 }
345363
346364 @ Override
@@ -350,7 +368,7 @@ public int getType() {
350368
351369 @ Override
352370 public boolean isNull () {
353- return ( comparableQualifier ( value ). compareTo ( RELEASE_VERSION_INDEX ) == 0 ) ;
371+ return QUALIFIERS . indexOf ( value ) == RELEASE_VERSION_INDEX ;
354372 }
355373
356374 /**
@@ -365,18 +383,42 @@ public boolean isNull() {
365383 *
366384 * @param qualifier
367385 * @return an equivalent value that can be used with lexical comparison
386+ * @deprecated Use {@link #compareQualifiers(String, String)} instead
368387 */
388+ @ Deprecated
369389 public static String comparableQualifier (String qualifier ) {
370- int i = QUALIFIERS .indexOf (qualifier );
390+ int index = QUALIFIERS .indexOf (qualifier ) + 1 ;
371391
372- return i == -1 ? (QUALIFIERS .size () + "-" + qualifier ) : String .valueOf (i );
392+ return index == 0 ? ("0-" + qualifier ) : String .valueOf (index );
393+ }
394+
395+ /**
396+ * Compare the qualifiers of two artifact versions.
397+ *
398+ * @param qualifier1 qualifier of first artifact
399+ * @param qualifier2 qualifier of second artifact
400+ * @return a negative integer, zero, or a positive integer as the first argument is less than, equal to, or
401+ * greater than the second
402+ */
403+ public static int compareQualifiers (String qualifier1 , String qualifier2 ) {
404+ int i1 = QUALIFIERS .indexOf (qualifier1 );
405+ int i2 = QUALIFIERS .indexOf (qualifier2 );
406+
407+ // if both pre-release, then use natural lexical ordering
408+ if (i1 == -1 && i2 == -1 ) {
409+ // alpha < beta < ea < milestone < preview < rc
410+ return qualifier1 .compareTo (qualifier2 );
411+ }
412+
413+ // 'other qualifier' < 'snapshot' < '' < 'sp'
414+ return Integer .compare (i1 , i2 );
373415 }
374416
375417 @ Override
376418 public int compareTo (Item item ) {
377419 if (item == null ) {
378420 // 1-rc < 1, 1-ga > 1
379- return comparableQualifier ( value ). compareTo ( RELEASE_VERSION_INDEX );
421+ return Integer . compare ( QUALIFIERS . indexOf ( value ), RELEASE_VERSION_INDEX );
380422 }
381423 switch (item .getType ()) {
382424 case INT_ITEM :
@@ -385,7 +427,7 @@ public int compareTo(Item item) {
385427 return -1 ; // 1.any < 1.1 ?
386428
387429 case STRING_ITEM :
388- return comparableQualifier (value ). compareTo ( comparableQualifier ((( StringItem ) item ).value ) );
430+ return compareQualifiers (value , (( StringItem ) item ).value );
389431
390432 case LIST_ITEM :
391433 return -1 ; // 1.any < 1-1
@@ -570,6 +612,13 @@ public final void parseVersion(String version) {
570612 stack .push (list );
571613 } else if (Character .isDigit (c )) {
572614 if (!isDigit && i > startIndex ) {
615+ // 1.0.0.RC1 < 1.0.0-RC2
616+ // treat .RC as -RC
617+ if (!list .isEmpty ()) {
618+ list .add (list = new ListItem ());
619+ stack .push (list );
620+ }
621+
573622 list .add (new StringItem (version .substring (startIndex , i ), true ));
574623 startIndex = i ;
575624
@@ -592,6 +641,13 @@ public final void parseVersion(String version) {
592641 }
593642
594643 if (version .length () > startIndex ) {
644+ // 1.0.0.RC1 < 1.0.0-RC2
645+ // treat .RC as -RC
646+ if (!isDigit && !list .isEmpty ()) {
647+ list .add (list = new ListItem ());
648+ stack .push (list );
649+ }
650+
595651 list .add (parseItem (isDigit , version .substring (startIndex )));
596652 }
597653
0 commit comments