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