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