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    *      https://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 side of comparison.
44           * @param obj2 right-hand side 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>{@code
320      * Range<Integer> range = Range.between(16, 64);
321      * range.fit(-9) -->  16
322      * range.fit(0)  -->  16
323      * range.fit(15) -->  16
324      * range.fit(16) -->  16
325      * range.fit(17) -->  17
326      * ...
327      * range.fit(63) -->  63
328      * range.fit(64) -->  64
329      * range.fit(99) -->  64
330      * }</pre>
331      *
332      * @param element the element to check for, not null.
333      * @return the minimum, the element, or the maximum depending on the element's location relative to the range.
334      * @throws NullPointerException if {@code element} is {@code null}.
335      * @since 3.10
336      */
337     public T fit(final T element) {
338         // Comparable API says throw NPE on null
339         Objects.requireNonNull(element, "element");
340         if (isAfter(element)) {
341             return minimum;
342         }
343         if (isBefore(element)) {
344             return maximum;
345         }
346         return element;
347     }
348 
349     /**
350      * Gets the comparator being used to determine if objects are within the range.
351      *
352      * <p>Natural ordering uses an internal comparator implementation, thus this
353      * method never returns null. See {@link #isNaturalOrdering()}.</p>
354      *
355      * @return the comparator being used, not null.
356      */
357     public Comparator<T> getComparator() {
358         return comparator;
359     }
360 
361     /**
362      * Gets the maximum value in this range.
363      *
364      * @return the maximum value in this range, not null.
365      */
366     public T getMaximum() {
367         return maximum;
368     }
369 
370     /**
371      * Gets the minimum value in this range.
372      *
373      * @return the minimum value in this range, not null.
374      */
375     public T getMinimum() {
376         return minimum;
377     }
378 
379     /**
380      * Gets a suitable hash code for the range.
381      *
382      * @return a hash code value for this object.
383      */
384     @Override
385     public int hashCode() {
386         int result = hashCode;
387         if (hashCode == 0) {
388             result = 17;
389             result = 37 * result + getClass().hashCode();
390             result = 37 * result + minimum.hashCode();
391             result = 37 * result + maximum.hashCode();
392             hashCode = result;
393         }
394         return result;
395     }
396 
397     /**
398      * Calculate the intersection of {@code this} and an overlapping Range.
399      *
400      * @param other overlapping Range.
401      * @return range representing the intersection of {@code this} and {@code other} ({@code this} if equal).
402      * @throws IllegalArgumentException if {@code other} does not overlap {@code this}.
403      * @since 3.0.1
404      */
405     public Range<T> intersectionWith(final Range<T> other) {
406         if (!this.isOverlappedBy(other)) {
407             throw new IllegalArgumentException(String.format(
408                 "Cannot calculate intersection with non-overlapping range %s", other));
409         }
410         if (this.equals(other)) {
411             return this;
412         }
413         final T min = getComparator().compare(minimum, other.minimum) < 0 ? other.minimum : minimum;
414         final T max = getComparator().compare(maximum, other.maximum) < 0 ? maximum : other.maximum;
415         return of(min, max, getComparator());
416     }
417 
418     /**
419      * Checks whether this range is after the specified element.
420      *
421      * @param element  the element to check for, null returns false.
422      * @return true if this range is entirely after the specified element.
423      */
424     public boolean isAfter(final T element) {
425         if (element == null) {
426             return false;
427         }
428         return comparator.compare(element, minimum) < 0;
429     }
430 
431     /**
432      * Checks whether this range is completely after the specified range.
433      *
434      * <p>This method may fail if the ranges have two different comparators or element types.</p>
435      *
436      * @param otherRange  the range to check, null returns false.
437      * @return true if this range is completely after the specified range.
438      * @throws RuntimeException if ranges cannot be compared.
439      */
440     public boolean isAfterRange(final Range<T> otherRange) {
441         if (otherRange == null) {
442             return false;
443         }
444         return isAfter(otherRange.maximum);
445     }
446 
447     /**
448      * Checks whether this range is before the specified element.
449      *
450      * @param element  the element to check for, null returns false.
451      * @return true if this range is entirely before the specified element.
452      */
453     public boolean isBefore(final T element) {
454         if (element == null) {
455             return false;
456         }
457         return comparator.compare(element, maximum) > 0;
458     }
459 
460     /**
461      * Checks whether this range is completely before the specified range.
462      *
463      * <p>This method may fail if the ranges have two different comparators or element types.</p>
464      *
465      * @param otherRange  the range to check, null returns false.
466      * @return true if this range is completely before the specified range.
467      * @throws RuntimeException if ranges cannot be compared.
468      */
469     public boolean isBeforeRange(final Range<T> otherRange) {
470         if (otherRange == null) {
471             return false;
472         }
473         return isBefore(otherRange.minimum);
474     }
475 
476     /**
477      * Checks whether this range ends with the specified element.
478      *
479      * @param element  the element to check for, null returns false.
480      * @return true if the specified element occurs within this range.
481      */
482     public boolean isEndedBy(final T element) {
483         if (element == null) {
484             return false;
485         }
486         return comparator.compare(element, maximum) == 0;
487     }
488 
489     /**
490      * Tests whether or not the Range is using the natural ordering of the elements.
491      *
492      * <p>Natural ordering uses an internal comparator implementation, thus this
493      * method is the only way to check if a null comparator was specified.</p>
494      *
495      * @return true if using natural ordering.
496      */
497     public boolean isNaturalOrdering() {
498         return comparator == ComparableComparator.INSTANCE;
499     }
500 
501     /**
502      * Tests whether this range is overlapped by the specified range.
503      *
504      * <p>Two ranges overlap if there is at least one element in common.</p>
505      *
506      * <p>This method may fail if the ranges have two different comparators or element types.</p>
507      *
508      * @param otherRange  the range to test, null returns false.
509      * @return true if the specified range overlaps with this
510      *  range; otherwise, {@code false}.
511      * @throws RuntimeException if ranges cannot be compared.
512      */
513     public boolean isOverlappedBy(final Range<T> otherRange) {
514         if (otherRange == null) {
515             return false;
516         }
517         return otherRange.contains(minimum)
518             || otherRange.contains(maximum)
519             || contains(otherRange.minimum);
520     }
521 
522     /**
523      * Tests whether this range starts with the specified element.
524      *
525      * @param element  the element to check for, null returns false.
526      * @return true if the specified element occurs within this range.
527      */
528     public boolean isStartedBy(final T element) {
529         if (element == null) {
530             return false;
531         }
532         return comparator.compare(element, minimum) == 0;
533     }
534 
535     /**
536      * Gets the range as a {@link String}.
537      *
538      * <p>The format of the String is '[<em>min</em>..<em>max</em>]'.</p>
539      *
540      * @return the {@link String} representation of this range.
541      */
542     @Override
543     public String toString() {
544         if (toString == null) {
545             toString = "[" + minimum + ".." + maximum + "]";
546         }
547         return toString;
548     }
549 
550     /**
551      * Formats the receiver using the given format.
552      *
553      * <p>This uses {@link java.util.Formattable} to perform the formatting. Three variables may
554      * be used to embed the minimum, maximum and comparator.
555      * Use {@code %1$s} for the minimum element, {@code %2$s} for the maximum element
556      * and {@code %3$s} for the comparator.
557      * The default format used by {@code toString()} is {@code [%1$s..%2$s]}.</p>
558      *
559      * @param format  the format string, optionally containing {@code %1$s}, {@code %2$s} and  {@code %3$s}, not null.
560      * @return the formatted string, not null.
561      */
562     public String toString(final String format) {
563         return String.format(format, minimum, maximum, comparator);
564     }
565 
566 }