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 01/29] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E5=A4=84=E7=90=86=E5=8C=BA=E9=97=B4=E7=9A=84=E5=B7=A5=E5=85=B7?= =?UTF-8?q?=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)); + } + +} From 55f93ceff1573211181c0c7e54094ce419d98a0f Mon Sep 17 00:00:00 2001 From: Looly Date: Sat, 8 Oct 2022 16:53:27 +0800 Subject: [PATCH 02/29] add blank char --- hutool-core/src/main/java/cn/hutool/core/util/CharUtil.java | 4 +++- .../src/test/java/cn/hutool/core/util/CharUtilTest.java | 6 ++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/util/CharUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/CharUtil.java index eabcd45f9..7663300f4 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/CharUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/CharUtil.java @@ -259,7 +259,9 @@ public class CharUtil implements CharPool { || Character.isSpaceChar(c) || c == '\ufeff' || c == '\u202a' - || c == '\u0000'; + || c == '\u0000' + // issue#I5UGSQ,Hangul Filler + || c == '\u3164'; } /** diff --git a/hutool-core/src/test/java/cn/hutool/core/util/CharUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/CharUtilTest.java index d6bf9a0ab..1fb388a69 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/CharUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/CharUtilTest.java @@ -55,4 +55,10 @@ public class CharUtilTest { Assert.assertEquals('⑫', CharUtil.toCloseByNumber(12)); Assert.assertEquals('⑳', CharUtil.toCloseByNumber(20)); } + + @Test + public void issueI5UGSQTest(){ + final Character c = '\u3164'; + Assert.assertTrue(CharUtil.isBlankChar(c)); + } } From 3835e6298fdeb50a05a3dfdeddca6a87390c0892 Mon Sep 17 00:00:00 2001 From: Looly Date: Sat, 8 Oct 2022 18:21:22 +0800 Subject: [PATCH 03/29] fix code --- .../hutool/core/comparator/CompareUtil.java | 28 ++- .../java/cn/hutool/core/date/DateRange.java | 2 +- .../java/cn/hutool/core/lang/range/Bound.java | 236 +----------------- .../cn/hutool/core/lang/range/BoundType.java | 7 +- .../hutool/core/lang/range/BoundedRange.java | 121 +++++---- .../core/lang/range/NoneLowerBound.java | 108 ++++++++ .../core/lang/range/NoneUpperBound.java | 106 ++++++++ .../hutool/core/lang/{ => range}/Range.java | 3 +- .../hutool/core/lang/range/package-info.java | 10 + .../java/cn/hutool/core/util/ArrayUtil.java | 6 +- .../java/cn/hutool/core/lang/RangeTest.java | 1 + 11 files changed, 334 insertions(+), 294 deletions(-) create mode 100644 hutool-core/src/main/java/cn/hutool/core/lang/range/NoneLowerBound.java create mode 100644 hutool-core/src/main/java/cn/hutool/core/lang/range/NoneUpperBound.java rename hutool-core/src/main/java/cn/hutool/core/lang/{ => range}/Range.java (98%) create mode 100644 hutool-core/src/main/java/cn/hutool/core/lang/range/package-info.java diff --git a/hutool-core/src/main/java/cn/hutool/core/comparator/CompareUtil.java b/hutool-core/src/main/java/cn/hutool/core/comparator/CompareUtil.java index bbd723284..ce85293c4 100644 --- a/hutool-core/src/main/java/cn/hutool/core/comparator/CompareUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/comparator/CompareUtil.java @@ -136,7 +136,7 @@ public class CompareUtil { *
  • {@code Comparator.nullsFirst(CompareUtil.reverse())}
  • * * - * @param 排序节点类型 + * @param 排序节点类型 * @param comparator 排序器 * @return 默认排序器 * @since 6.0.0 @@ -326,4 +326,30 @@ public class CompareUtil { final IndexedComparator indexedComparator = new IndexedComparator<>(atEndIfMiss, objs); return (o1, o2) -> indexedComparator.compare(keyExtractor.apply(o1), keyExtractor.apply(o2)); } + + /** + * 取两个值中的最小值,大小相同返回第一个值 + * + * @param 值类型 + * @param t1 第一个值 + * @param t2 第二个值 + * @return 最小值 + * @since 6.0.0 + */ + public static > T min(final T t1, final T t2) { + return compare(t1, t2) <= 0 ? t1 : t2; + } + + /** + * 取两个值中的最大值,大小相同返回第一个值 + * + * @param 值类型 + * @param t1 第一个值 + * @param t2 第二个值 + * @return 最大值 + * @since 6.0.0 + */ + public static > T max(final T t1, final T t2) { + return compare(t1, t2) >= 0 ? t1 : t2; + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/date/DateRange.java b/hutool-core/src/main/java/cn/hutool/core/date/DateRange.java index 26c04f8e1..2241d7091 100644 --- a/hutool-core/src/main/java/cn/hutool/core/date/DateRange.java +++ b/hutool-core/src/main/java/cn/hutool/core/date/DateRange.java @@ -1,6 +1,6 @@ package cn.hutool.core.date; -import cn.hutool.core.lang.Range; +import cn.hutool.core.lang.range.Range; import java.util.Date; 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 index 105d51f13..c26a559b7 100644 --- 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 @@ -8,7 +8,7 @@ import java.util.function.Predicate; /** *

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

    边界的类型

    + *

    边界的类型

    *

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

      *
    • {@link #noneLowerBound()}:{@code {x | x > -∞}};
    • @@ -20,7 +20,7 @@ import java.util.function.Predicate; *
    * 当作为{@link Predicate}使用时,可用于判断入参对象是否能满足当前实例所对应的不等式。 * - *

    边界的比较

    + *

    边界的比较

    *

    边界对象本身实现了{@link Comparable}接口, * 当使用{@link Comparable#compareTo}比较两个边界对象时, * 返回的比较值表示两个边界对象对应的点在实数轴上从左到右的先后顺序。
    @@ -41,27 +41,16 @@ import java.util.function.Predicate; 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(); - + // region --------------- static methods /** * {@code {x | x > -∞}} * @@ -70,7 +59,7 @@ public interface Bound> extends Predicate, Co */ @SuppressWarnings("unchecked") static > Bound noneLowerBound() { - return NONE_LOWER_BOUND; + return NoneLowerBound.INSTANCE; } /** @@ -81,7 +70,7 @@ public interface Bound> extends Predicate, Co */ @SuppressWarnings("unchecked") static > Bound noneUpperBound() { - return NONE_UPPER_BOUND; + return NoneUpperBound.INSTANCE; } /** @@ -127,6 +116,7 @@ public interface Bound> extends Predicate, Co static > Bound atMost(final T max) { return new FiniteBound<>(Objects.requireNonNull(max), BoundType.CLOSE_UPPER_BOUND); } + // endregion --------------- static methods /** * 获取边界值 @@ -148,6 +138,7 @@ public interface Bound> extends Predicate, Co * @param t 要检验的值,不允许为{@code null} * @return 是否 */ + @SuppressWarnings("AbstractMethodOverridesAbstractMethod") @Override boolean test(T t); @@ -163,6 +154,7 @@ public interface Bound> extends Predicate, Co * @param bound 边界 * @return 位置 */ + @SuppressWarnings("AbstractMethodOverridesAbstractMethod") @Override int compareTo(final Bound bound); @@ -277,11 +269,11 @@ public interface Bound> extends Predicate, Co @Override public int compareTo(final Bound bound) { // 另一边界为无限小的左边界,则当前边界必然靠后 - if (bound instanceof Bound.NoneLowerBound) { + if (bound instanceof NoneLowerBound) { return 1; } // 另一边界为无限大的右边界,则当前边界必然靠前 - if (bound instanceof Bound.NoneUpperBound) { + if (bound instanceof NoneUpperBound) { return -1; } // 两值不相等,直接比较边界值 @@ -307,8 +299,7 @@ public interface Bound> extends Predicate, Co return bt1.isLowerBound() ? 1 : -1; } // 都为左边界,则封闭边界在前,若都为右边界,则封闭边界在后 - return bt1.isLowerBound() ? - Integer.compare(bt1.getCode(), bt2.getCode()) : Integer.compare(bt1.getCode(), bt2.getCode()); + return Integer.compare(bt1.getCode(), bt2.getCode()); } /** @@ -383,205 +374,4 @@ public interface Bound> extends Predicate, Co ); } } - - /** - * 无限小的左边界 - * - * @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 index 55e43071f..ce22ec4af 100644 --- 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 @@ -1,7 +1,7 @@ package cn.hutool.core.lang.range; /** - * 边界类型 + * 边界类型枚举 * * @author huangchengxing * @since 6.0.0 @@ -45,6 +45,9 @@ public enum BoundType { /** * 构造 + * @param symbol 符号,如`[`或`(`等 + * @param operator 运算符,如`<`等 + * @param code 是否为开区间 */ BoundType(final String symbol, final String operator, final int code) { this.symbol = symbol; @@ -85,7 +88,7 @@ public enum BoundType { * @param boundType 另一边界类型 * @return 是否 */ - public boolean isDislocated(BoundType boundType) { + public boolean isDislocated(final BoundType boundType) { return code * boundType.code < 0; } 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 index bed3e30c4..5992c7754 100644 --- 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 @@ -1,5 +1,6 @@ package cn.hutool.core.lang.range; +import cn.hutool.core.comparator.CompareUtil; import cn.hutool.core.lang.Assert; import cn.hutool.core.lang.Opt; @@ -10,8 +11,8 @@ 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} @@ -24,7 +25,7 @@ import java.util.function.Predicate; *
    {@code (-∞, +∞)}{@code {x}} {@link #all} *
    * - *

    空区间

    + *

    空区间

    *

    根据数学定义,当区间中无任何实数时,认为该区间代表的集合为空集, * 用户可通过{@link #isEmpty}确认当前实例是否为空区间。
    * 若实例上界a,下界为b,则当实例满足下述任意条件时,认为其为一个空区间: @@ -49,7 +50,7 @@ public class BoundedRange> implements Predicate< /** * 双向无界的区间 */ - @SuppressWarnings("rawtypes") + @SuppressWarnings({"rawtypes", "unchecked"}) private static final BoundedRange ALL = new BoundedRange(Bound.noneLowerBound(), Bound.noneUpperBound()); /** @@ -80,15 +81,15 @@ public class BoundedRange> implements Predicate< * @param 边界值类型 * @return 区间 * @throws IllegalArgumentException 当创建的区间表示的集合为空时抛出 - * @throws NullPointerException 上界或下界为{@code null}时抛出 + * @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) - ) + new BoundedRange<>( + Bound.atLeast(lowerBound), Bound.atMost(upperBound) + ) ); } @@ -100,13 +101,13 @@ public class BoundedRange> implements Predicate< * @param 边界值类型 * @return 区间 * @throws IllegalArgumentException 当创建的区间表示的集合为空时抛出 - * @throws NullPointerException 上界或下界为{@code null}时抛出 + * @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)) + new BoundedRange<>(Bound.greaterThan(lowerBound), Bound.lessThan(upperBound)) ); } @@ -118,16 +119,16 @@ public class BoundedRange> implements Predicate< * @param 边界值类型 * @return 区间 * @throws IllegalArgumentException 当创建的区间表示的集合为空时抛出 - * @throws NullPointerException 上界或下界为{@code null}时抛出 + * @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) - ) + new BoundedRange<>( + Bound.atLeast(lowerBound), + Bound.lessThan(upperBound) + ) ); } @@ -139,16 +140,16 @@ public class BoundedRange> implements Predicate< * @param 边界值类型 * @return 区间 * @throws IllegalArgumentException 当创建的区间表示的集合为空时抛出 - * @throws NullPointerException 上界或下界为{@code null}时抛出 + * @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) - ) + new BoundedRange<>( + Bound.greaterThan(lowerBound), + Bound.atMost(upperBound) + ) ); } @@ -287,19 +288,19 @@ public class BoundedRange> implements Predicate< * @return 是否 */ public boolean isEmpty() { - Bound low = getLowerBound(); - Bound up = getUpperBound(); - if (low instanceof Bound.NoneLowerBound || up instanceof Bound.NoneUpperBound) { + final Bound low = getLowerBound(); + final Bound up = getUpperBound(); + if (low instanceof NoneLowerBound || up instanceof NoneUpperBound) { return false; } - int compareValue = low.getValue().compareTo(up.getValue()); + final int compareValue = low.getValue().compareTo(up.getValue()); if (compareValue < 0) { return false; } // 上界小于下界时为空 return compareValue > 0 - // 上下界的边界值相等,且不为退化区间是为空 - || !(low.getType().isClose() && up.getType().isClose()); + // 上下界的边界值相等,且不为退化区间是为空 + || !(low.getType().isClose() && up.getType().isClose()); } /** @@ -326,7 +327,7 @@ public class BoundedRange> implements Predicate< if (o == null || getClass() != o.getClass()) { return false; } - final BoundedRange that = (BoundedRange)o; + final BoundedRange that = (BoundedRange) o; return lowerBound.equals(that.lowerBound) && upperBound.equals(that.upperBound); } @@ -352,7 +353,7 @@ public class BoundedRange> implements Predicate< */ public boolean isSuperset(final BoundedRange other) { return getLowerBound().compareTo(other.getLowerBound()) <= 0 - && getUpperBound().compareTo(other.getUpperBound()) >= 0; + && getUpperBound().compareTo(other.getUpperBound()) >= 0; } /** @@ -363,7 +364,7 @@ public class BoundedRange> implements Predicate< */ public boolean isProperSuperset(final BoundedRange other) { return getLowerBound().compareTo(other.getLowerBound()) < 0 - && getUpperBound().compareTo(other.getUpperBound()) > 0; + && getUpperBound().compareTo(other.getUpperBound()) > 0; } /** @@ -374,7 +375,7 @@ public class BoundedRange> implements Predicate< */ public boolean isSubset(final BoundedRange other) { return getLowerBound().compareTo(other.getLowerBound()) >= 0 - && getUpperBound().compareTo(other.getUpperBound()) <= 0; + && getUpperBound().compareTo(other.getUpperBound()) <= 0; } /** @@ -385,7 +386,7 @@ public class BoundedRange> implements Predicate< */ public boolean isProperSubset(final BoundedRange other) { return getLowerBound().compareTo(other.getLowerBound()) > 0 - && getUpperBound().compareTo(other.getUpperBound()) < 0; + && getUpperBound().compareTo(other.getUpperBound()) < 0; } /** @@ -396,7 +397,7 @@ public class BoundedRange> implements Predicate< */ public boolean isDisjoint(final BoundedRange other) { return getLowerBound().compareTo(other.getUpperBound()) > 0 - || getUpperBound().compareTo(other.getLowerBound()) < 0; + || getUpperBound().compareTo(other.getLowerBound()) < 0; } /** @@ -418,7 +419,7 @@ public class BoundedRange> implements Predicate< public boolean isEquals(final BoundedRange other) { Objects.requireNonNull(other); return other.getLowerBound().compareTo(getLowerBound()) == 0 - && other.getUpperBound().compareTo(getUpperBound()) == 0; + && other.getUpperBound().compareTo(getUpperBound()) == 0; } /** @@ -430,8 +431,8 @@ public class BoundedRange> implements Predicate< @Override public boolean test(final T value) { return getLowerBound() - .and(getUpperBound()) - .test(value); + .and(getUpperBound()) + .test(value); } // endregion @@ -450,8 +451,8 @@ public class BoundedRange> implements Predicate< return this; } return new BoundedRange<>( - getMin(getLowerBound(), other.getLowerBound()), - getMax(getUpperBound(), other.getUpperBound()) + CompareUtil.min(getLowerBound(), other.getLowerBound()), + CompareUtil.max(getUpperBound(), other.getUpperBound()) ); } @@ -464,8 +465,8 @@ public class BoundedRange> implements Predicate< public BoundedRange span(final BoundedRange other) { Objects.requireNonNull(other); return new BoundedRange<>( - getMin(getLowerBound(), other.getLowerBound()), - getMax(getUpperBound(), other.getUpperBound()) + CompareUtil.min(getLowerBound(), other.getLowerBound()), + CompareUtil.max(getUpperBound(), other.getUpperBound()) ); } @@ -481,8 +482,8 @@ public class BoundedRange> implements Predicate< return null; } return new BoundedRange<>( - getMin(getUpperBound(), other.getUpperBound()).negate(), - getMax(getLowerBound(), other.getLowerBound()).negate() + CompareUtil.min(getUpperBound(), other.getUpperBound()).negate(), + CompareUtil.max(getLowerBound(), other.getLowerBound()).negate() ); } @@ -498,8 +499,8 @@ public class BoundedRange> implements Predicate< return null; } return new BoundedRange<>( - getMax(getLowerBound(), other.getLowerBound()), - getMin(getUpperBound(), other.getUpperBound()) + CompareUtil.min(getLowerBound(), other.getLowerBound()), + CompareUtil.max(getUpperBound(), other.getUpperBound()) ); } @@ -511,9 +512,9 @@ public class BoundedRange> implements Predicate< */ public BoundedRange subGreatThan(final T min) { return Opt.ofNullable(min) - .filter(this) - .map(t -> new BoundedRange<>(Bound.greaterThan(t), getUpperBound())) - .orElse(this); + .filter(this) + .map(t -> new BoundedRange<>(Bound.greaterThan(t), getUpperBound())) + .orElse(this); } /** @@ -524,9 +525,9 @@ public class BoundedRange> implements Predicate< */ public BoundedRange subAtLeast(final T min) { return Opt.ofNullable(min) - .filter(this) - .map(t -> new BoundedRange<>(Bound.atLeast(t), getUpperBound())) - .orElse(this); + .filter(this) + .map(t -> new BoundedRange<>(Bound.atLeast(t), getUpperBound())) + .orElse(this); } /** @@ -537,9 +538,9 @@ public class BoundedRange> implements Predicate< */ public BoundedRange subLessThan(final T max) { return Opt.ofNullable(max) - .filter(this) - .map(t -> new BoundedRange<>(getLowerBound(), Bound.lessThan(max))) - .orElse(this); + .filter(this) + .map(t -> new BoundedRange<>(getLowerBound(), Bound.lessThan(max))) + .orElse(this); } /** @@ -550,22 +551,14 @@ public class BoundedRange> implements Predicate< */ public BoundedRange subAtMost(final T max) { return Opt.ofNullable(max) - .filter(this) - .map(t -> new BoundedRange<>(getLowerBound(), Bound.atMost(max))) - .orElse(this); + .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) { + private static > BoundedRange checkEmpty(final BoundedRange range) { Assert.isFalse(range.isEmpty(), "{} is a empty range", range); return range; } diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/range/NoneLowerBound.java b/hutool-core/src/main/java/cn/hutool/core/lang/range/NoneLowerBound.java new file mode 100644 index 000000000..b9d69d397 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/lang/range/NoneLowerBound.java @@ -0,0 +1,108 @@ +package cn.hutool.core.lang.range; + +/** + * 无限小的左边界 + * + * @param 边界值类型 + * @author huangchengxing + * @since 6.0.0 + */ +class NoneLowerBound> implements Bound { + /** + * 无限小的左边界单例 + */ + @SuppressWarnings("rawtypes") + static final NoneLowerBound INSTANCE = new NoneLowerBound(); + + 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 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}"; + } + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/range/NoneUpperBound.java b/hutool-core/src/main/java/cn/hutool/core/lang/range/NoneUpperBound.java new file mode 100644 index 000000000..8112dbdd1 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/lang/range/NoneUpperBound.java @@ -0,0 +1,106 @@ +package cn.hutool.core.lang.range; + +/** + * 无限大的右边界 + * + * @param 边界值类型 + */ +class NoneUpperBound> implements Bound { + /** + * 无限大的右边界 + */ + @SuppressWarnings("rawtypes") + static final NoneUpperBound INSTANCE = new NoneUpperBound(); + + 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 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.java b/hutool-core/src/main/java/cn/hutool/core/lang/range/Range.java similarity index 98% rename from hutool-core/src/main/java/cn/hutool/core/lang/Range.java rename to hutool-core/src/main/java/cn/hutool/core/lang/range/Range.java index 99732147f..f53fd91e1 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/Range.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/range/Range.java @@ -1,5 +1,6 @@ -package cn.hutool.core.lang; +package cn.hutool.core.lang.range; +import cn.hutool.core.lang.Assert; import cn.hutool.core.thread.lock.NoLock; import java.io.Serializable; diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/range/package-info.java b/hutool-core/src/main/java/cn/hutool/core/lang/range/package-info.java new file mode 100644 index 000000000..5c75bb8ae --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/lang/range/package-info.java @@ -0,0 +1,10 @@ +/** + * 提供区间和边界封装,主要包括: + *
      + *
    • {@link cn.hutool.core.lang.range.Bound}: 提供边界的抽象表示,包括边界范围、开闭区间等。
    • + *
    • {@link cn.hutool.core.lang.range.Range}: 提供可迭代的区间。
    • + *
    + * + * @author huangchengxing, looly + */ +package cn.hutool.core.lang.range; diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ArrayUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ArrayUtil.java index 530656cd4..77332059d 100755 --- a/hutool-core/src/main/java/cn/hutool/core/util/ArrayUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ArrayUtil.java @@ -1394,7 +1394,8 @@ public class ArrayUtil extends PrimitiveArrayUtil { * @return 最小值 * @since 3.0.9 */ - public static > T min(final T[] numberArray) { + @SafeVarargs + public static > T min(final T... numberArray) { return min(numberArray, null); } @@ -1428,7 +1429,8 @@ public class ArrayUtil extends PrimitiveArrayUtil { * @return 最大值 * @since 3.0.9 */ - public static > T max(final T[] numberArray) { + @SafeVarargs + public static > T max(final T... numberArray) { return max(numberArray, null); } diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java index ba75a0224..791dc890f 100644 --- a/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java @@ -4,6 +4,7 @@ import cn.hutool.core.date.DateField; import cn.hutool.core.date.DateRange; import cn.hutool.core.date.DateTime; import cn.hutool.core.date.DateUtil; +import cn.hutool.core.lang.range.Range; import cn.hutool.core.text.StrUtil; import org.junit.Assert; import org.junit.Test; From 45c87bd1ff6513006167398778e2acb501321746 Mon Sep 17 00:00:00 2001 From: Looly Date: Sat, 8 Oct 2022 19:30:43 +0800 Subject: [PATCH 04/29] fix code --- .../core/collection/CollectionOperation.java | 20 +- .../java/cn/hutool/core/lang/range/Bound.java | 189 ----------------- .../hutool/core/lang/range/BoundedRange.java | 100 +++------ .../lang/range/BoundedRangeOperation.java | 179 ++++++++++++++++ .../hutool/core/lang/range/FiniteBound.java | 192 ++++++++++++++++++ .../cn/hutool/core/lang/range/BoundTest.java | 21 +- .../core/lang/range/BoundedRangeTest.java | 22 +- .../core/lang/{ => range}/RangeTest.java | 3 +- 8 files changed, 439 insertions(+), 287 deletions(-) create mode 100644 hutool-core/src/main/java/cn/hutool/core/lang/range/BoundedRangeOperation.java create mode 100644 hutool-core/src/main/java/cn/hutool/core/lang/range/FiniteBound.java rename hutool-core/src/test/java/cn/hutool/core/lang/{ => range}/RangeTest.java (98%) diff --git a/hutool-core/src/main/java/cn/hutool/core/collection/CollectionOperation.java b/hutool-core/src/main/java/cn/hutool/core/collection/CollectionOperation.java index 7e1a97ce6..c9ddfe34f 100755 --- a/hutool-core/src/main/java/cn/hutool/core/collection/CollectionOperation.java +++ b/hutool-core/src/main/java/cn/hutool/core/collection/CollectionOperation.java @@ -24,6 +24,13 @@ import java.util.Set; */ public class CollectionOperation { + /** + * 创建运算对象 + * + * @param colls 集合列表 + * @param 元素类型 + * @return CollectionOperation + */ @SafeVarargs public static CollectionOperation of(final Collection... colls) { return new CollectionOperation<>(colls); @@ -160,14 +167,14 @@ public class CollectionOperation { // 任意容器为空, 则返回空集 for (final Collection coll : colls) { - if(CollUtil.isEmpty(coll)){ + if (CollUtil.isEmpty(coll)) { return SetUtil.zeroLinked(); } } final Set result = SetUtil.of(true, colls[0]); for (int i = 1; i < colls.length; i++) { - if(CollUtil.isNotEmpty(colls[i])){ + if (CollUtil.isNotEmpty(colls[i])) { result.retainAll(colls[i]); } } @@ -215,7 +222,7 @@ public class CollectionOperation { */ public List subtract() { final Collection[] colls = this.colls; - if(ArrayUtil.isEmpty(colls)){ + if (ArrayUtil.isEmpty(colls)) { return ListUtil.zero(); } final List result = ListUtil.of(colls[0]); @@ -226,6 +233,7 @@ public class CollectionOperation { } // region private methods + /** * 两个集合的并集
    * 针对一个集合中存在多个相同元素的情况,计算两个集合中此元素的个数,保留最多的个数
    @@ -315,12 +323,12 @@ public class CollectionOperation { * @return 差集的集合,返回 {@link ArrayList} */ private static Collection _disjunction(final Collection coll1, final Collection coll2) { - if(CollUtil.isEmpty(coll1)){ - if(CollUtil.isEmpty(coll2)){ + if (CollUtil.isEmpty(coll1)) { + if (CollUtil.isEmpty(coll2)) { return ListUtil.zero(); } return coll2; - } else if(CollUtil.isEmpty(coll2)){ + } else if (CollUtil.isEmpty(coll2)) { return coll1; } 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 index c26a559b7..b17f51234 100644 --- 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 @@ -1,7 +1,5 @@ package cn.hutool.core.lang.range; -import cn.hutool.core.text.CharSequenceUtil; - import java.util.Objects; import java.util.function.Predicate; @@ -187,191 +185,4 @@ public interface Bound> extends Predicate, Co */ @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 NoneLowerBound) { - return 1; - } - // 另一边界为无限大的右边界,则当前边界必然靠前 - if (bound instanceof 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 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 - ); - } - } } 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 index 5992c7754..5660f7800 100644 --- 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 @@ -1,8 +1,6 @@ package cn.hutool.core.lang.range; -import cn.hutool.core.comparator.CompareUtil; import cn.hutool.core.lang.Assert; -import cn.hutool.core.lang.Opt; import java.util.Objects; import java.util.function.Predicate; @@ -53,23 +51,14 @@ public class BoundedRange> implements Predicate< @SuppressWarnings({"rawtypes", "unchecked"}) private static final BoundedRange ALL = new BoundedRange(Bound.noneLowerBound(), Bound.noneUpperBound()); - /** - * 下界 - */ - private final Bound lowerBound; - - /** - * 上界 - */ - private final Bound upperBound; - /** * 构建一个上下界皆无限大的区间,即{@code {x | -∞ < x < +∞}} * + * @param 比较对象类型 * @return 区间 */ @SuppressWarnings("unchecked") - static > BoundedRange all() { + public static > BoundedRange all() { return ALL; } @@ -83,7 +72,7 @@ public class BoundedRange> implements Predicate< * @throws IllegalArgumentException 当创建的区间表示的集合为空时抛出 * @throws NullPointerException 上界或下界为{@code null}时抛出 */ - static > BoundedRange close(final T lowerBound, final T upperBound) { + public static > BoundedRange close(final T lowerBound, final T upperBound) { Objects.requireNonNull(lowerBound); Objects.requireNonNull(upperBound); return checkEmpty( @@ -103,7 +92,7 @@ public class BoundedRange> implements Predicate< * @throws IllegalArgumentException 当创建的区间表示的集合为空时抛出 * @throws NullPointerException 上界或下界为{@code null}时抛出 */ - static > BoundedRange open(final T lowerBound, final T upperBound) { + public static > BoundedRange open(final T lowerBound, final T upperBound) { Objects.requireNonNull(lowerBound); Objects.requireNonNull(upperBound); return checkEmpty( @@ -121,7 +110,7 @@ public class BoundedRange> implements Predicate< * @throws IllegalArgumentException 当创建的区间表示的集合为空时抛出 * @throws NullPointerException 上界或下界为{@code null}时抛出 */ - static > BoundedRange closeOpen(final T lowerBound, final T upperBound) { + public static > BoundedRange closeOpen(final T lowerBound, final T upperBound) { Objects.requireNonNull(lowerBound); Objects.requireNonNull(upperBound); return checkEmpty( @@ -142,7 +131,7 @@ public class BoundedRange> implements Predicate< * @throws IllegalArgumentException 当创建的区间表示的集合为空时抛出 * @throws NullPointerException 上界或下界为{@code null}时抛出 */ - static > BoundedRange openClose(final T lowerBound, final T upperBound) { + public static > BoundedRange openClose(final T lowerBound, final T upperBound) { Objects.requireNonNull(lowerBound); Objects.requireNonNull(upperBound); return checkEmpty( @@ -162,7 +151,7 @@ public class BoundedRange> implements Predicate< * @throws NullPointerException 下界为{@code null}时抛出 * @see Bound#toRange() */ - static > BoundedRange greaterThan(final T lowerBound) { + public static > BoundedRange greaterThan(final T lowerBound) { return Bound.greaterThan(lowerBound).toRange(); } @@ -175,7 +164,7 @@ public class BoundedRange> implements Predicate< * @throws NullPointerException 下界为{@code null}时抛出 * @see Bound#toRange() */ - static > BoundedRange atLeast(final T lowerBound) { + public static > BoundedRange atLeast(final T lowerBound) { return Bound.atLeast(lowerBound).toRange(); } @@ -188,7 +177,7 @@ public class BoundedRange> implements Predicate< * @throws NullPointerException 上界为{@code null}时抛出 * @see Bound#toRange() */ - static > BoundedRange lessThan(final T upperBound) { + public static > BoundedRange lessThan(final T upperBound) { return Bound.lessThan(upperBound).toRange(); } @@ -201,10 +190,20 @@ public class BoundedRange> implements Predicate< * @throws NullPointerException 上界为{@code null}时抛出 * @see Bound#toRange() */ - static > BoundedRange atMost(final T upperBound) { + public static > BoundedRange atMost(final T upperBound) { return Bound.atMost(upperBound).toRange(); } + /** + * 下界 + */ + private final Bound lowerBound; + + /** + * 上界 + */ + private final Bound upperBound; + /** * 构造 * @@ -446,62 +445,37 @@ public class BoundedRange> implements Predicate< * @return 合并后的新区间,若两区间不相交则返回当前集合 */ public BoundedRange unionIfIntersected(final BoundedRange other) { - Objects.requireNonNull(other); - if (isDisjoint(other)) { - return this; - } - return new BoundedRange<>( - CompareUtil.min(getLowerBound(), other.getLowerBound()), - CompareUtil.max(getUpperBound(), other.getUpperBound()) - ); + return BoundedRangeOperation.unionIfIntersected(this, other); } /** * 获得包含当前区间与指定区间的最小的区间 * - * @param other 另一区间 + * @param other 另一个区间 * @return 包含当前区间与指定区间的最小的区间 */ public BoundedRange span(final BoundedRange other) { - Objects.requireNonNull(other); - return new BoundedRange<>( - CompareUtil.min(getLowerBound(), other.getLowerBound()), - CompareUtil.max(getUpperBound(), other.getUpperBound()) - ); + return BoundedRangeOperation.span(this, other); } /** * 若{@code other}与当前区间不相连,则获得两区间中间的间隔部分 * - * @param other 另一区间 + * @param other 另一个区间 * @return 代表间隔部分的区间,若两区间相交则返回{@code null} */ public BoundedRange gap(final BoundedRange other) { - Objects.requireNonNull(other); - if (isIntersected(other)) { - return null; - } - return new BoundedRange<>( - CompareUtil.min(getUpperBound(), other.getUpperBound()).negate(), - CompareUtil.max(getLowerBound(), other.getLowerBound()).negate() - ); + return BoundedRangeOperation.gap(this, other); } /** * 若{@code other}与当前区间相交,则获得该区间与当前区间的交集 * - * @param other 另一区间 + * @param other 另一个区间 * @return 代表交集的区间,若无交集则返回{@code null} */ public BoundedRange intersection(final BoundedRange other) { - Objects.requireNonNull(other); - if (isDisjoint(other)) { - return null; - } - return new BoundedRange<>( - CompareUtil.min(getLowerBound(), other.getLowerBound()), - CompareUtil.max(getUpperBound(), other.getUpperBound()) - ); + return BoundedRangeOperation.intersection(this, other); } /** @@ -511,10 +485,7 @@ public class BoundedRange> implements Predicate< * @return 区间 */ public BoundedRange subGreatThan(final T min) { - return Opt.ofNullable(min) - .filter(this) - .map(t -> new BoundedRange<>(Bound.greaterThan(t), getUpperBound())) - .orElse(this); + return BoundedRangeOperation.subGreatThan(this, min); } /** @@ -524,10 +495,7 @@ public class BoundedRange> implements Predicate< * @return 区间 */ public BoundedRange subAtLeast(final T min) { - return Opt.ofNullable(min) - .filter(this) - .map(t -> new BoundedRange<>(Bound.atLeast(t), getUpperBound())) - .orElse(this); + return BoundedRangeOperation.subAtLeast(this, min); } /** @@ -537,10 +505,7 @@ public class BoundedRange> implements Predicate< * @return 区间 */ public BoundedRange subLessThan(final T max) { - return Opt.ofNullable(max) - .filter(this) - .map(t -> new BoundedRange<>(getLowerBound(), Bound.lessThan(max))) - .orElse(this); + return BoundedRangeOperation.subLessThan(this, max); } /** @@ -550,10 +515,7 @@ public class BoundedRange> implements Predicate< * @return 区间 */ public BoundedRange subAtMost(final T max) { - return Opt.ofNullable(max) - .filter(this) - .map(t -> new BoundedRange<>(getLowerBound(), Bound.atMost(max))) - .orElse(this); + return BoundedRangeOperation.subAtMost(this, max); } // endregion diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/range/BoundedRangeOperation.java b/hutool-core/src/main/java/cn/hutool/core/lang/range/BoundedRangeOperation.java new file mode 100644 index 000000000..c7862b636 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/lang/range/BoundedRangeOperation.java @@ -0,0 +1,179 @@ +package cn.hutool.core.lang.range; + +import cn.hutool.core.comparator.CompareUtil; +import cn.hutool.core.lang.Opt; + +import java.util.Objects; + +/** + * 边界区间的操作工具,如子区间、合并区间等 + * + * @author huangchengxing + * @since 6.0.0 + */ +public class BoundedRangeOperation { + + /** + * 若{@code other}与当前区间相交,则将其与当前区间合并。 + * + * @param 可比较对象类型 + * @param boundedRange 区间 + * @param other 另一个区间 + * @return 合并后的新区间,若两区间不相交则返回当前集合 + */ + public static > BoundedRange unionIfIntersected(final BoundedRange boundedRange, final BoundedRange other) { + Objects.requireNonNull(boundedRange); + Objects.requireNonNull(other); + if (isDisjoint(boundedRange, other)) { + return boundedRange; + } + return new BoundedRange<>( + CompareUtil.min(boundedRange.getLowerBound(), other.getLowerBound()), + CompareUtil.max(boundedRange.getUpperBound(), other.getUpperBound()) + ); + } + + /** + * 获得包含当前区间与指定区间的最小的区间 + * + * @param 可比较对象类型 + * @param boundedRange 区间 + * @param other 另一个区间 + * @return 包含当前区间与指定区间的最小的区间 + */ + public static > BoundedRange span(final BoundedRange boundedRange, final BoundedRange other) { + Objects.requireNonNull(boundedRange); + Objects.requireNonNull(other); + return new BoundedRange<>( + CompareUtil.min(boundedRange.getLowerBound(), other.getLowerBound()), + CompareUtil.max(boundedRange.getUpperBound(), other.getUpperBound()) + ); + } + + /** + * 若{@code other}与当前区间不相连,则获得两区间中间的间隔部分 + * + * @param 可比较对象类型 + * @param boundedRange 区间 + * @param other 另一个区间 + * @return 代表间隔部分的区间,若两区间相交则返回{@code null} + */ + public static > BoundedRange gap(final BoundedRange boundedRange, final BoundedRange other) { + Objects.requireNonNull(boundedRange); + Objects.requireNonNull(other); + if (isIntersected(boundedRange, other)) { + return null; + } + return new BoundedRange<>( + CompareUtil.min(boundedRange.getUpperBound(), other.getUpperBound()).negate(), + CompareUtil.max(boundedRange.getLowerBound(), other.getLowerBound()).negate() + ); + } + + /** + * 若{@code other}与当前区间相交,则获得该区间与当前区间的交集 + * + * @param 可比较对象类型 + * @param boundedRange 区间 + * @param other 另一个区间 + * @return 代表交集的区间,若无交集则返回{@code null} + */ + public static > BoundedRange intersection(final BoundedRange boundedRange, final BoundedRange other) { + Objects.requireNonNull(boundedRange); + Objects.requireNonNull(other); + if (isDisjoint(boundedRange, other)) { + return null; + } + return new BoundedRange<>( + CompareUtil.max(boundedRange.getLowerBound(), other.getLowerBound()), + CompareUtil.min(boundedRange.getUpperBound(), other.getUpperBound()) + ); + } + + /** + * 截取当前区间中大于{@code min}的部分,若{@code min}不在该区间中,则返回当前区间本身 + * + * @param 可比较对象类型 + * @param boundedRange 区间 + * @param min 最大的左值 + * @return 区间 + */ + public static > BoundedRange subGreatThan(final BoundedRange boundedRange, final T min) { + return Opt.ofNullable(min) + .filter(boundedRange) + .map(t -> new BoundedRange<>(Bound.greaterThan(t), boundedRange.getUpperBound())) + .orElse(boundedRange); + } + + /** + * 截取当前区间中大于等于{@code min}的部分,若{@code min}不在该区间中,则返回当前区间本身 + * + * @param 可比较对象类型 + * @param boundedRange 区间 + * @param min 最大的左值 + * @return 区间 + */ + public static > BoundedRange subAtLeast(final BoundedRange boundedRange, final T min) { + return Opt.ofNullable(min) + .filter(boundedRange) + .map(t -> new BoundedRange<>(Bound.atLeast(t), boundedRange.getUpperBound())) + .orElse(boundedRange); + } + + /** + * 截取当前区间中小于{@code max}的部分,若{@code max}不在该区间中,则返回当前区间本身 + * + * @param 可比较对象类型 + * @param boundedRange 区间 + * @param max 最大的左值 + * @return 区间 + */ + public static > BoundedRange subLessThan(final BoundedRange boundedRange, final T max) { + return Opt.ofNullable(max) + .filter(boundedRange) + .map(t -> new BoundedRange<>(boundedRange.getLowerBound(), Bound.lessThan(max))) + .orElse(boundedRange); + } + + /** + * 截取当前区间中小于等于{@code max}的部分,若{@code max}不在该区间中,则返回当前区间本身 + * + * @param 可比较对象类型 + * @param boundedRange 区间 + * @param max 最大的左值 + * @return 区间 + */ + public static > BoundedRange subAtMost(final BoundedRange boundedRange, final T max) { + return Opt.ofNullable(max) + .filter(boundedRange) + .map(t -> new BoundedRange<>(boundedRange.getLowerBound(), Bound.atMost(max))) + .orElse(boundedRange); + } + + /** + * {@code other}是否与当前区间相交 + * + * @param 可比较对象类型 + * @param boundedRange 区间 + * @param other 另一个区间 + * @return 是否相交 + */ + public static > boolean isIntersected(final BoundedRange boundedRange, final BoundedRange other) { + return false == isDisjoint(boundedRange, other); + } + + /** + * {@code other}是否与当前区间不相交 + * + * @param 可比较对象类型 + * @param boundedRange 区间 + * @param other 另一个区间 + * @return 是否 + */ + public static > boolean isDisjoint(final BoundedRange boundedRange, final BoundedRange other) { + Objects.requireNonNull(boundedRange); + Objects.requireNonNull(other); + return boundedRange.getLowerBound().compareTo(other.getUpperBound()) > 0 + || boundedRange.getUpperBound().compareTo(other.getLowerBound()) < 0; + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/range/FiniteBound.java b/hutool-core/src/main/java/cn/hutool/core/lang/range/FiniteBound.java new file mode 100644 index 000000000..8ebd1d37e --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/lang/range/FiniteBound.java @@ -0,0 +1,192 @@ +package cn.hutool.core.lang.range; + +import cn.hutool.core.text.CharSequenceUtil; + +import java.util.Objects; + +/** + * 由一个有限值构成的边界 + * + * @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 NoneLowerBound) { + return 1; + } + // 另一边界为无限大的右边界,则当前边界必然靠前 + if (bound instanceof 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 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 + ); + } +} 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 index a14f05ba2..0a7ea480e 100644 --- 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 @@ -6,29 +6,30 @@ import org.junit.Test; /** * test for {@link Bound} */ +@SuppressWarnings("EqualsWithItself") public class BoundTest { @Test public void testEquals() { - Bound bound = new Bound.FiniteBound<>(1, BoundType.OPEN_UPPER_BOUND); + final Bound bound = new 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.assertEquals(bound, new FiniteBound<>(1, BoundType.OPEN_UPPER_BOUND)); + Assert.assertNotEquals(bound, new FiniteBound<>(2, BoundType.OPEN_UPPER_BOUND)); + Assert.assertNotEquals(bound, new 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()); + final int hashCode = new FiniteBound<>(1, BoundType.OPEN_UPPER_BOUND).hashCode(); + Assert.assertEquals(hashCode, new FiniteBound<>(1, BoundType.OPEN_UPPER_BOUND).hashCode()); + Assert.assertNotEquals(hashCode, new FiniteBound<>(2, BoundType.OPEN_UPPER_BOUND).hashCode()); + Assert.assertNotEquals(hashCode, new FiniteBound<>(1, BoundType.OPEN_LOWER_BOUND).hashCode()); } @Test public void testNoneLowerBound() { - Bound bound = Bound.noneLowerBound(); + final Bound bound = Bound.noneLowerBound(); // negate Assert.assertEquals(bound, bound.negate()); // test @@ -49,7 +50,7 @@ public class BoundTest { @Test public void testNoneUpperBound() { - Bound bound = Bound.noneUpperBound(); + final Bound bound = Bound.noneUpperBound(); // negate Assert.assertEquals(bound, bound.negate()); // test 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 index f99dec051..fd1a67bba 100644 --- 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 @@ -10,7 +10,7 @@ public class BoundedRangeTest { @Test public void testEquals() { - BoundedRange range = new BoundedRange<>( + final BoundedRange range = new BoundedRange<>( Bound.greaterThan(0), Bound.lessThan(10) ); Assert.assertEquals(range, range); @@ -25,7 +25,7 @@ public class BoundedRangeTest { @Test public void testHashCode() { - int hasCode = new BoundedRange<>( + final int hasCode = new BoundedRange<>( Bound.greaterThan(0), Bound.lessThan(10) ).hashCode(); Assert.assertEquals(hasCode, new BoundedRange<>( @@ -38,7 +38,7 @@ public class BoundedRangeTest { @Test public void testAll() { - BoundedRange range = BoundedRange.all(); + final BoundedRange range = BoundedRange.all(); Assert.assertEquals("(-∞, +∞)", range.toString()); // getBound @@ -83,7 +83,7 @@ public class BoundedRangeTest { @Test public void testOpen() { - BoundedRange range = BoundedRange.open(0, 5); + final BoundedRange range = BoundedRange.open(0, 5); Assert.assertEquals("(0, 5)", range.toString()); // getBound @@ -125,7 +125,7 @@ public class BoundedRangeTest { @Test public void testClose() { - BoundedRange range = BoundedRange.close(0, 5); + final BoundedRange range = BoundedRange.close(0, 5); Assert.assertEquals("[0, 5]", range.toString()); // getBound @@ -168,7 +168,7 @@ public class BoundedRangeTest { @Test public void testOpenClose() { - BoundedRange range = BoundedRange.openClose(0, 5); + final BoundedRange range = BoundedRange.openClose(0, 5); Assert.assertEquals("(0, 5]", range.toString()); // getBound @@ -187,7 +187,7 @@ public class BoundedRangeTest { @Test public void testCloseOpen() { - BoundedRange range = BoundedRange.closeOpen(0, 5); + final BoundedRange range = BoundedRange.closeOpen(0, 5); Assert.assertEquals("[0, 5)", range.toString()); // getBound @@ -206,7 +206,7 @@ public class BoundedRangeTest { @Test public void testGreatThan() { - BoundedRange range = BoundedRange.greaterThan(0); + final BoundedRange range = BoundedRange.greaterThan(0); Assert.assertEquals("(0, +∞)", range.toString()); // getBound @@ -225,7 +225,7 @@ public class BoundedRangeTest { @Test public void testAtLeast() { - BoundedRange range = BoundedRange.atLeast(0); + final BoundedRange range = BoundedRange.atLeast(0); Assert.assertEquals("[0, +∞)", range.toString()); // getBound @@ -244,7 +244,7 @@ public class BoundedRangeTest { @Test public void testLessThan() { - BoundedRange range = BoundedRange.lessThan(5); + final BoundedRange range = BoundedRange.lessThan(5); Assert.assertEquals("(-∞, 5)", range.toString()); // getBound @@ -263,7 +263,7 @@ public class BoundedRangeTest { @Test public void testAtMost() { - BoundedRange range = BoundedRange.atMost(5); + final BoundedRange range = BoundedRange.atMost(5); Assert.assertEquals("(-∞, 5]", range.toString()); // getBound diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/range/RangeTest.java similarity index 98% rename from hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java rename to hutool-core/src/test/java/cn/hutool/core/lang/range/RangeTest.java index 791dc890f..8bc27ea8c 100644 --- a/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/lang/range/RangeTest.java @@ -1,10 +1,9 @@ -package cn.hutool.core.lang; +package cn.hutool.core.lang.range; import cn.hutool.core.date.DateField; import cn.hutool.core.date.DateRange; import cn.hutool.core.date.DateTime; import cn.hutool.core.date.DateUtil; -import cn.hutool.core.lang.range.Range; import cn.hutool.core.text.StrUtil; import org.junit.Assert; import org.junit.Test; From fd138c2b70f04e1b1a3b3f2976116948a27e6ba7 Mon Sep 17 00:00:00 2001 From: Looly Date: Sat, 8 Oct 2022 20:51:45 +0800 Subject: [PATCH 05/29] fix code --- .../hutool/core/comparator/CompareUtil.java | 94 +++++++++++++++++++ .../hutool/core/lang/range/BoundedRange.java | 33 ++----- .../lang/range/BoundedRangeOperation.java | 6 +- .../hutool/core/lang/range/FiniteBound.java | 39 ++++---- .../java/cn/hutool/core/math/NumberUtil.java | 84 +---------------- .../core/lang/range/BoundedRangeTest.java | 8 +- 6 files changed, 135 insertions(+), 129 deletions(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/comparator/CompareUtil.java b/hutool-core/src/main/java/cn/hutool/core/comparator/CompareUtil.java index ce85293c4..afbeac703 100644 --- a/hutool-core/src/main/java/cn/hutool/core/comparator/CompareUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/comparator/CompareUtil.java @@ -352,4 +352,98 @@ public class CompareUtil { public static > T max(final T t1, final T t2) { return compare(t1, t2) >= 0 ? t1 : t2; } + + /** + * {@code null}安全的检查两个对象是否相同,通过调用{@code compare(c1, c2) == 0}完成 + * + * @param 被比较对象类型 + * @param c1 对象1,可以为{@code null} + * @param c2 对象2,可以为{@code null} + * @return 是否相等 + * @see java.util.Comparator#compare(Object, Object) + */ + public static > boolean equals(final T c1, final T c2) { + return compare(c1, c2) == 0; + } + + /** + * c1是否大于c2,通过调用{@code compare(c1, c2) > 0}完成 + * + * @param 被比较对象类型 + * @param c1 对象1,可以为{@code null} + * @param c2 对象2,可以为{@code null} + * @return c1是否大于c2 + * @see java.util.Comparator#compare(Object, Object) + */ + public static > boolean gt(final T c1, final T c2) { + return compare(c1, c2) > 0; + } + + /** + * c1是否大于或等于c2,通过调用{@code compare(c1, c2) >= 0}完成 + * + * @param 被比较对象类型 + * @param c1 对象1,可以为{@code null} + * @param c2 对象2,可以为{@code null} + * @return c1是否大于或等于c2 + * @see java.util.Comparator#compare(Object, Object) + */ + public static > boolean ge(final T c1, final T c2) { + return compare(c1, c2) >= 0; + } + + /** + * c1是否大小于c2,通过调用{@code compare(c1, c2) < 0}完成 + * + * @param 被比较对象类型 + * @param c1 对象1,可以为{@code null} + * @param c2 对象2,可以为{@code null} + * @return c1是否小于c2 + * @see java.util.Comparator#compare(Object, Object) + */ + public static > boolean lt(final T c1, final T c2) { + return compare(c1, c2) < 0; + } + + /** + * c1是否小于或等于c2,通过调用{@code compare(c1, c2) <= 0}完成 + * + * @param 被比较对象类型 + * @param c1 对象1,可以为{@code null} + * @param c2 对象2,可以为{@code null} + * @return c1是否小于或等于c2 + * @see java.util.Comparator#compare(Object, Object) + */ + public static > boolean le(final T c1, final T c2) { + return compare(c1, c2) <= 0; + } + + /** + * 给定的{@code value}是否在{@code c1}和{@code c2}的范围内
    + * 即 {@code min(c1,c2) <= value <= max(c1,c2)} + * + * @param 被比较对象类型 + * @param value 检查的对象,可以为{@code null} + * @param c1 对象1,可以为{@code null} + * @param c2 对象2,可以为{@code null} + * @return 给定的{@code value}是否在{@code c1}和{@code c2}的范围内 + */ + public static > boolean isIn(final T value, final T c1, final T c2) { + return ge(value, min(c1, c2)) && le(value, max(c1, c2)); + } + + /** + * 给定的{@code value}是否在{@code c1}和{@code c2}的范围内,但是不包括边界
    + * 即 {@code min(c1,c2) < value < max(c1,c2)} + * + * @param 被比较对象类型 + * @param value 检查的对象,可以为{@code null} + * @param c1 对象1,可以为{@code null} + * @param c2 对象2,可以为{@code null} + * @return c1是否小于或等于c2 + * @see java.util.Comparator#compare(Object, Object) + */ + public static > boolean isInExclusive(final T value, final T c1, final T c2) { + return gt(value, min(c1, c2)) && lt(value, max(c1, c2)); + } } 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 index 5660f7800..36776bfb4 100644 --- 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 @@ -299,7 +299,7 @@ public class BoundedRange> implements Predicate< // 上界小于下界时为空 return compareValue > 0 // 上下界的边界值相等,且不为退化区间是为空 - || !(low.getType().isClose() && up.getType().isClose()); + || false == (low.getType().isClose() && up.getType().isClose()); } /** @@ -347,7 +347,7 @@ public class BoundedRange> implements Predicate< /** * {@code other}是否是当前区间的子集 * - * @param other 另一区间 + * @param other 另一个区间 * @return 是否 */ public boolean isSuperset(final BoundedRange other) { @@ -358,7 +358,7 @@ public class BoundedRange> implements Predicate< /** * {@code other}是否是当前区间的子集 * - * @param other 另一区间 + * @param other 另一个区间 * @return 是否 */ public boolean isProperSuperset(final BoundedRange other) { @@ -369,7 +369,7 @@ public class BoundedRange> implements Predicate< /** * 当前区间是否是{@code other}的子集 * - * @param other 另一区间 + * @param other 另一个区间 * @return 是否 */ public boolean isSubset(final BoundedRange other) { @@ -380,7 +380,7 @@ public class BoundedRange> implements Predicate< /** * 当前区间是否是{@code other}的真子集 * - * @param other 另一区间 + * @param other 另一个区间 * @return 是否 */ public boolean isProperSubset(final BoundedRange other) { @@ -391,34 +391,21 @@ public class BoundedRange> implements Predicate< /** * {@code other}是否与当前区间不相交 * - * @param other 另一区间 + * @param other 另一个区间 * @return 是否 */ public boolean isDisjoint(final BoundedRange other) { - return getLowerBound().compareTo(other.getUpperBound()) > 0 - || getUpperBound().compareTo(other.getLowerBound()) < 0; + return BoundedRangeOperation.isDisjoint(this, other); } /** * {@code other}是否与当前区间相交: * - * @param 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; + return BoundedRangeOperation.isIntersected(this, other); } /** @@ -441,7 +428,7 @@ public class BoundedRange> implements Predicate< /** * 若{@code other}与当前区间相交,则将其与当前区间合并。 * - * @param other 另一区间 + * @param other 另一个区间 * @return 合并后的新区间,若两区间不相交则返回当前集合 */ public BoundedRange unionIfIntersected(final BoundedRange other) { diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/range/BoundedRangeOperation.java b/hutool-core/src/main/java/cn/hutool/core/lang/range/BoundedRangeOperation.java index c7862b636..2b7619e88 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/range/BoundedRangeOperation.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/range/BoundedRangeOperation.java @@ -150,8 +150,9 @@ public class BoundedRangeOperation { .orElse(boundedRange); } + // region ========== 判断交并集 ========== /** - * {@code other}是否与当前区间相交 + * {@code boundedRange}是否与{@code other}相交 * * @param 可比较对象类型 * @param boundedRange 区间 @@ -163,7 +164,7 @@ public class BoundedRangeOperation { } /** - * {@code other}是否与当前区间不相交 + * {@code boundedRange}是否与{@code other}前区间不相交 * * @param 可比较对象类型 * @param boundedRange 区间 @@ -176,4 +177,5 @@ public class BoundedRangeOperation { return boundedRange.getLowerBound().compareTo(other.getUpperBound()) > 0 || boundedRange.getUpperBound().compareTo(other.getLowerBound()) < 0; } + // endregion } diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/range/FiniteBound.java b/hutool-core/src/main/java/cn/hutool/core/lang/range/FiniteBound.java index 8ebd1d37e..be63f5788 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/range/FiniteBound.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/range/FiniteBound.java @@ -1,6 +1,7 @@ package cn.hutool.core.lang.range; import cn.hutool.core.text.CharSequenceUtil; +import cn.hutool.core.util.ObjUtil; import java.util.Objects; @@ -93,31 +94,13 @@ class FiniteBound> implements Bound { return -1; } // 两值不相等,直接比较边界值 - if (!Objects.equals(getValue(), bound.getValue())) { + if (ObjUtil.notEquals(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 Integer.compare(bt1.getCode(), bt2.getCode()); - } - /** * 获取{@code "[value"}或{@code "(value"}格式的字符串 * @@ -189,4 +172,22 @@ class FiniteBound> implements Bound { "{x | x {} {}}", type.getOperator(), value ); } + + /** + * 当两个边界的值不相等时,判断它们在坐标轴上位置的先后顺序 + */ + 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 Integer.compare(bt1.getCode(), bt2.getCode()); + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/math/NumberUtil.java b/hutool-core/src/main/java/cn/hutool/core/math/NumberUtil.java index 64a3d6c4b..9d5152290 100644 --- a/hutool-core/src/main/java/cn/hutool/core/math/NumberUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/math/NumberUtil.java @@ -1,5 +1,6 @@ package cn.hutool.core.math; +import cn.hutool.core.comparator.CompareUtil; import cn.hutool.core.lang.Assert; import cn.hutool.core.text.StrUtil; import cn.hutool.core.util.ArrayUtil; @@ -946,78 +947,6 @@ public class NumberUtil { return Long.parseLong(binaryStr, 2); } - /** - * 检查值是否在指定范围内 - * - * @param value 值 - * @param minInclude 最小值(包含) - * @param maxInclude 最大值(包含) - * @return 经过检查后的值 - * @since 5.8.5 - */ - public static boolean isIn(final BigDecimal value, final BigDecimal minInclude, final BigDecimal maxInclude) { - Assert.notNull(value); - Assert.notNull(minInclude); - Assert.notNull(maxInclude); - return isGreaterOrEqual(value, minInclude) && isLessOrEqual(value, maxInclude); - } - - /** - * 比较大小,参数1 > 参数2 返回true - * - * @param bigNum1 数字1 - * @param bigNum2 数字2 - * @return 是否大于 - * @since 3.0.9 - */ - public static boolean isGreater(final BigDecimal bigNum1, final BigDecimal bigNum2) { - Assert.notNull(bigNum1); - Assert.notNull(bigNum2); - return bigNum1.compareTo(bigNum2) > 0; - } - - /** - * 比较大小,参数1 >= 参数2 返回true - * - * @param bigNum1 数字1 - * @param bigNum2 数字2 - * @return 是否大于等于 - * @since 3, 0.9 - */ - public static boolean isGreaterOrEqual(final BigDecimal bigNum1, final BigDecimal bigNum2) { - Assert.notNull(bigNum1); - Assert.notNull(bigNum2); - return bigNum1.compareTo(bigNum2) >= 0; - } - - /** - * 比较大小,参数1 < 参数2 返回true - * - * @param bigNum1 数字1 - * @param bigNum2 数字2 - * @return 是否小于 - * @since 3, 0.9 - */ - public static boolean isLess(final BigDecimal bigNum1, final BigDecimal bigNum2) { - Assert.notNull(bigNum1); - Assert.notNull(bigNum2); - return bigNum1.compareTo(bigNum2) < 0; - } - - /** - * 比较大小,参数1<=参数2 返回true - * - * @param bigNum1 数字1 - * @param bigNum2 数字2 - * @return 是否小于等于 - * @since 3, 0.9 - */ - public static boolean isLessOrEqual(final BigDecimal bigNum1, final BigDecimal bigNum2) { - Assert.notNull(bigNum1); - Assert.notNull(bigNum2); - return bigNum1.compareTo(bigNum2) <= 0; - } - /** * 比较大小,值相等 返回true
    * 此方法通过调用{@link Double#doubleToLongBits(double)}方法来判断是否相等
    @@ -1067,17 +996,10 @@ public class NumberUtil { * @param bigNum1 数字1 * @param bigNum2 数字2 * @return 是否相等 + * @see CompareUtil#equals(Comparable, Comparable) */ public static boolean equals(final BigDecimal bigNum1, final BigDecimal bigNum2) { - //noinspection NumberEquality - if (bigNum1 == bigNum2) { - // 如果用户传入同一对象,省略compareTo以提高性能。 - return true; - } - if (bigNum1 == null || bigNum2 == null) { - return false; - } - return 0 == bigNum1.compareTo(bigNum2); + return CompareUtil.equals(bigNum1, bigNum2); } /** 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 index fd1a67bba..7d36e19d1 100644 --- 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 @@ -54,8 +54,8 @@ public class BoundedRangeTest { // isXXX Assert.assertFalse(range.isDisjoint(BoundedRange.open(0, 5))); - Assert.assertTrue(range.isEquals(range)); - Assert.assertFalse(range.isEquals(BoundedRange.open(0, 5))); + Assert.assertEquals(range, range); + Assert.assertNotEquals(range, 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))); @@ -101,7 +101,7 @@ public class BoundedRangeTest { Assert.assertFalse(range.test(-1)); // isXXX - Assert.assertTrue(range.isEquals(range)); + Assert.assertEquals(range, 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] @@ -142,7 +142,7 @@ public class BoundedRangeTest { Assert.assertFalse(range.test(-1)); // isXXX - Assert.assertTrue(range.isEquals(range)); + Assert.assertEquals(range, 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] From 338e986a9d7ae5c297e8e3b4e8ef11f95a89e62e Mon Sep 17 00:00:00 2001 From: Looly Date: Sat, 8 Oct 2022 21:24:36 +0800 Subject: [PATCH 06/29] fix code --- .../src/main/java/cn/hutool/core/util/ArrayUtil.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ArrayUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ArrayUtil.java index 77332059d..530656cd4 100755 --- a/hutool-core/src/main/java/cn/hutool/core/util/ArrayUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ArrayUtil.java @@ -1394,8 +1394,7 @@ public class ArrayUtil extends PrimitiveArrayUtil { * @return 最小值 * @since 3.0.9 */ - @SafeVarargs - public static > T min(final T... numberArray) { + public static > T min(final T[] numberArray) { return min(numberArray, null); } @@ -1429,8 +1428,7 @@ public class ArrayUtil extends PrimitiveArrayUtil { * @return 最大值 * @since 3.0.9 */ - @SafeVarargs - public static > T max(final T... numberArray) { + public static > T max(final T[] numberArray) { return max(numberArray, null); } From bcdbf5537cbcdae5ea29bf4a71ee2031ef0fb0e1 Mon Sep 17 00:00:00 2001 From: Looly Date: Sat, 8 Oct 2022 21:25:52 +0800 Subject: [PATCH 07/29] fix code --- hutool-swing/src/test/java/cn/hutool/swing/img/ImgTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hutool-swing/src/test/java/cn/hutool/swing/img/ImgTest.java b/hutool-swing/src/test/java/cn/hutool/swing/img/ImgTest.java index 984d526b3..b57d487c3 100755 --- a/hutool-swing/src/test/java/cn/hutool/swing/img/ImgTest.java +++ b/hutool-swing/src/test/java/cn/hutool/swing/img/ImgTest.java @@ -2,7 +2,7 @@ package cn.hutool.swing.img; import cn.hutool.core.io.FileTypeUtil; import cn.hutool.core.io.FileUtil; -import cn.hutool.core.net.URLUtil; +import cn.hutool.core.net.url.URLUtil; import org.junit.Ignore; import org.junit.Test; From 3ece54bf5826a13aac5889ae26d3fad506704b0b Mon Sep 17 00:00:00 2001 From: Looly Date: Sun, 9 Oct 2022 01:12:02 +0800 Subject: [PATCH 08/29] fix code --- .../java/cn/hutool/core/reflect/NullType.java | 23 +++++ .../java/cn/hutool/json/InternalJSONUtil.java | 58 +++++++++-- .../main/java/cn/hutool/json/JSONConfig.java | 38 +------ .../main/java/cn/hutool/json/JSONParser.java | 7 +- .../main/java/cn/hutool/json/JSONTokener.java | 53 +++------- .../main/java/cn/hutool/json/JSONUtil.java | 18 +++- .../cn/hutool/json/convert/JSONConverter.java | 98 ++++++++++++------- .../convert/JSONDeserializerConverter.java | 36 ------- .../serialize/GlobalSerializeMapping.java | 34 ++++--- .../hutool/json/serialize/package-info.java | 4 + .../json/writer/GlobalValueWriterMapping.java | 44 +++++++++ .../hutool/json/writer/JSONValueWriter.java | 3 + .../cn/hutool/json/writer/JSONWriter.java | 14 ++- .../writer/GlobalValueWriterMappingTest.java | 67 +++++++++++++ 14 files changed, 312 insertions(+), 185 deletions(-) create mode 100644 hutool-core/src/main/java/cn/hutool/core/reflect/NullType.java delete mode 100644 hutool-json/src/main/java/cn/hutool/json/convert/JSONDeserializerConverter.java create mode 100644 hutool-json/src/main/java/cn/hutool/json/serialize/package-info.java create mode 100644 hutool-json/src/main/java/cn/hutool/json/writer/GlobalValueWriterMapping.java create mode 100644 hutool-json/src/test/java/cn/hutool/json/writer/GlobalValueWriterMappingTest.java diff --git a/hutool-core/src/main/java/cn/hutool/core/reflect/NullType.java b/hutool-core/src/main/java/cn/hutool/core/reflect/NullType.java new file mode 100644 index 000000000..5bf5790a5 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/reflect/NullType.java @@ -0,0 +1,23 @@ +package cn.hutool.core.reflect; + +import java.lang.reflect.Type; + +/** + * 空类型表示 + * + * @author looly + * @since 6.0.0 + */ +public class NullType implements Type { + /** + * 单例对象 + */ + public static NullType INSTANCE = new NullType(); + + private NullType(){} + + @Override + public String toString() { + return "Type of null"; + } +} diff --git a/hutool-json/src/main/java/cn/hutool/json/InternalJSONUtil.java b/hutool-json/src/main/java/cn/hutool/json/InternalJSONUtil.java index c2e8724e7..a8931d3ad 100755 --- a/hutool-json/src/main/java/cn/hutool/json/InternalJSONUtil.java +++ b/hutool-json/src/main/java/cn/hutool/json/InternalJSONUtil.java @@ -9,25 +9,26 @@ import cn.hutool.core.map.CaseInsensitiveLinkedMap; import cn.hutool.core.map.CaseInsensitiveTreeMap; import cn.hutool.core.math.NumberUtil; import cn.hutool.core.reflect.ClassUtil; +import cn.hutool.core.reflect.ConstructorUtil; +import cn.hutool.core.reflect.TypeUtil; import cn.hutool.core.text.StrUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.CharUtil; import cn.hutool.core.util.ObjUtil; +import cn.hutool.json.serialize.GlobalSerializeMapping; +import cn.hutool.json.serialize.JSONDeserializer; import cn.hutool.json.serialize.JSONString; +import cn.hutool.json.writer.GlobalValueWriterMapping; +import cn.hutool.json.writer.JSONValueWriter; import java.io.IOException; import java.io.StringWriter; import java.io.Writer; +import java.lang.reflect.Type; import java.math.BigDecimal; import java.sql.SQLException; import java.time.temporal.TemporalAccessor; -import java.util.Calendar; -import java.util.Collection; -import java.util.Comparator; -import java.util.Date; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.TreeMap; +import java.util.*; import java.util.function.Predicate; /** @@ -56,9 +57,11 @@ public final class InternalJSONUtil { * @return 包装后的值,null表示此值需被忽略 */ static Object wrap(final Object object, final JSONConfig jsonConfig) { - if (object == null) { - return null; + // null和自定义对象原样存储 + if (null == object || null != InternalJSONUtil.getValueWriter(object)) { + return object; } + if (object instanceof JSON // || object instanceof JSONString // || object instanceof CharSequence // @@ -369,7 +372,44 @@ public final class InternalJSONUtil { return rawHashMap; } + /** + * 根据值类型获取{@link JSONValueWriter},首先判断对象是否实现了{@link JSONValueWriter}接口
    + * 如果未实现从{@link GlobalValueWriterMapping}中查找全局的writer,否则返回null。 + * + * @param value 值 + * @param 值类型 + * @return {@link JSONValueWriter} + */ + @SuppressWarnings("unchecked") + public static JSONValueWriter getValueWriter(final T value) { + if (value instanceof JSONValueWriter) { + return (JSONValueWriter) value; + } + // 全局自定义序列化,支持null的自定义写出 + return (JSONValueWriter) GlobalValueWriterMapping.get(null == value ? null : value.getClass()); + } + + /** + * 根据目标类型,获取对应的{@link JSONDeserializer},首先判断是否实现了{@link JSONDeserializer}接口
    + * 如果未实现从{@link GlobalSerializeMapping}中查找全局的{@link JSONDeserializer},否则返回null + * + * @param targetType 目标类型 + * @param 目标类型 + * @return {@link JSONDeserializer} + */ + @SuppressWarnings("unchecked") + public static JSONDeserializer getDeserializer(final Type targetType) { + final Class rawType = (Class) TypeUtil.getClass(targetType); + if (null != rawType && JSONDeserializer.class.isAssignableFrom(rawType)) { + return (JSONDeserializer) ConstructorUtil.newInstanceIfPossible(rawType); + } + + // 全局自定义反序列化(优先级低于实现JSONDeserializer接口) + return (JSONDeserializer) GlobalSerializeMapping.getDeserializer(targetType); + } + // --------------------------------------------------------------------------------------------- Private method start + /** * 对所有双引号做转义处理(使用双反斜杠做转义)
    * 为了能在HTML中较好的显示,会将</转义为<\/
    diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java b/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java index bfcffeb5f..c550c7b37 100755 --- a/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java @@ -1,19 +1,11 @@ package cn.hutool.json; import cn.hutool.core.comparator.CompareUtil; -import cn.hutool.core.convert.Convert; import cn.hutool.core.convert.Converter; -import cn.hutool.core.convert.impl.DateConverter; -import cn.hutool.core.convert.impl.TemporalAccessorConverter; -import cn.hutool.core.reflect.TypeUtil; -import cn.hutool.core.text.StrUtil; import cn.hutool.json.convert.JSONConverter; -import cn.hutool.json.serialize.JSONString; import java.io.Serializable; -import java.time.temporal.TemporalAccessor; import java.util.Comparator; -import java.util.Date; /** * JSON配置项 @@ -59,35 +51,7 @@ public class JSONConfig implements Serializable { /** * 自定义的类型转换器,用于在getXXX操作中自动转换类型 */ - private Converter converter = (type, value)->{ - if(null == value){ - return null; - } - if(value instanceof JSONString){ - // 被JSONString包装的对象,获取其原始类型 - value = ((JSONString) value).getRaw(); - } - - final Class rawType = TypeUtil.getClass(type); - if(null == rawType){ - return value; - } - if(JSON.class.isAssignableFrom(rawType)){ - return JSONConverter.INSTANCE.toJSON(value); - } - if(Date.class.isAssignableFrom(rawType) || TemporalAccessor.class.isAssignableFrom(rawType)){ - // 日期转换,支持自定义日期格式 - final String format = getDateFormat(); - if (StrUtil.isNotBlank(format)) { - if (Date.class.isAssignableFrom(rawType)) { - return new DateConverter(format).convert(type, value); - } else { - return new TemporalAccessorConverter(format).convert(type, value); - } - } - } - return Convert.convertWithCheck(type, value, null, isIgnoreError()); - }; + private Converter converter = JSONConverter.of(this); /** * 创建默认的配置项 diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONParser.java b/hutool-json/src/main/java/cn/hutool/json/JSONParser.java index a82d126a1..ab5423817 100755 --- a/hutool-json/src/main/java/cn/hutool/json/JSONParser.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONParser.java @@ -2,6 +2,7 @@ package cn.hutool.json; import cn.hutool.core.lang.mutable.Mutable; import cn.hutool.core.lang.mutable.MutableEntry; +import cn.hutool.core.util.CharUtil; import java.util.function.Predicate; @@ -80,7 +81,7 @@ public class JSONParser { switch (tokener.nextClean()) { case ';': - case ',': + case CharUtil.COMMA: if (tokener.nextClean() == '}') { // issue#2380 // 尾后逗号(Trailing Commas),JSON中虽然不支持,但是ECMAScript 2017支持,此处做兼容。 @@ -111,7 +112,7 @@ public class JSONParser { if (x.nextClean() != ']') { x.back(); for (; ; ) { - if (x.nextClean() == ',') { + if (x.nextClean() == CharUtil.COMMA) { x.back(); jsonArray.addRaw(null, predicate); } else { @@ -119,7 +120,7 @@ public class JSONParser { jsonArray.addRaw(x.nextValue(), predicate); } switch (x.nextClean()) { - case ',': + case CharUtil.COMMA: if (x.nextClean() == ']') { return; } diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONTokener.java b/hutool-json/src/main/java/cn/hutool/json/JSONTokener.java index 22bd82894..0a9d43fe0 100755 --- a/hutool-json/src/main/java/cn/hutool/json/JSONTokener.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONTokener.java @@ -71,6 +71,7 @@ public class JSONTokener { * * @param inputStream InputStream * @param config JSON配置 + * @throws JSONException JSON异常,包装IO异常 */ public JSONTokener(final InputStream inputStream, final JSONConfig config) throws JSONException { this(IoUtil.getUtf8Reader(inputStream), config); @@ -89,6 +90,8 @@ public class JSONTokener { /** * 将标记回退到第一个字符,重新开始解析新的JSON + * + * @throws JSONException JSON异常,包装IO异常 */ public void back() throws JSONException { if (this.usePrevious || this.index <= 0) { @@ -111,6 +114,7 @@ public class JSONTokener { * 源字符串是否有更多的字符 * * @return 如果未达到结尾返回true,否则false + * @throws JSONException JSON异常,包装IO异常 */ public boolean more() throws JSONException { this.next(); @@ -272,11 +276,11 @@ public class JSONTokener { } /** - * Get the text up but not including the specified character or the end of line, whichever comes first.
    * 获得从当前位置直到分隔符(不包括分隔符)或行尾的的所有字符。 * * @param delimiter 分隔符 * @return 字符串 + * @throws JSONException JSON异常,包装IO异常 */ public String nextTo(final char delimiter) throws JSONException { final StringBuilder sb = new StringBuilder(); @@ -297,6 +301,7 @@ public class JSONTokener { * * @param delimiters A set of delimiter characters. * @return A string, trimmed. + * @throws JSONException JSON异常,包装IO异常 */ public String nextTo(final String delimiters) throws JSONException { char c; @@ -314,9 +319,9 @@ public class JSONTokener { } /** - * 获得下一个值,值类型可以是Boolean, Double, Integer, JSONArray, JSONObject, Long, or String, or the JSONObject.NULL + * 获得下一个值,值类型可以是Boolean, Double, Integer, JSONArray, JSONObject, Long, or String * - * @return Boolean, Double, Integer, JSONArray, JSONObject, Long, or String, or the JSONObject.NULL + * @return Boolean, Double, Integer, JSONArray, JSONObject, Long, or String * @throws JSONException 语法错误 */ public Object nextValue() throws JSONException { @@ -360,6 +365,7 @@ public class JSONTokener { * * @param to 需要定位的字符 * @return 定位的字符,如果字符未找到返回0 + * @throws JSONException IO异常 */ public char skipTo(final char to) throws JSONException { char c; @@ -378,8 +384,8 @@ public class JSONTokener { return c; } } while (c != to); - } catch (final IOException exception) { - throw new JSONException(exception); + } catch (final IOException e) { + throw new JSONException(e); } this.back(); return c; @@ -396,43 +402,6 @@ public class JSONTokener { return new JSONException(message + this); } - /** - * 转为 {@link JSONArray} - * - * @return {@link JSONArray} - */ - public JSONArray toJSONArray() { - final JSONArray jsonArray = new JSONArray(this.config); - if (this.nextClean() != '[') { - throw this.syntaxError("A JSONArray text must start with '['"); - } - if (this.nextClean() != ']') { - this.back(); - while (true) { - if (this.nextClean() == ',') { - this.back(); - jsonArray.add(null); - } else { - this.back(); - jsonArray.add(this.nextValue()); - } - switch (this.nextClean()) { - case ',': - if (this.nextClean() == ']') { - return jsonArray; - } - this.back(); - break; - case ']': - return jsonArray; - default: - throw this.syntaxError("Expected a ',' or ']'"); - } - } - } - return jsonArray; - } - /** * Make a printable string of this JSONTokener. * diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java b/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java index 4f52ded37..55d71d46b 100755 --- a/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java @@ -10,12 +10,11 @@ import cn.hutool.json.serialize.GlobalSerializeMapping; import cn.hutool.json.serialize.JSONArraySerializer; import cn.hutool.json.serialize.JSONDeserializer; import cn.hutool.json.serialize.JSONObjectSerializer; +import cn.hutool.json.writer.JSONValueWriter; +import cn.hutool.json.writer.JSONWriter; import cn.hutool.json.xml.JSONXMLUtil; -import java.io.File; -import java.io.IOException; -import java.io.Reader; -import java.io.Writer; +import java.io.*; import java.lang.reflect.Type; import java.nio.charset.Charset; import java.util.List; @@ -296,7 +295,18 @@ public class JSONUtil { * @return JSON字符串 * @since 5.7.12 */ + @SuppressWarnings({"unchecked", "rawtypes"}) public static String toJsonStr(final Object obj, final JSONConfig jsonConfig) { + // 自定义规则,优先级高于全局规则 + final JSONValueWriter valueWriter = InternalJSONUtil.getValueWriter(obj); + if(null != valueWriter){ + final StringWriter stringWriter = new StringWriter(); + final JSONWriter jsonWriter = JSONWriter.of(stringWriter, 0, 0, null); + // 用户对象自定义实现了JSONValueWriter接口,理解为需要自定义输出 + valueWriter.write(jsonWriter, obj); + return stringWriter.toString(); + } + if (null == obj) { return null; } diff --git a/hutool-json/src/main/java/cn/hutool/json/convert/JSONConverter.java b/hutool-json/src/main/java/cn/hutool/json/convert/JSONConverter.java index a7badde32..253258b0a 100644 --- a/hutool-json/src/main/java/cn/hutool/json/convert/JSONConverter.java +++ b/hutool-json/src/main/java/cn/hutool/json/convert/JSONConverter.java @@ -2,39 +2,33 @@ package cn.hutool.json.convert; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.bean.copier.BeanCopier; +import cn.hutool.core.convert.Convert; import cn.hutool.core.convert.ConvertException; import cn.hutool.core.convert.Converter; import cn.hutool.core.convert.RegisterConverter; -import cn.hutool.core.convert.impl.ArrayConverter; -import cn.hutool.core.convert.impl.CollectionConverter; -import cn.hutool.core.convert.impl.MapConverter; +import cn.hutool.core.convert.impl.*; import cn.hutool.core.map.MapWrapper; import cn.hutool.core.reflect.ConstructorUtil; import cn.hutool.core.reflect.TypeReference; import cn.hutool.core.reflect.TypeUtil; import cn.hutool.core.text.StrUtil; import cn.hutool.core.util.ArrayUtil; -import cn.hutool.json.InternalJSONUtil; -import cn.hutool.json.JSON; -import cn.hutool.json.JSONArray; -import cn.hutool.json.JSONConfig; -import cn.hutool.json.JSONException; -import cn.hutool.json.JSONObject; -import cn.hutool.json.JSONUtil; -import cn.hutool.json.serialize.GlobalSerializeMapping; +import cn.hutool.json.*; import cn.hutool.json.serialize.JSONDeserializer; +import cn.hutool.json.serialize.JSONString; import java.lang.reflect.Type; +import java.time.temporal.TemporalAccessor; import java.util.Collection; +import java.util.Date; import java.util.Iterator; import java.util.Map; /** * JSON转换器,实现Object对象转换为{@link JSON},支持的对象: *
      - *
    • String: 转换为相应的对象
    • - *
    • Array、Iterable、Iterator:转换为JSONArray
    • - *
    • Bean对象:转为JSONObject
    • + *
    • 任意支持的对象,转换为JSON
    • + *
    • JSOn转换为指定对象Bean
    • *
    * * @author looly @@ -73,27 +67,40 @@ public class JSONConverter implements Converter { } @Override - public Object convert(Type targetType, final Object obj) throws ConvertException { - if (null == obj) { + public Object convert(Type targetType, Object value) throws ConvertException { + if (null == value) { return null; } - - // 对象转JSON - if (targetType instanceof JSON) { - return toJSON(obj); + if (value instanceof JSONString) { + // 被JSONString包装的对象,获取其原始类型 + value = ((JSONString) value).getRaw(); } // JSON转对象 - if (obj instanceof JSON) { + if (value instanceof JSON) { if (targetType instanceof TypeReference) { + // 还原原始类型 targetType = ((TypeReference) targetType).getType(); } - return toBean(targetType, (JSON) obj); + return toBean(targetType, (JSON) value); } - // 无法转换 - throw new JSONException("Can not convert from {}: [{}] to [{}]", - obj.getClass().getName(), obj, targetType.getTypeName()); + // 对象转JSON + final Class targetClass = TypeUtil.getClass(targetType); + if(null != targetClass){ + if (JSON.class.isAssignableFrom(targetClass)) { + return toJSON(value); + } + // 自定义日期格式 + if(Date.class.isAssignableFrom(targetClass) || TemporalAccessor.class.isAssignableFrom(targetClass)){ + final Object date = toDateWithFormat(targetClass, value); + if(null != date){ + return date; + } + } + } + + return Convert.convertWithCheck(targetType, value, null, config.isIgnoreError()); } /** @@ -127,22 +134,30 @@ public class JSONConverter implements Converter { return json; } + // ----------------------------------------------------------- Private method start + + /** + * JSON转Bean + * + * @param 目标类型 + * @param targetType 目标类型, + * @param json JSON + * @return bean + */ @SuppressWarnings("unchecked") private T toBean(final Type targetType, final JSON json) { - final Class rawType = (Class) TypeUtil.getClass(targetType); - if(null != rawType && JSONDeserializer.class.isAssignableFrom(rawType)){ - return (T) JSONDeserializerConverter.INSTANCE.convert(targetType, json); - } - // 全局自定义反序列化(优先级低于实现JSONDeserializer接口) - final JSONDeserializer deserializer = GlobalSerializeMapping.getDeserializer(targetType); + // 自定义对象反序列化 + final JSONDeserializer deserializer = InternalJSONUtil.getDeserializer(targetType); if (null != deserializer) { return (T) deserializer.deserialize(json); } - // 其他转换不支持非Class的泛型类型 + final Class rawType = (Class) TypeUtil.getClass(targetType); if (null == rawType) { - throw new JSONException("Can not get class from type: {}", targetType); + // 当目标类型不确定时,返回原JSON + return (T) json; + //throw new JSONException("Can not get class from type: {}", targetType); } // 特殊类型转换,包括Collection、Map、强转、Array等 final T result = toSpecial(targetType, rawType, json); @@ -164,7 +179,7 @@ public class JSONConverter implements Converter { } // 跳过异常时返回null - if(json.getConfig().isIgnoreError()){ + if (json.getConfig().isIgnoreError()) { return null; } @@ -173,8 +188,6 @@ public class JSONConverter implements Converter { json.getClass().getName(), json, targetType.getTypeName()); } - // ----------------------------------------------------------- Private method start - /** * 特殊类型转换
    * 包括: @@ -220,5 +233,18 @@ public class JSONConverter implements Converter { // 表示非需要特殊转换的对象 return null; } + + private Object toDateWithFormat(final Class targetClass, final Object value){ + // 日期转换,支持自定义日期格式 + final String format = config.getDateFormat(); + if (StrUtil.isNotBlank(format)) { + if (Date.class.isAssignableFrom(targetClass)) { + return new DateConverter(format).convert(targetClass, value); + } else { + return new TemporalAccessorConverter(format).convert(targetClass, value); + } + } + return null; + } // ----------------------------------------------------------- Private method end } diff --git a/hutool-json/src/main/java/cn/hutool/json/convert/JSONDeserializerConverter.java b/hutool-json/src/main/java/cn/hutool/json/convert/JSONDeserializerConverter.java deleted file mode 100644 index 9265a100a..000000000 --- a/hutool-json/src/main/java/cn/hutool/json/convert/JSONDeserializerConverter.java +++ /dev/null @@ -1,36 +0,0 @@ -package cn.hutool.json.convert; - -import cn.hutool.core.convert.AbstractConverter; -import cn.hutool.core.convert.ConvertException; -import cn.hutool.core.reflect.ConstructorUtil; -import cn.hutool.json.JSON; -import cn.hutool.json.serialize.JSONDeserializer; - -/** - * 实现了{@link JSONDeserializer}接口的Bean对象转换器,用于将指定JSON转换为JSONDeserializer子对象。 - * - * @author looly - * @since 6.0.0 - */ -public class JSONDeserializerConverter extends AbstractConverter { - private static final long serialVersionUID = 1L; - - /** - * 单例 - */ - public static final JSONDeserializerConverter INSTANCE = new JSONDeserializerConverter(); - - @Override - protected Object convertInternal(final Class targetClass, final Object value) { - // 自定义反序列化 - if (value instanceof JSON) { - final JSONDeserializer target = (JSONDeserializer) ConstructorUtil.newInstanceIfPossible(targetClass); - if (null == target) { - throw new ConvertException("Can not instance target: [{}]", targetClass); - } - return target.deserialize((JSON) value); - } - - throw new ConvertException("JSONDeserializer bean must be convert from JSON!"); - } -} diff --git a/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java b/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java index 3ccad807d..753cf4c41 100644 --- a/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java +++ b/hutool-json/src/main/java/cn/hutool/json/serialize/GlobalSerializeMapping.java @@ -1,6 +1,8 @@ package cn.hutool.json.serialize; import cn.hutool.core.map.SafeConcurrentHashMap; +import cn.hutool.core.reflect.NullType; +import cn.hutool.core.util.ObjUtil; import cn.hutool.json.JSON; import java.lang.reflect.Type; @@ -59,19 +61,6 @@ public class GlobalSerializeMapping { putInternal(type, serializer); } - /** - * 加入自定义的序列化器 - * - * @param type 对象类型 - * @param serializer 序列化器实现 - */ - synchronized private static void putInternal(final Type type, final JSONSerializer serializer) { - if (null == serializerMap) { - serializerMap = new ConcurrentHashMap<>(); - } - serializerMap.put(type, serializer); - } - /** * 加入自定义的反序列化器 * @@ -82,7 +71,7 @@ public class GlobalSerializeMapping { if (null == deserializerMap) { deserializerMap = new ConcurrentHashMap<>(); } - deserializerMap.put(type, deserializer); + deserializerMap.put(ObjUtil.defaultIfNull(type, NullType.INSTANCE), deserializer); } /** @@ -95,7 +84,7 @@ public class GlobalSerializeMapping { if (null == serializerMap) { return null; } - return serializerMap.get(type); + return serializerMap.get(ObjUtil.defaultIfNull(type, NullType.INSTANCE)); } /** @@ -108,6 +97,19 @@ public class GlobalSerializeMapping { if (null == deserializerMap) { return null; } - return deserializerMap.get(type); + return deserializerMap.get(ObjUtil.defaultIfNull(type, NullType.INSTANCE)); + } + + /** + * 加入自定义的序列化器 + * + * @param type 对象类型 + * @param serializer 序列化器实现 + */ + synchronized private static void putInternal(final Type type, final JSONSerializer serializer) { + if (null == serializerMap) { + serializerMap = new ConcurrentHashMap<>(); + } + serializerMap.put(ObjUtil.defaultIfNull(type, NullType.INSTANCE), serializer); } } diff --git a/hutool-json/src/main/java/cn/hutool/json/serialize/package-info.java b/hutool-json/src/main/java/cn/hutool/json/serialize/package-info.java new file mode 100644 index 000000000..7adc2a7b8 --- /dev/null +++ b/hutool-json/src/main/java/cn/hutool/json/serialize/package-info.java @@ -0,0 +1,4 @@ +/** + * JSON序列化和反序列化,提供对象和JSON之间的转换 + */ +package cn.hutool.json.serialize; diff --git a/hutool-json/src/main/java/cn/hutool/json/writer/GlobalValueWriterMapping.java b/hutool-json/src/main/java/cn/hutool/json/writer/GlobalValueWriterMapping.java new file mode 100644 index 000000000..d36d5e802 --- /dev/null +++ b/hutool-json/src/main/java/cn/hutool/json/writer/GlobalValueWriterMapping.java @@ -0,0 +1,44 @@ +package cn.hutool.json.writer; + +import cn.hutool.core.map.SafeConcurrentHashMap; +import cn.hutool.core.reflect.NullType; +import cn.hutool.core.util.ObjUtil; + +import java.lang.reflect.Type; +import java.util.Map; + +/** + * 全局自定义对象写出
    + * 用户通过此全局定义,可针对某些特殊对象 + * + * @author looly + * @since 6.0.0 + */ +public class GlobalValueWriterMapping { + + private static final Map> writerMap; + + static { + writerMap = new SafeConcurrentHashMap<>(); + } + + /** + * 加入自定义的对象值写出规则 + * + * @param type 对象类型 + * @param writer 自定义对象写出实现 + */ + public static void put(final Type type, final JSONValueWriter writer) { + writerMap.put(ObjUtil.defaultIfNull(type, NullType.INSTANCE), writer); + } + + /** + * 获取自定义对象值写出规则 + * + * @param type 对象类型 + * @return 自定义的 {@link JSONValueWriter} + */ + public static JSONValueWriter get(final Type type) { + return writerMap.get(ObjUtil.defaultIfNull(type, NullType.INSTANCE)); + } +} diff --git a/hutool-json/src/main/java/cn/hutool/json/writer/JSONValueWriter.java b/hutool-json/src/main/java/cn/hutool/json/writer/JSONValueWriter.java index cd3c7b722..e35f9420b 100755 --- a/hutool-json/src/main/java/cn/hutool/json/writer/JSONValueWriter.java +++ b/hutool-json/src/main/java/cn/hutool/json/writer/JSONValueWriter.java @@ -1,5 +1,7 @@ package cn.hutool.json.writer; +import java.io.IOException; + /** * JSON的值自定义写出 * @@ -14,6 +16,7 @@ public interface JSONValueWriter { * * @param writer {@link JSONWriter} * @param value 被写出的值 + * @throws IOException IO异常 */ void write(JSONWriter writer, T value); } diff --git a/hutool-json/src/main/java/cn/hutool/json/writer/JSONWriter.java b/hutool-json/src/main/java/cn/hutool/json/writer/JSONWriter.java index d6e1e93c5..b3b99d021 100755 --- a/hutool-json/src/main/java/cn/hutool/json/writer/JSONWriter.java +++ b/hutool-json/src/main/java/cn/hutool/json/writer/JSONWriter.java @@ -4,6 +4,7 @@ import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.lang.mutable.MutableEntry; import cn.hutool.core.text.StrUtil; import cn.hutool.core.util.CharUtil; +import cn.hutool.core.util.ObjUtil; import cn.hutool.json.InternalJSONUtil; import cn.hutool.json.JSON; import cn.hutool.json.JSONConfig; @@ -76,7 +77,7 @@ public class JSONWriter extends Writer { this.writer = writer; this.indentFactor = indentFactor; this.indent = indent; - this.config = config; + this.config = ObjUtil.defaultIfNull(config, JSONConfig.of()); } /** @@ -298,12 +299,21 @@ public class JSONWriter extends Writer { * @param predicate 过滤修改器 * @return this */ + @SuppressWarnings({"unchecked", "rawtypes"}) private JSONWriter writeObjValue(final Object value, final Predicate> predicate) { final int indent = indentFactor + this.indent; + + // 自定义规则 + final JSONValueWriter valueWriter = InternalJSONUtil.getValueWriter(value); + if(null != valueWriter){ + valueWriter.write(this, value); + return this; + } + if (value == null) { //noinspection resource writeRaw(StrUtil.NULL); - } else if (value instanceof JSON) { + }else if (value instanceof JSON) { ((JSON) value).write(writer, indentFactor, indent, predicate); } else if (value instanceof Number) { NumberValueWriter.INSTANCE.write(this, (Number) value); diff --git a/hutool-json/src/test/java/cn/hutool/json/writer/GlobalValueWriterMappingTest.java b/hutool-json/src/test/java/cn/hutool/json/writer/GlobalValueWriterMappingTest.java new file mode 100644 index 000000000..2243a32f5 --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/writer/GlobalValueWriterMappingTest.java @@ -0,0 +1,67 @@ +package cn.hutool.json.writer; + +import cn.hutool.core.convert.Converter; +import cn.hutool.json.JSONConfig; +import cn.hutool.json.JSONUtil; +import lombok.Data; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +public class GlobalValueWriterMappingTest { + + @Before + public void init(){ + GlobalValueWriterMapping.put(CustomSubBean.class, (JSONValueWriter) (writer, value) -> { + writer.writeRaw(String.valueOf(value.getId())); + }); + } + + @Test + public void customWriteTest(){ + final CustomSubBean customBean = new CustomSubBean(); + customBean.setId(12); + customBean.setName("aaa"); + final String s = JSONUtil.toJsonStr(customBean); + Assert.assertEquals("12", s); + } + + @Test + public void customWriteSubTest(){ + final CustomSubBean customSubBean = new CustomSubBean(); + customSubBean.setId(12); + customSubBean.setName("aaa"); + final CustomBean customBean = new CustomBean(); + customBean.setId(1); + customBean.setSub(customSubBean); + + final String s = JSONUtil.toJsonStr(customBean); + Assert.assertEquals("{\"id\":1,\"sub\":12}", s); + + // 自定义转换 + final JSONConfig jsonConfig = JSONConfig.of(); + final Converter converter = jsonConfig.getConverter(); + jsonConfig.setConverter((targetType, value) -> { + if(targetType == CustomSubBean.class){ + final CustomSubBean subBean = new CustomSubBean(); + subBean.setId((Integer) value); + return subBean; + } + return converter.convert(targetType, value); + }); + final CustomBean customBean1 = JSONUtil.parseObj(s, jsonConfig).toBean(CustomBean.class); + Assert.assertEquals(12, customBean1.getSub().getId()); + } + + @Data + static class CustomSubBean { + private int id; + private String name; + } + + @Data + static class CustomBean{ + private int id; + private CustomSubBean sub; + } +} From 053ebf7e027c3bca2403ba378c4075fbc9b74862 Mon Sep 17 00:00:00 2001 From: Looly Date: Sun, 9 Oct 2022 01:32:08 +0800 Subject: [PATCH 09/29] fix test --- .../hutool/core/stream/AbstractEnhancedWrappedStreamTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hutool-core/src/test/java/cn/hutool/core/stream/AbstractEnhancedWrappedStreamTest.java b/hutool-core/src/test/java/cn/hutool/core/stream/AbstractEnhancedWrappedStreamTest.java index 88db081cb..0879e288d 100644 --- a/hutool-core/src/test/java/cn/hutool/core/stream/AbstractEnhancedWrappedStreamTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/stream/AbstractEnhancedWrappedStreamTest.java @@ -301,8 +301,8 @@ public class AbstractEnhancedWrappedStreamTest { elements2.add(t); indexes2.add(i); }).exec(); - Assert.assertEquals(new HashSet<>(asList(1, null, 2)), elements2); - Assert.assertEquals(new HashSet<>(asList(-1, -1, -1)), indexes2); + Assert.assertEquals(new HashSet<>(asList(1, null, 2)).toString(), elements2.toString()); + Assert.assertEquals(new HashSet<>(asList(-1, -1, -1)).toString(), indexes2.toString()); } @Test From 36aa0537ba0e5a79a0bf40f137e9d65281bcaa9b Mon Sep 17 00:00:00 2001 From: Looly Date: Sun, 9 Oct 2022 01:34:17 +0800 Subject: [PATCH 10/29] fix test --- .../src/main/java/cn/hutool/json/writer/JSONValueWriter.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/hutool-json/src/main/java/cn/hutool/json/writer/JSONValueWriter.java b/hutool-json/src/main/java/cn/hutool/json/writer/JSONValueWriter.java index e35f9420b..cd3c7b722 100755 --- a/hutool-json/src/main/java/cn/hutool/json/writer/JSONValueWriter.java +++ b/hutool-json/src/main/java/cn/hutool/json/writer/JSONValueWriter.java @@ -1,7 +1,5 @@ package cn.hutool.json.writer; -import java.io.IOException; - /** * JSON的值自定义写出 * @@ -16,7 +14,6 @@ public interface JSONValueWriter { * * @param writer {@link JSONWriter} * @param value 被写出的值 - * @throws IOException IO异常 */ void write(JSONWriter writer, T value); } From 34fe9f577f0d25cd489344917ffc6a43a7e6fdae Mon Sep 17 00:00:00 2001 From: Looly Date: Sun, 9 Oct 2022 01:46:19 +0800 Subject: [PATCH 11/29] fix test --- .../core/stream/AbstractEnhancedWrappedStreamTest.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/hutool-core/src/test/java/cn/hutool/core/stream/AbstractEnhancedWrappedStreamTest.java b/hutool-core/src/test/java/cn/hutool/core/stream/AbstractEnhancedWrappedStreamTest.java index 0879e288d..b3b3a7737 100644 --- a/hutool-core/src/test/java/cn/hutool/core/stream/AbstractEnhancedWrappedStreamTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/stream/AbstractEnhancedWrappedStreamTest.java @@ -295,14 +295,14 @@ public class AbstractEnhancedWrappedStreamTest { Assert.assertEquals(asList(1, 2, 3), elements); Assert.assertEquals(asList(0, 1, 2), indexes); - final Set elements2 = new HashSet<>(); - final Set indexes2 = new HashSet<>(); + final Set elements2 = Collections.synchronizedSet(new HashSet<>()); + final Set indexes2 = Collections.synchronizedSet(new HashSet<>()); wrap(1, 2, null).parallel().peekIdx((t, i) -> { elements2.add(t); indexes2.add(i); }).exec(); - Assert.assertEquals(new HashSet<>(asList(1, null, 2)).toString(), elements2.toString()); - Assert.assertEquals(new HashSet<>(asList(-1, -1, -1)).toString(), indexes2.toString()); + Assert.assertEquals(new HashSet<>(asList(1, null, 2)), elements2); + Assert.assertEquals(new HashSet<>(asList(-1, -1, -1)), indexes2); } @Test From cd7343516156f0b61f5131e7496956cbe934e1a4 Mon Sep 17 00:00:00 2001 From: Looly Date: Sun, 9 Oct 2022 01:50:07 +0800 Subject: [PATCH 12/29] fix test --- hutool-http/src/test/java/cn/hutool/http/DownloadTest.java | 2 +- hutool-http/src/test/java/cn/hutool/http/IssueI5TPSYTest.java | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/hutool-http/src/test/java/cn/hutool/http/DownloadTest.java b/hutool-http/src/test/java/cn/hutool/http/DownloadTest.java index f66d7e278..888f5d765 100644 --- a/hutool-http/src/test/java/cn/hutool/http/DownloadTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/DownloadTest.java @@ -193,7 +193,7 @@ public class DownloadTest { } @Test - //@Ignore + @Ignore public void downloadTeamViewerTest() throws IOException { // 此URL有3次重定向, 需要请求4次 final String url = "https://download.teamviewer.com/download/TeamViewer_Setup_x64.exe"; diff --git a/hutool-http/src/test/java/cn/hutool/http/IssueI5TPSYTest.java b/hutool-http/src/test/java/cn/hutool/http/IssueI5TPSYTest.java index adaffc3ed..3de700e58 100755 --- a/hutool-http/src/test/java/cn/hutool/http/IssueI5TPSYTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/IssueI5TPSYTest.java @@ -1,12 +1,13 @@ package cn.hutool.http; import cn.hutool.core.lang.Console; +import org.junit.Ignore; import org.junit.Test; public class IssueI5TPSYTest { @Test - //@Ignore + @Ignore public void redirectTest() { final String url = "https://bsxt.gdzwfw.gov.cn/UnifiedReporting/auth/newIndex"; final HttpResponse res = HttpUtil.createGet(url).setFollowRedirects(true) From ee3022b4b3353713b12fe5273975c0b4e4e0e8b7 Mon Sep 17 00:00:00 2001 From: Looly Date: Sun, 9 Oct 2022 18:31:11 +0800 Subject: [PATCH 13/29] fix ext name bug --- .../main/java/cn/hutool/core/io/file/FileNameUtil.java | 8 ++++++++ .../java/cn/hutool/core/io/file/FileNameUtilTest.java | 6 ++++++ 2 files changed, 14 insertions(+) diff --git a/hutool-core/src/main/java/cn/hutool/core/io/file/FileNameUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/file/FileNameUtil.java index 7cf2d7b40..c2b6c818e 100755 --- a/hutool-core/src/main/java/cn/hutool/core/io/file/FileNameUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/file/FileNameUtil.java @@ -179,6 +179,14 @@ public class FileNameUtil { if (0 == len) { return fileName; } + + //issue#2642,多级扩展名的主文件名 + for (final CharSequence specialSuffix : SPECIAL_SUFFIX) { + if(StrUtil.endWith(fileName, "." + specialSuffix)){ + return StrUtil.subPre(fileName, len - specialSuffix.length() - 1); + } + } + if (CharUtil.isFileSeparator(fileName.charAt(len - 1))) { len--; } diff --git a/hutool-core/src/test/java/cn/hutool/core/io/file/FileNameUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/io/file/FileNameUtilTest.java index 706cc4492..344ba754f 100755 --- a/hutool-core/src/test/java/cn/hutool/core/io/file/FileNameUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/io/file/FileNameUtilTest.java @@ -12,4 +12,10 @@ public class FileNameUtilTest { name = FileNameUtil.cleanInvalid("\r1\r\n2\n"); Assert.assertEquals("12", name); } + + @Test + public void mainNameTest() { + final String s = FileNameUtil.mainName("abc.tar.gz"); + Assert.assertEquals("abc", s); + } } From 5fceca0bbc93fda3f03c41862566e35aa5867401 Mon Sep 17 00:00:00 2001 From: Looly Date: Sun, 9 Oct 2022 19:12:02 +0800 Subject: [PATCH 14/29] fix code --- CHANGELOG.md | 5 ++--- .../src/main/java/cn/hutool/poi/excel/cell/CellUtil.java | 5 +---- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c7f0577c4..44a90222b 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,15 +3,14 @@ ------------------------------------------------------------------------------------------------------------- -# 6.0.0.M1 (2022-08-27) +# 6.0.0.M1 (2022-10-09) ### 计划实现 * 【poi 】 PDF相关(基于PdfBox) * 【poi 】 HTML、DOCX转换相关 * 【poi 】 Markdown相关(如HTML转换等),基于commonmark-java * 【db 】 增加DDL封装 -* 【json 】 实现自定义的类型转换,不影响全局的转换器 -* 【core 】 Functional接口重新定义 +* 【poi 】 CellUtil.getCellIfMergedRegion考虑添加缓存支持,增加最大和最小范围判断,减少遍历 ### ❌不兼容特性 diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/CellUtil.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/CellUtil.java index 33dd684ae..8694d0082 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/CellUtil.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/CellUtil.java @@ -466,10 +466,7 @@ public class CellUtil { * @since 5.4.5 */ private static Cell getCellIfMergedRegion(final Sheet sheet, final int x, final int y) { - final int sheetMergeCount = sheet.getNumMergedRegions(); - CellRangeAddress ca; - for (int i = 0; i < sheetMergeCount; i++) { - ca = sheet.getMergedRegion(i); + for (final CellRangeAddress ca : sheet.getMergedRegions()) { if (ca.isInRange(y, x)) { return SheetUtil.getCell(sheet, ca.getFirstRow(), ca.getFirstColumn()); } From 66d5e4d3e9f1fe5fd2bc85738145d345381bcf95 Mon Sep 17 00:00:00 2001 From: Looly Date: Mon, 10 Oct 2022 22:06:49 +0800 Subject: [PATCH 15/29] add blank char --- hutool-core/src/main/java/cn/hutool/core/util/CharUtil.java | 4 +++- .../src/test/java/cn/hutool/core/util/CharUtilTest.java | 5 ++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/util/CharUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/CharUtil.java index 7663300f4..e585e901a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/CharUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/CharUtil.java @@ -261,7 +261,9 @@ public class CharUtil implements CharPool { || c == '\u202a' || c == '\u0000' // issue#I5UGSQ,Hangul Filler - || c == '\u3164'; + || c == '\u3164' + // Braille Pattern Blank + || c == '\u2800'; } /** diff --git a/hutool-core/src/test/java/cn/hutool/core/util/CharUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/CharUtilTest.java index 1fb388a69..a9f14d892 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/CharUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/CharUtilTest.java @@ -58,7 +58,10 @@ public class CharUtilTest { @Test public void issueI5UGSQTest(){ - final Character c = '\u3164'; + char c = '\u3164'; + Assert.assertTrue(CharUtil.isBlankChar(c)); + + c = '\u2800'; Assert.assertTrue(CharUtil.isBlankChar(c)); } } From 7b7240aa4bb78cfa068185bb47eb61feaa5bacea Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 11 Oct 2022 23:04:55 +0800 Subject: [PATCH 16/29] add test --- .../cn/hutool/core/bean/Issue2649Test.java | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100755 hutool-core/src/test/java/cn/hutool/core/bean/Issue2649Test.java diff --git a/hutool-core/src/test/java/cn/hutool/core/bean/Issue2649Test.java b/hutool-core/src/test/java/cn/hutool/core/bean/Issue2649Test.java new file mode 100755 index 000000000..2b33ac169 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/bean/Issue2649Test.java @@ -0,0 +1,52 @@ +package cn.hutool.core.bean; + +import cn.hutool.core.date.StopWatch; +import cn.hutool.core.lang.Console; +import lombok.Data; +import org.junit.Ignore; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +public class Issue2649Test { + + @Test + @Ignore + public void toListTest() { + final List view1List = new ArrayList<>(); + for (int i = 0; i < 200; i++) { + final View1 view1 = new View1(); + view1.setA(String.valueOf(i)); + view1.setB(String.valueOf(i)); + for (int j = 0; j < 2; j++) { + final View2 view2 = new View2(); + view1.setA(String.valueOf(i)); + view1.setB(String.valueOf(i)); + view1.getViewList().add(view2); + } + view1List.add(view1); + } + + final StopWatch stopWatch = new StopWatch(); + stopWatch.start(); + @SuppressWarnings("unused") + final List view2s = BeanUtil.copyToList(view1List, View2.class); + stopWatch.stop(); + Console.log(stopWatch.getTotalTimeSeconds()); + } + + @Data + static class View1{ + private String a; + private String b; + private List viewList = new ArrayList<>(); + } + + @Data + static class View2{ + private String a; + private String b; + private List viewList = new ArrayList<>(); + } +} From 9eb29695bf91dc5da5a052f9b725adb5ea6ed327 Mon Sep 17 00:00:00 2001 From: Looly Date: Wed, 12 Oct 2022 00:05:31 +0800 Subject: [PATCH 17/29] fix listener bug --- .../cn/hutool/core/cache/impl/LRUCache.java | 9 +++++- .../hutool/core/map/FixedLinkedHashMap.java | 32 ++++++++++++++++--- .../cn/hutool/core/cache/LRUCacheTest.java | 14 ++++++++ 3 files changed, 49 insertions(+), 6 deletions(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/cache/impl/LRUCache.java b/hutool-core/src/main/java/cn/hutool/core/cache/impl/LRUCache.java index 9f4b8cb35..a25728b4a 100755 --- a/hutool-core/src/main/java/cn/hutool/core/cache/impl/LRUCache.java +++ b/hutool-core/src/main/java/cn/hutool/core/cache/impl/LRUCache.java @@ -1,5 +1,6 @@ package cn.hutool.core.cache.impl; +import cn.hutool.core.lang.mutable.Mutable; import cn.hutool.core.map.FixedLinkedHashMap; import java.util.Iterator; @@ -42,7 +43,13 @@ public class LRUCache extends ReentrantCache { this.timeout = timeout; //链表key按照访问顺序排序,调用get方法后,会将这次访问的元素移至头部 - cacheMap = new FixedLinkedHashMap<>(capacity); + final FixedLinkedHashMap, CacheObj> fixedLinkedHashMap = new FixedLinkedHashMap<>(capacity); + fixedLinkedHashMap.setRemoveListener(entry -> { + if(null != listener){ + listener.onRemove(entry.getKey().get(), entry.getValue().getValue()); + } + }); + cacheMap = fixedLinkedHashMap; } // ---------------------------------------------------------------- prune diff --git a/hutool-core/src/main/java/cn/hutool/core/map/FixedLinkedHashMap.java b/hutool-core/src/main/java/cn/hutool/core/map/FixedLinkedHashMap.java index 1ef475ae5..a66321378 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/FixedLinkedHashMap.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/FixedLinkedHashMap.java @@ -1,21 +1,28 @@ package cn.hutool.core.map; import java.util.LinkedHashMap; +import java.util.Map; +import java.util.function.Consumer; /** * 固定大小的{@link LinkedHashMap} 实现
    * 注意此类非线程安全,由于{@link #get(Object)}操作会修改链表的顺序结构,因此也不可以使用读写锁。 * - * @author looly - * * @param 键类型 * @param 值类型 + * @author looly */ public class FixedLinkedHashMap extends LinkedHashMap { private static final long serialVersionUID = -629171177321416095L; - /** 容量,超过此容量自动删除末尾元素 */ + /** + * 容量,超过此容量自动删除末尾元素 + */ private int capacity; + /** + * 移除监听 + */ + private Consumer> removeListener; /** * 构造 @@ -45,10 +52,25 @@ public class FixedLinkedHashMap extends LinkedHashMap { this.capacity = capacity; } + /** + * 设置自定义移除监听 + * + * @param removeListener 移除监听 + */ + public void setRemoveListener(final Consumer> removeListener) { + this.removeListener = removeListener; + } + @Override protected boolean removeEldestEntry(final java.util.Map.Entry eldest) { //当链表元素大于容量时,移除最老(最久未被使用)的元素 - return size() > this.capacity; + if (size() > this.capacity) { + if (null != removeListener) { + // 自定义监听 + removeListener.accept(eldest); + } + return true; + } + return false; } - } diff --git a/hutool-core/src/test/java/cn/hutool/core/cache/LRUCacheTest.java b/hutool-core/src/test/java/cn/hutool/core/cache/LRUCacheTest.java index 62664faa5..a981e4fd1 100755 --- a/hutool-core/src/test/java/cn/hutool/core/cache/LRUCacheTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/cache/LRUCacheTest.java @@ -1,6 +1,8 @@ package cn.hutool.core.cache; import cn.hutool.core.cache.impl.LRUCache; +import cn.hutool.core.lang.Console; +import cn.hutool.core.text.StrUtil; import cn.hutool.core.thread.ThreadUtil; import cn.hutool.core.util.RandomUtil; import org.junit.Assert; @@ -64,4 +66,16 @@ public class LRUCacheTest { } Assert.assertEquals("null123456789", sb2.toString()); } + + @Test + public void issue2647Test(){ + final LRUCache cache = CacheUtil.newLRUCache(3,1); + cache.setListener((key, value) -> Console.log("Start remove k-v, key:{}, value:{}", key, value)); + + for (int i = 0; i < 10; i++) { + cache.put(StrUtil.format("key-{}", i), i); + } + + Assert.assertEquals(3, cache.size()); + } } From 9ecc1406b88ddb8a3131e3193a25d22654a6ef0e Mon Sep 17 00:00:00 2001 From: Looly Date: Wed, 12 Oct 2022 00:13:48 +0800 Subject: [PATCH 18/29] fix test --- .../test/java/cn/hutool/core/cache/LRUCacheTest.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/hutool-core/src/test/java/cn/hutool/core/cache/LRUCacheTest.java b/hutool-core/src/test/java/cn/hutool/core/cache/LRUCacheTest.java index a981e4fd1..08243df95 100755 --- a/hutool-core/src/test/java/cn/hutool/core/cache/LRUCacheTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/cache/LRUCacheTest.java @@ -1,7 +1,6 @@ package cn.hutool.core.cache; import cn.hutool.core.cache.impl.LRUCache; -import cn.hutool.core.lang.Console; import cn.hutool.core.text.StrUtil; import cn.hutool.core.thread.ThreadUtil; import cn.hutool.core.util.RandomUtil; @@ -10,6 +9,7 @@ import org.junit.Ignore; import org.junit.Test; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicInteger; /** * 见:https://github.com/dromara/hutool/issues/1895
    @@ -69,13 +69,20 @@ public class LRUCacheTest { @Test public void issue2647Test(){ + final AtomicInteger removeCount = new AtomicInteger(); + final LRUCache cache = CacheUtil.newLRUCache(3,1); - cache.setListener((key, value) -> Console.log("Start remove k-v, key:{}, value:{}", key, value)); + cache.setListener((key, value) -> { + // 共移除7次 + removeCount.incrementAndGet(); + //Console.log("Start remove k-v, key:{}, value:{}", key, value); + }); for (int i = 0; i < 10; i++) { cache.put(StrUtil.format("key-{}", i), i); } + Assert.assertEquals(7, removeCount.get()); Assert.assertEquals(3, cache.size()); } } From 9ecd57889612ec4143c9b8f20bdef9cfe3188719 Mon Sep 17 00:00:00 2001 From: Looly Date: Wed, 12 Oct 2022 13:44:19 +0800 Subject: [PATCH 19/29] add qlexpress support --- hutool-extra/pom.xml | 13 +++++-- .../engine/qlexpress/QLExpressEngine.java | 38 +++++++++++++++++++ .../engine/qlexpress/package-info.java | 7 ++++ ...n.hutool.extra.expression.ExpressionEngine | 3 +- .../extra/expression/ExpressionUtilTest.java | 13 +++++++ 5 files changed, 70 insertions(+), 4 deletions(-) create mode 100755 hutool-extra/src/main/java/cn/hutool/extra/expression/engine/qlexpress/QLExpressEngine.java create mode 100755 hutool-extra/src/main/java/cn/hutool/extra/expression/engine/qlexpress/package-info.java diff --git a/hutool-extra/pom.xml b/hutool-extra/pom.xml index 51e42bd62..a7c4357e6 100755 --- a/hutool-extra/pom.xml +++ b/hutool-extra/pom.xml @@ -31,7 +31,7 @@ 3.8.0 5.1.1 4.0.1 - 2.7.2 + 2.7.4 3.3.0 @@ -430,7 +430,7 @@ com.googlecode.aviator aviator - 5.3.1 + 5.3.2 compile true @@ -465,7 +465,7 @@ org.springframework spring-expression - 5.3.22 + 5.3.23 compile true @@ -476,6 +476,13 @@ compile true + + com.alibaba + QLExpress + 3.3.0 + compile + true + org.apache.commons diff --git a/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/qlexpress/QLExpressEngine.java b/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/qlexpress/QLExpressEngine.java new file mode 100755 index 000000000..d941685d1 --- /dev/null +++ b/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/qlexpress/QLExpressEngine.java @@ -0,0 +1,38 @@ +package cn.hutool.extra.expression.engine.qlexpress; + +import cn.hutool.extra.expression.ExpressionEngine; +import cn.hutool.extra.expression.ExpressionException; +import com.ql.util.express.DefaultContext; +import com.ql.util.express.ExpressRunner; + +import java.util.Map; + +/** + * QLExpress引擎封装
    + * 见:https://github.com/alibaba/QLExpress + * + * @author looly + * @since 5.8.9 + */ +public class QLExpressEngine implements ExpressionEngine { + + private final ExpressRunner engine; + + /** + * 构造 + */ + public QLExpressEngine() { + engine = new ExpressRunner(); + } + + @Override + public Object eval(final String expression, final Map context) { + final DefaultContext defaultContext = new DefaultContext<>(); + defaultContext.putAll(context); + try { + return engine.execute(expression, defaultContext, null, true, false); + } catch (final Exception e) { + throw new ExpressionException(e); + } + } +} diff --git a/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/qlexpress/package-info.java b/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/qlexpress/package-info.java new file mode 100755 index 000000000..f3c4cf1e5 --- /dev/null +++ b/hutool-extra/src/main/java/cn/hutool/extra/expression/engine/qlexpress/package-info.java @@ -0,0 +1,7 @@ +/** + * QLExpress引擎封装
    + * 见:https://github.com/alibaba/QLExpress + * + * @author looly + */ +package cn.hutool.extra.expression.engine.qlexpress; diff --git a/hutool-extra/src/main/resources/META-INF/services/cn.hutool.extra.expression.ExpressionEngine b/hutool-extra/src/main/resources/META-INF/services/cn.hutool.extra.expression.ExpressionEngine index df7e3a18c..33aa4f4b6 100644 --- a/hutool-extra/src/main/resources/META-INF/services/cn.hutool.extra.expression.ExpressionEngine +++ b/hutool-extra/src/main/resources/META-INF/services/cn.hutool.extra.expression.ExpressionEngine @@ -3,4 +3,5 @@ cn.hutool.extra.expression.engine.jexl.JexlEngine cn.hutool.extra.expression.engine.mvel.MvelEngine cn.hutool.extra.expression.engine.jfireel.JfireELEngine cn.hutool.extra.expression.engine.spel.SpELEngine -cn.hutool.extra.expression.engine.rhino.RhinoEngine \ No newline at end of file +cn.hutool.extra.expression.engine.rhino.RhinoEngine +cn.hutool.extra.expression.engine.qlexpress.QLExpressEngine diff --git a/hutool-extra/src/test/java/cn/hutool/extra/expression/ExpressionUtilTest.java b/hutool-extra/src/test/java/cn/hutool/extra/expression/ExpressionUtilTest.java index 3499e9dad..fc169d55e 100755 --- a/hutool-extra/src/test/java/cn/hutool/extra/expression/ExpressionUtilTest.java +++ b/hutool-extra/src/test/java/cn/hutool/extra/expression/ExpressionUtilTest.java @@ -4,6 +4,7 @@ import cn.hutool.core.map.Dict; import cn.hutool.extra.expression.engine.jexl.JexlEngine; import cn.hutool.extra.expression.engine.jfireel.JfireELEngine; import cn.hutool.extra.expression.engine.mvel.MvelEngine; +import cn.hutool.extra.expression.engine.qlexpress.QLExpressEngine; import cn.hutool.extra.expression.engine.rhino.RhinoEngine; import cn.hutool.extra.expression.engine.spel.SpELEngine; import org.junit.Assert; @@ -95,4 +96,16 @@ public class ExpressionUtilTest { Assert.assertEquals(-143.8, (double)eval, 0); } + @Test + public void qlExpressTest(){ + final ExpressionEngine engine = new QLExpressEngine(); + + final Dict dict = Dict.of() + .set("a", 100.3) + .set("b", 45) + .set("c", -199.100); + final Object eval = engine.eval("a-(b-c)", dict); + Assert.assertEquals(-143.8, (double)eval, 0); + } + } From dff3dfec6833c3ff4185575ad4ad18a46a0a1314 Mon Sep 17 00:00:00 2001 From: Looly Date: Wed, 12 Oct 2022 14:42:43 +0800 Subject: [PATCH 20/29] fix bug --- .../hutool/core/bean/copier/MapToMapCopier.java | 4 ++++ .../java/cn/hutool/core/bean/BeanUtilTest.java | 15 +++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/hutool-core/src/main/java/cn/hutool/core/bean/copier/MapToMapCopier.java b/hutool-core/src/main/java/cn/hutool/core/bean/copier/MapToMapCopier.java index f0457a0e0..5fb51a00b 100755 --- a/hutool-core/src/main/java/cn/hutool/core/bean/copier/MapToMapCopier.java +++ b/hutool-core/src/main/java/cn/hutool/core/bean/copier/MapToMapCopier.java @@ -50,6 +50,10 @@ public class MapToMapCopier extends AbsCopier { return; } sValue = entry.getValue(); + // 忽略空值 + if (copyOptions.ignoreNullValue && sValue == null) { + return; + } final Object targetValue = target.get(sKey); // 非覆盖模式下,如果目标值存在,则跳过 diff --git a/hutool-core/src/test/java/cn/hutool/core/bean/BeanUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/bean/BeanUtilTest.java index 3d13df13b..941989921 100755 --- a/hutool-core/src/test/java/cn/hutool/core/bean/BeanUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/bean/BeanUtilTest.java @@ -575,6 +575,21 @@ public class BeanUtilTest { Assert.assertNull(BeanUtil.copyProperties(null, Food.class)); } + @Test + public void copyPropertiesMapToMapIgnoreNullTest() { + // 测试MapToMap + final Map p1 = new HashMap<>(); + p1.put("isSlow", true); + p1.put("name", "测试"); + p1.put("subName", null); + + final Map map = MapUtil.newHashMap(); + BeanUtil.copyProperties(p1, map, CopyOptions.of().setIgnoreNullValue(true)); + Assert.assertTrue((Boolean) map.get("isSlow")); + Assert.assertEquals("测试", map.get("name")); + Assert.assertFalse(map.containsKey("subName")); + } + @Test public void copyBeanPropertiesFilterTest() { final Food info = new Food(); From 0c50dfeed8d01b27ae740005e386efe51258c698 Mon Sep 17 00:00:00 2001 From: Looly Date: Wed, 12 Oct 2022 21:50:11 +0800 Subject: [PATCH 21/29] fix doc --- hutool-core/src/main/java/cn/hutool/core/date/DateUtil.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/date/DateUtil.java b/hutool-core/src/main/java/cn/hutool/core/date/DateUtil.java index 7e1d9a880..6b3051f3e 100755 --- a/hutool-core/src/main/java/cn/hutool/core/date/DateUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/date/DateUtil.java @@ -126,7 +126,7 @@ public class DateUtil extends CalendarUtil { /** * Long类型时间转为{@link DateTime}
    - * 只支持毫秒级别时间戳,如果需要秒级别时间戳,请自行×1000 + * 只支持毫秒级别时间戳,如果需要秒级别时间戳,请自行×1000L * * @param date Long类型Date(Unix时间戳) * @return 时间对象 From 5575043ba9768d78c7efcc5afb11d8d2f46fa040 Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 13 Oct 2022 17:30:42 +0800 Subject: [PATCH 22/29] remove methods --- .../hutool/core/util/PrimitiveArrayUtil.java | 84 ------------------- 1 file changed, 84 deletions(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/util/PrimitiveArrayUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/PrimitiveArrayUtil.java index 9cf2d9a6d..5dd89d88d 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/PrimitiveArrayUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/PrimitiveArrayUtil.java @@ -2766,18 +2766,6 @@ public class PrimitiveArrayUtil { return array; } - /** - * 检查数组是否升序,即array[i] <= array[i+1],若传入空数组,则返回false - * - * @param array 数组 - * @return 数组是否升序 - * @author FengBaoheng - * @since 5.5.2 - */ - public static boolean isSorted(final byte[] array) { - return isSortedASC(array); - } - /** * 检查数组是否升序,即array[i] <= array[i+1],若传入空数组,则返回false * @@ -2822,18 +2810,6 @@ public class PrimitiveArrayUtil { return true; } - /** - * 检查数组是否升序,即array[i] <= array[i+1],若传入空数组,则返回false - * - * @param array 数组 - * @return 数组是否升序 - * @author FengBaoheng - * @since 5.5.2 - */ - public static boolean isSorted(final short[] array) { - return isSortedASC(array); - } - /** * 检查数组是否升序,即array[i] <= array[i+1],若传入空数组,则返回false * @@ -2878,18 +2854,6 @@ public class PrimitiveArrayUtil { return true; } - /** - * 检查数组是否升序,即array[i] <= array[i+1],若传入空数组,则返回false - * - * @param array 数组 - * @return 数组是否升序 - * @author FengBaoheng - * @since 5.5.2 - */ - public static boolean isSorted(final char[] array) { - return isSortedASC(array); - } - /** * 检查数组是否升序,即array[i] <= array[i+1],若传入空数组,则返回false * @@ -2934,18 +2898,6 @@ public class PrimitiveArrayUtil { return true; } - /** - * 检查数组是否升序,即array[i] <= array[i+1],若传入空数组,则返回false - * - * @param array 数组 - * @return 数组是否升序 - * @author FengBaoheng - * @since 5.5.2 - */ - public static boolean isSorted(final int[] array) { - return isSortedASC(array); - } - /** * 检查数组是否升序,即array[i] <= array[i+1],若传入空数组,则返回false * @@ -2990,18 +2942,6 @@ public class PrimitiveArrayUtil { return true; } - /** - * 检查数组是否升序,即array[i] <= array[i+1],若传入空数组,则返回false - * - * @param array 数组 - * @return 数组是否升序 - * @author FengBaoheng - * @since 5.5.2 - */ - public static boolean isSorted(final long[] array) { - return isSortedASC(array); - } - /** * 检查数组是否升序,即array[i] <= array[i+1],若传入空数组,则返回false * @@ -3046,18 +2986,6 @@ public class PrimitiveArrayUtil { return true; } - /** - * 检查数组是否升序,即array[i] <= array[i+1],若传入空数组,则返回false - * - * @param array 数组 - * @return 数组是否升序 - * @author FengBaoheng - * @since 5.5.2 - */ - public static boolean isSorted(final double[] array) { - return isSortedASC(array); - } - /** * 检查数组是否升序,即array[i] <= array[i+1],若传入空数组,则返回false * @@ -3102,18 +3030,6 @@ public class PrimitiveArrayUtil { return true; } - /** - * 检查数组是否升序,即array[i] <= array[i+1],若传入空数组,则返回false - * - * @param array 数组 - * @return 数组是否升序 - * @author FengBaoheng - * @since 5.5.2 - */ - public static boolean isSorted(final float[] array) { - return isSortedASC(array); - } - /** * 检查数组是否升序,即array[i] <= array[i+1],若传入空数组,则返回false * From 1051b7cb5f07174368be4e70b2fcfec3811e01a4 Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 13 Oct 2022 17:32:35 +0800 Subject: [PATCH 23/29] fix doc --- .../src/main/java/cn/hutool/core/util/PrimitiveArrayUtil.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/util/PrimitiveArrayUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/PrimitiveArrayUtil.java index 5dd89d88d..bf68827df 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/PrimitiveArrayUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/PrimitiveArrayUtil.java @@ -2612,7 +2612,7 @@ public class PrimitiveArrayUtil { return array; } - // ---------------------------------------------------------------------- shuffle + // ---------------------------------------------------------------------- swap /** * 交换数组中两个位置的值 @@ -2766,6 +2766,8 @@ public class PrimitiveArrayUtil { return array; } + // ---------------------------------------------------------------------- asc and desc + /** * 检查数组是否升序,即array[i] <= array[i+1],若传入空数组,则返回false * From f5d68a0360471d758e34ad07a23d7fcea0c22327 Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 13 Oct 2022 17:41:12 +0800 Subject: [PATCH 24/29] add methods --- .../java/cn/hutool/core/net/url/UrlBuilder.java | 17 ++++++++++++++++- .../java/cn/hutool/core/net/UrlBuilderTest.java | 13 +++++++++---- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java index 2e2ab9193..0df307b3d 100755 --- a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java @@ -1,7 +1,7 @@ package cn.hutool.core.net.url; -import cn.hutool.core.lang.builder.Builder; import cn.hutool.core.lang.Assert; +import cn.hutool.core.lang.builder.Builder; import cn.hutool.core.text.StrUtil; import cn.hutool.core.util.CharsetUtil; @@ -280,6 +280,21 @@ public final class UrlBuilder implements Builder { return port; } + /** + * 获取端口,如果未自定义返回协议默认端口 + * + * @return 端口 + * @since 5.8.9 + */ + public int getPortWithDefault() { + int port = getPort(); + if (port <= 0) { + port = toURL().getDefaultPort(); + return port; + } + return port; + } + /** * 设置端口,默认-1 * diff --git a/hutool-core/src/test/java/cn/hutool/core/net/UrlBuilderTest.java b/hutool-core/src/test/java/cn/hutool/core/net/UrlBuilderTest.java index e8f093228..765b26917 100644 --- a/hutool-core/src/test/java/cn/hutool/core/net/UrlBuilderTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/net/UrlBuilderTest.java @@ -16,8 +16,11 @@ public class UrlBuilderTest { @Test public void buildTest() { - final String buildUrl = UrlBuilder.of().setHost("www.hutool.cn").build(); + final UrlBuilder builder = UrlBuilder.of(); + final String buildUrl = builder.setHost("www.hutool.cn").build(); + Assert.assertEquals("http://www.hutool.cn/", buildUrl); + Assert.assertEquals(buildUrl, 80, builder.getPortWithDefault()); } @Test @@ -26,9 +29,11 @@ public class UrlBuilderTest { String buildUrl = UrlBuilder.of().setScheme("http").setHost("192.168.1.1").setPort(8080).setWithEndTag(false).build(); Assert.assertEquals("http://192.168.1.1:8080", buildUrl); - buildUrl = UrlBuilder.of().setScheme("http").setHost("192.168.1.1").setPort(8080).addQuery("url", "http://192.168.1.1/test/1") + final UrlBuilder urlBuilder = UrlBuilder.of(); + buildUrl = urlBuilder.setScheme("http").setHost("192.168.1.1").setPort(8080).addQuery("url", "http://192.168.1.1/test/1") .setWithEndTag(false).build(); Assert.assertEquals("http://192.168.1.1:8080?url=http://192.168.1.1/test/1", buildUrl); + Assert.assertEquals(buildUrl, 8080, urlBuilder.getPortWithDefault()); } @Test @@ -459,7 +464,7 @@ public class UrlBuilderTest { } @Test - public void getAuthorityTest(){ + public void getAuthorityTest() { final UrlBuilder builder = UrlBuilder.ofHttp("127.0.0.1:8080") .addQuery("param[0].field", "编码"); @@ -467,7 +472,7 @@ public class UrlBuilderTest { } @Test - public void addPathTest(){ + public void addPathTest() { //https://gitee.com/dromara/hutool/issues/I5O4ML UrlBuilder.of().addPath(""); UrlBuilder.of().addPath("/"); From 3b0201c8643a91914e8539890e3515ee7891e227 Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 13 Oct 2022 18:02:18 +0800 Subject: [PATCH 25/29] fix test --- .../src/test/java/cn/hutool/core/bean/Issue2649Test.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/hutool-core/src/test/java/cn/hutool/core/bean/Issue2649Test.java b/hutool-core/src/test/java/cn/hutool/core/bean/Issue2649Test.java index 2b33ac169..8fae58a90 100755 --- a/hutool-core/src/test/java/cn/hutool/core/bean/Issue2649Test.java +++ b/hutool-core/src/test/java/cn/hutool/core/bean/Issue2649Test.java @@ -30,8 +30,11 @@ public class Issue2649Test { final StopWatch stopWatch = new StopWatch(); stopWatch.start(); - @SuppressWarnings("unused") - final List view2s = BeanUtil.copyToList(view1List, View2.class); + + for (int i = 0; i < 50; i++) { + @SuppressWarnings("unused") + final List view2s = BeanUtil.copyToList(view1List, View2.class); + } stopWatch.stop(); Console.log(stopWatch.getTotalTimeSeconds()); } From 17d259a30970cbaffcbe6bd76d209923880fb236 Mon Sep 17 00:00:00 2001 From: Looly Date: Sun, 16 Oct 2022 20:40:20 +0800 Subject: [PATCH 26/29] fix ser bug --- .../main/java/cn/hutool/core/map/CamelCaseMap.java | 5 ++++- .../java/cn/hutool/core/map/CaseInsensitiveMap.java | 5 ++++- .../java/cn/hutool/core/map/CamelCaseMapTest.java | 13 +++++++++++++ 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/map/CamelCaseMap.java b/hutool-core/src/main/java/cn/hutool/core/map/CamelCaseMap.java index d3fc0d2e7..e5e7b04ef 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/CamelCaseMap.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/CamelCaseMap.java @@ -2,8 +2,10 @@ package cn.hutool.core.map; import cn.hutool.core.text.StrUtil; +import java.io.Serializable; import java.util.HashMap; import java.util.Map; +import java.util.function.Function; /** * 驼峰Key风格的Map
    @@ -73,7 +75,8 @@ public class CamelCaseMap extends FuncKeyMap { */ @SuppressWarnings("unchecked") CamelCaseMap(final MapBuilder emptyMapBuilder) { - super(emptyMapBuilder.build(), (key) -> { + // issue#I5VRHW@Gitee 使Function可以被序列化 + super(emptyMapBuilder.build(), (Function & Serializable)(key) -> { if (key instanceof CharSequence) { key = StrUtil.toCamelCase(key.toString()); } diff --git a/hutool-core/src/main/java/cn/hutool/core/map/CaseInsensitiveMap.java b/hutool-core/src/main/java/cn/hutool/core/map/CaseInsensitiveMap.java index c3c7c5c69..6ca479ba3 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/CaseInsensitiveMap.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/CaseInsensitiveMap.java @@ -1,7 +1,9 @@ package cn.hutool.core.map; +import java.io.Serializable; import java.util.HashMap; import java.util.Map; +import java.util.function.Function; /** * 忽略大小写的Map
    @@ -73,7 +75,8 @@ public class CaseInsensitiveMap extends FuncKeyMap { */ @SuppressWarnings("unchecked") CaseInsensitiveMap(final MapBuilder emptyMapBuilder) { - super(emptyMapBuilder.build(), (key)->{ + // issue#I5VRHW@Gitee 使Function可以被序列化 + super(emptyMapBuilder.build(), (Function & Serializable)(key)->{ if (key instanceof CharSequence) { key = key.toString().toLowerCase(); } diff --git a/hutool-core/src/test/java/cn/hutool/core/map/CamelCaseMapTest.java b/hutool-core/src/test/java/cn/hutool/core/map/CamelCaseMapTest.java index 9f8dc2760..b436a7b7f 100644 --- a/hutool-core/src/test/java/cn/hutool/core/map/CamelCaseMapTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/map/CamelCaseMapTest.java @@ -1,5 +1,6 @@ package cn.hutool.core.map; +import cn.hutool.core.io.SerializeUtil; import org.junit.Assert; import org.junit.Test; @@ -20,4 +21,16 @@ public class CamelCaseMapTest { Assert.assertEquals("OK", map.get("customKey")); Assert.assertEquals("OK", map.get("custom_key")); } + + @Test + public void serializableKeyFuncTest() { + final CamelCaseMap map = new CamelCaseMap<>(); + map.put("serializable_key", "OK"); + final CamelCaseMap deSerializableMap = SerializeUtil.deserialize(SerializeUtil.serialize(map)); + Assert.assertEquals("OK", deSerializableMap.get("serializable_key")); + Assert.assertEquals("OK", deSerializableMap.get("serializableKey")); + deSerializableMap.put("serializable_func", "OK"); + Assert.assertEquals("OK", deSerializableMap.get("serializable_func")); + Assert.assertEquals("OK", deSerializableMap.get("serializableFunc")); + } } From 909a523b175ca1f13a937cfbfc30e36b016e54cc Mon Sep 17 00:00:00 2001 From: Looly Date: Sun, 16 Oct 2022 21:30:42 +0800 Subject: [PATCH 27/29] add spring3 support --- ....springframework.boot.autoconfigure.AutoConfiguration.imports | 1 + 1 file changed, 1 insertion(+) create mode 100644 hutool-extra/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports diff --git a/hutool-extra/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/hutool-extra/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 000000000..d16c1928b --- /dev/null +++ b/hutool-extra/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +cn.hutool.extra.spring.SpringUtil From d2b08250ffd6c2e94354dd76faa002bb5603f3f8 Mon Sep 17 00:00:00 2001 From: Looly Date: Sun, 16 Oct 2022 21:32:38 +0800 Subject: [PATCH 28/29] fix code --- .../src/main/java/cn/hutool/core/reflect/MethodUtil.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/reflect/MethodUtil.java b/hutool-core/src/main/java/cn/hutool/core/reflect/MethodUtil.java index 14c960ecd..99e10bb70 100644 --- a/hutool-core/src/main/java/cn/hutool/core/reflect/MethodUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/reflect/MethodUtil.java @@ -336,7 +336,7 @@ public class MethodUtil { public static Method[] getDeclaredMethods(final Class beanClass) throws SecurityException { Assert.notNull(beanClass); return DECLARED_METHODS_CACHE.computeIfAbsent(beanClass, - key -> getMethodsDirectly(beanClass, false, Objects.equals(Object.class, beanClass))); + key -> getMethodsDirectly(beanClass, false, Objects.equals(Object.class, beanClass))); } /** @@ -781,7 +781,7 @@ public class MethodUtil { actualArgs[i] = null; } else if (false == parameterTypes[i].isAssignableFrom(args[i].getClass())) { //对于类型不同的字段,尝试转换,转换失败则使用原对象类型 - final Object targetValue = Convert.convert(parameterTypes[i], args[i]); + final Object targetValue = Convert.convertQuietly(parameterTypes[i], args[i], args[i]); if (null != targetValue) { actualArgs[i] = targetValue; } From 3e21370503fd8bdd06f217d8836a082c32a7b585 Mon Sep 17 00:00:00 2001 From: Looly Date: Mon, 17 Oct 2022 11:36:28 +0800 Subject: [PATCH 29/29] add method --- .../java/cn/hutool/core/util/ArrayUtil.java | 37 ++++++++--- .../cn/hutool/core/util/ArrayUtilTest.java | 61 ++++++++++++++++++- 2 files changed, 87 insertions(+), 11 deletions(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ArrayUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ArrayUtil.java index 530656cd4..2e48d0097 100755 --- a/hutool-core/src/main/java/cn/hutool/core/util/ArrayUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ArrayUtil.java @@ -4,6 +4,7 @@ import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.ListUtil; import cn.hutool.core.collection.UniqueKeySet; import cn.hutool.core.comparator.CompareUtil; +import cn.hutool.core.convert.Convert; import cn.hutool.core.exceptions.UtilException; import cn.hutool.core.lang.Assert; import cn.hutool.core.map.MapUtil; @@ -139,6 +140,20 @@ public class ArrayUtil extends PrimitiveArrayUtil { return null == firstNonNull(array); } + /** + * 是否包含非{@code null}元素
    + * 如果列表是{@code null}或者空,返回{@code false},否则当列表中有非{@code null}字符时返回{@code true} + * + * @param 数组元素类型 + * @param array 被检查的数组 + * @return 是否包含非{@code null}元素 + * @since 5.4.0 + */ + @SuppressWarnings("unchecked") + public static boolean hasNonNull(final T... array) { + return null != firstNonNull(array); + } + /** * 返回数组中第一个非空元素 * @@ -317,15 +332,18 @@ public class ArrayUtil extends PrimitiveArrayUtil { * 将新元素添加到已有数组中
    * 添加新元素会生成一个新的数组,不影响原数组 * + * @param 数组类型 * @param 数组元素类型 * @param array 已有数组 * @param newElements 新元素 * @return 新数组 */ + @SuppressWarnings("unchecked") @SafeVarargs - public static Object append(final Object array, final T... newElements) { + public static A append(final A array, final T... newElements) { if (isEmpty(array)) { - return newElements; + // 可变长参数可能为包装类型,如果array是原始类型,则此处强转不合适,采用万能转换器完成转换 + return (A) Convert.convert(array.getClass(), newElements); } return insert(array, length(array), newElements); } @@ -357,13 +375,14 @@ public class ArrayUtil extends PrimitiveArrayUtil { /** * 将元素值设置为数组的某个位置,当给定的index大于数组长度,则追加 * + * @param 数组类型 * @param array 已有数组 * @param index 位置,大于长度追加,否则替换 * @param value 新值 * @return 新数组或原有数组 * @since 4.1.2 */ - public static Object setOrAppend(final Object array, final int index, final Object value) { + public static A setOrAppend(final A array, final int index, final Object value) { if (index < length(array)) { Array.set(array, index, value); return array; @@ -436,6 +455,7 @@ public class ArrayUtil extends PrimitiveArrayUtil { * 添加新元素会生成一个新的数组,不影响原数组
    * 如果插入位置为为负数,从原数组从后向前计数,若大于原数组长度,则空白处用null填充 * + * @param
    数组类型 * @param 数组元素类型 * @param array 已有数组 * @param index 插入位置,此位置为对应此位置元素之前的空档 @@ -444,12 +464,12 @@ public class ArrayUtil extends PrimitiveArrayUtil { * @since 4.0.8 */ @SuppressWarnings({"unchecked", "SuspiciousSystemArraycopy"}) - public static Object insert(final Object array, int index, final T... newElements) { + public static A insert(final A array, int index, final T... newElements) { if (isEmpty(newElements)) { return array; } if (isEmpty(array)) { - return newElements; + return (A) Convert.convert(array.getClass(), newElements); } final int len = length(array); @@ -457,13 +477,13 @@ public class ArrayUtil extends PrimitiveArrayUtil { index = (index % len) + len; } - final T[] result = newArray(array.getClass().getComponentType(), Math.max(len, index) + newElements.length); + final Object result = Array.newInstance(array.getClass().getComponentType(), Math.max(len, index) + newElements.length); System.arraycopy(array, 0, result, 0, Math.min(len, index)); System.arraycopy(newElements, 0, result, index, newElements.length); if (index < len) { System.arraycopy(array, index, result, index + newElements.length, len - index); } - return result; + return (A) result; } /** @@ -496,6 +516,7 @@ public class ArrayUtil extends PrimitiveArrayUtil { * @param newSize 新的数组大小 * @return 调整后的新数组 * @since 4.6.7 + * @see System#arraycopy(Object, int, Object, int, int) */ public static Object resize(final Object array, final int newSize) { if (newSize < 0) { @@ -1572,7 +1593,7 @@ public class ArrayUtil extends PrimitiveArrayUtil { /** * 是否存都为{@code null}或空对象,通过{@link ObjUtil#isEmpty(Object)} 判断元素 * - * @param 元素类型 + * @param 元素类型 * @param args 被检查的对象,一个或者多个 * @return 是否都为空 * @since 4.5.18 diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java index 46a21f528..ca992b7ca 100755 --- a/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java @@ -63,6 +63,9 @@ public class ArrayUtilTest { public void newArrayTest() { final String[] newArray = ArrayUtil.newArray(String.class, 3); Assert.assertEquals(3, newArray.length); + + final Object[] newArray2 = ArrayUtil.newArray(3); + Assert.assertEquals(3, newArray2.length); } @Test @@ -508,10 +511,20 @@ public class ArrayUtilTest { } @Test - public void setOrAppendTest(){ - String[] arr = new String[0]; - String[] newArr = ArrayUtil.setOrAppend(arr, 0, "Good");// ClassCastException + public void setOrAppendTest() { + final String[] arr = new String[0]; + final String[] newArr = ArrayUtil.setOrAppend(arr, 0, "Good");// ClassCastException Assert.assertArrayEquals(new String[]{"Good"}, newArr); + + // 非空数组替换第一个元素 + int[] arr2 = new int[]{1}; + int[] o = ArrayUtil.setOrAppend(arr2, 0, 2); + Assert.assertArrayEquals(new int[]{2}, o); + + // 空数组追加 + arr2 = new int[0]; + o = ArrayUtil.setOrAppend(arr2, 0, 2); + Assert.assertArrayEquals(new int[]{2}, o); } @Test @@ -522,4 +535,46 @@ public class ArrayUtilTest { final String[] c = {"d", "e"}; Assert.assertTrue(ArrayUtil.containsAll(c, resultO[0], resultO[1])); } + + @Test + public void hasNonNullTest() { + String[] a = {null, "e"}; + Assert.assertTrue(ArrayUtil.hasNonNull(a)); + + a = new String[]{null, null}; + Assert.assertFalse(ArrayUtil.hasNonNull(a)); + + a = new String[]{"", null}; + Assert.assertTrue(ArrayUtil.hasNonNull(a)); + + a = new String[]{null}; + Assert.assertFalse(ArrayUtil.hasNonNull(a)); + + a = new String[]{}; + Assert.assertFalse(ArrayUtil.hasNonNull(a)); + + a = null; + Assert.assertFalse(ArrayUtil.hasNonNull(a)); + } + + @Test + public void isAllNullTest() { + String[] a = {null, "e"}; + Assert.assertFalse(ArrayUtil.isAllNull(a)); + + a = new String[]{null, null}; + Assert.assertTrue(ArrayUtil.isAllNull(a)); + + a = new String[]{"", null}; + Assert.assertFalse(ArrayUtil.isAllNull(a)); + + a = new String[]{null}; + Assert.assertTrue(ArrayUtil.isAllNull(a)); + + a = new String[]{}; + Assert.assertTrue(ArrayUtil.isAllNull(a)); + + a = null; + Assert.assertTrue(ArrayUtil.isAllNull(a)); + } }