From 130dc689ba323fc0013b69773af71192bffe61ed Mon Sep 17 00:00:00 2001 From: huangchengxing <841396397@qq.com> Date: Fri, 30 Sep 2022 14:41:12 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=94=AF=E6=8C=81=E5=A4=84?= =?UTF-8?q?=E7=90=86=E5=8C=BA=E9=97=B4=E7=9A=84=E5=B7=A5=E5=85=B7=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/cn/hutool/core/lang/range/Bound.java | 587 ++++++++++++++++++ .../cn/hutool/core/lang/range/BoundType.java | 140 +++++ .../hutool/core/lang/range/BoundedRange.java | 572 +++++++++++++++++ .../cn/hutool/core/lang/range/BoundTest.java | 211 +++++++ .../hutool/core/lang/range/BoundTypeTest.java | 83 +++ .../core/lang/range/BoundedRangeTest.java | 283 +++++++++ 6 files changed, 1876 insertions(+) create mode 100644 hutool-core/src/main/java/cn/hutool/core/lang/range/Bound.java create mode 100644 hutool-core/src/main/java/cn/hutool/core/lang/range/BoundType.java create mode 100644 hutool-core/src/main/java/cn/hutool/core/lang/range/BoundedRange.java create mode 100644 hutool-core/src/test/java/cn/hutool/core/lang/range/BoundTest.java create mode 100644 hutool-core/src/test/java/cn/hutool/core/lang/range/BoundTypeTest.java create mode 100644 hutool-core/src/test/java/cn/hutool/core/lang/range/BoundedRangeTest.java diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/range/Bound.java b/hutool-core/src/main/java/cn/hutool/core/lang/range/Bound.java new file mode 100644 index 000000000..105d51f13 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/lang/range/Bound.java @@ -0,0 +1,587 @@ +package cn.hutool.core.lang.range; + +import cn.hutool.core.text.CharSequenceUtil; + +import java.util.Objects; +import java.util.function.Predicate; + +/** + *

边界对象,描述具有特定上界或下界的单侧无界的区间。 + * + *

边界的类型

+ *

边界根据其{@link #getType()}所获得的类型,可用于描述基于边界值t的不等式: + *

+ * 当作为{@link Predicate}使用时,可用于判断入参对象是否能满足当前实例所对应的不等式。 + * + *

边界的比较

+ *

边界对象本身实现了{@link Comparable}接口, + * 当使用{@link Comparable#compareTo}比较两个边界对象时, + * 返回的比较值表示两个边界对象对应的点在实数轴上从左到右的先后顺序。
+ * 比如: + * 若令当前边界点为t1,另一边界点为t2,则有 + *

+ * + * @param 边界值类型 + * @author huangchengxing + * @see BoundType + * @see BoundedRange + * @since 6.0.0 + */ +public interface Bound> extends Predicate, Comparable> { + + /** + * 无穷小 + */ + String INFINITE_MIN = "-\u221e"; + + /** + * 无穷大 + */ + String INFINITE_MAX = "+\u221e"; + + /** + * 无限小的左边界 + */ + @SuppressWarnings("rawtypes") + NoneLowerBound NONE_LOWER_BOUND = new NoneLowerBound(); + + /** + * 无限大的右边界 + */ + @SuppressWarnings("rawtypes") + NoneUpperBound NONE_UPPER_BOUND = new NoneUpperBound(); + + /** + * {@code {x | x > -∞}} + * + * @param 边界值类型 + * @return 区间 + */ + @SuppressWarnings("unchecked") + static > Bound noneLowerBound() { + return NONE_LOWER_BOUND; + } + + /** + * {@code {x | x < +∞}} + * + * @param 边界值类型 + * @return 区间 + */ + @SuppressWarnings("unchecked") + static > Bound noneUpperBound() { + return NONE_UPPER_BOUND; + } + + /** + * {@code {x | x > min}} + * + * @param min 最小值 + * @param 边界值类型 + * @return 区间 + */ + static > Bound greaterThan(final T min) { + return new FiniteBound<>(Objects.requireNonNull(min), BoundType.OPEN_LOWER_BOUND); + } + + /** + * {@code {x | x >= min}} + * + * @param min 最小值 + * @param 边界值类型 + * @return 区间 + */ + static > Bound atLeast(final T min) { + return new FiniteBound<>(Objects.requireNonNull(min), BoundType.CLOSE_LOWER_BOUND); + } + + /** + * {@code {x | x < max}} + * + * @param max 最大值 + * @param 边界值类型 + * @return 区间 + */ + static > Bound lessThan(final T max) { + return new FiniteBound<>(Objects.requireNonNull(max), BoundType.OPEN_UPPER_BOUND); + } + + /** + * {@code {x | x <= max}} + * + * @param max 最大值 + * @param 边界值类型 + * @return 区间 + */ + static > Bound atMost(final T max) { + return new FiniteBound<>(Objects.requireNonNull(max), BoundType.CLOSE_UPPER_BOUND); + } + + /** + * 获取边界值 + * + * @return 边界值 + */ + T getValue(); + + /** + * 获取边界类型 + * + * @return 边界类型 + */ + BoundType getType(); + + /** + * 检验指定值是否在当前边界表示的范围内 + * + * @param t 要检验的值,不允许为{@code null} + * @return 是否 + */ + @Override + boolean test(T t); + + /** + *

比较另一边界与当前边界在坐标轴上位置的先后顺序。
+ * 若令当前边界为t1,另一边界为t2,则有 + *

    + *
  • -1:t1t2的左侧;
  • + *
  • 0:t1t2的重合;
  • + *
  • -1:t1t2的右侧;
  • + *
+ * + * @param bound 边界 + * @return 位置 + */ + @Override + int compareTo(final Bound bound); + + /** + * 获取{@code "[value"}或{@code "(value"}格式的字符串 + * + * @return 字符串 + */ + String descBound(); + + /** + * 对当前边界取反 + * + * @return 取反后的边界 + */ + @Override + Bound negate(); + + /** + * 将当前实例转为一个区间 + * + * @return 区间 + */ + BoundedRange toRange(); + + /** + * 获得当前实例对应的{@code {x| x >= xxx}}格式的不等式字符串 + * + * @return 字符串 + */ + @Override + String toString(); + + /** + * 由一个有限值构成的边界 + * + * @param 边界值类型 + */ + class FiniteBound> implements Bound { + + /** + * 边界值 + */ + private final T value; + + /** + * 边界类型 + */ + private final BoundType type; + + /** + * 构造 + * + * @param value 边界值 + * @param type 边界类型 + */ + FiniteBound(final T value, final BoundType type) { + this.value = value; + this.type = type; + } + + /** + * 获取边界值 + * + * @return 边界值 + */ + @Override + public T getValue() { + return value; + } + + /** + * 获取边界类型 + * + * @return 边界类型 + */ + @Override + public BoundType getType() { + return type; + } + + /** + * 检验指定值是否在当前边界表示的范围内 + * + * @param t 要检验的值,不允许为{@code null} + * @return 是否 + */ + @Override + public boolean test(final T t) { + final BoundType bt = this.getType(); + final int compareValue = getValue().compareTo(t); + // 与边界值相等 + if (compareValue == 0) { + return bt.isClose(); + } + // 小于或大于边界值 + return compareValue > 0 ? bt.isUpperBound() : bt.isLowerBound(); + } + + /** + *

比较另一边界与当前边界在坐标轴上位置的先后顺序。
+ * 若令当前边界为t1,另一边界为t2,则有 + *

    + *
  • -1:t1t2的左侧;
  • + *
  • 0:t1t2的重合;
  • + *
  • -1:t1t2的右侧;
  • + *
+ * + * @param bound 边界 + * @return 位置 + */ + @Override + public int compareTo(final Bound bound) { + // 另一边界为无限小的左边界,则当前边界必然靠后 + if (bound instanceof Bound.NoneLowerBound) { + return 1; + } + // 另一边界为无限大的右边界,则当前边界必然靠前 + if (bound instanceof Bound.NoneUpperBound) { + return -1; + } + // 两值不相等,直接比较边界值 + if (!Objects.equals(getValue(), bound.getValue())) { + return getValue().compareTo(bound.getValue()); + } + // 两边界值相等 + return compareIfSameBoundValue(bound); + } + + /** + * 当两个边界的值不相等时,判断它们在坐标轴上位置的先后顺序 + */ + private int compareIfSameBoundValue(final Bound bound) { + final BoundType bt1 = this.getType(); + final BoundType bt2 = bound.getType(); + // 两边界类型相同,说明连边界重合 + if (bt1 == bt2) { + return 0; + } + // 一为左边界,一为右边界,则左边界恒在右边界后 + if (bt1.isDislocated(bt2)) { + return bt1.isLowerBound() ? 1 : -1; + } + // 都为左边界,则封闭边界在前,若都为右边界,则封闭边界在后 + return bt1.isLowerBound() ? + Integer.compare(bt1.getCode(), bt2.getCode()) : Integer.compare(bt1.getCode(), bt2.getCode()); + } + + /** + * 获取{@code "[value"}或{@code "(value"}格式的字符串 + * + * @return 字符串 + */ + @Override + public String descBound() { + final BoundType bt = getType(); + return bt.isLowerBound() ? bt.getSymbol() + getValue() : getValue() + bt.getSymbol(); + } + + /** + * 对当前边界取反 + * + * @return 取反后的边界 + */ + @Override + public Bound negate() { + return new FiniteBound<>(value, getType().negate()); + } + + /** + * 将当前实例转为一个区间 + * + * @return 区间 + */ + @Override + public BoundedRange toRange() { + return getType().isLowerBound() ? + new BoundedRange<>(this, Bound.noneUpperBound()) : new BoundedRange<>(Bound.noneLowerBound(), this); + } + + /** + * 两实例是否相等 + * + * @param o 另一实例 + * @return 是否 + */ + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final FiniteBound that = (FiniteBound)o; + return value.equals(that.value) && type == that.type; + } + + /** + * 获取哈希值 + * + * @return 哈希值 + */ + @Override + public int hashCode() { + return Objects.hash(value, type); + } + + /** + * 获得当前实例对应的{@code {x| x >= xxx}}格式的不等式字符串 + * + * @return 字符串 + */ + @Override + public String toString() { + return CharSequenceUtil.format( + "{x | x {} {}}", type.getOperator(), value + ); + } + } + + /** + * 无限小的左边界 + * + * @param 边界值类型 + */ + class NoneLowerBound> implements Bound { + + private NoneLowerBound() { + } + + /** + * 获取边界值 + * + * @return 边界值 + */ + @Override + public T getValue() { + return null; + } + + /** + * 获取边界类型 + * + * @return 边界类型 + */ + @Override + public BoundType getType() { + return BoundType.OPEN_LOWER_BOUND; + } + + /** + * 检验指定值是否在当前边界表示的范围内 + * + * @param t 要检验的值,不允许为{@code null} + * @return 是否 + */ + @Override + public boolean test(final T t) { + return true; + } + + /** + *

比较另一边界与当前边界在坐标轴上位置的先后顺序。
+ * 若令当前边界为t1,另一边界为t2,则有 + *

    + *
  • -1:t1t2的左侧;
  • + *
  • 0:t1t2的重合;
  • + *
  • -1:t1t2的右侧;
  • + *
+ * + * @param bound 边界 + * @return 位置 + */ + @Override + public int compareTo(final Bound bound) { + return bound instanceof Bound.NoneLowerBound ? 0 : -1; + } + + /** + * 获取{@code "[value"}或{@code "(value"}格式的字符串 + * + * @return 字符串 + */ + @Override + public String descBound() { + return getType().getSymbol() + INFINITE_MIN; + } + + /** + * 对当前边界取反 + * + * @return 取反后的边界 + */ + @Override + public Bound negate() { + return this; + } + + /** + * 将当前实例转为一个区间 + * + * @return 区间 + */ + @Override + public BoundedRange toRange() { + return BoundedRange.all(); + } + + /** + * 获得当前实例对应的{@code { x | x >= xxx}}格式的不等式字符串 + * + * @return 字符串 + */ + @Override + public String toString() { + return "{x | x > -\u221e}"; + } + + } + + /** + * 无限大的右边界 + * + * @param 边界值类型 + */ + class NoneUpperBound> implements Bound { + + private NoneUpperBound() { + } + + /** + * 获取边界值 + * + * @return 边界值 + */ + @Override + public T getValue() { + return null; + } + + /** + * 获取边界类型 + * + * @return 边界类型 + */ + @Override + public BoundType getType() { + return BoundType.OPEN_UPPER_BOUND; + } + + /** + * 检验指定值是否在当前边界表示的范围内 + * + * @param t 要检验的值,不允许为{@code null} + * @return 是否 + */ + @Override + public boolean test(final T t) { + return true; + } + + /** + *

比较另一边界与当前边界在坐标轴上位置的先后顺序。
+ * 若令当前边界为t1,另一边界为t2,则有 + *

    + *
  • -1:t1t2的左侧;
  • + *
  • 0:t1t2的重合;
  • + *
  • -1:t1t2的右侧;
  • + *
+ * + * @param bound 边界 + * @return 位置 + */ + @Override + public int compareTo(final Bound bound) { + return bound instanceof Bound.NoneUpperBound ? 0 : 1; + } + + /** + * 获取{@code "[value"}或{@code "(value"}格式的字符串 + * + * @return 字符串 + */ + @Override + public String descBound() { + return INFINITE_MAX + getType().getSymbol(); + } + + /** + * 获得当前实例对应的{@code { x | x >= xxx}}格式的不等式字符串 + * + * @return 字符串 + */ + @Override + public String toString() { + return "{x | x < +\u221e}"; + } + + /** + * 对当前边界取反 + * + * @return 取反后的边界 + */ + @Override + public Bound negate() { + return this; + } + + /** + * 将当前实例转为一个区间 + * + * @return 区间 + */ + @Override + public BoundedRange toRange() { + return BoundedRange.all(); + } + + } + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/range/BoundType.java b/hutool-core/src/main/java/cn/hutool/core/lang/range/BoundType.java new file mode 100644 index 000000000..55e43071f --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/lang/range/BoundType.java @@ -0,0 +1,140 @@ +package cn.hutool.core.lang.range; + +/** + * 边界类型 + * + * @author huangchengxing + * @since 6.0.0 + */ +public enum BoundType { + + /** + * 表示一个左闭区间,等同于{@code {x | x >= a}} + */ + CLOSE_LOWER_BOUND("[", ">=", -2), + + /** + * 表示一个左开区间,等同于{@code {x | x > a}} + */ + OPEN_LOWER_BOUND("(", ">", -1), + + /** + * 表示一个右开区间,等同于{@code {x | x < a}} + */ + OPEN_UPPER_BOUND(")", "<", 1), + + /** + * 表示一个右闭区间,等同于{@code {x | x <= a}} + */ + CLOSE_UPPER_BOUND("]", "<=", 2); + + /** + * 符号 + */ + private final String symbol; + + /** + * 运算符 + */ + private final String operator; + + /** + * 是否为开区间 + */ + private final int code; + + /** + * 构造 + */ + BoundType(final String symbol, final String operator, final int code) { + this.symbol = symbol; + this.operator = operator; + this.code = code; + } + + /** + * 获取符号 + * + * @return 符号 + */ + public String getSymbol() { + return symbol; + } + + /** + * 获取code + * + * @return code + */ + public int getCode() { + return code; + } + + /** + * 获取运算符 + * + * @return 运算符 + */ + public String getOperator() { + return operator; + } + + /** + * 该边界类型是否与当前边界错位,即一个的左边界,一个是右边界 + * + * @param boundType 另一边界类型 + * @return 是否 + */ + public boolean isDislocated(BoundType boundType) { + return code * boundType.code < 0; + } + + /** + * 是下界 + * + * @return 是否 + */ + public boolean isLowerBound() { + return code < 0; + } + + /** + * 是上界 + * + * @return 是否 + */ + public boolean isUpperBound() { + return code > 0; + } + + /** + * 是闭区间 + * + * @return 是否 + */ + public boolean isClose() { + return (code & 1) == 0; + } + + /** + * 是开区间 + * + * @return 是否 + */ + public boolean isOpen() { + return (code & 1) == 1; + } + + /** + * 对边界类型取反 + * + * @return 取反后的边界类型 + */ + public BoundType negate() { + if (isLowerBound()) { + return isOpen() ? CLOSE_UPPER_BOUND : OPEN_UPPER_BOUND; + } + return isOpen() ? CLOSE_LOWER_BOUND : OPEN_LOWER_BOUND; + } + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/range/BoundedRange.java b/hutool-core/src/main/java/cn/hutool/core/lang/range/BoundedRange.java new file mode 100644 index 000000000..bed3e30c4 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/lang/range/BoundedRange.java @@ -0,0 +1,572 @@ +package cn.hutool.core.lang.range; + +import cn.hutool.core.lang.Assert; +import cn.hutool.core.lang.Opt; + +import java.util.Objects; +import java.util.function.Predicate; + +/** + *

参考GuavaRange实现,用于描述作为上下界的两个{@link Bound}实例围成的一段区间。
+ * 作为{@link Predicate}使用时,可检验指定值是否在区间中,即指定值是否同时满足上下界的{@link Bound#test}方法。 + * + *

区间的类型

+ *

支持通过工厂方法创建下述几种类型的区间 + * 区间 数学定义 工厂方法 + * {@code (a, b)} {@code {x | a < x < b}} {@link #open} + * {@code [a, b]} {@code {x | a <= x <= b}}{@link #close} + * {@code (a, b]} {@code {x | a < x <= b}} {@link #openClose} + * {@code [a, b)} {@code {x | a <= x < b}} {@link #closeOpen} + * {@code (a, +∞)} {@code {x | x > a}} {@link #greaterThan} + * {@code [a, +∞)} {@code {x | x >= a}} {@link #atLeast} + * {@code (-∞, b)} {@code {x | x < b}} {@link #lessThan} + * {@code (-∞, b]} {@code {x | x <= b}} {@link #atMost} + * {@code (-∞, +∞)}{@code {x}} {@link #all} + * + * + *

空区间

+ *

根据数学定义,当区间中无任何实数时,认为该区间代表的集合为空集, + * 用户可通过{@link #isEmpty}确认当前实例是否为空区间。
+ * 若实例上界a,下界为b,则当实例满足下述任意条件时,认为其为一个空区间: + *

    + *
  • {@code a > b};
  • + *
  • {@code [a, b)},且{@code a == b};
  • + *
  • {@code (a, b)},且{@code a == b};
  • + *
  • {@code (a, b]},且{@code a == b};
  • + *
+ * 当通过工厂方法创建区间时,若区间为空,则会抛出{@link IllegalArgumentException}, + * 但是通过交并操作仍有可能创建出满足上述描述的空区间。 + * 此时若空区间参与操作可能得到意外的结果, + * 因此对通过非工厂方法得到的区间,在操作前有必要通过{@link #isEmpty}进行检验。 + * + * @param 边界值类型 + * @author huangchengxing + * @see Bound + * @since 6.0.0 + */ +public class BoundedRange> implements Predicate { + + /** + * 双向无界的区间 + */ + @SuppressWarnings("rawtypes") + private static final BoundedRange ALL = new BoundedRange(Bound.noneLowerBound(), Bound.noneUpperBound()); + + /** + * 下界 + */ + private final Bound lowerBound; + + /** + * 上界 + */ + private final Bound upperBound; + + /** + * 构建一个上下界皆无限大的区间,即{@code {x | -∞ < x < +∞}} + * + * @return 区间 + */ + @SuppressWarnings("unchecked") + static > BoundedRange all() { + return ALL; + } + + /** + * 构建一个闭区间,即{@code {x | lowerBound <= x <= upperBound}} + * + * @param lowerBound 下界,不能为空 + * @param upperBound 上界,不能为空 + * @param 边界值类型 + * @return 区间 + * @throws IllegalArgumentException 当创建的区间表示的集合为空时抛出 + * @throws NullPointerException 上界或下界为{@code null}时抛出 + */ + static > BoundedRange close(final T lowerBound, final T upperBound) { + Objects.requireNonNull(lowerBound); + Objects.requireNonNull(upperBound); + return checkEmpty( + new BoundedRange<>( + Bound.atLeast(lowerBound), Bound.atMost(upperBound) + ) + ); + } + + /** + * 构建一个开区间,即{@code {x | lowerBound < x < upperBound}} + * + * @param lowerBound 下界,不能为空 + * @param upperBound 上界,不能为空 + * @param 边界值类型 + * @return 区间 + * @throws IllegalArgumentException 当创建的区间表示的集合为空时抛出 + * @throws NullPointerException 上界或下界为{@code null}时抛出 + */ + static > BoundedRange open(final T lowerBound, final T upperBound) { + Objects.requireNonNull(lowerBound); + Objects.requireNonNull(upperBound); + return checkEmpty( + new BoundedRange<>(Bound.greaterThan(lowerBound), Bound.lessThan(upperBound)) + ); + } + + /** + * 构建一个左闭右开区间,即{@code {x | lowerBound <= x < upperBound}} + * + * @param lowerBound 下界,不能为空 + * @param upperBound 上界,不能为空 + * @param 边界值类型 + * @return 区间 + * @throws IllegalArgumentException 当创建的区间表示的集合为空时抛出 + * @throws NullPointerException 上界或下界为{@code null}时抛出 + */ + static > BoundedRange closeOpen(final T lowerBound, final T upperBound) { + Objects.requireNonNull(lowerBound); + Objects.requireNonNull(upperBound); + return checkEmpty( + new BoundedRange<>( + Bound.atLeast(lowerBound), + Bound.lessThan(upperBound) + ) + ); + } + + /** + * 构建一个左闭右开区间,即{@code {x | lowerBound < x <= upperBound}} + * + * @param lowerBound 下界,不能为空 + * @param upperBound 上界,不能为空 + * @param 边界值类型 + * @return 区间 + * @throws IllegalArgumentException 当创建的区间表示的集合为空时抛出 + * @throws NullPointerException 上界或下界为{@code null}时抛出 + */ + static > BoundedRange openClose(final T lowerBound, final T upperBound) { + Objects.requireNonNull(lowerBound); + Objects.requireNonNull(upperBound); + return checkEmpty( + new BoundedRange<>( + Bound.greaterThan(lowerBound), + Bound.atMost(upperBound) + ) + ); + } + + /** + * {@code {x | lowerBound < x < +∞}} + * + * @param lowerBound 下界,不能为空 + * @param 边界值类型 + * @return 区间 + * @throws NullPointerException 下界为{@code null}时抛出 + * @see Bound#toRange() + */ + static > BoundedRange greaterThan(final T lowerBound) { + return Bound.greaterThan(lowerBound).toRange(); + } + + /** + * {@code {x | lowerBound < x < +∞}} + * + * @param lowerBound 下界,不能为空 + * @param 边界值类型 + * @return 区间 + * @throws NullPointerException 下界为{@code null}时抛出 + * @see Bound#toRange() + */ + static > BoundedRange atLeast(final T lowerBound) { + return Bound.atLeast(lowerBound).toRange(); + } + + /** + * {@code {x | -∞ < x < upperBound}} + * + * @param upperBound 上界,不能为空 + * @param 边界值类型 + * @return 区间 + * @throws NullPointerException 上界为{@code null}时抛出 + * @see Bound#toRange() + */ + static > BoundedRange lessThan(final T upperBound) { + return Bound.lessThan(upperBound).toRange(); + } + + /** + * {@code {x | -∞ < x <= max}} + * + * @param upperBound 上界,不能为空 + * @param 边界值类型 + * @return 区间 + * @throws NullPointerException 上界为{@code null}时抛出 + * @see Bound#toRange() + */ + static > BoundedRange atMost(final T upperBound) { + return Bound.atMost(upperBound).toRange(); + } + + /** + * 构造 + * + * @param lowerBound 下界 + * @param upperBound 上界 + */ + BoundedRange(final Bound lowerBound, final Bound upperBound) { + this.lowerBound = lowerBound; + this.upperBound = upperBound; + } + + // endregion + + // region ========== 通用方法 ========== + + /** + * 获取下界 + * + * @return 下界 + */ + public Bound getLowerBound() { + return lowerBound; + } + + /** + * 获取下界值 + * + * @return 下界值 + */ + public T getLowerBoundValue() { + return getLowerBound().getValue(); + } + + /** + * 是否有下界 + * + * @return 是否 + */ + public boolean hasLowerBound() { + return Objects.nonNull(getLowerBound().getValue()); + } + + /** + * 获取上界 + * + * @return 上界 + */ + public Bound getUpperBound() { + return upperBound; + } + + /** + * 获取上界值 + * + * @return 上界值 + */ + public T getUpperBoundValue() { + return getUpperBound().getValue(); + } + + /** + * 是否有上界 + * + * @return 是否 + */ + public boolean hasUpperBound() { + return Objects.nonNull(getUpperBound().getValue()); + } + + /** + *

当前区间是否为空。
+ * 当由下界left与上界right构成的区间, + * 符合下述任意条件时,认为当前区间为空: + *

    + *
  • 对任何区间,有{@code left > right};
  • + *
  • 对半开半闭区间{@code [left, right)},有{@code left == right};
  • + *
  • 对开区间{@code (left, right)},有{@code left == right};
  • + *
  • 对半开半闭区间{@code (left, right]},有{@code left == right};
  • + *
+ * + * @return 是否 + */ + public boolean isEmpty() { + Bound low = getLowerBound(); + Bound up = getUpperBound(); + if (low instanceof Bound.NoneLowerBound || up instanceof Bound.NoneUpperBound) { + return false; + } + int compareValue = low.getValue().compareTo(up.getValue()); + if (compareValue < 0) { + return false; + } + // 上界小于下界时为空 + return compareValue > 0 + // 上下界的边界值相等,且不为退化区间是为空 + || !(low.getType().isClose() && up.getType().isClose()); + } + + /** + * 输出当前区间的字符串,格式为{@code "[a, b]"} + * + * @return 字符串 + */ + @Override + public String toString() { + return getLowerBound().descBound() + ", " + getUpperBound().descBound(); + } + + /** + * 比较两个实例是否相等 + * + * @param o 另一实例 + * @return 是否 + */ + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final BoundedRange that = (BoundedRange)o; + return lowerBound.equals(that.lowerBound) && upperBound.equals(that.upperBound); + } + + /** + * 获取实例哈希值 + * + * @return 哈希值 + */ + @Override + public int hashCode() { + return Objects.hash(lowerBound, upperBound); + } + + // endregion + + // region ========== 判断交并集 ========== + + /** + * {@code other}是否是当前区间的子集 + * + * @param other 另一区间 + * @return 是否 + */ + public boolean isSuperset(final BoundedRange other) { + return getLowerBound().compareTo(other.getLowerBound()) <= 0 + && getUpperBound().compareTo(other.getUpperBound()) >= 0; + } + + /** + * {@code other}是否是当前区间的子集 + * + * @param other 另一区间 + * @return 是否 + */ + public boolean isProperSuperset(final BoundedRange other) { + return getLowerBound().compareTo(other.getLowerBound()) < 0 + && getUpperBound().compareTo(other.getUpperBound()) > 0; + } + + /** + * 当前区间是否是{@code other}的子集 + * + * @param other 另一区间 + * @return 是否 + */ + public boolean isSubset(final BoundedRange other) { + return getLowerBound().compareTo(other.getLowerBound()) >= 0 + && getUpperBound().compareTo(other.getUpperBound()) <= 0; + } + + /** + * 当前区间是否是{@code other}的真子集 + * + * @param other 另一区间 + * @return 是否 + */ + public boolean isProperSubset(final BoundedRange other) { + return getLowerBound().compareTo(other.getLowerBound()) > 0 + && getUpperBound().compareTo(other.getUpperBound()) < 0; + } + + /** + * {@code other}是否与当前区间不相交 + * + * @param other 另一区间 + * @return 是否 + */ + public boolean isDisjoint(final BoundedRange other) { + return getLowerBound().compareTo(other.getUpperBound()) > 0 + || getUpperBound().compareTo(other.getLowerBound()) < 0; + } + + /** + * {@code other}是否与当前区间相交: + * + * @param other 另一区间 + * @return 是否 + */ + public boolean isIntersected(final BoundedRange other) { + return !isDisjoint(other); + } + + /** + * {@code other}与当前区间是否相等 + * + * @param other 另一区间 + * @return 是否 + */ + public boolean isEquals(final BoundedRange other) { + Objects.requireNonNull(other); + return other.getLowerBound().compareTo(getLowerBound()) == 0 + && other.getUpperBound().compareTo(getUpperBound()) == 0; + } + + /** + * 指定值是否在当前区间内 + * + * @param value 要检测的值 + * @return 是否 + */ + @Override + public boolean test(final T value) { + return getLowerBound() + .and(getUpperBound()) + .test(value); + } + + // endregion + + // region ========== 交并集操作 ========== + + /** + * 若{@code other}与当前区间相交,则将其与当前区间合并。 + * + * @param other 另一区间 + * @return 合并后的新区间,若两区间不相交则返回当前集合 + */ + public BoundedRange unionIfIntersected(final BoundedRange other) { + Objects.requireNonNull(other); + if (isDisjoint(other)) { + return this; + } + return new BoundedRange<>( + getMin(getLowerBound(), other.getLowerBound()), + getMax(getUpperBound(), other.getUpperBound()) + ); + } + + /** + * 获得包含当前区间与指定区间的最小的区间 + * + * @param other 另一区间 + * @return 包含当前区间与指定区间的最小的区间 + */ + public BoundedRange span(final BoundedRange other) { + Objects.requireNonNull(other); + return new BoundedRange<>( + getMin(getLowerBound(), other.getLowerBound()), + getMax(getUpperBound(), other.getUpperBound()) + ); + } + + /** + * 若{@code other}与当前区间不相连,则获得两区间中间的间隔部分 + * + * @param other 另一区间 + * @return 代表间隔部分的区间,若两区间相交则返回{@code null} + */ + public BoundedRange gap(final BoundedRange other) { + Objects.requireNonNull(other); + if (isIntersected(other)) { + return null; + } + return new BoundedRange<>( + getMin(getUpperBound(), other.getUpperBound()).negate(), + getMax(getLowerBound(), other.getLowerBound()).negate() + ); + } + + /** + * 若{@code other}与当前区间相交,则获得该区间与当前区间的交集 + * + * @param other 另一区间 + * @return 代表交集的区间,若无交集则返回{@code null} + */ + public BoundedRange intersection(final BoundedRange other) { + Objects.requireNonNull(other); + if (isDisjoint(other)) { + return null; + } + return new BoundedRange<>( + getMax(getLowerBound(), other.getLowerBound()), + getMin(getUpperBound(), other.getUpperBound()) + ); + } + + /** + * 截取当前区间中大于{@code min}的部分,若{@code min}不在该区间中,则返回当前区间本身 + * + * @param min 最大的左值 + * @return 区间 + */ + public BoundedRange subGreatThan(final T min) { + return Opt.ofNullable(min) + .filter(this) + .map(t -> new BoundedRange<>(Bound.greaterThan(t), getUpperBound())) + .orElse(this); + } + + /** + * 截取当前区间中大于等于{@code min}的部分,若{@code min}不在该区间中,则返回当前区间本身 + * + * @param min 最大的左值 + * @return 区间 + */ + public BoundedRange subAtLeast(final T min) { + return Opt.ofNullable(min) + .filter(this) + .map(t -> new BoundedRange<>(Bound.atLeast(t), getUpperBound())) + .orElse(this); + } + + /** + * 截取当前区间中小于{@code max}的部分,若{@code max}不在该区间中,则返回当前区间本身 + * + * @param max 最大的左值 + * @return 区间 + */ + public BoundedRange subLessThan(final T max) { + return Opt.ofNullable(max) + .filter(this) + .map(t -> new BoundedRange<>(getLowerBound(), Bound.lessThan(max))) + .orElse(this); + } + + /** + * 截取当前区间中小于等于{@code max}的部分,若{@code max}不在该区间中,则返回当前区间本身 + * + * @param max 最大的左值 + * @return 区间 + */ + public BoundedRange subAtMost(final T max) { + return Opt.ofNullable(max) + .filter(this) + .map(t -> new BoundedRange<>(getLowerBound(), Bound.atMost(max))) + .orElse(this); + } + + // endregion + + private static > Bound getMin(Bound b1, Bound b2) { + return b1.compareTo(b2) <= 0 ? b1 : b2; + } + + private static > Bound getMax(Bound b1, Bound b2) { + return b1.compareTo(b2) >= 0 ? b1 : b2; + } + + private static > BoundedRange checkEmpty(BoundedRange range) { + Assert.isFalse(range.isEmpty(), "{} is a empty range", range); + return range; + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/range/BoundTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/range/BoundTest.java new file mode 100644 index 000000000..a14f05ba2 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/range/BoundTest.java @@ -0,0 +1,211 @@ +package cn.hutool.core.lang.range; + +import org.junit.Assert; +import org.junit.Test; + +/** + * test for {@link Bound} + */ +public class BoundTest { + + @Test + public void testEquals() { + Bound bound = new Bound.FiniteBound<>(1, BoundType.OPEN_UPPER_BOUND); + Assert.assertEquals(bound, bound); + Assert.assertEquals(bound, new Bound.FiniteBound<>(1, BoundType.OPEN_UPPER_BOUND)); + Assert.assertNotEquals(bound, new Bound.FiniteBound<>(2, BoundType.OPEN_UPPER_BOUND)); + Assert.assertNotEquals(bound, new Bound.FiniteBound<>(1, BoundType.OPEN_LOWER_BOUND)); + Assert.assertNotEquals(bound, null); + } + + @Test + public void testHashCode() { + int hashCode = new Bound.FiniteBound<>(1, BoundType.OPEN_UPPER_BOUND).hashCode(); + Assert.assertEquals(hashCode, new Bound.FiniteBound<>(1, BoundType.OPEN_UPPER_BOUND).hashCode()); + Assert.assertNotEquals(hashCode, new Bound.FiniteBound<>(2, BoundType.OPEN_UPPER_BOUND).hashCode()); + Assert.assertNotEquals(hashCode, new Bound.FiniteBound<>(1, BoundType.OPEN_LOWER_BOUND).hashCode()); + } + + @Test + public void testNoneLowerBound() { + Bound bound = Bound.noneLowerBound(); + // negate + Assert.assertEquals(bound, bound.negate()); + // test + Assert.assertTrue(bound.test(Integer.MAX_VALUE)); + // getType + Assert.assertEquals(BoundType.OPEN_LOWER_BOUND, bound.getType()); + // getValue + Assert.assertNull(bound.getValue()); + // toString + Assert.assertEquals("(" + "-\u221e", bound.descBound()); + // compareTo + Assert.assertEquals(0, bound.compareTo(bound)); + Assert.assertEquals(-1, bound.compareTo(Bound.atMost(1))); + + Assert.assertEquals(BoundedRange.all(), bound.toRange()); + Assert.assertEquals("{x | x > -\u221e}", bound.toString()); + } + + @Test + public void testNoneUpperBound() { + Bound bound = Bound.noneUpperBound(); + // negate + Assert.assertEquals(bound, bound.negate()); + // test + Assert.assertTrue(bound.test(Integer.MAX_VALUE)); + // getType + Assert.assertEquals(BoundType.OPEN_UPPER_BOUND, bound.getType()); + // getValue + Assert.assertNull(bound.getValue()); + // toString + Assert.assertEquals("+\u221e" + ")", bound.descBound()); + // compareTo + Assert.assertEquals(0, bound.compareTo(bound)); + Assert.assertEquals(1, bound.compareTo(Bound.atMost(1))); + + Assert.assertEquals(BoundedRange.all(), bound.toRange()); + Assert.assertEquals("{x | x < +\u221e}", bound.toString()); + } + + @Test + public void testGreatThan() { + // { x | x > 0} + Bound bound = Bound.greaterThan(0); + + // test + Assert.assertTrue(bound.test(1)); + Assert.assertFalse(bound.test(0)); + Assert.assertFalse(bound.test(-1)); + // getType + Assert.assertEquals(BoundType.OPEN_LOWER_BOUND, bound.getType()); + // getValue + Assert.assertEquals((Integer)0, bound.getValue()); + // toString + Assert.assertEquals("(0", bound.descBound()); + Assert.assertEquals("{x | x > 0}", bound.toString()); + + // compareTo + Assert.assertEquals(0, bound.compareTo(bound)); + Assert.assertEquals(-1, bound.compareTo(Bound.noneUpperBound())); + Assert.assertEquals(1, bound.compareTo(Bound.atLeast(-1))); + Assert.assertEquals(-1, bound.compareTo(Bound.atLeast(2))); + Assert.assertEquals(1, bound.compareTo(Bound.lessThan(0))); + Assert.assertEquals(1, bound.compareTo(Bound.atMost(0))); + Assert.assertEquals(-1, bound.compareTo(Bound.atMost(2))); + Assert.assertEquals(1, bound.compareTo(Bound.noneLowerBound())); + + // { x | x >= 0} + bound = bound.negate(); + Assert.assertEquals((Integer)0, bound.getValue()); + Assert.assertEquals(BoundType.CLOSE_UPPER_BOUND, bound.getType()); + + Assert.assertNotNull(bound.toRange()); + } + + @Test + public void testAtLeast() { + // { x | x >= 0} + Bound bound = Bound.atLeast(0); + + // test + Assert.assertTrue(bound.test(1)); + Assert.assertTrue(bound.test(0)); + Assert.assertFalse(bound.test(-1)); + // getType + Assert.assertEquals(BoundType.CLOSE_LOWER_BOUND, bound.getType()); + // getValue + Assert.assertEquals((Integer)0, bound.getValue()); + // toString + Assert.assertEquals("[0", bound.descBound()); + Assert.assertEquals("{x | x >= 0}", bound.toString()); + + // compareTo + Assert.assertEquals(0, bound.compareTo(bound)); + Assert.assertEquals(-1, bound.compareTo(Bound.noneUpperBound())); + Assert.assertEquals(1, bound.compareTo(Bound.greaterThan(-1))); + Assert.assertEquals(-1, bound.compareTo(Bound.greaterThan(0))); + Assert.assertEquals(1, bound.compareTo(Bound.lessThan(0))); + Assert.assertEquals(1, bound.compareTo(Bound.atMost(0))); + Assert.assertEquals(-1, bound.compareTo(Bound.atMost(2))); + Assert.assertEquals(1, bound.compareTo(Bound.noneLowerBound())); + + // { x | x < 0} + bound = bound.negate(); + Assert.assertEquals((Integer)0, bound.getValue()); + Assert.assertEquals(BoundType.OPEN_UPPER_BOUND, bound.getType()); + + Assert.assertNotNull(bound.toRange()); + } + + @Test + public void testLessThan() { + // { x | x < 0} + Bound bound = Bound.lessThan(0); + + // test + Assert.assertFalse(bound.test(1)); + Assert.assertFalse(bound.test(0)); + Assert.assertTrue(bound.test(-1)); + // getType + Assert.assertEquals(BoundType.OPEN_UPPER_BOUND, bound.getType()); + // getValue + Assert.assertEquals((Integer)0, bound.getValue()); + // toString + Assert.assertEquals("0)", bound.descBound()); + Assert.assertEquals("{x | x < 0}", bound.toString()); + + // compareTo + Assert.assertEquals(0, bound.compareTo(bound)); + Assert.assertEquals(-1, bound.compareTo(Bound.noneUpperBound())); + Assert.assertEquals(1, bound.compareTo(Bound.greaterThan(-1))); + Assert.assertEquals(-1, bound.compareTo(Bound.greaterThan(0))); + Assert.assertEquals(1, bound.compareTo(Bound.lessThan(-1))); + Assert.assertEquals(-1, bound.compareTo(Bound.atMost(0))); + Assert.assertEquals(1, bound.compareTo(Bound.atMost(-1))); + Assert.assertEquals(1, bound.compareTo(Bound.noneLowerBound())); + + // { x | x >= 0} + bound = bound.negate(); + Assert.assertEquals((Integer)0, bound.getValue()); + Assert.assertEquals(BoundType.CLOSE_LOWER_BOUND, bound.getType()); + + Assert.assertNotNull(bound.toRange()); + } + + @Test + public void testAtMost() { + // { x | x <= 0} + Bound bound = Bound.atMost(0); + + // test + Assert.assertFalse(bound.test(1)); + Assert.assertTrue(bound.test(0)); + Assert.assertTrue(bound.test(-1)); + // getType + Assert.assertEquals(BoundType.CLOSE_UPPER_BOUND, bound.getType()); + // getValue + Assert.assertEquals((Integer)0, bound.getValue()); + // toString + Assert.assertEquals("0]", bound.descBound()); + Assert.assertEquals("{x | x <= 0}", bound.toString()); + + // compareTo + Assert.assertEquals(0, bound.compareTo(bound)); + Assert.assertEquals(-1, bound.compareTo(Bound.noneUpperBound())); + Assert.assertEquals(1, bound.compareTo(Bound.greaterThan(-1))); + Assert.assertEquals(-1, bound.compareTo(Bound.greaterThan(0))); + Assert.assertEquals(1, bound.compareTo(Bound.atMost(-1))); + Assert.assertEquals(1, bound.compareTo(Bound.lessThan(0))); + Assert.assertEquals(1, bound.compareTo(Bound.lessThan(-1))); + Assert.assertEquals(1, bound.compareTo(Bound.noneLowerBound())); + + // { x | x > 0} + bound = bound.negate(); + Assert.assertEquals((Integer)0, bound.getValue()); + Assert.assertEquals(BoundType.OPEN_LOWER_BOUND, bound.getType()); + + Assert.assertNotNull(bound.toRange()); + } + +} diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/range/BoundTypeTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/range/BoundTypeTest.java new file mode 100644 index 000000000..6fc061ae7 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/range/BoundTypeTest.java @@ -0,0 +1,83 @@ +package cn.hutool.core.lang.range; + +import org.junit.Assert; +import org.junit.Test; + +/** + * test for {@link BoundType} + */ +public class BoundTypeTest { + + @Test + public void testIsDislocated() { + Assert.assertTrue(BoundType.CLOSE_LOWER_BOUND.isDislocated(BoundType.CLOSE_UPPER_BOUND)); + Assert.assertTrue(BoundType.CLOSE_LOWER_BOUND.isDislocated(BoundType.OPEN_UPPER_BOUND)); + Assert.assertFalse(BoundType.CLOSE_LOWER_BOUND.isDislocated(BoundType.CLOSE_LOWER_BOUND)); + Assert.assertFalse(BoundType.CLOSE_LOWER_BOUND.isDislocated(BoundType.OPEN_LOWER_BOUND)); + } + + @Test + public void testIsLowerBound() { + Assert.assertFalse(BoundType.CLOSE_UPPER_BOUND.isLowerBound()); + Assert.assertFalse(BoundType.OPEN_UPPER_BOUND.isLowerBound()); + Assert.assertTrue(BoundType.CLOSE_LOWER_BOUND.isLowerBound()); + Assert.assertTrue(BoundType.OPEN_LOWER_BOUND.isLowerBound()); + } + + @Test + public void testIsUpperBound() { + Assert.assertTrue(BoundType.CLOSE_UPPER_BOUND.isUpperBound()); + Assert.assertTrue(BoundType.OPEN_UPPER_BOUND.isUpperBound()); + Assert.assertFalse(BoundType.CLOSE_LOWER_BOUND.isUpperBound()); + Assert.assertFalse(BoundType.OPEN_LOWER_BOUND.isUpperBound()); + } + + @Test + public void testIsOpen() { + Assert.assertFalse(BoundType.CLOSE_UPPER_BOUND.isOpen()); + Assert.assertTrue(BoundType.OPEN_UPPER_BOUND.isOpen()); + Assert.assertFalse(BoundType.CLOSE_LOWER_BOUND.isOpen()); + Assert.assertTrue(BoundType.OPEN_LOWER_BOUND.isOpen()); + } + + @Test + public void testIsClose() { + Assert.assertTrue(BoundType.CLOSE_UPPER_BOUND.isClose()); + Assert.assertFalse(BoundType.OPEN_UPPER_BOUND.isClose()); + Assert.assertTrue(BoundType.CLOSE_LOWER_BOUND.isClose()); + Assert.assertFalse(BoundType.OPEN_LOWER_BOUND.isClose()); + } + + @Test + public void testNegate() { + Assert.assertEquals(BoundType.CLOSE_UPPER_BOUND, BoundType.OPEN_LOWER_BOUND.negate()); + Assert.assertEquals(BoundType.OPEN_UPPER_BOUND, BoundType.CLOSE_LOWER_BOUND.negate()); + Assert.assertEquals(BoundType.OPEN_LOWER_BOUND, BoundType.CLOSE_UPPER_BOUND.negate()); + Assert.assertEquals(BoundType.CLOSE_LOWER_BOUND, BoundType.OPEN_UPPER_BOUND.negate()); + } + + @Test + public void testGetSymbol() { + Assert.assertEquals("]", BoundType.CLOSE_UPPER_BOUND.getSymbol()); + Assert.assertEquals(")", BoundType.OPEN_UPPER_BOUND.getSymbol()); + Assert.assertEquals("[", BoundType.CLOSE_LOWER_BOUND.getSymbol()); + Assert.assertEquals("(", BoundType.OPEN_LOWER_BOUND.getSymbol()); + } + + @Test + public void testGetCode() { + Assert.assertEquals(2, BoundType.CLOSE_UPPER_BOUND.getCode()); + Assert.assertEquals(1, BoundType.OPEN_UPPER_BOUND.getCode()); + Assert.assertEquals(-1, BoundType.OPEN_LOWER_BOUND.getCode()); + Assert.assertEquals(-2, BoundType.CLOSE_LOWER_BOUND.getCode()); + } + + @Test + public void testGetOperator() { + Assert.assertEquals("<=", BoundType.CLOSE_UPPER_BOUND.getOperator()); + Assert.assertEquals("<", BoundType.OPEN_UPPER_BOUND.getOperator()); + Assert.assertEquals(">", BoundType.OPEN_LOWER_BOUND.getOperator()); + Assert.assertEquals(">=", BoundType.CLOSE_LOWER_BOUND.getOperator()); + } + +} diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/range/BoundedRangeTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/range/BoundedRangeTest.java new file mode 100644 index 000000000..f99dec051 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/range/BoundedRangeTest.java @@ -0,0 +1,283 @@ +package cn.hutool.core.lang.range; + +import org.junit.Assert; +import org.junit.Test; + +/** + * test for {@link BoundedRange} + */ +public class BoundedRangeTest { + + @Test + public void testEquals() { + BoundedRange range = new BoundedRange<>( + Bound.greaterThan(0), Bound.lessThan(10) + ); + Assert.assertEquals(range, range); + Assert.assertNotEquals(range, null); + Assert.assertEquals(range, new BoundedRange<>( + Bound.greaterThan(0), Bound.lessThan(10) + )); + Assert.assertNotEquals(range, new BoundedRange<>( + Bound.greaterThan(1), Bound.lessThan(10) + )); + } + + @Test + public void testHashCode() { + int hasCode = new BoundedRange<>( + Bound.greaterThan(0), Bound.lessThan(10) + ).hashCode(); + Assert.assertEquals(hasCode, new BoundedRange<>( + Bound.greaterThan(0), Bound.lessThan(10) + ).hashCode()); + Assert.assertNotEquals(hasCode, new BoundedRange<>( + Bound.greaterThan(1), Bound.lessThan(10) + ).hashCode()); + } + + @Test + public void testAll() { + BoundedRange range = BoundedRange.all(); + Assert.assertEquals("(-∞, +∞)", range.toString()); + + // getBound + Assert.assertFalse(range.hasLowerBound()); + Assert.assertFalse(range.hasUpperBound()); + Assert.assertNull(range.getLowerBoundValue()); + Assert.assertNull(range.getUpperBoundValue()); + + // test + Assert.assertFalse(range.isEmpty()); + Assert.assertTrue(range.test(Integer.MAX_VALUE)); + Assert.assertTrue(range.test(Integer.MIN_VALUE)); + + // isXXX + Assert.assertFalse(range.isDisjoint(BoundedRange.open(0, 5))); + Assert.assertTrue(range.isEquals(range)); + Assert.assertFalse(range.isEquals(BoundedRange.open(0, 5))); + Assert.assertTrue(range.isIntersected(BoundedRange.open(0, 5))); + Assert.assertTrue(range.isIntersected(range)); + Assert.assertFalse(range.isSubset(BoundedRange.open(0, 5))); + Assert.assertTrue(range.isSubset(range)); + Assert.assertFalse(range.isProperSubset(BoundedRange.open(0, 5))); + Assert.assertFalse(range.isProperSubset(range)); + Assert.assertTrue(range.isSuperset(BoundedRange.open(0, 5))); + Assert.assertTrue(range.isSuperset(range)); + Assert.assertTrue(range.isProperSuperset(BoundedRange.open(0, 5))); + Assert.assertFalse(range.isProperSuperset(range)); + + // operate + Assert.assertEquals(range, range.unionIfIntersected(BoundedRange.open(0, 5))); + Assert.assertEquals(range, range.span(BoundedRange.open(0, 5))); + Assert.assertNull(range.gap(BoundedRange.open(0, 5))); + Assert.assertEquals(range, range.intersection(range)); + Assert.assertEquals(BoundedRange.open(0, 5), range.intersection(BoundedRange.open(0, 5))); + + // sub + Assert.assertEquals("(0, +∞)", range.subGreatThan(0).toString()); + Assert.assertEquals("[0, +∞)", range.subAtLeast(0).toString()); + Assert.assertEquals("(-∞, 0)", range.subLessThan(0).toString()); + Assert.assertEquals("(-∞, 0]", range.subAtMost(0).toString()); + } + + @Test + public void testOpen() { + BoundedRange range = BoundedRange.open(0, 5); + Assert.assertEquals("(0, 5)", range.toString()); + + // getBound + Assert.assertTrue(range.hasLowerBound()); + Assert.assertTrue(range.hasUpperBound()); + Assert.assertEquals((Integer)0, range.getLowerBoundValue()); + Assert.assertEquals((Integer)5, range.getUpperBoundValue()); + + // test + Assert.assertFalse(range.isEmpty()); + Assert.assertFalse(range.test(6)); + Assert.assertFalse(range.test(5)); + Assert.assertTrue(range.test(3)); + Assert.assertFalse(range.test(0)); + Assert.assertFalse(range.test(-1)); + + // isXXX + Assert.assertTrue(range.isEquals(range)); + Assert.assertTrue(range.isDisjoint(BoundedRange.open(-5, 0))); // (-5, 0) + Assert.assertTrue(range.isDisjoint(BoundedRange.close(-5, 0))); // [-5, 0] + Assert.assertTrue(range.isIntersected(BoundedRange.close(-5, 1))); // [-5, 1] + Assert.assertTrue(range.isSubset(BoundedRange.close(0, 5))); // [0, 5] + Assert.assertTrue(range.isProperSubset(BoundedRange.close(0, 5))); // [0, 5] + Assert.assertFalse(range.isSuperset(BoundedRange.close(0, 5))); // [0, 5] + Assert.assertFalse(range.isProperSuperset(BoundedRange.close(0, 5))); // [0, 5] + + // operate + Assert.assertEquals("(0, 10]", range.unionIfIntersected(BoundedRange.close(4, 10)).toString()); + Assert.assertEquals("(0, 10)", range.span(BoundedRange.open(9, 10)).toString()); + Assert.assertEquals("(-2, 0]", range.gap(BoundedRange.close(-10, -2)).toString()); + Assert.assertEquals("(3, 5)", range.intersection(BoundedRange.open(3, 10)).toString()); + + // sub + Assert.assertEquals("(3, 5)", range.subGreatThan(3).toString()); + Assert.assertEquals("[3, 5)", range.subAtLeast(3).toString()); + Assert.assertEquals("(0, 3)", range.subLessThan(3).toString()); + Assert.assertEquals("(0, 3]", range.subAtMost(3).toString()); + } + + @Test + public void testClose() { + BoundedRange range = BoundedRange.close(0, 5); + Assert.assertEquals("[0, 5]", range.toString()); + + // getBound + Assert.assertTrue(range.hasLowerBound()); + Assert.assertTrue(range.hasUpperBound()); + Assert.assertEquals((Integer)0, range.getLowerBoundValue()); + Assert.assertEquals((Integer)5, range.getUpperBoundValue()); + + // test + Assert.assertFalse(range.isEmpty()); + Assert.assertFalse(range.test(6)); + Assert.assertTrue(range.test(5)); + Assert.assertTrue(range.test(0)); + Assert.assertFalse(range.test(-1)); + + // isXXX + Assert.assertTrue(range.isEquals(range)); + Assert.assertTrue(range.isDisjoint(BoundedRange.open(-5, 0))); // (-5, 0) + Assert.assertTrue(range.isDisjoint(BoundedRange.close(-5, 0))); // [-5, 0] + Assert.assertTrue(range.isIntersected(BoundedRange.open(-5, 1))); // [-5, 1] + Assert.assertFalse(range.isSubset(BoundedRange.open(0, 5))); // (0, 5) + Assert.assertFalse(range.isProperSubset(BoundedRange.open(0, 5))); // (0, 5) + Assert.assertTrue(range.isSuperset(BoundedRange.open(0, 5))); // (0, 5) + Assert.assertTrue(range.isProperSuperset(BoundedRange.open(0, 5))); // (0, 5) + + // operate + Assert.assertEquals("[0, 5]", range.unionIfIntersected(BoundedRange.open(5, 10)).toString()); + Assert.assertEquals("[0, 10]", range.unionIfIntersected(BoundedRange.close(4, 10)).toString()); + Assert.assertEquals("[0, 10)", range.span(BoundedRange.open(9, 10)).toString()); + Assert.assertEquals("(-2, 0)", range.gap(BoundedRange.close(-10, -2)).toString()); + Assert.assertEquals("(3, 5]", range.intersection(BoundedRange.open(3, 10)).toString()); + Assert.assertNull(range.intersection(BoundedRange.open(5, 10))); + + // sub + Assert.assertEquals("(3, 5]", range.subGreatThan(3).toString()); + Assert.assertEquals("[3, 5]", range.subAtLeast(3).toString()); + Assert.assertEquals("[0, 3)", range.subLessThan(3).toString()); + Assert.assertEquals("[0, 3]", range.subAtMost(3).toString()); + } + + @Test + public void testOpenClose() { + BoundedRange range = BoundedRange.openClose(0, 5); + Assert.assertEquals("(0, 5]", range.toString()); + + // getBound + Assert.assertTrue(range.hasLowerBound()); + Assert.assertTrue(range.hasUpperBound()); + Assert.assertEquals((Integer)0, range.getLowerBoundValue()); + Assert.assertEquals((Integer)5, range.getUpperBoundValue()); + + // test + Assert.assertFalse(range.isEmpty()); + Assert.assertFalse(range.test(6)); + Assert.assertTrue(range.test(5)); + Assert.assertFalse(range.test(0)); + Assert.assertFalse(range.test(-1)); + } + + @Test + public void testCloseOpen() { + BoundedRange range = BoundedRange.closeOpen(0, 5); + Assert.assertEquals("[0, 5)", range.toString()); + + // getBound + Assert.assertTrue(range.hasLowerBound()); + Assert.assertTrue(range.hasUpperBound()); + Assert.assertEquals((Integer)0, range.getLowerBoundValue()); + Assert.assertEquals((Integer)5, range.getUpperBoundValue()); + + // test + Assert.assertFalse(range.isEmpty()); + Assert.assertFalse(range.test(6)); + Assert.assertFalse(range.test(5)); + Assert.assertTrue(range.test(0)); + Assert.assertFalse(range.test(-1)); + } + + @Test + public void testGreatThan() { + BoundedRange range = BoundedRange.greaterThan(0); + Assert.assertEquals("(0, +∞)", range.toString()); + + // getBound + Assert.assertTrue(range.hasLowerBound()); + Assert.assertFalse(range.hasUpperBound()); + Assert.assertEquals((Integer)0, range.getLowerBoundValue()); + Assert.assertNull(range.getUpperBoundValue()); + + // test + Assert.assertFalse(range.isEmpty()); + Assert.assertTrue(range.test(6)); + Assert.assertTrue(range.test(5)); + Assert.assertFalse(range.test(0)); + Assert.assertFalse(range.test(-1)); + } + + @Test + public void testAtLeast() { + BoundedRange range = BoundedRange.atLeast(0); + Assert.assertEquals("[0, +∞)", range.toString()); + + // getBound + Assert.assertTrue(range.hasLowerBound()); + Assert.assertFalse(range.hasUpperBound()); + Assert.assertEquals((Integer)0, range.getLowerBoundValue()); + Assert.assertNull(range.getUpperBoundValue()); + + // test + Assert.assertFalse(range.isEmpty()); + Assert.assertTrue(range.test(6)); + Assert.assertTrue(range.test(5)); + Assert.assertTrue(range.test(0)); + Assert.assertFalse(range.test(-1)); + } + + @Test + public void testLessThan() { + BoundedRange range = BoundedRange.lessThan(5); + Assert.assertEquals("(-∞, 5)", range.toString()); + + // getBound + Assert.assertTrue(range.hasUpperBound()); + Assert.assertFalse(range.hasLowerBound()); + Assert.assertEquals((Integer)5, range.getUpperBoundValue()); + Assert.assertNull(range.getLowerBoundValue()); + + // test + Assert.assertFalse(range.isEmpty()); + Assert.assertFalse(range.test(6)); + Assert.assertFalse(range.test(5)); + Assert.assertTrue(range.test(0)); + Assert.assertTrue(range.test(-1)); + } + + @Test + public void testAtMost() { + BoundedRange range = BoundedRange.atMost(5); + Assert.assertEquals("(-∞, 5]", range.toString()); + + // getBound + Assert.assertTrue(range.hasUpperBound()); + Assert.assertFalse(range.hasLowerBound()); + Assert.assertEquals((Integer)5, range.getUpperBoundValue()); + Assert.assertNull(range.getLowerBoundValue()); + + // test + Assert.assertFalse(range.isEmpty()); + Assert.assertFalse(range.test(6)); + Assert.assertTrue(range.test(5)); + Assert.assertTrue(range.test(0)); + Assert.assertTrue(range.test(-1)); + } + +}