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