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
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.length() > 0) {
147 if (restrictions.size() > 0) {
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.length() > 0) {
185 lowerVersion = new DefaultArtifactVersion(lowerBound);
186 }
187 ArtifactVersion upperVersion = null;
188 if (upperBound.length() > 0) {
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 VersionRange cached = CACHE_VERSION.get(version);
207 if (cached == null) {
208 List<Restriction> restrictions = Collections.emptyList();
209 cached = new VersionRange(new DefaultArtifactVersion(version), restrictions);
210 CACHE_VERSION.put(version, cached);
211 }
212 return cached;
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
243 public VersionRange restrict(VersionRange restriction) {
244 List<Restriction> r1 = this.restrictions;
245 List<Restriction> r2 = restriction.restrictions;
246 List<Restriction> restrictions;
247
248 if (r1.isEmpty() || r2.isEmpty()) {
249 restrictions = Collections.emptyList();
250 } else {
251 restrictions = Collections.unmodifiableList(intersection(r1, r2));
252 }
253
254 ArtifactVersion version = null;
255 if (restrictions.size() > 0) {
256 for (Restriction r : restrictions) {
257 if (recommendedVersion != null && r.containsVersion(recommendedVersion)) {
258
259 version = recommendedVersion;
260 break;
261 } else if (version == null
262 && restriction.getRecommendedVersion() != null
263 && r.containsVersion(restriction.getRecommendedVersion())) {
264
265 version = restriction.getRecommendedVersion();
266 }
267 }
268 }
269
270 else if (recommendedVersion != null) {
271
272 version = recommendedVersion;
273 } else if (restriction.recommendedVersion != null) {
274
275
276 version = restriction.recommendedVersion;
277 }
278
279
280
281
282
283
284
285 return new VersionRange(version, restrictions);
286 }
287
288 private List<Restriction> intersection(List<Restriction> r1, List<Restriction> r2) {
289 List<Restriction> restrictions = new ArrayList<>(r1.size() + r2.size());
290 Iterator<Restriction> i1 = r1.iterator();
291 Iterator<Restriction> i2 = r2.iterator();
292 Restriction res1 = i1.next();
293 Restriction res2 = i2.next();
294
295 boolean done = false;
296 while (!done) {
297 if (res1.getLowerBound() == null
298 || res2.getUpperBound() == null
299 || res1.getLowerBound().compareTo(res2.getUpperBound()) <= 0) {
300 if (res1.getUpperBound() == null
301 || res2.getLowerBound() == null
302 || res1.getUpperBound().compareTo(res2.getLowerBound()) >= 0) {
303 ArtifactVersion lower;
304 ArtifactVersion upper;
305 boolean lowerInclusive;
306 boolean upperInclusive;
307
308
309 if (res1.getLowerBound() == null) {
310 lower = res2.getLowerBound();
311 lowerInclusive = res2.isLowerBoundInclusive();
312 } else if (res2.getLowerBound() == null) {
313 lower = res1.getLowerBound();
314 lowerInclusive = res1.isLowerBoundInclusive();
315 } else {
316 int comparison = res1.getLowerBound().compareTo(res2.getLowerBound());
317 if (comparison < 0) {
318 lower = res2.getLowerBound();
319 lowerInclusive = res2.isLowerBoundInclusive();
320 } else if (comparison == 0) {
321 lower = res1.getLowerBound();
322 lowerInclusive = res1.isLowerBoundInclusive() && res2.isLowerBoundInclusive();
323 } else {
324 lower = res1.getLowerBound();
325 lowerInclusive = res1.isLowerBoundInclusive();
326 }
327 }
328
329 if (res1.getUpperBound() == null) {
330 upper = res2.getUpperBound();
331 upperInclusive = res2.isUpperBoundInclusive();
332 } else if (res2.getUpperBound() == null) {
333 upper = res1.getUpperBound();
334 upperInclusive = res1.isUpperBoundInclusive();
335 } else {
336 int comparison = res1.getUpperBound().compareTo(res2.getUpperBound());
337 if (comparison < 0) {
338 upper = res1.getUpperBound();
339 upperInclusive = res1.isUpperBoundInclusive();
340 } else if (comparison == 0) {
341 upper = res1.getUpperBound();
342 upperInclusive = res1.isUpperBoundInclusive() && res2.isUpperBoundInclusive();
343 } else {
344 upper = res2.getUpperBound();
345 upperInclusive = res2.isUpperBoundInclusive();
346 }
347 }
348
349
350 if (lower == null || upper == null || lower.compareTo(upper) != 0) {
351 restrictions.add(new Restriction(lower, lowerInclusive, upper, upperInclusive));
352 } else if (lowerInclusive && upperInclusive) {
353 restrictions.add(new Restriction(lower, lowerInclusive, upper, upperInclusive));
354 }
355
356
357 if (upper == res2.getUpperBound()) {
358
359 if (i2.hasNext()) {
360 res2 = i2.next();
361 } else {
362 done = true;
363 }
364 } else {
365
366 if (i1.hasNext()) {
367 res1 = i1.next();
368 } else {
369 done = true;
370 }
371 }
372 } else {
373
374 if (i1.hasNext()) {
375 res1 = i1.next();
376 } else {
377 done = true;
378 }
379 }
380 } else {
381
382 if (i2.hasNext()) {
383 res2 = i2.next();
384 } else {
385 done = true;
386 }
387 }
388 }
389
390 return restrictions;
391 }
392
393 public ArtifactVersion getSelectedVersion(Artifact artifact) throws OverConstrainedVersionException {
394 ArtifactVersion version;
395 if (recommendedVersion != null) {
396 version = recommendedVersion;
397 } else {
398 if (restrictions.size() == 0) {
399 throw new OverConstrainedVersionException("The artifact has no valid ranges", artifact);
400 }
401
402 version = null;
403 }
404 return version;
405 }
406
407 public boolean isSelectedVersionKnown(Artifact artifact) throws OverConstrainedVersionException {
408 boolean value = false;
409 if (recommendedVersion != null) {
410 value = true;
411 } else {
412 if (restrictions.size() == 0) {
413 throw new OverConstrainedVersionException("The artifact has no valid ranges", artifact);
414 }
415 }
416 return value;
417 }
418
419 public String toString() {
420 if (recommendedVersion != null) {
421 return recommendedVersion.toString();
422 } else {
423 StringBuilder buf = new StringBuilder();
424 for (Iterator<Restriction> i = restrictions.iterator(); i.hasNext(); ) {
425 Restriction r = i.next();
426
427 buf.append(r.toString());
428
429 if (i.hasNext()) {
430 buf.append(',');
431 }
432 }
433 return buf.toString();
434 }
435 }
436
437 public ArtifactVersion matchVersion(List<ArtifactVersion> versions) {
438
439
440 ArtifactVersion matched = null;
441 for (ArtifactVersion version : versions) {
442 if (containsVersion(version)) {
443
444 if (matched == null || version.compareTo(matched) > 0) {
445 matched = version;
446 }
447 }
448 }
449 return matched;
450 }
451
452 public boolean containsVersion(ArtifactVersion version) {
453 for (Restriction restriction : restrictions) {
454 if (restriction.containsVersion(version)) {
455 return true;
456 }
457 }
458 return false;
459 }
460
461 public boolean hasRestrictions() {
462 return !restrictions.isEmpty() && recommendedVersion == null;
463 }
464
465 public boolean equals(Object obj) {
466 if (this == obj) {
467 return true;
468 }
469 if (!(obj instanceof VersionRange)) {
470 return false;
471 }
472 VersionRange other = (VersionRange) obj;
473
474 return Objects.equals(recommendedVersion, other.recommendedVersion)
475 && Objects.equals(restrictions, other.restrictions);
476 }
477
478 public int hashCode() {
479 int hash = 7;
480 hash = 31 * hash + (recommendedVersion == null ? 0 : recommendedVersion.hashCode());
481 hash = 31 * hash + (restrictions == null ? 0 : restrictions.hashCode());
482 return hash;
483 }
484 }