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.ArrayList;
24 import java.util.Arrays;
25 import java.util.Iterator;
26 import java.util.List;
27 import java.util.Locale;
28 import java.util.Properties;
29 import java.util.Stack;
30
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 public class ComparableVersion
63 implements Comparable<ComparableVersion>
64 {
65 private String value;
66
67 private String canonical;
68
69 private ListItem items;
70
71 private interface Item
72 {
73 int INTEGER_ITEM = 0;
74 int STRING_ITEM = 1;
75 int LIST_ITEM = 2;
76
77 int compareTo( Item item );
78
79 int getType();
80
81 boolean isNull();
82 }
83
84
85
86
87 private static class IntegerItem
88 implements Item
89 {
90 private static final BigInteger BIG_INTEGER_ZERO = new BigInteger( "0" );
91
92 private final BigInteger value;
93
94 public static final IntegerItem ZERO = new IntegerItem();
95
96 private IntegerItem()
97 {
98 this.value = BIG_INTEGER_ZERO;
99 }
100
101 IntegerItem( String str )
102 {
103 this.value = new BigInteger( str );
104 }
105
106 public int getType()
107 {
108 return INTEGER_ITEM;
109 }
110
111 public boolean isNull()
112 {
113 return BIG_INTEGER_ZERO.equals( value );
114 }
115
116 public int compareTo( Item item )
117 {
118 if ( item == null )
119 {
120 return BIG_INTEGER_ZERO.equals( value ) ? 0 : 1;
121 }
122
123 switch ( item.getType() )
124 {
125 case INTEGER_ITEM:
126 return value.compareTo( ( (IntegerItem) item ).value );
127
128 case STRING_ITEM:
129 return 1;
130
131 case LIST_ITEM:
132 return 1;
133
134 default:
135 throw new RuntimeException( "invalid item: " + item.getClass() );
136 }
137 }
138
139 public String toString()
140 {
141 return value.toString();
142 }
143 }
144
145
146
147
148 private static class StringItem
149 implements Item
150 {
151 private static final List<String> QUALIFIERS =
152 Arrays.asList( "alpha", "beta", "milestone", "rc", "snapshot", "", "sp" );
153
154 private static final Properties ALIASES = new Properties();
155 static
156 {
157 ALIASES.put( "ga", "" );
158 ALIASES.put( "final", "" );
159 ALIASES.put( "cr", "rc" );
160 }
161
162
163
164
165
166 private static final String RELEASE_VERSION_INDEX = String.valueOf( QUALIFIERS.indexOf( "" ) );
167
168 private String value;
169
170 StringItem( String value, boolean followedByDigit )
171 {
172 if ( followedByDigit && value.length() == 1 )
173 {
174
175 switch ( value.charAt( 0 ) )
176 {
177 case 'a':
178 value = "alpha";
179 break;
180 case 'b':
181 value = "beta";
182 break;
183 case 'm':
184 value = "milestone";
185 break;
186 default:
187 }
188 }
189 this.value = ALIASES.getProperty( value , value );
190 }
191
192 public int getType()
193 {
194 return STRING_ITEM;
195 }
196
197 public boolean isNull()
198 {
199 return ( comparableQualifier( value ).compareTo( RELEASE_VERSION_INDEX ) == 0 );
200 }
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215 public static String comparableQualifier( String qualifier )
216 {
217 int i = QUALIFIERS.indexOf( qualifier );
218
219 return i == -1 ? ( QUALIFIERS.size() + "-" + qualifier ) : String.valueOf( i );
220 }
221
222 public int compareTo( Item item )
223 {
224 if ( item == null )
225 {
226
227 return comparableQualifier( value ).compareTo( RELEASE_VERSION_INDEX );
228 }
229 switch ( item.getType() )
230 {
231 case INTEGER_ITEM:
232 return -1;
233
234 case STRING_ITEM:
235 return comparableQualifier( value ).compareTo( comparableQualifier( ( (StringItem) item ).value ) );
236
237 case LIST_ITEM:
238 return -1;
239
240 default:
241 throw new RuntimeException( "invalid item: " + item.getClass() );
242 }
243 }
244
245 public String toString()
246 {
247 return value;
248 }
249 }
250
251
252
253
254
255 private static class ListItem
256 extends ArrayList<Item>
257 implements Item
258 {
259 public int getType()
260 {
261 return LIST_ITEM;
262 }
263
264 public boolean isNull()
265 {
266 return ( size() == 0 );
267 }
268
269 void normalize()
270 {
271 for ( int i = size() - 1; i >= 0; i-- )
272 {
273 Item lastItem = get( i );
274
275 if ( lastItem.isNull() )
276 {
277
278 remove( i );
279 }
280 else if ( !( lastItem instanceof ListItem ) )
281 {
282 break;
283 }
284 }
285 }
286
287 public int compareTo( Item item )
288 {
289 if ( item == null )
290 {
291 if ( size() == 0 )
292 {
293 return 0;
294 }
295 Item first = get( 0 );
296 return first.compareTo( null );
297 }
298 switch ( item.getType() )
299 {
300 case INTEGER_ITEM:
301 return -1;
302
303 case STRING_ITEM:
304 return 1;
305
306 case LIST_ITEM:
307 Iterator<Item> left = iterator();
308 Iterator<Item> right = ( (ListItem) item ).iterator();
309
310 while ( left.hasNext() || right.hasNext() )
311 {
312 Item l = left.hasNext() ? left.next() : null;
313 Item r = right.hasNext() ? right.next() : null;
314
315
316 int result = l == null ? ( r == null ? 0 : -1 * r.compareTo( l ) ) : l.compareTo( r );
317
318 if ( result != 0 )
319 {
320 return result;
321 }
322 }
323
324 return 0;
325
326 default:
327 throw new RuntimeException( "invalid item: " + item.getClass() );
328 }
329 }
330
331 public String toString()
332 {
333 StringBuilder buffer = new StringBuilder();
334 for ( Item item : this )
335 {
336 if ( buffer.length() > 0 )
337 {
338 buffer.append( ( item instanceof ListItem ) ? '-' : '.' );
339 }
340 buffer.append( item );
341 }
342 return buffer.toString();
343 }
344 }
345
346 public ComparableVersion( String version )
347 {
348 parseVersion( version );
349 }
350
351 @SuppressWarnings( "checkstyle:innerassignment" )
352 public final void parseVersion( String version )
353 {
354 this.value = version;
355
356 items = new ListItem();
357
358 version = version.toLowerCase( Locale.ENGLISH );
359
360 ListItem list = items;
361
362 Stack<Item> stack = new Stack<>();
363 stack.push( list );
364
365 boolean isDigit = false;
366
367 int startIndex = 0;
368
369 for ( int i = 0; i < version.length(); i++ )
370 {
371 char c = version.charAt( i );
372
373 if ( c == '.' )
374 {
375 if ( i == startIndex )
376 {
377 list.add( IntegerItem.ZERO );
378 }
379 else
380 {
381 list.add( parseItem( isDigit, version.substring( startIndex, i ) ) );
382 }
383 startIndex = i + 1;
384 }
385 else if ( c == '-' )
386 {
387 if ( i == startIndex )
388 {
389 list.add( IntegerItem.ZERO );
390 }
391 else
392 {
393 list.add( parseItem( isDigit, version.substring( startIndex, i ) ) );
394 }
395 startIndex = i + 1;
396
397 list.add( list = new ListItem() );
398 stack.push( list );
399 }
400 else if ( Character.isDigit( c ) )
401 {
402 if ( !isDigit && i > startIndex )
403 {
404 list.add( new StringItem( version.substring( startIndex, i ), true ) );
405 startIndex = i;
406
407 list.add( list = new ListItem() );
408 stack.push( list );
409 }
410
411 isDigit = true;
412 }
413 else
414 {
415 if ( isDigit && i > startIndex )
416 {
417 list.add( parseItem( true, version.substring( startIndex, i ) ) );
418 startIndex = i;
419
420 list.add( list = new ListItem() );
421 stack.push( list );
422 }
423
424 isDigit = false;
425 }
426 }
427
428 if ( version.length() > startIndex )
429 {
430 list.add( parseItem( isDigit, version.substring( startIndex ) ) );
431 }
432
433 while ( !stack.isEmpty() )
434 {
435 list = (ListItem) stack.pop();
436 list.normalize();
437 }
438
439 canonical = items.toString();
440 }
441
442 private static Item parseItem( boolean isDigit, String buf )
443 {
444 return isDigit ? new IntegerItem( buf ) : new StringItem( buf, false );
445 }
446
447 public int compareTo( ComparableVersion o )
448 {
449 return items.compareTo( o.items );
450 }
451
452 public String toString()
453 {
454 return value;
455 }
456
457 public String getCanonical()
458 {
459 return canonical;
460 }
461
462 public boolean equals( Object o )
463 {
464 return ( o instanceof ComparableVersion ) && canonical.equals( ( (ComparableVersion) o ).canonical );
465 }
466
467 public int hashCode()
468 {
469 return canonical.hashCode();
470 }
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490 public static void main( String... args )
491 {
492 System.out.println( "Display parameters as parsed by Maven (in canonical form) and comparison result:" );
493 if ( args.length == 0 )
494 {
495 return;
496 }
497
498 ComparableVersion prev = null;
499 int i = 1;
500 for ( String version : args )
501 {
502 ComparableVersion c = new ComparableVersion( version );
503
504 if ( prev != null )
505 {
506 int compare = prev.compareTo( c );
507 System.out.println( " " + prev.toString() + ' '
508 + ( ( compare == 0 ) ? "==" : ( ( compare < 0 ) ? "<" : ">" ) ) + ' ' + version );
509 }
510
511 System.out.println( String.valueOf( i++ ) + ". " + version + " == " + c.getCanonical() );
512
513 prev = c;
514 }
515 }
516 }