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