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