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.isEmpty()) {
146 if (!restrictions.isEmpty()) {
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.isEmpty()) {
184 lowerVersion = new DefaultArtifactVersion(lowerBound);
185 }
186 ArtifactVersion upperVersion = null;
187 if (!upperBound.isEmpty()) {
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 }