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