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

io.permazen.util.Bounds Maven / Gradle / Ivy

There is a newer version: 5.1.0
Show newest version

/*
 * Copyright (C) 2015 Archie L. Cobbs. All rights reserved.
 */

package io.permazen.util;

import com.google.common.base.Preconditions;

import java.util.Comparator;
import java.util.Objects;

/**
 * Utility class used by {@link AbstractNavigableSet} and {@link AbstractNavigableMap} to define
 * the (optional) upper and lower bounds of a restricted range.
 *
 * @param  Java type of range bounds
 */
public class Bounds {

    private final T lowerBound;
    private final T upperBound;
    private final BoundType lowerBoundType;
    private final BoundType upperBoundType;

    /**
     * Convenience constructor to create a new instance with no upper or lower bounds.
     */
    public Bounds() {
        this(null, BoundType.NONE, null, BoundType.NONE);
    }

    /**
     * Create a one-sided bound.
     *
     * @param bound bound restriction value; ignored if {@code boundType} is {@link BoundType#NONE}
     * @param boundType type of bound for {@code bound}, or {@link BoundType#NONE} if there is no lower bound
     * @param upper true to create an upper bound, false to create a lower bound
     * @throws IllegalArgumentException if {@code boundType} is null
     */
    public Bounds(T bound, BoundType boundType, boolean upper) {
        this(upper ? null : bound, upper ? BoundType.NONE : boundType, upper ? bound : null, upper ? boundType : BoundType.NONE);
    }

    /**
     * Create two-sided bounds with inclusive lower bound and exclusive upper bound.
     *
     * @param lowerBound lower bound restriction value (inclusive)
     * @param upperBound upper bound restriction value (exclusive)
     */
    public Bounds(T lowerBound, T upperBound) {
        this(lowerBound, BoundType.INCLUSIVE, upperBound, BoundType.EXCLUSIVE);
    }

    /**
     * Create two-sided bounds.
     *
     * @param lowerBound lower bound restriction value (inclusive)
     * @param lowerInclusive true if {@code lowerBound} is inclusive, false if {@code lowerBound} is exclusive
     * @param upperBound upper bound restriction value (exclusive)
     * @param upperInclusive true if {@code upperBound} is inclusive, false if {@code upperBound} is exclusive
     */
    public Bounds(T lowerBound, boolean lowerInclusive, T upperBound, boolean upperInclusive) {
        this(lowerBound, lowerInclusive ? BoundType.INCLUSIVE : BoundType.EXCLUSIVE,
             upperBound, upperInclusive ? BoundType.INCLUSIVE : BoundType.EXCLUSIVE);
    }

    /**
     * Primary constructor.
     *
     * @param lowerBound lower bound restriction value; ignored if {@code lowerBoundType} is {@link BoundType#NONE}
     * @param lowerBoundType type of bound for {@code lowerBound}, or {@link BoundType#NONE} if there is no lower bound
     * @param upperBound upper bound restriction value; ignored if {@code upperBoundType} is {@link BoundType#NONE}
     * @param upperBoundType type of bound for {@code upperBound}, or {@link BoundType#NONE} if there is no upper bound
     * @throws IllegalArgumentException if {@code lowerBoundType} or {@code upperBoundType} is null
     */
    public Bounds(T lowerBound, BoundType lowerBoundType, T upperBound, BoundType upperBoundType) {
        Preconditions.checkArgument(lowerBoundType != null, "null lowerBoundType");
        Preconditions.checkArgument(upperBoundType != null, "null upperBoundType");
        this.lowerBound = lowerBoundType != BoundType.NONE ? lowerBound : null;
        this.upperBound = upperBoundType != BoundType.NONE ? upperBound : null;
        this.lowerBoundType = lowerBoundType;
        this.upperBoundType = upperBoundType;
    }

    /**
     * Determine whether this instance has a lower bound.
     *
     * @return false if this instance's {@linkplain #getLowerBoundType lower bound type} is {@link BoundType#NONE}, otherwise true
     */
    public boolean hasLowerBound() {
        return this.lowerBoundType != BoundType.NONE;
    }

    /**
     * Determine whether this instance has an upper bound.
     *
     * @return false if this instance's {@linkplain #getUpperBoundType upper bound type} is {@link BoundType#NONE}, otherwise true
     */
    public boolean hasUpperBound() {
        return this.upperBoundType != BoundType.NONE;
    }

    /**
     * Get the Java value corresponding to the lower bound restriction, if any.
     * Returns null when {@link #getLowerBoundType} returns {@link BoundType#NONE}.
     *
     * @return lower bound Java value, or null if there is no lower bound
     */
    public T getLowerBound() {
        return this.lowerBound;
    }

    /**
     * Get the Java value corresponding to the upper bound restriction, if any.
     * Returns null when {@link #getUpperBoundType} returns {@link BoundType#NONE}.
     *
     * @return upper bound Java value, or null if there is no upper bound
     */
    public T getUpperBound() {
        return this.upperBound;
    }

    /**
     * Get the type of the lower bound that corresponds to {@link #getLowerBound}.
     *
     * @return lower bound restriction type, never null
     */
    public BoundType getLowerBoundType() {
        return this.lowerBoundType;
    }

    /**
     * Get the type of the upper bound that corresponds to {@link #getUpperBound}.
     *
     * @return upper bound restriction type, never null
     */
    public BoundType getUpperBoundType() {
        return this.upperBoundType;
    }

    /**
     * Create an instance like this instance but with the upper and lower bounds reversed.
     * Obviously, the result is only sensible when a reversed {@link Comparator} is also used.
     *
     * @return reversal of this instance
     */
    public Bounds reverse() {
        return new Bounds<>(this.upperBound, this.upperBoundType, this.lowerBound, this.lowerBoundType);
    }

    /**
     * Create an instance like this instance but with a different lower bound.
     *
     * @param newLowerBound new lower bound restriction value; ignored if {@code newLowerBoundType} is {@link BoundType#NONE}
     * @param newLowerBoundType type of bound for {@code newLowerBound}, or {@link BoundType#NONE} if there is no lower bound
     * @return instance with new lower bound
     * @throws IllegalArgumentException if {@code newLowerBoundType} is null
     */
    public Bounds withLowerBound(T newLowerBound, BoundType newLowerBoundType) {
        return new Bounds<>(newLowerBound, newLowerBoundType, this.upperBound, this.upperBoundType);
    }

    /**
     * Create an instance like this instance but with a different upper bound.
     *
     * @param newUpperBound new upper bound restriction value; ignored if {@code newUpperBoundType} is {@link BoundType#NONE}
     * @param newUpperBoundType type of bound for {@code newUpperBound}, or {@link BoundType#NONE} if there is no upper bound
     * @return instance with new upper bound
     * @throws IllegalArgumentException if {@code newUpperBoundType} is null
     */
    public Bounds withUpperBound(T newUpperBound, BoundType newUpperBoundType) {
        return new Bounds<>(this.lowerBound, this.lowerBoundType, newUpperBound, newUpperBoundType);
    }

    /**
     * Create an instance like this instance but with the lower bound removed.
     *
     * @return new instance
     */
    public Bounds withoutLowerBound() {
        return new Bounds<>(null, BoundType.NONE, this.upperBound, this.upperBoundType);
    }

    /**
     * Create an instance like this instance but with the upper bound removed.
     *
     * @return new instance
     */
    public Bounds withoutUpperBound() {
        return new Bounds<>(this.lowerBound, this.lowerBoundType, null, BoundType.NONE);
    }

    /**
     * Check whether the given value is within the lower bound of this instance.
     *
     * @param comparator comparator used to compare values, or null for natural ordering
     * @param value value to check
     * @return true if {@code value} is within bounds, false otherwise
     */
    public boolean isWithinLowerBound(Comparator comparator, T value) {
        return this.isWithinBound(comparator, value, true, false);
    }

    /**
     * Check whether the given value is within the upper bound of this instance.
     *
     * @param comparator comparator used to compare values, or null for natural ordering
     * @param value value to check
     * @return true if {@code value} is within bounds, false otherwise
     */
    public boolean isWithinUpperBound(Comparator comparator, T value) {
        return this.isWithinBound(comparator, value, true, true);
    }

    /**
     * Check whether the given value is within the bounds of this instance.
     *
     * 

* Equivalent to: *

* isWithinLowerBound(comparator, value) && isWithinUpperBound(comparator, value) *
* * @param comparator comparator used to compare values, or null for natural ordering * @param value value to check * @return true if {@code value} is within bounds, false otherwise */ public boolean isWithinBounds(Comparator comparator, T value) { return this.isWithinLowerBound(comparator, value) && this.isWithinUpperBound(comparator, value); } /** * Determine whether the given new {@link Bounds} is within of the bounds of this instance. * * @param comparator comparator used to compare values, or null for natural ordering * @param newBounds new bounds * @return true if {@code newBounds} is in range, false otherwise * @throws IllegalArgumentException if {@code newBounds} is null */ public boolean isWithinBounds(Comparator comparator, Bounds newBounds) { Preconditions.checkArgument(newBounds != null, "null newBounds"); if (newBounds.lowerBoundType != BoundType.NONE && !this.isWithinBound(comparator, newBounds.lowerBound, newBounds.lowerBoundType.isInclusive(), false)) return false; if (newBounds.upperBoundType != BoundType.NONE && !this.isWithinBound(comparator, newBounds.upperBound, newBounds.upperBoundType.isInclusive(), true)) return false; return true; } /** * Check whether the given value is outside one of the bounds of this instance. * * @param comparator comparator used to compare values, or null for natural ordering * @param value value to check * @param requireInclusive whether value should not be considered included when it is exactly equal to an exclusive bound * @param upper true to check against upper bound, false to check against lower bound * @throws IllegalArgumentException if {@link newBound} is out of range * @throws IllegalArgumentException if {@link newBoundType} is null */ @SuppressWarnings("unchecked") private boolean isWithinBound(Comparator comparator, T value, boolean requireInclusive, boolean upper) { // Check bound type final BoundType boundType = upper ? this.upperBoundType : this.lowerBoundType; if (boundType == BoundType.NONE) return true; // Compare value to bound final T bound = upper ? this.upperBound : this.lowerBound; final int diff = comparator != null ? comparator.compare(value, bound) : ((Comparable)value).compareTo(bound); // Handle value equal to bound if (diff == 0) { if (boundType == BoundType.INCLUSIVE || !requireInclusive) return true; return false; } // Value is either inside or outside bound return upper ? diff < 0 : diff > 0; } // Static Methods /** * Create an instance with bounds that allow only the given value. * * @param value unique bounded value * @param Java type of range bounds * @return bounds containing exactly {@code value} */ public static Bounds eq(T value) { return new Bounds<>(value, BoundType.INCLUSIVE, value, BoundType.INCLUSIVE); } /** * Create an instance with an inclusive lower bound. * * @param lowerBound inclusive lower bound * @param Java type of range bounds * @return bounds containing all values greater than or equal to {@code value} */ public static Bounds ge(T lowerBound) { return new Bounds<>(lowerBound, BoundType.INCLUSIVE, false); } /** * Create an instance with an exclusive lower bound. * * @param lowerBound exclusive lower bound * @param Java type of range bounds * @return bounds containing all values greater than {@code value} */ public static Bounds gt(T lowerBound) { return new Bounds<>(lowerBound, BoundType.EXCLUSIVE, false); } /** * Create an instance with an inclusive upper bound. * * @param upperBound inclusive upper bound * @param Java type of range bounds * @return bounds containing all values less than or equal to {@code value} */ public static Bounds le(T upperBound) { return new Bounds<>(upperBound, BoundType.INCLUSIVE, true); } /** * Create an instance with an exclusive upper bound. * * @param upperBound exclusive upper bound * @param Java type of range bounds * @return bounds containing all values less than {@code value} */ public static Bounds lt(T upperBound) { return new Bounds<>(upperBound, BoundType.EXCLUSIVE, true); } // Object @Override public String toString() { return "Bounds[lower(" + this.lowerBoundType + ")=" + this.lowerBound + ",upper(" + this.upperBoundType + ")=" + this.upperBound + "]"; } @Override public boolean equals(Object obj) { if (obj == this) return true; if (obj == null || obj.getClass() != this.getClass()) return false; final Bounds that = (Bounds)obj; return this.lowerBoundType == that.lowerBoundType && this.upperBoundType == that.upperBoundType && Objects.equals(this.lowerBound, that.lowerBound) && Objects.equals(this.upperBound, that.upperBound); } @Override public int hashCode() { return this.lowerBoundType.hashCode() ^ this.upperBoundType.hashCode() ^ Objects.hashCode(this.lowerBound) ^ Objects.hashCode(this.upperBound); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy