    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     */
    017    package org.apache.commons.lang3;
    018
    019    import java.io.Serializable;
    020    import 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     * @version $Id: Range.java 1199894 2011-11-09 17:53:59Z ggregory $
    032     */
    033    public final class Range<T> implements Serializable {
    034
    035        /**
    036         * Serialization version.
    037         * @see java.io.Serializable
    038         */
    039        private static final long serialVersionUID = 1L;
    040
    041        /**
    042         * The ordering scheme used in this range.
    043         */
    044        private final Comparator<T> comparator;
    045        /**
    046         * The minimum value in this range (inclusive).
    047         */
    048        private final T minimum;
    049        /**
    050         * The maximum value in this range (inclusive).
    051         */
    052        private final T maximum;
    053        /**
    054         * Cached output hashCode (class is immutable).
    055         */
    056        private transient int hashCode;
    057        /**
    058         * Cached output toString (class is immutable).
    059         */
    060        private transient String toString;
    061
    062        /**
    063         * <p>Obtains a range using the specified element as both the minimum
    064         * and maximum in this range.</p>
    065         *
    066         * <p>The range uses the natural ordering of the elements to determine where
    067         * values lie in the range.</p>
    068         *
    069         * @param <T> the type of the elements in this range
    070         * @param element  the value to use for this range, not null
    071         * @return the range object, not null
    072         * @throws IllegalArgumentException if the element is null
    073         * @throws ClassCastException if the element is not {@code Comparable}
    074         */
    075        public static <T extends Comparable<T>> Range<T> is(T element) {
    076            return between(element, element, null);
    077        }
    078
    079        /**
    080         * <p>Obtains a range using the specified element as both the minimum
    081         * and maximum in this range.</p>
    082         *
    083         * <p>The range uses the specified {@code Comparator} to determine where
    084         * values lie in the range.</p>
    085         *
    086         * @param <T> the type of the elements in this range
    087         * @param element  the value to use for this range, must not be {@code null}
    088         * @param comparator  the comparator to be used, null for natural ordering
    089         * @return the range object, not null
    090         * @throws IllegalArgumentException if the element is null
    091         * @throws ClassCastException if using natural ordering and the elements are not {@code Comparable}
    092         */
    093        public static <T> Range<T> is(T element, Comparator<T> comparator) {
    094            return between(element, element, comparator);
    095        }
    096
    097        /**
    098         * <p>Obtains a range with the specified minimum and maximum values (both inclusive).</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         * <p>The arguments may be passed in the order (min,max) or (max,min).
    104         * The getMinimum and getMaximum methods will return the correct values.</p>
    105         *
    106         * @param <T> the type of the elements in this range
    107         * @param fromInclusive  the first value that defines the edge of the range, inclusive
    108         * @param toInclusive  the second value that defines the edge of the range, inclusive
    109         * @return the range object, not null
    110         * @throws IllegalArgumentException if either element is null
    111         * @throws ClassCastException if the elements are not {@code Comparable}
    112         */
    113        public static <T extends Comparable<T>> Range<T> between(T fromInclusive, T toInclusive) {
    114            return between(fromInclusive, toInclusive, null);
    115        }
    116
    117        /**
    118         * <p>Obtains a range with the specified minimum and maximum values (both inclusive).</p>
    119         *
    120         * <p>The range uses the specified {@code Comparator} to determine where
    121         * values lie in the range.</p>
    122         *
    123         * <p>The arguments may be passed in the order (min,max) or (max,min).
    124         * The getMinimum and getMaximum methods will return the correct values.</p>
    125         *
    126         * @param <T> the type of the elements in this range
    127         * @param fromInclusive  the first value that defines the edge of the range, inclusive
    128         * @param toInclusive  the second value that defines the edge of the range, inclusive
    129         * @param comparator  the comparator to be used, null for natural ordering
    130         * @return the range object, not null
    131         * @throws IllegalArgumentException if either element is null
    132         * @throws ClassCastException if using natural ordering and the elements are not {@code Comparable}
    133         */
    134        public static <T> Range<T> between(T fromInclusive, T toInclusive, Comparator<T> comparator) {
    135            return new Range<T>(fromInclusive, toInclusive, comparator);
    136        }
    137
    138        /**
    139         * Creates an instance.
    140         *
    141         * @param element1  the first element, not null
    142         * @param element2  the second element, not null
    143         * @param comparator  the comparator to be used, null for natural ordering
    144         */
    145        @SuppressWarnings("unchecked")
    146        private Range(T element1, T element2, Comparator<T> comparator) {
    147            if (element1 == null || element2 == null) {
    148                throw new IllegalArgumentException("Elements in a range must not be null: element1=" +
    149                                                   element1 + ", element2=" + element2);
    150            }
    151            if (comparator == null) {
    152                comparator = ComparableComparator.INSTANCE;
    153            }
    154            if (comparator.compare(element1, element2) < 1) {
    155                this.minimum = element1;
    156                this.maximum = element2;
    157            } else {
    158                this.minimum = element2;
    159                this.maximum = element1;
    160            }
    161            this.comparator = comparator;
    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(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(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(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 starts 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(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(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(T element) {
    288            if (element == null) {
    289                // Comparable API says throw NPE on null
    290                throw new NullPointerException("Element is null");
    291            }
    292            if (isAfter(element)) {
    293                return -1;
    294            } else if (isBefore(element)) {
    295                return 1;
    296            } else {
    297                return 0;
    298            }
    299        }
    300
    301        // Range tests
    302        //--------------------------------------------------------------------
    303
    304        /**
    305         * <p>Checks whether this range contains all the elements of the specified range.</p>
    306         *
    307         * <p>This method may fail if the ranges have two different comparators or element types.</p>
    308         *
    309         * @param otherRange  the range to check, null returns false
    310         * @return true if this range contains the specified range
    311         * @throws RuntimeException if ranges cannot be compared
    312         */
    313        public boolean containsRange(Range<T> otherRange) {
    314            if (otherRange == null) {
    315                return false;
    316            }
    317            return contains(otherRange.minimum)
    318                && contains(otherRange.maximum);
    319        }
    320
    321        /**
    322         * <p>Checks whether this range is completely after the specified range.</p>
    323         *
    324         * <p>This method may fail if the ranges have two different comparators or element types.</p>
    325         *
    326         * @param otherRange  the range to check, null returns false
    327         * @return true if this range is completely after the specified range
    328         * @throws RuntimeException if ranges cannot be compared
    329         */
    330        public boolean isAfterRange(Range<T> otherRange) {
    331            if (otherRange == null) {
    332                return false;
    333            }
    334            return isAfter(otherRange.maximum);
    335        }
    336
    337        /**
    338         * <p>Checks whether this range is overlapped by the specified range.</p>
    339         *
    340         * <p>Two ranges overlap if there is at least one element in common.</p>
    341         *
    342         * <p>This method may fail if the ranges have two different comparators or element types.</p>
    343         *
    344         * @param otherRange  the range to test, null returns false
    345         * @return true if the specified range overlaps with this
    346         *  range; otherwise, {@code false}
    347         * @throws RuntimeException if ranges cannot be compared
    348         */
    349        public boolean isOverlappedBy(Range<T> otherRange) {
    350            if (otherRange == null) {
    351                return false;
    352            }
    353            return otherRange.contains(minimum)
    354                || otherRange.contains(maximum)
    355                || contains(otherRange.minimum);
    356        }
    357
    358        /**
    359         * <p>Checks whether this range is completely before the specified range.</p>
    360         *
    361         * <p>This method may fail if the ranges have two different comparators or element types.</p>
    362         *
    363         * @param otherRange  the range to check, null returns false
    364         * @return true if this range is completely before the specified range
    365         * @throws RuntimeException if ranges cannot be compared
    366         */
    367        public boolean isBeforeRange(Range<T> otherRange) {
    368            if (otherRange == null) {
    369                return false;
    370            }
    371            return isBefore(otherRange.minimum);
    372        }
    373
    374        /**
    375         * Calculate the intersection of {@code this} and an overlapping Range.
    376         * @param other overlapping Range
    377         * @return range representing the intersection of {@code this} and {@code other} ({@code this} if equal)
    378         * @throws IllegalArgumentException if {@code other} does not overlap {@code this}
    379         * @since 3.0.1
    380         */
    381        public Range<T> intersectionWith(Range<T> other) {
    382            if (!this.isOverlappedBy(other)) {
    383                throw new IllegalArgumentException(String.format(
    384                    "Cannot calculate intersection with non-overlapping range %s", other));
    385            }
    386            if (this.equals(other)) {
    387                return this;
    388            }
    389            T min = getComparator().compare(minimum, other.minimum) < 0 ? other.minimum : minimum;
    390            T max = getComparator().compare(maximum, other.maximum) < 0 ? maximum : other.maximum;
    391            return between(min, max, getComparator());
    392        }
    393
    394        // Basics
    395        //--------------------------------------------------------------------
    396
    397        /**
    398         * <p>Compares this range to another object to test if they are equal.</p>.
    399         *
    400         * <p>To be equal, the minimum and maximum values must be equal, which
    401         * ignores any differences in the comparator.</p>
    402         *
    403         * @param obj the reference object with which to compare
    404         * @return true if this object is equal
    405         */
    406        @Override
    407        public boolean equals(Object obj) {
    408            if (obj == this) {
    409                return true;
    410            } else if (obj == null || obj.getClass() != getClass()) {
    411                return false;
    412            } else {
    413                @SuppressWarnings("unchecked") // OK because we checked the class above
    414                Range<T> range = (Range<T>) obj;
    415                return minimum.equals(range.minimum) &&
    416                       maximum.equals(range.maximum);
    417            }
    418        }
    419
    420        /**
    421         * <p>Gets a suitable hash code for the range.</p>
    422         *
    423         * @return a hash code value for this object
    424         */
    425        @Override
    426        public int hashCode() {
    427            int result = hashCode;
    428            if (hashCode == 0) {
    429                result = 17;
    430                result = 37 * result + getClass().hashCode();
    431                result = 37 * result + minimum.hashCode();
    432                result = 37 * result + maximum.hashCode();
    433                hashCode = result;
    434            }
    435            return result;
    436        }
    437
    438        /**
    439         * <p>Gets the range as a {@code String}.</p>
    440         *
    441         * <p>The format of the String is '[<i>min</i>..<i>max</i>]'.</p>
    442         *
    443         * @return the {@code String} representation of this range
    444         */
    445        @Override
    446        public String toString() {
    447            String result = toString;
    448            if (result == null) {
    449                StringBuilder buf = new StringBuilder(32);
    450                buf.append('[');
    451                buf.append(minimum);
    452                buf.append("..");
    453                buf.append(maximum);
    454                buf.append(']');
    455                result = buf.toString();
    456                toString = result;
    457            }
    458            return result;
    459        }
    460
    461        /**
    462         * <p>Formats the receiver using the given format.</p>
    463         *
    464         * <p>This uses {@link java.util.Formattable} to perform the formatting. Three variables may
    465         * be used to embed the minimum, maximum and comparator.
    466         * Use {@code %1$s} for the minimum element, {@code %2$s} for the maximum element
    467         * and {@code %3$s} for the comparator.
    468         * The default format used by {@code toString()} is {@code [%1$s..%2$s]}.</p>
    469         *
    470         * @param format  the format string, optionally containing {@code %1$s}, {@code %2$s} and  {@code %3$s}, not null
    471         * @return the formatted string, not null
    472         */
    473        public String toString(String format) {
    474            return String.format(format, minimum, maximum, comparator);
    475        }
    476
    477        //-----------------------------------------------------------------------
    478        @SuppressWarnings({"rawtypes", "unchecked"})
    479        private enum ComparableComparator implements Comparator {
    480            INSTANCE;
    481            /**
    482             * Comparable based compare implementation.
    483             *
    484             * @param obj1 left hand side of comparison
    485             * @param obj2 right hand side of comparison
    486             * @return negative, 0, positive comparison value
    487             */
    488            public int compare(Object obj1, Object obj2) {
    489                return ((Comparable) obj1).compareTo(obj2);
    490            }
    491        }
    492
    493    }