View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.lang3;
18  
19  import java.io.Serializable;
20  import java.util.Comparator;
21  import java.util.Objects;
22  
23  /**
24   * An immutable range of objects from a minimum to maximum point inclusive.
25   *
26   * <p>The objects need to either be implementations of {@link Comparable}
27   * or you need to supply a {@link Comparator}.</p>
28   *
29   * <p>#ThreadSafe# if the objects and comparator are thread-safe.</p>
30   *
31   * @param <T> The type of range values.
32   * @since 3.0
33   */
34  public class Range<T> implements Serializable {
35  
36      @SuppressWarnings({"rawtypes", "unchecked"})
37      private enum ComparableComparator implements Comparator {
38          INSTANCE;
39  
40          /**
41           * Comparable based compare implementation.
42           *
43           * @param obj1 left-hand side of comparison
44           * @param obj2 right-hand side of comparison
45           * @return negative, 0, positive comparison value
46           */
47          @Override
48          public int compare(final Object obj1, final Object obj2) {
49              return ((Comparable) obj1).compareTo(obj2);
50          }
51      }
52  
53      /**
54       * Serialization version.
55       *
56       * @see java.io.Serializable
57       */
58      private static final long serialVersionUID = 1L;
59  
60      /**
61       * Creates a range with the specified minimum and maximum values (both inclusive).
62       *
63       * <p>The range uses the natural ordering of the elements to determine where
64       * values lie in the range.</p>
65       *
66       * <p>The arguments may be passed in the order (min,max) or (max,min).
67       * The getMinimum and getMaximum methods will return the correct values.</p>
68       *
69       * @param <T> the type of the elements in this range
70       * @param fromInclusive  the first value that defines the edge of the range, inclusive
71       * @param toInclusive  the second value that defines the edge of the range, inclusive
72       * @return the range object, not null
73       * @throws NullPointerException when fromInclusive is null.
74       * @throws NullPointerException when toInclusive is null.
75       * @throws ClassCastException if the elements are not {@link Comparable}
76       * @deprecated Use {@link #of(Comparable, Comparable)}.
77       */
78      @Deprecated
79      public static <T extends Comparable<? super T>> Range<T> between(final T fromInclusive, final T toInclusive) {
80          return of(fromInclusive, toInclusive, null);
81      }
82  
83      /**
84       * Creates a range with the specified minimum and maximum values (both inclusive).
85       *
86       * <p>The range uses the specified {@link Comparator} to determine where
87       * values lie in the range.</p>
88       *
89       * <p>The arguments may be passed in the order (min,max) or (max,min).
90       * The getMinimum and getMaximum methods will return the correct values.</p>
91       *
92       * @param <T> the type of the elements in this range
93       * @param fromInclusive  the first value that defines the edge of the range, inclusive
94       * @param toInclusive  the second value that defines the edge of the range, inclusive
95       * @param comparator  the comparator to be used, null for natural ordering
96       * @return the range object, not null
97       * @throws NullPointerException when fromInclusive is null.
98       * @throws NullPointerException when toInclusive is null.
99       * @throws ClassCastException if using natural ordering and the elements are not {@link Comparable}
100      * @deprecated Use {@link #of(Object, Object, Comparator)}.
101      */
102     @Deprecated
103     public static <T> Range<T> between(final T fromInclusive, final T toInclusive, final Comparator<T> comparator) {
104         return new Range<>(fromInclusive, toInclusive, comparator);
105     }
106 
107     /**
108      * Creates a range using the specified element as both the minimum
109      * and maximum in this range.
110      *
111      * <p>The range uses the natural ordering of the elements to determine where
112      * values lie in the range.</p>
113      *
114      * @param <T> the type of the elements in this range
115      * @param element  the value to use for this range, not null
116      * @return the range object, not null
117      * @throws NullPointerException if the element is null
118      * @throws ClassCastException if the element is not {@link Comparable}
119      */
120     public static <T extends Comparable<? super T>> Range<T> is(final T element) {
121         return of(element, element, null);
122     }
123 
124     /**
125      * Creates a range using the specified element as both the minimum
126      * and maximum in this range.
127      *
128      * <p>The range uses the specified {@link Comparator} to determine where
129      * values lie in the range.</p>
130      *
131      * @param <T> the type of the elements in this range
132      * @param element  the value to use for this range, must not be {@code null}
133      * @param comparator  the comparator to be used, null for natural ordering
134      * @return the range object, not null
135      * @throws NullPointerException if the element is null
136      * @throws ClassCastException if using natural ordering and the elements are not {@link Comparable}
137      */
138     public static <T> Range<T> is(final T element, final Comparator<T> comparator) {
139         return of(element, element, comparator);
140     }
141 
142     /**
143      * Creates a range with the specified minimum and maximum values (both inclusive).
144      *
145      * <p>The range uses the natural ordering of the elements to determine where
146      * values lie in the range.</p>
147      *
148      * <p>The arguments may be passed in the order (min,max) or (max,min).
149      * The getMinimum and getMaximum methods will return the correct values.</p>
150      *
151      * @param <T> the type of the elements in this range
152      * @param fromInclusive  the first value that defines the edge of the range, inclusive
153      * @param toInclusive  the second value that defines the edge of the range, inclusive
154      * @return the range object, not null
155      * @throws NullPointerException if either element is null
156      * @throws ClassCastException if the elements are not {@link Comparable}
157      * @since 3.13.0
158      */
159     public static <T extends Comparable<? super T>> Range<T> of(final T fromInclusive, final T toInclusive) {
160         return of(fromInclusive, toInclusive, null);
161     }
162 
163     /**
164      * Creates a range with the specified minimum and maximum values (both inclusive).
165      *
166      * <p>The range uses the specified {@link Comparator} to determine where
167      * values lie in the range.</p>
168      *
169      * <p>The arguments may be passed in the order (min,max) or (max,min).
170      * The getMinimum and getMaximum methods will return the correct values.</p>
171      *
172      * @param <T> the type of the elements in this range
173      * @param fromInclusive  the first value that defines the edge of the range, inclusive
174      * @param toInclusive  the second value that defines the edge of the range, inclusive
175      * @param comparator  the comparator to be used, null for natural ordering
176      * @return the range object, not null
177      * @throws NullPointerException when fromInclusive is null.
178      * @throws NullPointerException when toInclusive is null.
179      * @throws ClassCastException if using natural ordering and the elements are not {@link Comparable}
180      * @since 3.13.0
181      */
182     public static <T> Range<T> of(final T fromInclusive, final T toInclusive, final Comparator<T> comparator) {
183         return new Range<>(fromInclusive, toInclusive, comparator);
184     }
185 
186     /**
187      * The ordering scheme used in this range.
188      */
189     private final Comparator<T> comparator;
190 
191     /**
192      * Cached output hashCode (class is immutable).
193      */
194     private transient int hashCode;
195 
196     /**
197      * The maximum value in this range (inclusive).
198      */
199     private final T maximum;
200 
201     /**
202      * The minimum value in this range (inclusive).
203      */
204     private final T minimum;
205 
206     /**
207      * Cached output toString (class is immutable).
208      */
209     private transient String toString;
210 
211     /**
212      * Creates an instance.
213      *
214      * @param element1  the first element, not null
215      * @param element2  the second element, not null
216      * @param comp  the comparator to be used, null for natural ordering
217      * @throws NullPointerException when element1 is null.
218      * @throws NullPointerException when element2 is null.
219      */
220     @SuppressWarnings("unchecked")
221     Range(final T element1, final T element2, final Comparator<T> comp) {
222         Objects.requireNonNull(element1, "element1");
223         Objects.requireNonNull(element2, "element2");
224         if (comp == null) {
225             this.comparator = ComparableComparator.INSTANCE;
226         } else {
227             this.comparator = comp;
228         }
229         if (this.comparator.compare(element1, element2) < 1) {
230             this.minimum = element1;
231             this.maximum = element2;
232         } else {
233             this.minimum = element2;
234             this.maximum = element1;
235         }
236     }
237 
238     /**
239      * Checks whether the specified element occurs within this range.
240      *
241      * @param element  the element to check for, null returns false
242      * @return true if the specified element occurs within this range
243      */
244     public boolean contains(final T element) {
245         if (element == null) {
246             return false;
247         }
248         return comparator.compare(element, minimum) > -1 && comparator.compare(element, maximum) < 1;
249     }
250 
251     /**
252      * Checks whether this range contains all the elements of the specified range.
253      *
254      * <p>This method may fail if the ranges have two different comparators or element types.</p>
255      *
256      * @param otherRange  the range to check, null returns false
257      * @return true if this range contains the specified range
258      * @throws RuntimeException if ranges cannot be compared
259      */
260     public boolean containsRange(final Range<T> otherRange) {
261         if (otherRange == null) {
262             return false;
263         }
264         return contains(otherRange.minimum)
265             && contains(otherRange.maximum);
266     }
267 
268     /**
269      * Checks where the specified element occurs relative to this range.
270      *
271      * <p>The API is reminiscent of the Comparable interface returning {@code -1} if
272      * the element is before the range, {@code 0} if contained within the range and
273      * {@code 1} if the element is after the range.</p>
274      *
275      * @param element  the element to check for, not null
276      * @return -1, 0 or +1 depending on the element's location relative to the range
277      * @throws NullPointerException if {@code element} is {@code null}
278      */
279     public int elementCompareTo(final T element) {
280         // Comparable API says throw NPE on null
281         Objects.requireNonNull(element, "element");
282         if (isAfter(element)) {
283             return -1;
284         }
285         if (isBefore(element)) {
286             return 1;
287         }
288         return 0;
289     }
290 
291     /**
292      * Compares this range to another object to test if they are equal..
293      *
294      * <p>To be equal, the minimum and maximum values must be equal, which
295      * ignores any differences in the comparator.</p>
296      *
297      * @param obj the reference object with which to compare
298      * @return true if this object is equal
299      */
300     @Override
301     public boolean equals(final Object obj) {
302         if (obj == this) {
303             return true;
304         }
305         if (obj == null || obj.getClass() != getClass()) {
306             return false;
307         }
308         @SuppressWarnings("unchecked") // OK because we checked the class above
309         final
310         Range<T> range = (Range<T>) obj;
311         return minimum.equals(range.minimum) &&
312                maximum.equals(range.maximum);
313     }
314 
315     /**
316      * Fits the given element into this range by returning the given element or, if out of bounds, the range minimum if
317      * below, or the range maximum if above.
318      *
319      * <pre>
320      * Range&lt;Integer&gt; range = Range.between(16, 64);
321      * range.fit(-9) --&gt;  16
322      * range.fit(0)  --&gt;  16
323      * range.fit(15) --&gt;  16
324      * range.fit(16) --&gt;  16
325      * range.fit(17) --&gt;  17
326      * ...
327      * range.fit(63) --&gt;  63
328      * range.fit(64) --&gt;  64
329      * range.fit(99) --&gt;  64
330      * </pre>
331      * @param element the element to check for, not null
332      * @return the minimum, the element, or the maximum depending on the element's location relative to the range
333      * @throws NullPointerException if {@code element} is {@code null}
334      * @since 3.10
335      */
336     public T fit(final T element) {
337         // Comparable API says throw NPE on null
338         Objects.requireNonNull(element, "element");
339         if (isAfter(element)) {
340             return minimum;
341         }
342         if (isBefore(element)) {
343             return maximum;
344         }
345         return element;
346     }
347 
348     /**
349      * Gets the comparator being used to determine if objects are within the range.
350      *
351      * <p>Natural ordering uses an internal comparator implementation, thus this
352      * method never returns null. See {@link #isNaturalOrdering()}.</p>
353      *
354      * @return the comparator being used, not null
355      */
356     public Comparator<T> getComparator() {
357         return comparator;
358     }
359 
360     /**
361      * Gets the maximum value in this range.
362      *
363      * @return the maximum value in this range, not null
364      */
365     public T getMaximum() {
366         return maximum;
367     }
368 
369     /**
370      * Gets the minimum value in this range.
371      *
372      * @return the minimum value in this range, not null
373      */
374     public T getMinimum() {
375         return minimum;
376     }
377 
378     /**
379      * Gets a suitable hash code for the range.
380      *
381      * @return a hash code value for this object
382      */
383     @Override
384     public int hashCode() {
385         int result = hashCode;
386         if (hashCode == 0) {
387             result = 17;
388             result = 37 * result + getClass().hashCode();
389             result = 37 * result + minimum.hashCode();
390             result = 37 * result + maximum.hashCode();
391             hashCode = result;
392         }
393         return result;
394     }
395 
396     /**
397      * Calculate the intersection of {@code this} and an overlapping Range.
398      * @param other overlapping Range
399      * @return range representing the intersection of {@code this} and {@code other} ({@code this} if equal)
400      * @throws IllegalArgumentException if {@code other} does not overlap {@code this}
401      * @since 3.0.1
402      */
403     public Range<T> intersectionWith(final Range<T> other) {
404         if (!this.isOverlappedBy(other)) {
405             throw new IllegalArgumentException(String.format(
406                 "Cannot calculate intersection with non-overlapping range %s", other));
407         }
408         if (this.equals(other)) {
409             return this;
410         }
411         final T min = getComparator().compare(minimum, other.minimum) < 0 ? other.minimum : minimum;
412         final T max = getComparator().compare(maximum, other.maximum) < 0 ? maximum : other.maximum;
413         return of(min, max, getComparator());
414     }
415 
416     /**
417      * Checks whether this range is after the specified element.
418      *
419      * @param element  the element to check for, null returns false
420      * @return true if this range is entirely after the specified element
421      */
422     public boolean isAfter(final T element) {
423         if (element == null) {
424             return false;
425         }
426         return comparator.compare(element, minimum) < 0;
427     }
428 
429     /**
430      * Checks whether this range is completely after the specified range.
431      *
432      * <p>This method may fail if the ranges have two different comparators or element types.</p>
433      *
434      * @param otherRange  the range to check, null returns false
435      * @return true if this range is completely after the specified range
436      * @throws RuntimeException if ranges cannot be compared
437      */
438     public boolean isAfterRange(final Range<T> otherRange) {
439         if (otherRange == null) {
440             return false;
441         }
442         return isAfter(otherRange.maximum);
443     }
444 
445     /**
446      * Checks whether this range is before the specified element.
447      *
448      * @param element  the element to check for, null returns false
449      * @return true if this range is entirely before the specified element
450      */
451     public boolean isBefore(final T element) {
452         if (element == null) {
453             return false;
454         }
455         return comparator.compare(element, maximum) > 0;
456     }
457 
458     /**
459      * Checks whether this range is completely before the specified range.
460      *
461      * <p>This method may fail if the ranges have two different comparators or element types.</p>
462      *
463      * @param otherRange  the range to check, null returns false
464      * @return true if this range is completely before the specified range
465      * @throws RuntimeException if ranges cannot be compared
466      */
467     public boolean isBeforeRange(final Range<T> otherRange) {
468         if (otherRange == null) {
469             return false;
470         }
471         return isBefore(otherRange.minimum);
472     }
473 
474     /**
475      * Checks whether this range ends with the specified element.
476      *
477      * @param element  the element to check for, null returns false
478      * @return true if the specified element occurs within this range
479      */
480     public boolean isEndedBy(final T element) {
481         if (element == null) {
482             return false;
483         }
484         return comparator.compare(element, maximum) == 0;
485     }
486 
487     /**
488      * Whether or not the Range is using the natural ordering of the elements.
489      *
490      * <p>Natural ordering uses an internal comparator implementation, thus this
491      * method is the only way to check if a null comparator was specified.</p>
492      *
493      * @return true if using natural ordering
494      */
495     public boolean isNaturalOrdering() {
496         return comparator == ComparableComparator.INSTANCE;
497     }
498 
499     /**
500      * Checks whether this range is overlapped by the specified range.
501      *
502      * <p>Two ranges overlap if there is at least one element in common.</p>
503      *
504      * <p>This method may fail if the ranges have two different comparators or element types.</p>
505      *
506      * @param otherRange  the range to test, null returns false
507      * @return true if the specified range overlaps with this
508      *  range; otherwise, {@code false}
509      * @throws RuntimeException if ranges cannot be compared
510      */
511     public boolean isOverlappedBy(final Range<T> otherRange) {
512         if (otherRange == null) {
513             return false;
514         }
515         return otherRange.contains(minimum)
516             || otherRange.contains(maximum)
517             || contains(otherRange.minimum);
518     }
519 
520     /**
521      * Checks whether this range starts with the specified element.
522      *
523      * @param element  the element to check for, null returns false
524      * @return true if the specified element occurs within this range
525      */
526     public boolean isStartedBy(final T element) {
527         if (element == null) {
528             return false;
529         }
530         return comparator.compare(element, minimum) == 0;
531     }
532 
533     /**
534      * Gets the range as a {@link String}.
535      *
536      * <p>The format of the String is '[<i>min</i>..<i>max</i>]'.</p>
537      *
538      * @return the {@link String} representation of this range
539      */
540     @Override
541     public String toString() {
542         if (toString == null) {
543             toString = "[" + minimum + ".." + maximum + "]";
544         }
545         return toString;
546     }
547 
548     /**
549      * Formats the receiver using the given format.
550      *
551      * <p>This uses {@link java.util.Formattable} to perform the formatting. Three variables may
552      * be used to embed the minimum, maximum and comparator.
553      * Use {@code %1$s} for the minimum element, {@code %2$s} for the maximum element
554      * and {@code %3$s} for the comparator.
555      * The default format used by {@code toString()} is {@code [%1$s..%2$s]}.</p>
556      *
557      * @param format  the format string, optionally containing {@code %1$s}, {@code %2$s} and  {@code %3$s}, not null
558      * @return the formatted string, not null
559      */
560     public String toString(final String format) {
561         return String.format(format, minimum, maximum, comparator);
562     }
563 
564 }