    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 */
    034public class Range<T> implements Serializable {
    035
    036    @SuppressWarnings({"rawtypes", "unchecked"})
    037    private enum ComparableComparator implements Comparator {
    038        INSTANCE;
    039
    040        /**
    041         * Comparable based compare implementation.
    042         *
    043         * @param obj1 left-hand side side of comparison.
    044         * @param obj2 right-hand side side of comparison.
    045         * @return negative, 0, positive comparison value.
    046         */
    047        @Override
    048        public int compare(final Object obj1, final Object obj2) {
    049            return ((Comparable) obj1).compareTo(obj2);
    050        }
    051    }
    052
    053    /**
    054     * Serialization version.
    055     *
    056     * @see java.io.Serializable
    057     */
    058    private static final long serialVersionUID = 1L;
    059
    060    /**
    061     * Creates a range with the specified minimum and maximum values (both inclusive).
    062     *
    063     * <p>The range uses the natural ordering of the elements to determine where
    064     * values lie in the range.</p>
    065     *
    066     * <p>The arguments may be passed in the order (min,max) or (max,min).
    067     * The getMinimum and getMaximum methods will return the correct values.</p>
    068     *
    069     * @param <T> the type of the elements in this range.
    070     * @param fromInclusive  the first value that defines the edge of the range, inclusive.
    071     * @param toInclusive  the second value that defines the edge of the range, inclusive.
    072     * @return the range object, not null.
    073     * @throws NullPointerException when fromInclusive is null.
    074     * @throws NullPointerException when toInclusive is null.
    075     * @throws ClassCastException if the elements are not {@link Comparable}.
    076     * @deprecated Use {@link #of(Comparable, Comparable)}.
    077     */
    078    @Deprecated
    079    public static <T extends Comparable<? super T>> Range<T> between(final T fromInclusive, final T toInclusive) {
    080        return of(fromInclusive, toInclusive, null);
    081    }
    082
    083    /**
    084     * Creates a range with the specified minimum and maximum values (both inclusive).
    085     *
    086     * <p>The range uses the specified {@link Comparator} to determine where
    087     * values lie in the range.</p>
    088     *
    089     * <p>The arguments may be passed in the order (min,max) or (max,min).
    090     * The getMinimum and getMaximum methods will return the correct values.</p>
    091     *
    092     * @param <T> the type of the elements in this range.
    093     * @param fromInclusive  the first value that defines the edge of the range, inclusive.
    094     * @param toInclusive  the second value that defines the edge of the range, inclusive.
    095     * @param comparator  the comparator to be used, null for natural ordering.
    096     * @return the range object, not null.
    097     * @throws NullPointerException when fromInclusive is null.
    098     * @throws NullPointerException when toInclusive is null.
    099     * @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}