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