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