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