All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.landawn.abacus.util.Range Maven / Gradle / Ivy

Go to download

A general programming library in Java/Android. It's easy to learn and simple to use with concise and powerful APIs.

There is a newer version: 5.2.4
Show newest version
/*
 * 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 java.util.function.Function;

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");//NOSONAR } 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); } /** * * @param * @param mapper * @return */ public > Range map(final Function mapper) { return new Range<>(new LowerEndpoint<>(mapper.apply(lowerEndpoint.value), lowerEndpoint.isClosed), new UpperEndpoint<>(mapper.apply(upperEndpoint.value), upperEndpoint.isClosed), boundType); } /** * * * @return */ 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 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 || isAfterRange(other) || isBeforeRange(other)) {//NOSONAR 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;//NOSONAR 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;//NOSONAR 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) {//NOSONAR 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(); } /** * * * @return */ @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 */ abstract static class Endpoint> implements Serializable { /** The Constant serialVersionUID. */ private static final long serialVersionUID = -1404748904424344410L; /** The value. */ final T value; //NOSONAR /** 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 ? "]" : ")"); } } }