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<>();
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[0] );
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.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( "release", 0 );
202 QUALIFIERS.put( "", 0 );
203 QUALIFIERS.put( "sp", 1 );
204 }
205
206 private final String version;
207
208 private int index;
209
210 private String token;
211
212 private boolean number;
213
214 private boolean terminatedByNumber;
215
216 Tokenizer( String version )
217 {
218 this.version = ( version.length() > 0 ) ? version : "0";
219 }
220
221 public boolean next()
222 {
223 final int n = version.length();
224 if ( index >= n )
225 {
226 return false;
227 }
228
229 int state = -2;
230
231 int start = index;
232 int end = n;
233 terminatedByNumber = false;
234
235 for ( ; index < n; index++ )
236 {
237 char c = version.charAt( index );
238
239 if ( c == '.' || c == '-' || c == '_' )
240 {
241 end = index;
242 index++;
243 break;
244 }
245 else
246 {
247 int digit = Character.digit( c, 10 );
248 if ( digit >= 0 )
249 {
250 if ( state == -1 )
251 {
252 end = index;
253 terminatedByNumber = true;
254 break;
255 }
256 if ( state == 0 )
257 {
258
259 start++;
260 }
261 state = ( state > 0 || digit > 0 ) ? 1 : 0;
262 }
263 else
264 {
265 if ( state >= 0 )
266 {
267 end = index;
268 break;
269 }
270 state = -1;
271 }
272 }
273
274 }
275
276 if ( end - start > 0 )
277 {
278 token = version.substring( start, end );
279 number = state >= 0;
280 }
281 else
282 {
283 token = "0";
284 number = true;
285 }
286
287 return true;
288 }
289
290 @Override
291 public String toString()
292 {
293 return String.valueOf( token );
294 }
295
296 public Item toItem()
297 {
298 if ( number )
299 {
300 try
301 {
302 if ( token.length() < 10 )
303 {
304 return new Item( Item.KIND_INT, Integer.parseInt( token ) );
305 }
306 else
307 {
308 return new Item( Item.KIND_BIGINT, new BigInteger( token ) );
309 }
310 }
311 catch ( NumberFormatException e )
312 {
313 throw new IllegalStateException( e );
314 }
315 }
316 else
317 {
318 if ( index >= version.length() )
319 {
320 if ( "min".equalsIgnoreCase( token ) )
321 {
322 return Item.MIN;
323 }
324 else if ( "max".equalsIgnoreCase( token ) )
325 {
326 return Item.MAX;
327 }
328 }
329 if ( terminatedByNumber && token.length() == 1 )
330 {
331 switch ( token.charAt( 0 ) )
332 {
333 case 'a':
334 case 'A':
335 return new Item( Item.KIND_QUALIFIER, QUALIFIER_ALPHA );
336 case 'b':
337 case 'B':
338 return new Item( Item.KIND_QUALIFIER, QUALIFIER_BETA );
339 case 'm':
340 case 'M':
341 return new Item( Item.KIND_QUALIFIER, QUALIFIER_MILESTONE );
342 default:
343 }
344 }
345 Integer qualifier = QUALIFIERS.get( token );
346 if ( qualifier != null )
347 {
348 return new Item( Item.KIND_QUALIFIER, qualifier );
349 }
350 else
351 {
352 return new Item( Item.KIND_STRING, token.toLowerCase( Locale.ENGLISH ) );
353 }
354 }
355 }
356
357 }
358
359 static final class Item
360 {
361
362 static final int KIND_MAX = 8;
363
364 static final int KIND_BIGINT = 5;
365
366 static final int KIND_INT = 4;
367
368 static final int KIND_STRING = 3;
369
370 static final int KIND_QUALIFIER = 2;
371
372 static final int KIND_MIN = 0;
373
374 static final Item MAX = new Item( KIND_MAX, "max" );
375
376 static final Item MIN = new Item( KIND_MIN, "min" );
377
378 private final int kind;
379
380 private final Object value;
381
382 Item( int kind, Object value )
383 {
384 this.kind = kind;
385 this.value = value;
386 }
387
388 public boolean isNumber()
389 {
390 return ( kind & KIND_QUALIFIER ) == 0;
391 }
392
393 public int compareTo( Item that )
394 {
395 int rel;
396 if ( that == null )
397 {
398
399 switch ( kind )
400 {
401 case KIND_MIN:
402 rel = -1;
403 break;
404 case KIND_MAX:
405 case KIND_BIGINT:
406 case KIND_STRING:
407 rel = 1;
408 break;
409 case KIND_INT:
410 case KIND_QUALIFIER:
411 rel = (Integer) value;
412 break;
413 default:
414 throw new IllegalStateException( "unknown version item kind " + kind );
415 }
416 }
417 else
418 {
419 rel = kind - that.kind;
420 if ( rel == 0 )
421 {
422 switch ( kind )
423 {
424 case KIND_MAX:
425 case KIND_MIN:
426 break;
427 case KIND_BIGINT:
428 rel = ( (BigInteger) value ).compareTo( (BigInteger) that.value );
429 break;
430 case KIND_INT:
431 case KIND_QUALIFIER:
432 rel = ( (Integer) value ).compareTo( (Integer) that.value );
433 break;
434 case KIND_STRING:
435 rel = ( (String) value ).compareToIgnoreCase( (String) that.value );
436 break;
437 default:
438 throw new IllegalStateException( "unknown version item kind " + kind );
439 }
440 }
441 }
442 return rel;
443 }
444
445 @Override
446 public boolean equals( Object obj )
447 {
448 return ( obj instanceof Item ) && compareTo( (Item) obj ) == 0;
449 }
450
451 @Override
452 public int hashCode()
453 {
454 return value.hashCode() + kind * 31;
455 }
456
457 @Override
458 public String toString()
459 {
460 return String.valueOf( value );
461 }
462
463 }
464
465 }