diff options
Diffstat (limited to 'src/main/java/org/apache/commons/lang3/Range.java')
-rw-r--r-- | src/main/java/org/apache/commons/lang3/Range.java | 564 |
1 files changed, 564 insertions, 0 deletions
diff --git a/src/main/java/org/apache/commons/lang3/Range.java b/src/main/java/org/apache/commons/lang3/Range.java new file mode 100644 index 000000000..66cb5ddf4 --- /dev/null +++ b/src/main/java/org/apache/commons/lang3/Range.java @@ -0,0 +1,564 @@ +/* + * 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; +import java.util.Objects; + +/** + * An immutable range of objects from a minimum to maximum point inclusive. + * + * <p>The objects need to either be implementations of {@link Comparable} + * or you need to supply a {@link Comparator}.</p> + * + * <p>#ThreadSafe# if the objects and comparator are thread-safe.</p> + * + * @param <T> The type of range values. + * @since 3.0 + */ +public class Range<T> implements Serializable { + + @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 + */ + @Override + public int compare(final Object obj1, final Object obj2) { + return ((Comparable) obj1).compareTo(obj2); + } + } + + /** + * Serialization version. + * + * @see java.io.Serializable + */ + private static final long serialVersionUID = 1L; + + /** + * Creates a range with the specified minimum and maximum values (both inclusive). + * + * <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 NullPointerException when fromInclusive is null. + * @throws NullPointerException when toInclusive is null. + * @throws ClassCastException if the elements are not {@link Comparable} + * @deprecated Use {@link #of(Comparable, Comparable)}. + */ + @Deprecated + public static <T extends Comparable<? super T>> Range<T> between(final T fromInclusive, final T toInclusive) { + return of(fromInclusive, toInclusive, null); + } + + /** + * Creates a range with the specified minimum and maximum values (both inclusive). + * + * <p>The range uses the specified {@link 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 NullPointerException when fromInclusive is null. + * @throws NullPointerException when toInclusive is null. + * @throws ClassCastException if using natural ordering and the elements are not {@link Comparable} + * @deprecated Use {@link #of(Object, Object, Comparator)}. + */ + @Deprecated + public static <T> Range<T> between(final T fromInclusive, final T toInclusive, final Comparator<T> comparator) { + return new Range<>(fromInclusive, toInclusive, comparator); + } + + /** + * Creates a range using the specified element as both the minimum + * and maximum in this range. + * + * <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 NullPointerException if the element is null + * @throws ClassCastException if the element is not {@link Comparable} + */ + public static <T extends Comparable<? super T>> Range<T> is(final T element) { + return of(element, element, null); + } + + /** + * Creates a range using the specified element as both the minimum + * and maximum in this range. + * + * <p>The range uses the specified {@link 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 NullPointerException if the element is null + * @throws ClassCastException if using natural ordering and the elements are not {@link Comparable} + */ + public static <T> Range<T> is(final T element, final Comparator<T> comparator) { + return of(element, element, comparator); + } + + /** + * Creates a range with the specified minimum and maximum values (both inclusive). + * + * <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 NullPointerException if either element is null + * @throws ClassCastException if the elements are not {@link Comparable} + * @since 3.13.0 + */ + public static <T extends Comparable<? super T>> Range<T> of(final T fromInclusive, final T toInclusive) { + return of(fromInclusive, toInclusive, null); + } + + /** + * Creates a range with the specified minimum and maximum values (both inclusive). + * + * <p>The range uses the specified {@link 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 NullPointerException when fromInclusive is null. + * @throws NullPointerException when toInclusive is null. + * @throws ClassCastException if using natural ordering and the elements are not {@link Comparable} + * @since 3.13.0 + */ + public static <T> Range<T> of(final T fromInclusive, final T toInclusive, final Comparator<T> comparator) { + return new Range<>(fromInclusive, toInclusive, comparator); + } + + /** + * The ordering scheme used in this range. + */ + private final Comparator<T> comparator; + + /** + * Cached output hashCode (class is immutable). + */ + private transient int hashCode; + + /** + * The maximum value in this range (inclusive). + */ + private final T maximum; + + /** + * The minimum value in this range (inclusive). + */ + private final T minimum; + + /** + * Cached output toString (class is immutable). + */ + private transient String toString; + + /** + * Creates an instance. + * + * @param element1 the first element, not null + * @param element2 the second element, not null + * @param comp the comparator to be used, null for natural ordering + * @throws NullPointerException when element1 is null. + * @throws NullPointerException when element2 is null. + */ + @SuppressWarnings("unchecked") + Range(final T element1, final T element2, final Comparator<T> comp) { + Objects.requireNonNull(element1, "element1"); + Objects.requireNonNull(element2, "element2"); + if (comp == null) { + this.comparator = ComparableComparator.INSTANCE; + } else { + this.comparator = comp; + } + if (this.comparator.compare(element1, element2) < 1) { + this.minimum = element1; + this.maximum = element2; + } else { + this.minimum = element2; + this.maximum = element1; + } + } + + /** + * Checks whether the specified element occurs within this range. + * + * @param element the element to check for, null returns false + * @return true if the specified element occurs within this range + */ + public boolean contains(final T element) { + if (element == null) { + return false; + } + return comparator.compare(element, minimum) > -1 && comparator.compare(element, maximum) < 1; + } + + /** + * Checks whether this range contains all the elements of the specified range. + * + * <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(final Range<T> otherRange) { + if (otherRange == null) { + return false; + } + return contains(otherRange.minimum) + && contains(otherRange.maximum); + } + + /** + * Checks where the specified element occurs relative to this range. + * + * <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 + * @throws NullPointerException if {@code element} is {@code null} + */ + public int elementCompareTo(final T element) { + // Comparable API says throw NPE on null + Objects.requireNonNull(element, "element"); + if (isAfter(element)) { + return -1; + } + if (isBefore(element)) { + return 1; + } + return 0; + } + + /** + * Compares this range to another object to test if they are equal.. + * + * <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(final Object obj) { + if (obj == this) { + return true; + } + if (obj == null || obj.getClass() != getClass()) { + return false; + } + @SuppressWarnings("unchecked") // OK because we checked the class above + final + Range<T> range = (Range<T>) obj; + return minimum.equals(range.minimum) && + maximum.equals(range.maximum); + } + + /** + * Fits the given element into this range by returning the given element or, if out of bounds, the range minimum if + * below, or the range maximum if above. + * + * <pre> + * Range<Integer> range = Range.between(16, 64); + * range.fit(-9) --> 16 + * range.fit(0) --> 16 + * range.fit(15) --> 16 + * range.fit(16) --> 16 + * range.fit(17) --> 17 + * ... + * range.fit(63) --> 63 + * range.fit(64) --> 64 + * range.fit(99) --> 64 + * </pre> + * @param element the element to check for, not null + * @return the minimum, the element, or the maximum depending on the element's location relative to the range + * @throws NullPointerException if {@code element} is {@code null} + * @since 3.10 + */ + public T fit(final T element) { + // Comparable API says throw NPE on null + Objects.requireNonNull(element, "element"); + if (isAfter(element)) { + return minimum; + } + if (isBefore(element)) { + return maximum; + } + return element; + } + + /** + * Gets the comparator being used to determine if objects are within the range. + * + * <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; + } + + /** + * Gets the maximum value in this range. + * + * @return the maximum value in this range, not null + */ + public T getMaximum() { + return maximum; + } + + /** + * Gets the minimum value in this range. + * + * @return the minimum value in this range, not null + */ + public T getMinimum() { + return minimum; + } + + /** + * Gets a suitable hash code for the range. + * + * @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; + } + + /** + * 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(final 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; + } + final T min = getComparator().compare(minimum, other.minimum) < 0 ? other.minimum : minimum; + final T max = getComparator().compare(maximum, other.maximum) < 0 ? maximum : other.maximum; + return of(min, max, getComparator()); + } + + /** + * Checks whether this range is after the specified element. + * + * @param element the element to check for, null returns false + * @return true if this range is entirely after the specified element + */ + public boolean isAfter(final T element) { + if (element == null) { + return false; + } + return comparator.compare(element, minimum) < 0; + } + + /** + * Checks whether this range is completely after the specified range. + * + * <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(final Range<T> otherRange) { + if (otherRange == null) { + return false; + } + return isAfter(otherRange.maximum); + } + + /** + * Checks whether this range is before the specified element. + * + * @param element the element to check for, null returns false + * @return true if this range is entirely before the specified element + */ + public boolean isBefore(final T element) { + if (element == null) { + return false; + } + return comparator.compare(element, maximum) > 0; + } + + /** + * Checks whether this range is completely before the specified range. + * + * <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(final Range<T> otherRange) { + if (otherRange == null) { + return false; + } + return isBefore(otherRange.minimum); + } + + /** + * Checks whether this range ends with the specified element. + * + * @param element the element to check for, null returns false + * @return true if the specified element occurs within this range + */ + public boolean isEndedBy(final T element) { + if (element == null) { + return false; + } + return comparator.compare(element, maximum) == 0; + } + + /** + * Whether or not the Range is using the natural ordering of the elements. + * + * <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; + } + + /** + * Checks whether this range is overlapped by the specified range. + * + * <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(final Range<T> otherRange) { + if (otherRange == null) { + return false; + } + return otherRange.contains(minimum) + || otherRange.contains(maximum) + || contains(otherRange.minimum); + } + + /** + * Checks whether this range starts with the specified element. + * + * @param element the element to check for, null returns false + * @return true if the specified element occurs within this range + */ + public boolean isStartedBy(final T element) { + if (element == null) { + return false; + } + return comparator.compare(element, minimum) == 0; + } + + /** + * Gets the range as a {@link String}. + * + * <p>The format of the String is '[<i>min</i>..<i>max</i>]'.</p> + * + * @return the {@link String} representation of this range + */ + @Override + public String toString() { + if (toString == null) { + toString = "[" + minimum + ".." + maximum + "]"; + } + return toString; + } + + /** + * Formats the receiver using the given format. + * + * <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(final String format) { + return String.format(format, minimum, maximum, comparator); + } + +} |