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 Integer.compare(value, itemValue);
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 Long.compare(value, itemValue);
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 (buffer.length() > 0) {
505                     buffer.append((item instanceof ListItem) ? '-' : '.');
506                 }
507                 buffer.append(item);
508             }
509             return buffer.toString();
510         }
511 
512         
513 
514 
515         private String toListString() {
516             StringBuilder buffer = new StringBuilder();
517             buffer.append("[");
518             for (Item item : this) {
519                 if (buffer.length() > 1) {
520                     buffer.append(", ");
521                 }
522                 if (item instanceof ListItem) {
523                     buffer.append(((ListItem) item).toListString());
524                 } else {
525                     buffer.append(item);
526                 }
527             }
528             buffer.append("]");
529             return buffer.toString();
530         }
531     }
532 
533     public ComparableVersion(String version) {
534         parseVersion(version);
535     }
536 
537     @SuppressWarnings("checkstyle:innerassignment")
538     public final void parseVersion(String version) {
539         this.value = version;
540 
541         items = new ListItem();
542 
543         version = version.toLowerCase(Locale.ENGLISH);
544 
545         ListItem list = items;
546 
547         Deque<Item> stack = new ArrayDeque<>();
548         stack.push(list);
549 
550         boolean isDigit = false;
551 
552         int startIndex = 0;
553 
554         for (int i = 0; i < version.length(); i++) {
555             char c = version.charAt(i);
556 
557             if (c == '.') {
558                 if (i == startIndex) {
559                     list.add(IntItem.ZERO);
560                 } else {
561                     list.add(parseItem(isDigit, version.substring(startIndex, i)));
562                 }
563                 startIndex = i + 1;
564             } else if (c == '-') {
565                 if (i == startIndex) {
566                     list.add(IntItem.ZERO);
567                 } else {
568                     list.add(parseItem(isDigit, version.substring(startIndex, i)));
569                 }
570                 startIndex = i + 1;
571 
572                 list.add(list = new ListItem());
573                 stack.push(list);
574             } else if (Character.isDigit(c)) {
575                 if (!isDigit && i > startIndex) {
576                     
577                     
578                     if (!list.isEmpty()) {
579                         list.add(list = new ListItem());
580                         stack.push(list);
581                     }
582 
583                     list.add(new StringItem(version.substring(startIndex, i), true));
584                     startIndex = i;
585 
586                     list.add(list = new ListItem());
587                     stack.push(list);
588                 }
589 
590                 isDigit = true;
591             } else {
592                 if (isDigit && i > startIndex) {
593                     list.add(parseItem(true, version.substring(startIndex, i)));
594                     startIndex = i;
595 
596                     list.add(list = new ListItem());
597                     stack.push(list);
598                 }
599 
600                 isDigit = false;
601             }
602         }
603 
604         if (version.length() > startIndex) {
605             
606             
607             if (!isDigit && !list.isEmpty()) {
608                 list.add(list = new ListItem());
609                 stack.push(list);
610             }
611 
612             list.add(parseItem(isDigit, version.substring(startIndex)));
613         }
614 
615         while (!stack.isEmpty()) {
616             list = (ListItem) stack.pop();
617             list.normalize();
618         }
619     }
620 
621     private static Item parseItem(boolean isDigit, String buf) {
622         if (isDigit) {
623             buf = stripLeadingZeroes(buf);
624             if (buf.length() <= MAX_INTITEM_LENGTH) {
625                 
626                 return new IntItem(buf);
627             } else if (buf.length() <= MAX_LONGITEM_LENGTH) {
628                 
629                 return new LongItem(buf);
630             }
631             return new BigIntegerItem(buf);
632         }
633         return new StringItem(buf, false);
634     }
635 
636     private static String stripLeadingZeroes(String buf) {
637         if (buf == null || buf.isEmpty()) {
638             return "0";
639         }
640         for (int i = 0; i < buf.length(); ++i) {
641             char c = buf.charAt(i);
642             if (c != '0') {
643                 return buf.substring(i);
644             }
645         }
646         return buf;
647     }
648 
649     @Override
650     public int compareTo(ComparableVersion o) {
651         return items.compareTo(o.items);
652     }
653 
654     @Override
655     public String toString() {
656         return value;
657     }
658 
659     public String getCanonical() {
660         if (canonical == null) {
661             canonical = items.toString();
662         }
663         return canonical;
664     }
665 
666     @Override
667     public boolean equals(Object o) {
668         return (o instanceof ComparableVersion) && items.equals(((ComparableVersion) o).items);
669     }
670 
671     @Override
672     public int hashCode() {
673         return items.hashCode();
674     }
675 
676     
677     
678 
679 
680 
681 
682 
683 
684 
685 
686 
687 
688 
689 
690 
691 
692 
693     
694     public static void main(String... args) {
695         System.out.println("Display parameters as parsed by Maven (in canonical form and as a list of tokens) and"
696                 + " comparison result:");
697         if (args.length == 0) {
698             return;
699         }
700 
701         ComparableVersion prev = null;
702         int i = 1;
703         for (String version : args) {
704             ComparableVersion c = new ComparableVersion(version);
705 
706             if (prev != null) {
707                 int compare = prev.compareTo(c);
708                 System.out.println("   " + prev.toString() + ' ' + ((compare == 0) ? "==" : ((compare < 0) ? "<" : ">"))
709                         + ' ' + version);
710             }
711 
712             System.out.println(
713                     (i++) + ". " + version + " -> " + c.getCanonical() + "; tokens: " + c.items.toListString());
714 
715             prev = c;
716         }
717     }
718 }