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 public class ComparableVersion
61 implements Comparable<ComparableVersion>
62 {
63 private String value;
64
65 private String canonical;
66
67 private ListItem items;
68
69 private interface Item
70 {
71 int INTEGER_ITEM = 0;
72 int STRING_ITEM = 1;
73 int LIST_ITEM = 2;
74
75 int compareTo( Item item );
76
77 int getType();
78
79 boolean isNull();
80 }
81
82
83
84
85 private static class IntegerItem
86 implements Item
87 {
88 private static final BigInteger BIG_INTEGER_ZERO = new BigInteger( "0" );
89
90 private final BigInteger value;
91
92 public static final IntegerItem ZERO = new IntegerItem();
93
94 private IntegerItem()
95 {
96 this.value = BIG_INTEGER_ZERO;
97 }
98
99 public IntegerItem( String str )
100 {
101 this.value = new BigInteger( str );
102 }
103
104 public int getType()
105 {
106 return INTEGER_ITEM;
107 }
108
109 public boolean isNull()
110 {
111 return BIG_INTEGER_ZERO.equals( value );
112 }
113
114 public int compareTo( Item item )
115 {
116 if ( item == null )
117 {
118 return BIG_INTEGER_ZERO.equals( value ) ? 0 : 1;
119 }
120
121 switch ( item.getType() )
122 {
123 case INTEGER_ITEM:
124 return value.compareTo( ( (IntegerItem) item ).value );
125
126 case STRING_ITEM:
127 return 1;
128
129 case LIST_ITEM:
130 return 1;
131
132 default:
133 throw new RuntimeException( "invalid item: " + item.getClass() );
134 }
135 }
136
137 public String toString()
138 {
139 return value.toString();
140 }
141 }
142
143
144
145
146 private static class StringItem
147 implements Item
148 {
149 private static final String[] QUALIFIERS = { "alpha", "beta", "milestone", "rc", "snapshot", "", "sp" };
150
151 @SuppressWarnings( "checkstyle:constantname" )
152 private static final List<String> _QUALIFIERS = Arrays.asList( QUALIFIERS );
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 public 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 public final void parseVersion( String version )
352 {
353 this.value = version;
354
355 items = new ListItem();
356
357 version = version.toLowerCase( Locale.ENGLISH );
358
359 ListItem list = items;
360
361 Stack<Item> stack = new Stack<>();
362 stack.push( list );
363
364 boolean isDigit = false;
365
366 int startIndex = 0;
367
368 for ( int i = 0; i < version.length(); i++ )
369 {
370 char c = version.charAt( i );
371
372 if ( c == '.' )
373 {
374 if ( i == startIndex )
375 {
376 list.add( IntegerItem.ZERO );
377 }
378 else
379 {
380 list.add( parseItem( isDigit, version.substring( startIndex, i ) ) );
381 }
382 startIndex = i + 1;
383 }
384 else if ( c == '-' )
385 {
386 if ( i == startIndex )
387 {
388 list.add( IntegerItem.ZERO );
389 }
390 else
391 {
392 list.add( parseItem( isDigit, version.substring( startIndex, i ) ) );
393 }
394 startIndex = i + 1;
395
396 list.add( list = new ListItem() );
397 stack.push( list );
398 }
399 else if ( Character.isDigit( c ) )
400 {
401 if ( !isDigit && i > startIndex )
402 {
403 list.add( new StringItem( version.substring( startIndex, i ), true ) );
404 startIndex = i;
405
406 list.add( list = new ListItem() );
407 stack.push( list );
408 }
409
410 isDigit = true;
411 }
412 else
413 {
414 if ( isDigit && i > startIndex )
415 {
416 list.add( parseItem( true, version.substring( startIndex, i ) ) );
417 startIndex = i;
418
419 list.add( list = new ListItem() );
420 stack.push( list );
421 }
422
423 isDigit = false;
424 }
425 }
426
427 if ( version.length() > startIndex )
428 {
429 list.add( parseItem( isDigit, version.substring( startIndex ) ) );
430 }
431
432 while ( !stack.isEmpty() )
433 {
434 list = (ListItem) stack.pop();
435 list.normalize();
436 }
437
438 canonical = items.toString();
439 }
440
441 private static Item parseItem( boolean isDigit, String buf )
442 {
443 return isDigit ? new IntegerItem( buf ) : new StringItem( buf, false );
444 }
445
446 public int compareTo( ComparableVersion o )
447 {
448 return items.compareTo( o.items );
449 }
450
451 public String toString()
452 {
453 return value;
454 }
455
456 public String getCanonical()
457 {
458 return canonical;
459 }
460
461 public boolean equals( Object o )
462 {
463 return ( o instanceof ComparableVersion ) && canonical.equals( ( (ComparableVersion) o ).canonical );
464 }
465
466 public int hashCode()
467 {
468 return canonical.hashCode();
469 }
470
471
472
473
474
475
476 public static void main( String... args )
477 {
478 System.out.println( "Display parameters as parsed by Maven (in canonical form) and comparison result:" );
479 if ( args.length == 0 )
480 {
481 return;
482 }
483
484 ComparableVersion prev = null;
485 int i = 1;
486 for ( String version : args )
487 {
488 ComparableVersion c = new ComparableVersion( version );
489
490 if ( prev != null )
491 {
492 int compare = prev.compareTo( c );
493 System.out.println( " " + prev.toString() + ' '
494 + ( ( compare == 0 ) ? "==" : ( ( compare < 0 ) ? "<" : ">" ) ) + ' ' + version );
495 }
496
497 System.out.println( String.valueOf( i++ ) + ". " + version + " == " + c.getCanonical() );
498
499 prev = c;
500 }
501 }
502 }