1 package org.apache.maven.artifact.versioning;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import java.util.ArrayList;
23 import java.util.Collections;
24 import java.util.Iterator;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.WeakHashMap;
28 import java.util.Objects;
29
30 import org.apache.maven.artifact.Artifact;
31
32
33
34
35
36
37 public class VersionRange
38 {
39 private static final Map<String, VersionRange> CACHE_SPEC =
40 Collections.<String, VersionRange>synchronizedMap( new WeakHashMap<String, VersionRange>() );
41
42 private static final Map<String, VersionRange> CACHE_VERSION =
43 Collections.<String, VersionRange>synchronizedMap( new WeakHashMap<String, VersionRange>() );
44
45 private final ArtifactVersion recommendedVersion;
46
47 private final List<Restriction> restrictions;
48
49 private VersionRange( ArtifactVersion recommendedVersion,
50 List<Restriction> restrictions )
51 {
52 this.recommendedVersion = recommendedVersion;
53 this.restrictions = restrictions;
54 }
55
56 public ArtifactVersion getRecommendedVersion()
57 {
58 return recommendedVersion;
59 }
60
61 public List<Restriction> getRestrictions()
62 {
63 return restrictions;
64 }
65
66
67
68
69
70 @Deprecated
71 public VersionRange cloneOf()
72 {
73 List<Restriction> copiedRestrictions = null;
74
75 if ( restrictions != null )
76 {
77 copiedRestrictions = new ArrayList<>();
78
79 if ( !restrictions.isEmpty() )
80 {
81 copiedRestrictions.addAll( restrictions );
82 }
83 }
84
85 return new VersionRange( recommendedVersion, copiedRestrictions );
86 }
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107 public static VersionRange createFromVersionSpec( String spec )
108 throws InvalidVersionSpecificationException
109 {
110 if ( spec == null )
111 {
112 return null;
113 }
114
115 VersionRange cached = CACHE_SPEC.get( spec );
116 if ( cached != null )
117 {
118 return cached;
119 }
120
121 List<Restriction> restrictions = new ArrayList<>();
122 String process = spec;
123 ArtifactVersion version = null;
124 ArtifactVersion upperBound = null;
125 ArtifactVersion lowerBound = null;
126
127 while ( process.startsWith( "[" ) || process.startsWith( "(" ) )
128 {
129 int index1 = process.indexOf( ')' );
130 int index2 = process.indexOf( ']' );
131
132 int index = index2;
133 if ( index2 < 0 || index1 < index2 )
134 {
135 if ( index1 >= 0 )
136 {
137 index = index1;
138 }
139 }
140
141 if ( index < 0 )
142 {
143 throw new InvalidVersionSpecificationException( "Unbounded range: " + spec );
144 }
145
146 Restriction restriction = parseRestriction( process.substring( 0, index + 1 ) );
147 if ( lowerBound == null )
148 {
149 lowerBound = restriction.getLowerBound();
150 }
151 if ( upperBound != null )
152 {
153 if ( restriction.getLowerBound() == null || restriction.getLowerBound().compareTo( upperBound ) < 0 )
154 {
155 throw new InvalidVersionSpecificationException( "Ranges overlap: " + spec );
156 }
157 }
158 restrictions.add( restriction );
159 upperBound = restriction.getUpperBound();
160
161 process = process.substring( index + 1 ).trim();
162
163 if ( process.length() > 0 && process.startsWith( "," ) )
164 {
165 process = process.substring( 1 ).trim();
166 }
167 }
168
169 if ( process.length() > 0 )
170 {
171 if ( restrictions.size() > 0 )
172 {
173 throw new InvalidVersionSpecificationException(
174 "Only fully-qualified sets allowed in multiple set scenario: " + spec );
175 }
176 else
177 {
178 version = new DefaultArtifactVersion( process );
179 restrictions.add( Restriction.EVERYTHING );
180 }
181 }
182
183 cached = new VersionRange( version, restrictions );
184 CACHE_SPEC.put( spec, cached );
185 return cached;
186 }
187
188 private static Restriction parseRestriction( String spec )
189 throws InvalidVersionSpecificationException
190 {
191 boolean lowerBoundInclusive = spec.startsWith( "[" );
192 boolean upperBoundInclusive = spec.endsWith( "]" );
193
194 String process = spec.substring( 1, spec.length() - 1 ).trim();
195
196 Restriction restriction;
197
198 int index = process.indexOf( ',' );
199
200 if ( index < 0 )
201 {
202 if ( !lowerBoundInclusive || !upperBoundInclusive )
203 {
204 throw new InvalidVersionSpecificationException( "Single version must be surrounded by []: " + spec );
205 }
206
207 ArtifactVersion version = new DefaultArtifactVersion( process );
208
209 restriction = new Restriction( version, lowerBoundInclusive, version, upperBoundInclusive );
210 }
211 else
212 {
213 String lowerBound = process.substring( 0, index ).trim();
214 String upperBound = process.substring( index + 1 ).trim();
215 if ( lowerBound.equals( upperBound ) )
216 {
217 throw new InvalidVersionSpecificationException( "Range cannot have identical boundaries: " + spec );
218 }
219
220 ArtifactVersion lowerVersion = null;
221 if ( lowerBound.length() > 0 )
222 {
223 lowerVersion = new DefaultArtifactVersion( lowerBound );
224 }
225 ArtifactVersion upperVersion = null;
226 if ( upperBound.length() > 0 )
227 {
228 upperVersion = new DefaultArtifactVersion( upperBound );
229 }
230
231 if ( upperVersion != null && lowerVersion != null && upperVersion.compareTo( lowerVersion ) < 0 )
232 {
233 throw new InvalidVersionSpecificationException( "Range defies version ordering: " + spec );
234 }
235
236 restriction = new Restriction( lowerVersion, lowerBoundInclusive, upperVersion, upperBoundInclusive );
237 }
238
239 return restriction;
240 }
241
242 public static VersionRange createFromVersion( String version )
243 {
244 VersionRange cached = CACHE_VERSION.get( version );
245 if ( cached == null )
246 {
247 List<Restriction> restrictions = Collections.emptyList();
248 cached = new VersionRange( new DefaultArtifactVersion( version ), restrictions );
249 CACHE_VERSION.put( version, cached );
250 }
251 return cached;
252 }
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282 public VersionRange restrict( VersionRange restriction )
283 {
284 List<Restriction> r1 = this.restrictions;
285 List<Restriction> r2 = restriction.restrictions;
286 List<Restriction> restrictions;
287
288 if ( r1.isEmpty() || r2.isEmpty() )
289 {
290 restrictions = Collections.emptyList();
291 }
292 else
293 {
294 restrictions = Collections.unmodifiableList( intersection( r1, r2 ) );
295 }
296
297 ArtifactVersion version = null;
298 if ( restrictions.size() > 0 )
299 {
300 for ( Restriction r : restrictions )
301 {
302 if ( recommendedVersion != null && r.containsVersion( recommendedVersion ) )
303 {
304
305 version = recommendedVersion;
306 break;
307 }
308 else if ( version == null && restriction.getRecommendedVersion() != null
309 && r.containsVersion( restriction.getRecommendedVersion() ) )
310 {
311
312 version = restriction.getRecommendedVersion();
313 }
314 }
315 }
316
317 else if ( recommendedVersion != null )
318 {
319
320 version = recommendedVersion;
321 }
322 else if ( restriction.recommendedVersion != null )
323 {
324
325
326 version = restriction.recommendedVersion;
327 }
328
329
330
331
332
333
334
335 return new VersionRange( version, restrictions );
336 }
337
338 private List<Restriction> intersection( List<Restriction> r1, List<Restriction> r2 )
339 {
340 List<Restriction> restrictions = new ArrayList<>( r1.size() + r2.size() );
341 Iterator<Restriction> i1 = r1.iterator();
342 Iterator<Restriction> i2 = r2.iterator();
343 Restriction res1 = i1.next();
344 Restriction res2 = i2.next();
345
346 boolean done = false;
347 while ( !done )
348 {
349 if ( res1.getLowerBound() == null || res2.getUpperBound() == null
350 || res1.getLowerBound().compareTo( res2.getUpperBound() ) <= 0 )
351 {
352 if ( res1.getUpperBound() == null || res2.getLowerBound() == null
353 || res1.getUpperBound().compareTo( res2.getLowerBound() ) >= 0 )
354 {
355 ArtifactVersion lower;
356 ArtifactVersion upper;
357 boolean lowerInclusive;
358 boolean upperInclusive;
359
360
361 if ( res1.getLowerBound() == null )
362 {
363 lower = res2.getLowerBound();
364 lowerInclusive = res2.isLowerBoundInclusive();
365 }
366 else if ( res2.getLowerBound() == null )
367 {
368 lower = res1.getLowerBound();
369 lowerInclusive = res1.isLowerBoundInclusive();
370 }
371 else
372 {
373 int comparison = res1.getLowerBound().compareTo( res2.getLowerBound() );
374 if ( comparison < 0 )
375 {
376 lower = res2.getLowerBound();
377 lowerInclusive = res2.isLowerBoundInclusive();
378 }
379 else if ( comparison == 0 )
380 {
381 lower = res1.getLowerBound();
382 lowerInclusive = res1.isLowerBoundInclusive() && res2.isLowerBoundInclusive();
383 }
384 else
385 {
386 lower = res1.getLowerBound();
387 lowerInclusive = res1.isLowerBoundInclusive();
388 }
389 }
390
391 if ( res1.getUpperBound() == null )
392 {
393 upper = res2.getUpperBound();
394 upperInclusive = res2.isUpperBoundInclusive();
395 }
396 else if ( res2.getUpperBound() == null )
397 {
398 upper = res1.getUpperBound();
399 upperInclusive = res1.isUpperBoundInclusive();
400 }
401 else
402 {
403 int comparison = res1.getUpperBound().compareTo( res2.getUpperBound() );
404 if ( comparison < 0 )
405 {
406 upper = res1.getUpperBound();
407 upperInclusive = res1.isUpperBoundInclusive();
408 }
409 else if ( comparison == 0 )
410 {
411 upper = res1.getUpperBound();
412 upperInclusive = res1.isUpperBoundInclusive() && res2.isUpperBoundInclusive();
413 }
414 else
415 {
416 upper = res2.getUpperBound();
417 upperInclusive = res2.isUpperBoundInclusive();
418 }
419 }
420
421
422 if ( lower == null || upper == null || lower.compareTo( upper ) != 0 )
423 {
424 restrictions.add( new Restriction( lower, lowerInclusive, upper, upperInclusive ) );
425 }
426 else if ( lowerInclusive && upperInclusive )
427 {
428 restrictions.add( new Restriction( lower, lowerInclusive, upper, upperInclusive ) );
429 }
430
431
432 if ( upper == res2.getUpperBound() )
433 {
434
435 if ( i2.hasNext() )
436 {
437 res2 = i2.next();
438 }
439 else
440 {
441 done = true;
442 }
443 }
444 else
445 {
446
447 if ( i1.hasNext() )
448 {
449 res1 = i1.next();
450 }
451 else
452 {
453 done = true;
454 }
455 }
456 }
457 else
458 {
459
460 if ( i1.hasNext() )
461 {
462 res1 = i1.next();
463 }
464 else
465 {
466 done = true;
467 }
468 }
469 }
470 else
471 {
472
473 if ( i2.hasNext() )
474 {
475 res2 = i2.next();
476 }
477 else
478 {
479 done = true;
480 }
481 }
482 }
483
484 return restrictions;
485 }
486
487 public ArtifactVersion getSelectedVersion( Artifact artifact )
488 throws OverConstrainedVersionException
489 {
490 ArtifactVersion version;
491 if ( recommendedVersion != null )
492 {
493 version = recommendedVersion;
494 }
495 else
496 {
497 if ( restrictions.size() == 0 )
498 {
499 throw new OverConstrainedVersionException( "The artifact has no valid ranges", artifact );
500 }
501
502 version = null;
503 }
504 return version;
505 }
506
507 public boolean isSelectedVersionKnown( Artifact artifact )
508 throws OverConstrainedVersionException
509 {
510 boolean value = false;
511 if ( recommendedVersion != null )
512 {
513 value = true;
514 }
515 else
516 {
517 if ( restrictions.size() == 0 )
518 {
519 throw new OverConstrainedVersionException( "The artifact has no valid ranges", artifact );
520 }
521 }
522 return value;
523 }
524
525 public String toString()
526 {
527 if ( recommendedVersion != null )
528 {
529 return recommendedVersion.toString();
530 }
531 else
532 {
533 StringBuilder buf = new StringBuilder();
534 for ( Iterator<Restriction> i = restrictions.iterator(); i.hasNext(); )
535 {
536 Restriction r = i.next();
537
538 buf.append( r.toString() );
539
540 if ( i.hasNext() )
541 {
542 buf.append( ',' );
543 }
544 }
545 return buf.toString();
546 }
547 }
548
549 public ArtifactVersion matchVersion( List<ArtifactVersion> versions )
550 {
551
552
553 ArtifactVersion matched = null;
554 for ( ArtifactVersion version : versions )
555 {
556 if ( containsVersion( version ) )
557 {
558
559 if ( matched == null || version.compareTo( matched ) > 0 )
560 {
561 matched = version;
562 }
563 }
564 }
565 return matched;
566 }
567
568 public boolean containsVersion( ArtifactVersion version )
569 {
570 for ( Restriction restriction : restrictions )
571 {
572 if ( restriction.containsVersion( version ) )
573 {
574 return true;
575 }
576 }
577 return false;
578 }
579
580 public boolean hasRestrictions()
581 {
582 return !restrictions.isEmpty() && recommendedVersion == null;
583 }
584
585 public boolean equals( Object obj )
586 {
587 if ( this == obj )
588 {
589 return true;
590 }
591 if ( !( obj instanceof VersionRange ) )
592 {
593 return false;
594 }
595 VersionRange other = (VersionRange) obj;
596
597 return Objects.equals( recommendedVersion, other.recommendedVersion )
598 && Objects.equals( restrictions, other.restrictions );
599 }
600
601 public int hashCode()
602 {
603 int hash = 7;
604 hash = 31 * hash + ( recommendedVersion == null ? 0 : recommendedVersion.hashCode() );
605 hash = 31 * hash + ( restrictions == null ? 0 : restrictions.hashCode() );
606 return hash;
607 }
608 }