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

File DefaultVersionConstraint.java

 

Coverage histogram

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

Code metrics

52
105
22
1
428
260
55
0.52
4.77
22
2.5

Classes

Class Line # Actions
DefaultVersionConstraint 53 105 0% 55 18
0.8994413689.9%
 

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.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: 4d0058c711444c243d4e9eaa1b904525c5bf0fe0 $
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 int hashCode = -1;
81   
82    /**
83    * @param rawConstraint the version range to parse
84    */
 
85  734855 toggle public DefaultVersionConstraint(String rawConstraint)
86    {
87  734855 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  2 toggle public DefaultVersionConstraint(Collection<? extends VersionRangeCollection> ranges, Version version)
105    {
106  2 if (ranges != null && !ranges.isEmpty()) {
107  1 this.ranges = new ArrayList<>(ranges);
108    } else {
109  1 this.ranges = Collections.emptyList();
110    }
111  2 this.version = version;
112    }
113   
114    /**
115    * @param version the recommended version
116    */
 
117  0 toggle public DefaultVersionConstraint(Version version)
118    {
119  0 this.ranges = Collections.emptyList();
120  0 this.version = version;
121    }
122   
 
123  1471779 toggle private void init()
124    {
125  1471779 if (this.ranges == null && this.value != null) {
126    // Parse
127   
128  734764 List<VersionRangeCollection> newRanges = null;
129  734764 try {
130  734764 newRanges = parseRanges(this.value);
131    } catch (InvalidVersionConstraintException e) {
132    // Invalid range syntax, lets use it as version
133    }
134   
135    // Version
136   
137  734764 if (newRanges == null || newRanges.isEmpty()) {
138  734744 this.version = new DefaultVersion(this.value);
139  734744 this.ranges = Collections.emptyList();
140    } else {
141  20 this.ranges = newRanges;
142    }
143    }
144    }
145   
146    /**
147    * @param rawConstraint the constraint to parse
148    * @return the list of version ranges
149    * @throws InvalidVersionConstraintException invalid constraint range syntax
150    */
 
151  734764 toggle private List<VersionRangeCollection> parseRanges(String rawConstraint) throws InvalidVersionConstraintException
152    {
153  734764 String constraint = rawConstraint;
154   
155  734764 List<VersionRangeCollection> newRanges = new ArrayList<>();
156   
157  734767 while (VersionUtils.startsWith(constraint, '{')) {
158  3 int index = constraint.indexOf('}');
159   
160  3 if (index < 0) {
161  0 throw new InvalidVersionConstraintException(
162    String.format("Unbounded version range [{%s}]", rawConstraint));
163    }
164   
165  3 String range = constraint.substring(1, index);
166  3 try {
167  3 newRanges.add(new DefaultVersionRangeCollection(range));
168    } catch (InvalidVersionRangeException e) {
169  0 throw new InvalidVersionConstraintException(
170    String.format("Failed to parse version range [%s] in constraint [%s]", range, rawConstraint), e);
171    }
172   
173  3 constraint = constraint.substring(index + 1).trim();
174   
175  3 if (VersionUtils.startsWith(constraint, RANGE_SEPARATOR)) {
176  1 constraint = constraint.substring(1).trim();
177    }
178    }
179   
180  734764 if (!constraint.isEmpty()) {
181  734762 if (newRanges.isEmpty()) {
182  734762 try {
183  734762 newRanges.add(new DefaultVersionRangeCollection(constraint));
184    } catch (InvalidVersionRangeException e) {
185  734744 throw new InvalidVersionConstraintException(
186    String.format("Failed to parse version range [{%s}]", constraint), e);
187    }
188    } else {
189  0 throw new InvalidVersionConstraintException(String
190    .format("Invalid version range [{%s}], expected [ or ( but got [{%s}]", rawConstraint, constraint));
191    }
192    }
193   
194  20 return newRanges;
195    }
196   
 
197  735023 toggle private List<VersionRangeCollection> getRangesInternal()
198    {
199  735023 init();
200   
201  735023 return this.ranges;
202    }
203   
 
204  125 toggle @Override
205    public Collection<VersionRangeCollection> getRanges()
206    {
207  125 return getRangesInternal();
208    }
209   
 
210  736718 toggle @Override
211    public Version getVersion()
212    {
213  736718 init();
214   
215  736718 return this.version;
216    }
217   
 
218  115 toggle @Override
219    public boolean containsVersion(Version version)
220    {
221  115 if (getRangesInternal().isEmpty()) {
222  93 return getVersion() != null && getVersion().equals(version);
223    } else {
224  22 for (VersionRange range : getRangesInternal()) {
225  22 if (!range.containsVersion(version)) {
226  4 return false;
227    }
228    }
229    }
230   
231  18 return true;
232    }
233   
 
234  74 toggle @Override
235    public boolean isCompatible(Version version)
236    {
237  74 boolean compatible;
238  74 if (getVersion() == null) {
239  3 compatible = containsVersion(version);
240    } else {
241  71 compatible = version.compareTo(getVersion()) >= 0;
242    }
243   
244  74 return compatible;
245    }
246   
 
247  14 toggle @Override
248    public VersionConstraint merge(VersionConstraint versionConstraint) throws IncompatibleVersionConstraintException
249    {
250  14 if (equals(versionConstraint)) {
251  1 return this;
252    } else {
253  13 VersionConstraint mergedConstraint = mergeVersions(versionConstraint);
254   
255  12 return mergedConstraint == null ? mergeRanges(versionConstraint.getRanges()) : mergedConstraint;
256    }
257    }
258   
259    /**
260    * @param versionConstraint the version constraint to merge
261    * @return the version constraint with the upper version or null if both are ranges based
262    * @throws IncompatibleVersionConstraintException version constraints are incompatible
263    */
 
264  13 toggle private VersionConstraint mergeVersions(VersionConstraint versionConstraint)
265    throws IncompatibleVersionConstraintException
266    {
267  13 if (getVersion() != null) {
268  10 return mergeVersions(this, versionConstraint);
269  3 } else if (versionConstraint.getVersion() != null) {
270  1 return mergeVersions(versionConstraint, this);
271    }
272   
273  2 return null;
274    }
275   
276    /**
277    * @param versionConstraintWithVersion the version constraint based on version
278    * @param versionConstraint the version constraint for which we don't know yet if it's based on version or ranges
279    * @return the merged version constraint
280    * @throws IncompatibleVersionConstraintException version constraints are incompatible
281    */
 
282  11 toggle private VersionConstraint mergeVersions(VersionConstraint versionConstraintWithVersion,
283    VersionConstraint versionConstraint) throws IncompatibleVersionConstraintException
284    {
285  11 if (versionConstraint.getVersion() != null) {
286  9 return versionConstraintWithVersion.getVersion().compareTo(versionConstraint.getVersion()) >= 0
287    ? versionConstraintWithVersion : versionConstraint;
288    } else {
289  2 if (!versionConstraint.containsVersion(versionConstraintWithVersion.getVersion())) {
290  1 throw new IncompatibleVersionConstraintException("Version [" + versionConstraintWithVersion.getVersion()
291    + "] is not part of version constraint [" + versionConstraint + "]");
292    }
293   
294  1 return versionConstraintWithVersion;
295    }
296    }
297   
298    /**
299    * Create a new {@link DefaultVersionConstraint} instance which is the combination of the provided version ranges
300    * and this version ranges.
301    *
302    * @param otherRanges the version ranges to merge with this version ranges
303    * @return the new {@link DefaultVersionConstraint}
304    * @throws IncompatibleVersionConstraintException the provided version and version ranges are not compatible with
305    * this version constraint
306    */
307    // TODO: some optimizations to do like removing unnecessary ranges and avoid validating twice the same ranges
 
308  2 toggle private DefaultVersionConstraint mergeRanges(Collection<VersionRangeCollection> otherRanges)
309    throws IncompatibleVersionConstraintException
310    {
311    // Validate
312  2 validateCompatibility(otherRanges);
313   
314  1 Collection<VersionRangeCollection> newRanges = new ArrayList<>(getRangesInternal().size() + otherRanges.size());
315  1 newRanges.addAll(getRangesInternal());
316  1 newRanges.addAll(otherRanges);
317   
318  1 return new DefaultVersionConstraint(newRanges, null);
319    }
320   
321    /**
322    * @param otherRanges the ranges to validate with this ranges
323    * @throws IncompatibleVersionConstraintException the provided ranges is not compatible with this ranges
324    */
 
325  2 toggle private void validateCompatibility(Collection<VersionRangeCollection> otherRanges)
326    throws IncompatibleVersionConstraintException
327    {
328  2 for (VersionRange otherRange : otherRanges) {
329  2 for (VersionRange range : getRangesInternal()) {
330  2 if (!range.isCompatible(otherRange)) {
331  1 throw new IncompatibleVersionConstraintException(
332    "Ranges [" + range + "] and [" + otherRange + "] are incompatibles");
333    }
334    }
335    }
336    }
337   
 
338  694217 toggle @Override
339    public String getValue()
340    {
341  694217 if (this.value == null) {
342  2 StringBuilder builder = new StringBuilder();
343   
344  2 if (getVersion() != null) {
345  1 builder.append(getVersion());
346    } else {
347  1 if (getRangesInternal().size() == 1) {
348  0 builder.append(getRangesInternal().get(0).getValue());
349    } else {
350  1 for (VersionRange range : getRangesInternal()) {
351  2 if (builder.length() > 0) {
352  1 builder.append(RANGE_SEPARATOR);
353    }
354  2 builder.append('{');
355  2 builder.append(range.getValue());
356  2 builder.append('}');
357    }
358    }
359    }
360   
361  2 this.value = builder.toString();
362    }
363   
364  694217 return this.value;
365    }
366   
367    // Object
368   
 
369  645 toggle @Override
370    public String toString()
371    {
372  645 return getValue();
373    }
374   
 
375  39 toggle @Override
376    public boolean equals(Object obj)
377    {
378  39 if (this == obj) {
379  1 return true;
380    }
381   
382  38 if (obj == null || !(obj instanceof VersionConstraint)) {
383  0 return false;
384    }
385   
386  38 init();
387   
388  38 VersionConstraint versionConstraint = (VersionConstraint) obj;
389   
390  38 return getRangesInternal().equals(versionConstraint.getRanges())
391    && Objects.equals(getVersion(), versionConstraint.getVersion());
392    }
393   
 
394  2085481 toggle @Override
395    public int hashCode()
396    {
397  2085481 if (this.hashCode == -1) {
398  734717 HashCodeBuilder builder = new HashCodeBuilder(17, 31);
399  734717 builder.append(getRangesInternal());
400  734717 builder.append(getVersion());
401   
402  734717 this.hashCode = builder.toHashCode();
403    }
404   
405  2085481 return this.hashCode;
406    }
407   
408    // Serializable
409   
410    /**
411    * @param out the stream
412    * @throws IOException error when serializing the version
413    */
 
414  0 toggle private void writeObject(ObjectOutputStream out) throws IOException
415    {
416  0 out.writeObject(getValue());
417    }
418   
419    /**
420    * @param in the stream
421    * @throws IOException error when unserializing the version
422    * @throws ClassNotFoundException error when unserializing the version
423    */
 
424  0 toggle private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException
425    {
426  0 this.value = (String) in.readObject();
427    }
428    }