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 }