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<Restriction>();
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<Restriction>();
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<Restriction>( 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 }