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