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