617 lines
17 KiB
Java
617 lines
17 KiB
Java
/*
|
|
* 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 net.lax1dude.eaglercraft.v1_8.HString;
|
|
|
|
/**
|
|
* <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>
|
|
*
|
|
* @param <T> The type of range values.
|
|
* @since 3.0
|
|
*/
|
|
public final 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;
|
|
|
|
/**
|
|
* <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(final T fromInclusive, final 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(final T fromInclusive, final T toInclusive, final Comparator<T> comparator) {
|
|
return new Range<>(fromInclusive, toInclusive, comparator);
|
|
}
|
|
|
|
/**
|
|
* <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(final 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(final T element, final Comparator<T> comparator) {
|
|
return between(element, element, 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
|
|
*/
|
|
@SuppressWarnings("unchecked")
|
|
private Range(final T element1, final T element2, final Comparator<T> comp) {
|
|
if (element1 == null || element2 == null) {
|
|
throw new IllegalArgumentException(
|
|
"Elements in a range must not be null: element1=" + element1 + ", 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;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* <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(final T element) {
|
|
if (element == null) {
|
|
return false;
|
|
}
|
|
return comparator.compare(element, minimum) > -1 && comparator.compare(element, maximum) < 1;
|
|
}
|
|
|
|
/**
|
|
* <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(final Range<T> otherRange) {
|
|
if (otherRange == null) {
|
|
return false;
|
|
}
|
|
return contains(otherRange.minimum) && contains(otherRange.maximum);
|
|
}
|
|
|
|
/**
|
|
* <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(final T element) {
|
|
// Comparable API says throw NPE on null
|
|
Validate.notNull(element, "element");
|
|
if (isAfter(element)) {
|
|
return -1;
|
|
} else if (isBefore(element)) {
|
|
return 1;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// Element tests
|
|
// --------------------------------------------------------------------
|
|
|
|
/**
|
|
* <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(final 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
|
|
final Range<T> range = (Range<T>) obj;
|
|
return minimum.equals(range.minimum) && maximum.equals(range.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>
|
|
* 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 minimum value in this range.
|
|
* </p>
|
|
*
|
|
* @return the minimum value in this range, not null
|
|
*/
|
|
public T getMinimum() {
|
|
return minimum;
|
|
}
|
|
|
|
/**
|
|
* <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;
|
|
}
|
|
|
|
/**
|
|
* 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(
|
|
HString.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 between(min, max, getComparator());
|
|
}
|
|
|
|
/**
|
|
* <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(final T element) {
|
|
if (element == null) {
|
|
return false;
|
|
}
|
|
return comparator.compare(element, minimum) < 0;
|
|
}
|
|
|
|
/**
|
|
* <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(final Range<T> otherRange) {
|
|
if (otherRange == null) {
|
|
return false;
|
|
}
|
|
return isAfter(otherRange.maximum);
|
|
}
|
|
|
|
/**
|
|
* <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(final T element) {
|
|
if (element == null) {
|
|
return false;
|
|
}
|
|
return comparator.compare(element, maximum) > 0;
|
|
}
|
|
|
|
/**
|
|
* <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(final Range<T> otherRange) {
|
|
if (otherRange == null) {
|
|
return false;
|
|
}
|
|
return isBefore(otherRange.minimum);
|
|
}
|
|
|
|
/**
|
|
* <p>
|
|
* Checks whether this range ends 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(final T element) {
|
|
if (element == null) {
|
|
return false;
|
|
}
|
|
return comparator.compare(element, maximum) == 0;
|
|
}
|
|
|
|
/**
|
|
* <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;
|
|
}
|
|
|
|
/**
|
|
* <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(final Range<T> otherRange) {
|
|
if (otherRange == null) {
|
|
return false;
|
|
}
|
|
return otherRange.contains(minimum) || otherRange.contains(maximum) || contains(otherRange.minimum);
|
|
}
|
|
|
|
/**
|
|
* <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(final T element) {
|
|
if (element == null) {
|
|
return false;
|
|
}
|
|
return comparator.compare(element, minimum) == 0;
|
|
}
|
|
|
|
/**
|
|
* <p>
|
|
* 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.
|
|
* </p>
|
|
*
|
|
* <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
|
|
* @since 3.10
|
|
*/
|
|
public T fit(final T element) {
|
|
// Comparable API says throw NPE on null
|
|
Validate.notNull(element, "element");
|
|
if (isAfter(element)) {
|
|
return minimum;
|
|
} else if (isBefore(element)) {
|
|
return maximum;
|
|
} else {
|
|
return element;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* <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() {
|
|
if (toString == null) {
|
|
toString = "[" + minimum + ".." + maximum + "]";
|
|
}
|
|
return toString;
|
|
}
|
|
|
|
/**
|
|
* <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(final String format) {
|
|
return HString.format(format, minimum, maximum, comparator);
|
|
}
|
|
|
|
}
|