    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 *      http://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;
    021
    022/**
    023 * <p>An immutable range of objects from a minimum to maximum point inclusive.</p>
    024 *
    025 * <p>The objects need to either be implementations of {@code Comparable}
    026 * or you need to supply a {@code Comparator}. </p>
    027 *
    028 * <p>#ThreadSafe# if the objects and comparator are thread-safe</p>
    029 *
    030 * @param <T> The type of range values.
    031 * @since 3.0
    032 */
    033public final class Range<T> implements Serializable {
    034
    035    //-----------------------------------------------------------------------
    036    @SuppressWarnings({"rawtypes", "unchecked"})
    037    private enum ComparableComparator implements Comparator {
    038        INSTANCE;
    039        /**
    040         * Comparable based compare implementation.
    041         *
    042         * @param obj1 left hand side of comparison
    043         * @param obj2 right hand side of comparison
    044         * @return negative, 0, positive comparison value
    045         */
    046        @Override
    047        public int compare(final Object obj1, final Object obj2) {
    048            return ((Comparable) obj1).compareTo(obj2);
    049        }
    050    }
    051
    052    /**
    053     * Serialization version.
    054     * @see java.io.Serializable
    055     */
    056    private static final long serialVersionUID = 1L;
    057    /**
    058     * <p>Obtains a range with the specified minimum and maximum values (both inclusive).</p>
    059     *
    060     * <p>The range uses the natural ordering of the elements to determine where
    061     * values lie in the range.</p>
    062     *
    063     * <p>The arguments may be passed in the order (min,max) or (max,min).
    064     * The getMinimum and getMaximum methods will return the correct values.</p>
    065     *
    066     * @param <T> the type of the elements in this range
    067     * @param fromInclusive  the first value that defines the edge of the range, inclusive
    068     * @param toInclusive  the second value that defines the edge of the range, inclusive
    069     * @return the range object, not null
    070     * @throws IllegalArgumentException if either element is null
    071     * @throws ClassCastException if the elements are not {@code Comparable}
    072     */
    073    public static <T extends Comparable<T>> Range<T> between(final T fromInclusive, final T toInclusive) {
    074        return between(fromInclusive, toInclusive, null);
    075    }
    076    /**
    077     * <p>Obtains a range with the specified minimum and maximum values (both inclusive).</p>
    078     *
    079     * <p>The range uses the specified {@code Comparator} to determine where
    080     * values lie in the range.</p>
    081     *
    082     * <p>The arguments may be passed in the order (min,max) or (max,min).
    083     * The getMinimum and getMaximum methods will return the correct values.</p>
    084     *
    085     * @param <T> the type of the elements in this range
    086     * @param fromInclusive  the first value that defines the edge of the range, inclusive
    087     * @param toInclusive  the second value that defines the edge of the range, inclusive
    088     * @param comparator  the comparator to be used, null for natural ordering
    089     * @return the range object, not null
    090     * @throws IllegalArgumentException if either element is null
    091     * @throws ClassCastException if using natural ordering and the elements are not {@code Comparable}
    092     */
    093    public static <T> Range<T> between(final T fromInclusive, final T toInclusive, final Comparator<T> comparator) {
    094        return new Range<>(fromInclusive, toInclusive, comparator);
    095    }
    096    /**
    097     * <p>Obtains a range using the specified element as both the minimum
    098     * and maximum in this range.</p>
    099     *
    100     * <p>The range uses the natural ordering of the elements to determine where
    101     * values lie in the range.</p>
    102     *
    103     * @param <T> the type of the elements in this range
    104     * @param element  the value to use for this range, not null
    105     * @return the range object, not null
    106     * @throws IllegalArgumentException if the element is null
    107     * @throws ClassCastException if the element is not {@code Comparable}
    108     */
    109    public static <T extends Comparable<T>> Range<T> is(final T element) {
    110        return between(element, element, null);
    111    }
    112    /**
    113     * <p>Obtains a range using the specified element as both the minimum
    114     * and maximum in this range.</p>
    115     *
    116     * <p>The range uses the specified {@code Comparator} to determine where
    117     * values lie in the range.</p>
    118     *
    119     * @param <T> the type of the elements in this range
    120     * @param element  the value to use for this range, must not be {@code null}
    121     * @param comparator  the comparator to be used, null for natural ordering
    122     * @return the range object, not null
    123     * @throws IllegalArgumentException if the element is null
    124     * @throws ClassCastException if using natural ordering and the elements are not {@code Comparable}
    125     */
    126    public static <T> Range<T> is(final T element, final Comparator<T> comparator) {
    127        return between(element, element, comparator);
    128    }
    129
    130    /**
    131     * The ordering scheme used in this range.
    132     */
    133    private final Comparator<T> comparator;
    134
    135    /**
    136     * Cached output hashCode (class is immutable).
    137     */
    138    private transient int hashCode;
    139
    140    /**
    141     * The maximum value in this range (inclusive).
    142     */
    143    private final T maximum;
    144
    145    /**
    146     * The minimum value in this range (inclusive).
    147     */
    148    private final T minimum;
    149
    150    /**
    151     * Cached output toString (class is immutable).
    152     */
    153    private transient String toString;
    154
    155    // Accessors
    156    //--------------------------------------------------------------------
    157
    158    /**
    159     * Creates an instance.
    160     *
    161     * @param element1  the first element, not null
    162     * @param element2  the second element, not null
    163     * @param comp  the comparator to be used, null for natural ordering
    164     */
    165    @SuppressWarnings("unchecked")
    166    private Range(final T element1, final T element2, final Comparator<T> comp) {
    167        if (element1 == null || element2 == null) {
    168            throw new IllegalArgumentException("Elements in a range must not be null: element1=" +
    169                                               element1 + ", element2=" + element2);
    170        }
    171        if (comp == null) {
    172            this.comparator = ComparableComparator.INSTANCE;
    173        } else {
    174            this.comparator = comp;
    175        }
    176        if (this.comparator.compare(element1, element2) < 1) {
    177            this.minimum = element1;
    178            this.maximum = element2;
    179        } else {
    180            this.minimum = element2;
    181            this.maximum = element1;
    182        }
    183    }
    184
    185    /**
    186     * <p>Checks whether the specified element occurs within this range.</p>
    187     *
    188     * @param element  the element to check for, null returns false
    189     * @return true if the specified element occurs within this range
    190     */
    191    public boolean contains(final T element) {
    192        if (element == null) {
    193            return false;
    194        }
    195        return comparator.compare(element, minimum) > -1 && comparator.compare(element, maximum) < 1;
    196    }
    197
    198    /**
    199     * <p>Checks whether this range contains all the elements of the specified range.</p>
    200     *
    201     * <p>This method may fail if the ranges have two different comparators or element types.</p>
    202     *
    203     * @param otherRange  the range to check, null returns false
    204     * @return true if this range contains the specified range
    205     * @throws RuntimeException if ranges cannot be compared
    206     */
    207    public boolean containsRange(final Range<T> otherRange) {
    208        if (otherRange == null) {
    209            return false;
    210        }
    211        return contains(otherRange.minimum)
    212            && contains(otherRange.maximum);
    213    }
    214
    215    /**
    216     * <p>Checks where the specified element occurs relative to this range.</p>
    217     *
    218     * <p>The API is reminiscent of the Comparable interface returning {@code -1} if
    219     * the element is before the range, {@code 0} if contained within the range and
    220     * {@code 1} if the element is after the range. </p>
    221     *
    222     * @param element  the element to check for, not null
    223     * @return -1, 0 or +1 depending on the element's location relative to the range
    224     */
    225    public int elementCompareTo(final T element) {
    226        // Comparable API says throw NPE on null
    227        Validate.notNull(element, "Element is null");
    228        if (isAfter(element)) {
    229            return -1;
    230        } else if (isBefore(element)) {
    231            return 1;
    232        } else {
    233            return 0;
    234        }
    235    }
    236
    237    // Element tests
    238    //--------------------------------------------------------------------
    239
    240    /**
    241     * <p>Compares this range to another object to test if they are equal.</p>.
    242     *
    243     * <p>To be equal, the minimum and maximum values must be equal, which
    244     * ignores any differences in the comparator.</p>
    245     *
    246     * @param obj the reference object with which to compare
    247     * @return true if this object is equal
    248     */
    249    @Override
    250    public boolean equals(final Object obj) {
    251        if (obj == this) {
    252            return true;
    253        } else if (obj == null || obj.getClass() != getClass()) {
    254            return false;
    255        } else {
    256            @SuppressWarnings("unchecked") // OK because we checked the class above
    257            final
    258            Range<T> range = (Range<T>) obj;
    259            return minimum.equals(range.minimum) &&
    260                   maximum.equals(range.maximum);
    261        }
    262    }
    263
    264    /**
    265     * <p>Gets the comparator being used to determine if objects are within the range.</p>
    266     *
    267     * <p>Natural ordering uses an internal comparator implementation, thus this
    268     * method never returns null. See {@link #isNaturalOrdering()}.</p>
    269     *
    270     * @return the comparator being used, not null
    271     */
    272    public Comparator<T> getComparator() {
    273        return comparator;
    274    }
    275
    276    /**
    277     * <p>Gets the maximum value in this range.</p>
    278     *
    279     * @return the maximum value in this range, not null
    280     */
    281    public T getMaximum() {
    282        return maximum;
    283    }
    284
    285    /**
    286     * <p>Gets the minimum value in this range.</p>
    287     *
    288     * @return the minimum value in this range, not null
    289     */
    290    public T getMinimum() {
    291        return minimum;
    292    }
    293
    294    /**
    295     * <p>Gets a suitable hash code for the range.</p>
    296     *
    297     * @return a hash code value for this object
    298     */
    299    @Override
    300    public int hashCode() {
    301        int result = hashCode;
    302        if (hashCode == 0) {
    303            result = 17;
    304            result = 37 * result + getClass().hashCode();
    305            result = 37 * result + minimum.hashCode();
    306            result = 37 * result + maximum.hashCode();
    307            hashCode = result;
    308        }
    309        return result;
    310    }
    311
    312    /**
    313     * Calculate the intersection of {@code this} and an overlapping Range.
    314     * @param other overlapping Range
    315     * @return range representing the intersection of {@code this} and {@code other} ({@code this} if equal)
    316     * @throws IllegalArgumentException if {@code other} does not overlap {@code this}
    317     * @since 3.0.1
    318     */
    319    public Range<T> intersectionWith(final Range<T> other) {
    320        if (!this.isOverlappedBy(other)) {
    321            throw new IllegalArgumentException(String.format(
    322                "Cannot calculate intersection with non-overlapping range %s", other));
    323        }
    324        if (this.equals(other)) {
    325            return this;
    326        }
    327        final T min = getComparator().compare(minimum, other.minimum) < 0 ? other.minimum : minimum;
    328        final T max = getComparator().compare(maximum, other.maximum) < 0 ? maximum : other.maximum;
    329        return between(min, max, getComparator());
    330    }
    331
    332    // Range tests
    333    //--------------------------------------------------------------------
    334
    335    /**
    336     * <p>Checks whether this range is after the specified element.</p>
    337     *
    338     * @param element  the element to check for, null returns false
    339     * @return true if this range is entirely after the specified element
    340     */
    341    public boolean isAfter(final T element) {
    342        if (element == null) {
    343            return false;
    344        }
    345        return comparator.compare(element, minimum) < 0;
    346    }
    347
    348    /**
    349     * <p>Checks whether this range is completely after the specified range.</p>
    350     *
    351     * <p>This method may fail if the ranges have two different comparators or element types.</p>
    352     *
    353     * @param otherRange  the range to check, null returns false
    354     * @return true if this range is completely after the specified range
    355     * @throws RuntimeException if ranges cannot be compared
    356     */
    357    public boolean isAfterRange(final Range<T> otherRange) {
    358        if (otherRange == null) {
    359            return false;
    360        }
    361        return isAfter(otherRange.maximum);
    362    }
    363
    364    /**
    365     * <p>Checks whether this range is before the specified element.</p>
    366     *
    367     * @param element  the element to check for, null returns false
    368     * @return true if this range is entirely before the specified element
    369     */
    370    public boolean isBefore(final T element) {
    371        if (element == null) {
    372            return false;
    373        }
    374        return comparator.compare(element, maximum) > 0;
    375    }
    376
    377    /**
    378     * <p>Checks whether this range is completely before the specified range.</p>
    379     *
    380     * <p>This method may fail if the ranges have two different comparators or element types.</p>
    381     *
    382     * @param otherRange  the range to check, null returns false
    383     * @return true if this range is completely before the specified range
    384     * @throws RuntimeException if ranges cannot be compared
    385     */
    386    public boolean isBeforeRange(final Range<T> otherRange) {
    387        if (otherRange == null) {
    388            return false;
    389        }
    390        return isBefore(otherRange.minimum);
    391    }
    392
    393    /**
    394     * <p>Checks whether this range ends with the specified element.</p>
    395     *
    396     * @param element  the element to check for, null returns false
    397     * @return true if the specified element occurs within this range
    398     */
    399    public boolean isEndedBy(final T element) {
    400        if (element == null) {
    401            return false;
    402        }
    403        return comparator.compare(element, maximum) == 0;
    404    }
    405
    406    // Basics
    407    //--------------------------------------------------------------------
    408
    409    /**
    410     * <p>Whether or not the Range is using the natural ordering of the elements.</p>
    411     *
    412     * <p>Natural ordering uses an internal comparator implementation, thus this
    413     * method is the only way to check if a null comparator was specified.</p>
    414     *
    415     * @return true if using natural ordering
    416     */
    417    public boolean isNaturalOrdering() {
    418        return comparator == ComparableComparator.INSTANCE;
    419    }
    420
    421    /**
    422     * <p>Checks whether this range is overlapped by the specified range.</p>
    423     *
    424     * <p>Two ranges overlap if there is at least one element in common.</p>
    425     *
    426     * <p>This method may fail if the ranges have two different comparators or element types.</p>
    427     *
    428     * @param otherRange  the range to test, null returns false
    429     * @return true if the specified range overlaps with this
    430     *  range; otherwise, {@code false}
    431     * @throws RuntimeException if ranges cannot be compared
    432     */
    433    public boolean isOverlappedBy(final Range<T> otherRange) {
    434        if (otherRange == null) {
    435            return false;
    436        }
    437        return otherRange.contains(minimum)
    438            || otherRange.contains(maximum)
    439            || contains(otherRange.minimum);
    440    }
    441
    442    /**
    443     * <p>Checks whether this range starts with the specified element.</p>
    444     *
    445     * @param element  the element to check for, null returns false
    446     * @return true if the specified element occurs within this range
    447     */
    448    public boolean isStartedBy(final T element) {
    449        if (element == null) {
    450            return false;
    451        }
    452        return comparator.compare(element, minimum) == 0;
    453    }
    454
    455    /**
    456     * <p>
    457     * Fits the given element into this range by returning the given element or, if out of bounds, the range minimum if
    458     * below, or the range maximum if above.
    459     * </p>
    460     * <pre>
    461     * Range&lt;Integer&gt; range = Range.between(16, 64);
    462     * range.fit(-9) --&gt;  16
    463     * range.fit(0)  --&gt;  16
    464     * range.fit(15) --&gt;  16
    465     * range.fit(16) --&gt;  16
    466     * range.fit(17) --&gt;  17
    467     * ...
    468     * range.fit(63) --&gt;  63
    469     * range.fit(64) --&gt;  64
    470     * range.fit(99) --&gt;  64
    471     * </pre>
    472     * @param element the element to check for, not null
    473     * @return the minimum, the element, or the maximum depending on the element's location relative to the range
    474     * @since 3.10
    475     */
    476    public T fit(final T element) {
    477        // Comparable API says throw NPE on null
    478        Validate.notNull(element, "element");
    479        if (isAfter(element)) {
    480            return minimum;
    481        } else if (isBefore(element)) {
    482            return maximum;
    483        } else {
    484            return element;
    485        }
    486    }
    487
    488    /**
    489     * <p>Gets the range as a {@code String}.</p>
    490     *
    491     * <p>The format of the String is '[<i>min</i>..<i>max</i>]'.</p>
    492     *
    493     * @return the {@code String} representation of this range
    494     */
    495    @Override
    496    public String toString() {
    497        if (toString == null) {
    498            toString = "[" + minimum + ".." + maximum + "]";
    499        }
    500        return toString;
    501    }
    502
    503    /**
    504     * <p>Formats the receiver using the given format.</p>
    505     *
    506     * <p>This uses {@link java.util.Formattable} to perform the formatting. Three variables may
    507     * be used to embed the minimum, maximum and comparator.
    508     * Use {@code %1$s} for the minimum element, {@code %2$s} for the maximum element
    509     * and {@code %3$s} for the comparator.
    510     * The default format used by {@code toString()} is {@code [%1$s..%2$s]}.</p>
    511     *
    512     * @param format  the format string, optionally containing {@code %1$s}, {@code %2$s} and  {@code %3$s}, not null
    513     * @return the formatted string, not null
    514     */
    515    public String toString(final String format) {
    516        return String.format(format, minimum, maximum, comparator);
    517    }
    518
    519}