001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      https://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.lang3;
018
019import java.io.Serializable;
020import java.util.Comparator;
021import java.util.Objects;
022
023/**
024 * An immutable range of objects from a minimum to maximum point inclusive.
025 *
026 * <p>The objects need to either be implementations of {@link Comparable}
027 * or you need to supply a {@link Comparator}.</p>
028 *
029 * <p>#ThreadSafe# if the objects and comparator are thread-safe.</p>
030 *
031 * @param <T> The type of range values.
032 * @since 3.0
033 * @since 3.21.0 {@code serialVersionUID} changed from {@code 1L} to {@code 2L}.
034 */
035public class Range<T> implements Serializable {
036
037    @SuppressWarnings({"rawtypes", "unchecked"})
038    private enum ComparableComparator implements Comparator {
039        INSTANCE;
040
041        /**
042         * Comparable based compare implementation.
043         *
044         * @param obj1 left-hand side side of comparison.
045         * @param obj2 right-hand side side of comparison.
046         * @return negative, 0, positive comparison value.
047         */
048        @Override
049        public int compare(final Object obj1, final Object obj2) {
050            return ((Comparable) obj1).compareTo(obj2);
051        }
052    }
053
054    /**
055     * Serialization version.
056     *
057     * @see java.io.Serializable
058     * @since 3.21.0 {@code serialVersionUID} changed from {@code 1L} to {@value}.
059     */
060    private static final long serialVersionUID = 2L;
061
062    /**
063     * Creates a range with the specified minimum and maximum values (both inclusive).
064     *
065     * <p>The range uses the natural ordering of the elements to determine where
066     * values lie in the range.</p>
067     *
068     * <p>The arguments may be passed in the order (min, max) or (max, min).
069     * The getMinimum and getMaximum methods will return the correct values.</p>
070     *
071     * @param <T> the type of the elements in this range.
072     * @param fromInclusive  the first value that defines the edge of the range, inclusive.
073     * @param toInclusive  the second value that defines the edge of the range, inclusive.
074     * @return the range object, not null.
075     * @throws NullPointerException when fromInclusive is null.
076     * @throws NullPointerException when toInclusive is null.
077     * @throws ClassCastException if the elements are not {@link Comparable}.
078     * @deprecated Use {@link #of(Comparable, Comparable)}.
079     */
080    @Deprecated
081    public static <T extends Comparable<? super T>> Range<T> between(final T fromInclusive, final T toInclusive) {
082        return of(fromInclusive, toInclusive, null);
083    }
084
085    /**
086     * Creates a range with the specified minimum and maximum values (both inclusive).
087     *
088     * <p>The range uses the specified {@link Comparator} to determine where
089     * values lie in the range.</p>
090     *
091     * <p>The arguments may be passed in the order (min, max) or (max, min).
092     * The getMinimum and getMaximum methods will return the correct values.</p>
093     *
094     * @param <T> the type of the elements in this range.
095     * @param fromInclusive  the first value that defines the edge of the range, inclusive.
096     * @param toInclusive  the second value that defines the edge of the range, inclusive.
097     * @param comparator  the comparator to be used, null for natural ordering.
098     * @return the range object, not null.
099     * @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}