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