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 ;
30+ import java .util .Map ;
2931import java .util .Objects ;
30- import java .util .Properties ;
3132
3233/**
3334 * <p>
4142 * <code>1.0alpha1 => [1, [alpha, 1]]</code></li>
4243 * <li>Unlimited number of version components,</li>
4344 * <li>Version components in the text can be digits or strings,</li>
44- * <li>Strings are checked for well-known qualifiers, and the qualifier ordering is used for version ordering.
45- * Well-known qualifiers (case-insensitive) are, in order from least to greatest:<ol>
46- * <li><code>alpha</code> or <code>a</code></li>
47- * <li><code>beta</code> or <code>b</code></li>
48- * <li><code>milestone</code> or <code>m</code></li>
49- * <li><code>rc</code> or <code>cr</code></li>
50- * <li><code>snapshot</code></li>
51- * <li><code>ga</code> or <code>final</code></li>
52- * <li><code>sp</code></li>
53- * </ol>
54- * Unknown qualifiers are considered after known qualifiers,
55- * with lexical order (case-insensitive in the English locale).
56- * <code>ga</code> and <code>final</code> sort the same as not having a qualifier.
57- * </li>
45+ * <li>
46+ * Following semver rules is encouraged, and some qualifiers are discouraged (no matter the case sensitivity):
47+ * <ul>
48+ * <li> The usage of 'CR' qualifier is discouraged. Use 'RC' instead. </li>
49+ * <li> The usage of 'Final', 'GA', and 'Release' qualifiers is discouraged. Use no qualifier instead. </li>
50+ * <li> The usage of 'SP' qualifier is discouraged. Increment the patch version instead. </li>
51+ * </ul>
52+ * String qualifiers are ordered lexically (case insensitive in the English locale), with the following exceptions:
53+ * <ul>
54+ * <li><code> alpha = a < beta = b < milestone = m < rc = cr
55+ * < snapshot < '' = final = ga = release < sp </code></li>
56+ * <li>Other qualifiers are ordered lexically (case insensitive in the English locale),
57+ * and considered less than Snapshot and Release.</li>
58+ * </ul>
59+ * </li>
5860 * <li>A hyphen usually precedes a qualifier, and is always less important than digits/number. For example
5961 * {@code 1.0.RC2 < 1.0-RC3 < 1.0.1}; but prefer {@code 1.0.0-RC2} over {@code 1.0.0.RC2}, and more
6062 * generally: {@code 1.0.X2 < 1.0-X3 < 1.0.1} for any string {@code X}; but prefer {@code 1.0.0-X1}
@@ -295,41 +297,37 @@ public String toString() {
295297 * Represents a string in the version item list, usually a qualifier.
296298 */
297299 private static class StringItem implements Item {
298- private static final List <String > QUALIFIERS =
299- Arrays .asList ("alpha" , "beta" , "milestone" , "rc" , "snapshot" , "" , "sp" );
300- private static final List <String > RELEASE_QUALIFIERS = Arrays .asList ("ga" , "final" , "release" );
300+ private static final List <String > QUALIFIERS = Arrays .asList ("snapshot" , "" , "sp" );
301+ private static final List <String > RELEASE_QUALIFIERS = Arrays .asList ("final" , "ga" , "release" );
301302
302- private static final Properties ALIASES = new Properties ( );
303+ private static final Map < String , String > ALIASES = new HashMap <>( 4 );
303304
304305 static {
305306 ALIASES .put ("cr" , "rc" );
307+ ALIASES .put ("final" , "" );
308+ ALIASES .put ("ga" , "" );
309+ ALIASES .put ("release" , "" );
306310 }
307311
308312 /**
309- * A comparable value for the empty-string qualifier. This one is used to determine if a given qualifier makes
313+ * An index value for the empty-string qualifier. This one is used to determine if a given qualifier makes
310314 * the version older than one without a qualifier, or more recent.
311315 */
312- private static final String RELEASE_VERSION_INDEX = String . valueOf ( QUALIFIERS .indexOf ("" ) );
316+ private static final int RELEASE_VERSION_INDEX = QUALIFIERS .indexOf ("" );
313317
314318 private final String value ;
315319
316320 StringItem (String value , boolean followedByDigit ) {
317321 if (followedByDigit && value .length () == 1 ) {
318322 // a1 = alpha-1, b1 = beta-1, m1 = milestone-1
319- switch (value .charAt (0 )) {
320- case 'a' :
321- value = "alpha" ;
322- break ;
323- case 'b' :
324- value = "beta" ;
325- break ;
326- case 'm' :
327- value = "milestone" ;
328- break ;
329- default :
330- }
323+ value = switch (value .charAt (0 )) {
324+ case 'a' -> "alpha" ;
325+ case 'b' -> "beta" ;
326+ case 'm' -> "milestone" ;
327+ default -> value ;
328+ };
331329 }
332- this .value = ALIASES .getProperty (value , value );
330+ this .value = ALIASES .getOrDefault (value , value );
333331 }
334332
335333 @ Override
@@ -351,27 +349,55 @@ public boolean isNull() {
351349 *
352350 * @param qualifier
353351 * @return an equivalent value that can be used with lexical comparison
352+ * @deprecated Use {@link #compareQualifiers(String, String)} instead
354353 */
354+ @ Deprecated
355355 public static String comparableQualifier (String qualifier ) {
356+ // Just returning an Integer with the index here is faster, but requires a lot of if/then/else to check for
357+ // -1 or QUALIFIERS.size and then resort to lexical ordering. Most comparisons are decided by the first
358+ // character, so this is still fast. If more characters are needed then it requires a lexical sort anyway.
359+
356360 if (RELEASE_QUALIFIERS .contains (qualifier )) {
357- return String . valueOf ( QUALIFIERS . indexOf ( "" )) ;
361+ qualifier = "" ;
358362 }
359363
360- int i = QUALIFIERS .indexOf (qualifier );
364+ int index = QUALIFIERS .indexOf (qualifier ) + 1 ;
361365
362- // Just returning an Integer with the index here is faster, but requires a lot of if/then/else to check for
363- // -1
364- // or QUALIFIERS.size and then resort to lexical ordering. Most comparisons are decided by the first
365- // character,
366- // so this is still fast. If more characters are needed then it requires a lexical sort anyway.
367- return i == -1 ? (QUALIFIERS .size () + "-" + qualifier ) : String .valueOf (i );
366+ return index == 0 ? ("0-" + qualifier ) : String .valueOf (index );
367+ }
368+
369+ /**
370+ * Compare the qualifiers of two artifact versions.
371+ *
372+ * @param qualifier1 qualifier of first artifact
373+ * @param qualifier2 qualifier of second artifact
374+ * @return a negative integer, zero, or a positive integer as the first argument is less than, equal to, or
375+ * greater than the second
376+ */
377+ public static int compareQualifiers (String qualifier1 , String qualifier2 ) {
378+ if (RELEASE_QUALIFIERS .contains (qualifier1 )) {
379+ qualifier1 = "" ;
380+ }
381+ if (RELEASE_QUALIFIERS .contains (qualifier2 )) {
382+ qualifier2 = "" ;
383+ }
384+ int i1 = QUALIFIERS .indexOf (qualifier1 );
385+ int i2 = QUALIFIERS .indexOf (qualifier2 );
386+
387+ // if both pre-release, then use natural lexical ordering
388+ if (i1 == -1 && i2 == -1 ) {
389+ return qualifier1 .compareTo (qualifier2 );
390+ }
391+
392+ // 'other qualifier' < 'snapshot' < '' < 'sp'
393+ return Integer .compare (i1 , i2 );
368394 }
369395
370396 @ Override
371397 public int compareTo (Item item ) {
372398 if (item == null ) {
373399 // 1-rc < 1, 1-ga > 1
374- return comparableQualifier ( value ). compareTo ( RELEASE_VERSION_INDEX );
400+ return Integer . compare ( QUALIFIERS . indexOf ( value ), RELEASE_VERSION_INDEX );
375401 }
376402 switch (item .getType ()) {
377403 case INT_ITEM :
@@ -380,7 +406,7 @@ public int compareTo(Item item) {
380406 return -1 ; // 1.any < 1.1 ?
381407
382408 case STRING_ITEM :
383- return comparableQualifier (value ). compareTo ( comparableQualifier ((( StringItem ) item ).value ) );
409+ return compareQualifiers (value , (( StringItem ) item ).value );
384410
385411 case COMBINATION_ITEM :
386412 int result = this .compareTo (((CombinationItem ) item ).getStringPart ());
0 commit comments