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 =
38 Collections.<String, VersionRange>synchronizedMap(new WeakHashMap<String, VersionRange>());
39
40 private static final Map<String, VersionRange> CACHE_VERSION =
41 Collections.<String, VersionRange>synchronizedMap(new WeakHashMap<String, VersionRange>());
42
43 private final ArtifactVersion recommendedVersion;
44
45 private final List<Restriction> restrictions;
46
47 private VersionRange(ArtifactVersion recommendedVersion, List<Restriction> restrictions) {
48 this.recommendedVersion = recommendedVersion;
49 this.restrictions = restrictions;
50 }
51
52 public ArtifactVersion getRecommendedVersion() {
53 return recommendedVersion;
54 }
55
56 public List<Restriction> getRestrictions() {
57 return restrictions;
58 }
59
60
61
62
63
64 @Deprecated
65 public VersionRange cloneOf() {
66 List<Restriction> copiedRestrictions = null;
67
68 if (restrictions != null) {
69 copiedRestrictions = new ArrayList<>();
70
71 if (!restrictions.isEmpty()) {
72 copiedRestrictions.addAll(restrictions);
73 }
74 }
75
76 return new VersionRange(recommendedVersion, copiedRestrictions);
77 }
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98 public static VersionRange createFromVersionSpec(String spec) throws InvalidVersionSpecificationException {
99 if (spec == null) {
100 return null;
101 }
102
103 VersionRange cached = CACHE_SPEC.get(spec);
104 if (cached != null) {
105 return cached;
106 }
107
108 List<Restriction> restrictions = new ArrayList<>();
109 String process = spec;
110 ArtifactVersion version = null;
111 ArtifactVersion upperBound = null;
112 ArtifactVersion lowerBound = null;
113
114 while (process.startsWith("[") || process.startsWith("(")) {
115 int index1 = process.indexOf(')');
116 int index2 = process.indexOf(']');
117
118 int index = index2;
119 if (index2 < 0 || index1 < index2) {
120 if (index1 >= 0) {
121 index = index1;
122 }
123 }
124
125 if (index < 0) {
126 throw new InvalidVersionSpecificationException("Unbounded range: " + spec);
127 }
128
129 Restriction restriction = parseRestriction(process.substring(0, index + 1));
130 if (lowerBound == null) {
131 lowerBound = restriction.getLowerBound();
132 }
133 if (upperBound != null) {
134 if (restriction.getLowerBound() == null
135 || restriction.getLowerBound().compareTo(upperBound) < 0) {
136 throw new InvalidVersionSpecificationException("Ranges overlap: " + spec);
137 }
138 }
139 restrictions.add(restriction);
140 upperBound = restriction.getUpperBound();
141
142 process = process.substring(index + 1).trim();
143
144 if (process.length() > 0 && process.startsWith(",")) {
145 process = process.substring(1).trim();
146 }
147 }
148
149 if (process.length() > 0) {
150 if (restrictions.size() > 0) {
151 throw new InvalidVersionSpecificationException(
152 "Only fully-qualified sets allowed in multiple set scenario: " + spec);
153 } else {
154 version = new DefaultArtifactVersion(process);
155 restrictions.add(Restriction.EVERYTHING);
156 }
157 }
158
159 cached = new VersionRange(version, restrictions);
160 CACHE_SPEC.put(spec, cached);
161 return cached;
162 }
163
164 private static Restriction parseRestriction(String spec) throws InvalidVersionSpecificationException {
165 boolean lowerBoundInclusive = spec.startsWith("[");
166 boolean upperBoundInclusive = spec.endsWith("]");
167
168 String process = spec.substring(1, spec.length() - 1).trim();
169
170 Restriction restriction;
171
172 int index = process.indexOf(',');
173
174 if (index < 0) {
175 if (!lowerBoundInclusive || !upperBoundInclusive) {
176 throw new InvalidVersionSpecificationException("Single version must be surrounded by []: " + spec);
177 }
178
179 ArtifactVersion version = new DefaultArtifactVersion(process);
180
181 restriction = new Restriction(version, lowerBoundInclusive, version, upperBoundInclusive);
182 } else {
183 String lowerBound = process.substring(0, index).trim();
184 String upperBound = process.substring(index + 1).trim();
185
186 ArtifactVersion lowerVersion = null;
187 if (lowerBound.length() > 0) {
188 lowerVersion = new DefaultArtifactVersion(lowerBound);
189 }
190 ArtifactVersion upperVersion = null;
191 if (upperBound.length() > 0) {
192 upperVersion = new DefaultArtifactVersion(upperBound);
193 }
194
195 if (upperVersion != null && lowerVersion != null) {
196 int result = upperVersion.compareTo(lowerVersion);
197 if (result < 0 || (result == 0 && (!lowerBoundInclusive || !upperBoundInclusive))) {
198 throw new InvalidVersionSpecificationException("Range defies version ordering: " + spec);
199 }
200 }
201
202 restriction = new Restriction(lowerVersion, lowerBoundInclusive, upperVersion, upperBoundInclusive);
203 }
204
205 return restriction;
206 }
207
208 public static VersionRange createFromVersion(String version) {
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 }