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