com.landawn.abacus.util.Range Maven / Gradle / Ivy
Show all versions of abacus-common Show documentation
/*
* 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 com.landawn.abacus.util;
import java.io.Serializable;
import java.util.Collection;
import com.landawn.abacus.util.u.Optional;
/**
*
* Note: it's copied from Apache Commons Lang developed at The Apache Software Foundation (http://www.apache.org/), or
* under the Apache License 2.0. The methods copied from other products/frameworks may be modified in this class.
*
*
*
* An immutable range of objects from a minimum to maximum point inclusive.
*
*
*
* #ThreadSafe# if the objects and comparator are thread-safe
*
*
* @version $Id: Range.java 1565243 2014-02-06 13:37:12Z sebb $
* @param
* @since 3.0
*/
@com.landawn.abacus.annotation.Immutable
public final class Range> implements Serializable, Immutable {
private static final long serialVersionUID = 545606166758706779L;
/**
* The minimum value in this range (inclusive).
*/
private final LowerEndpoint lowerEndpoint;
/**
* The maximum value in this range (inclusive).
*/
private final UpperEndpoint upperEndpoint;
private final BoundType boundType;
@SuppressWarnings("unchecked")
private Range(final LowerEndpoint lowerEndpoint, final UpperEndpoint upperEndpoint, final BoundType boundType) {
this.lowerEndpoint = lowerEndpoint;
this.upperEndpoint = upperEndpoint;
this.boundType = boundType;
}
/**
*
* Obtains a range using the specified element as both the minimum and maximum in this range.
*
*
*
* The range uses the natural ordering of the elements to determine where values lie in the range.
*
*
* @param
* the type of the elements in this range
* @param element
* the value to use for this range, not null
* @return
* @throws IllegalArgumentException if the element is null
*/
public static > Range just(final T element) {
return closed(element, element);
}
/**
*
* @param
* @param min
* @param max
* @return
* @throws IllegalArgumentException if the 'min' or 'max' is null, or min > max.
*/
public static > Range open(final T min, final T max) {
if (min == null || max == null || min.compareTo(max) > 0) {
throw new IllegalArgumentException("'fromInclusive' and 'toInclusive' can't be null, or min > max");
}
return new Range<>(new LowerEndpoint<>(min, false), new UpperEndpoint<>(max, false), BoundType.OPEN_OPEN);
}
/**
*
* @param
* @param min
* @param max
* @return
* @throws IllegalArgumentException if the 'min' or 'max' is null, or min > max.
*/
public static > Range openClosed(final T min, final T max) {
if (min == null || max == null || min.compareTo(max) > 0) {
throw new IllegalArgumentException("'fromInclusive' and 'toInclusive' can't be null, or min > max");
}
return new Range<>(new LowerEndpoint<>(min, false), new UpperEndpoint<>(max, true), BoundType.OPEN_CLOSED);
}
/**
*
* @param
* @param min
* @param max
* @return
* @throws IllegalArgumentException if the 'min' or 'max' is null, or min > max.
*/
public static > Range closedOpen(final T min, final T max) {
if (min == null || max == null || min.compareTo(max) > 0) {
throw new IllegalArgumentException("'fromInclusive' and 'toInclusive' can't be null, or min > max");
}
return new Range<>(new LowerEndpoint<>(min, true), new UpperEndpoint<>(max, false), BoundType.CLOSED_OPEN);
}
/**
*
* @param
* @param min
* @param max
* @return
* @throws IllegalArgumentException if the 'min' or 'max' is null, or min > max.
*/
public static > Range closed(final T min, final T max) {
if (min == null || max == null || min.compareTo(max) > 0) {
throw new IllegalArgumentException("'fromInclusive' and 'toInclusive' can't be null, or min > max");
}
return new Range<>(new LowerEndpoint<>(min, true), new UpperEndpoint<>(max, true), BoundType.CLOSED_CLOSED);
}
public BoundType boundType() {
return boundType;
}
/**
*
* Gets the minimum value in this range.
*
*
* @return
*/
public T lowerEndpoint() {
return lowerEndpoint.value;
}
/**
*
* Gets the maximum value in this range.
*
*
* @return
*/
public T upperEndpoint() {
return upperEndpoint.value;
}
// Element tests
//--------------------------------------------------------------------
/**
*
* 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 lowerEndpoint.includes(element) && upperEndpoint.includes(element);
}
/**
*
* @param c
* @return
*/
public boolean containsAll(Collection extends T> c) {
if (N.isNullOrEmpty(c)) {
return true;
}
for (T e : c) {
if (!contains(e)) {
return false;
}
}
return true;
}
/**
*
* 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 lowerEndpoint.isClosed && lowerEndpoint.compareTo(element) == 0;
}
/**
*
* 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 isEndedBy(final T element) {
if (element == null) {
return false;
}
return upperEndpoint.isClosed && upperEndpoint.compareTo(element) == 0;
}
/**
*
* 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 !lowerEndpoint.includes(element);
}
/**
*
* 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 !upperEndpoint.includes(element);
}
/**
*
* Checks where the specified element occurs relative to this range.
*
*
*
* Returns {@code -1} if this range is before the specified element,
* {@code 1} if the this range is after the specified element, otherwise {@code 0} if the specified element is contained in this range.
*
*
* @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 compareTo(final T element) {
if (element == null) {
// Comparable API says throw NPE on null
throw new NullPointerException("Element is null");
}
if (isBefore(element)) {
return -1;
} else if (isAfter(element)) {
return 1;
} else {
return 0;
}
}
// Range tests
//--------------------------------------------------------------------
/**
*
* Checks whether this range contains all the elements of the specified range.
*
*
* @param other
* 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 other) {
if (other == null) {
return false;
}
return (other.lowerEndpoint.isClosed ? contains(other.lowerEndpoint.value) : lowerEndpoint.value.compareTo(other.lowerEndpoint.value) <= 0)
&& (other.upperEndpoint.isClosed ? contains(other.upperEndpoint.value) : upperEndpoint.value.compareTo(other.upperEndpoint.value) >= 0);
}
/**
*
* Checks whether this range is completely after the specified range.
*
*
*
* @param other
* 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 other) {
if (other == null) {
return false;
}
return other.upperEndpoint.isClosed ? isAfter(other.upperEndpoint.value) : this.lowerEndpoint.compareTo(other.upperEndpoint.value) >= 0;
}
/**
*
* Checks whether this range is completely before the specified range.
*
*
*
* @param other
* 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 other) {
if (other == null) {
return false;
}
return other.lowerEndpoint.isClosed ? isBefore(other.lowerEndpoint.value) : this.upperEndpoint.compareTo(other.lowerEndpoint.value) <= 0;
}
/**
*
* Checks whether this range is overlapped by the specified range.
*
*
*
* Two ranges overlap if there is at least one element in common.
*
*
*
* @param other
* 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 other) {
if (other == null) {
return false;
} else if (isAfterRange(other) || isBeforeRange(other)) {
return false;
}
return true;
}
/**
* 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, or Optional.empty()
if they're not overlapped.
* @since 3.0.1
*/
public Optional> intersection(final Range other) {
if (!this.isOverlappedBy(other)) {
return Optional.empty();
} else if (this.equals(other)) {
return Optional.of(this);
}
final LowerEndpoint newLowerEndpoint = lowerEndpoint.includes(other.lowerEndpoint.value) ? other.lowerEndpoint : lowerEndpoint;
final UpperEndpoint newUpperEndpoint = upperEndpoint.includes(other.upperEndpoint.value) ? other.upperEndpoint : upperEndpoint;
BoundType boundType = null;
if (newLowerEndpoint.isClosed) {
boundType = newUpperEndpoint.isClosed ? BoundType.CLOSED_CLOSED : BoundType.CLOSED_OPEN;
} else {
boundType = newUpperEndpoint.isClosed ? BoundType.OPEN_CLOSED : BoundType.OPEN_OPEN;
}
return Optional.of(new Range<>(newLowerEndpoint, newUpperEndpoint, boundType));
}
/**
* Copied from Guava under Apache License v2.0
*
* Returns the minimal range that {@linkplain #encloses encloses} both this range and {@code
* other}. For example, the span of {@code [1..3]} and {@code (5..7)} is {@code [1..7)}.
*
* If the input ranges are {@linkplain #isConnected connected}, the returned range can
* also be called their union. If they are not, note that the span might contain values
* that are not contained in either input range.
*
*
Like {@link #intersection(Range) intersection}, this operation is commutative, associative
* and idempotent. Unlike it, it is always well-defined for any two input ranges.
*
* @param other
* @return
*/
public Range span(Range other) {
final LowerEndpoint newLowerEndpoint = lowerEndpoint.includes(other.lowerEndpoint.value) ? lowerEndpoint : other.lowerEndpoint;
final UpperEndpoint newUpperEndpoint = upperEndpoint.includes(other.upperEndpoint.value) ? upperEndpoint : other.upperEndpoint;
BoundType boundType = null;
if (newLowerEndpoint.isClosed) {
boundType = newUpperEndpoint.isClosed ? BoundType.CLOSED_CLOSED : BoundType.CLOSED_OPEN;
} else {
boundType = newUpperEndpoint.isClosed ? BoundType.OPEN_CLOSED : BoundType.OPEN_OPEN;
}
return new Range<>(newLowerEndpoint, newUpperEndpoint, boundType);
}
/**
* Checks if is empty.
*
* @return true, if is empty
*/
public boolean isEmpty() {
if (lowerEndpoint.isClosed || upperEndpoint.isClosed || lowerEndpoint.compareTo(upperEndpoint.value) != 0) {
return false;
}
return true;
}
// Basics
//--------------------------------------------------------------------
/**
*
* Compares this range to another object to test if they are equal.
*
* .
*
*
* To be equal, the minimum and maximum values must be equal, which ignores any differences in the comparator.
*
*
* @param obj
* the reference object with which to compare
* @return true if this object is equal
*/
@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof Range) {
final Range other = (Range) obj;
return N.equals(lowerEndpoint, other.lowerEndpoint) && N.equals(upperEndpoint, other.upperEndpoint);
}
return false;
}
/**
*
* Gets a suitable hash code for the range.
*
*
* @return a hash code value for this object
*/
@Override
public int hashCode() {
int result = 17;
result = 37 * result + getClass().hashCode();
result = 37 * result + lowerEndpoint.hashCode();
return 37 * result + upperEndpoint.hashCode();
}
@Override
public String toString() {
return lowerEndpoint.toString() + ", " + upperEndpoint.toString();
}
/**
* The Enum BoundType.
*/
public enum BoundType {
/** The open open. */
OPEN_OPEN,
/** The open closed. */
OPEN_CLOSED,
/** The closed open. */
CLOSED_OPEN,
/** The closed closed. */
CLOSED_CLOSED
}
/**
* The Class Endpoint.
*
* @param
*/
static abstract class Endpoint> implements Serializable {
/** The Constant serialVersionUID. */
private static final long serialVersionUID = -1404748904424344410L;
/** The value. */
final T value;
/** The is closed. */
final boolean isClosed;
/**
* Instantiates a new endpoint.
*
* @param value
* @param isClosed
*/
protected Endpoint(final T value, boolean isClosed) {
this.value = value;
this.isClosed = isClosed;
}
/**
*
* @param value
* @return
*/
public int compareTo(T value) {
return N.compare(this.value, value);
}
/**
*
* @param value
* @return
*/
public abstract boolean includes(T value);
}
/**
* The Class LowerEndpoint.
*
* @param
*/
static class LowerEndpoint> extends Endpoint {
/** The Constant serialVersionUID. */
private static final long serialVersionUID = -1369183906861608859L;
/**
* Instantiates a new lower endpoint.
*
* @param value
* @param isClosed
*/
LowerEndpoint(final T value, boolean isClosed) {
super(value, isClosed);
}
/**
*
* @param value
* @return
*/
@Override
public boolean includes(T value) {
return isClosed ? N.compare(value, this.value) >= 0 : N.compare(value, this.value) > 0;
}
/**
*
* Gets a suitable hash code for the range.
*
*
* @return a hash code value for this object
*/
@Override
public int hashCode() {
int result = isClosed ? 0 : 1;
return 37 * result + N.hashCode(value);
}
/**
*
* Compares this range to another object to test if they are equal.
*
* .
*
*
* To be equal, the minimum and maximum values must be equal, which ignores any differences in the comparator.
*
*
* @param obj
* the reference object with which to compare
* @return true if this object is equal
*/
@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof LowerEndpoint) {
final LowerEndpoint other = (LowerEndpoint) obj;
return N.equals(isClosed, other.isClosed) && N.equals(value, other.value);
}
return false;
}
/**
*
* @return
*/
@Override
public String toString() {
return (isClosed ? "[" : "(") + N.toString(value);
}
}
/**
* The Class UpperEndpoint.
*
* @param
*/
static class UpperEndpoint> extends Endpoint {
/** The Constant serialVersionUID. */
private static final long serialVersionUID = 3180376045860768477L;
/**
* Instantiates a new upper endpoint.
*
* @param value
* @param isClosed
*/
UpperEndpoint(final T value, boolean isClosed) {
super(value, isClosed);
}
/**
*
* @param value
* @return
*/
@Override
public boolean includes(T value) {
return isClosed ? N.compare(value, this.value) <= 0 : N.compare(value, this.value) < 0;
}
/**
*
* @return
*/
@Override
public int hashCode() {
int result = isClosed ? 0 : 1;
return 37 * result + N.hashCode(value);
}
/**
*
* @param obj
* @return
*/
@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof UpperEndpoint) {
final UpperEndpoint other = (UpperEndpoint) obj;
return N.equals(isClosed, other.isClosed) && N.equals(value, other.value);
}
return false;
}
/**
*
* @return
*/
@Override
public String toString() {
return N.toString(value) + (isClosed ? "]" : ")");
}
}
}