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-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/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/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/comparator/CompareUtil.java b/hutool-core/src/main/java/cn/hutool/core/comparator/CompareUtil.java index bbd723284..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 @@ -136,7 +136,7 @@ public class CompareUtil { *
  • {@code Comparator.nullsFirst(CompareUtil.reverse())}
  • * * - * @param 排序节点类型 + * @param 排序节点类型 * @param comparator 排序器 * @return 默认排序器 * @since 6.0.0 @@ -326,4 +326,124 @@ 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; + } + + /** + * {@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/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/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 时间对象 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/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..b17f51234 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/lang/range/Bound.java @@ -0,0 +1,188 @@ +package cn.hutool.core.lang.range; + +import java.util.Objects; +import java.util.function.Predicate; + +/** + *

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

    边界的类型

    + *

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

      + *
    • {@link #noneLowerBound()}:{@code {x | x > -∞}};
    • + *
    • {@link #noneUpperBound()}:{@code {x | x < +∞}};
    • + *
    • {@link #greaterThan}:{@code {x | x > t}};
    • + *
    • {@link #atLeast}:{@code {x | x >= t}};
    • + *
    • {@link #lessThan}:{@code {x | x < t}};
    • + *
    • {@link #atMost}:{@code {x | x <= t}};
    • + *
    + * 当作为{@link Predicate}使用时,可用于判断入参对象是否能满足当前实例所对应的不等式。 + * + *

    边界的比较

    + *

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

      + *
    • -1:t1t2的左侧;
    • + *
    • 0:t1t2所表示的点彼此重合;
    • + *
    • -1:t1t2的右侧;
    • + *
    + * + * @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"; + + // region --------------- static methods + /** + * {@code {x | x > -∞}} + * + * @param 边界值类型 + * @return 区间 + */ + @SuppressWarnings("unchecked") + static > Bound noneLowerBound() { + return NoneLowerBound.INSTANCE; + } + + /** + * {@code {x | x < +∞}} + * + * @param 边界值类型 + * @return 区间 + */ + @SuppressWarnings("unchecked") + static > Bound noneUpperBound() { + return NoneUpperBound.INSTANCE; + } + + /** + * {@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); + } + // endregion --------------- static methods + + /** + * 获取边界值 + * + * @return 边界值 + */ + T getValue(); + + /** + * 获取边界类型 + * + * @return 边界类型 + */ + BoundType getType(); + + /** + * 检验指定值是否在当前边界表示的范围内 + * + * @param t 要检验的值,不允许为{@code null} + * @return 是否 + */ + @SuppressWarnings("AbstractMethodOverridesAbstractMethod") + @Override + boolean test(T t); + + /** + *

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

      + *
    • -1:t1t2的左侧;
    • + *
    • 0:t1t2的重合;
    • + *
    • -1:t1t2的右侧;
    • + *
    + * + * @param bound 边界 + * @return 位置 + */ + @SuppressWarnings("AbstractMethodOverridesAbstractMethod") + @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(); +} 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..ce22ec4af --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/lang/range/BoundType.java @@ -0,0 +1,143 @@ +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; + + /** + * 构造 + * @param symbol 符号,如`[`或`(`等 + * @param operator 运算符,如`<`等 + * @param 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(final 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..36776bfb4 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/lang/range/BoundedRange.java @@ -0,0 +1,514 @@ +package cn.hutool.core.lang.range; + +import cn.hutool.core.lang.Assert; + +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", "unchecked"}) + private static final BoundedRange ALL = new BoundedRange(Bound.noneLowerBound(), Bound.noneUpperBound()); + + /** + * 构建一个上下界皆无限大的区间,即{@code {x | -∞ < x < +∞}} + * + * @param 比较对象类型 + * @return 区间 + */ + @SuppressWarnings("unchecked") + public static > BoundedRange all() { + return ALL; + } + + /** + * 构建一个闭区间,即{@code {x | lowerBound <= x <= upperBound}} + * + * @param lowerBound 下界,不能为空 + * @param upperBound 上界,不能为空 + * @param 边界值类型 + * @return 区间 + * @throws IllegalArgumentException 当创建的区间表示的集合为空时抛出 + * @throws NullPointerException 上界或下界为{@code null}时抛出 + */ + public 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}时抛出 + */ + public 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}时抛出 + */ + public 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}时抛出 + */ + public 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() + */ + public 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() + */ + public 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() + */ + public 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() + */ + public static > BoundedRange atMost(final T upperBound) { + return Bound.atMost(upperBound).toRange(); + } + + /** + * 下界 + */ + private final Bound lowerBound; + + /** + * 上界 + */ + private final Bound upperBound; + + /** + * 构造 + * + * @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() { + final Bound low = getLowerBound(); + final Bound up = getUpperBound(); + if (low instanceof NoneLowerBound || up instanceof NoneUpperBound) { + return false; + } + final int compareValue = low.getValue().compareTo(up.getValue()); + if (compareValue < 0) { + return false; + } + // 上界小于下界时为空 + return compareValue > 0 + // 上下界的边界值相等,且不为退化区间是为空 + || false == (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 BoundedRangeOperation.isDisjoint(this, other); + } + + /** + * {@code other}是否与当前区间相交: + * + * @param other 另一个区间 + * @return 是否 + */ + public boolean isIntersected(final BoundedRange other) { + return BoundedRangeOperation.isIntersected(this, other); + } + + /** + * 指定值是否在当前区间内 + * + * @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) { + return BoundedRangeOperation.unionIfIntersected(this, other); + } + + /** + * 获得包含当前区间与指定区间的最小的区间 + * + * @param other 另一个区间 + * @return 包含当前区间与指定区间的最小的区间 + */ + public BoundedRange span(final BoundedRange other) { + return BoundedRangeOperation.span(this, other); + } + + /** + * 若{@code other}与当前区间不相连,则获得两区间中间的间隔部分 + * + * @param other 另一个区间 + * @return 代表间隔部分的区间,若两区间相交则返回{@code null} + */ + public BoundedRange gap(final BoundedRange other) { + return BoundedRangeOperation.gap(this, other); + } + + /** + * 若{@code other}与当前区间相交,则获得该区间与当前区间的交集 + * + * @param other 另一个区间 + * @return 代表交集的区间,若无交集则返回{@code null} + */ + public BoundedRange intersection(final BoundedRange other) { + return BoundedRangeOperation.intersection(this, other); + } + + /** + * 截取当前区间中大于{@code min}的部分,若{@code min}不在该区间中,则返回当前区间本身 + * + * @param min 最大的左值 + * @return 区间 + */ + public BoundedRange subGreatThan(final T min) { + return BoundedRangeOperation.subGreatThan(this, min); + } + + /** + * 截取当前区间中大于等于{@code min}的部分,若{@code min}不在该区间中,则返回当前区间本身 + * + * @param min 最大的左值 + * @return 区间 + */ + public BoundedRange subAtLeast(final T min) { + return BoundedRangeOperation.subAtLeast(this, min); + } + + /** + * 截取当前区间中小于{@code max}的部分,若{@code max}不在该区间中,则返回当前区间本身 + * + * @param max 最大的左值 + * @return 区间 + */ + public BoundedRange subLessThan(final T max) { + return BoundedRangeOperation.subLessThan(this, max); + } + + /** + * 截取当前区间中小于等于{@code max}的部分,若{@code max}不在该区间中,则返回当前区间本身 + * + * @param max 最大的左值 + * @return 区间 + */ + public BoundedRange subAtMost(final T max) { + return BoundedRangeOperation.subAtMost(this, max); + } + + // endregion + + 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/BoundedRangeOperation.java b/hutool-core/src/main/java/cn/hutool/core/lang/range/BoundedRangeOperation.java new file mode 100644 index 000000000..2b7619e88 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/lang/range/BoundedRangeOperation.java @@ -0,0 +1,181 @@ +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); + } + + // region ========== 判断交并集 ========== + /** + * {@code 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 boundedRange}是否与{@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; + } + // 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 new file mode 100644 index 000000000..be63f5788 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/lang/range/FiniteBound.java @@ -0,0 +1,193 @@ +package cn.hutool.core.lang.range; + +import cn.hutool.core.text.CharSequenceUtil; +import cn.hutool.core.util.ObjUtil; + +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 (ObjUtil.notEquals(getValue(), bound.getValue())) { + return getValue().compareTo(bound.getValue()); + } + // 两边界值相等 + return compareIfSameBoundValue(bound); + } + + /** + * 获取{@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 + ); + } + + /** + * 当两个边界的值不相等时,判断它们在坐标轴上位置的先后顺序 + */ + 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/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/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/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/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/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/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; } 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-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/main/java/cn/hutool/core/util/CharUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/CharUtil.java index eabcd45f9..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 @@ -259,7 +259,11 @@ public class CharUtil implements CharPool { || Character.isSpaceChar(c) || c == '\ufeff' || c == '\u202a' - || c == '\u0000'; + || c == '\u0000' + // issue#I5UGSQ,Hangul Filler + || c == '\u3164' + // Braille Pattern Blank + || c == '\u2800'; } /** 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..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,17 +2766,7 @@ 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); - } + // ---------------------------------------------------------------------- asc and desc /** * 检查数组是否升序,即array[i] <= array[i+1],若传入空数组,则返回false @@ -2822,18 +2812,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 +2856,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 +2900,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 +2944,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 +2988,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 +3032,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 * 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(); 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..8fae58a90 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/bean/Issue2649Test.java @@ -0,0 +1,55 @@ +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(); + + for (int i = 0; i < 50; i++) { + @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<>(); + } +} 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..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,6 +1,7 @@ package cn.hutool.core.cache; import cn.hutool.core.cache.impl.LRUCache; +import cn.hutool.core.text.StrUtil; import cn.hutool.core.thread.ThreadUtil; import cn.hutool.core.util.RandomUtil; import org.junit.Assert; @@ -8,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
    @@ -64,4 +66,23 @@ public class LRUCacheTest { } Assert.assertEquals("null123456789", sb2.toString()); } + + @Test + public void issue2647Test(){ + final AtomicInteger removeCount = new AtomicInteger(); + + final LRUCache cache = CacheUtil.newLRUCache(3,1); + 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()); + } } 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); + } } 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..0a7ea480e --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/range/BoundTest.java @@ -0,0 +1,212 @@ +package cn.hutool.core.lang.range; + +import org.junit.Assert; +import org.junit.Test; + +/** + * test for {@link Bound} + */ +@SuppressWarnings("EqualsWithItself") +public class BoundTest { + + @Test + public void testEquals() { + final Bound bound = new FiniteBound<>(1, BoundType.OPEN_UPPER_BOUND); + Assert.assertEquals(bound, 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() { + 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() { + final 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() { + final 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..7d36e19d1 --- /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() { + final 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() { + final 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() { + final 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.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))); + 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() { + final 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.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] + 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() { + final 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.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] + 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() { + final 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() { + final 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() { + final 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() { + final 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() { + final 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() { + final 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)); + } + +} 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 99% 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 ba75a0224..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,4 +1,4 @@ -package cn.hutool.core.lang; +package cn.hutool.core.lang.range; import cn.hutool.core.date.DateField; import cn.hutool.core.date.DateRange; 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")); + } } 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("/"); 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 9f7563a01..4071bd502 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 @@ -181,7 +181,6 @@ public class AbstractEnhancedWrappedStreamTest { final List list = asList(1, 2, 3); final Map> map = new HashMap>() { private static final long serialVersionUID = 1L; - { put(Boolean.TRUE, singletonList(2)); put(Boolean.FALSE, asList(1, 3)); @@ -625,7 +624,6 @@ public class AbstractEnhancedWrappedStreamTest { public void testToEntries() { final Map expect = new HashMap() { private static final long serialVersionUID = 1L; - { put(1, 1); put(2, 2); 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)); + } } 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..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 @@ -55,4 +55,13 @@ public class CharUtilTest { Assert.assertEquals('⑫', CharUtil.toCloseByNumber(12)); Assert.assertEquals('⑳', CharUtil.toCloseByNumber(20)); } + + @Test + public void issueI5UGSQTest(){ + char c = '\u3164'; + Assert.assertTrue(CharUtil.isBlankChar(c)); + + c = '\u2800'; + Assert.assertTrue(CharUtil.isBlankChar(c)); + } } 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/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 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); + } + } 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) 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/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; + } +} 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()); } 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;