1 package com.atlassian.plugin.util;
2
3 import java.util.Comparator;
4 import java.util.Objects;
5 import java.util.regex.Matcher;
6 import java.util.regex.Pattern;
7
8 import static com.atlassian.plugin.util.VersionStringComparator.VALID_VERSION_PATTERN;
9 import static com.google.common.base.Preconditions.checkNotNull;
10 import static com.google.common.base.Preconditions.checkState;
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58 public abstract class VersionRange {
59 private static final Pattern RANGE_PATTERN = Pattern.compile("(\\(|\\[)?(" + VALID_VERSION_PATTERN + ")?(?:,(" + VALID_VERSION_PATTERN + ")?)?(\\)|\\])?");
60
61 private VersionRange() {
62 }
63
64 abstract boolean isInRange(String version);
65
66 public VersionRange or(VersionRange other) {
67 return new OrVersionRange(this, other);
68 }
69
70 public static VersionRange empty() {
71 return new EmptyVersionRange();
72 }
73
74 public static VersionRange all() {
75 return new AllVersionRange();
76 }
77
78 public static VersionRange parse(String range) {
79 final Matcher matcher = RANGE_PATTERN.matcher(range);
80 checkState(matcher.matches(), "Range '" + range + "' doesn't match pattern " + RANGE_PATTERN.pattern());
81
82 final String leftParenthesis = matcher.group(1);
83 final String leftVersion = matcher.group(2);
84 final String rightVersion = matcher.group(3);
85 final String rightParenthesis = matcher.group(4);
86
87 checkState(leftVersion != null || rightVersion != null, "No version configured for range!");
88
89 if (leftParenthesis == null) {
90 checkState(leftVersion != null);
91 checkState(rightParenthesis == null);
92 checkState(rightVersion == null);
93 return VersionRange.include(leftVersion).unbounded();
94 } else if (leftParenthesis.equals("[") && rightParenthesis.equals("]") && rightVersion == null)
95 {
96 return VersionRange.single(leftVersion);
97 } else {
98 final ActualVersionRangeBuilder builder;
99 if (leftParenthesis.equals("[")) {
100 checkState(leftVersion != null);
101 builder = VersionRange.include(leftVersion);
102 } else if (leftParenthesis.equals("(")) {
103 if (leftVersion != null) {
104 builder = VersionRange.exclude(leftVersion);
105 } else {
106 builder = VersionRange.unbounded();
107 }
108 } else {
109 throw new IllegalStateException("Incorrect start of range! " + leftParenthesis);
110 }
111 if (rightParenthesis.equals("]")) {
112 checkState(rightVersion != null);
113 return builder.include(rightVersion);
114 } else if (rightParenthesis.equals(")")) {
115 if (rightVersion != null) {
116 return builder.exclude(rightVersion);
117 } else {
118 return builder.unbounded();
119 }
120 } else {
121 throw new IllegalStateException("Incorrect ent of range! " + rightParenthesis);
122 }
123 }
124 }
125
126 public static VersionRange single(String version) {
127 return new SingleVersionRange(version);
128 }
129
130 public static ActualVersionRangeBuilder include(String version) {
131 return new ActualVersionRangeBuilder(true, version);
132 }
133
134 public static ActualVersionRangeBuilder exclude(String version) {
135 return new ActualVersionRangeBuilder(false, version);
136 }
137
138 public static ActualVersionRangeBuilder unbounded() {
139 return new ActualVersionRangeBuilder(true, null);
140 }
141
142 private static class SingleVersionRange extends VersionRange {
143 private final String version;
144
145 private SingleVersionRange(String version) {
146 this.version = checkNotNull(version);
147 }
148
149 @Override
150 boolean isInRange(String v) {
151 return newVersionComparator().compare(this.version, v) == 0;
152 }
153
154 @Override
155 public int hashCode() {
156 return Objects.hash(version);
157 }
158
159 @Override
160 public boolean equals(Object obj) {
161 if (obj == null) {
162 return false;
163 }
164 if (getClass() != obj.getClass()) {
165 return false;
166 }
167 final SingleVersionRange that = (SingleVersionRange) obj;
168 return Objects.equals(this.version, that.version);
169 }
170
171 @Override
172 public String toString() {
173 return "[" + version + "]";
174 }
175 }
176
177 public static final class ActualVersionRangeBuilder {
178 private final boolean leftIncluded;
179 private final String leftVersion;
180
181 public ActualVersionRangeBuilder(boolean leftIncluded, String leftVersion) {
182 this.leftIncluded = leftIncluded;
183 this.leftVersion = leftVersion;
184 }
185
186 public VersionRange include(String version) {
187 return newRange(version, true);
188 }
189
190 public VersionRange exclude(String version) {
191 return newRange(version, false);
192 }
193
194 private VersionRange newRange(String version, boolean rightIncluded) {
195 if (leftVersion != null) {
196 return newActualRange(version, rightIncluded);
197 } else {
198 return newLeftUnboundedRange(version, rightIncluded);
199 }
200 }
201
202 private LeftUnboundedVersionRange newLeftUnboundedRange(String version, boolean rightIncluded) {
203 return new LeftUnboundedVersionRange(rightIncluded, version);
204 }
205
206 private ActualVersionRange newActualRange(String version, boolean rightIncluded) {
207 return new ActualVersionRange(leftIncluded, leftVersion, rightIncluded, version);
208 }
209
210 public VersionRange unbounded() {
211 if (leftVersion == null) {
212 throw new IllegalStateException();
213 }
214 return new RightUnboundedVersionRange(leftIncluded, leftVersion);
215 }
216 }
217
218 private static final class ActualVersionRange extends VersionRange {
219 private final boolean leftIncluded;
220 private final String leftVersion;
221 private final boolean rightIncluded;
222 private final String rightVersion;
223
224 private ActualVersionRange(
225 boolean leftIncluded, String leftVersion,
226 boolean rightIncluded, String rightVersion) {
227 this.leftIncluded = leftIncluded;
228 this.leftVersion = checkNotNull(leftVersion);
229 this.rightIncluded = rightIncluded;
230 this.rightVersion = checkNotNull(rightVersion);
231 }
232
233 @Override
234 boolean isInRange(String v) {
235 return isGreaterThan(leftIncluded, leftVersion, v)
236 && isLowerThan(v, rightVersion, rightIncluded);
237 }
238
239 @Override
240 public int hashCode() {
241 return Objects.hash(leftIncluded, leftVersion, rightIncluded, rightVersion);
242 }
243
244 @Override
245 public boolean equals(Object obj) {
246 if (obj == null) {
247 return false;
248 }
249 if (getClass() != obj.getClass()) {
250 return false;
251 }
252 final ActualVersionRange that = (ActualVersionRange) obj;
253 return Objects.equals(this.leftIncluded, that.leftIncluded)
254 && Objects.equals(this.leftVersion, that.leftVersion)
255 && Objects.equals(this.rightIncluded, that.rightIncluded)
256 && Objects.equals(this.rightVersion, that.rightVersion);
257 }
258
259 @Override
260 public String toString() {
261 return (leftIncluded ? "[" : "(") + leftVersion + "," + rightVersion + (rightIncluded ? "]" : ")");
262 }
263 }
264
265 private static final class LeftUnboundedVersionRange extends VersionRange {
266 private final boolean rightIncluded;
267 private final String rightVersion;
268
269 private LeftUnboundedVersionRange(boolean rightIncluded, String rightVersion) {
270 this.rightIncluded = rightIncluded;
271 this.rightVersion = checkNotNull(rightVersion);
272 }
273
274 @Override
275 boolean isInRange(String v) {
276 return isLowerThan(v, rightVersion, rightIncluded);
277 }
278
279 @Override
280 public int hashCode() {
281 return Objects.hash(rightIncluded, rightVersion);
282 }
283
284 @Override
285 public boolean equals(Object obj) {
286 if (obj == null) {
287 return false;
288 }
289 if (getClass() != obj.getClass()) {
290 return false;
291 }
292 final LeftUnboundedVersionRange that = (LeftUnboundedVersionRange) obj;
293 return Objects.equals(this.rightIncluded, that.rightIncluded)
294 && Objects.equals(this.rightVersion, that.rightVersion);
295 }
296
297 @Override
298 public String toString() {
299 return "(," + rightVersion + (rightIncluded ? "]" : ")");
300 }
301 }
302
303 private static final class RightUnboundedVersionRange extends VersionRange {
304 private final boolean leftIncluded;
305 private final String leftVersion;
306
307 private RightUnboundedVersionRange(boolean leftIncluded, String leftVersion) {
308 this.leftIncluded = leftIncluded;
309 this.leftVersion = checkNotNull(leftVersion);
310 }
311
312 @Override
313 boolean isInRange(String v) {
314 return isGreaterThan(leftIncluded, leftVersion, v);
315 }
316
317 @Override
318 public int hashCode() {
319 return Objects.hash(leftIncluded, leftVersion);
320 }
321
322 @Override
323 public boolean equals(Object obj) {
324 if (obj == null) {
325 return false;
326 }
327 if (getClass() != obj.getClass()) {
328 return false;
329 }
330 final RightUnboundedVersionRange that = (RightUnboundedVersionRange) obj;
331 return Objects.equals(this.leftIncluded, that.leftIncluded)
332 && Objects.equals(this.leftVersion, that.leftVersion);
333 }
334
335 @Override
336 public String toString() {
337 return (leftIncluded ? "[" : "(") + leftVersion + ",)";
338 }
339 }
340
341 private static final class AllVersionRange extends VersionRange {
342 @Override
343 boolean isInRange(String version) {
344 return true;
345 }
346
347 @Override
348 public int hashCode() {
349 return 1;
350 }
351
352 @Override
353 public boolean equals(Object obj) {
354 return obj != null && getClass() == obj.getClass();
355 }
356
357 @Override
358 public String toString() {
359 return "(,)";
360 }
361 }
362
363 private static final class EmptyVersionRange extends VersionRange {
364 @Override
365 boolean isInRange(String version) {
366 return false;
367 }
368
369 @Override
370 public int hashCode() {
371 return 2;
372 }
373
374 @Override
375 public boolean equals(Object obj) {
376 return obj != null && getClass() == obj.getClass();
377 }
378
379 @Override
380 public String toString() {
381 return "()";
382 }
383 }
384
385 private static final class OrVersionRange extends VersionRange {
386 private final VersionRange or1;
387 private final VersionRange or2;
388
389 private OrVersionRange(VersionRange or1, VersionRange or2) {
390 this.or1 = checkNotNull(or1);
391 this.or2 = checkNotNull(or2);
392 }
393
394 @Override
395 boolean isInRange(String v) {
396 return or1.isInRange(v) || or2.isInRange(v);
397 }
398
399 @Override
400 public int hashCode() {
401 return Objects.hash(or1, or2);
402 }
403
404 @Override
405 public boolean equals(Object obj) {
406 if (obj == null) {
407 return false;
408 }
409 if (getClass() != obj.getClass()) {
410 return false;
411 }
412 final OrVersionRange that = (OrVersionRange) obj;
413 return Objects.equals(this.or1, that.or1)
414 && Objects.equals(this.or2, that.or2);
415 }
416
417 @Override
418 public String toString() {
419 return or1 + "," + or2;
420 }
421 }
422
423
424 private static boolean isLowerThan(String version, String rightVersion, boolean rightIncluded) {
425 final int rightCompare = newVersionComparator().compare(rightVersion, version);
426 return rightCompare > 0 || (rightIncluded && rightCompare == 0);
427 }
428
429 private static boolean isGreaterThan(boolean leftIncluded, String leftVersion, String version) {
430 final int leftCompare = newVersionComparator().compare(version, leftVersion);
431 return leftCompare > 0 || (leftIncluded && leftCompare == 0);
432 }
433
434 private static Comparator<String> newVersionComparator() {
435 return new VersionStringComparator();
436 }
437 }