1 package org.eclipse.aether.util.version;
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.List;
26 import java.util.Locale;
27 import java.util.Map;
28 import java.util.TreeMap;
29
30 import org.eclipse.aether.version.Version;
31
32
33
34
35
36 final class GenericVersion
37 implements Version
38 {
39
40 private final String version;
41
42 private final Item[] items;
43
44 private final int hash;
45
46
47
48
49
50
51 GenericVersion( String version )
52 {
53 this.version = version;
54 items = parse( version );
55 hash = Arrays.hashCode( items );
56 }
57
58 private static Item[] parse( String version )
59 {
60 List<Item> items = new ArrayList<Item>();
61
62 for ( Tokenizer tokenizer = new Tokenizer( version ); tokenizer.next(); )
63 {
64 Item item = tokenizer.toItem();
65 items.add( item );
66 }
67
68 trimPadding( items );
69
70 return items.toArray( new Item[items.size()] );
71 }
72
73 private static void trimPadding( List<Item> items )
74 {
75 Boolean number = null;
76 int end = items.size() - 1;
77 for ( int i = end; i > 0; i-- )
78 {
79 Item item = items.get( i );
80 if ( !Boolean.valueOf( item.isNumber() ).equals( number ) )
81 {
82 end = i;
83 number = item.isNumber();
84 }
85 if ( end == i && ( i == items.size() - 1 || items.get( i - 1 ).isNumber() == item.isNumber() )
86 && item.compareTo( null ) == 0 )
87 {
88 items.remove( i );
89 end--;
90 }
91 }
92 }
93
94 public int compareTo( Version obj )
95 {
96 final Item[] these = items;
97 final Item[] those = ( (GenericVersion) obj ).items;
98
99 boolean number = true;
100
101 for ( int index = 0;; index++ )
102 {
103 if ( index >= these.length && index >= those.length )
104 {
105 return 0;
106 }
107 else if ( index >= these.length )
108 {
109 return -comparePadding( those, index, null );
110 }
111 else if ( index >= those.length )
112 {
113 return comparePadding( these, index, null );
114 }
115
116 Item thisItem = these[index];
117 Item thatItem = those[index];
118
119 if ( thisItem.isNumber() != thatItem.isNumber() )
120 {
121 if ( number == thisItem.isNumber() )
122 {
123 return comparePadding( these, index, number );
124 }
125 else
126 {
127 return -comparePadding( those, index, number );
128 }
129 }
130 else
131 {
132 int rel = thisItem.compareTo( thatItem );
133 if ( rel != 0 )
134 {
135 return rel;
136 }
137 number = thisItem.isNumber();
138 }
139 }
140 }
141
142 private static int comparePadding( Item[] items, int index, Boolean number )
143 {
144 int rel = 0;
145 for ( int i = index; i < items.length; i++ )
146 {
147 Item item = items[i];
148 if ( number != null && number != item.isNumber() )
149 {
150 break;
151 }
152 rel = item.compareTo( null );
153 if ( rel != 0 )
154 {
155 break;
156 }
157 }
158 return rel;
159 }
160
161 @Override
162 public boolean equals( Object obj )
163 {
164 return ( obj instanceof GenericVersion ) && compareTo( (GenericVersion) obj ) == 0;
165 }
166
167 @Override
168 public int hashCode()
169 {
170 return hash;
171 }
172
173 @Override
174 public String toString()
175 {
176 return version;
177 }
178
179 static final class Tokenizer
180 {
181
182 private static final Integer QUALIFIER_ALPHA = -5;
183
184 private static final Integer QUALIFIER_BETA = -4;
185
186 private static final Integer QUALIFIER_MILESTONE = -3;
187
188 private static final Map<String, Integer> QUALIFIERS;
189
190 static
191 {
192 QUALIFIERS = new TreeMap<String, Integer>( String.CASE_INSENSITIVE_ORDER );
193 QUALIFIERS.put( "alpha", QUALIFIER_ALPHA );
194 QUALIFIERS.put( "beta", QUALIFIER_BETA );
195 QUALIFIERS.put( "milestone", QUALIFIER_MILESTONE );
196 QUALIFIERS.put( "cr", -2 );
197 QUALIFIERS.put( "rc", -2 );
198 QUALIFIERS.put( "snapshot", -1 );
199 QUALIFIERS.put( "ga", 0 );
200 QUALIFIERS.put( "final", 0 );
201 QUALIFIERS.put( "", 0 );
202 QUALIFIERS.put( "sp", 1 );
203 }
204
205 private final String version;
206
207 private int index;
208
209 private String token;
210
211 private boolean number;
212
213 private boolean terminatedByNumber;
214
215 Tokenizer( String version )
216 {
217 this.version = ( version.length() > 0 ) ? version : "0";
218 }
219
220 public boolean next()
221 {
222 final int n = version.length();
223 if ( index >= n )
224 {
225 return false;
226 }
227
228 int state = -2;
229
230 int start = index;
231 int end = n;
232 terminatedByNumber = false;
233
234 for ( ; index < n; index++ )
235 {
236 char c = version.charAt( index );
237
238 if ( c == '.' || c == '-' || c == '_' )
239 {
240 end = index;
241 index++;
242 break;
243 }
244 else
245 {
246 int digit = Character.digit( c, 10 );
247 if ( digit >= 0 )
248 {
249 if ( state == -1 )
250 {
251 end = index;
252 terminatedByNumber = true;
253 break;
254 }
255 if ( state == 0 )
256 {
257
258 start++;
259 }
260 state = ( state > 0 || digit > 0 ) ? 1 : 0;
261 }
262 else
263 {
264 if ( state >= 0 )
265 {
266 end = index;
267 break;
268 }
269 state = -1;
270 }
271 }
272
273 }
274
275 if ( end - start > 0 )
276 {
277 token = version.substring( start, end );
278 number = state >= 0;
279 }
280 else
281 {
282 token = "0";
283 number = true;
284 }
285
286 return true;
287 }
288
289 @Override
290 public String toString()
291 {
292 return String.valueOf( token );
293 }
294
295 public Item toItem()
296 {
297 if ( number )
298 {
299 try
300 {
301 if ( token.length() < 10 )
302 {
303 return new Item( Item.KIND_INT, Integer.parseInt( token ) );
304 }
305 else
306 {
307 return new Item( Item.KIND_BIGINT, new BigInteger( token ) );
308 }
309 }
310 catch ( NumberFormatException e )
311 {
312 throw new IllegalStateException( e );
313 }
314 }
315 else
316 {
317 if ( index >= version.length() )
318 {
319 if ( "min".equalsIgnoreCase( token ) )
320 {
321 return Item.MIN;
322 }
323 else if ( "max".equalsIgnoreCase( token ) )
324 {
325 return Item.MAX;
326 }
327 }
328 if ( terminatedByNumber && token.length() == 1 )
329 {
330 switch ( token.charAt( 0 ) )
331 {
332 case 'a':
333 case 'A':
334 return new Item( Item.KIND_QUALIFIER, QUALIFIER_ALPHA );
335 case 'b':
336 case 'B':
337 return new Item( Item.KIND_QUALIFIER, QUALIFIER_BETA );
338 case 'm':
339 case 'M':
340 return new Item( Item.KIND_QUALIFIER, QUALIFIER_MILESTONE );
341 default:
342 }
343 }
344 Integer qualifier = QUALIFIERS.get( token );
345 if ( qualifier != null )
346 {
347 return new Item( Item.KIND_QUALIFIER, qualifier );
348 }
349 else
350 {
351 return new Item( Item.KIND_STRING, token.toLowerCase( Locale.ENGLISH ) );
352 }
353 }
354 }
355
356 }
357
358 static final class Item
359 {
360
361 static final int KIND_MAX = 8;
362
363 static final int KIND_BIGINT = 5;
364
365 static final int KIND_INT = 4;
366
367 static final int KIND_STRING = 3;
368
369 static final int KIND_QUALIFIER = 2;
370
371 static final int KIND_MIN = 0;
372
373 static final Item MAX = new Item( KIND_MAX, "max" );
374
375 static final Item MIN = new Item( KIND_MIN, "min" );
376
377 private final int kind;
378
379 private final Object value;
380
381 Item( int kind, Object value )
382 {
383 this.kind = kind;
384 this.value = value;
385 }
386
387 public boolean isNumber()
388 {
389 return ( kind & KIND_QUALIFIER ) == 0;
390 }
391
392 public int compareTo( Item that )
393 {
394 int rel;
395 if ( that == null )
396 {
397
398 switch ( kind )
399 {
400 case KIND_MIN:
401 rel = -1;
402 break;
403 case KIND_MAX:
404 case KIND_BIGINT:
405 case KIND_STRING:
406 rel = 1;
407 break;
408 case KIND_INT:
409 case KIND_QUALIFIER:
410 rel = (Integer) value;
411 break;
412 default:
413 throw new IllegalStateException( "unknown version item kind " + kind );
414 }
415 }
416 else
417 {
418 rel = kind - that.kind;
419 if ( rel == 0 )
420 {
421 switch ( kind )
422 {
423 case KIND_MAX:
424 case KIND_MIN:
425 break;
426 case KIND_BIGINT:
427 rel = ( (BigInteger) value ).compareTo( (BigInteger) that.value );
428 break;
429 case KIND_INT:
430 case KIND_QUALIFIER:
431 rel = ( (Integer) value ).compareTo( (Integer) that.value );
432 break;
433 case KIND_STRING:
434 rel = ( (String) value ).compareToIgnoreCase( (String) that.value );
435 break;
436 default:
437 throw new IllegalStateException( "unknown version item kind " + kind );
438 }
439 }
440 }
441 return rel;
442 }
443
444 @Override
445 public boolean equals( Object obj )
446 {
447 return ( obj instanceof Item ) && compareTo( (Item) obj ) == 0;
448 }
449
450 @Override
451 public int hashCode()
452 {
453 return value.hashCode() + kind * 31;
454 }
455
456 @Override
457 public String toString()
458 {
459 return String.valueOf( value );
460 }
461
462 }
463
464 }