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