/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.commons.lang3; import java.io.Serializable; import java.util.Comparator; /** * <p>An immutable range of objects from a minimum to maximum point inclusive.</p> * * <p>The objects need to either be implementations of {@code Comparable} * or you need to supply a {@code Comparator}. </p> * * <p>#ThreadSafe# if the objects and comparator are thread-safe</p> * * @since 3.0 * @version $Id: Range.java 1199894 2011-11-09 17:53:59Z ggregory $ */ public final class Range<T> implements Serializable { /** * Serialization version. * @see java.io.Serializable */ private static final long serialVersionUID = 1L; /** * The ordering scheme used in this range. */ private final Comparator<T> comparator; /** * The minimum value in this range (inclusive). */ private final T minimum; /** * The maximum value in this range (inclusive). */ private final T maximum; /** * Cached output hashCode (class is immutable). */ private transient int hashCode; /** * Cached output toString (class is immutable). */ private transient String toString; /** * <p>Obtains a range using the specified element as both the minimum * and maximum in this range.</p> * * <p>The range uses the natural ordering of the elements to determine where * values lie in the range.</p> * * @param <T> the type of the elements in this range * @param element the value to use for this range, not null * @return the range object, not null * @throws IllegalArgumentException if the element is null * @throws ClassCastException if the element is not {@code Comparable} */ public static <T extends Comparable<T>> Range<T> is(T element) { return between(element, element, null); } /** * <p>Obtains a range using the specified element as both the minimum * and maximum in this range.</p> * * <p>The range uses the specified {@code Comparator} to determine where * values lie in the range.</p> * * @param <T> the type of the elements in this range * @param element the value to use for this range, must not be {@code null} * @param comparator the comparator to be used, null for natural ordering * @return the range object, not null * @throws IllegalArgumentException if the element is null * @throws ClassCastException if using natural ordering and the elements are not {@code Comparable} */ public static <T> Range<T> is(T element, Comparator<T> comparator) { return between(element, element, comparator); } /** * <p>Obtains a range with the specified minimum and maximum values (both inclusive).</p> * * <p>The range uses the natural ordering of the elements to determine where * values lie in the range.</p> * * <p>The arguments may be passed in the order (min,max) or (max,min). * The getMinimum and getMaximum methods will return the correct values.</p> * * @param <T> the type of the elements in this range * @param fromInclusive the first value that defines the edge of the range, inclusive * @param toInclusive the second value that defines the edge of the range, inclusive * @return the range object, not null * @throws IllegalArgumentException if either element is null * @throws ClassCastException if the elements are not {@code Comparable} */ public static <T extends Comparable<T>> Range<T> between(T fromInclusive, T toInclusive) { return between(fromInclusive, toInclusive, null); } /** * <p>Obtains a range with the specified minimum and maximum values (both inclusive).</p> * * <p>The range uses the specified {@code Comparator} to determine where * values lie in the range.</p> * * <p>The arguments may be passed in the order (min,max) or (max,min). * The getMinimum and getMaximum methods will return the correct values.</p> * * @param <T> the type of the elements in this range * @param fromInclusive the first value that defines the edge of the range, inclusive * @param toInclusive the second value that defines the edge of the range, inclusive * @param comparator the comparator to be used, null for natural ordering * @return the range object, not null * @throws IllegalArgumentException if either element is null * @throws ClassCastException if using natural ordering and the elements are not {@code Comparable} */ public static <T> Range<T> between(T fromInclusive, T toInclusive, Comparator<T> comparator) { return new Range<T>(fromInclusive, toInclusive, comparator); } /** * Creates an instance. * * @param element1 the first element, not null * @param element2 the second element, not null * @param comparator the comparator to be used, null for natural ordering */ @SuppressWarnings("unchecked") private Range(T element1, T element2, Comparator<T> comparator) { if (element1 == null || element2 == null) { throw new IllegalArgumentException("Elements in a range must not be null: element1=" + element1 + ", element2=" + element2); } if (comparator == null) { comparator = ComparableComparator.INSTANCE; } if (comparator.compare(element1, element2) < 1) { this.minimum = element1; this.maximum = element2; } else { this.minimum = element2; this.maximum = element1; } this.comparator = comparator; } // Accessors //-------------------------------------------------------------------- /** * <p>Gets the minimum value in this range.</p> * * @return the minimum value in this range, not null */ public T getMinimum() { return minimum; } /** * <p>Gets the maximum value in this range.</p> * * @return the maximum value in this range, not null */ public T getMaximum() { return maximum; } /** * <p>Gets the comparator being used to determine if objects are within the range.</p> * * <p>Natural ordering uses an internal comparator implementation, thus this * method never returns null. See {@link #isNaturalOrdering()}.</p> * * @return the comparator being used, not null */ public Comparator<T> getComparator() { return comparator; } /** * <p>Whether or not the Range is using the natural ordering of the elements.</p> * * <p>Natural ordering uses an internal comparator implementation, thus this * method is the only way to check if a null comparator was specified.</p> * * @return true if using natural ordering */ public boolean isNaturalOrdering() { return comparator == ComparableComparator.INSTANCE; } // Element tests //-------------------------------------------------------------------- /** * <p>Checks whether the specified element occurs within this range.</p> * * @param element the element to check for, null returns false * @return true if the specified element occurs within this range */ public boolean contains(T element) { if (element == null) { return false; } return comparator.compare(element, minimum) > -1 && comparator.compare(element, maximum) < 1; } /** * <p>Checks whether this range is after the specified element.</p> * * @param element the element to check for, null returns false * @return true if this range is entirely after the specified element */ public boolean isAfter(T element) { if (element == null) { return false; } return comparator.compare(element, minimum) < 0; } /** * <p>Checks whether this range starts with the specified element.</p> * * @param element the element to check for, null returns false * @return true if the specified element occurs within this range */ public boolean isStartedBy(T element) { if (element == null) { return false; } return comparator.compare(element, minimum) == 0; } /** * <p>Checks whether this range starts with the specified element.</p> * * @param element the element to check for, null returns false * @return true if the specified element occurs within this range */ public boolean isEndedBy(T element) { if (element == null) { return false; } return comparator.compare(element, maximum) == 0; } /** * <p>Checks whether this range is before the specified element.</p> * * @param element the element to check for, null returns false * @return true if this range is entirely before the specified element */ public boolean isBefore(T element) { if (element == null) { return false; } return comparator.compare(element, maximum) > 0; } /** * <p>Checks where the specified element occurs relative to this range.</p> * * <p>The API is reminiscent of the Comparable interface returning {@code -1} if * the element is before the range, {@code 0} if contained within the range and * {@code 1} if the element is after the range. </p> * * @param element the element to check for, not null * @return -1, 0 or +1 depending on the element's location relative to the range */ public int elementCompareTo(T element) { if (element == null) { // Comparable API says throw NPE on null throw new NullPointerException("Element is null"); } if (isAfter(element)) { return -1; } else if (isBefore(element)) { return 1; } else { return 0; } } // Range tests //-------------------------------------------------------------------- /** * <p>Checks whether this range contains all the elements of the specified range.</p> * * <p>This method may fail if the ranges have two different comparators or element types.</p> * * @param otherRange the range to check, null returns false * @return true if this range contains the specified range * @throws RuntimeException if ranges cannot be compared */ public boolean containsRange(Range<T> otherRange) { if (otherRange == null) { return false; } return contains(otherRange.minimum) && contains(otherRange.maximum); } /** * <p>Checks whether this range is completely after the specified range.</p> * * <p>This method may fail if the ranges have two different comparators or element types.</p> * * @param otherRange the range to check, null returns false * @return true if this range is completely after the specified range * @throws RuntimeException if ranges cannot be compared */ public boolean isAfterRange(Range<T> otherRange) { if (otherRange == null) { return false; } return isAfter(otherRange.maximum); } /** * <p>Checks whether this range is overlapped by the specified range.</p> * * <p>Two ranges overlap if there is at least one element in common.</p> * * <p>This method may fail if the ranges have two different comparators or element types.</p> * * @param otherRange the range to test, null returns false * @return true if the specified range overlaps with this * range; otherwise, {@code false} * @throws RuntimeException if ranges cannot be compared */ public boolean isOverlappedBy(Range<T> otherRange) { if (otherRange == null) { return false; } return otherRange.contains(minimum) || otherRange.contains(maximum) || contains(otherRange.minimum); } /** * <p>Checks whether this range is completely before the specified range.</p> * * <p>This method may fail if the ranges have two different comparators or element types.</p> * * @param otherRange the range to check, null returns false * @return true if this range is completely before the specified range * @throws RuntimeException if ranges cannot be compared */ public boolean isBeforeRange(Range<T> otherRange) { if (otherRange == null) { return false; } return isBefore(otherRange.minimum); } /** * Calculate the intersection of {@code this} and an overlapping Range. * @param other overlapping Range * @return range representing the intersection of {@code this} and {@code other} ({@code this} if equal) * @throws IllegalArgumentException if {@code other} does not overlap {@code this} * @since 3.0.1 */ public Range<T> intersectionWith(Range<T> other) { if (!this.isOverlappedBy(other)) { throw new IllegalArgumentException(String.format( "Cannot calculate intersection with non-overlapping range %s", other)); } if (this.equals(other)) { return this; } T min = getComparator().compare(minimum, other.minimum) < 0 ? other.minimum : minimum; T max = getComparator().compare(maximum, other.maximum) < 0 ? maximum : other.maximum; return between(min, max, getComparator()); } // Basics //-------------------------------------------------------------------- /** * <p>Compares this range to another object to test if they are equal.</p>. * * <p>To be equal, the minimum and maximum values must be equal, which * ignores any differences in the comparator.</p> * * @param obj the reference object with which to compare * @return true if this object is equal */ @Override public boolean equals(Object obj) { if (obj == this) { return true; } else if (obj == null || obj.getClass() != getClass()) { return false; } else { @SuppressWarnings("unchecked") // OK because we checked the class above Range<T> range = (Range<T>) obj; return minimum.equals(range.minimum) && maximum.equals(range.maximum); } } /** * <p>Gets a suitable hash code for the range.</p> * * @return a hash code value for this object */ @Override public int hashCode() { int result = hashCode; if (hashCode == 0) { result = 17; result = 37 * result + getClass().hashCode(); result = 37 * result + minimum.hashCode(); result = 37 * result + maximum.hashCode(); hashCode = result; } return result; } /** * <p>Gets the range as a {@code String}.</p> * * <p>The format of the String is '[<i>min</i>..<i>max</i>]'.</p> * * @return the {@code String} representation of this range */ @Override public String toString() { String result = toString; if (result == null) { StringBuilder buf = new StringBuilder(32); buf.append('['); buf.append(minimum); buf.append(".."); buf.append(maximum); buf.append(']'); result = buf.toString(); toString = result; } return result; } /** * <p>Formats the receiver using the given format.</p> * * <p>This uses {@link java.util.Formattable} to perform the formatting. Three variables may * be used to embed the minimum, maximum and comparator. * Use {@code %1$s} for the minimum element, {@code %2$s} for the maximum element * and {@code %3$s} for the comparator. * The default format used by {@code toString()} is {@code [%1$s..%2$s]}.</p> * * @param format the format string, optionally containing {@code %1$s}, {@code %2$s} and {@code %3$s}, not null * @return the formatted string, not null */ public String toString(String format) { return String.format(format, minimum, maximum, comparator); } //----------------------------------------------------------------------- @SuppressWarnings({"rawtypes", "unchecked"}) private enum ComparableComparator implements Comparator { INSTANCE; /** * Comparable based compare implementation. * * @param obj1 left hand side of comparison * @param obj2 right hand side of comparison * @return negative, 0, positive comparison value */ public int compare(Object obj1, Object obj2) { return ((Comparable) obj1).compareTo(obj2); } } }