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 }