1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.artifact.versioning;
20
21 import java.math.BigInteger;
22 import java.util.ArrayDeque;
23 import java.util.ArrayList;
24 import java.util.Arrays;
25 import java.util.Deque;
26 import java.util.Iterator;
27 import java.util.List;
28 import java.util.Locale;
29 import java.util.Properties;
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
63
64
65 public class ComparableVersion implements Comparable<ComparableVersion> {
66 private static final int MAX_INTITEM_LENGTH = 9;
67
68 private static final int MAX_LONGITEM_LENGTH = 18;
69
70 private String value;
71
72 private String canonical;
73
74 private ListItem items;
75
76 private interface Item {
77 int INT_ITEM = 3;
78 int LONG_ITEM = 4;
79 int BIGINTEGER_ITEM = 0;
80 int STRING_ITEM = 1;
81 int LIST_ITEM = 2;
82
83 int compareTo(Item item);
84
85 int getType();
86
87 boolean isNull();
88 }
89
90
91
92
93 private static class IntItem implements Item {
94 private final int value;
95
96 public static final IntItem ZERO = new IntItem();
97
98 private IntItem() {
99 this.value = 0;
100 }
101
102 IntItem(String str) {
103 this.value = Integer.parseInt(str);
104 }
105
106 @Override
107 public int getType() {
108 return INT_ITEM;
109 }
110
111 @Override
112 public boolean isNull() {
113 return value == 0;
114 }
115
116 @Override
117 public int compareTo(Item item) {
118 if (item == null) {
119 return (value == 0) ? 0 : 1;
120 }
121
122 switch (item.getType()) {
123 case INT_ITEM:
124 int itemValue = ((IntItem) item).value;
125 return (value < itemValue) ? -1 : ((value == itemValue) ? 0 : 1);
126 case LONG_ITEM:
127 case BIGINTEGER_ITEM:
128 return -1;
129
130 case STRING_ITEM:
131 return 1;
132
133 case LIST_ITEM:
134 return 1;
135
136 default:
137 throw new IllegalStateException("invalid item: " + item.getClass());
138 }
139 }
140
141 @Override
142 public boolean equals(Object o) {
143 if (this == o) {
144 return true;
145 }
146 if (o == null || getClass() != o.getClass()) {
147 return false;
148 }
149
150 IntItem intItem = (IntItem) o;
151
152 return value == intItem.value;
153 }
154
155 @Override
156 public int hashCode() {
157 return value;
158 }
159
160 @Override
161 public String toString() {
162 return Integer.toString(value);
163 }
164 }
165
166
167
168
169 private static class LongItem implements Item {
170 private final long value;
171
172 LongItem(String str) {
173 this.value = Long.parseLong(str);
174 }
175
176 @Override
177 public int getType() {
178 return LONG_ITEM;
179 }
180
181 @Override
182 public boolean isNull() {
183 return value == 0;
184 }
185
186 @Override
187 public int compareTo(Item item) {
188 if (item == null) {
189 return (value == 0) ? 0 : 1;
190 }
191
192 switch (item.getType()) {
193 case INT_ITEM:
194 return 1;
195 case LONG_ITEM:
196 long itemValue = ((LongItem) item).value;
197 return (value < itemValue) ? -1 : ((value == itemValue) ? 0 : 1);
198 case BIGINTEGER_ITEM:
199 return -1;
200
201 case STRING_ITEM:
202 return 1;
203
204 case LIST_ITEM:
205 return 1;
206
207 default:
208 throw new IllegalStateException("invalid item: " + item.getClass());
209 }
210 }
211
212 @Override
213 public boolean equals(Object o) {
214 if (this == o) {
215 return true;
216 }
217 if (o == null || getClass() != o.getClass()) {
218 return false;
219 }
220
221 LongItem longItem = (LongItem) o;
222
223 return value == longItem.value;
224 }
225
226 @Override
227 public int hashCode() {
228 return (int) (value ^ (value >>> 32));
229 }
230
231 @Override
232 public String toString() {
233 return Long.toString(value);
234 }
235 }
236
237
238
239
240 private static class BigIntegerItem implements Item {
241 private final BigInteger value;
242
243 BigIntegerItem(String str) {
244 this.value = new BigInteger(str);
245 }
246
247 @Override
248 public int getType() {
249 return BIGINTEGER_ITEM;
250 }
251
252 @Override
253 public boolean isNull() {
254 return BigInteger.ZERO.equals(value);
255 }
256
257 @Override
258 public int compareTo(Item item) {
259 if (item == null) {
260 return BigInteger.ZERO.equals(value) ? 0 : 1;
261 }
262
263 switch (item.getType()) {
264 case INT_ITEM:
265 case LONG_ITEM:
266 return 1;
267
268 case BIGINTEGER_ITEM:
269 return value.compareTo(((BigIntegerItem) item).value);
270
271 case STRING_ITEM:
272 return 1;
273
274 case LIST_ITEM:
275 return 1;
276
277 default:
278 throw new IllegalStateException("invalid item: " + item.getClass());
279 }
280 }
281
282 @Override
283 public boolean equals(Object o) {
284 if (this == o) {
285 return true;
286 }
287 if (o == null || getClass() != o.getClass()) {
288 return false;
289 }
290
291 BigIntegerItem that = (BigIntegerItem) o;
292
293 return value.equals(that.value);
294 }
295
296 @Override
297 public int hashCode() {
298 return value.hashCode();
299 }
300
301 public String toString() {
302 return value.toString();
303 }
304 }
305
306
307
308
309 private static class StringItem implements Item {
310 private static final List<String> QUALIFIERS =
311 Arrays.asList("alpha", "beta", "milestone", "rc", "snapshot", "", "sp");
312
313 private static final Properties ALIASES = new Properties();
314
315 static {
316 ALIASES.put("ga", "");
317 ALIASES.put("final", "");
318 ALIASES.put("release", "");
319 ALIASES.put("cr", "rc");
320 }
321
322
323
324
325
326 private static final String RELEASE_VERSION_INDEX = String.valueOf(QUALIFIERS.indexOf(""));
327
328 private final String value;
329
330 StringItem(String value, boolean followedByDigit) {
331 if (followedByDigit && value.length() == 1) {
332
333 switch (value.charAt(0)) {
334 case 'a':
335 value = "alpha";
336 break;
337 case 'b':
338 value = "beta";
339 break;
340 case 'm':
341 value = "milestone";
342 break;
343 default:
344 }
345 }
346 this.value = ALIASES.getProperty(value, value);
347 }
348
349 @Override
350 public int getType() {
351 return STRING_ITEM;
352 }
353
354 @Override
355 public boolean isNull() {
356 return (comparableQualifier(value).compareTo(RELEASE_VERSION_INDEX) == 0);
357 }
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372 public static String comparableQualifier(String qualifier) {
373 int i = QUALIFIERS.indexOf(qualifier);
374
375 return i == -1 ? (QUALIFIERS.size() + "-" + qualifier) : String.valueOf(i);
376 }
377
378 @Override
379 public int compareTo(Item item) {
380 if (item == null) {
381
382 return comparableQualifier(value).compareTo(RELEASE_VERSION_INDEX);
383 }
384 switch (item.getType()) {
385 case INT_ITEM:
386 case LONG_ITEM:
387 case BIGINTEGER_ITEM:
388 return -1;
389
390 case STRING_ITEM:
391 return comparableQualifier(value).compareTo(comparableQualifier(((StringItem) item).value));
392
393 case LIST_ITEM:
394 return -1;
395
396 default:
397 throw new IllegalStateException("invalid item: " + item.getClass());
398 }
399 }
400
401 @Override
402 public boolean equals(Object o) {
403 if (this == o) {
404 return true;
405 }
406 if (o == null || getClass() != o.getClass()) {
407 return false;
408 }
409
410 StringItem that = (StringItem) o;
411
412 return value.equals(that.value);
413 }
414
415 @Override
416 public int hashCode() {
417 return value.hashCode();
418 }
419
420 public String toString() {
421 return value;
422 }
423 }
424
425
426
427
428
429 private static class ListItem extends ArrayList<Item> implements Item {
430 @Override
431 public int getType() {
432 return LIST_ITEM;
433 }
434
435 @Override
436 public boolean isNull() {
437 return (size() == 0);
438 }
439
440 void normalize() {
441 for (int i = size() - 1; i >= 0; i--) {
442 Item lastItem = get(i);
443
444 if (lastItem.isNull()) {
445
446 remove(i);
447 } else if (!(lastItem instanceof ListItem)) {
448 break;
449 }
450 }
451 }
452
453 @Override
454 public int compareTo(Item item) {
455 if (item == null) {
456 if (size() == 0) {
457 return 0;
458 }
459
460 for (Item i : this) {
461 int result = i.compareTo(null);
462 if (result != 0) {
463 return result;
464 }
465 }
466 return 0;
467 }
468 switch (item.getType()) {
469 case INT_ITEM:
470 case LONG_ITEM:
471 case BIGINTEGER_ITEM:
472 return -1;
473
474 case STRING_ITEM:
475 return 1;
476
477 case LIST_ITEM:
478 Iterator<Item> left = iterator();
479 Iterator<Item> right = ((ListItem) item).iterator();
480
481 while (left.hasNext() || right.hasNext()) {
482 Item l = left.hasNext() ? left.next() : null;
483 Item r = right.hasNext() ? right.next() : null;
484
485
486 int result = l == null ? (r == null ? 0 : -1 * r.compareTo(l)) : l.compareTo(r);
487
488 if (result != 0) {
489 return result;
490 }
491 }
492
493 return 0;
494
495 default:
496 throw new IllegalStateException("invalid item: " + item.getClass());
497 }
498 }
499
500 @Override
501 public String toString() {
502 StringBuilder buffer = new StringBuilder();
503 for (Item item : this) {
504 if (item instanceof ListItem) {
505
506
507 buffer.append('-');
508 } else if (buffer.length() > 0) {
509 buffer.append('.');
510 }
511 buffer.append(item);
512 }
513 return buffer.toString();
514 }
515
516
517
518
519 private String toListString() {
520 StringBuilder buffer = new StringBuilder();
521 buffer.append("[");
522 for (Item item : this) {
523 if (buffer.length() > 1) {
524 buffer.append(", ");
525 }
526 if (item instanceof ListItem) {
527 buffer.append(((ListItem) item).toListString());
528 } else {
529 buffer.append(item);
530 }
531 }
532 buffer.append("]");
533 return buffer.toString();
534 }
535 }
536
537 public ComparableVersion(String version) {
538 parseVersion(version);
539 }
540
541 @SuppressWarnings("checkstyle:innerassignment")
542 public final void parseVersion(String version) {
543 this.value = version;
544
545 items = new ListItem();
546
547 version = version.toLowerCase(Locale.ENGLISH);
548
549 ListItem list = items;
550
551 Deque<Item> stack = new ArrayDeque<>();
552 stack.push(list);
553
554 boolean isDigit = false;
555
556 int startIndex = 0;
557
558 for (int i = 0; i < version.length(); i++) {
559 char c = version.charAt(i);
560
561 if (c == '.') {
562 if (i == startIndex) {
563 list.add(IntItem.ZERO);
564 } else {
565 list.add(parseItem(isDigit, version.substring(startIndex, i)));
566 }
567 startIndex = i + 1;
568 } else if (c == '-') {
569 if (i == startIndex) {
570 list.add(IntItem.ZERO);
571 } else {
572 list.add(parseItem(isDigit, version.substring(startIndex, i)));
573 }
574 startIndex = i + 1;
575
576 list.add(list = new ListItem());
577 stack.push(list);
578 } else if (Character.isDigit(c)) {
579 if (!isDigit && i > startIndex) {
580
581
582 if (!list.isEmpty()) {
583 list.add(list = new ListItem());
584 stack.push(list);
585 }
586
587 list.add(new StringItem(version.substring(startIndex, i), true));
588 startIndex = i;
589
590 list.add(list = new ListItem());
591 stack.push(list);
592 }
593
594 isDigit = true;
595 } else {
596 if (isDigit && i > startIndex) {
597 list.add(parseItem(true, version.substring(startIndex, i)));
598 startIndex = i;
599
600 list.add(list = new ListItem());
601 stack.push(list);
602 }
603
604 isDigit = false;
605 }
606 }
607
608 if (version.length() > startIndex) {
609
610
611 if (!isDigit && !list.isEmpty()) {
612 list.add(list = new ListItem());
613 stack.push(list);
614 }
615
616 list.add(parseItem(isDigit, version.substring(startIndex)));
617 }
618
619 while (!stack.isEmpty()) {
620 list = (ListItem) stack.pop();
621 list.normalize();
622 }
623 }
624
625 private static Item parseItem(boolean isDigit, String buf) {
626 if (isDigit) {
627 buf = stripLeadingZeroes(buf);
628 if (buf.length() <= MAX_INTITEM_LENGTH) {
629
630 return new IntItem(buf);
631 } else if (buf.length() <= MAX_LONGITEM_LENGTH) {
632
633 return new LongItem(buf);
634 }
635 return new BigIntegerItem(buf);
636 }
637 return new StringItem(buf, false);
638 }
639
640 private static String stripLeadingZeroes(String buf) {
641 if (buf == null || buf.isEmpty()) {
642 return "0";
643 }
644 for (int i = 0; i < buf.length(); ++i) {
645 char c = buf.charAt(i);
646 if (c != '0') {
647 return buf.substring(i);
648 }
649 }
650 return buf;
651 }
652
653 @Override
654 public int compareTo(ComparableVersion o) {
655 return items.compareTo(o.items);
656 }
657
658 @Override
659 public String toString() {
660 return value;
661 }
662
663 public String getCanonical() {
664 if (canonical == null) {
665 canonical = items.toString();
666 }
667 return canonical;
668 }
669
670 @Override
671 public boolean equals(Object o) {
672 return (o instanceof ComparableVersion) && items.equals(((ComparableVersion) o).items);
673 }
674
675 @Override
676 public int hashCode() {
677 return items.hashCode();
678 }
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697 public static void main(String... args) {
698 System.out.println("Display parameters as parsed by Maven (in canonical form and as a list of tokens) and"
699 + " comparison result:");
700 if (args.length == 0) {
701 return;
702 }
703
704 ComparableVersion prev = null;
705 int i = 1;
706 for (String version : args) {
707 ComparableVersion c = new ComparableVersion(version);
708
709 if (prev != null) {
710 int compare = prev.compareTo(c);
711 System.out.println(" " + prev.toString() + ' ' + ((compare == 0) ? "==" : ((compare < 0) ? "<" : ">"))
712 + ' ' + version);
713 }
714
715 System.out.println(
716 (i++) + ". " + version + " -> " + c.getCanonical() + "; tokens: " + c.items.toListString());
717
718 prev = c;
719 }
720 }
721
722 }