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