1. Project Clover database Sat Feb 2 2019 06:45:20 CET
  2. Package org.xwiki.extension.version.internal

File DefaultVersion.java

 

Coverage histogram

../../../../../img/srcFileCovDistChart9.png
41% 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 12
0.9124087791.2%
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 106 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: 2df6ad4a41d8d7865f6fd25a2ad683c71021d543 $
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 transient int hashCode;
79   
80    /**
81    * Used to parse the string representation of the version.
82    *
83    * @version $Id: 2df6ad4a41d8d7865f6fd25a2ad683c71021d543 $
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  1568091 toggle public Tokenizer(String rawVersion)
111    {
112  1568091 this.rawVersion = (rawVersion.length() > 0) ? rawVersion : "0";
113    }
114   
115    /**
116    * @return the token
117    */
 
118  4439983 toggle public String getToken()
119    {
120  4439983 return this.token;
121    }
122   
123    /**
124    * @return indicate if the token is a number
125    */
 
126  4439983 toggle public boolean isNumber()
127    {
128  4439983 return this.number;
129    }
130   
131    /**
132    * @return move to the next token
133    */
 
134  6008074 toggle public boolean next()
135    {
136  6008074 final int n = this.rawVersion.length();
137  6008074 if (this.index >= n) {
138  1568091 return false;
139    }
140   
141  4439983 int state = -2;
142   
143  4439983 int start = this.index;
144  4439983 int end = n;
145   
146  10058403 for (; this.index < n; this.index++) {
147  8490313 char c = this.rawVersion.charAt(this.index);
148   
149  8490313 if (c == '.' || c == '-') {
150  2841956 end = this.index;
151  2841956 this.index++;
152  2841956 break;
153    } else {
154  5648357 int digit = Character.digit(c, 10);
155  5648357 if (digit >= 0) {
156  4870211 if (state == -1) {
157  15964 end = this.index;
158  15964 break;
159    }
160  4854247 if (state == 0) {
161    // normalize numbers and strip leading zeros
162  7632 start++;
163    }
164  4854247 state = (state > 0 || digit > 0) ? 1 : 0;
165    } else {
166  778146 if (state >= 0) {
167  13973 end = this.index;
168  13973 break;
169    }
170  764173 state = -1;
171    }
172    }
173    }
174   
175  4439983 if (start < end) {
176  4439983 this.token = this.rawVersion.substring(start, end);
177  4439983 this.number = state >= 0;
178    } else {
179  0 this.token = "0";
180  0 this.number = true;
181    }
182   
183  4439983 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: 2df6ad4a41d8d7865f6fd25a2ad683c71021d543 $
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: 2df6ad4a41d8d7865f6fd25a2ad683c71021d543 $
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  71 toggle static {
234  71 QUALIFIERS = new HashMap<>();
235  71 QUALIFIERS.put("alpha", Integer.valueOf(-5));
236  71 QUALIFIERS.put("a", Integer.valueOf(-5));
237  71 QUALIFIERS.put("beta", Integer.valueOf(-4));
238  71 QUALIFIERS.put("b", Integer.valueOf(-4));
239  71 QUALIFIERS.put("milestone", Integer.valueOf(-3));
240  71 QUALIFIERS.put("cr", Integer.valueOf(-2));
241  71 QUALIFIERS.put("rc", Integer.valueOf(-2));
242  71 QUALIFIERS.put("snapshot", Integer.valueOf(-1));
243  71 QUALIFIERS.put("ga", Integer.valueOf(0));
244  71 QUALIFIERS.put("final", Integer.valueOf(0));
245  71 QUALIFIERS.put("", Integer.valueOf(0));
246  71 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  4290150 toggle private boolean isInteger(String number)
265    {
266  4290150 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  4439983 toggle public Element(Tokenizer tokenizer)
280    {
281  4439983 String token = tokenizer.getToken();
282  4439983 if (tokenizer.isNumber()) {
283  4290150 if (isInteger(token)) {
284  4289685 try {
285  4289685 this.elementType = ElementType.INT;
286  4289685 this.value = Integer.valueOf(token);
287    } catch (NumberFormatException e) {
288  0 throw new IllegalStateException(e);
289    }
290    } else {
291  465 this.elementType = ElementType.STRING;
292  465 this.value = token;
293    }
294    } else {
295  149833 String lowerCaseToken = token.toLowerCase(Locale.ENGLISH);
296  149833 Integer qualifier = QUALIFIERS.get(lowerCaseToken);
297  149833 if (qualifier != null) {
298  89102 this.elementType = ElementType.QUALIFIER;
299  89102 this.value = qualifier;
300  89102 if (qualifier.intValue() == -1) {
301  27498 this.versionType = Type.SNAPSHOT;
302  61604 } else if (qualifier < 0) {
303  10267 this.versionType = Type.BETA;
304    }
305    } else {
306  60731 this.elementType = ElementType.STRING;
307  60731 this.value = lowerCaseToken;
308    }
309    }
310    }
311   
312    /**
313    * @return indicate of the element is a number
314    */
 
315  1400540 toggle public boolean isNumber()
316    {
317  1400540 return this.elementType == ElementType.INT || this.elementType == ElementType.QUALIFIER;
318    }
319   
320    /**
321    * @return the type of the version element
322    */
 
323  4477748 toggle public Type getVersionType()
324    {
325  4477748 return this.versionType;
326    }
327   
 
328  2320918 toggle @Override
329    public int compareTo(Element that)
330    {
331  2320918 int rel;
332   
333  2320918 if (that == null) {
334    // null in this context denotes the pad element (0 or "ga")
335  1852788 switch (this.elementType) {
336  42607 case STRING:
337  42607 rel = 1;
338  42607 break;
339  1731678 case INT:
340  78503 case QUALIFIER:
341  1810181 rel = (Integer) this.value;
342  1810181 break;
343  0 default:
344  0 throw new IllegalStateException(ERROR_UNKNOWNKIND + this.elementType);
345    }
346    } else {
347  468130 rel = this.elementType.compareTo(that.elementType);
348  468130 if (rel == 0) {
349  468129 switch (this.elementType) {
350  323955 case INT:
351  143626 case QUALIFIER:
352  467581 rel = (Integer) this.value - (Integer) that.value;
353  467581 break;
354  548 case STRING:
355  548 rel = ((String) this.value).compareToIgnoreCase((String) that.value);
356  548 break;
357  0 default:
358  0 throw new IllegalStateException(ERROR_UNKNOWNKIND + this.elementType);
359    }
360    }
361    }
362   
363  2320918 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  4153856 toggle @Override
373    public int hashCode()
374    {
375  4153856 HashCodeBuilder builder = new HashCodeBuilder();
376   
377  4153856 builder.append(this.value);
378  4153856 builder.append(this.elementType);
379   
380  4153856 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  1581303 toggle public DefaultVersion(String rawVersion)
395    {
396  1581303 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  1890299 toggle private void initElements()
413    {
414  1890299 if (this.elements == null) {
415  1568091 parse();
416    }
417    }
418   
419    /**
420    * @param rawVersion the string representation to parse
421    */
 
422  1581323 toggle private void setVersion(String rawVersion)
423    {
424  1581323 this.rawVersion = rawVersion;
425    }
426   
427    /**
428    * Parse the string representation of the version into separated elements.
429    */
 
430  1568091 toggle private void parse()
431    {
432  1568091 this.elements = new ArrayList<>();
433   
434  1568091 try {
435  6008074 for (Tokenizer tokenizer = new Tokenizer(this.rawVersion); tokenizer.next();) {
436  4439983 Element element = new Element(tokenizer);
437  4439983 this.elements.add(element);
438  4439983 if (element.getVersionType() != Type.STABLE) {
439  37765 this.type = element.getVersionType();
440    }
441    }
442   
443  1568091 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  1568091 toggle private static void trimPadding(List<Element> elements)
457    {
458  1852453 for (ListIterator<Element> it = elements.listIterator(elements.size()); it.hasPrevious();) {
459  1852453 Element element = it.previous();
460   
461  1852453 if (element.compareTo(null) == 0) {
462  284362 it.remove();
463    } else {
464  1568091 break;
465    }
466    }
467    }
468   
 
469  51 toggle @Override
470    public Type getType()
471    {
472  51 initElements();
473   
474  51 return this.type;
475    }
476   
477    // Version
478   
 
479  2589176 toggle @Override
480    public String getValue()
481    {
482  2589175 return this.rawVersion;
483    }
484   
485    // Object
486   
 
487  2074734 toggle @Override
488    public String toString()
489    {
490  2074734 return getValue();
491    }
492   
 
493  1573599 toggle @Override
494    public int hashCode()
495    {
496  1573599 if (this.hashCode == 0) {
497  1567132 initElements();
498   
499  1567132 this.hashCode = this.elements.hashCode();
500    }
501   
502  1573599 return this.hashCode;
503    }
504   
 
505  20877 toggle @Override
506    public boolean equals(Object obj)
507    {
508  20877 if (obj == this) {
509  2055 return true;
510    }
511   
512  18822 boolean equals;
513   
514  18822 if (obj instanceof DefaultVersion) {
515  14655 equals = equals((DefaultVersion) obj);
516  4167 } else if (obj instanceof Version) {
517  2 equals = equals(new DefaultVersion(((Version) obj).getValue()));
518    } else {
519  4165 equals = false;
520    }
521   
522  18822 return equals;
523    }
524   
525    /**
526    * @param version the version
527    * @return true if the provided version is equals to this version
528    */
 
529  14657 toggle public boolean equals(DefaultVersion version)
530    {
531  14657 return compareTo(version) == 0;
532    }
533   
 
534  147298 toggle @Override
535    public int compareTo(Version version)
536    {
537  147298 if (version == this) {
538  425 return 0;
539    }
540   
541  146873 if (version instanceof DefaultVersion) {
542  146873 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  161558 toggle public int compareTo(DefaultVersion version)
566    {
567  161558 initElements();
568  161558 version.initElements();
569   
570  161558 final List<Element> otherElements = version.elements;
571   
572  161558 boolean number = true;
573   
574  161558 int rel;
575   
576  161558 for (int index = 0;; index++) {
577  625066 if (index >= this.elements.size() && index >= otherElements.size()) {
578  156631 return 0;
579  468434 } else if (index >= this.elements.size()) {
580  7 return -comparePadding(otherElements, index, null);
581  468427 } else if (index >= otherElements.size()) {
582  104 return comparePadding(this.elements, index, null);
583    }
584   
585  468323 Element thisElement = this.elements.get(index);
586  468323 Element thatElement = otherElements.get(index);
587   
588  468323 if (thisElement.isNumber() != thatElement.isNumber()) {
589  193 if (number == thisElement.isNumber()) {
590  99 rel = comparePadding(this.elements, index, Boolean.valueOf(number));
591    } else {
592  94 rel = -comparePadding(otherElements, index, Boolean.valueOf(number));
593    }
594   
595  193 break;
596    } else {
597  468130 rel = thisElement.compareTo(thatElement);
598  468130 if (rel != 0) {
599  4622 break;
600    }
601  463508 number = thisElement.isNumber();
602    }
603    }
604   
605  4815 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  304 toggle private static int comparePadding(List<Element> elements, int index, Boolean number)
617    {
618  304 int rel = 0;
619   
620  335 for (Iterator<Element> it = elements.listIterator(index); it.hasNext();) {
621  335 Element element = it.next();
622  335 if (number != null && number.booleanValue() != element.isNumber()) {
623  0 break;
624    }
625   
626  335 rel = element.compareTo(null);
627  335 if (rel != 0) {
628  304 break;
629    }
630    }
631   
632  304 return rel;
633    }
634   
635    // Serializable
636   
637    /**
638    * @param out the stream
639    * @throws IOException error when serializing the version
640    */
 
641  219 toggle private void writeObject(ObjectOutputStream out) throws IOException
642    {
643  219 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  20 toggle private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException
652    {
653  20 setVersion((String) in.readObject());
654    }
655    }