1. Project Clover database Tue Dec 20 2016 21:24:09 CET
  2. Package org.xwiki.extension.version.internal

File DefaultVersion.java

 

Coverage histogram

../../../../../img/srcFileCovDistChart9.png
38% of files have more coverage

Code metrics

74
182
33
4
655
389
82
0.45
5.52
8.25
2.48

Classes

Class Line # Actions
DefaultVersion 50 79 0% 41 18
0.868613186.9%
DefaultVersion.Tokenizer 85 34 0% 17 6
0.8983050689.8%
DefaultVersion.Element 198 69 0% 24 12
0.8709677587.1%
DefaultVersion.Element.ElementType 210 0 - 0 0
-1.0 -
 

Contributing tests

This file is covered by 104 tests. .

Source view

1    /*
2    * See the NOTICE file distributed with this work for additional
3    * information regarding copyright ownership.
4    *
5    * This is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU Lesser General Public License as
7    * published by the Free Software Foundation; either version 2.1 of
8    * the License, or (at your option) any later version.
9    *
10    * This software is distributed in the hope that it will be useful,
11    * but WITHOUT ANY WARRANTY; without even the implied warranty of
12    * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13    * Lesser General Public License for more details.
14    *
15    * You should have received a copy of the GNU Lesser General Public
16    * License along with this software; if not, write to the Free
17    * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
18    * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
19    */
20    package org.xwiki.extension.version.internal;
21   
22    import java.io.IOException;
23    import java.io.ObjectInputStream;
24    import java.io.ObjectOutputStream;
25    import java.util.ArrayList;
26    import java.util.HashMap;
27    import java.util.Iterator;
28    import java.util.List;
29    import java.util.ListIterator;
30    import java.util.Locale;
31    import java.util.Map;
32   
33    import org.apache.commons.lang3.builder.HashCodeBuilder;
34    import org.slf4j.Logger;
35    import org.slf4j.LoggerFactory;
36    import org.xwiki.extension.version.Version;
37   
38    /**
39    * Default implementation of {@link Version}. Note each repositories generally provide their own implementation based on
40    * their own version standard.
41    * <p>
42    * Based on AETHER rules which is itself based on Maven specifications.
43    * <p>
44    * org.sonatype.aether.util.version.GenericVersion has been rewritten because it's impossible to extends it or even
45    * access its details to properly implements {@link #getType()} for example.
46    *
47    * @version $Id: 552dbc63ba69efb10a94e0dbdb7b9780ac937013 $
48    * @since 4.0M1
49    */
 
50    public class DefaultVersion implements Version
51    {
52    /**
53    * Serialization identifier.
54    */
55    private static final long serialVersionUID = 1L;
56   
57    private static final String MAX_INTEGER_STRING = String.valueOf(Integer.MAX_VALUE);
58   
59    private static final int MAX_INTEGER_LENGTH = MAX_INTEGER_STRING.length();
60   
61    private static final Logger LOGGER = LoggerFactory.getLogger(DefaultVersion.class);
62   
63    /**
64    * The original version string representation.
65    */
66    private String rawVersion;
67   
68    /**
69    * The version cut in peaces for easier comparison.
70    */
71    private List<Element> elements;
72   
73    /**
74    * @see #getType()
75    */
76    private Type type = Type.STABLE;
77   
78    private int hashCode = -1;
79   
80    /**
81    * Used to parse the string representation of the version.
82    *
83    * @version $Id: 552dbc63ba69efb10a94e0dbdb7b9780ac937013 $
84    */
 
85    static final class Tokenizer
86    {
87    /**
88    * The string representation of the version.
89    */
90    private final String rawVersion;
91   
92    /**
93    * The current index in the parsed version.
94    */
95    private int index;
96   
97    /**
98    * @see #isNumber()
99    */
100    private boolean number;
101   
102    /**
103    * @see #getToken()
104    */
105    private String token;
106   
107    /**
108    * @param rawVersion the string representation of the version
109    */
 
110  737025 toggle public Tokenizer(String rawVersion)
111    {
112  737025 this.rawVersion = (rawVersion.length() > 0) ? rawVersion : "0";
113    }
114   
115    /**
116    * @return the token
117    */
 
118  2045671 toggle public String getToken()
119    {
120  2045671 return this.token;
121    }
122   
123    /**
124    * @return indicate if the token is a number
125    */
 
126  2045671 toggle public boolean isNumber()
127    {
128  2045671 return this.number;
129    }
130   
131    /**
132    * @return move to the next token
133    */
 
134  2782696 toggle public boolean next()
135    {
136  2782696 final int n = this.rawVersion.length();
137  2782696 if (this.index >= n) {
138  737025 return false;
139    }
140   
141  2045671 int state = -2;
142   
143  2045671 int start = this.index;
144  2045671 int end = n;
145   
146  4856701 for (; this.index < n; this.index++) {
147  4119676 char c = this.rawVersion.charAt(this.index);
148   
149  4119676 if (c == '.' || c == '-') {
150  1289942 end = this.index;
151  1289942 this.index++;
152  1289942 break;
153    } else {
154  2829734 int digit = Character.digit(c, 10);
155  2829734 if (digit >= 0) {
156  2206173 if (state == -1) {
157  8103 end = this.index;
158  8103 break;
159    }
160  2198070 if (state == 0) {
161    // normalize numbers and strip leading zeros
162  6138 start++;
163    }
164  2198070 state = (state > 0 || digit > 0) ? 1 : 0;
165    } else {
166  623561 if (state >= 0) {
167  10601 end = this.index;
168  10601 break;
169    }
170  612960 state = -1;
171    }
172    }
173    }
174   
175  2045671 if (start < end) {
176  2045671 this.token = this.rawVersion.substring(start, end);
177  2045671 this.number = state >= 0;
178    } else {
179  0 this.token = "0";
180  0 this.number = true;
181    }
182   
183  2045671 return true;
184    }
185   
 
186  0 toggle @Override
187    public String toString()
188    {
189  0 return this.token;
190    }
191    }
192   
193    /**
194    * A peace of the version.
195    *
196    * @version $Id: 552dbc63ba69efb10a94e0dbdb7b9780ac937013 $
197    */
 
198    static final class Element implements Comparable<Element>
199    {
200    /**
201    * Message used in the exception produced when one of the {@link ElementType} is unknown.
202    */
203    private static final String ERROR_UNKNOWNKIND = "Unknown version element kind ";
204   
205    /**
206    * The kind of element.
207    *
208    * @version $Id: 552dbc63ba69efb10a94e0dbdb7b9780ac937013 $
209    */
 
210    enum ElementType
211    {
212    /**
213    * A known qualifier id.
214    */
215    QUALIFIER,
216   
217    /**
218    * An integer.
219    */
220    INT,
221   
222    /**
223    * An unknown literal string.
224    */
225    STRING
226    }
227   
228    /**
229    * The list of known qualifiers.
230    */
231    private static final Map<String, Integer> QUALIFIERS;
232   
 
233  57 toggle static {
234  57 QUALIFIERS = new HashMap<String, Integer>();
235  57 QUALIFIERS.put("alpha", Integer.valueOf(-5));
236  57 QUALIFIERS.put("a", Integer.valueOf(-5));
237  57 QUALIFIERS.put("beta", Integer.valueOf(-4));
238  57 QUALIFIERS.put("b", Integer.valueOf(-4));
239  57 QUALIFIERS.put("milestone", Integer.valueOf(-3));
240  57 QUALIFIERS.put("cr", Integer.valueOf(-2));
241  57 QUALIFIERS.put("rc", Integer.valueOf(-2));
242  57 QUALIFIERS.put("snapshot", Integer.valueOf(-1));
243  57 QUALIFIERS.put("ga", Integer.valueOf(0));
244  57 QUALIFIERS.put("final", Integer.valueOf(0));
245  57 QUALIFIERS.put("", Integer.valueOf(0));
246  57 QUALIFIERS.put("sp", Integer.valueOf(1));
247    }
248   
249    /**
250    * The kind of element.
251    */
252    private final ElementType elementType;
253   
254    /**
255    * The value of the element.
256    */
257    private final Object value;
258   
259    /**
260    * @see #getVersionType()
261    */
262    private Type versionType = Type.STABLE;
263   
 
264  1935408 toggle private boolean isInteger(String number)
265    {
266  1935408 return number.length() < MAX_INTEGER_LENGTH
267    || (number.length() == MAX_INTEGER_LENGTH && MAX_INTEGER_STRING.compareTo(number) >= 0);
268    }
269   
 
270  0 toggle public Element(String token)
271    {
272  0 this.elementType = ElementType.STRING;
273  0 this.value = token;
274    }
275   
276    /**
277    * @param tokenizer the token from which to create the version element
278    */
 
279  2045671 toggle public Element(Tokenizer tokenizer)
280    {
281  2045671 String token = tokenizer.getToken();
282  2045671 if (tokenizer.isNumber()) {
283  1935408 if (isInteger(token)) {
284  1935355 try {
285  1935355 this.elementType = ElementType.INT;
286  1935355 this.value = Integer.valueOf(token);
287    } catch (NumberFormatException e) {
288  0 throw new IllegalStateException(e);
289    }
290    } else {
291  53 this.elementType = ElementType.STRING;
292  53 this.value = token;
293    }
294    } else {
295  110263 String lowerCaseToken = token.toLowerCase(Locale.ENGLISH);
296  110263 Integer qualifier = QUALIFIERS.get(lowerCaseToken);
297  110263 if (qualifier != null) {
298  63824 this.elementType = ElementType.QUALIFIER;
299  63824 this.value = qualifier;
300  63824 if (qualifier.intValue() == -1) {
301  29374 this.versionType = Type.SNAPSHOT;
302  34450 } else if (qualifier < 0) {
303  3147 this.versionType = Type.BETA;
304    }
305    } else {
306  46439 this.elementType = ElementType.STRING;
307  46439 this.value = lowerCaseToken;
308    }
309    }
310    }
311   
312    /**
313    * @return indicate of the element is a number
314    */
 
315  10823 toggle public boolean isNumber()
316    {
317  10823 return this.elementType == ElementType.INT || this.elementType == ElementType.QUALIFIER;
318    }
319   
320    /**
321    * @return the type of the version element
322    */
 
323  2078192 toggle public Type getVersionType()
324    {
325  2078192 return this.versionType;
326    }
327   
 
328  883757 toggle @Override
329    public int compareTo(Element that)
330    {
331  883757 int rel;
332   
333  883757 if (that == null) {
334    // null in this context denotes the pad element (0 or "ga")
335  880173 switch (this.elementType) {
336  31351 case STRING:
337  31351 rel = 1;
338  31351 break;
339  788816 case INT:
340  60006 case QUALIFIER:
341  848822 rel = (Integer) this.value;
342  848822 break;
343  0 default:
344  0 throw new IllegalStateException(ERROR_UNKNOWNKIND + this.elementType);
345    }
346    } else {
347  3584 rel = this.elementType.compareTo(that.elementType);
348  3584 if (rel == 0) {
349  3583 switch (this.elementType) {
350  2888 case INT:
351  184 case QUALIFIER:
352  3072 rel = (Integer) this.value - (Integer) that.value;
353  3072 break;
354  511 case STRING:
355  511 rel = ((String) this.value).compareToIgnoreCase((String) that.value);
356  511 break;
357  0 default:
358  0 throw new IllegalStateException(ERROR_UNKNOWNKIND + this.elementType);
359    }
360    }
361    }
362   
363  883757 return rel;
364    }
365   
 
366  0 toggle @Override
367    public boolean equals(Object obj)
368    {
369  0 return (obj instanceof Element) && compareTo((Element) obj) == 0;
370    }
371   
 
372  1900991 toggle @Override
373    public int hashCode()
374    {
375  1900991 HashCodeBuilder builder = new HashCodeBuilder();
376   
377  1900991 builder.append(this.value);
378  1900991 builder.append(this.elementType);
379   
380  1900991 return builder.toHashCode();
381    }
382   
 
383  0 toggle @Override
384    public String toString()
385    {
386  0 return this.value.toString();
387    }
388   
389    }
390   
391    /**
392    * @param rawVersion the original string representation of the version
393    */
 
394  752517 toggle public DefaultVersion(String rawVersion)
395    {
396  752517 setVersion(rawVersion);
397    }
398   
399    /**
400    * Create a new {@link DefaultVersion} by cloning the provided version.
401    *
402    * @param version the version to copy
403    */
 
404  0 toggle public DefaultVersion(Version version)
405    {
406  0 this(version.getValue());
407    }
408   
409    /**
410    * Make sure the version has been parsed.
411    */
 
412  741268 toggle private void initElements()
413    {
414  741268 if (this.elements == null) {
415  737025 parse();
416    }
417    }
418   
419    /**
420    * @param rawVersion the string representation to parse
421    */
 
422  752523 toggle private void setVersion(String rawVersion)
423    {
424  752523 this.rawVersion = rawVersion;
425    }
426   
427    /**
428    * Parse the string representation of the version into separated elements.
429    */
 
430  737025 toggle private void parse()
431    {
432  737025 this.elements = new ArrayList<Element>();
433   
434  737025 try {
435  2782696 for (Tokenizer tokenizer = new Tokenizer(this.rawVersion); tokenizer.next();) {
436  2045671 Element element = new Element(tokenizer);
437  2045671 this.elements.add(element);
438  2045671 if (element.getVersionType() != Type.STABLE) {
439  32521 this.type = element.getVersionType();
440    }
441    }
442   
443  737025 trimPadding(this.elements);
444    } catch (Exception e) {
445    // Make sure to never fail no matter what
446  0 LOGGER.error("Failed to parse version [" + this.rawVersion + "]", e);
447  0 this.elements.add(new Element(this.rawVersion));
448    }
449    }
450   
451    /**
452    * Remove empty elements.
453    *
454    * @param elements the list of clean
455    */
 
456  737025 toggle private static void trimPadding(List<Element> elements)
457    {
458  879960 for (ListIterator<Element> it = elements.listIterator(elements.size()); it.hasPrevious();) {
459  879960 Element element = it.previous();
460   
461  879960 if (element.compareTo(null) == 0) {
462  142935 it.remove();
463    } else {
464  737025 break;
465    }
466    }
467    }
468   
 
469  38 toggle @Override
470    public Type getType()
471    {
472  38 initElements();
473   
474  38 return this.type;
475    }
476   
477    // Version
478   
 
479  52849 toggle @Override
480    public String getValue()
481    {
482  52849 return this.rawVersion;
483    }
484   
485    // Object
486   
 
487  11133 toggle @Override
488    public String toString()
489    {
490  11133 return getValue();
491    }
492   
 
493  737314 toggle @Override
494    public int hashCode()
495    {
496  737314 if (this.hashCode == -1) {
497  736068 initElements();
498   
499  736068 this.hashCode = this.elements.hashCode();
500    }
501   
502  737314 return this.hashCode;
503    }
504   
 
505  1114 toggle @Override
506    public boolean equals(Object obj)
507    {
508  1114 if (obj == this) {
509  2 return true;
510    }
511   
512  1112 boolean equals;
513   
514  1112 if (obj instanceof DefaultVersion) {
515  1112 equals = equals((DefaultVersion) obj);
516  0 } else if (obj instanceof Version) {
517  0 equals = equals(new DefaultVersion(((Version) obj).getValue()));
518    } else {
519  0 equals = false;
520    }
521   
522  1112 return equals;
523    }
524   
525    /**
526    * @param version the version
527    * @return true if the provided version is equals to this version
528    */
 
529  1112 toggle public boolean equals(DefaultVersion version)
530    {
531  1112 return compareTo(version) == 0;
532    }
533   
 
534  1731 toggle @Override
535    public int compareTo(Version version)
536    {
537  1731 if (version == this) {
538  284 return 0;
539    }
540   
541  1447 if (version instanceof DefaultVersion) {
542  1447 return compareTo((DefaultVersion) version);
543    } else {
544  0 return compareTo(new DefaultVersion(version.getValue()));
545    }
546    }
547   
548    /**
549    * @param version the version to compare as a String
550    * @return a negative integer, zero, or a positive integer as this version is less than, equal to, or greater than
551    * the specified version
552    * @since 7.4.2
553    * @since 8.0M2
554    */
 
555  0 toggle public int compareTo(String version)
556    {
557  0 return compareTo(new DefaultVersion(version));
558    }
559   
560    /**
561    * @param version the version to compare
562    * @return a negative integer, zero, or a positive integer as this version is less than, equal to, or greater than
563    * the specified version
564    */
 
565  2581 toggle public int compareTo(DefaultVersion version)
566    {
567  2581 initElements();
568  2581 version.initElements();
569   
570  2581 final List<Element> otherElements = version.elements;
571   
572  2581 boolean number = true;
573   
574  2581 int rel;
575   
576  2581 for (int index = 0;; index++) {
577  5524 if (index >= this.elements.size() && index >= otherElements.size()) {
578  1728 return 0;
579  3796 } else if (index >= this.elements.size()) {
580  5 return -comparePadding(otherElements, index, null);
581  3791 } else if (index >= otherElements.size()) {
582  29 return comparePadding(this.elements, index, null);
583    }
584   
585  3762 Element thisElement = this.elements.get(index);
586  3762 Element thatElement = otherElements.get(index);
587   
588  3762 if (thisElement.isNumber() != thatElement.isNumber()) {
589  178 if (number == thisElement.isNumber()) {
590  91 rel = comparePadding(this.elements, index, Boolean.valueOf(number));
591    } else {
592  87 rel = -comparePadding(otherElements, index, Boolean.valueOf(number));
593    }
594   
595  178 break;
596    } else {
597  3584 rel = thisElement.compareTo(thatElement);
598  3584 if (rel != 0) {
599  641 break;
600    }
601  2943 number = thisElement.isNumber();
602    }
603    }
604   
605  819 return rel;
606    }
607   
608    /**
609    * Compare the end of the version with 0.
610    *
611    * @param elements the elements to compare to 0
612    * @param index the index where to start comparing with 0
613    * @param number indicate of the previous element is a number
614    * @return the comparison result
615    */
 
616  212 toggle private static int comparePadding(List<Element> elements, int index, Boolean number)
617    {
618  212 int rel = 0;
619   
620  213 for (Iterator<Element> it = elements.listIterator(index); it.hasNext();) {
621  213 Element element = it.next();
622  213 if (number != null && number.booleanValue() != element.isNumber()) {
623  0 break;
624    }
625   
626  213 rel = element.compareTo(null);
627  213 if (rel != 0) {
628  212 break;
629    }
630    }
631   
632  212 return rel;
633    }
634   
635    // Serializable
636   
637    /**
638    * @param out the stream
639    * @throws IOException error when serializing the version
640    */
 
641  93 toggle private void writeObject(ObjectOutputStream out) throws IOException
642    {
643  93 out.writeObject(getValue());
644    }
645   
646    /**
647    * @param in the stream
648    * @throws IOException error when unserializing the version
649    * @throws ClassNotFoundException error when unserializing the version
650    */
 
651  6 toggle private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException
652    {
653  6 setVersion((String) in.readObject());
654    }
655    }