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.util.ArrayList;
22  import java.util.Collections;
23  import java.util.Iterator;
24  import java.util.List;
25  import java.util.Map;
26  import java.util.Objects;
27  import java.util.WeakHashMap;
28  
29  import org.apache.maven.artifact.Artifact;
30  
31  
32  
33  
34  
35  public class VersionRange {
36      private static final Map<String, VersionRange> CACHE_SPEC = Collections.synchronizedMap(new WeakHashMap<>());
37  
38      private static final Map<String, VersionRange> CACHE_VERSION = Collections.synchronizedMap(new WeakHashMap<>());
39  
40      private final ArtifactVersion recommendedVersion;
41  
42      private final List<Restriction> restrictions;
43  
44      private VersionRange(ArtifactVersion recommendedVersion, List<Restriction> restrictions) {
45          this.recommendedVersion = recommendedVersion;
46          this.restrictions = restrictions;
47      }
48  
49      public ArtifactVersion getRecommendedVersion() {
50          return recommendedVersion;
51      }
52  
53      public List<Restriction> getRestrictions() {
54          return restrictions;
55      }
56  
57      
58  
59  
60  
61      @Deprecated
62      public VersionRange cloneOf() {
63          List<Restriction> copiedRestrictions = null;
64  
65          if (restrictions != null) {
66              copiedRestrictions = new ArrayList<>();
67  
68              if (!restrictions.isEmpty()) {
69                  copiedRestrictions.addAll(restrictions);
70              }
71          }
72  
73          return new VersionRange(recommendedVersion, copiedRestrictions);
74      }
75  
76      
77  
78  
79  
80  
81  
82  
83  
84  
85  
86  
87  
88  
89  
90  
91  
92  
93  
94      public static VersionRange createFromVersionSpec(String spec) throws InvalidVersionSpecificationException {
95          if (spec == null) {
96              return null;
97          }
98  
99          VersionRange cached = CACHE_SPEC.get(spec);
100         if (cached != null) {
101             return cached;
102         }
103 
104         List<Restriction> restrictions = new ArrayList<>();
105         String process = spec;
106         ArtifactVersion version = null;
107         ArtifactVersion upperBound = null;
108         ArtifactVersion lowerBound = null;
109 
110         while (process.startsWith("[") || process.startsWith("(")) {
111             int index1 = process.indexOf(')');
112             int index2 = process.indexOf(']');
113 
114             int index = index2;
115             if (index2 < 0 || index1 < index2) {
116                 if (index1 >= 0) {
117                     index = index1;
118                 }
119             }
120 
121             if (index < 0) {
122                 throw new InvalidVersionSpecificationException("Unbounded range: " + spec);
123             }
124 
125             Restriction restriction = parseRestriction(process.substring(0, index + 1));
126             if (lowerBound == null) {
127                 lowerBound = restriction.getLowerBound();
128             }
129             if (upperBound != null) {
130                 if (restriction.getLowerBound() == null
131                         || restriction.getLowerBound().compareTo(upperBound) < 0) {
132                     throw new InvalidVersionSpecificationException("Ranges overlap: " + spec);
133                 }
134             }
135             restrictions.add(restriction);
136             upperBound = restriction.getUpperBound();
137 
138             process = process.substring(index + 1).trim();
139 
140             if (process.startsWith(",")) {
141                 process = process.substring(1).trim();
142             }
143         }
144 
145         if (process.length() > 0) {
146             if (restrictions.size() > 0) {
147                 throw new InvalidVersionSpecificationException(
148                         "Only fully-qualified sets allowed in multiple set scenario: " + spec);
149             } else {
150                 version = new DefaultArtifactVersion(process);
151                 restrictions.add(Restriction.EVERYTHING);
152             }
153         }
154 
155         cached = new VersionRange(version, restrictions);
156         CACHE_SPEC.put(spec, cached);
157         return cached;
158     }
159 
160     private static Restriction parseRestriction(String spec) throws InvalidVersionSpecificationException {
161         boolean lowerBoundInclusive = spec.startsWith("[");
162         boolean upperBoundInclusive = spec.endsWith("]");
163 
164         String process = spec.substring(1, spec.length() - 1).trim();
165 
166         Restriction restriction;
167 
168         int index = process.indexOf(',');
169 
170         if (index < 0) {
171             if (!lowerBoundInclusive || !upperBoundInclusive) {
172                 throw new InvalidVersionSpecificationException("Single version must be surrounded by []: " + spec);
173             }
174 
175             ArtifactVersion version = new DefaultArtifactVersion(process);
176 
177             restriction = new Restriction(version, lowerBoundInclusive, version, upperBoundInclusive);
178         } else {
179             String lowerBound = process.substring(0, index).trim();
180             String upperBound = process.substring(index + 1).trim();
181 
182             ArtifactVersion lowerVersion = null;
183             if (lowerBound.length() > 0) {
184                 lowerVersion = new DefaultArtifactVersion(lowerBound);
185             }
186             ArtifactVersion upperVersion = null;
187             if (upperBound.length() > 0) {
188                 upperVersion = new DefaultArtifactVersion(upperBound);
189             }
190 
191             if (upperVersion != null && lowerVersion != null) {
192                 int result = upperVersion.compareTo(lowerVersion);
193                 if (result < 0 || (result == 0 && (!lowerBoundInclusive || !upperBoundInclusive))) {
194                     throw new InvalidVersionSpecificationException("Range defies version ordering: " + spec);
195                 }
196             }
197 
198             restriction = new Restriction(lowerVersion, lowerBoundInclusive, upperVersion, upperBoundInclusive);
199         }
200 
201         return restriction;
202     }
203 
204     public static VersionRange createFromVersion(String version) {
205         VersionRange cached = CACHE_VERSION.get(version);
206         if (cached == null) {
207             List<Restriction> restrictions = Collections.emptyList();
208             cached = new VersionRange(new DefaultArtifactVersion(version), restrictions);
209             CACHE_VERSION.put(version, cached);
210         }
211         return cached;
212     }
213 
214     
215 
216 
217 
218 
219 
220 
221 
222 
223 
224 
225 
226 
227 
228 
229 
230 
231 
232 
233 
234 
235 
236 
237 
238 
239 
240 
241 
242     public VersionRange restrict(VersionRange restriction) {
243         List<Restriction> r1 = this.restrictions;
244         List<Restriction> r2 = restriction.restrictions;
245         List<Restriction> restrictions;
246 
247         if (r1.isEmpty() || r2.isEmpty()) {
248             restrictions = Collections.emptyList();
249         } else {
250             restrictions = Collections.unmodifiableList(intersection(r1, r2));
251         }
252 
253         ArtifactVersion version = null;
254         if (restrictions.size() > 0) {
255             for (Restriction r : restrictions) {
256                 if (recommendedVersion != null && r.containsVersion(recommendedVersion)) {
257                     
258                     version = recommendedVersion;
259                     break;
260                 } else if (version == null
261                         && restriction.getRecommendedVersion() != null
262                         && r.containsVersion(restriction.getRecommendedVersion())) {
263                     
264                     version = restriction.getRecommendedVersion();
265                 }
266             }
267         }
268         
269         else if (recommendedVersion != null) {
270             
271             version = recommendedVersion;
272         } else if (restriction.recommendedVersion != null) {
273             
274             
275             version = restriction.recommendedVersion;
276         }
277         
278 
279 
280 
281 
282 
283 
284         return new VersionRange(version, restrictions);
285     }
286 
287     private List<Restriction> intersection(List<Restriction> r1, List<Restriction> r2) {
288         List<Restriction> restrictions = new ArrayList<>(r1.size() + r2.size());
289         Iterator<Restriction> i1 = r1.iterator();
290         Iterator<Restriction> i2 = r2.iterator();
291         Restriction res1 = i1.next();
292         Restriction res2 = i2.next();
293 
294         boolean done = false;
295         while (!done) {
296             if (res1.getLowerBound() == null
297                     || res2.getUpperBound() == null
298                     || res1.getLowerBound().compareTo(res2.getUpperBound()) <= 0) {
299                 if (res1.getUpperBound() == null
300                         || res2.getLowerBound() == null
301                         || res1.getUpperBound().compareTo(res2.getLowerBound()) >= 0) {
302                     ArtifactVersion lower;
303                     ArtifactVersion upper;
304                     boolean lowerInclusive;
305                     boolean upperInclusive;
306 
307                     
308                     if (res1.getLowerBound() == null) {
309                         lower = res2.getLowerBound();
310                         lowerInclusive = res2.isLowerBoundInclusive();
311                     } else if (res2.getLowerBound() == null) {
312                         lower = res1.getLowerBound();
313                         lowerInclusive = res1.isLowerBoundInclusive();
314                     } else {
315                         int comparison = res1.getLowerBound().compareTo(res2.getLowerBound());
316                         if (comparison < 0) {
317                             lower = res2.getLowerBound();
318                             lowerInclusive = res2.isLowerBoundInclusive();
319                         } else if (comparison == 0) {
320                             lower = res1.getLowerBound();
321                             lowerInclusive = res1.isLowerBoundInclusive() && res2.isLowerBoundInclusive();
322                         } else {
323                             lower = res1.getLowerBound();
324                             lowerInclusive = res1.isLowerBoundInclusive();
325                         }
326                     }
327 
328                     if (res1.getUpperBound() == null) {
329                         upper = res2.getUpperBound();
330                         upperInclusive = res2.isUpperBoundInclusive();
331                     } else if (res2.getUpperBound() == null) {
332                         upper = res1.getUpperBound();
333                         upperInclusive = res1.isUpperBoundInclusive();
334                     } else {
335                         int comparison = res1.getUpperBound().compareTo(res2.getUpperBound());
336                         if (comparison < 0) {
337                             upper = res1.getUpperBound();
338                             upperInclusive = res1.isUpperBoundInclusive();
339                         } else if (comparison == 0) {
340                             upper = res1.getUpperBound();
341                             upperInclusive = res1.isUpperBoundInclusive() && res2.isUpperBoundInclusive();
342                         } else {
343                             upper = res2.getUpperBound();
344                             upperInclusive = res2.isUpperBoundInclusive();
345                         }
346                     }
347 
348                     
349                     if (lower == null || upper == null || lower.compareTo(upper) != 0) {
350                         restrictions.add(new Restriction(lower, lowerInclusive, upper, upperInclusive));
351                     } else if (lowerInclusive && upperInclusive) {
352                         restrictions.add(new Restriction(lower, lowerInclusive, upper, upperInclusive));
353                     }
354 
355                     
356                     if (upper == res2.getUpperBound()) {
357                         
358                         if (i2.hasNext()) {
359                             res2 = i2.next();
360                         } else {
361                             done = true;
362                         }
363                     } else {
364                         
365                         if (i1.hasNext()) {
366                             res1 = i1.next();
367                         } else {
368                             done = true;
369                         }
370                     }
371                 } else {
372                     
373                     if (i1.hasNext()) {
374                         res1 = i1.next();
375                     } else {
376                         done = true;
377                     }
378                 }
379             } else {
380                 
381                 if (i2.hasNext()) {
382                     res2 = i2.next();
383                 } else {
384                     done = true;
385                 }
386             }
387         }
388 
389         return restrictions;
390     }
391 
392     public ArtifactVersion getSelectedVersion(Artifact artifact) throws OverConstrainedVersionException {
393         ArtifactVersion version;
394         if (recommendedVersion != null) {
395             version = recommendedVersion;
396         } else {
397             if (restrictions.size() == 0) {
398                 throw new OverConstrainedVersionException("The artifact has no valid ranges", artifact);
399             }
400 
401             version = null;
402         }
403         return version;
404     }
405 
406     public boolean isSelectedVersionKnown(Artifact artifact) throws OverConstrainedVersionException {
407         boolean value = false;
408         if (recommendedVersion != null) {
409             value = true;
410         } else {
411             if (restrictions.size() == 0) {
412                 throw new OverConstrainedVersionException("The artifact has no valid ranges", artifact);
413             }
414         }
415         return value;
416     }
417 
418     public String toString() {
419         if (recommendedVersion != null) {
420             return recommendedVersion.toString();
421         } else {
422             StringBuilder buf = new StringBuilder();
423             for (Iterator<Restriction> i = restrictions.iterator(); i.hasNext(); ) {
424                 Restriction r = i.next();
425 
426                 buf.append(r.toString());
427 
428                 if (i.hasNext()) {
429                     buf.append(',');
430                 }
431             }
432             return buf.toString();
433         }
434     }
435 
436     public ArtifactVersion matchVersion(List<ArtifactVersion> versions) {
437         
438 
439         ArtifactVersion matched = null;
440         for (ArtifactVersion version : versions) {
441             if (containsVersion(version)) {
442                 
443                 if (matched == null || version.compareTo(matched) > 0) {
444                     matched = version;
445                 }
446             }
447         }
448         return matched;
449     }
450 
451     public boolean containsVersion(ArtifactVersion version) {
452         for (Restriction restriction : restrictions) {
453             if (restriction.containsVersion(version)) {
454                 return true;
455             }
456         }
457         return false;
458     }
459 
460     public boolean hasRestrictions() {
461         return !restrictions.isEmpty() && recommendedVersion == null;
462     }
463 
464     public boolean equals(Object obj) {
465         if (this == obj) {
466             return true;
467         }
468         if (!(obj instanceof VersionRange)) {
469             return false;
470         }
471         VersionRange other = (VersionRange) obj;
472 
473         return Objects.equals(recommendedVersion, other.recommendedVersion)
474                 && Objects.equals(restrictions, other.restrictions);
475     }
476 
477     public int hashCode() {
478         int hash = 7;
479         hash = 31 * hash + (recommendedVersion == null ? 0 : recommendedVersion.hashCode());
480         hash = 31 * hash + (restrictions == null ? 0 : restrictions.hashCode());
481         return hash;
482     }
483 }