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

File DefaultVersionConstraint.java

 

Coverage histogram

../../../../../img/srcFileCovDistChart10.png
0% of files have more coverage

Code metrics

54
115
25
1
452
282
62
0.54
4.6
25
2.48

Classes

Class Line # Actions
DefaultVersionConstraint 53 115 0% 62 16
0.9175257791.8%
 

Contributing tests

This file is covered by 110 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.Collection;
27    import java.util.Collections;
28    import java.util.List;
29    import java.util.Objects;
30   
31    import org.apache.commons.lang3.builder.HashCodeBuilder;
32    import org.xwiki.extension.version.IncompatibleVersionConstraintException;
33    import org.xwiki.extension.version.InvalidVersionConstraintException;
34    import org.xwiki.extension.version.InvalidVersionRangeException;
35    import org.xwiki.extension.version.Version;
36    import org.xwiki.extension.version.VersionConstraint;
37    import org.xwiki.extension.version.VersionRange;
38    import org.xwiki.extension.version.VersionRangeCollection;
39   
40    /**
41    * Default implementation of {@link VersionConstraint}.
42    * <p>
43    * Mostly based on AETHER implementation which is itself based on Maven specifications. The main difference is that it
44    * can contains a list of ranges OR collection instead of just a OR collection to allow combining several constraints in
45    * one.
46    * <p>
47    * {(,1.0],[2.0,)},{[3.0)}
48    *
49    * @see org.sonatype.aether.util.version.GenericVersionConstraint
50    * @version $Id: b0fd9090b8a7b08babe35a88057070336dcf45ed $
51    * @since 4.0M1
52    */
 
53    public class DefaultVersionConstraint implements VersionConstraint
54    {
55    /**
56    * Serialization identifier.
57    */
58    private static final long serialVersionUID = 1L;
59   
60    /**
61    * The character used to separated version ranges.
62    */
63    private static final char RANGE_SEPARATOR = ',';
64   
65    /**
66    * @see #getRanges()
67    */
68    private List<VersionRangeCollection> ranges;
69   
70    /**
71    * @see #getVersion()
72    */
73    private Version version;
74   
75    /**
76    * @see #getValue()
77    */
78    private String value;
79   
80    private transient int hashCode;
81   
82    /**
83    * @param rawConstraint the version range to parse
84    */
 
85  1563910 toggle public DefaultVersionConstraint(String rawConstraint)
86    {
87  1563910 this.value = rawConstraint;
88    }
89   
90    /**
91    * Created a new {@link DefaultVersionConstraint} by cloning the provided version constraint.
92    *
93    * @param versionConstraint the version constrain to copy
94    */
 
95  0 toggle public DefaultVersionConstraint(VersionConstraint versionConstraint)
96    {
97  0 this(versionConstraint.getRanges(), versionConstraint.getVersion());
98    }
99   
100    /**
101    * @param ranges the ranges of versions
102    * @param version the recommended version
103    */
 
104  1896 toggle public DefaultVersionConstraint(Collection<? extends VersionRangeCollection> ranges, Version version)
105    {
106  1896 this.version = version;
107   
108  1896 setRanges(ranges);
109    }
110   
111    /**
112    * @param version the recommended version
113    */
 
114  0 toggle public DefaultVersionConstraint(Version version)
115    {
116  0 this.version = version;
117   
118  0 this.ranges = Collections.emptyList();
119    }
120   
 
121  2657 toggle private VersionRangeCollection getStrictVersion(Collection<? extends VersionRangeCollection> ranges)
122    {
123  2657 for (VersionRangeCollection collection : ranges) {
124  4431 if (collection.getRanges().size() == 1) {
125  4278 VersionRange range = collection.getRanges().iterator().next();
126  4278 if (range instanceof DefaultVersionRange && ((DefaultVersionRange) range).getLowerBound() != null
127    && ((DefaultVersionRange) range).getLowerBound()
128    .equals(((DefaultVersionRange) range).getUpperBound())) {
129  1923 return collection;
130    }
131   
132    }
133    }
134   
135  734 return null;
136    }
137   
 
138  2658 toggle private void setRanges(Collection<? extends VersionRangeCollection> ranges)
139    {
140  2658 if (ranges != null && !ranges.isEmpty()) {
141    // Optimize ranges in case there is range collection with a strict version
142  2657 VersionRangeCollection strictVersion = getStrictVersion(ranges);
143  2657 if (strictVersion != null) {
144  1923 this.ranges = Collections.singletonList(strictVersion);
145    } else {
146  734 this.ranges = new ArrayList<>(ranges);
147    }
148    } else {
149  1 this.ranges = Collections.emptyList();
150    }
151    }
152   
 
153  3349188 toggle private void init()
154    {
155  3349188 if (this.ranges == null && this.value != null) {
156    // Parse
157   
158  1563812 List<VersionRangeCollection> newRanges = null;
159  1563812 try {
160  1563812 newRanges = parseRanges(this.value);
161    } catch (InvalidVersionConstraintException e) {
162    // Invalid range syntax, lets use it as version
163    }
164   
165    // Version
166   
167  1563812 if (newRanges == null || newRanges.isEmpty()) {
168  1563050 this.version = new DefaultVersion(this.value);
169  1563050 this.ranges = Collections.emptyList();
170    } else {
171  762 setRanges(newRanges);
172    }
173    }
174    }
175   
176    /**
177    * @param rawConstraint the constraint to parse
178    * @return the list of version ranges
179    * @throws InvalidVersionConstraintException invalid constraint range syntax
180    */
 
181  1563812 toggle private List<VersionRangeCollection> parseRanges(String rawConstraint) throws InvalidVersionConstraintException
182    {
183  1563812 String constraint = rawConstraint;
184   
185  1563812 List<VersionRangeCollection> newRanges = new ArrayList<>();
186   
187  1563815 while (VersionUtils.startsWith(constraint, '{')) {
188  3 int index = constraint.indexOf('}');
189   
190  3 if (index < 0) {
191  0 throw new InvalidVersionConstraintException(
192    String.format("Unbounded version range [{%s}]", rawConstraint));
193    }
194   
195  3 String range = constraint.substring(1, index);
196  3 try {
197  3 newRanges.add(new DefaultVersionRangeCollection(range));
198    } catch (InvalidVersionRangeException e) {
199  0 throw new InvalidVersionConstraintException(
200    String.format("Failed to parse version range [%s] in constraint [%s]", range, rawConstraint), e);
201    }
202   
203  3 constraint = constraint.substring(index + 1).trim();
204   
205  3 if (VersionUtils.startsWith(constraint, RANGE_SEPARATOR)) {
206  1 constraint = constraint.substring(1).trim();
207    }
208    }
209   
210  1563812 if (!constraint.isEmpty()) {
211  1563810 if (newRanges.isEmpty()) {
212  1563810 try {
213  1563810 newRanges.add(new DefaultVersionRangeCollection(constraint));
214    } catch (InvalidVersionRangeException e) {
215  1563050 throw new InvalidVersionConstraintException(
216    String.format("Failed to parse version range [{%s}]", constraint), e);
217    }
218    } else {
219  0 throw new InvalidVersionConstraintException(String
220    .format("Invalid version range [{%s}], expected [ or ( but got [{%s}]", rawConstraint, constraint));
221    }
222    }
223   
224  762 return newRanges;
225    }
226   
 
227  1728473 toggle private List<VersionRangeCollection> getRangesInternal()
228    {
229  1728472 init();
230   
231  1728472 return this.ranges;
232    }
233   
 
234  9038 toggle @Override
235    public Collection<VersionRangeCollection> getRanges()
236    {
237  9038 return getRangesInternal();
238    }
239   
 
240  1616099 toggle @Override
241    public Version getVersion()
242    {
243  1616099 init();
244   
245  1616099 return this.version;
246    }
247   
 
248  79882 toggle @Override
249    public boolean containsVersion(Version version)
250    {
251  79882 if (getRangesInternal().isEmpty()) {
252  11555 return getVersion() != null && getVersion().equals(version);
253    } else {
254  68327 for (VersionRange range : getRangesInternal()) {
255  68327 if (!range.containsVersion(version)) {
256  165 return false;
257    }
258    }
259    }
260   
261  68162 return true;
262    }
263   
 
264  2703 toggle @Override
265    public boolean isCompatible(Version version)
266    {
267  2703 boolean compatible;
268  2703 if (getVersion() == null) {
269  1610 compatible = containsVersion(version);
270    } else {
271  1093 compatible = version.compareTo(getVersion()) >= 0;
272    }
273   
274  2703 return compatible;
275    }
276   
 
277  1981 toggle @Override
278    public VersionConstraint merge(VersionConstraint versionConstraint) throws IncompatibleVersionConstraintException
279    {
280  1981 if (equals(versionConstraint)) {
281  44 return this;
282    } else {
283  1937 VersionConstraint mergedConstraint = mergeVersions(versionConstraint);
284   
285  1937 return mergedConstraint == null ? mergeRanges(versionConstraint) : mergedConstraint;
286    }
287    }
288   
289    /**
290    * @param versionConstraint the version constraint to merge
291    * @return the version constraint with the upper version or null if both are ranges based
292    */
 
293  1937 toggle private VersionConstraint mergeVersions(VersionConstraint versionConstraint)
294    {
295  1937 if (getVersion() != null && versionConstraint.getVersion() != null) {
296  40 return getVersion().compareTo(versionConstraint.getVersion()) >= 0 ? this : versionConstraint;
297    }
298   
299  1897 return null;
300    }
301   
302    /**
303    * Create a new {@link DefaultVersionConstraint} instance which is the combination of the provided version ranges
304    * and this version ranges.
305    *
306    * @param otherRanges the version ranges to merge with this version ranges
307    * @return the new {@link DefaultVersionConstraint}
308    * @throws IncompatibleVersionConstraintException the provided version and version ranges are not compatible with
309    * this version constraint
310    */
 
311  1897 toggle private DefaultVersionConstraint mergeRanges(VersionConstraint otherConstraint)
312    throws IncompatibleVersionConstraintException
313    {
314  1897 Collection<VersionRangeCollection> resolvedRanges = resolveRanges(this);
315  1897 Collection<VersionRangeCollection> otherResolvedRanges = resolveRanges(otherConstraint);
316   
317  1897 return mergeRanges(resolvedRanges, otherResolvedRanges);
318    }
319   
 
320  1897 toggle private DefaultVersionConstraint mergeRanges(Collection<VersionRangeCollection> ranges1,
321    Collection<VersionRangeCollection> ranges2) throws IncompatibleVersionConstraintException
322    {
323    // Validate
324   
325  1897 validateCompatibility(ranges1, ranges2);
326   
327    // Merge
328   
329  1895 Collection<VersionRangeCollection> newRanges = new ArrayList<>(ranges1.size() + ranges2.size());
330  1895 newRanges.addAll(ranges1);
331  1895 newRanges.addAll(ranges2);
332   
333  1895 return new DefaultVersionConstraint(newRanges, null);
334    }
335   
 
336  3794 toggle private Collection<VersionRangeCollection> resolveRanges(VersionConstraint constraint)
337    {
338  3794 Collection<VersionRangeCollection> resolvedRanges = constraint.getRanges();
339   
340    // If no range is provided convert the recommended version into a range
341  3794 if (constraint.getVersion() != null) {
342  1893 resolvedRanges = Collections.singleton(new DefaultVersionRangeCollection(
343    Collections.singleton(new DefaultVersionRange(constraint.getVersion(), true, null, true))));
344    }
345   
346  3794 return resolvedRanges;
347    }
348   
 
349  1897 toggle private void validateCompatibility(Collection<VersionRangeCollection> ranges1,
350    Collection<VersionRangeCollection> ranges2) throws IncompatibleVersionConstraintException
351    {
352  1897 for (VersionRange range2 : ranges2) {
353  1897 for (VersionRange range1 : ranges1) {
354  1897 if (!range1.isCompatible(range2)) {
355  2 throw new IncompatibleVersionConstraintException(
356    "Ranges [" + range1 + "] and [" + range2 + "] are incompatibles");
357    }
358    }
359    }
360    }
361   
 
362  3141778 toggle @Override
363    public String getValue()
364    {
365  3141778 if (this.value == null) {
366  547 StringBuilder builder = new StringBuilder();
367   
368  547 if (getVersion() != null) {
369  1 builder.append(getVersion());
370    } else {
371  545 if (getRangesInternal().size() == 1) {
372  544 builder.append(getRangesInternal().get(0).getValue());
373    } else {
374  1 for (VersionRange range : getRangesInternal()) {
375  2 if (builder.length() > 0) {
376  1 builder.append(RANGE_SEPARATOR);
377    }
378  2 builder.append('{');
379  2 builder.append(range.getValue());
380  2 builder.append('}');
381    }
382    }
383    }
384   
385  546 this.value = builder.toString();
386    }
387   
388  3141779 return this.value;
389    }
390   
391    // Object
392   
 
393  1640548 toggle @Override
394    public String toString()
395    {
396  1640548 return getValue();
397    }
398   
 
399  4664 toggle @Override
400    public boolean equals(Object obj)
401    {
402  4664 if (this == obj) {
403  48 return true;
404    }
405   
406  4616 if (obj == null || !(obj instanceof VersionConstraint)) {
407  0 return false;
408    }
409   
410  4616 init();
411   
412  4616 VersionConstraint versionConstraint = (VersionConstraint) obj;
413   
414  4616 return getRangesInternal().equals(versionConstraint.getRanges())
415    && Objects.equals(getVersion(), versionConstraint.getVersion());
416    }
417   
 
418  5085683 toggle @Override
419    public int hashCode()
420    {
421  5085683 if (this.hashCode == 0) {
422  1565519 HashCodeBuilder builder = new HashCodeBuilder(17, 31);
423  1565519 builder.append(getRangesInternal());
424  1565519 builder.append(getVersion());
425   
426  1565519 this.hashCode = builder.toHashCode();
427    }
428   
429  5085683 return this.hashCode;
430    }
431   
432    // Serializable
433   
434    /**
435    * @param out the stream
436    * @throws IOException error when serializing the version
437    */
 
438  0 toggle private void writeObject(ObjectOutputStream out) throws IOException
439    {
440  0 out.writeObject(getValue());
441    }
442   
443    /**
444    * @param in the stream
445    * @throws IOException error when unserializing the version
446    * @throws ClassNotFoundException error when unserializing the version
447    */
 
448  0 toggle private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException
449    {
450  0 this.value = (String) in.readObject();
451    }
452    }