View Javadoc
1   package org.eclipse.aether.util.version;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   * 
12   *  http://www.apache.org/licenses/LICENSE-2.0
13   * 
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import org.eclipse.aether.version.InvalidVersionSpecificationException;
23  import org.eclipse.aether.version.Version;
24  import org.eclipse.aether.version.VersionRange;
25  
26  /**
27   * A version range inspired by mathematical range syntax. For example, "[1.0,2.0)", "[1.0,)" or "[1.0]".
28   */
29  final class GenericVersionRange
30      implements VersionRange
31  {
32  
33      private final Bound lowerBound;
34  
35      private final Bound upperBound;
36  
37      /**
38       * Creates a version range from the specified range specification.
39       * 
40       * @param range The range specification to parse, must not be {@code null}.
41       * @throws InvalidVersionSpecificationException If the range could not be parsed.
42       */
43      GenericVersionRange( String range )
44          throws InvalidVersionSpecificationException
45      {
46          String process = range;
47  
48          boolean lowerBoundInclusive, upperBoundInclusive;
49          Version lowerBound, upperBound;
50  
51          if ( range.startsWith( "[" ) )
52          {
53              lowerBoundInclusive = true;
54          }
55          else if ( range.startsWith( "(" ) )
56          {
57              lowerBoundInclusive = false;
58          }
59          else
60          {
61              throw new InvalidVersionSpecificationException( range, "Invalid version range " + range
62                  + ", a range must start with either [ or (" );
63          }
64  
65          if ( range.endsWith( "]" ) )
66          {
67              upperBoundInclusive = true;
68          }
69          else if ( range.endsWith( ")" ) )
70          {
71              upperBoundInclusive = false;
72          }
73          else
74          {
75              throw new InvalidVersionSpecificationException( range, "Invalid version range " + range
76                  + ", a range must end with either [ or (" );
77          }
78  
79          process = process.substring( 1, process.length() - 1 );
80  
81          int index = process.indexOf( "," );
82  
83          if ( index < 0 )
84          {
85              if ( !lowerBoundInclusive || !upperBoundInclusive )
86              {
87                  throw new InvalidVersionSpecificationException( range, "Invalid version range " + range
88                      + ", single version must be surrounded by []" );
89              }
90  
91              String version = process.trim();
92              if ( version.endsWith( ".*" ) )
93              {
94                  String prefix = version.substring( 0, version.length() - 1 );
95                  lowerBound = parse( prefix + "min" );
96                  upperBound = parse( prefix + "max" );
97              }
98              else
99              {
100                 lowerBound = upperBound = parse( version );
101             }
102         }
103         else
104         {
105             String parsedLowerBound = process.substring( 0, index ).trim();
106             String parsedUpperBound = process.substring( index + 1 ).trim();
107 
108             // more than two bounds, e.g. (1,2,3)
109             if ( parsedUpperBound.contains( "," ) )
110             {
111                 throw new InvalidVersionSpecificationException( range, "Invalid version range " + range
112                     + ", bounds may not contain additional ','" );
113             }
114 
115             lowerBound = parsedLowerBound.length() > 0 ? parse( parsedLowerBound ) : null;
116             upperBound = parsedUpperBound.length() > 0 ? parse( parsedUpperBound ) : null;
117 
118             if ( upperBound != null && lowerBound != null )
119             {
120                 if ( upperBound.compareTo( lowerBound ) < 0 )
121                 {
122                     throw new InvalidVersionSpecificationException( range, "Invalid version range " + range
123                         + ", lower bound must not be greater than upper bound" );
124                 }
125             }
126         }
127 
128         this.lowerBound = ( lowerBound != null ) ? new Bound( lowerBound, lowerBoundInclusive ) : null;
129         this.upperBound = ( upperBound != null ) ? new Bound( upperBound, upperBoundInclusive ) : null;
130     }
131 
132     private Version parse( String version )
133     {
134         return new GenericVersion( version );
135     }
136 
137     public Bound getLowerBound()
138     {
139         return lowerBound;
140     }
141 
142     public Bound getUpperBound()
143     {
144         return upperBound;
145     }
146 
147     public boolean containsVersion( Version version )
148     {
149         if ( lowerBound != null )
150         {
151             int comparison = lowerBound.getVersion().compareTo( version );
152 
153             if ( comparison == 0 && !lowerBound.isInclusive() )
154             {
155                 return false;
156             }
157             if ( comparison > 0 )
158             {
159                 return false;
160             }
161         }
162 
163         if ( upperBound != null )
164         {
165             int comparison = upperBound.getVersion().compareTo( version );
166 
167             if ( comparison == 0 && !upperBound.isInclusive() )
168             {
169                 return false;
170             }
171             if ( comparison < 0 )
172             {
173                 return false;
174             }
175         }
176 
177         return true;
178     }
179 
180     @Override
181     public boolean equals( Object obj )
182     {
183         if ( obj == this )
184         {
185             return true;
186         }
187         else if ( obj == null || !getClass().equals( obj.getClass() ) )
188         {
189             return false;
190         }
191 
192         GenericVersionRange that = (GenericVersionRange) obj;
193 
194         return eq( upperBound, that.upperBound ) && eq( lowerBound, that.lowerBound );
195     }
196 
197     private static <T> boolean eq( T s1, T s2 )
198     {
199         return s1 != null ? s1.equals( s2 ) : s2 == null;
200     }
201 
202     @Override
203     public int hashCode()
204     {
205         int hash = 17;
206         hash = hash * 31 + hash( upperBound );
207         hash = hash * 31 + hash( lowerBound );
208         return hash;
209     }
210 
211     private static int hash( Object obj )
212     {
213         return obj != null ? obj.hashCode() : 0;
214     }
215 
216     @Override
217     public String toString()
218     {
219         StringBuilder buffer = new StringBuilder( 64 );
220         if ( lowerBound != null )
221         {
222             buffer.append( lowerBound.isInclusive() ? '[' : '(' );
223             buffer.append( lowerBound.getVersion() );
224         }
225         else
226         {
227             buffer.append( '(' );
228         }
229         buffer.append( ',' );
230         if ( upperBound != null )
231         {
232             buffer.append( upperBound.getVersion() );
233             buffer.append( upperBound.isInclusive() ? ']' : ')' );
234         }
235         else
236         {
237             buffer.append( ')' );
238         }
239         return buffer.toString();
240     }
241 
242 }