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  import org.eclipse.aether.version.VersionScheme;
27  
28  import static java.util.Objects.requireNonNull;
29  
30  /**
31   * A version range inspired by mathematical range syntax. For example, "[1.0,2.0)", "[1.0,)" or "[1.0]".
32   * <p>
33   * Despite its name, this class is generic in a sense it works with any {@link Version}
34   */
35  final class GenericVersionRange implements VersionRange {
36      private final VersionScheme versionScheme;
37  
38      private final Bound lowerBound;
39  
40      private final Bound upperBound;
41  
42      /**
43       * Creates a version range from the specified range specification.
44       *
45       * @param range The range specification to parse, must not be {@code null}.
46       * @throws InvalidVersionSpecificationException If the range could not be parsed.
47       */
48      GenericVersionRange(VersionScheme versionScheme, String range) throws InvalidVersionSpecificationException {
49          this.versionScheme = requireNonNull(versionScheme, "versionScheme cannot be null");
50          String process = requireNonNull(range, "version range cannot be null");
51  
52          boolean lowerBoundInclusive, upperBoundInclusive;
53          Version lowerBound, upperBound;
54  
55          if (range.startsWith("[")) {
56              lowerBoundInclusive = true;
57          } else if (range.startsWith("(")) {
58              lowerBoundInclusive = false;
59          } else {
60              throw new InvalidVersionSpecificationException(
61                      range, "Invalid version range " + range + ", a range must start with either [ or (");
62          }
63  
64          if (range.endsWith("]")) {
65              upperBoundInclusive = true;
66          } else if (range.endsWith(")")) {
67              upperBoundInclusive = false;
68          } else {
69              throw new InvalidVersionSpecificationException(
70                      range, "Invalid version range " + range + ", a range must end with either [ or (");
71          }
72  
73          process = process.substring(1, process.length() - 1);
74  
75          int index = process.indexOf(",");
76  
77          if (index < 0) {
78              if (!lowerBoundInclusive || !upperBoundInclusive) {
79                  throw new InvalidVersionSpecificationException(
80                          range, "Invalid version range " + range + ", single version must be surrounded by []");
81              }
82  
83              String version = process.trim();
84              if (version.endsWith(".*")) {
85                  String prefix = version.substring(0, version.length() - 1);
86                  lowerBound = parse(prefix + "min");
87                  upperBound = parse(prefix + "max");
88              } else {
89                  lowerBound = parse(version);
90                  upperBound = lowerBound;
91              }
92          } else {
93              String parsedLowerBound = process.substring(0, index).trim();
94              String parsedUpperBound = process.substring(index + 1).trim();
95  
96              // more than two bounds, e.g. (1,2,3)
97              if (parsedUpperBound.contains(",")) {
98                  throw new InvalidVersionSpecificationException(
99                          range, "Invalid version range " + range + ", bounds may not contain additional ','");
100             }
101 
102             lowerBound = !parsedLowerBound.isEmpty() ? parse(parsedLowerBound) : null;
103             upperBound = !parsedUpperBound.isEmpty() ? parse(parsedUpperBound) : null;
104 
105             if (upperBound != null && lowerBound != null) {
106                 if (upperBound.compareTo(lowerBound) < 0) {
107                     throw new InvalidVersionSpecificationException(
108                             range,
109                             "Invalid version range " + range + ", lower bound must not be greater than upper bound");
110                 }
111             }
112         }
113 
114         this.lowerBound = (lowerBound != null) ? new Bound(lowerBound, lowerBoundInclusive) : null;
115         this.upperBound = (upperBound != null) ? new Bound(upperBound, upperBoundInclusive) : null;
116     }
117 
118     private Version parse(String version) throws InvalidVersionSpecificationException {
119         return versionScheme.parseVersion(version);
120     }
121 
122     @Override
123     public Bound getLowerBound() {
124         return lowerBound;
125     }
126 
127     @Override
128     public Bound getUpperBound() {
129         return upperBound;
130     }
131 
132     @Override
133     public boolean containsVersion(Version version) {
134         if (lowerBound != null) {
135             int comparison = lowerBound.getVersion().compareTo(version);
136 
137             if (comparison == 0 && !lowerBound.isInclusive()) {
138                 return false;
139             }
140             if (comparison > 0) {
141                 return false;
142             }
143         }
144 
145         if (upperBound != null) {
146             int comparison = upperBound.getVersion().compareTo(version);
147 
148             if (comparison == 0 && !upperBound.isInclusive()) {
149                 return false;
150             }
151             if (comparison < 0) {
152                 return false;
153             }
154         }
155 
156         return true;
157     }
158 
159     @Override
160     public boolean equals(Object obj) {
161         if (obj == this) {
162             return true;
163         } else if (obj == null || !getClass().equals(obj.getClass())) {
164             return false;
165         }
166 
167         VersionRange that = (VersionRange) obj;
168 
169         return Objects.equals(upperBound, that.getUpperBound()) && Objects.equals(lowerBound, that.getLowerBound());
170     }
171 
172     @Override
173     public int hashCode() {
174         int hash = 17;
175         hash = hash * 31 + hash(upperBound);
176         hash = hash * 31 + hash(lowerBound);
177         return hash;
178     }
179 
180     private static int hash(Object obj) {
181         return obj != null ? obj.hashCode() : 0;
182     }
183 
184     @Override
185     public String toString() {
186         StringBuilder buffer = new StringBuilder(64);
187         if (lowerBound != null) {
188             buffer.append(lowerBound.isInclusive() ? '[' : '(');
189             buffer.append(lowerBound.getVersion());
190         } else {
191             buffer.append('(');
192         }
193         buffer.append(',');
194         if (upperBound != null) {
195             buffer.append(upperBound.getVersion());
196             buffer.append(upperBound.isInclusive() ? ']' : ')');
197         } else {
198             buffer.append(')');
199         }
200         return buffer.toString();
201     }
202 }