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
534 for ( Item i : this )
535 {
536 int result = i.compareTo( null );
537 if ( result != 0 )
538 {
539 return result;
540 }
541 }
542 return 0;
543 }
544 switch ( item.getType() )
545 {
546 case INT_ITEM:
547 case LONG_ITEM:
548 case BIGINTEGER_ITEM:
549 return -1;
550
551 case STRING_ITEM:
552 return 1;
553
554 case LIST_ITEM:
555 Iterator<Item> left = iterator();
556 Iterator<Item> right = ( (ListItem) item ).iterator();
557
558 while ( left.hasNext() || right.hasNext() )
559 {
560 Item l = left.hasNext() ? left.next() : null;
561 Item r = right.hasNext() ? right.next() : null;
562
563
564 int result = l == null ? ( r == null ? 0 : -1 * r.compareTo( l ) ) : l.compareTo( r );
565
566 if ( result != 0 )
567 {
568 return result;
569 }
570 }
571
572 return 0;
573
574 default:
575 throw new IllegalStateException( "invalid item: " + item.getClass() );
576 }
577 }
578
579 @Override
580 public String toString()
581 {
582 StringBuilder buffer = new StringBuilder();
583 for ( Item item : this )
584 {
585 if ( buffer.length() > 0 )
586 {
587 buffer.append( ( item instanceof ListItem ) ? '-' : '.' );
588 }
589 buffer.append( item );
590 }
591 return buffer.toString();
592 }
593
594
595
596
597 private String toListString()
598 {
599 StringBuilder buffer = new StringBuilder();
600 buffer.append( "[" );
601 for ( Item item : this )
602 {
603 if ( buffer.length() > 1 )
604 {
605 buffer.append( ", " );
606 }
607 if ( item instanceof ListItem )
608 {
609 buffer.append( ( (ListItem ) item ).toListString() );
610 }
611 else
612 {
613 buffer.append( item );
614 }
615 }
616 buffer.append( "]" );
617 return buffer.toString();
618 }
619 }
620
621 public ComparableVersion( String version )
622 {
623 parseVersion( version );
624 }
625
626 @SuppressWarnings( "checkstyle:innerassignment" )
627 public final void parseVersion( String version )
628 {
629 this.value = version;
630
631 items = new ListItem();
632
633 version = version.toLowerCase( Locale.ENGLISH );
634
635 ListItem list = items;
636
637 Deque<Item> stack = new ArrayDeque<>();
638 stack.push( list );
639
640 boolean isDigit = false;
641
642 int startIndex = 0;
643
644 for ( int i = 0; i < version.length(); i++ )
645 {
646 char c = version.charAt( i );
647
648 if ( c == '.' )
649 {
650 if ( i == startIndex )
651 {
652 list.add( IntItem.ZERO );
653 }
654 else
655 {
656 list.add( parseItem( isDigit, version.substring( startIndex, i ) ) );
657 }
658 startIndex = i + 1;
659 }
660 else if ( c == '-' )
661 {
662 if ( i == startIndex )
663 {
664 list.add( IntItem.ZERO );
665 }
666 else
667 {
668 list.add( parseItem( isDigit, version.substring( startIndex, i ) ) );
669 }
670 startIndex = i + 1;
671
672 list.add( list = new ListItem() );
673 stack.push( list );
674 }
675 else if ( Character.isDigit( c ) )
676 {
677 if ( !isDigit && i > startIndex )
678 {
679 list.add( new StringItem( version.substring( startIndex, i ), true ) );
680 startIndex = i;
681
682 list.add( list = new ListItem() );
683 stack.push( list );
684 }
685
686 isDigit = true;
687 }
688 else
689 {
690 if ( isDigit && i > startIndex )
691 {
692 list.add( parseItem( true, version.substring( startIndex, i ) ) );
693 startIndex = i;
694
695 list.add( list = new ListItem() );
696 stack.push( list );
697 }
698
699 isDigit = false;
700 }
701 }
702
703 if ( version.length() > startIndex )
704 {
705 list.add( parseItem( isDigit, version.substring( startIndex ) ) );
706 }
707
708 while ( !stack.isEmpty() )
709 {
710 list = (ListItem) stack.pop();
711 list.normalize();
712 }
713 }
714
715 private static Item parseItem( boolean isDigit, String buf )
716 {
717 if ( isDigit )
718 {
719 buf = stripLeadingZeroes( buf );
720 if ( buf.length() <= MAX_INTITEM_LENGTH )
721 {
722
723 return new IntItem( buf );
724 }
725 else if ( buf.length() <= MAX_LONGITEM_LENGTH )
726 {
727
728 return new LongItem( buf );
729 }
730 return new BigIntegerItem( buf );
731 }
732 return new StringItem( buf, false );
733 }
734
735 private static String stripLeadingZeroes( String buf )
736 {
737 if ( buf == null || buf.isEmpty() )
738 {
739 return "0";
740 }
741 for ( int i = 0; i < buf.length(); ++i )
742 {
743 char c = buf.charAt( i );
744 if ( c != '0' )
745 {
746 return buf.substring( i );
747 }
748 }
749 return buf;
750 }
751
752 @Override
753 public int compareTo( ComparableVersion o )
754 {
755 return items.compareTo( o.items );
756 }
757
758 @Override
759 public String toString()
760 {
761 return value;
762 }
763
764 public String getCanonical()
765 {
766 if ( canonical == null )
767 {
768 canonical = items.toString();
769 }
770 return canonical;
771 }
772
773 @Override
774 public boolean equals( Object o )
775 {
776 return ( o instanceof ComparableVersion ) && items.equals( ( (ComparableVersion) o ).items );
777 }
778
779 @Override
780 public int hashCode()
781 {
782 return items.hashCode();
783 }
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802 public static void main( String... args )
803 {
804 System.out.println( "Display parameters as parsed by Maven (in canonical form and as a list of tokens) and"
805 + " comparison result:" );
806 if ( args.length == 0 )
807 {
808 return;
809 }
810
811 ComparableVersion prev = null;
812 int i = 1;
813 for ( String version : args )
814 {
815 ComparableVersion c = new ComparableVersion( version );
816
817 if ( prev != null )
818 {
819 int compare = prev.compareTo( c );
820 System.out.println( " " + prev.toString() + ' '
821 + ( ( compare == 0 ) ? "==" : ( ( compare < 0 ) ? "<" : ">" ) ) + ' ' + version );
822 }
823
824 System.out.println( ( i++ ) + ". " + version + " -> " + c.getCanonical() + "; tokens: " + c.items.toListString() );
825
826 prev = c;
827 }
828 }
829
830 }