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