View Javadoc

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   * Represents a version range. Version ranges can be built programmatically or parsed with the
14   * following definition:
15   * <table>
16   *   <thead>
17   *     <th>Range</th>
18   *     <th>Meaning</th>
19   *   </thead>
20   *   <tbody>
21   *     <tr>
22   *       <td>1.0</td>
23   *       <td>x >= 1.0</td>
24   *     </tr>
25   *     <tr>
26   *       <td>(,1.0]</td>
27   *       <td>x <= 1.0</td>
28   *     </tr>
29   *     <tr>
30   *       <td>(,1.0)</td>
31   *       <td>x < 1.0</td>
32   *     </tr>
33   *     <tr>
34   *       <td>[1.0]</td>
35   *       <td>x == 1.0</td>
36   *     </tr>
37   *     <tr>
38   *       <td>[1.0,)</td>
39   *       <td>x >= 1.0</td>
40   *     </tr>
41   *     <tr>
42   *       <td>(1.0,)</td>
43   *       <td>x > 1.0</td>
44   *     </tr>
45   *     <tr>
46   *       <td>(1.0,2.0)</td>
47   *       <td>1.0 < x < 2.0</td>
48   *     </tr>
49   *     <tr>
50   *       <td>[1.0,2.0]</td>
51   *       <td>1.0 <= x <= 2.0</td>
52   *     </tr>
53   *   </tbody>
54   * </table>
55   *
56   *
57   * @since 2.14.0
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) // single version
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 }