1 package org.apache.maven.artifact.versioning;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import java.math.BigInteger;
23 import java.util.ArrayDeque;
24 import java.util.ArrayList;
25 import java.util.Arrays;
26 import java.util.Deque;
27 import java.util.Iterator;
28 import java.util.List;
29 import java.util.Locale;
30 import java.util.Properties;
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63 public class ComparableVersion
64 implements Comparable<ComparableVersion>
65 {
66 private static final int MAX_INTITEM_LENGTH = 9;
67
68 private static final int MAX_LONGITEM_LENGTH = 18;
69
70 private String value;
71
72 private String canonical;
73
74 private ListItem items;
75
76 private interface Item
77 {
78 int INT_ITEM = 3;
79 int LONG_ITEM = 4;
80 int BIGINTEGER_ITEM = 0;
81 int STRING_ITEM = 1;
82 int LIST_ITEM = 2;
83
84 int compareTo( Item item );
85
86 int getType();
87
88 boolean isNull();
89 }
90
91
92
93
94 private static class IntItem
95 implements Item
96 {
97 private final int value;
98
99 public static final IntItem ZERO = new IntItem();
100
101 private IntItem()
102 {
103 this.value = 0;
104 }
105
106 IntItem( String str )
107 {
108 this.value = Integer.parseInt( str );
109 }
110
111 @Override
112 public int getType()
113 {
114 return INT_ITEM;
115 }
116
117 @Override
118 public boolean isNull()
119 {
120 return value == 0;
121 }
122
123 @Override
124 public int compareTo( Item item )
125 {
126 if ( item == null )
127 {
128 return ( value == 0 ) ? 0 : 1;
129 }
130
131 switch ( item.getType() )
132 {
133 case INT_ITEM:
134 int itemValue = ( (IntItem) item ).value;
135 return ( value < itemValue ) ? -1 : ( ( value == itemValue ) ? 0 : 1 );
136 case LONG_ITEM:
137 case BIGINTEGER_ITEM:
138 return -1;
139
140 case STRING_ITEM:
141 return 1;
142
143 case LIST_ITEM:
144 return 1;
145
146 default:
147 throw new IllegalStateException( "invalid item: " + item.getClass() );
148 }
149 }
150
151 @Override
152 public boolean equals( Object o )
153 {
154 if ( this == o )
155 {
156 return true;
157 }
158 if ( o == null || getClass() != o.getClass() )
159 {
160 return false;
161 }
162
163 IntItem intItem = (IntItem) o;
164
165 return value == intItem.value;
166
167 }
168
169 @Override
170 public int hashCode()
171 {
172 return value;
173 }
174
175 @Override
176 public String toString()
177 {
178 return Integer.toString( value );
179 }
180 }
181
182
183
184
185 private static class LongItem
186 implements Item
187 {
188 private final long value;
189
190 LongItem( String str )
191 {
192 this.value = Long.parseLong( str );
193 }
194
195 @Override
196 public int getType()
197 {
198 return LONG_ITEM;
199 }
200
201 @Override
202 public boolean isNull()
203 {
204 return value == 0;
205 }
206
207 @Override
208 public int compareTo( Item item )
209 {
210 if ( item == null )
211 {
212 return ( value == 0 ) ? 0 : 1;
213 }
214
215 switch ( item.getType() )
216 {
217 case INT_ITEM:
218 return 1;
219 case LONG_ITEM:
220 long itemValue = ( (LongItem) item ).value;
221 return ( value < itemValue ) ? -1 : ( ( value == itemValue ) ? 0 : 1 );
222 case BIGINTEGER_ITEM:
223 return -1;
224
225 case STRING_ITEM:
226 return 1;
227
228 case LIST_ITEM:
229 return 1;
230
231 default:
232 throw new IllegalStateException( "invalid item: " + item.getClass() );
233 }
234 }
235
236 @Override
237 public boolean equals( Object o )
238 {
239 if ( this == o )
240 {
241 return true;
242 }
243 if ( o == null || getClass() != o.getClass() )
244 {
245 return false;
246 }
247
248 LongItem longItem = (LongItem) o;
249
250 return value == longItem.value;
251
252 }
253
254 @Override
255 public int hashCode()
256 {
257 return (int) ( value ^ ( value >>> 32 ) );
258 }
259
260 @Override
261 public String toString()
262 {
263 return Long.toString( value );
264 }
265 }
266
267
268
269
270 private static class BigIntegerItem
271 implements Item
272 {
273 private final BigInteger value;
274
275 BigIntegerItem( String str )
276 {
277 this.value = new BigInteger( str );
278 }
279
280 @Override
281 public int getType()
282 {
283 return BIGINTEGER_ITEM;
284 }
285
286 @Override
287 public boolean isNull()
288 {
289 return BigInteger.ZERO.equals( value );
290 }
291
292 @Override
293 public int compareTo( Item item )
294 {
295 if ( item == null )
296 {
297 return BigInteger.ZERO.equals( value ) ? 0 : 1;
298 }
299
300 switch ( item.getType() )
301 {
302 case INT_ITEM:
303 case LONG_ITEM:
304 return 1;
305
306 case BIGINTEGER_ITEM:
307 return value.compareTo( ( (BigIntegerItem) item ).value );
308
309 case STRING_ITEM:
310 return 1;
311
312 case LIST_ITEM:
313 return 1;
314
315 default:
316 throw new IllegalStateException( "invalid item: " + item.getClass() );
317 }
318 }
319
320 @Override
321 public boolean equals( Object o )
322 {
323 if ( this == o )
324 {
325 return true;
326 }
327 if ( o == null || getClass() != o.getClass() )
328 {
329 return false;
330 }
331
332 BigIntegerItem that = (BigIntegerItem) o;
333
334 return value.equals( that.value );
335
336 }
337
338 @Override
339 public int hashCode()
340 {
341 return value.hashCode();
342 }
343
344 public String toString()
345 {
346 return value.toString();
347 }
348 }
349
350
351
352
353 private static class StringItem
354 implements Item
355 {
356 private static final List<String> QUALIFIERS =
357 Arrays.asList( "alpha", "beta", "milestone", "rc", "snapshot", "", "sp" );
358
359 private static final Properties ALIASES = new Properties();
360 static
361 {
362 ALIASES.put( "ga", "" );
363 ALIASES.put( "final", "" );
364 ALIASES.put( "release", "" );
365 ALIASES.put( "cr", "rc" );
366 }
367
368
369
370
371
372 private static final String RELEASE_VERSION_INDEX = String.valueOf( QUALIFIERS.indexOf( "" ) );
373
374 private final String value;
375
376 StringItem( String value, boolean followedByDigit )
377 {
378 if ( followedByDigit && value.length() == 1 )
379 {
380
381 switch ( value.charAt( 0 ) )
382 {
383 case 'a':
384 value = "alpha";
385 break;
386 case 'b':
387 value = "beta";
388 break;
389 case 'm':
390 value = "milestone";
391 break;
392 default:
393 }
394 }
395 this.value = ALIASES.getProperty( value , value );
396 }
397
398 @Override
399 public int getType()
400 {
401 return STRING_ITEM;
402 }
403
404 @Override
405 public boolean isNull()
406 {
407 return ( comparableQualifier( value ).compareTo( RELEASE_VERSION_INDEX ) == 0 );
408 }
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423 public static String comparableQualifier( String qualifier )
424 {
425 int i = QUALIFIERS.indexOf( qualifier );
426
427 return i == -1 ? ( QUALIFIERS.size() + "-" + qualifier ) : String.valueOf( i );
428 }
429
430 @Override
431 public int compareTo( Item item )
432 {
433 if ( item == null )
434 {
435
436 return comparableQualifier( value ).compareTo( RELEASE_VERSION_INDEX );
437 }
438 switch ( item.getType() )
439 {
440 case INT_ITEM:
441 case LONG_ITEM:
442 case BIGINTEGER_ITEM:
443 return -1;
444
445 case STRING_ITEM:
446 return comparableQualifier( value ).compareTo( comparableQualifier( ( (StringItem) item ).value ) );
447
448 case LIST_ITEM:
449 return -1;
450
451 default:
452 throw new IllegalStateException( "invalid item: " + item.getClass() );
453 }
454 }
455
456 @Override
457 public boolean equals( Object o )
458 {
459 if ( this == o )
460 {
461 return true;
462 }
463 if ( o == null || getClass() != o.getClass() )
464 {
465 return false;
466 }
467
468 StringItem that = (StringItem) o;
469
470 return value.equals( that.value );
471
472 }
473
474 @Override
475 public int hashCode()
476 {
477 return value.hashCode();
478 }
479
480 public String toString()
481 {
482 return value;
483 }
484 }
485
486
487
488
489
490 private static class ListItem
491 extends ArrayList<Item>
492 implements Item
493 {
494 @Override
495 public int getType()
496 {
497 return LIST_ITEM;
498 }
499
500 @Override
501 public boolean isNull()
502 {
503 return ( size() == 0 );
504 }
505
506 void normalize()
507 {
508 for ( int i = size() - 1; i >= 0; i-- )
509 {
510 Item lastItem = get( i );
511
512 if ( lastItem.isNull() )
513 {
514
515 remove( i );
516 }
517 else if ( !( lastItem instanceof ListItem ) )
518 {
519 break;
520 }
521 }
522 }
523
524 @Override
525 public int compareTo( Item item )
526 {
527 if ( item == null )
528 {
529 if ( size() == 0 )
530 {
531 return 0;
532 }
533 Item first = get( 0 );
534 return first.compareTo( null );
535 }
536 switch ( item.getType() )
537 {
538 case INT_ITEM:
539 case LONG_ITEM:
540 case BIGINTEGER_ITEM:
541 return -1;
542
543 case STRING_ITEM:
544 return 1;
545
546 case LIST_ITEM:
547 Iterator<Item> left = iterator();
548 Iterator<Item> right = ( (ListItem) item ).iterator();
549
550 while ( left.hasNext() || right.hasNext() )
551 {
552 Item l = left.hasNext() ? left.next() : null;
553 Item r = right.hasNext() ? right.next() : null;
554
555
556 int result = l == null ? ( r == null ? 0 : -1 * r.compareTo( l ) ) : l.compareTo( r );
557
558 if ( result != 0 )
559 {
560 return result;
561 }
562 }
563
564 return 0;
565
566 default:
567 throw new IllegalStateException( "invalid item: " + item.getClass() );
568 }
569 }
570
571 @Override
572 public String toString()
573 {
574 StringBuilder buffer = new StringBuilder();
575 for ( Item item : this )
576 {
577 if ( buffer.length() > 0 )
578 {
579 buffer.append( ( item instanceof ListItem ) ? '-' : '.' );
580 }
581 buffer.append( item );
582 }
583 return buffer.toString();
584 }
585 }
586
587 public ComparableVersion( String version )
588 {
589 parseVersion( version );
590 }
591
592 @SuppressWarnings( "checkstyle:innerassignment" )
593 public final void parseVersion( String version )
594 {
595 this.value = version;
596
597 items = new ListItem();
598
599 version = version.toLowerCase( Locale.ENGLISH );
600
601 ListItem list = items;
602
603 Deque<Item> stack = new ArrayDeque<>();
604 stack.push( list );
605
606 boolean isDigit = false;
607
608 int startIndex = 0;
609
610 for ( int i = 0; i < version.length(); i++ )
611 {
612 char c = version.charAt( i );
613
614 if ( c == '.' )
615 {
616 if ( i == startIndex )
617 {
618 list.add( IntItem.ZERO );
619 }
620 else
621 {
622 list.add( parseItem( isDigit, version.substring( startIndex, i ) ) );
623 }
624 startIndex = i + 1;
625 }
626 else if ( c == '-' )
627 {
628 if ( i == startIndex )
629 {
630 list.add( IntItem.ZERO );
631 }
632 else
633 {
634 list.add( parseItem( isDigit, version.substring( startIndex, i ) ) );
635 }
636 startIndex = i + 1;
637
638 list.add( list = new ListItem() );
639 stack.push( list );
640 }
641 else if ( Character.isDigit( c ) )
642 {
643 if ( !isDigit && i > startIndex )
644 {
645 list.add( new StringItem( version.substring( startIndex, i ), true ) );
646 startIndex = i;
647
648 list.add( list = new ListItem() );
649 stack.push( list );
650 }
651
652 isDigit = true;
653 }
654 else
655 {
656 if ( isDigit && i > startIndex )
657 {
658 list.add( parseItem( true, version.substring( startIndex, i ) ) );
659 startIndex = i;
660
661 list.add( list = new ListItem() );
662 stack.push( list );
663 }
664
665 isDigit = false;
666 }
667 }
668
669 if ( version.length() > startIndex )
670 {
671 list.add( parseItem( isDigit, version.substring( startIndex ) ) );
672 }
673
674 while ( !stack.isEmpty() )
675 {
676 list = (ListItem) stack.pop();
677 list.normalize();
678 }
679 }
680
681 private static Item parseItem( boolean isDigit, String buf )
682 {
683 if ( isDigit )
684 {
685 buf = stripLeadingZeroes( buf );
686 if ( buf.length() <= MAX_INTITEM_LENGTH )
687 {
688
689 return new IntItem( buf );
690 }
691 else if ( buf.length() <= MAX_LONGITEM_LENGTH )
692 {
693
694 return new LongItem( buf );
695 }
696 return new BigIntegerItem( buf );
697 }
698 return new StringItem( buf, false );
699 }
700
701 private static String stripLeadingZeroes( String buf )
702 {
703 if ( buf == null || buf.isEmpty() )
704 {
705 return "0";
706 }
707 for ( int i = 0; i < buf.length(); ++i )
708 {
709 char c = buf.charAt( i );
710 if ( c != '0' )
711 {
712 return buf.substring( i );
713 }
714 }
715 return buf;
716 }
717
718 @Override
719 public int compareTo( ComparableVersion o )
720 {
721 return items.compareTo( o.items );
722 }
723
724 @Override
725 public String toString()
726 {
727 return value;
728 }
729
730 public String getCanonical()
731 {
732 if ( canonical == null )
733 {
734 canonical = items.toString();
735 }
736 return canonical;
737 }
738
739 @Override
740 public boolean equals( Object o )
741 {
742 return ( o instanceof ComparableVersion ) && items.equals( ( (ComparableVersion) o ).items );
743 }
744
745 @Override
746 public int hashCode()
747 {
748 return items.hashCode();
749 }
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769 public static void main( String... args )
770 {
771 System.out.println( "Display parameters as parsed by Maven (in canonical form) and comparison result:" );
772 if ( args.length == 0 )
773 {
774 return;
775 }
776
777 ComparableVersion prev = null;
778 int i = 1;
779 for ( String version : args )
780 {
781 ComparableVersion c = new ComparableVersion( version );
782
783 if ( prev != null )
784 {
785 int compare = prev.compareTo( c );
786 System.out.println( " " + prev.toString() + ' '
787 + ( ( compare == 0 ) ? "==" : ( ( compare < 0 ) ? "<" : ">" ) ) + ' ' + version );
788 }
789
790 System.out.println( String.valueOf( i++ ) + ". " + version + " == " + c.getCanonical() );
791
792 prev = c;
793 }
794 }
795 }