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  
35  public class VersionRange
36  {
37      private final ArtifactVersion recommendedVersion;
38  
39      private final List<Restriction> restrictions;
40  
41      private VersionRange( ArtifactVersion recommendedVersion,
42                            List<Restriction> restrictions )
43      {
44          this.recommendedVersion = recommendedVersion;
45          this.restrictions = restrictions;
46      }
47  
48      public ArtifactVersion getRecommendedVersion()
49      {
50          return recommendedVersion;
51      }
52  
53      public List<Restriction> getRestrictions()
54      {
55          return restrictions;
56      }
57  
58      public VersionRange cloneOf()
59      {
60          List<Restriction> copiedRestrictions = null;
61  
62          if ( restrictions != null )
63          {
64              copiedRestrictions = new ArrayList<Restriction>();
65  
66              if ( !restrictions.isEmpty() )
67              {
68                  copiedRestrictions.addAll( restrictions );
69              }
70          }
71  
72          return new VersionRange( recommendedVersion, copiedRestrictions );
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<Restriction>();
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 = 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<Restriction>( 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 }