diff --git a/CHANGELOG.md b/CHANGELOG.md index df97fc9c3..bc829136e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,38 @@ # 🚀Changelog ------------------------------------------------------------------------------------------------------------- +# 5.7.21 (2022-02-14) +### 🐣新特性 +* 【extra 】 增加jetbrick模板支持 +* 【extra 】 EmojiUtil增加方法(pr#519@Gitee) +* 【core 】 DateUtil 添加两个日期是否同一周方法(pr#516@Gitee) +* 【db 】 新增条件组,用于处理复杂的where条件(pr#514@Gitee) +* 【core 】 新增LocalDateTimeUtil.weekOfYear(issue#I4RWXC@Gitee) +* 【core 】 Month增加toJdkMonth、getValueBaseOne +* 【core 】 CsvWriter修改规则,去除末尾多余换行符(issue#I4RSQY@Gitee) +* 【core 】 DateUtil增加rangeFunc和rangeConsume(issue#I4RSQY@Gitee) +* 【core 】 DateTime增加setUseJdkToStringStyle方法 +* 【core 】 CharSequenceUtil增加replace重载(issue#2122@Github) +* 【core 】 IntMap和LongMap使用位运算快速求解取余运算(pr#2123@Github) +* 【core 】 新增通用builder类:GenericBuilder(pr#526@Gitee) +* 【core 】 新增copySafely方法与mkdirsSafely方法(pr#527@Gitee) +* 【core 】 新增MetroHash(pr#532@Gitee) +* 【core 】 SpringUtil增加publishEvent重载(pr#2139@Github) +* 【core 】 DateUtil增加rangeContains、rangeNotContains(pr#537@Gitee) +* 【core 】 Resource增加isModified默认方法 +* 【core 】 增加VfsResource +* 【json 】 JSONConfig增加setKeyComparator、setNatureKeyComparator方法,支持自定义排序(issue#I4RBZ4@Gitee) + +### 🐞Bug修复 +* 【core 】 修复ChineseDate农历获取正月出现数组越界BUG(issue#2112@Github) +* 【extra 】 修复EmojiUtil.toHtmlHex()方法(pr#519@Gitee) +* 【system 】 修复CpuInfo.getUsed()方法(issue#2116@Github) +* 【dfa 】 修复密集匹配和贪婪匹配冲突问题(issue#2126@Github) +* 【db 】 修复c3p0丢失信息问题(issue#I4T7XZ@Gitee) +* 【http 】 修复Action中HttpExchange没有关闭问题 + +------------------------------------------------------------------------------------------------------------- # 5.7.20 (2022-01-20) ### 🐣新特性 diff --git a/README-EN.md b/README-EN.md index b55c1128d..3f9f3cf01 100644 --- a/README-EN.md +++ b/README-EN.md @@ -12,7 +12,7 @@ - + @@ -142,18 +142,18 @@ We provide the T-Shirt and Sweater with Hutool Logo, please visit the shop: cn.hutool hutool-all - 5.7.20 + 5.7.21 ``` ### 🍐Gradle ``` -implementation 'cn.hutool:hutool-all:5.7.20' +implementation 'cn.hutool:hutool-all:5.7.21' ``` ## 📥Download -- [Maven Repo](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.7.20/) +- [Maven Repo](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.7.21/) > 🔔️note: > Hutool 5.x supports JDK8+ and is not tested on Android platforms, and cannot guarantee that all tool classes or tool methods are available. diff --git a/README.md b/README.md index 8aeb70262..1d8f3f7c9 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ - + @@ -142,20 +142,20 @@ Hutool的存在就是为了减少代码搜索成本,避免网络上参差不 cn.hutool hutool-all - 5.7.20 + 5.7.21 ``` ### 🍐Gradle ``` -implementation 'cn.hutool:hutool-all:5.7.20' +implementation 'cn.hutool:hutool-all:5.7.21' ``` ### 📥下载jar 点击以下链接,下载`hutool-all-X.X.X.jar`即可: -- [Maven中央库](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.7.20/) +- [Maven中央库](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.7.21/) > 🔔️注意 > Hutool 5.x支持JDK8+,对Android平台没有测试,不能保证所有工具类或工具方法可用。 diff --git a/bin/logo.sh b/bin/logo.sh index 36a14646a..3366de914 100755 --- a/bin/logo.sh +++ b/bin/logo.sh @@ -7,5 +7,5 @@ echo ' / /_/ // / / // __// __ \ / __ \ / / ' echo ' / __ // /_/ // /_ / /_/ // /_/ // / ' echo '/_/ /_/ \____/ \__/ \____/ \____//_/ ' echo '' -echo '-----------http://hutool.cn/------------' +echo '-----------https://hutool.cn/-----------' echo '========================================' diff --git a/bin/version.txt b/bin/version.txt index b0917fb0e..fcad0d34c 100755 --- a/bin/version.txt +++ b/bin/version.txt @@ -1 +1 @@ -5.7.20 +5.7.21 diff --git a/docs/js/version.js b/docs/js/version.js index 978cfac6d..6bdf634cd 100644 --- a/docs/js/version.js +++ b/docs/js/version.js @@ -1 +1 @@ -var version = '5.7.20' \ No newline at end of file +var version = '5.7.21' \ No newline at end of file diff --git a/hutool-all/pom.xml b/hutool-all/pom.xml index 511746da4..f43ef756e 100644 --- a/hutool-all/pom.xml +++ b/hutool-all/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.20 + 5.7.21 hutool-all diff --git a/hutool-aop/pom.xml b/hutool-aop/pom.xml index 7ea45314f..225ca551a 100644 --- a/hutool-aop/pom.xml +++ b/hutool-aop/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.20 + 5.7.21 hutool-aop @@ -19,7 +19,7 @@ 3.3.0 - 5.3.14 + 5.3.15 diff --git a/hutool-bloomFilter/pom.xml b/hutool-bloomFilter/pom.xml index c8cdcd926..83a23d5dc 100644 --- a/hutool-bloomFilter/pom.xml +++ b/hutool-bloomFilter/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.20 + 5.7.21 hutool-bloomFilter diff --git a/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/bitMap/IntMap.java b/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/bitMap/IntMap.java index f941d0696..4bda77f63 100644 --- a/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/bitMap/IntMap.java +++ b/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/bitMap/IntMap.java @@ -32,22 +32,22 @@ public class IntMap implements BitMap, Serializable { @Override public void add(long i) { int r = (int) (i / BitMap.MACHINE32); - int c = (int) (i % BitMap.MACHINE32); + int c = (int) (i & (BitMap.MACHINE32 - 1)); ints[r] = ints[r] | (1 << c); } @Override public boolean contains(long i) { int r = (int) (i / BitMap.MACHINE32); - int c = (int) (i % BitMap.MACHINE32); + int c = (int) (i & (BitMap.MACHINE32 - 1)); return ((ints[r] >>> c) & 1) == 1; } @Override public void remove(long i) { int r = (int) (i / BitMap.MACHINE32); - int c = (int) (i % BitMap.MACHINE32); + int c = (int) (i & (BitMap.MACHINE32 - 1)); ints[r] &= ~(1 << c); } -} \ No newline at end of file +} diff --git a/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/bitMap/LongMap.java b/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/bitMap/LongMap.java index 93f0d6d99..7c44325af 100644 --- a/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/bitMap/LongMap.java +++ b/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/bitMap/LongMap.java @@ -32,22 +32,22 @@ public class LongMap implements BitMap, Serializable { @Override public void add(long i) { int r = (int) (i / BitMap.MACHINE64); - long c = i % BitMap.MACHINE64; + long c = i & (BitMap.MACHINE64 - 1); longs[r] = longs[r] | (1L << c); } @Override public boolean contains(long i) { int r = (int) (i / BitMap.MACHINE64); - long c = i % BitMap.MACHINE64; + long c = i & (BitMap.MACHINE64 - 1); return ((longs[r] >>> c) & 1) == 1; } @Override public void remove(long i) { int r = (int) (i / BitMap.MACHINE64); - long c = i % BitMap.MACHINE64; + long c = i & (BitMap.MACHINE64 - 1); longs[r] &= ~(1L << c); } -} \ No newline at end of file +} diff --git a/hutool-bom/pom.xml b/hutool-bom/pom.xml index c3cee33b3..88d32779c 100644 --- a/hutool-bom/pom.xml +++ b/hutool-bom/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.20 + 5.7.21 hutool-bom diff --git a/hutool-cache/pom.xml b/hutool-cache/pom.xml index 062080971..f114370a3 100644 --- a/hutool-cache/pom.xml +++ b/hutool-cache/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.20 + 5.7.21 hutool-cache diff --git a/hutool-captcha/pom.xml b/hutool-captcha/pom.xml index 45754904a..db28bdb33 100644 --- a/hutool-captcha/pom.xml +++ b/hutool-captcha/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.20 + 5.7.21 hutool-captcha diff --git a/hutool-core/pom.xml b/hutool-core/pom.xml index 995d36026..30c9bbae4 100644 --- a/hutool-core/pom.xml +++ b/hutool-core/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.20 + 5.7.21 hutool-core diff --git a/hutool-core/src/main/java/cn/hutool/core/bean/BeanDescCache.java b/hutool-core/src/main/java/cn/hutool/core/bean/BeanDescCache.java index d1eaa920a..b41d030c2 100644 --- a/hutool-core/src/main/java/cn/hutool/core/bean/BeanDescCache.java +++ b/hutool-core/src/main/java/cn/hutool/core/bean/BeanDescCache.java @@ -6,8 +6,8 @@ import cn.hutool.core.lang.func.Func0; /** * Bean属性缓存
* 缓存用于防止多次反射造成的性能问题 - * @author Looly * + * @author Looly */ public enum BeanDescCache { INSTANCE; @@ -16,12 +16,22 @@ public enum BeanDescCache { /** * 获得属性名和{@link BeanDesc}Map映射 + * * @param beanClass Bean的类 - * @param supplier 对象不存在时创建对象的函数 + * @param supplier 对象不存在时创建对象的函数 * @return 属性名和{@link BeanDesc}映射 * @since 5.4.2 */ - public BeanDesc getBeanDesc(Class beanClass, Func0 supplier){ + public BeanDesc getBeanDesc(Class beanClass, Func0 supplier) { return bdCache.get(beanClass, supplier); } + + /** + * 清空全局的Bean属性缓存 + * + * @since 5.7.21 + */ + public void clear() { + this.bdCache.clear(); + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/bean/BeanInfoCache.java b/hutool-core/src/main/java/cn/hutool/core/bean/BeanInfoCache.java index af9b83a54..7a950ba0c 100644 --- a/hutool-core/src/main/java/cn/hutool/core/bean/BeanInfoCache.java +++ b/hutool-core/src/main/java/cn/hutool/core/bean/BeanInfoCache.java @@ -56,6 +56,16 @@ public enum BeanInfoCache { getCache(ignoreCase).put(beanClass, fieldNamePropertyDescriptorMap); } + /** + * 清空缓存 + * + * @since 5.7.21 + */ + public void clear() { + this.pdCache.clear(); + this.ignoreCasePdCache.clear(); + } + /** * 根据是否忽略字段名的大小写,返回不用Cache对象 * diff --git a/hutool-core/src/main/java/cn/hutool/core/builder/GenericBuilder.java b/hutool-core/src/main/java/cn/hutool/core/builder/GenericBuilder.java new file mode 100644 index 000000000..829ff54db --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/builder/GenericBuilder.java @@ -0,0 +1,208 @@ +package cn.hutool.core.builder; + +import cn.hutool.core.lang.func.Supplier1; +import cn.hutool.core.lang.func.Supplier2; +import cn.hutool.core.lang.func.Supplier3; +import cn.hutool.core.lang.func.Supplier4; +import cn.hutool.core.lang.func.Supplier5; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Supplier; + +/** + *

通用Builder

+ * 参考:
一看就会的java8通用Builder + *

使用方法如下:

+ *
+ * Box box = GenericBuilder
+ * 		.of(Box::new)
+ * 		.with(Box::setId, 1024L)
+ * 		.with(Box::setTitle, "Hello World!")
+ * 		.with(Box::setLength, 9)
+ * 		.with(Box::setWidth, 8)
+ * 		.with(Box::setHeight, 7)
+ * 		.build();
+ *
+ * 
+ * + *

我们也可以对已创建的对象进行修改:

+ *
+ * Box boxModified = GenericBuilder
+ * 		.of(() -> box)
+ * 		.with(Box::setTitle, "Hello Friend!")
+ * 		.with(Box::setLength, 3)
+ * 		.with(Box::setWidth, 4)
+ * 		.with(Box::setHeight, 5)
+ * 		.build();
+ * 
+ *

我们还可以对这样调用有参构造,这对于创建一些在有参构造中包含初始化函数的对象是有意义的:

+ *
+ * Box box1 = GenericBuilder
+ * 		.of(Box::new, 2048L, "Hello Partner!", 222, 333, 444)
+ * 		.with(Box::alis)
+ * 		.build();
+ * 
+ *

注意:本工具类支持调用的方法的参数数量不超过1个,更多的参数不利于阅读和维护。

+ * + * @author TomXin + * @since 5.7.21 + */ +public class GenericBuilder implements Builder { + + /** + * 实例化器 + */ + private final Supplier instant; + + /** + * 修改器列表 + */ + private final List> modifiers = new ArrayList<>(); + + /** + * 构造 + * + * @param instant 实例化器 + */ + public GenericBuilder(Supplier instant) { + this.instant = instant; + } + + /** + * 通过无参数实例化器创建GenericBuilder + * + * @param instant 实例化器 + * @param 目标类型 + * @return GenericBuilder对象 + */ + public static GenericBuilder of(Supplier instant) { + return new GenericBuilder<>(instant); + } + + /** + * 通过1参数实例化器创建GenericBuilder + * + * @param instant 实例化器 + * @param p1 参数一 + * @param 目标类型 + * @param 参数一类型 + * @return GenericBuilder对象 + */ + public static GenericBuilder of(Supplier1 instant, P1 p1) { + return of(instant.toSupplier(p1)); + } + + /** + * 通过2参数实例化器创建GenericBuilder + * + * @param instant 实例化器 + * @param p1 参数一 + * @param p2 参数二 + * @param 目标类型 + * @param 参数一类型 + * @param 参数二类型 + * @return GenericBuilder对象 + */ + public static GenericBuilder of(Supplier2 instant, P1 p1, P2 p2) { + return of(instant.toSupplier(p1, p2)); + } + + /** + * 通过3参数实例化器创建GenericBuilder + * + * @param instant 实例化器 + * @param p1 参数一 + * @param p2 参数二 + * @param p3 参数三 + * @param 目标类型 + * @param 参数一类型 + * @param 参数二类型 + * @param 参数三类型 + * @return GenericBuilder对象 + */ + public static GenericBuilder of(Supplier3 instant, P1 p1, P2 p2, P3 p3) { + return of(instant.toSupplier(p1, p2, p3)); + } + + /** + * 通过4参数实例化器创建GenericBuilder + * + * @param instant 实例化器 + * @param p1 参数一 + * @param p2 参数二 + * @param p3 参数三 + * @param p4 参数四 + * @param 目标类型 + * @param 参数一类型 + * @param 参数二类型 + * @param 参数三类型 + * @param 参数四类型 + * @return GenericBuilder对象 + */ + public static GenericBuilder of(Supplier4 instant, P1 p1, P2 p2, P3 p3, P4 p4) { + return of(instant.toSupplier(p1, p2, p3, p4)); + } + + /** + * 通过5参数实例化器创建GenericBuilder + * + * @param instant 实例化器 + * @param p1 参数一 + * @param p2 参数二 + * @param p3 参数三 + * @param p4 参数四 + * @param p5 参数五 + * @param 目标类型 + * @param 参数一类型 + * @param 参数二类型 + * @param 参数三类型 + * @param 参数四类型 + * @param 参数五类型 + * @return GenericBuilder对象 + */ + public static GenericBuilder of(Supplier5 instant, P1 p1, P2 p2, P3 p3, P4 p4, P5 p5) { + return of(instant.toSupplier(p1, p2, p3, p4, p5)); + } + + + /** + * 调用无参数方法 + * + * @param consumer 无参数Consumer + * @return GenericBuilder对象 + */ + public GenericBuilder with(Consumer consumer) { + modifiers.add(consumer); + return this; + } + + + /** + * 调用1参数方法 + * + * @param 参数一类型 + * @param consumer 1参数Consumer,一般为Setter方法引用 + * @param p1 参数一 + * @return GenericBuilder对象 + */ + public GenericBuilder with(BiConsumer consumer, P1 p1) { + modifiers.add(instant -> consumer.accept(instant, p1)); + return this; + } + + /** + * 构建 + * + * @return 目标对象 + */ + @Override + public T build() { + T value = instant.get(); + modifiers.forEach(modifier -> modifier.accept(value)); + modifiers.clear(); + return value; + } +} 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 4ff9948dd..d91516a09 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 @@ -11,6 +11,18 @@ import java.util.function.Function; */ public class CompareUtil { + /** + * 获取自然排序器,即默认排序器 + * + * @param 排序节点类型 + * @return 默认排序器 + * @since 5.7.21 + */ + @SuppressWarnings("unchecked") + public static > Comparator naturalComparator() { + return ComparableComparator.INSTANCE; + } + /** * 对象比较,比较结果取决于comparator,如果被比较对象为null,传入的comparator对象应处理此情况
* 如果传入comparator为null,则使用默认规则比较(此时被比较对象必须实现Comparable接口) diff --git a/hutool-core/src/main/java/cn/hutool/core/convert/ConverterRegistry.java b/hutool-core/src/main/java/cn/hutool/core/convert/ConverterRegistry.java index 5cac6c295..96b19c52f 100644 --- a/hutool-core/src/main/java/cn/hutool/core/convert/ConverterRegistry.java +++ b/hutool-core/src/main/java/cn/hutool/core/convert/ConverterRegistry.java @@ -135,7 +135,7 @@ public class ConverterRegistry implements Serializable { ServiceLoaderUtil.load(Converter.class).forEach(converter -> { try { Type type = TypeUtil.getTypeArgument(ClassUtil.getClass(converter)); - if(null != type){ + if (null != type) { putCustom(type, converter); } } catch (Exception e) { @@ -266,7 +266,6 @@ public class ConverterRegistry implements Serializable { } } - // 特殊类型转换,包括Collection、Map、强转、Array等 final T result = convertSpecial(type, rowType, value, defaultValue); if (null != result) { diff --git a/hutool-core/src/main/java/cn/hutool/core/convert/NumberChineseFormatter.java b/hutool-core/src/main/java/cn/hutool/core/convert/NumberChineseFormatter.java index 29c2405e1..d6da1d22b 100644 --- a/hutool-core/src/main/java/cn/hutool/core/convert/NumberChineseFormatter.java +++ b/hutool-core/src/main/java/cn/hutool/core/convert/NumberChineseFormatter.java @@ -2,6 +2,7 @@ package cn.hutool.core.convert; import cn.hutool.core.lang.Assert; import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.NumberUtil; import cn.hutool.core.util.StrUtil; /** @@ -153,6 +154,27 @@ public class NumberChineseFormatter { return chineseStr.toString(); } + /** + * 阿拉伯数字(支持正负整数)四舍五入后转换成中文节权位简洁计数单位,例如 -5_5555 =》 -5.56万 + * + * @param amount 数字 + * @return 中文 + */ + public static String formatSimple(long amount) { + if (amount < 1_0000 && amount > -1_0000) { + return String.valueOf(amount); + } + String res; + if (amount < 1_0000_0000 && amount > -1_0000_0000) { + res = NumberUtil.div(amount, 1_0000, 2) + "万"; + } else if (amount < 1_0000_0000_0000L && amount > -1_0000_0000_0000L) { + res = NumberUtil.div(amount, 1_0000_0000, 2) + "亿"; + } else { + res = NumberUtil.div(amount, 1_0000_0000_0000L, 2) + "万亿"; + } + return res; + } + /** * 格式化-999~999之间的数字
* 这个方法显示10~19以下的数字时使用"十一"而非"一十一"。 diff --git a/hutool-core/src/main/java/cn/hutool/core/date/CalendarUtil.java b/hutool-core/src/main/java/cn/hutool/core/date/CalendarUtil.java index a1e2f176b..91744046f 100644 --- a/hutool-core/src/main/java/cn/hutool/core/date/CalendarUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/date/CalendarUtil.java @@ -132,7 +132,7 @@ public class CalendarUtil { } /** - * 获取秒级别的开始时间,即忽略毫秒部分 + * 修改秒级别的开始时间,即忽略毫秒部分 * * @param calendar 日期 {@link Calendar} * @return {@link Calendar} @@ -143,7 +143,7 @@ public class CalendarUtil { } /** - * 获取秒级别的结束时间,即毫秒设置为999 + * 修改秒级别的结束时间,即毫秒设置为999 * * @param calendar 日期 {@link Calendar} * @return {@link Calendar} @@ -154,7 +154,7 @@ public class CalendarUtil { } /** - * 获取某小时的开始时间 + * 修改某小时的开始时间 * * @param calendar 日期 {@link Calendar} * @return {@link Calendar} @@ -164,7 +164,7 @@ public class CalendarUtil { } /** - * 获取某小时的结束时间 + * 修改某小时的结束时间 * * @param calendar 日期 {@link Calendar} * @return {@link Calendar} @@ -174,7 +174,7 @@ public class CalendarUtil { } /** - * 获取某分钟的开始时间 + * 修改某分钟的开始时间 * * @param calendar 日期 {@link Calendar} * @return {@link Calendar} @@ -184,7 +184,7 @@ public class CalendarUtil { } /** - * 获取某分钟的结束时间 + * 修改某分钟的结束时间 * * @param calendar 日期 {@link Calendar} * @return {@link Calendar} @@ -194,7 +194,7 @@ public class CalendarUtil { } /** - * 获取某天的开始时间 + * 修改某天的开始时间 * * @param calendar 日期 {@link Calendar} * @return {@link Calendar} @@ -204,7 +204,7 @@ public class CalendarUtil { } /** - * 获取某天的结束时间 + * 修改某天的结束时间 * * @param calendar 日期 {@link Calendar} * @return {@link Calendar} @@ -214,7 +214,7 @@ public class CalendarUtil { } /** - * 获取给定日期当前周的开始时间,周一定为一周的开始时间 + * 修改给定日期当前周的开始时间,周一定为一周的开始时间 * * @param calendar 日期 {@link Calendar} * @return {@link Calendar} @@ -224,7 +224,7 @@ public class CalendarUtil { } /** - * 获取给定日期当前周的开始时间 + * 修改给定日期当前周的开始时间 * * @param calendar 日期 {@link Calendar} * @param isMondayAsFirstDay 是否周一做为一周的第一天(false表示周日做为第一天) @@ -238,7 +238,7 @@ public class CalendarUtil { } /** - * 获取某周的结束时间,周日定为一周的结束 + * 修改某周的结束时间,周日定为一周的结束 * * @param calendar 日期 {@link Calendar} * @return {@link Calendar} @@ -248,7 +248,7 @@ public class CalendarUtil { } /** - * 获取某周的结束时间 + * 修改某周的结束时间 * * @param calendar 日期 {@link Calendar} * @param isSundayAsLastDay 是否周日做为一周的最后一天(false表示周六做为最后一天) @@ -261,7 +261,7 @@ public class CalendarUtil { } /** - * 获取某月的开始时间 + * 修改某月的开始时间 * * @param calendar 日期 {@link Calendar} * @return {@link Calendar} @@ -271,7 +271,7 @@ public class CalendarUtil { } /** - * 获取某月的结束时间 + * 修改某月的结束时间 * * @param calendar 日期 {@link Calendar} * @return {@link Calendar} @@ -281,7 +281,7 @@ public class CalendarUtil { } /** - * 获取某季度的开始时间 + * 修改某季度的开始时间 * * @param calendar 日期 {@link Calendar} * @return {@link Calendar} @@ -313,7 +313,7 @@ public class CalendarUtil { } /** - * 获取某年的开始时间 + * 修改某年的开始时间 * * @param calendar 日期 {@link Calendar} * @return {@link Calendar} @@ -323,7 +323,7 @@ public class CalendarUtil { } /** - * 获取某年的结束时间 + * 修改某年的结束时间 * * @param calendar 日期 {@link Calendar} * @return {@link Calendar} @@ -348,6 +348,40 @@ public class CalendarUtil { cal1.get(Calendar.ERA) == cal2.get(Calendar.ERA); } + /** + * 比较两个日期是否为同一周 + * + * @param cal1 日期1 + * @param cal2 日期2 + * @param isMon 是否为周一。国内第一天为星期一,国外第一天为星期日 + * @return 是否为同一周 + * @since 5.7.21 + */ + public static boolean isSameWeek(Calendar cal1, Calendar cal2, boolean isMon) { + if (cal1 == null || cal2 == null) { + throw new IllegalArgumentException("The date must not be null"); + } + + // 防止比较前修改原始Calendar对象 + cal1 = (Calendar) cal1.clone(); + cal2 = (Calendar) cal2.clone(); + + // 把所传日期设置为其当前周的第一天 + // 比较设置后的两个日期是否是同一天:true 代表同一周 + if (isMon) { + cal1.setFirstDayOfWeek(Calendar.MONDAY); + cal1.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY); + cal2.setFirstDayOfWeek(Calendar.MONDAY); + cal2.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY); + } else { + cal1.setFirstDayOfWeek(Calendar.SUNDAY); + cal1.set(Calendar.DAY_OF_WEEK, Calendar.SUNDAY); + cal2.setFirstDayOfWeek(Calendar.SUNDAY); + cal2.set(Calendar.DAY_OF_WEEK, Calendar.SUNDAY); + } + return isSameDay(cal1, cal2); + } + /** * 比较两个日期是否为同一月 * diff --git a/hutool-core/src/main/java/cn/hutool/core/date/ChineseDate.java b/hutool-core/src/main/java/cn/hutool/core/date/ChineseDate.java index 061489bf3..1dc0d2136 100644 --- a/hutool-core/src/main/java/cn/hutool/core/date/ChineseDate.java +++ b/hutool-core/src/main/java/cn/hutool/core/date/ChineseDate.java @@ -92,7 +92,7 @@ public class ChineseDate { offset -= daysOfMonth; } - this.isLeapMonth = month == (leapMonth + 1); + this.isLeapMonth = leapMonth > 0 && (month == (leapMonth + 1)); if (hasLeapMonth && false == this.isLeapMonth) { // 当前月份前有闰月,则月份显示要-1,除非当前月份就是润月 month--; diff --git a/hutool-core/src/main/java/cn/hutool/core/date/DateTime.java b/hutool-core/src/main/java/cn/hutool/core/date/DateTime.java index 76547adfc..2956eade4 100644 --- a/hutool-core/src/main/java/cn/hutool/core/date/DateTime.java +++ b/hutool-core/src/main/java/cn/hutool/core/date/DateTime.java @@ -24,13 +24,30 @@ import java.util.Locale; import java.util.TimeZone; /** - * 包装java.util.Date + * 包装{@link Date}
+ * 此类继承了{@link Date},并提供扩展方法,如时区等。
+ * 此类重写了父类的{@code toString()}方法,返回值为"yyyy-MM-dd HH:mm:ss"格式 * * @author xiaoleilu */ public class DateTime extends Date { private static final long serialVersionUID = -5395712593979185936L; + private static boolean useJdkToStringStyle = false; + + /** + * 设置全局的,是否使用{@link Date}默认的toString()格式
+ * 如果为{@code true},则调用toString()时返回"EEE MMM dd HH:mm:ss zzz yyyy"格式,
+ * 如果为{@code false},则返回"yyyy-MM-dd HH:mm:ss",
+ * 默认为{@code false} + * + * @param customUseJdkToStringStyle 是否使用{@link Date}默认的toString()格式 + * @since 5.7.21 + */ + public static void setUseJdkToStringStyle(boolean customUseJdkToStringStyle){ + useJdkToStringStyle = customUseJdkToStringStyle; + } + /** * 是否可变对象 */ @@ -932,13 +949,18 @@ public class DateTime extends Date { // -------------------------------------------------------------------- toString start /** - * 转为"yyyy-MM-dd HH:mm:ss" 格式字符串
- * 如果时区被设置,会转换为其时区对应的时间,否则转换为当前地点对应的时区 + * 转为字符串,如果时区被设置,会转换为其时区对应的时间,否则转换为当前地点对应的时区
+ * 可以调用{@link DateTime#setUseJdkToStringStyle(boolean)} 方法自定义默认的风格
+ * 如果{@link #useJdkToStringStyle}为{@code true},返回"EEE MMM dd HH:mm:ss zzz yyyy"格式,
+ * 如果为{@code false},则返回"yyyy-MM-dd HH:mm:ss" * - * @return "yyyy-MM-dd HH:mm:ss" 格式字符串 + * @return 格式字符串 */ @Override public String toString() { + if(useJdkToStringStyle){ + return super.toString(); + } return toString(this.timeZone); } 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 813fba971..7754adb9e 100644 --- a/hutool-core/src/main/java/cn/hutool/core/date/DateUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/date/DateUtil.java @@ -20,22 +20,18 @@ import java.time.LocalDateTime; import java.time.Year; import java.time.format.DateTimeFormatter; import java.time.temporal.TemporalAccessor; -import java.util.Calendar; -import java.util.Date; -import java.util.GregorianCalendar; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Locale; -import java.util.TimeZone; +import java.util.*; import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.stream.Collectors; /** * 时间工具类 * + * @author xiaoleilu * @see LocalDateTimeUtil java8日志工具类 * @see DatePattern 日期常用格式工具类 - * - * @author xiaoleilu */ public class DateUtil extends CalendarUtil { @@ -1586,6 +1582,21 @@ public class DateUtil extends CalendarUtil { return CalendarUtil.isSameDay(calendar(date1), calendar(date2)); } + /** + * 比较两个日期是否为同一周 + * + * @param date1 日期1 + * @param date2 日期2 + * @param isMon 是否为周一。国内第一天为星期一,国外第一天为星期日 + * @return 是否为同一周 + */ + public static boolean isSameWeek(final Date date1, final Date date2, boolean isMon) { + if (date1 == null || date2 == null) { + throw new IllegalArgumentException("The date must not be null"); + } + return CalendarUtil.isSameWeek(calendar(date1), calendar(date2), isMon); + } + /** * 比较两个日期是否为同一月 * @@ -1870,6 +1881,74 @@ public class DateUtil extends CalendarUtil { return new DateRange(start, end, unit); } + /** + * 俩个时间区间取交集 + * + * @param start 开始区间 + * @param end 结束区间 + * @return true 包含 + * @author handy + * @since 5.7.21 + */ + public static List rangeContains(DateRange start, DateRange end) { + List startDateTimes = CollUtil.newArrayList((Iterable) start); + List endDateTimes = CollUtil.newArrayList((Iterable) end); + return startDateTimes.stream().filter(endDateTimes::contains).collect(Collectors.toList()); + } + + /** + * 俩个时间区间取差集(end - start) + * + * @param start 开始区间 + * @param end 结束区间 + * @return true 包含 + * @author handy + * @since 5.7.21 + */ + public static List rangeNotContains(DateRange start, DateRange end) { + List startDateTimes = CollUtil.newArrayList((Iterable) start); + List endDateTimes = CollUtil.newArrayList((Iterable) end); + return endDateTimes.stream().filter(item -> !startDateTimes.contains(item)).collect(Collectors.toList()); + } + + /** + * 按日期范围遍历,执行 function + * + * @param start 起始日期时间(包括) + * @param end 结束日期时间 + * @param unit 步进单位 + * @param func 每次遍历要执行的 function + * @param Date经过函数处理结果类型 + * @return 结果列表 + * @since 5.7.21 + */ + public static List rangeFunc(Date start, Date end, final DateField unit, Function func) { + if (start == null || end == null || start.after(end)) { + return Collections.emptyList(); + } + ArrayList list = new ArrayList<>(); + for (DateTime date : range(start, end, unit)) { + list.add(func.apply(date)); + } + return list; + } + + /** + * 按日期范围遍历,执行 consumer + * + * @param start 起始日期时间(包括) + * @param end 结束日期时间 + * @param unit 步进单位 + * @param consumer 每次遍历要执行的 consumer + * @since 5.7.21 + */ + public static void rangeConsume(Date start, Date end, final DateField unit, Consumer consumer) { + if (start == null || end == null || start.after(end)) { + return; + } + range(start, end, unit).forEach(consumer); + } + /** * 创建日期范围生成器 * @@ -1878,7 +1957,7 @@ public class DateUtil extends CalendarUtil { * @param unit 步进单位 * @return {@link DateRange} */ - public static List rangeToList(Date start, Date end, final DateField unit) { + public static List rangeToList(Date start, Date end, DateField unit) { return CollUtil.newArrayList((Iterable) range(start, end, unit)); } diff --git a/hutool-core/src/main/java/cn/hutool/core/date/LocalDateTimeUtil.java b/hutool-core/src/main/java/cn/hutool/core/date/LocalDateTimeUtil.java index 102fdff4e..b065c4ed1 100644 --- a/hutool-core/src/main/java/cn/hutool/core/date/LocalDateTimeUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/date/LocalDateTimeUtil.java @@ -22,6 +22,7 @@ import java.time.temporal.ChronoUnit; import java.time.temporal.Temporal; import java.time.temporal.TemporalAccessor; import java.time.temporal.TemporalUnit; +import java.time.temporal.WeekFields; import java.util.Date; import java.util.TimeZone; @@ -562,4 +563,21 @@ public class LocalDateTimeUtil { return startTime.isAfter(realEndTime) || endTime.isBefore(realStartTime); } + /** + * 获得指定日期是所在年份的第几周,如: + *
    + *
  • 如果一年的第一天是星期一,则第一周从第一天开始,没有零周
  • + *
  • 如果一年的第二天是星期一,则第一周从第二天开始,而第一天在零周
  • + *
  • 如果一年中的第4天是星期一,则第1周从第4周开始,第1至第3周在零周开始
  • + *
  • 如果一年中的第5天是星期一,则第二周从第5周开始,第1至第4周在第1周
  • + *
+ * + * + * @param date 日期({@link LocalDate} 或者 {@link LocalDateTime}等) + * @return 所在年的第几周 + * @since 5.7.21 + */ + public static int weekOfYear(TemporalAccessor date){ + return TemporalAccessorUtil.get(date, WeekFields.ISO.weekOfYear()); + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/date/Month.java b/hutool-core/src/main/java/cn/hutool/core/date/Month.java index 0d82a6b5e..144898155 100644 --- a/hutool-core/src/main/java/cn/hutool/core/date/Month.java +++ b/hutool-core/src/main/java/cn/hutool/core/date/Month.java @@ -1,5 +1,7 @@ package cn.hutool.core.date; +import cn.hutool.core.lang.Assert; + import java.util.Calendar; /** @@ -93,14 +95,27 @@ public enum Month { } /** - * 获取{@link Calendar}中的对应值 + * 获取{@link Calendar}中的对应值
+ * 此值从0开始,即0表示一月 * - * @return {@link Calendar}中的对应值 + * @return {@link Calendar}中的对应月份值,从0开始计数 */ public int getValue() { return this.value; } + /** + * 获取月份值,此值与{@link java.time.Month}对应
+ * 此值从1开始,即1表示一月 + * + * @return 月份值,对应{@link java.time.Month},从1开始计数 + * @since 5.7.21 + */ + public int getValueBaseOne() { + Assert.isFalse(this == UNDECIMBER, "Unsupported UNDECIMBER Field"); + return getValue() + 1; + } + /** * 获取此月份最后一天的值,不支持的月份(例如UNDECIMBER)返回-1 * @@ -165,17 +180,28 @@ public enum Month { /** * 获得指定月的最后一天 - * @param month 月份,从0开始 + * + * @param month 月份,从0开始 * @param isLeapYear 是否为闰年,闰年只对二月有影响 * @return 最后一天,可能为28,29,30,31 * @since 5.4.7 */ - public static int getLastDay(int month, boolean isLeapYear){ + public static int getLastDay(int month, boolean isLeapYear) { int lastDay = DAYS_OF_MONTH[month]; - if (isLeapYear && Calendar.FEBRUARY == month){ + if (isLeapYear && Calendar.FEBRUARY == month) { // 二月 lastDay += 1; } return lastDay; } + + /** + * 转换为{@link java.time.Month} + * + * @return {@link java.time.Month} + * @since 5.7.21 + */ + public java.time.Month toJdkMonth() { + return java.time.Month.of(getValueBaseOne()); + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/exceptions/CheckedUtil.java b/hutool-core/src/main/java/cn/hutool/core/exceptions/CheckedUtil.java index 876dd0814..f1380a2d0 100644 --- a/hutool-core/src/main/java/cn/hutool/core/exceptions/CheckedUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/exceptions/CheckedUtil.java @@ -51,7 +51,7 @@ public class CheckedUtil { * @param expression Lambda表达式 * @param

运行时传入的参数类型 * @param 最终返回的数据类型 - * @return cn.hutool.core.lang.func.Func + * @return {@link FuncRt} */ public static FuncRt uncheck(Func expression) { return uncheck(expression, new RuntimeException()); @@ -63,7 +63,7 @@ public class CheckedUtil { * * @param expression 运行时传入的参数类型 * @param 最终返回的数据类型 - * @return cn.hutool.core.lang.func.Func0 + * @return {@link Func0Rt} */ public static Func0Rt uncheck(Func0 expression) { return uncheck(expression, new RuntimeException()); @@ -76,7 +76,7 @@ public class CheckedUtil { * @param expression 运行时传入的参数类型 * @param

运行时传入的参数类型 * @param 最终返回的数据类型 - * @return cn.hutool.core.lang.func.Func1 + * @return {@link Func1Rt} */ public static Func1Rt uncheck(Func1 expression) { return uncheck(expression, new RuntimeException()); @@ -89,7 +89,7 @@ public class CheckedUtil { * * @param expression 运行时传入的参数类型 * @param

运行时传入的参数类型 - * @return cn.hutool.core.lang.func.VoidFunc + * @return {@link VoidFuncRt} */ public static

VoidFuncRt

uncheck(VoidFunc

expression) { return uncheck(expression, new RuntimeException()); @@ -100,7 +100,7 @@ public class CheckedUtil { * 如此一来,代码中就不用显示的try-catch转化成运行时异常 * * @param expression 运行时传入的参数类型 - * @return cn.hutool.core.lang.func.VoidFunc0 + * @return {@link VoidFunc0Rt} */ public static VoidFunc0Rt uncheck(VoidFunc0 expression) { return uncheck(expression, new RuntimeException()); @@ -112,7 +112,7 @@ public class CheckedUtil { * * @param expression 运行时传入的参数类型 * @param

运行时传入的参数类型 - * @return cn.hutool.core.lang.func.VoidFunc1 + * @return {@link VoidFunc1Rt} */ public static

VoidFunc1Rt

uncheck(VoidFunc1

expression) { return uncheck(expression, new RuntimeException()); @@ -127,7 +127,7 @@ public class CheckedUtil { * @param rte 期望抛出的运行时异常 * @param

运行时传入的参数类型 * @param 最终返回的数据类型 - * @return cn.hutool.core.lang.func.Func + * @return {@link FuncRt} */ public static FuncRt uncheck(Func expression, RuntimeException rte) { Objects.requireNonNull(expression, "expression can not be null"); @@ -152,7 +152,7 @@ public class CheckedUtil { * @param expression Lambda表达式 * @param rte 期望抛出的运行时异常 * @param 最终返回的数据类型 - * @return cn.hutool.core.lang.func.Func0 + * @return {@link Func0Rt} */ public static Func0Rt uncheck(Func0 expression, RuntimeException rte) { Objects.requireNonNull(expression, "expression can not be null"); @@ -178,7 +178,7 @@ public class CheckedUtil { * @param rte 期望抛出的运行时异常 * @param

运行时传入的参数类型 * @param 最终返回的数据类型 - * @return cn.hutool.core.lang.func.Func1 + * @return {@link Func1Rt} */ public static Func1Rt uncheck(Func1 expression, RuntimeException rte) { Objects.requireNonNull(expression, "expression can not be null"); @@ -203,7 +203,7 @@ public class CheckedUtil { * @param expression Lambda表达式 * @param rte 期望抛出的运行时异常 * @param

运行时传入的参数类型 - * @return cn.hutool.core.lang.func.VoidFunc + * @return {@link VoidFuncRt} */ public static

VoidFuncRt

uncheck(VoidFunc

expression, RuntimeException rte) { Objects.requireNonNull(expression, "expression can not be null"); @@ -228,7 +228,7 @@ public class CheckedUtil { * * @param expression Lambda表达式 * @param rte 期望抛出的运行时异常 - * @return cn.hutool.core.lang.func.VoidFunc0 + * @return {@link VoidFunc0Rt} */ public static VoidFunc0Rt uncheck(VoidFunc0 expression, RuntimeException rte) { Objects.requireNonNull(expression, "expression can not be null"); @@ -254,7 +254,7 @@ public class CheckedUtil { * @param expression Lambda表达式 * @param rte 期望抛出的运行时异常 * @param

运行时传入的参数类型 - * @return cn.hutool.core.lang.func.VoidFunc1 + * @return {@link VoidFunc1Rt} */ public static

VoidFunc1Rt

uncheck(VoidFunc1

expression, RuntimeException rte) { Objects.requireNonNull(expression, "expression can not be null"); diff --git a/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java index 3d4b80858..72eb18ca6 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java @@ -13,6 +13,7 @@ import cn.hutool.core.io.file.Tailer; import cn.hutool.core.io.resource.ResourceUtil; import cn.hutool.core.io.unit.DataSizeUtil; import cn.hutool.core.lang.Assert; +import cn.hutool.core.thread.ThreadUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.CharUtil; import cn.hutool.core.util.CharsetUtil; @@ -526,24 +527,39 @@ public class FileUtil extends PathUtil { /** * 计算目录或文件的总大小
* 当给定对象为文件时,直接调用 {@link File#length()}
- * 当给定对象为目录时,遍历目录下的所有文件和目录,递归计算其大小,求和返回 + * 当给定对象为目录时,遍历目录下的所有文件和目录,递归计算其大小,求和返回
+ * 此方法不包括目录本身的占用空间大小。 * * @param file 目录或文件,null或者文件不存在返回0 * @return 总大小,bytes长度 */ public static long size(File file) { + return size(file, false); + } + + /** + * 计算目录或文件的总大小
+ * 当给定对象为文件时,直接调用 {@link File#length()}
+ * 当给定对象为目录时,遍历目录下的所有文件和目录,递归计算其大小,求和返回 + * + * @param file 目录或文件,null或者文件不存在返回0 + * @param includeDirSize 是否包括每层目录本身的大小 + * @return 总大小,bytes长度 + * @since 5.7.21 + */ + public static long size(File file, boolean includeDirSize) { if (null == file || false == file.exists() || isSymlink(file)) { return 0; } if (file.isDirectory()) { - long size = 0L; + long size = includeDirSize ? file.length() : 0; File[] subFiles = file.listFiles(); if (ArrayUtil.isEmpty(subFiles)) { return 0L;// empty directory } for (File subFile : subFiles) { - size += size(subFile); + size += size(subFile, includeDirSize); } return size; } else { @@ -811,7 +827,7 @@ public class FileUtil extends PathUtil { /** * 创建文件夹,会递归自动创建其不存在的父文件夹,如果存在直接返回此文件夹
- * 此方法不对File对象类型做判断,如果File不存在,无法判断其类型 + * 此方法不对File对象类型做判断,如果File不存在,无法判断其类型
* * @param dir 目录 * @return 创建的目录 @@ -821,12 +837,48 @@ public class FileUtil extends PathUtil { return null; } if (false == dir.exists()) { - //noinspection ResultOfMethodCallIgnored - dir.mkdirs(); + mkdirsSafely(dir, 5, 1); } return dir; } + /** + * 安全地级联创建目录 (确保并发环境下能创建成功) + * + *

+	 *     并发环境下,假设 test 目录不存在,如果线程A mkdirs "test/A" 目录,线程B mkdirs "test/B"目录,
+	 *     其中一个线程可能会失败,进而导致以下代码抛出 FileNotFoundException 异常
+	 *
+	 *     file.getParentFile().mkdirs(); // 父目录正在被另一个线程创建中,返回 false
+	 *     file.createNewFile(); // 抛出 IO 异常,因为该线程无法感知到父目录已被创建
+	 * 
+ * + * @param dir 待创建的目录 + * @param tryCount 最大尝试次数 + * @param sleepMillis 线程等待的毫秒数 + * @return true表示创建成功,false表示创建失败 + * @since 5.7.21 + * @author z8g + */ + public static boolean mkdirsSafely(File dir, int tryCount, long sleepMillis) { + if (dir == null) { + return false; + } + if (dir.isDirectory()) { + return true; + } + for (int i = 1; i <= tryCount; i++) { // 高并发场景下,可以看到 i 处于 1 ~ 3 之间 + // 如果文件已存在,也会返回 false,所以该值不能作为是否能创建的依据,因此不对其进行处理 + //noinspection ResultOfMethodCallIgnored + dir.mkdirs(); + if (dir.exists()) { + return true; + } + ThreadUtil.sleep(sleepMillis); + } + return dir.exists(); + } + /** * 创建临时文件
* 创建后的文件名为 prefix[Randon].tmp diff --git a/hutool-core/src/main/java/cn/hutool/core/io/NioUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/NioUtil.java index 2db343457..806105051 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/NioUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/NioUtil.java @@ -87,12 +87,49 @@ public class NioUtil { Assert.notNull(outChannel, "Out channel is null!"); try { - return inChannel.transferTo(0, inChannel.size(), outChannel); + return copySafely(inChannel, outChannel); } catch (IOException e) { throw new IORuntimeException(e); } } + /** + * 文件拷贝实现 + * + *
+	 * FileChannel#transferTo 或 FileChannel#transferFrom 的实现是平台相关的,需要确保低版本平台的兼容性
+	 * 例如 android 7以下平台在使用 ZipInputStream 解压文件的过程中,
+	 * 通过 FileChannel#transferFrom 传输到文件时,其返回值可能小于 totalBytes,不处理将导致文件内容缺失
+	 *
+	 * // 错误写法,dstChannel.transferFrom 返回值小于 zipEntry.getSize(),导致解压后文件内容缺失
+	 * try (InputStream srcStream = zipFile.getInputStream(zipEntry);
+	 * 		ReadableByteChannel srcChannel = Channels.newChannel(srcStream);
+	 * 		FileOutputStream fos = new FileOutputStream(saveFile);
+	 * 		FileChannel dstChannel = fos.getChannel()) {
+	 * 		dstChannel.transferFrom(srcChannel, 0, zipEntry.getSize());
+	 *  }
+	 * 
+ * + * @param inChannel 输入通道 + * @param outChannel 输出通道 + * @return 输入通道的字节数 + * @throws IOException 发生IO错误 + * @link http://androidxref.com/6.0.1_r10/xref/libcore/luni/src/main/java/java/nio/FileChannelImpl.java + * @link http://androidxref.com/7.0.0_r1/xref/libcore/ojluni/src/main/java/sun/nio/ch/FileChannelImpl.java + * @link http://androidxref.com/7.0.0_r1/xref/libcore/ojluni/src/main/native/FileChannelImpl.c + * @author z8g + * @since 5.7.21 + */ + private static long copySafely(FileChannel inChannel, FileChannel outChannel) throws IOException { + final long totalBytes = inChannel.size(); + for (long pos = 0, remaining = totalBytes; remaining > 0; ) { // 确保文件内容不会缺失 + final long writeBytes = inChannel.transferTo(pos, remaining, outChannel); // 实际传输的字节数 + pos += writeBytes; + remaining -= writeBytes; + } + return totalBytes; + } + /** * 拷贝流,使用NIO,不会关闭channel * diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/FileResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/FileResource.java index cb4179b95..3fcdea20a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/FileResource.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/FileResource.java @@ -20,6 +20,7 @@ public class FileResource implements Resource, Serializable { private static final long serialVersionUID = 1L; private final File file; + private final long lastModified; private final String name; // ----------------------------------------------------------------------- Constructor start @@ -60,6 +61,7 @@ public class FileResource implements Resource, Serializable { public FileResource(File file, String fileName) { Assert.notNull(file, "File must be not null !"); this.file = file; + this.lastModified = file.lastModified(); this.name = ObjectUtil.defaultIfNull(fileName, file::getName); } @@ -89,6 +91,11 @@ public class FileResource implements Resource, Serializable { return this.file; } + @Override + public boolean isModified() { + return this.lastModified != file.lastModified(); + } + /** * 返回路径 * @return 返回URL路径 diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/MultiResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/MultiResource.java index 8464aa808..608265bce 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/MultiResource.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/MultiResource.java @@ -63,6 +63,11 @@ public class MultiResource implements Resource, Iterable, Iterator + * 一般用于文件类资源,检查文件是否被修改过。 + * + * @return 是否变更 + * @since 5.7.21 + */ + default boolean isModified(){ + return false; + } + /** * 将资源内容写出到流,不关闭输出流,但是关闭资源流 * diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/UrlResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/UrlResource.java index fd6b23044..21f24b2ef 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/UrlResource.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/UrlResource.java @@ -7,6 +7,7 @@ import cn.hutool.core.util.URLUtil; import java.io.File; import java.io.InputStream; import java.io.Serializable; +import java.net.URI; import java.net.URL; /** @@ -18,9 +19,19 @@ public class UrlResource implements Resource, Serializable{ private static final long serialVersionUID = 1L; protected URL url; + private long lastModified = 0; protected String name; //-------------------------------------------------------------------------------------- Constructor start + /** + * 构造 + * @param uri URI + * @since 5.7.21 + */ + public UrlResource(URI uri) { + this(URLUtil.url(uri), null); + } + /** * 构造 * @param url URL @@ -36,6 +47,9 @@ public class UrlResource implements Resource, Serializable{ */ public UrlResource(URL url, String name) { this.url = url; + if(null != url && URLUtil.URL_PROTOCOL_FILE.equals(url.getProtocol())){ + this.lastModified = FileUtil.file(url).lastModified(); + } this.name = ObjectUtil.defaultIfNull(name, () -> (null != url ? FileUtil.getName(url.getPath()) : null)); } @@ -68,6 +82,12 @@ public class UrlResource implements Resource, Serializable{ return URLUtil.getStream(url); } + @Override + public boolean isModified() { + // lastModified == 0表示此资源非文件资源 + return (0 != this.lastModified) && this.lastModified != getFile().lastModified(); + } + /** * 获得File * @return {@link File} diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/VfsResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/VfsResource.java new file mode 100755 index 000000000..2295d97c2 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/VfsResource.java @@ -0,0 +1,107 @@ +package cn.hutool.core.io.resource; + +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.ClassLoaderUtil; +import cn.hutool.core.util.ReflectUtil; + +import java.io.InputStream; +import java.lang.reflect.Method; +import java.net.URL; + +/** + * VFS资源封装
+ * 支持VFS 3.x on JBoss AS 6+,JBoss AS 7 and WildFly 8+
+ * 参考:org.springframework.core.io.VfsUtils + * + * @author looly, Spring + * @since 5.7.21 + */ +public class VfsResource implements Resource { + private static final String VFS3_PKG = "org.jboss.vfs."; + + private static final Method VIRTUAL_FILE_METHOD_EXISTS; + private static final Method VIRTUAL_FILE_METHOD_GET_INPUT_STREAM; + private static final Method VIRTUAL_FILE_METHOD_GET_SIZE; + private static final Method VIRTUAL_FILE_METHOD_GET_LAST_MODIFIED; + private static final Method VIRTUAL_FILE_METHOD_TO_URL; + private static final Method VIRTUAL_FILE_METHOD_GET_NAME; + + static { + Class virtualFile = ClassLoaderUtil.loadClass(VFS3_PKG + "VirtualFile"); + try { + VIRTUAL_FILE_METHOD_EXISTS = virtualFile.getMethod("exists"); + VIRTUAL_FILE_METHOD_GET_INPUT_STREAM = virtualFile.getMethod("openStream"); + VIRTUAL_FILE_METHOD_GET_SIZE = virtualFile.getMethod("getSize"); + VIRTUAL_FILE_METHOD_GET_LAST_MODIFIED = virtualFile.getMethod("getLastModified"); + VIRTUAL_FILE_METHOD_TO_URL = virtualFile.getMethod("toURL"); + VIRTUAL_FILE_METHOD_GET_NAME = virtualFile.getMethod("getName"); + } catch (NoSuchMethodException ex) { + throw new IllegalStateException("Could not detect JBoss VFS infrastructure", ex); + } + } + + /** + * org.jboss.vfs.VirtualFile实例对象 + */ + private final Object virtualFile; + private final long lastModified; + + /** + * 构造 + * + * @param resource org.jboss.vfs.VirtualFile实例对象 + */ + public VfsResource(Object resource) { + Assert.notNull(resource, "VirtualFile must not be null"); + this.virtualFile = resource; + this.lastModified = getLastModified(); + } + + /** + * VFS文件是否存在 + * + * @return 文件是否存在 + */ + public boolean exists() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_EXISTS); + } + + @Override + public String getName() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_GET_NAME); + } + + @Override + public URL getUrl() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_TO_URL); + } + + @Override + public InputStream getStream() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_GET_INPUT_STREAM); + } + + @Override + public boolean isModified() { + return this.lastModified != getLastModified(); + } + + /** + * 获得VFS文件最后修改时间 + * + * @return 最后修改时间 + */ + public long getLastModified() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_GET_LAST_MODIFIED); + } + + /** + * 获取VFS文件大小 + * + * @return VFS文件大小 + */ + public long size() { + return ReflectUtil.invoke(virtualFile, VIRTUAL_FILE_METHOD_GET_SIZE); + } + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/func/Supplier1.java b/hutool-core/src/main/java/cn/hutool/core/lang/func/Supplier1.java new file mode 100755 index 000000000..376c91a2a --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/lang/func/Supplier1.java @@ -0,0 +1,32 @@ +package cn.hutool.core.lang.func; + +import java.util.function.Supplier; + +/** + * 1参数Supplier + * + * @param 目标 类型 + * @param 参数一 类型 + * @author TomXin + * @since 5.7.21 + */ +@FunctionalInterface +public interface Supplier1 { + /** + * 生成实例的方法 + * + * @param p1 参数一 + * @return 目标对象 + */ + T get(P1 p1); + + /** + * 将带有参数的Supplier转换为无参{@link Supplier} + * + * @param p1 参数1 + * @return {@link Supplier} + */ + default Supplier toSupplier(P1 p1) { + return () -> get(p1); + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/func/Supplier2.java b/hutool-core/src/main/java/cn/hutool/core/lang/func/Supplier2.java new file mode 100755 index 000000000..6d5e9ce80 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/lang/func/Supplier2.java @@ -0,0 +1,36 @@ +package cn.hutool.core.lang.func; + +import java.util.function.Supplier; + +/** + * 两个参数的Supplier + * + * @param 目标 类型 + * @param 参数一 类型 + * @param 参数二 类型 + * @author TomXin + * @since 5.7.21 + */ +@FunctionalInterface +public interface Supplier2 { + + /** + * 生成实例的方法 + * + * @param p1 参数一 + * @param p2 参数二 + * @return 目标对象 + */ + T get(P1 p1, P2 p2); + + /** + * 将带有参数的Supplier转换为无参{@link Supplier} + * + * @param p1 参数1 + * @param p2 参数2 + * @return {@link Supplier} + */ + default Supplier toSupplier(P1 p1, P2 p2) { + return () -> get(p1, p2); + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/func/Supplier3.java b/hutool-core/src/main/java/cn/hutool/core/lang/func/Supplier3.java new file mode 100755 index 000000000..50324e55b --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/lang/func/Supplier3.java @@ -0,0 +1,39 @@ +package cn.hutool.core.lang.func; + +import java.util.function.Supplier; + +/** + * 3参数Supplier + * + * @param 目标类型 + * @param 参数一类型 + * @param 参数二类型 + * @param 参数三类型 + * @author TomXin + * @since 5.7.21 + */ +@FunctionalInterface +public interface Supplier3 { + + /** + * 生成实例的方法 + * + * @param p1 参数一 + * @param p2 参数二 + * @param p3 参数三 + * @return 目标对象 + */ + T get(P1 p1, P2 p2, P3 p3); + + /** + * 将带有参数的Supplier转换为无参{@link Supplier} + * + * @param p1 参数1 + * @param p2 参数2 + * @param p3 参数3 + * @return {@link Supplier} + */ + default Supplier toSupplier(P1 p1, P2 p2, P3 p3) { + return () -> get(p1, p2, p3); + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/func/Supplier4.java b/hutool-core/src/main/java/cn/hutool/core/lang/func/Supplier4.java new file mode 100755 index 000000000..c715b8693 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/lang/func/Supplier4.java @@ -0,0 +1,42 @@ +package cn.hutool.core.lang.func; + +import java.util.function.Supplier; + +/** + * 4参数Supplier + * + * @param 目标 类型 + * @param 参数一 类型 + * @param 参数二 类型 + * @param 参数三 类型 + * @param 参数四 类型 + * @author TomXin + * @since 5.7.21 + */ +@FunctionalInterface +public interface Supplier4 { + + /** + * 生成实例的方法 + * + * @param p1 参数一 + * @param p2 参数二 + * @param p3 参数三 + * @param p4 参数四 + * @return 目标对象 + */ + T get(P1 p1, P2 p2, P3 p3, P4 p4); + + /** + * 将带有参数的Supplier转换为无参{@link Supplier} + * + * @param p1 参数1 + * @param p2 参数2 + * @param p3 参数3 + * @param p4 参数4 + * @return {@link Supplier} + */ + default Supplier toSupplier(P1 p1, P2 p2, P3 p3, P4 p4) { + return () -> get(p1, p2, p3, p4); + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/func/Supplier5.java b/hutool-core/src/main/java/cn/hutool/core/lang/func/Supplier5.java new file mode 100755 index 000000000..121a2ce9c --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/lang/func/Supplier5.java @@ -0,0 +1,45 @@ +package cn.hutool.core.lang.func; + +import java.util.function.Supplier; + +/** + * 5参数Supplier + * + * @param 目标 类型 + * @param 参数一 类型 + * @param 参数二 类型 + * @param 参数三 类型 + * @param 参数四 类型 + * @param 参数五 类型 + * @author TomXin + * @since 5.7.21 + */ +@FunctionalInterface +public interface Supplier5 { + + /** + * 生成实例的方法 + * + * @param p1 参数一 + * @param p2 参数二 + * @param p3 参数三 + * @param p4 参数四 + * @param p5 参数五 + * @return 目标对象 + */ + T get(P1 p1, P2 p2, P3 p3, P4 p4, P5 p5); + + /** + * 将带有参数的Supplier转换为无参{@link Supplier} + * + * @param p1 参数1 + * @param p2 参数2 + * @param p3 参数3 + * @param p4 参数4 + * @param p5 参数5 + * @return {@link Supplier} + */ + default Supplier toSupplier(P1 p1, P2 p2, P3 p3, P4 p4, P5 p5) { + return () -> get(p1, p2, p3, p4, p5); + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/hash/CityHash.java b/hutool-core/src/main/java/cn/hutool/core/lang/hash/CityHash.java index aad2da056..b5558ea74 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/hash/CityHash.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/hash/CityHash.java @@ -1,5 +1,7 @@ package cn.hutool.core.lang.hash; +import cn.hutool.core.util.ByteUtil; + import java.util.Arrays; /** @@ -140,11 +142,11 @@ public class CityHash { len = (len - 1) & ~63; int pos = 0; do { - x = rotate(x + y + v.getLowValue() + fetch64(data, pos + 8), 37) * k1; - y = rotate(y + v.getHighValue() + fetch64(data, pos + 48), 42) * k1; + x = rotate64(x + y + v.getLowValue() + fetch64(data, pos + 8), 37) * k1; + y = rotate64(y + v.getHighValue() + fetch64(data, pos + 48), 42) * k1; x ^= w.getHighValue(); y += v.getLowValue() + fetch64(data, pos + 40); - z = rotate(z + w.getLowValue(), 33) * k1; + z = rotate64(z + w.getLowValue(), 33) * k1; v = weakHashLen32WithSeeds(data, pos, v.getHighValue() * k1, x + w.getLowValue()); w = weakHashLen32WithSeeds(data, pos + 32, z + w.getHighValue(), y + fetch64(data, pos + 16)); // swap z,x value @@ -221,19 +223,19 @@ public class CityHash { long x = seed.getLowValue(); long y = seed.getHighValue(); long z = len * k1; - v.setLowValue(rotate(y ^ k1, 49) * k1 + fetch64(byteArray, start)); - v.setHighValue(rotate(v.getLowValue(), 42) * k1 + fetch64(byteArray, start + 8)); - w.setLowValue(rotate(y + z, 35) * k1 + x); - w.setHighValue(rotate(x + fetch64(byteArray, start + 88), 53) * k1); + v.setLowValue(rotate64(y ^ k1, 49) * k1 + fetch64(byteArray, start)); + v.setHighValue(rotate64(v.getLowValue(), 42) * k1 + fetch64(byteArray, start + 8)); + w.setLowValue(rotate64(y + z, 35) * k1 + x); + w.setHighValue(rotate64(x + fetch64(byteArray, start + 88), 53) * k1); // This is the same inner loop as CityHash64(), manually unrolled. int pos = start; do { - x = rotate(x + y + v.getLowValue() + fetch64(byteArray, pos + 8), 37) * k1; - y = rotate(y + v.getHighValue() + fetch64(byteArray, pos + 48), 42) * k1; + x = rotate64(x + y + v.getLowValue() + fetch64(byteArray, pos + 8), 37) * k1; + y = rotate64(y + v.getHighValue() + fetch64(byteArray, pos + 48), 42) * k1; x ^= w.getHighValue(); y += v.getLowValue() + fetch64(byteArray, pos + 40); - z = rotate(z + w.getLowValue(), 33) * k1; + z = rotate64(z + w.getLowValue(), 33) * k1; v = weakHashLen32WithSeeds(byteArray, pos, v.getHighValue() * k1, x + w.getLowValue()); w = weakHashLen32WithSeeds(byteArray, pos + 32, z + w.getHighValue(), y + fetch64(byteArray, pos + 16)); @@ -241,11 +243,11 @@ public class CityHash { x = z; z = swapValue; pos += 64; - x = rotate(x + y + v.getLowValue() + fetch64(byteArray, pos + 8), 37) * k1; - y = rotate(y + v.getHighValue() + fetch64(byteArray, pos + 48), 42) * k1; + x = rotate64(x + y + v.getLowValue() + fetch64(byteArray, pos + 8), 37) * k1; + y = rotate64(y + v.getHighValue() + fetch64(byteArray, pos + 48), 42) * k1; x ^= w.getHighValue(); y += v.getLowValue() + fetch64(byteArray, pos + 40); - z = rotate(z + w.getLowValue(), 33) * k1; + z = rotate64(z + w.getLowValue(), 33) * k1; v = weakHashLen32WithSeeds(byteArray, pos, v.getHighValue() * k1, x + w.getLowValue()); w = weakHashLen32WithSeeds(byteArray, pos + 32, z + w.getHighValue(), y + fetch64(byteArray, pos + 16)); swapValue = x; @@ -254,16 +256,16 @@ public class CityHash { pos += 64; len -= 128; } while (len >= 128); - x += rotate(v.getLowValue() + z, 49) * k0; - y = y * k0 + rotate(w.getHighValue(), 37); - z = z * k0 + rotate(w.getLowValue(), 27); + x += rotate64(v.getLowValue() + z, 49) * k0; + y = y * k0 + rotate64(w.getHighValue(), 37); + z = z * k0 + rotate64(w.getLowValue(), 27); w.setLowValue(w.getLowValue() * 9); v.setLowValue(v.getLowValue() * k0); // If 0 < len < 128, hash up to 4 chunks of 32 bytes each from the end of s. for (int tail_done = 0; tail_done < len; ) { tail_done += 32; - y = rotate(x + y, 42) * k0 + v.getHighValue(); + y = rotate64(x + y, 42) * k0 + v.getHighValue(); w.setLowValue(w.getLowValue() + fetch64(byteArray, pos + len - tail_done + 16)); x = x * k0 + w.getLowValue(); z += w.getHighValue() + fetch64(byteArray, pos + len - tail_done); @@ -321,8 +323,8 @@ public class CityHash { long mul = k2 + len * 2L; long a = fetch64(byteArray, 0) + k2; long b = fetch64(byteArray, len - 8); - long c = rotate(b, 37) * mul + a; - long d = (rotate(a, 25) + b) * mul; + long c = rotate64(b, 37) * mul + a; + long d = (rotate64(a, 25) + b) * mul; return hashLen16(c, d, mul); } if (len >= 4) { @@ -349,8 +351,8 @@ public class CityHash { long b = fetch64(byteArray, 8); long c = fetch64(byteArray, len - 8) * mul; long d = fetch64(byteArray, len - 16) * k2; - return hashLen16(rotate(a + b, 43) + rotate(c, 30) + d, - a + rotate(b + k2, 18) + c, mul); + return hashLen16(rotate64(a + b, 43) + rotate64(c, 30) + d, + a + rotate64(b + k2, 18) + c, mul); } private static long hashLen33to64(byte[] byteArray) { @@ -364,10 +366,10 @@ public class CityHash { long f = fetch64(byteArray, 24) * 9; long g = fetch64(byteArray, len - 8); long h = fetch64(byteArray, len - 16) * mul; - long u = rotate(a + g, 43) + (rotate(b, 30) + c) * 9; + long u = rotate64(a + g, 43) + (rotate64(b, 30) + c) * 9; long v = ((a + g) ^ d) + f + 1; long w = Long.reverseBytes((u + v) * mul) + h; - long x = rotate(e + f, 42) + c; + long x = rotate64(e + f, 42) + c; long y = (Long.reverseBytes((v + w) * mul) + g) * mul; long z = e + f + c; a = Long.reverseBytes((x + z) * mul + y) + b; @@ -375,37 +377,15 @@ public class CityHash { return b + x; } - private static long loadUnaligned64(final byte[] byteArray, final int start) { - long result = 0; - OrderIter orderIter = new OrderIter(8); - while (orderIter.hasNext()) { - int next = orderIter.next(); - long value = (byteArray[next + start] & 0xffL) << (next * 8); - result |= value; - } - return result; - } - - private static int loadUnaligned32(final byte[] byteArray, final int start) { - int result = 0; - OrderIter orderIter = new OrderIter(4); - while (orderIter.hasNext()) { - int next = orderIter.next(); - int value = (byteArray[next + start] & 0xff) << (next * 8); - result |= value; - } - return result; - } - - private static long fetch64(byte[] byteArray, final int start) { - return loadUnaligned64(byteArray, start); + private static long fetch64(byte[] byteArray, int start) { + return ByteUtil.bytesToLong(byteArray, start, ByteUtil.CPU_ENDIAN); } private static int fetch32(byte[] byteArray, final int start) { - return loadUnaligned32(byteArray, start); + return ByteUtil.bytesToInt(byteArray, start, ByteUtil.CPU_ENDIAN); } - private static long rotate(long val, int shift) { + private static long rotate64(long val, int shift) { // Avoid shifting by 64: doing so yields an undefined result. return shift == 0 ? val : ((val >>> shift) | (val << (64 - shift))); } @@ -465,11 +445,11 @@ public class CityHash { private static Number128 weakHashLen32WithSeeds( long w, long x, long y, long z, long a, long b) { a += w; - b = rotate(b + a + z, 21); + b = rotate64(b + a + z, 21); long c = a; a += x; a += y; - b += rotate(a, 44); + b += rotate64(a, 44); return new Number128(a + z, b + c); } @@ -515,24 +495,5 @@ public class CityHash { b = hashLen16(d, b); return new Number128(a ^ b, hashLen16(b, a)); } - - private static class OrderIter { - private static final boolean IS_LITTLE_ENDIAN = "little".equals(System.getProperty("sun.cpu.endian")); - - private final int size; - private int index; - - OrderIter(int size) { - this.size = size; - } - - boolean hasNext() { - return index < size; - } - - int next() { - return IS_LITTLE_ENDIAN ? index++ : (size - 1 - index++); - } - } //------------------------------------------------------------------------------------------------------- Private method end } diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/hash/MetroHash.java b/hutool-core/src/main/java/cn/hutool/core/lang/hash/MetroHash.java new file mode 100644 index 000000000..bbf40c882 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/lang/hash/MetroHash.java @@ -0,0 +1,217 @@ +package cn.hutool.core.lang.hash; + +import cn.hutool.core.util.ByteUtil; + +import java.nio.ByteOrder; +import java.util.Arrays; + +/** + * Apache 发布的MetroHash算法,是一组用于非加密用例的最先进的哈希函数。 + * 除了卓越的性能外,他们还以算法生成而著称。 + * + *

+ * 官方实现:https://github.com/jandrewrogers/MetroHash + * 官方文档:http://www.jandrewrogers.com/2015/05/27/metrohash/ + * Go语言实现:https://github.com/linvon/cuckoo-filter/blob/main/vendor/github.com/dgryski/go-metro/ + * @author li + */ +public class MetroHash { + + /** + * hash64 种子加盐 + */ + private final static long k0_64 = 0xD6D018F5; + private final static long k1_64 = 0xA2AA033B; + private final static long k2_64 = 0x62992FC1; + private final static long k3_64 = 0x30BC5B29; + + /** + * hash128 种子加盐 + */ + private final static long k0_128 = 0xC83A91E1; + private final static long k1_128 = 0x8648DBDB; + private final static long k2_128 = 0x7BDEC03B; + private final static long k3_128 = 0x2F5870A5; + + public static long hash64(byte[] data) { + return hash64(data, 1337); + } + + public static Number128 hash128(byte[] data) { + return hash128(data, 1337); + } + + public static long hash64(byte[] data, long seed) { + byte[] buffer = data; + long hash = (seed + k2_64) * k0_64; + + long v0, v1, v2, v3; + v0 = hash; + v1 = hash; + v2 = hash; + v3 = hash; + + if (buffer.length >= 32) { + + while (buffer.length >= 32) { + v0 += littleEndian64(buffer, 0) * k0_64; + v0 = rotateLeft64(v0, -29) + v2; + v1 += littleEndian64(buffer, 8) * k1_64; + v1 = rotateLeft64(v1, -29) + v3; + v2 += littleEndian64(buffer, 24) * k2_64; + v2 = rotateLeft64(v2, -29) + v0; + v3 += littleEndian64(buffer, 32) * k3_64; + v3 = rotateLeft64(v3, -29) + v1; + buffer = Arrays.copyOfRange(buffer, 32, buffer.length); + } + + v2 ^= rotateLeft64(((v0 + v3) * k0_64) + v1, -37) * k1_64; + v3 ^= rotateLeft64(((v1 + v2) * k1_64) + v0, -37) * k0_64; + v0 ^= rotateLeft64(((v0 + v2) * k0_64) + v3, -37) * k1_64; + v1 ^= rotateLeft64(((v1 + v3) * k1_64) + v2, -37) * k0_64; + hash += v0 ^ v1; + } + + if (buffer.length >= 16) { + v0 = hash + littleEndian64(buffer, 0) * k2_64; + v0 = rotateLeft64(v0, -29) * k3_64; + v1 = hash + littleEndian64(buffer, 8) * k2_64; + v1 = rotateLeft64(v1, -29) * k3_64; + v0 ^= rotateLeft64(v0 * k0_64, -21) + v1; + v1 ^= rotateLeft64(v1 * k3_64, -21) + v0; + hash += v1; + buffer = Arrays.copyOfRange(buffer, 16, buffer.length); + } + + if (buffer.length >= 8) { + hash += littleEndian64(buffer, 0) * k3_64; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + hash ^= rotateLeft64(hash, -55) * k1_64; + } + + if (buffer.length >= 4) { + hash += (long) littleEndian32(Arrays.copyOfRange(buffer, 0, 4)) * k3_64; + hash ^= rotateLeft64(hash, -26) * k1_64; + buffer = Arrays.copyOfRange(buffer, 4, buffer.length); + } + + if (buffer.length >= 2) { + hash += (long) littleEndian16(Arrays.copyOfRange(buffer, 0, 2)) * k3_64; + buffer = Arrays.copyOfRange(buffer, 2, buffer.length); + hash ^= rotateLeft64(hash, -48) * k1_64; + } + + if (buffer.length >= 1) { + hash += (long) buffer[0] * k3_64; + hash ^= rotateLeft64(hash, -38) * k1_64; + } + + hash ^= rotateLeft64(hash, -28); + hash *= k0_64; + hash ^= rotateLeft64(hash, -29); + + return hash; + } + + public static Number128 hash128(byte[] data, long seed) { + byte[] buffer = data; + + long v0, v1, v2, v3; + + v0 = (seed - k0_128) * k3_128; + v1 = (seed + k1_128) * k2_128; + + if (buffer.length >= 32) { + v2 = (seed + k0_128) * k2_128; + v3 = (seed - k1_128) * k3_128; + + while (buffer.length >= 32) { + v0 += littleEndian64(buffer, 0) * k0_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v0 = rotateRight(v0, 29) + v2; + v1 += littleEndian64(buffer, 0) * k1_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v1 = rotateRight(v1, 29) + v3; + v2 += littleEndian64(buffer, 0) * k2_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v2 = rotateRight(v2, 29) + v0; + v3 = littleEndian64(buffer, 0) * k3_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v3 = rotateRight(v3, 29) + v1; + } + + v2 ^= rotateRight(((v0 + v3) * k0_128) + v1, 21) * k1_128; + v3 ^= rotateRight(((v1 + v2) * k1_128) + v0, 21) * k0_128; + v0 ^= rotateRight(((v0 + v2) * k0_128) + v3, 21) * k1_128; + v1 ^= rotateRight(((v1 + v3) * k1_128) + v2, 21) * k0_128; + } + + if (buffer.length >= 16) { + v0 += littleEndian64(buffer, 0) * k2_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v0 = rotateRight(v0, 33) * k3_128; + v1 += littleEndian64(buffer, 0) * k2_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v1 = rotateRight(v1, 33) * k3_128; + v0 ^= rotateRight((v0 * k2_128) + v1, 45) + k1_128; + v1 ^= rotateRight((v1 * k3_128) + v0, 45) + k0_128; + } + + if (buffer.length >= 8) { + v0 += littleEndian64(buffer, 0) * k2_128; + buffer = Arrays.copyOfRange(buffer, 8, buffer.length); + v0 = rotateRight(v0, 33) * k3_128; + v0 ^= rotateRight((v0 * k2_128) + v1, 27) * k1_128; + } + + if (buffer.length >= 4) { + v1 += (long) littleEndian32(buffer) * k2_128; + buffer = Arrays.copyOfRange(buffer, 4, buffer.length); + v1 = rotateRight(v1, 33) * k3_128; + v1 ^= rotateRight((v1 * k3_128) + v0, 46) * k0_128; + } + + if (buffer.length >= 2) { + v0 += (long) littleEndian16(buffer) * k2_128; + buffer = Arrays.copyOfRange(buffer, 2, buffer.length); + v0 = rotateRight(v0, 33) * k3_128; + v0 ^= rotateRight((v0 * k2_128) * v1, 22) * k1_128; + } + + if (buffer.length >= 1) { + v1 += (long) buffer[0] * k2_128; + v1 = rotateRight(v1, 33) * k3_128; + v1 ^= rotateRight((v1 * k3_128) + v0, 58) * k0_128; + } + + v0 += rotateRight((v0 * k0_128) + v1, 13); + v1 += rotateRight((v1 * k1_128) + v0, 37); + v0 += rotateRight((v0 * k2_128) + v1, 13); + v1 += rotateRight((v1 * k3_128) + v0, 37); + + return new Number128(v0, v1); + } + + + private static long littleEndian64(byte[] b, int start) { + return ByteUtil.bytesToLong(b, start, ByteOrder.LITTLE_ENDIAN); + } + + private static int littleEndian32(byte[] b) { + return (int) b[0] | (int) b[1] << 8 | (int) b[2] << 16 | (int) b[3] << 24; + } + + private static int littleEndian16(byte[] b) { + return ByteUtil.bytesToShort(b, ByteOrder.LITTLE_ENDIAN); + } + + private static long rotateLeft64(long x, int k) { + int n = 64; + int s = k & (n - 1); + return x << s | x >> (n - s); + } + + private static long rotateRight(long val, int shift) { + return (val >> shift) | (val << (64 - shift)); + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/hash/MurmurHash.java b/hutool-core/src/main/java/cn/hutool/core/lang/hash/MurmurHash.java index b3dd26961..94826602b 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/hash/MurmurHash.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/hash/MurmurHash.java @@ -1,9 +1,11 @@ package cn.hutool.core.lang.hash; +import cn.hutool.core.util.ByteUtil; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.StrUtil; import java.io.Serializable; +import java.nio.ByteOrder; import java.nio.charset.Charset; /** @@ -41,6 +43,7 @@ public class MurmurHash implements Serializable{ private static final int DEFAULT_SEED = 0; private static final Charset DEFAULT_CHARSET = CharsetUtil.CHARSET_UTF_8; + private static final ByteOrder DEFAULT_ORDER = ByteOrder.LITTLE_ENDIAN; /** * Murmur3 32-bit Hash值计算 @@ -76,11 +79,8 @@ public class MurmurHash implements Serializable{ // body for (int i = 0; i < nblocks; i++) { - int i_4 = i << 2; - int k = (data[i_4] & 0xff) // - | ((data[i_4 + 1] & 0xff) << 8) // - | ((data[i_4 + 2] & 0xff) << 16) // - | ((data[i_4 + 3] & 0xff) << 24); + int i4 = i << 2; + int k = ByteUtil.bytesToInt(data, i4, DEFAULT_ORDER); // mix functions k *= C1_32; @@ -157,14 +157,7 @@ public class MurmurHash implements Serializable{ // body for (int i = 0; i < nblocks; i++) { final int i8 = i << 3; - long k = ((long) data[i8] & 0xff) // - | (((long) data[i8 + 1] & 0xff) << 8) // - | (((long) data[i8 + 2] & 0xff) << 16) // - | (((long) data[i8 + 3] & 0xff) << 24) // - | (((long) data[i8 + 4] & 0xff) << 32)// - | (((long) data[i8 + 5] & 0xff) << 40) // - | (((long) data[i8 + 6] & 0xff) << 48) // - | (((long) data[i8 + 7] & 0xff) << 56); + long k = ByteUtil.bytesToLong(data, i8, DEFAULT_ORDER); // mix functions k *= C1; @@ -241,23 +234,8 @@ public class MurmurHash implements Serializable{ // body for (int i = 0; i < nblocks; i++) { final int i16 = i << 4; - long k1 = ((long) data[i16] & 0xff) // - | (((long) data[i16 + 1] & 0xff) << 8) // - | (((long) data[i16 + 2] & 0xff) << 16) // - | (((long) data[i16 + 3] & 0xff) << 24) // - | (((long) data[i16 + 4] & 0xff) << 32) // - | (((long) data[i16 + 5] & 0xff) << 40) // - | (((long) data[i16 + 6] & 0xff) << 48) // - | (((long) data[i16 + 7] & 0xff) << 56); - - long k2 = ((long) data[i16 + 8] & 0xff) // - | (((long) data[i16 + 9] & 0xff) << 8) // - | (((long) data[i16 + 10] & 0xff) << 16) // - | (((long) data[i16 + 11] & 0xff) << 24) // - | (((long) data[i16 + 12] & 0xff) << 32) // - | (((long) data[i16 + 13] & 0xff) << 40) // - | (((long) data[i16 + 14] & 0xff) << 48) // - | (((long) data[i16 + 15] & 0xff) << 56); + long k1 = ByteUtil.bytesToLong(data, i16, DEFAULT_ORDER); + long k2 = ByteUtil.bytesToLong(data, i16 + 8, DEFAULT_ORDER); // mix functions for k1 k1 *= C1; diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/hash/Number128.java b/hutool-core/src/main/java/cn/hutool/core/lang/hash/Number128.java index 4f6301095..12e0a8c35 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/hash/Number128.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/hash/Number128.java @@ -6,7 +6,7 @@ package cn.hutool.core.lang.hash; * @author hexiufeng * @since 5.2.5 */ -public class Number128 extends Number{ +public class Number128 extends Number { private static final long serialVersionUID = 1L; private long lowValue; @@ -23,22 +23,47 @@ public class Number128 extends Number{ this.highValue = highValue; } + /** + * 获取低位值 + * + * @return 地位值 + */ public long getLowValue() { return lowValue; } - public long getHighValue() { - return highValue; - } - + /** + * 设置低位值 + * + * @param lowValue 低位值 + */ public void setLowValue(long lowValue) { this.lowValue = lowValue; } + /** + * 获取高位值 + * + * @return 高位值 + */ + public long getHighValue() { + return highValue; + } + + /** + * 设置高位值 + * + * @param hiValue 高位值 + */ public void setHighValue(long hiValue) { this.highValue = hiValue; } + /** + * 获取高低位数组,long[0]:低位,long[1]:高位 + * + * @return 高低位数组,long[0]:低位,long[1]:高位 + */ public long[] getLongArray() { return new long[]{lowValue, highValue}; } diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeBuilder.java b/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeBuilder.java index 37af3f94a..ebc1e0c13 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/tree/TreeBuilder.java @@ -228,7 +228,6 @@ public class TreeBuilder implements Builder> { } final Map> eTreeMap = MapUtil.sortByValue(this.idTreeMap, false); - List> rootTreeList = CollUtil.newArrayList(); E parentId; for (Tree node : eTreeMap.values()) { if (null == node) { @@ -237,7 +236,6 @@ public class TreeBuilder implements Builder> { parentId = node.getParentId(); if (ObjectUtil.equals(this.root.getId(), parentId)) { this.root.addChildren(node); - rootTreeList.add(node); continue; } diff --git a/hutool-core/src/main/java/cn/hutool/core/map/CaseInsensitiveTreeMap.java b/hutool-core/src/main/java/cn/hutool/core/map/CaseInsensitiveTreeMap.java new file mode 100755 index 000000000..7f8b065f6 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/map/CaseInsensitiveTreeMap.java @@ -0,0 +1,73 @@ +package cn.hutool.core.map; + +import java.util.Comparator; +import java.util.Map; +import java.util.SortedMap; +import java.util.TreeMap; + +/** + * 忽略大小写的{@link TreeMap}
+ * 对KEY忽略大小写,get("Value")和get("value")获得的值相同,put进入的值也会被覆盖 + * + * @author Looly + * + * @param 键类型 + * @param 值类型 + * @since 3.3.1 + */ +public class CaseInsensitiveTreeMap extends CustomKeyMap { + private static final long serialVersionUID = 4043263744224569870L; + + // ------------------------------------------------------------------------- Constructor start + /** + * 构造 + */ + public CaseInsensitiveTreeMap() { + this((Comparator) null); + } + + /** + * 构造 + * + * @param m Map + * @since 3.1.2 + */ + public CaseInsensitiveTreeMap(Map m) { + this(); + this.putAll(m); + } + + /** + * 构造 + * + * @param m Map + * @since 3.1.2 + */ + public CaseInsensitiveTreeMap(SortedMap m) { + super(new TreeMap(m)); + } + + /** + * 构造 + * + * @param comparator 比较器,{@code null}表示使用默认比较器 + */ + public CaseInsensitiveTreeMap(Comparator comparator) { + super(new TreeMap<>(comparator)); + } + // ------------------------------------------------------------------------- Constructor end + + /** + * 将Key转为小写 + * + * @param key KEY + * @return 小写KEY + */ + @Override + protected Object customKey(Object key) { + if (key instanceof CharSequence) { + key = key.toString().toLowerCase(); + } + return key; + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java b/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java index 82821897d..e41e9b84f 100644 --- a/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java @@ -8,12 +8,15 @@ import java.util.EnumSet; import java.util.HashMap; import java.util.Map; import java.util.Set; +import java.util.List; import java.util.StringJoiner; import java.util.function.BiConsumer; import java.util.function.BinaryOperator; import java.util.function.Function; import java.util.function.Supplier; +import java.util.function.UnaryOperator; import java.util.stream.Collector; +import java.util.stream.Collectors; /** * 可变的汇聚操作{@link Collector} 相关工具封装 @@ -140,6 +143,18 @@ public class CollectorUtil { return groupingBy(classifier, HashMap::new, downstream); } + /** + * 提供对null值友好的groupingBy操作的{@link Collector}实现 + * + * @param classifier 分组依据 + * @param 实体类型 + * @param 实体中的分组依据对应类型,也是Map中key的类型 + * @return {@link Collector} + */ + public static Collector>> + groupingBy(Function classifier) { + return groupingBy(classifier, Collectors.toList()); + } /** * 对null友好的 toMap 操作的 {@link Collector}实现,默认使用HashMap diff --git a/hutool-core/src/main/java/cn/hutool/core/text/CharSequenceUtil.java b/hutool-core/src/main/java/cn/hutool/core/text/CharSequenceUtil.java index 2d7bcad89..4a308c14d 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/CharSequenceUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/CharSequenceUtil.java @@ -3601,6 +3601,46 @@ public class CharSequenceUtil { return stringBuilder.toString(); } + /** + * 替换指定字符串的指定区间内字符为指定字符串,字符串只重复一次
+ * 此方法使用{@link String#codePoints()}完成拆分替换 + * + * @param str 字符串 + * @param startInclude 开始位置(包含) + * @param endExclude 结束位置(不包含) + * @param replacedStr 被替换的字符串 + * @return 替换后的字符串 + * @since 3.2.1 + */ + public static String replace(CharSequence str, int startInclude, int endExclude, CharSequence replacedStr) { + if (isEmpty(str)) { + return str(str); + } + final String originalStr = str(str); + int[] strCodePoints = originalStr.codePoints().toArray(); + final int strLength = strCodePoints.length; + if (startInclude > strLength) { + return originalStr; + } + if (endExclude > strLength) { + endExclude = strLength; + } + if (startInclude > endExclude) { + // 如果起始位置大于结束位置,不替换 + return originalStr; + } + + final StringBuilder stringBuilder = new StringBuilder(); + for (int i = 0; i < startInclude; i++) { + stringBuilder.append(new String(strCodePoints, i, 1)); + } + stringBuilder.append(replacedStr); + for (int i = endExclude; i < strLength; i++) { + stringBuilder.append(new String(strCodePoints, i, 1)); + } + return stringBuilder.toString(); + } + /** * 替换所有正则匹配的文本,并使用自定义函数决定如何替换
* replaceFun可以通过{@link Matcher}提取出匹配到的内容的不同部分,然后经过重新处理、组装变成新的内容放回原位。 diff --git a/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvWriter.java b/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvWriter.java index 878011082..5c386214e 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvWriter.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvWriter.java @@ -1,6 +1,7 @@ package cn.hutool.core.text.csv; import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.ArrayIter; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.convert.Convert; import cn.hutool.core.io.FileUtil; @@ -45,6 +46,10 @@ public final class CsvWriter implements Closeable, Flushable, Serializable { * 是否处于新行开始 */ private boolean newline = true; + /** + * 是否首行,即CSV开始的位置,当初始化时默认为true,一旦写入内容,为false + */ + private boolean isFirstLine = true; // --------------------------------------------------------------------------------------------------- Constructor start @@ -183,13 +188,7 @@ public final class CsvWriter implements Closeable, Flushable, Serializable { * @throws IORuntimeException IO异常 */ public CsvWriter write(String[]... lines) throws IORuntimeException { - if (ArrayUtil.isNotEmpty(lines)) { - for (final String[] values : lines) { - appendLine(values); - } - flush(); - } - return this; + return write(new ArrayIter<>(lines)); } /** @@ -320,9 +319,14 @@ public final class CsvWriter implements Closeable, Flushable, Serializable { public CsvWriter writeComment(String comment) { Assert.notNull(this.config.commentCharacter, "Comment is disable!"); try { + if(isFirstLine){ + // 首行不补换行符 + isFirstLine = false; + }else { + writer.write(config.lineDelimiter); + } writer.write(this.config.commentCharacter); writer.write(comment); - writer.write(config.lineDelimiter); newline = true; } catch (IOException e) { throw new IORuntimeException(e); @@ -366,12 +370,17 @@ public final class CsvWriter implements Closeable, Flushable, Serializable { * @param fields 字段列表 ({@code null} 值会被做为空值追加) * @throws IOException IO异常 */ - private void doAppendLine(final String... fields) throws IOException { + private void doAppendLine(String... fields) throws IOException { if (null != fields) { + if(isFirstLine){ + // 首行不补换行符 + isFirstLine = false; + }else { + writer.write(config.lineDelimiter); + } for (String field : fields) { appendField(field); } - writer.write(config.lineDelimiter); newline = true; } } diff --git a/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java b/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java index bc6af864b..7863d5c06 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/csv/package-info.java @@ -1,7 +1,8 @@ /** - * 提供CSV文件读写的封装,入口为CsvUtil + * 提供CSV文件读写的封装,入口为CsvUtil
+ * 规范见:https://datatracker.ietf.org/doc/html/rfc4180 * * @author looly * */ -package cn.hutool.core.text.csv; \ No newline at end of file +package cn.hutool.core.text.csv; diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java index f20d26914..46f988c65 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ByteUtil.java @@ -27,7 +27,11 @@ import java.util.concurrent.atomic.LongAdder; */ public class ByteUtil { - public static ByteOrder DEFAULT_ORDER = ByteOrder.LITTLE_ENDIAN; + public static final ByteOrder DEFAULT_ORDER = ByteOrder.LITTLE_ENDIAN; + /** + * CPU的字节序 + */ + public static final ByteOrder CPU_ENDIAN = "little".equals(System.getProperty("sun.cpu.endian")) ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN; /** * int转byte @@ -130,16 +134,29 @@ public class ByteUtil { * @return int值 */ public static int bytesToInt(byte[] bytes, ByteOrder byteOrder) { + return bytesToInt(bytes, 0, byteOrder); + } + + /** + * byte[]转int值
+ * 自定义端序 + * + * @param bytes byte数组 + * @param byteOrder 端序 + * @return int值 + * @since 5.7.21 + */ + public static int bytesToInt(byte[] bytes, int start, ByteOrder byteOrder) { if (ByteOrder.LITTLE_ENDIAN == byteOrder) { - return bytes[0] & 0xFF | // - (bytes[1] & 0xFF) << 8 | // - (bytes[2] & 0xFF) << 16 | // - (bytes[3] & 0xFF) << 24; // + return bytes[start] & 0xFF | // + (bytes[1 + start] & 0xFF) << 8 | // + (bytes[2 + start] & 0xFF) << 16 | // + (bytes[3 + start] & 0xFF) << 24; // } else { - return bytes[3] & 0xFF | // - (bytes[2] & 0xFF) << 8 | // - (bytes[1] & 0xFF) << 16 | // - (bytes[0] & 0xFF) << 24; // + return bytes[3 + start] & 0xFF | // + (bytes[2 + start] & 0xFF) << 8 | // + (bytes[1 + start] & 0xFF) << 16 | // + (bytes[start] & 0xFF) << 24; // } } @@ -243,16 +260,31 @@ public class ByteUtil { * @return long值 */ public static long bytesToLong(byte[] bytes, ByteOrder byteOrder) { + return bytesToLong(bytes, 0, byteOrder); + } + + /** + * byte数组转long
+ * 自定义端序
+ * from: https://stackoverflow.com/questions/4485128/how-do-i-convert-long-to-byte-and-back-in-java + * + * @param bytes byte数组 + * @param start 计算数组开始位置 + * @param byteOrder 端序 + * @return long值 + * @since 5.7.21 + */ + public static long bytesToLong(byte[] bytes, int start, ByteOrder byteOrder) { long values = 0; if (ByteOrder.LITTLE_ENDIAN == byteOrder) { for (int i = (Long.BYTES - 1); i >= 0; i--) { values <<= Byte.SIZE; - values |= (bytes[i] & 0xff); + values |= (bytes[i + start] & 0xff); } } else { for (int i = 0; i < Long.BYTES; i++) { values <<= Byte.SIZE; - values |= (bytes[i] & 0xff); + values |= (bytes[i + start] & 0xff); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java index 97ee1537b..bafe37995 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/HashUtil.java @@ -1,6 +1,7 @@ package cn.hutool.core.util; import cn.hutool.core.lang.hash.CityHash; +import cn.hutool.core.lang.hash.MetroHash; import cn.hutool.core.lang.hash.MurmurHash; import cn.hutool.core.lang.hash.Number128; @@ -386,7 +387,7 @@ public class HashUtil { if (ucChar <= 'Z' && ucChar >= 'A') { ucChar = (char) (ucChar + 32); } - hash += (3 * i * ucChar * ucChar + 5 * i * ucChar + 7 * i + 11 * ucChar) % 16777216; + hash += (3L * i * ucChar * ucChar + 5L * i * ucChar + 7L * i + 11 * ucChar) % 16777216; } } else { for (i = 1; i <= 96; i++) { @@ -394,7 +395,7 @@ public class HashUtil { if (ucChar <= 'Z' && ucChar >= 'A') { ucChar = (char) (ucChar + 32); } - hash += (3 * i * ucChar * ucChar + 5 * i * ucChar + 7 * i + 11 * ucChar) % 16777216; + hash += (3L * i * ucChar * ucChar + 5L * i * ucChar + 7L * i + 11 * ucChar) % 16777216; } } if (hash < 0) { @@ -545,4 +546,46 @@ public class HashUtil { public static long[] cityHash128(byte[] data, Number128 seed) { return CityHash.hash128(data, seed).getLongArray(); } + + /** + * MetroHash 算法64-bit实现 + * + * @param data 数据 + * @param seed 种子 + * @return hash值 + */ + public static long metroHash64(byte[] data, long seed) { + return MetroHash.hash64(data, seed); + } + + /** + * MetroHash 算法64-bit实现 + * + * @param data 数据 + * @return hash值 + */ + public static long metroHash64(byte[] data) { + return MetroHash.hash64(data); + } + + /** + * MetroHash 算法128-bit实现 + * + * @param data 数据 + * @param seed 种子 + * @return hash值,long[0]:低位,long[1]:高位 + */ + public static long[] metroHash128(byte[] data, long seed) { + return MetroHash.hash128(data,seed).getLongArray(); + } + + /** + * MetroHash 算法128-bit实现 + * + * @param data 数据 + * @return hash值,long[0]:低位,long[1]:高位 + */ + public static long[] metroHash128(byte[] data) { + return MetroHash.hash128(data).getLongArray(); + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java index 724312695..fde460ff2 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/URLUtil.java @@ -95,6 +95,26 @@ public class URLUtil extends URLEncodeUtil { */ public static final String WAR_URL_SEPARATOR = "*/"; + /** + * 将{@link URI}转换为{@link URL} + * + * @param uri {@link URI} + * @return URL对象 + * @see URI#toURL() + * @throws UtilException {@link MalformedURLException}包装,URI格式有问题时抛出 + * @since 5.7.21 + */ + public static URL url(URI uri) throws UtilException{ + if(null == uri){ + return null; + } + try { + return uri.toURL(); + } catch (MalformedURLException e) { + throw new UtilException(e); + } + } + /** * 通过一个字符串形式的URL地址创建URL对象 * @@ -114,7 +134,9 @@ public class URLUtil extends URLEncodeUtil { * @since 4.1.1 */ public static URL url(String url, URLStreamHandler handler) { - Assert.notNull(url, "URL must not be null"); + if(null == url){ + return null; + } // 兼容Spring的ClassPath路径 if (url.startsWith(CLASSPATH_URL_PREFIX)) { @@ -142,6 +164,9 @@ public class URLUtil extends URLEncodeUtil { * @since 5.5.2 */ public static URI getStringURI(CharSequence content) { + if(null == content){ + return null; + } final String contentStr = StrUtil.addPrefixIfNot(content, "string:///"); return URI.create(contentStr); } @@ -461,6 +486,7 @@ public class URLUtil extends URLEncodeUtil { * @since 3.0.9 */ public static boolean isFileURL(URL url) { + Assert.notNull(url, "URL must be not null"); String protocol = url.getProtocol(); return (URL_PROTOCOL_FILE.equals(protocol) || // URL_PROTOCOL_VFSFILE.equals(protocol) || // @@ -474,6 +500,7 @@ public class URLUtil extends URLEncodeUtil { * @return 是否为jar包URL */ public static boolean isJarURL(URL url) { + Assert.notNull(url, "URL must be not null"); final String protocol = url.getProtocol(); return (URL_PROTOCOL_JAR.equals(protocol) || // URL_PROTOCOL_ZIP.equals(protocol) || // @@ -489,6 +516,7 @@ public class URLUtil extends URLEncodeUtil { * @since 4.1 */ public static boolean isJarFileURL(URL url) { + Assert.notNull(url, "URL must be not null"); return (URL_PROTOCOL_FILE.equals(url.getProtocol()) && // url.getPath().toLowerCase().endsWith(FileUtil.JAR_FILE_EXT)); } @@ -501,7 +529,7 @@ public class URLUtil extends URLEncodeUtil { * @since 3.2.1 */ public static InputStream getStream(URL url) { - Assert.notNull(url); + Assert.notNull(url, "URL must be not null"); try { return url.openStream(); } catch (IOException e) { diff --git a/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java b/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java new file mode 100644 index 000000000..84f6d4581 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java @@ -0,0 +1,93 @@ +package cn.hutool.core.builder; + +import cn.hutool.core.util.StrUtil; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import lombok.experimental.Accessors; +import org.junit.Assert; +import org.junit.Test; + +/** + * {@link GenericBuilder} 单元测试类 + * + * @author TomXin + */ +public class GenericBuilderTest { + + @Test + public void test() { + Box box = GenericBuilder + .of(Box::new) + .with(Box::setId, 1024L) + .with(Box::setTitle, "Hello World!") + .with(Box::setLength, 9) + .with(Box::setWidth, 8) + .with(Box::setHeight, 7) + .build(); + + Assert.assertEquals(1024L, box.getId().longValue()); + Assert.assertEquals("Hello World!", box.getTitle()); + Assert.assertEquals(9, box.getLength().intValue()); + Assert.assertEquals(8, box.getWidth().intValue()); + Assert.assertEquals(7, box.getHeight().intValue()); + + // 对象修改 + Box boxModified = GenericBuilder + .of(() -> box) + .with(Box::setTitle, "Hello Friend!") + .with(Box::setLength, 3) + .with(Box::setWidth, 4) + .with(Box::setHeight, 5) + .build(); + + Assert.assertEquals(1024L, boxModified.getId().longValue()); + Assert.assertEquals("Hello Friend!", box.getTitle()); + Assert.assertEquals(3, boxModified.getLength().intValue()); + Assert.assertEquals(4, boxModified.getWidth().intValue()); + Assert.assertEquals(5, boxModified.getHeight().intValue()); + + // 多参数构造 + Box box1 = GenericBuilder + .of(Box::new, 2048L, "Hello Partner!", 222, 333, 444) + .with(Box::alis) + .build(); + + Assert.assertEquals(2048L, box1.getId().longValue()); + Assert.assertEquals("Hello Partner!", box1.getTitle()); + Assert.assertEquals(222, box1.getLength().intValue()); + Assert.assertEquals(333, box1.getWidth().intValue()); + Assert.assertEquals(444, box1.getHeight().intValue()); + } + + @Getter + @Setter + @ToString + @Accessors(chain = true) + public static class Box { + private Long id; + private String title; + private Integer length; + private Integer width; + private Integer height; + private String titleAlias; + + public Box() { + } + + public Box(Long id, String title, Integer length, Integer width, Integer height) { + this.id = id; + this.title = title; + this.length = length; + this.width = width; + this.height = height; + } + + public void alis() { + if (StrUtil.isNotBlank(this.title)) { + this.titleAlias = "TomXin:\"" + title + "\""; + } + } + } + +} diff --git a/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java index 912df5904..5cecd5de3 100644 --- a/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java @@ -19,7 +19,7 @@ import java.util.Map; public class ListUtilTest { @Test - public void splitTest(){ + public void splitTest() { List> lists = ListUtil.split(null, 3); Assert.assertEquals(ListUtil.empty(), lists); @@ -60,7 +60,7 @@ public class ListUtilTest { } @Test - public void splitAvgTest(){ + public void splitAvgTest() { List> lists = ListUtil.splitAvg(null, 3); Assert.assertEquals(ListUtil.empty(), lists); @@ -80,13 +80,13 @@ public class ListUtilTest { } @Test(expected = IllegalArgumentException.class) - public void splitAvgNotZero(){ + public void splitAvgNotZero() { // limit不能小于等于0 ListUtil.splitAvg(Arrays.asList(1, 2, 3, 4), 0); } @Test - public void editTest(){ + public void editTest() { List a = ListUtil.toLinkedList("1", "2", "3"); final List filter = (List) CollUtil.edit(a, str -> "edit" + str); Assert.assertEquals("edit1", filter.get(0)); @@ -104,7 +104,7 @@ public class ListUtilTest { } @Test - public void pageTest(){ + public void pageTest() { List a = ListUtil.toLinkedList(1, 2, 3,4,5); PageUtil.setFirstPageNo(1); @@ -167,10 +167,13 @@ public class ListUtilTest { Assert.assertArrayEquals(new int[]{}, pageListData.get(0).stream().mapToInt(Integer::valueOf).toArray()); Assert.assertArrayEquals(new int[]{3, 4}, pageListData.get(1).stream().mapToInt(Integer::valueOf).toArray()); Assert.assertArrayEquals(new int[]{5}, pageListData.get(2).stream().mapToInt(Integer::valueOf).toArray()); + + // 恢复默认值,避免影响其他测试用例 + PageUtil.setFirstPageNo(0); } @Test - public void subTest(){ + public void subTest() { final List of = ListUtil.of(1, 2, 3, 4); final List sub = ListUtil.sub(of, 2, 4); sub.remove(0); @@ -181,10 +184,10 @@ public class ListUtilTest { } @Test - public void sortByPropertyTest(){ + public void sortByPropertyTest() { @Data @AllArgsConstructor - class TestBean{ + class TestBean { private int order; private String name; } diff --git a/hutool-core/src/test/java/cn/hutool/core/convert/DateConvertTest.java b/hutool-core/src/test/java/cn/hutool/core/convert/DateConvertTest.java index f7ffeac24..e3815d7c0 100644 --- a/hutool-core/src/test/java/cn/hutool/core/convert/DateConvertTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/convert/DateConvertTest.java @@ -1,15 +1,14 @@ package cn.hutool.core.convert; +import cn.hutool.core.date.DateUtil; +import org.junit.Assert; +import org.junit.Test; + import java.sql.Timestamp; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.Date; -import org.junit.Assert; -import org.junit.Test; - -import cn.hutool.core.date.DateUtil; - public class DateConvertTest { @Test @@ -28,7 +27,7 @@ public class DateConvertTest { int dateLong = -1497600000; Date value = Convert.toDate(dateLong); Assert.assertNotNull(value); - Assert.assertEquals("Mon Dec 15 00:00:00 CST 1969", value.toString()); + Assert.assertEquals("Mon Dec 15 00:00:00 CST 1969", value.toString().replace("GMT+08:00", "CST")); final java.sql.Date sqlDate = Convert.convert(java.sql.Date.class, dateLong); Assert.assertNotNull(sqlDate); @@ -53,18 +52,18 @@ public class DateConvertTest { java.sql.Date value2 = Convert.convert(java.sql.Date.class, timeLong); Assert.assertEquals(timeLong, value2.getTime()); } - + @Test public void toLocalDateTimeTest() { Date src = new Date(); - + LocalDateTime ldt = Convert.toLocalDateTime(src); Assert.assertEquals(ldt, DateUtil.toLocalDateTime(src)); - + Timestamp ts = Timestamp.from(src.toInstant()); ldt = Convert.toLocalDateTime(ts); Assert.assertEquals(ldt, DateUtil.toLocalDateTime(src)); - + String str = "2020-12-12 12:12:12.0"; ldt = Convert.toLocalDateTime(str); Assert.assertEquals(ldt.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.S")), str); diff --git a/hutool-core/src/test/java/cn/hutool/core/convert/NumberChineseFormatterTest.java b/hutool-core/src/test/java/cn/hutool/core/convert/NumberChineseFormatterTest.java index f9bec8251..5dc83a714 100644 --- a/hutool-core/src/test/java/cn/hutool/core/convert/NumberChineseFormatterTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/convert/NumberChineseFormatterTest.java @@ -175,6 +175,26 @@ public class NumberChineseFormatterTest { Assert.assertEquals("零点零伍", f1); } + @Test + public void formatSimpleTest() { + String f1 = NumberChineseFormatter.formatSimple(1_2345); + Assert.assertEquals("1.23万", f1); + f1 = NumberChineseFormatter.formatSimple(-5_5555); + Assert.assertEquals("-5.56万", f1); + f1 = NumberChineseFormatter.formatSimple(1_2345_6789); + Assert.assertEquals("1.23亿", f1); + f1 = NumberChineseFormatter.formatSimple(-5_5555_5555); + Assert.assertEquals("-5.56亿", f1); + f1 = NumberChineseFormatter.formatSimple(1_2345_6789_1011L); + Assert.assertEquals("1.23万亿", f1); + f1 = NumberChineseFormatter.formatSimple(-5_5555_5555_5555L); + Assert.assertEquals("-5.56万亿", f1); + f1 = NumberChineseFormatter.formatSimple(123); + Assert.assertEquals("123", f1); + f1 = NumberChineseFormatter.formatSimple(-123); + Assert.assertEquals("-123", f1); + } + @Test public void digitToChineseTest() { String digitToChinese = Convert.digitToChinese(12_4124_1241_2421.12); diff --git a/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java b/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java index da29455fb..e2d86d1e9 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/ChineseDateTest.java @@ -105,4 +105,12 @@ public class ChineseDateTest { Assert.assertEquals("戊申猴年 五月初五", c1.toString()); Assert.assertEquals("戊申猴年 闰五月初五", c2.toString()); } + + @Test + public void getChineseMonthTest2(){ + //https://github.com/dromara/hutool/issues/2112 + ChineseDate springFestival = new ChineseDate(DateUtil.parseDate("2022-02-01")); + final String chineseMonth = springFestival.getChineseMonth(); + Assert.assertEquals("一月", chineseMonth); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java index 24e7321a8..8b9dd7500 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java @@ -3,7 +3,6 @@ package cn.hutool.core.date; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.date.BetweenFormatter.Level; import cn.hutool.core.date.format.FastDateFormat; -import cn.hutool.core.lang.Console; import cn.hutool.core.util.RandomUtil; import org.junit.Assert; import org.junit.Test; @@ -694,6 +693,8 @@ public class DateUtilTest { String dateStr = "Wed Sep 16 11:26:23 CST 2009"; SimpleDateFormat sdf = new SimpleDateFormat(DatePattern.JDK_DATETIME_PATTERN, Locale.US); + // Asia/Shanghai是以地区命名的地区标准时,在中国叫CST,因此如果解析CST时不使用"Asia/Shanghai"而使用"GMT+08:00",会导致相差一个小时 + sdf.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai")); final DateTime parse = DateUtil.parse(dateStr, sdf); DateTime dateTime = DateUtil.parseCST(dateStr); @@ -992,11 +993,13 @@ public class DateUtilTest { @Test public void parseSingleMonthAndDayTest() { - final DateTime parse = DateUtil.parse("2021-1-1"); + DateTime parse = DateUtil.parse("2021-1-1"); Assert.assertNotNull(parse); Assert.assertEquals("2021-01-01 00:00:00", parse.toString()); - Console.log(DateUtil.parse("2021-1-22 00:00:00")); + parse = DateUtil.parse("2021-1-22 00:00:00"); + Assert.assertNotNull(parse); + Assert.assertEquals("2021-01-22 00:00:00", parse.toString()); } @Test @@ -1004,4 +1007,17 @@ public class DateUtilTest { final DateTime parse = DateUtil.parse("2021-12-01", DatePattern.NORM_DATE_FORMATTER); Assert.assertEquals("2021-12-01 00:00:00", parse.toString()); } + + @Test + public void isSameWeekTest() { + // 周六与周日比较 + final boolean isSameWeek = DateUtil.isSameWeek(DateTime.of("2022-01-01", "yyyy-MM-dd"), DateTime.of("2022-01-02", "yyyy-MM-dd"), true); + Assert.assertTrue(isSameWeek); + // 周日与周一比较 + final boolean isSameWeek1 = DateUtil.isSameWeek(DateTime.of("2022-01-02", "yyyy-MM-dd"), DateTime.of("2022-01-03", "yyyy-MM-dd"), false); + Assert.assertTrue(isSameWeek1); + // 跨月比较 + final boolean isSameWeek2 = DateUtil.isSameWeek(DateTime.of("2021-12-29", "yyyy-MM-dd"), DateTime.of("2022-01-01", "yyyy-MM-dd"), true); + Assert.assertTrue(isSameWeek2); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java index 8f35c59a5..57d6eb717 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java @@ -197,4 +197,24 @@ public class LocalDateTimeUtilTest { Assert.assertTrue(LocalDateTimeUtil.isOverlap(oneStartTime2,oneEndTime2,realStartTime,realEndTime)); Assert.assertFalse(LocalDateTimeUtil.isOverlap(oneStartTime3,oneEndTime3,realStartTime,realEndTime)); } + + @Test + public void weekOfYearTest(){ + LocalDate date1 = LocalDate.of(2021, 12, 31); + final int weekOfYear1 = LocalDateTimeUtil.weekOfYear(date1); + Assert.assertEquals(52, weekOfYear1); + + final int weekOfYear2 = LocalDateTimeUtil.weekOfYear(date1.atStartOfDay()); + Assert.assertEquals(52, weekOfYear2); + } + + @Test + public void weekOfYearTest2(){ + LocalDate date1 = LocalDate.of(2022, 1, 31); + final int weekOfYear1 = LocalDateTimeUtil.weekOfYear(date1); + Assert.assertEquals(5, weekOfYear1); + + final int weekOfYear2 = LocalDateTimeUtil.weekOfYear(date1.atStartOfDay()); + Assert.assertEquals(5, weekOfYear2); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/date/MonthTest.java b/hutool-core/src/test/java/cn/hutool/core/date/MonthTest.java index dce94bb09..7a7929f39 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/MonthTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/MonthTest.java @@ -37,4 +37,15 @@ public class MonthTest { lastDay = Month.of(Calendar.DECEMBER).getLastDay(true); Assert.assertEquals(31, lastDay); } + + @Test + public void toJdkMonthTest(){ + final java.time.Month month = Month.AUGUST.toJdkMonth(); + Assert.assertEquals(java.time.Month.AUGUST, month); + } + + @Test(expected = IllegalArgumentException.class) + public void toJdkMonthTest2(){ + Month.UNDECIMBER.toJdkMonth(); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java index fbfe82cb2..35be0f241 100644 --- a/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java @@ -21,11 +21,8 @@ public class CheckedUtilTest { @Test public void sleepTest() { - VoidFunc0 func = () -> Thread.sleep(1000L); func.callWithRuntimeException(); - - } @@ -39,7 +36,6 @@ public class CheckedUtilTest { } catch (Exception re) { Assert.assertTrue(re instanceof RuntimeException); } - } @SuppressWarnings("ConstantConditions") diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java index c08e5dbb1..b10b115eb 100644 --- a/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java @@ -4,6 +4,7 @@ import cn.hutool.core.date.DateField; import cn.hutool.core.date.DateRange; import cn.hutool.core.date.DateTime; import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.StrUtil; import org.junit.Assert; import org.junit.Test; @@ -12,8 +13,8 @@ import java.util.NoSuchElementException; /** * {@link Range} 单元测试 - * @author Looly * + * @author Looly */ public class RangeTest { @@ -36,6 +37,32 @@ public class RangeTest { Assert.assertFalse(range.hasNext()); } + @Test + public void dateRangeFuncTest() { + DateTime start = DateUtil.parse("2021-01-01"); + DateTime end = DateUtil.parse("2021-01-03"); + + List dayOfMonthList = DateUtil.rangeFunc(start, end, DateField.DAY_OF_YEAR, a -> DateTime.of(a).dayOfMonth()); + Assert.assertArrayEquals(dayOfMonthList.toArray(new Integer[]{}), new Integer[]{1, 2, 3}); + + List dayOfMonthList2 = DateUtil.rangeFunc(null, null, DateField.DAY_OF_YEAR, a -> DateTime.of(a).dayOfMonth()); + Assert.assertArrayEquals(dayOfMonthList2.toArray(new Integer[]{}), new Integer[]{}); + } + + @Test + public void dateRangeConsumeTest() { + DateTime start = DateUtil.parse("2021-01-01"); + DateTime end = DateUtil.parse("2021-01-03"); + + StringBuilder sb = new StringBuilder(); + DateUtil.rangeConsume(start, end, DateField.DAY_OF_YEAR, a -> sb.append(DateTime.of(a).dayOfMonth()).append("#")); + Assert.assertEquals(sb.toString(), "1#2#3#"); + + StringBuilder sb2 = new StringBuilder(); + DateUtil.rangeConsume(null, null, DateField.DAY_OF_YEAR, a -> sb2.append(DateTime.of(a).dayOfMonth()).append("#")); + Assert.assertEquals(sb2.toString(), StrUtil.EMPTY); + } + @Test public void dateRangeTest2() { DateTime start = DateUtil.parse("2021-01-31"); @@ -84,7 +111,7 @@ public class RangeTest { } @Test - public void rangeDayOfYearTest(){ + public void rangeDayOfYearTest() { DateTime start = DateUtil.parse("2017-01-01"); DateTime end = DateUtil.parse("2017-01-05"); @@ -109,4 +136,39 @@ public class RangeTest { Assert.assertEquals(DateUtil.parse("2017-01-01"), rangeToList.get(0)); Assert.assertEquals(DateUtil.parse("2017-01-02"), rangeToList.get(1)); } + + + @Test + public void rangeContains() { + // 开始区间 + DateTime start = DateUtil.parse("2017-01-01"); + DateTime end = DateUtil.parse("2017-01-31"); + DateRange startRange = DateUtil.range(start, end, DateField.DAY_OF_YEAR); + // 结束区间 + DateTime start1 = DateUtil.parse("2017-01-31"); + DateTime end1 = DateUtil.parse("2017-02-02"); + DateRange endRange = DateUtil.range(start1, end1, DateField.DAY_OF_YEAR); + // 交集 + List dateTimes = DateUtil.rangeContains(startRange, endRange); + Assert.assertEquals(1, dateTimes.size()); + Assert.assertEquals(DateUtil.parse("2017-01-31"), dateTimes.get(0)); + } + + @Test + public void rangeNotContains() { + // 开始区间 + DateTime start = DateUtil.parse("2017-01-01"); + DateTime end = DateUtil.parse("2017-01-30"); + DateRange startRange = DateUtil.range(start, end, DateField.DAY_OF_YEAR); + // 结束区间 + DateTime start1 = DateUtil.parse("2017-01-01"); + DateTime end1 = DateUtil.parse("2017-01-31"); + DateRange endRange = DateUtil.range(start1, end1, DateField.DAY_OF_YEAR); + // 差集 + List dateTimes1 = DateUtil.rangeNotContains(startRange, endRange); + + Assert.assertEquals(1, dateTimes1.size()); + Assert.assertEquals(DateUtil.parse("2017-01-31"), dateTimes1.get(0)); + } + } diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/hash/CityHashTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/hash/CityHashTest.java new file mode 100755 index 000000000..d253ffa2e --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/hash/CityHashTest.java @@ -0,0 +1,36 @@ +package cn.hutool.core.lang.hash; + +import cn.hutool.core.util.StrUtil; +import org.junit.Assert; +import org.junit.Test; + +public class CityHashTest { + + @Test + public void hash32Test() { + int hv = CityHash.hash32(StrUtil.utf8Bytes("你")); + Assert.assertEquals(1290029860, hv); + + hv = CityHash.hash32(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(1374181357, hv); + + hv = CityHash.hash32(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(1475516842, hv); + hv = CityHash.hash32(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(0x51020cae, hv); + } + + @Test + public void hash64Test() { + long hv = CityHash.hash64(StrUtil.utf8Bytes("你")); + Assert.assertEquals(-4296898700418225525L, hv); + + hv = CityHash.hash64(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(-4294276205456761303L, hv); + + hv = CityHash.hash64(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(272351505337503793L, hv); + hv = CityHash.hash64(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(-8234735310919228703L, hv); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/hash/MetroHashTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MetroHashTest.java new file mode 100644 index 000000000..5577a1bb5 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MetroHashTest.java @@ -0,0 +1,93 @@ +package cn.hutool.core.lang.hash; + + +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.HexUtil; +import cn.hutool.core.util.RandomUtil; +import cn.hutool.core.util.StrUtil; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +/** + * https://gitee.com/dromara/hutool/pulls/532 + */ +public class MetroHashTest { + + @Test + public void testEmpty() { + Assert.assertEquals("31290877cceaea29", HexUtil.toHex(MetroHash.hash64(StrUtil.utf8Bytes(""), 0))); + } + + @Test + public void metroHash64Test() { + byte[] str = "我是一段测试123".getBytes(CharsetUtil.CHARSET_UTF_8); + final long hash64 = MetroHash.hash64(str); + Assert.assertEquals(62920234463891865L, hash64); + } + + @Test + public void metroHash128Test() { + byte[] str = "我是一段测试123".getBytes(CharsetUtil.CHARSET_UTF_8); + final long[] hash128 = MetroHash.hash128(str).getLongArray(); + Assert.assertEquals(4956592424592439349L, hash128[0]); + Assert.assertEquals(6301214698325086246L, hash128[1]); + } + + /** + * 数据量越大 MetroHash 优势越明显, + */ + @Test + @Ignore + public void bulkHashing64Test() { + String[] strArray = getRandomStringArray(); + long startCity = System.currentTimeMillis(); + for (String s : strArray) { + CityHash.hash64(s.getBytes()); + } + long endCity = System.currentTimeMillis(); + + long startMetro = System.currentTimeMillis(); + for (String s : strArray) { + MetroHash.hash64(StrUtil.utf8Bytes(s)); + } + long endMetro = System.currentTimeMillis(); + + System.out.println("metroHash =============" + (endMetro - startMetro)); + System.out.println("cityHash =============" + (endCity - startCity)); + } + + + /** + * 数据量越大 MetroHash 优势越明显, + */ + @Test + @Ignore + public void bulkHashing128Test() { + String[] strArray = getRandomStringArray(); + long startCity = System.currentTimeMillis(); + for (String s : strArray) { + CityHash.hash128(s.getBytes()); + } + long endCity = System.currentTimeMillis(); + + long startMetro = System.currentTimeMillis(); + for (String s : strArray) { + MetroHash.hash128(StrUtil.utf8Bytes(s)); + } + long endMetro = System.currentTimeMillis(); + + System.out.println("metroHash =============" + (endMetro - startMetro)); + System.out.println("cityHash =============" + (endCity - startCity)); + } + + + private static String[] getRandomStringArray() { + String[] result = new String[10000000]; + int index = 0; + while (index < 10000000) { + result[index++] = RandomUtil.randomString(RandomUtil.randomInt(64)); + } + return result; + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/hash/MurMurHashTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MurMurHashTest.java new file mode 100755 index 000000000..fcf946b5e --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/hash/MurMurHashTest.java @@ -0,0 +1,36 @@ +package cn.hutool.core.lang.hash; + +import cn.hutool.core.util.StrUtil; +import org.junit.Assert; +import org.junit.Test; + +public class MurMurHashTest { + + @Test + public void hash32Test() { + int hv = MurmurHash.hash32(StrUtil.utf8Bytes("你")); + Assert.assertEquals(222142701, hv); + + hv = MurmurHash.hash32(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(1188098267, hv); + + hv = MurmurHash.hash32(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(-1898490321, hv); + hv = MurmurHash.hash32(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(-1713131054, hv); + } + + @Test + public void hash64Test() { + long hv = MurmurHash.hash64(StrUtil.utf8Bytes("你")); + Assert.assertEquals(-1349759534971957051L, hv); + + hv = MurmurHash.hash64(StrUtil.utf8Bytes("你好")); + Assert.assertEquals(-7563732748897304996L, hv); + + hv = MurmurHash.hash64(StrUtil.utf8Bytes("见到你很高兴")); + Assert.assertEquals(-766658210119995316L, hv); + hv = MurmurHash.hash64(StrUtil.utf8Bytes("我们将通过生成一个大的文件的方式来检验各种方法的执行效率因为这种方式在结束的时候需要执行文件")); + Assert.assertEquals(-7469283059271653317L, hv); + } +} 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 764167662..d6a1e290d 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 @@ -348,4 +348,19 @@ public class UrlBuilderTest { builder.setFragment(builder.getFragment() + "?timestamp=1640391380204"); Assert.assertEquals("https://www.hutool.cn/#/a/b?timestamp=1640391380204", builder.toString()); } + + @Test + public void paramWithPlusTest(){ + String url = "http://127.0.0.1/?" + + "Expires=1642734164&" + + "security-token=CAIS+AF1q6Ft5B2yfSjIr5fYEeju1b1ggpPee2KGpjlgQtdfl43urjz2IHtKdXRvBu8Xs" + + "/4wnmxX7f4YlqB6T55OSAmcNZEoPwKpT4zmMeT7oMWQweEurv" + + "/MQBqyaXPS2MvVfJ+OLrf0ceusbFbpjzJ6xaCAGxypQ12iN+/m6" + + "/Ngdc9FHHPPD1x8CcxROxFppeIDKHLVLozNCBPxhXfKB0ca0WgVy0EHsPnvm5DNs0uH1AKjkbRM9r6ceMb0M5NeW75kSMqw0eBMca7M7TVd8RAi9t0t1" + + "/IVpGiY4YDAWQYLv0rda7DOltFiMkpla7MmXqlft+hzcgeQY0pc" + + "/RqAAYRYVCBiyuzAexSiDiJX1VqWljg4jYp1sdyv3HpV3sXVcf6VH6AN9ot5YNTw4JNO0aNpLpLm93rRMrOKIOsve+OmNyZ4HS7qHQKt1qp7HY1A" + + "/wGhJstkAoGQt+CHSMwVdIx3bVT1+ZYnJdM/oIQ/90afw4EEEQaRE51Z0rQC7z8d"; + final String build = UrlBuilder.of(url).build(); + Assert.assertEquals(url, build); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/text/CharSequenceUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/text/CharSequenceUtilTest.java index 18b84edb2..07afd2b4a 100644 --- a/hutool-core/src/test/java/cn/hutool/core/text/CharSequenceUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/text/CharSequenceUtilTest.java @@ -22,6 +22,13 @@ public class CharSequenceUtilTest { Assert.assertEquals(replace, result); } + @Test + public void replaceByStrTest(){ + String replace = "SSM15930297701BeryAllen"; + String result = CharSequenceUtil.replace(replace, 5, 12, "***"); + Assert.assertEquals("SSM15***01BeryAllen", result); + } + @Test public void addPrefixIfNotTest(){ String str = "hutool"; diff --git a/hutool-core/src/test/java/cn/hutool/core/text/NamingCaseTest.java b/hutool-core/src/test/java/cn/hutool/core/text/NamingCaseTest.java index a8800578a..62545d934 100644 --- a/hutool-core/src/test/java/cn/hutool/core/text/NamingCaseTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/text/NamingCaseTest.java @@ -12,4 +12,10 @@ public class NamingCaseTest { .set("customerNickV2", "customer_nick_v2") .forEach((key, value) -> Assert.assertEquals(value, NamingCase.toUnderlineCase(key))); } + + @Test + public void toUnderLineCaseTest2(){ + final String wPRunOZTime = NamingCase.toUnderlineCase("wPRunOZTime"); + Assert.assertEquals("w_P_run_OZ_time", wPRunOZTime); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java b/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java index e1a27b206..e8fc851e8 100644 --- a/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvParserTest.java @@ -8,7 +8,7 @@ import org.junit.Test; import java.io.StringReader; public class CsvParserTest { - + @Test public void parseTest1() { StringReader reader = StrUtil.getReader("aaa,b\"bba\",ccc"); @@ -18,7 +18,7 @@ public class CsvParserTest { Assert.assertEquals("b\"bba\"", row.getRawList().get(1)); IoUtil.close(parser); } - + @Test public void parseTest2() { StringReader reader = StrUtil.getReader("aaa,\"bba\"bbb,ccc"); @@ -28,7 +28,7 @@ public class CsvParserTest { Assert.assertEquals("\"bba\"bbb", row.getRawList().get(1)); IoUtil.close(parser); } - + @Test public void parseTest3() { StringReader reader = StrUtil.getReader("aaa,\"bba\",ccc"); @@ -38,7 +38,7 @@ public class CsvParserTest { Assert.assertEquals("bba", row.getRawList().get(1)); IoUtil.close(parser); } - + @Test public void parseTest4() { StringReader reader = StrUtil.getReader("aaa,\"\",ccc"); @@ -48,4 +48,16 @@ public class CsvParserTest { Assert.assertEquals("", row.getRawList().get(1)); IoUtil.close(parser); } + + @Test + public void parseEscapeTest(){ + // https://datatracker.ietf.org/doc/html/rfc4180#section-2 + // 第七条规则 + StringReader reader = StrUtil.getReader("\"b\"\"bb\""); + CsvParser parser = new CsvParser(reader, null); + CsvRow row = parser.nextRow(); + Assert.assertNotNull(row); + Assert.assertEquals(1, row.size()); + Assert.assertEquals("b\"bb", row.get(0)); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvWriterTest.java b/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvWriterTest.java index 18203196c..e79c9b784 100644 --- a/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvWriterTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvWriterTest.java @@ -19,6 +19,8 @@ public class CsvWriterTest { CharsetUtil.CHARSET_GBK, false, csvWriteConfig); writer.writeHeaderLine("name", "gender", "address"); + writer.writeLine("张三", "男", "XX市XX区"); + writer.writeLine("李四", "男", "XX市XX区,01号"); writer.close(); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java index b68b95ce9..ba7121f53 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ByteUtilTest.java @@ -10,9 +10,16 @@ public class ByteUtilTest { @Test public void intAndBytesLittleEndianTest() { // 测试 int 转小端序 byte 数组 - int int1 = RandomUtil.randomInt(); + int int1 = RandomUtil.randomInt((Integer.MAX_VALUE)); + + ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES); + buffer.order(ByteOrder.LITTLE_ENDIAN); + buffer.putInt(int1); + byte[] bytesIntFromBuffer = buffer.array(); byte[] bytesInt = ByteUtil.intToBytes(int1, ByteOrder.LITTLE_ENDIAN); + Assert.assertArrayEquals(bytesIntFromBuffer, bytesInt); + int int2 = ByteUtil.bytesToInt(bytesInt, ByteOrder.LITTLE_ENDIAN); Assert.assertEquals(int1, int2); @@ -28,8 +35,14 @@ public class ByteUtilTest { @Test public void intAndBytesBigEndianTest() { // 测试 int 转大端序 byte 数组 - int int2 = RandomUtil.randomInt(); + int int2 = RandomUtil.randomInt(Integer.MAX_VALUE); + + ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES); + buffer.putInt(int2); + byte[] bytesIntFromBuffer = buffer.array(); + byte[] bytesInt = ByteUtil.intToBytes(int2, ByteOrder.BIG_ENDIAN); + Assert.assertArrayEquals(bytesIntFromBuffer, bytesInt); // 测试大端序 byte 数组转 int int int3 = ByteUtil.bytesToInt(bytesInt, ByteOrder.BIG_ENDIAN); @@ -39,9 +52,16 @@ public class ByteUtilTest { @Test public void longAndBytesLittleEndianTest() { // 测试 long 转 byte 数组 - long long1 = 2223; + long long1 = RandomUtil.randomLong(Long.MAX_VALUE); + + ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES); + buffer.order(ByteOrder.LITTLE_ENDIAN); + buffer.putLong(long1); + byte[] bytesLongFromBuffer = buffer.array(); byte[] bytesLong = ByteUtil.longToBytes(long1, ByteOrder.LITTLE_ENDIAN); + Assert.assertArrayEquals(bytesLongFromBuffer, bytesLong); + long long2 = ByteUtil.bytesToLong(bytesLong, ByteOrder.LITTLE_ENDIAN); Assert.assertEquals(long1, long2); @@ -57,11 +77,16 @@ public class ByteUtilTest { @Test public void longAndBytesBigEndianTest() { // 测试大端序 long 转 byte 数组 - long long1 = 2223; + long long1 = RandomUtil.randomLong(Long.MAX_VALUE); + + ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES); + buffer.putLong(long1); + byte[] bytesLongFromBuffer = buffer.array(); byte[] bytesLong = ByteUtil.longToBytes(long1, ByteOrder.BIG_ENDIAN); - long long2 = ByteUtil.bytesToLong(bytesLong, ByteOrder.BIG_ENDIAN); + Assert.assertArrayEquals(bytesLongFromBuffer, bytesLong); + long long2 = ByteUtil.bytesToLong(bytesLong, ByteOrder.BIG_ENDIAN); Assert.assertEquals(long1, long2); } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/PageUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/PageUtilTest.java index 46e6e606b..f382b8711 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/PageUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/PageUtilTest.java @@ -5,32 +5,31 @@ import org.junit.Test; /** * 分页单元测试 - * @author Looly * + * @author Looly */ public class PageUtilTest { - + @Test - public void transToStartEndTest(){ - PageUtil.setFirstPageNo(0); + public void transToStartEndTest() { int[] startEnd1 = PageUtil.transToStartEnd(0, 10); Assert.assertEquals(0, startEnd1[0]); Assert.assertEquals(10, startEnd1[1]); - + int[] startEnd2 = PageUtil.transToStartEnd(1, 10); Assert.assertEquals(10, startEnd2[0]); Assert.assertEquals(20, startEnd2[1]); } - + @Test - public void totalPage(){ + public void totalPage() { int totalPage = PageUtil.totalPage(20, 3); Assert.assertEquals(7, totalPage); } - + @Test public void rainbowTest() { int[] rainbow = PageUtil.rainbow(5, 20, 6); - Assert.assertArrayEquals(new int[] {3, 4, 5, 6, 7, 8}, rainbow); + Assert.assertArrayEquals(new int[]{3, 4, 5, 6, 7, 8}, rainbow); } } diff --git a/hutool-cron/pom.xml b/hutool-cron/pom.xml index f0929496c..df16039ae 100644 --- a/hutool-cron/pom.xml +++ b/hutool-cron/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.20 + 5.7.21 hutool-cron diff --git a/hutool-crypto/pom.xml b/hutool-crypto/pom.xml index 7105bd7f5..f7763706e 100644 --- a/hutool-crypto/pom.xml +++ b/hutool-crypto/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.20 + 5.7.21 hutool-crypto diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml index 4d6d83222..6db487dac 100644 --- a/hutool-db/pom.xml +++ b/hutool-db/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.20 + 5.7.21 hutool-db @@ -27,7 +27,7 @@ 3.36.0.3 2.5.2 - 4.0.0 + 4.1.1 @@ -81,7 +81,7 @@ com.github.chris2018998 beecp - 3.3.0 + 3.3.1 slf4j-api @@ -143,13 +143,13 @@ mysql mysql-connector-java - 8.0.27 + 8.0.28 test org.postgresql postgresql - 42.3.1 + 42.3.2 test @@ -167,13 +167,13 @@ com.h2database h2 - 2.0.206 + 2.1.210 test ru.yandex.clickhouse clickhouse-jdbc - 0.3.1 + 0.3.2 test diff --git a/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java b/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java index ecc7a4371..4f11a56bc 100644 --- a/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java +++ b/hutool-db/src/main/java/cn/hutool/db/ds/c3p0/C3p0DSFactory.java @@ -1,5 +1,6 @@ package cn.hutool.db.ds.c3p0; +import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.db.DbRuntimeException; import cn.hutool.db.ds.AbstractDSFactory; @@ -12,13 +13,13 @@ import java.beans.PropertyVetoException; /** * Druid数据源工厂类 - * + * * @author Looly * */ public class C3p0DSFactory extends AbstractDSFactory { private static final long serialVersionUID = -6090788225842047281L; - + public static final String DS_NAME = "C3P0"; /** @@ -30,24 +31,16 @@ public class C3p0DSFactory extends AbstractDSFactory { /** * 构造 - * + * * @param setting 配置 */ public C3p0DSFactory(Setting setting) { super(DS_NAME, ComboPooledDataSource.class, setting); } - + @Override protected DataSource createDataSource(String jdbcUrl, String driver, String user, String pass, Setting poolSetting) { final ComboPooledDataSource ds = new ComboPooledDataSource(); - ds.setJdbcUrl(jdbcUrl); - try { - ds.setDriverClass(driver); - } catch (PropertyVetoException e) { - throw new DbRuntimeException(e); - } - ds.setUser(user); - ds.setPassword(pass); // remarks等特殊配置,since 5.3.8 final Props connProps = new Props(); @@ -58,7 +51,18 @@ public class C3p0DSFactory extends AbstractDSFactory { connProps.setProperty(key, connValue); } } - ds.setProperties(connProps); + if(MapUtil.isNotEmpty(connProps)){ + ds.setProperties(connProps); + } + + ds.setJdbcUrl(jdbcUrl); + try { + ds.setDriverClass(driver); + } catch (PropertyVetoException e) { + throw new DbRuntimeException(e); + } + ds.setUser(user); + ds.setPassword(pass); // 注入属性 poolSetting.toBean(ds); diff --git a/hutool-db/src/main/java/cn/hutool/db/sql/ConditionGroup.java b/hutool-db/src/main/java/cn/hutool/db/sql/ConditionGroup.java new file mode 100644 index 000000000..fb33305cd --- /dev/null +++ b/hutool-db/src/main/java/cn/hutool/db/sql/ConditionGroup.java @@ -0,0 +1,54 @@ +package cn.hutool.db.sql; + +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.StrUtil; + +import java.util.List; + +/** + * 条件组
+ * 用于构建复杂where条件 + * + * @author tjh + * @since 5.7.21 + */ +public class ConditionGroup extends Condition { + /** + * 条件列表 + */ + private Condition[] conditions; + + /** + * 追加条件 + * + * @param conditions 条件列表 + */ + public void addConditions(Condition... conditions) { + if (null == this.conditions) { + this.conditions = conditions; + } else { + this.conditions = ArrayUtil.addAll(this.conditions, conditions); + } + } + + /** + * 将条件组转换为条件字符串,使用括号包裹,并回填占位符对应的参数值 + * + * @param paramValues 参数列表,用于回填占位符对应参数值 + * @return 条件字符串 + */ + @Override + public String toString(List paramValues) { + if (ArrayUtil.isEmpty(conditions)) { + return StrUtil.EMPTY; + } + + final StringBuilder conditionStrBuilder = StrUtil.builder(); + conditionStrBuilder.append("("); + // 将组内的条件构造为SQL,因为toString,会进行递归,处理所有的条件组 + conditionStrBuilder.append(ConditionBuilder.of(this.conditions).build(paramValues)); + conditionStrBuilder.append(")"); + + return conditionStrBuilder.toString(); + } +} diff --git a/hutool-db/src/test/java/cn/hutool/db/DsTest.java b/hutool-db/src/test/java/cn/hutool/db/DsTest.java index 1d89ca6b1..cec55a376 100644 --- a/hutool-db/src/test/java/cn/hutool/db/DsTest.java +++ b/hutool-db/src/test/java/cn/hutool/db/DsTest.java @@ -2,6 +2,7 @@ package cn.hutool.db; import cn.hutool.core.collection.CollUtil; import cn.hutool.db.ds.DSFactory; +import cn.hutool.db.ds.DataSourceWrapper; import cn.hutool.db.ds.bee.BeeDSFactory; import cn.hutool.db.ds.c3p0.C3p0DSFactory; import cn.hutool.db.ds.dbcp.DbcpDSFactory; @@ -9,6 +10,7 @@ import cn.hutool.db.ds.druid.DruidDSFactory; import cn.hutool.db.ds.hikari.HikariDSFactory; import cn.hutool.db.ds.pooled.PooledDSFactory; import cn.hutool.db.ds.tomcat.TomcatDSFactory; +import com.mchange.v2.c3p0.ComboPooledDataSource; import org.junit.Assert; import org.junit.Test; @@ -87,6 +89,15 @@ public class DsTest { Assert.assertTrue(CollUtil.isNotEmpty(all)); } + @Test + public void c3p0DsUserAndPassTest() { + // https://gitee.com/dromara/hutool/issues/I4T7XZ + DSFactory.setCurrentDSFactory(new C3p0DSFactory()); + ComboPooledDataSource ds = (ComboPooledDataSource) ((DataSourceWrapper) DSFactory.get("mysql")).getRaw(); + Assert.assertEquals("root", ds.getUser()); + Assert.assertEquals("123456", ds.getPassword()); + } + @Test public void hutoolPoolTest() throws SQLException { DSFactory.setCurrentDSFactory(new PooledDSFactory()); diff --git a/hutool-db/src/test/java/cn/hutool/db/sql/ConditionGroupTest.java b/hutool-db/src/test/java/cn/hutool/db/sql/ConditionGroupTest.java new file mode 100644 index 000000000..843e0420b --- /dev/null +++ b/hutool-db/src/test/java/cn/hutool/db/sql/ConditionGroupTest.java @@ -0,0 +1,28 @@ +package cn.hutool.db.sql; + +import cn.hutool.core.collection.ListUtil; +import org.junit.Assert; +import org.junit.Test; + +public class ConditionGroupTest { + @Test + public void ConditionGroupToStringTest() { + Condition condition1 = new Condition("a", "A"); + Condition condition2 = new Condition("b", "B"); + condition2.setLinkOperator(LogicalOperator.OR); + Condition condition3 = new Condition("c", "C"); + Condition condition4 = new Condition("d", "D"); + + ConditionGroup cg = new ConditionGroup(); + cg.addConditions(condition1, condition2); + + // 条件组嵌套情况 + ConditionGroup cg2 = new ConditionGroup(); + cg2.addConditions(cg, condition3); + + final ConditionBuilder conditionBuilder = ConditionBuilder.of(cg2, condition4); + + Assert.assertEquals("((a = ? OR b = ?) AND c = ?) AND d = ?", conditionBuilder.build()); + Assert.assertEquals(ListUtil.of("A", "B", "C", "D"), conditionBuilder.getParamValues()); + } +} diff --git a/hutool-dfa/pom.xml b/hutool-dfa/pom.xml index 244b9c3fe..88f0992e9 100644 --- a/hutool-dfa/pom.xml +++ b/hutool-dfa/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.20 + 5.7.21 hutool-dfa @@ -17,11 +17,6 @@ Hutool 基于DFA的关键词查找 - - cn.hutool - hutool-core - ${project.parent.version} - cn.hutool hutool-json diff --git a/hutool-dfa/src/main/java/cn/hutool/dfa/SensitiveUtil.java b/hutool-dfa/src/main/java/cn/hutool/dfa/SensitiveUtil.java index 0c2584690..bcf81a784 100644 --- a/hutool-dfa/src/main/java/cn/hutool/dfa/SensitiveUtil.java +++ b/hutool-dfa/src/main/java/cn/hutool/dfa/SensitiveUtil.java @@ -195,11 +195,21 @@ public final class SensitiveUtil { */ public static T sensitiveFilter(T bean, boolean isGreedMatch, SensitiveProcessor sensitiveProcessor) { String jsonText = JSONUtil.toJsonStr(bean); - @SuppressWarnings("unchecked") - final Class c = (Class) bean.getClass(); + @SuppressWarnings("unchecked") final Class c = (Class) bean.getClass(); return JSONUtil.toBean(sensitiveFilter(jsonText, isGreedMatch, sensitiveProcessor), c); } + /** + * 处理过滤文本中的敏感词,默认替换成* + * + * @param text 文本 + * @return 敏感词过滤处理后的文本 + * @since 5.7.21 + */ + public static String sensitiveFilter(String text) { + return sensitiveFilter(text, true, null); + } + /** * 处理过滤文本中的敏感词,默认替换成* * @@ -214,13 +224,14 @@ public final class SensitiveUtil { } //敏感词过滤场景下,不需要密集匹配 - List foundWordList = getFoundAllSensitive(text, false, isGreedMatch); + List foundWordList = getFoundAllSensitive(text, true, isGreedMatch); if (CollUtil.isEmpty(foundWordList)) { return text; } sensitiveProcessor = sensitiveProcessor == null ? new SensitiveProcessor() { } : sensitiveProcessor; - Map foundWordMap = new HashMap<>(foundWordList.size()); + + final Map foundWordMap = new HashMap<>(foundWordList.size(), 1); foundWordList.forEach(foundWord -> foundWordMap.put(foundWord.getStartIndex(), foundWord)); int length = text.length(); StringBuilder textStringBuilder = new StringBuilder(); diff --git a/hutool-dfa/src/main/java/cn/hutool/dfa/WordTree.java b/hutool-dfa/src/main/java/cn/hutool/dfa/WordTree.java index 310f5958b..a58371707 100644 --- a/hutool-dfa/src/main/java/cn/hutool/dfa/WordTree.java +++ b/hutool-dfa/src/main/java/cn/hutool/dfa/WordTree.java @@ -3,7 +3,6 @@ package cn.hutool.dfa; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.lang.Filter; -import cn.hutool.core.text.StrBuilder; import cn.hutool.core.util.StrUtil; import java.util.ArrayList; @@ -247,15 +246,15 @@ public class WordTree extends HashMap { List foundWords = new ArrayList<>(); WordTree current = this; - int length = text.length(); + final int length = text.length(); final Filter charFilter = this.charFilter; //存放查找到的字符缓存。完整出现一个词时加到findedWords中,否则清空 - final StrBuilder wordBuffer = StrUtil.strBuilder(); - final StrBuilder keyBuffer = StrUtil.strBuilder(); + final StringBuilder wordBuffer = StrUtil.builder(); + final StringBuilder keyBuffer = StrUtil.builder(); char currentChar; for (int i = 0; i < length; i++) { - wordBuffer.reset(); - keyBuffer.reset(); + wordBuffer.setLength(0); + keyBuffer.setLength(0); for (int j = i; j < length; j++) { currentChar = text.charAt(j); // Console.log("i: {}, j: {}, currentChar: {}", i, j, currentChar); @@ -284,6 +283,7 @@ public class WordTree extends HashMap { if (false == isDensityMatch) { //如果非密度匹配,跳过匹配到的词 i = j; + break; } if (false == isGreedMatch) { //如果懒惰匹配(非贪婪匹配)。当遇到第一个结尾标记就结束本轮匹配 diff --git a/hutool-dfa/src/test/java/cn/hutool/dfa/DfaTest.java b/hutool-dfa/src/test/java/cn/hutool/dfa/DfaTest.java index d466f69b7..f185fac31 100644 --- a/hutool-dfa/src/test/java/cn/hutool/dfa/DfaTest.java +++ b/hutool-dfa/src/test/java/cn/hutool/dfa/DfaTest.java @@ -47,7 +47,7 @@ public class DfaTest { } /** - * 贪婪匹配原则测试 + * 贪婪非密集匹配原则测试 */ @Test public void greedMatchTest() { @@ -56,15 +56,15 @@ public class DfaTest { // ----------------------------------------------------------------------------------------------------------------------------------- // 情况三:匹配到最长关键词,跳过已经匹配的关键词 - // 匹配到【大】,由于到最长匹配,因此【大土豆】接着被匹配 - // 由于【大土豆】被匹配,【土豆】被跳过,由于【刚出锅】被匹配,【出锅】被跳过 + // 匹配到【大】,由于非密集匹配,因此从下一个字符开始查找,匹配到【土豆】接着被匹配 + // 由于【刚出锅】被匹配,由于非密集匹配,【出锅】被跳过 List matchAll = tree.matchAll(text, -1, false, true); - Assert.assertEquals(matchAll, CollUtil.newArrayList("大", "大土^豆", "刚出锅")); + Assert.assertEquals(matchAll, CollUtil.newArrayList("大", "土^豆", "刚出锅")); } /** - * 密集匹配原则(最短匹配)和贪婪匹配原则测试 + * 密集匹配原则(最长匹配)和贪婪匹配原则测试 */ @Test public void densityAndGreedMatchTest() { @@ -80,6 +80,29 @@ public class DfaTest { } + @Test + public void densityAndGreedMatchTest2(){ + WordTree tree = new WordTree(); + tree.addWord("赵"); + tree.addWord("赵阿"); + tree.addWord("赵阿三"); + + final List result = tree.matchAllWords("赵阿三在做什么", -1, true, true); + Assert.assertEquals(3, result.size()); + + Assert.assertEquals("赵", result.get(0).getWord()); + Assert.assertEquals(0, result.get(0).getStartIndex().intValue()); + Assert.assertEquals(0, result.get(0).getEndIndex().intValue()); + + Assert.assertEquals("赵阿", result.get(1).getWord()); + Assert.assertEquals(0, result.get(1).getStartIndex().intValue()); + Assert.assertEquals(1, result.get(1).getEndIndex().intValue()); + + Assert.assertEquals("赵阿三", result.get(2).getWord()); + Assert.assertEquals(0, result.get(2).getStartIndex().intValue()); + Assert.assertEquals(2, result.get(2).getEndIndex().intValue()); + } + /** * 停顿词测试 */ diff --git a/hutool-dfa/src/test/java/cn/hutool/dfa/SensitiveUtilTest.java b/hutool-dfa/src/test/java/cn/hutool/dfa/SensitiveUtilTest.java index ba7348b09..5bbbe9f23 100644 --- a/hutool-dfa/src/test/java/cn/hutool/dfa/SensitiveUtilTest.java +++ b/hutool-dfa/src/test/java/cn/hutool/dfa/SensitiveUtilTest.java @@ -1,5 +1,7 @@ package cn.hutool.dfa; +import cn.hutool.core.collection.ListUtil; +import lombok.Data; import org.junit.Assert; import org.junit.Test; @@ -24,25 +26,17 @@ public class SensitiveUtilTest { Assert.assertEquals(bean.getStr(), "我有一颗$****,***的"); } + @Data public static class TestBean { private String str; private Integer num; - - public String getStr() { - return str; - } - - public void setStr(String str) { - this.str = str; - } - - public Integer getNum() { - return num; - } - - public void setNum(Integer num) { - this.num = num; - } } + @Test + public void issue2126(){ + SensitiveUtil.init(ListUtil.of("赵", "赵阿", "赵阿三")); + + String result = SensitiveUtil.sensitiveFilter("赵阿三在做什么。", true, null); + Assert.assertEquals("***在做什么。", result); + } } diff --git a/hutool-extra/pom.xml b/hutool-extra/pom.xml index 0a6a57284..5e5ddd694 100644 --- a/hutool-extra/pom.xml +++ b/hutool-extra/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.20 + 5.7.21 hutool-extra @@ -19,11 +19,11 @@ 2.3 - 3.9.3.RELEASE + 3.10.0.RELEASE 1.4.1 2.3.31 4.9.16 - 3.0.14.RELEASE + 3.0.15.RELEASE 1.6.2 0.1.55 0.32.0 @@ -31,7 +31,7 @@ 3.8.0 5.1.1 4.0.1 - 2.6.2 + 2.6.3 3.3.0 @@ -119,6 +119,12 @@ 2.6.0 true + + com.github.subchen + jetbrick-template + 2.1.10 + true + @@ -334,7 +340,7 @@ com.github.houbb pinyin - 0.2.2 + 0.3.1 true @@ -440,7 +446,7 @@ org.springframework spring-expression - 5.3.14 + 5.3.15 compile true diff --git a/hutool-extra/src/main/java/cn/hutool/extra/emoji/EmojiUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/emoji/EmojiUtil.java index 1a55be8a5..585eb8dad 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/emoji/EmojiUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/emoji/EmojiUtil.java @@ -126,11 +126,11 @@ public class EmojiUtil { * @return 替换后的字符串 */ public static String toHtmlHex(String str) { - return EmojiParser.parseToHtmlHexadecimal(str); + return toHtml(str, true); } /** - * 将字符串中的Unicode Emoji字符转换为HTML表现形式 + * 将字符串中的Unicode Emoji字符转换为HTML表现形式(Hex方式) *

* 例如:👦🏿 转换为 &#128102; * @@ -138,7 +138,24 @@ public class EmojiUtil { * @return 替换后的字符串 */ public static String toHtml(String str) { - return EmojiParser.parseToHtmlHexadecimal(str); + return toHtml(str, false); + } + + /** + * 将字符串中的Unicode Emoji字符转换为HTML表现形式,例如: + *

+	 * 如果为hex形式,👦🏿 转换为 &#x1f466;
+	 * 否则,👦🏿 转换为 &#128102;
+	 * 
+ * + * @param str 包含Emoji Unicode字符的字符串 + * @param isHex 是否hex形式 + * @return 替换后的字符串 + * @since 5.7.21 + */ + public static String toHtml(String str, boolean isHex) { + return isHex ? EmojiParser.parseToHtmlHexadecimal(str) : + EmojiParser.parseToHtmlDecimal(str); } /** diff --git a/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java index c12071a73..ea08f0921 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java @@ -259,7 +259,7 @@ public class SpringUtil implements BeanFactoryPostProcessor, ApplicationContextA /** * 发布事件 * - * @param event the event to publish + * @param event 待发布的事件,事件必须是{@link ApplicationEvent}的子类 * @since 5.7.12 */ public static void publishEvent(ApplicationEvent event) { @@ -267,6 +267,19 @@ public class SpringUtil implements BeanFactoryPostProcessor, ApplicationContextA applicationContext.publishEvent(event); } } + + /** + * 发布事件 + * Spring 4.2+ 版本事件可以不再是{@link ApplicationEvent}的子类 + * + * @param event 待发布的事件 + * @since 5.7.21 + */ + public static void publishEvent(Object event) { + if (null != applicationContext) { + applicationContext.publishEvent(event); + } + } } diff --git a/hutool-extra/src/main/java/cn/hutool/extra/template/engine/jetbrick/JetbrickEngine.java b/hutool-extra/src/main/java/cn/hutool/extra/template/engine/jetbrick/JetbrickEngine.java new file mode 100755 index 000000000..f6b3f24ed --- /dev/null +++ b/hutool-extra/src/main/java/cn/hutool/extra/template/engine/jetbrick/JetbrickEngine.java @@ -0,0 +1,109 @@ +package cn.hutool.extra.template.engine.jetbrick; + +import cn.hutool.extra.template.Template; +import cn.hutool.extra.template.TemplateConfig; +import cn.hutool.extra.template.TemplateEngine; +import jetbrick.template.JetEngine; + +import java.util.Properties; + +/** + * Jetbrick模板引擎封装
+ * 见:https://github.com/subchen/jetbrick-template-2x + * + * @author looly + * @since 5.7.21 + */ +public class JetbrickEngine implements TemplateEngine { + + private JetEngine engine; + + // --------------------------------------------------------------------------------- Constructor start + /** + * 默认构造 + */ + public JetbrickEngine() {} + + /** + * 构造 + * + * @param config 模板配置 + */ + public JetbrickEngine(TemplateConfig config) { + init(config); + } + + /** + * 构造 + * + * @param engine {@link JetEngine} + */ + public JetbrickEngine(JetEngine engine) { + init(engine); + } + // --------------------------------------------------------------------------------- Constructor end + + + @Override + public TemplateEngine init(TemplateConfig config) { + init(createEngine(config)); + return this; + } + + /** + * 初始化引擎 + * @param engine 引擎 + */ + private void init(JetEngine engine){ + this.engine = engine; + } + + @Override + public Template getTemplate(String resource) { + if(null == this.engine){ + init(TemplateConfig.DEFAULT); + } + return JetbrickTemplate.wrap(engine.getTemplate(resource)); + } + + /** + * 创建引擎 + * + * @param config 模板配置 + * @return {@link JetEngine} + */ + private static JetEngine createEngine(TemplateConfig config) { + if (null == config) { + config = TemplateConfig.DEFAULT; + } + + Properties props = new Properties(); + props.setProperty("jetx.input.encoding", config.getCharsetStr()); + props.setProperty("jetx.output.encoding", config.getCharsetStr()); + props.setProperty("jetx.template.loaders", "$loader"); + + switch (config.getResourceMode()){ + case CLASSPATH: + props.setProperty("$loader", "jetbrick.template.loader.ClasspathResourceLoader"); + props.setProperty("$loader.root", config.getPath()); + break; + case FILE: + props.setProperty("$loader", "jetbrick.template.loader.FileSystemResourceLoader"); + props.setProperty("$loader.root", config.getPath()); + break; + case WEB_ROOT: + props.setProperty("$loader", "jetbrick.template.loader.ServletResourceLoader"); + props.setProperty("$loader.root", config.getPath()); + break; + case STRING: + props.setProperty("$loader", "cn.hutool.extra.template.engine.jetbrick.loader.StringResourceLoader"); + props.setProperty("$loader.charset", config.getCharsetStr()); + break; + default: + // 默认 + return JetEngine.create(); + } + + return JetEngine.create(props); + } +} diff --git a/hutool-extra/src/main/java/cn/hutool/extra/template/engine/jetbrick/JetbrickTemplate.java b/hutool-extra/src/main/java/cn/hutool/extra/template/engine/jetbrick/JetbrickTemplate.java new file mode 100755 index 000000000..78470a41f --- /dev/null +++ b/hutool-extra/src/main/java/cn/hutool/extra/template/engine/jetbrick/JetbrickTemplate.java @@ -0,0 +1,56 @@ +package cn.hutool.extra.template.engine.jetbrick; + +import cn.hutool.core.convert.Convert; +import cn.hutool.core.lang.TypeReference; +import cn.hutool.extra.template.AbstractTemplate; +import jetbrick.template.JetTemplate; + +import java.io.OutputStream; +import java.io.Serializable; +import java.io.Writer; +import java.util.Map; + +/** + * Jetbrick模板实现
+ * 见:https://github.com/subchen/jetbrick-template-2x + * + * @author looly + * @since 5.7.21 + */ +public class JetbrickTemplate extends AbstractTemplate implements Serializable{ + private static final long serialVersionUID = 1L; + + private final JetTemplate rawTemplate; + + /** + * 包装Jetbrick模板 + * + * @param jetTemplate Jetbrick的模板对象 {@link JetTemplate } + * @return JetbrickTemplate + */ + public static JetbrickTemplate wrap(JetTemplate jetTemplate) { + return (null == jetTemplate) ? null : new JetbrickTemplate(jetTemplate); + } + + /** + * 构造 + * + * @param jetTemplate Jetbrick的模板对象 {@link JetTemplate } + */ + public JetbrickTemplate(JetTemplate jetTemplate) { + this.rawTemplate = jetTemplate; + } + + @Override + public void render(Map bindingMap, Writer writer) { + final Map map = Convert.convert(new TypeReference>() {}, bindingMap); + rawTemplate.render(map, writer); + } + + @Override + public void render(Map bindingMap, OutputStream out) { + final Map map = Convert.convert(new TypeReference>() {}, bindingMap); + rawTemplate.render(map, out); + } + +} diff --git a/hutool-extra/src/main/java/cn/hutool/extra/template/engine/jetbrick/loader/StringResourceLoader.java b/hutool-extra/src/main/java/cn/hutool/extra/template/engine/jetbrick/loader/StringResourceLoader.java new file mode 100755 index 000000000..768970963 --- /dev/null +++ b/hutool-extra/src/main/java/cn/hutool/extra/template/engine/jetbrick/loader/StringResourceLoader.java @@ -0,0 +1,82 @@ +package cn.hutool.extra.template.engine.jetbrick.loader; + +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.util.StrUtil; +import jetbrick.io.resource.AbstractResource; +import jetbrick.io.resource.Resource; +import jetbrick.io.resource.ResourceNotFoundException; +import jetbrick.template.loader.AbstractResourceLoader; + +import java.io.InputStream; +import java.net.URL; +import java.nio.charset.Charset; + +/** + * 字符串模板加载器 + * + * @author looly + * @since 5.7.21 + */ +public class StringResourceLoader extends AbstractResourceLoader { + + private Charset charset; + + @Override + public Resource load(String name) { + return new StringTemplateResource(name, charset); + } + + /** + * 设置编码 + * @param charset 编码 + */ + public void setCharset(Charset charset){ + this.charset = charset; + } + + /** + * 字符串模板 + * + * @author looly + */ + static class StringTemplateResource extends AbstractResource { + + private final String content; + private final Charset charset; + + /** + * 构造 + * @param content 模板内容 + * @param charset 编码 + */ + public StringTemplateResource(String content, Charset charset){ + this.content = content; + this.charset = charset; + } + + @Override + public InputStream openStream() throws ResourceNotFoundException { + return IoUtil.toStream(content, charset); + } + + @Override + public URL getURL() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean exist() { + return StrUtil.isEmpty(content); + } + + @Override + public String toString() { + return this.content; + } + + @Override + public long lastModified() { + return 1; + } + } +} diff --git a/hutool-extra/src/main/java/cn/hutool/extra/template/engine/jetbrick/package-info.java b/hutool-extra/src/main/java/cn/hutool/extra/template/engine/jetbrick/package-info.java new file mode 100755 index 000000000..0f33263e5 --- /dev/null +++ b/hutool-extra/src/main/java/cn/hutool/extra/template/engine/jetbrick/package-info.java @@ -0,0 +1,6 @@ +/** + * jetbrick-template实现,模板引擎介绍见:https://github.com/subchen/jetbrick-template-2x + * + * @author looly + */ +package cn.hutool.extra.template.engine.jetbrick; diff --git a/hutool-extra/src/main/resources/META-INF/services/cn.hutool.extra.template.TemplateEngine b/hutool-extra/src/main/resources/META-INF/services/cn.hutool.extra.template.TemplateEngine index 798b778cc..04d98dc93 100644 --- a/hutool-extra/src/main/resources/META-INF/services/cn.hutool.extra.template.TemplateEngine +++ b/hutool-extra/src/main/resources/META-INF/services/cn.hutool.extra.template.TemplateEngine @@ -5,3 +5,4 @@ cn.hutool.extra.template.engine.rythm.RythmEngine cn.hutool.extra.template.engine.enjoy.EnjoyEngine cn.hutool.extra.template.engine.thymeleaf.ThymeleafEngine cn.hutool.extra.template.engine.wit.WitEngine +cn.hutool.extra.template.engine.jetbrick.JetbrickEngine diff --git a/hutool-extra/src/test/java/cn/hutool/extra/qrcode/QrCodeUtilTest.java b/hutool-extra/src/test/java/cn/hutool/extra/qrcode/QrCodeUtilTest.java index 7f31d796f..30dde68d6 100644 --- a/hutool-extra/src/test/java/cn/hutool/extra/qrcode/QrCodeUtilTest.java +++ b/hutool-extra/src/test/java/cn/hutool/extra/qrcode/QrCodeUtilTest.java @@ -94,4 +94,6 @@ public class QrCodeUtilTest { final BufferedImage image = QrCodeUtil.generate("content111", BarcodeFormat.PDF_417, QrConfig.create()); Assert.assertNotNull(image); } + + } diff --git a/hutool-extra/src/test/java/cn/hutool/extra/template/JetbrickTest.java b/hutool-extra/src/test/java/cn/hutool/extra/template/JetbrickTest.java new file mode 100755 index 000000000..1ee03b473 --- /dev/null +++ b/hutool-extra/src/test/java/cn/hutool/extra/template/JetbrickTest.java @@ -0,0 +1,32 @@ +package cn.hutool.extra.template; + +import cn.hutool.core.lang.Dict; +import cn.hutool.core.util.StrUtil; +import cn.hutool.extra.template.engine.jetbrick.JetbrickEngine; +import org.junit.Assert; +import org.junit.Test; + +public class JetbrickTest { + + @Test + public void jetbrickEngineTest() { + //classpath模板 + TemplateConfig config = new TemplateConfig("templates", TemplateConfig.ResourceMode.CLASSPATH) + .setCustomEngine(JetbrickEngine.class); + TemplateEngine engine = TemplateUtil.createEngine(config); + Template template = engine.getTemplate("jetbrick_test.jetx"); + String result = template.render(Dict.create().set("name", "hutool")); + Assert.assertEquals("你好,hutool", StrUtil.trim(result)); + } + + @Test + public void jetbrickEngineWithStringTest() { + // 字符串模板 + TemplateConfig config = new TemplateConfig("templates", TemplateConfig.ResourceMode.STRING) + .setCustomEngine(JetbrickEngine.class); + TemplateEngine engine = TemplateUtil.createEngine(config); + Template template = engine.getTemplate("hello,${name}"); + String result = template.render(Dict.create().set("name", "hutool")); + Assert.assertEquals("hello,hutool", StrUtil.trim(result)); + } +} diff --git a/hutool-extra/src/test/java/cn/hutool/extra/template/TemplateUtilTest.java b/hutool-extra/src/test/java/cn/hutool/extra/template/TemplateUtilTest.java index c7db4fa11..2889ddf50 100644 --- a/hutool-extra/src/test/java/cn/hutool/extra/template/TemplateUtilTest.java +++ b/hutool-extra/src/test/java/cn/hutool/extra/template/TemplateUtilTest.java @@ -156,7 +156,7 @@ public class TemplateUtilTest { } @Test - public void WitEngineTest() { + public void witEngineTest() { //classpath模板 TemplateConfig config = new TemplateConfig("templates", ResourceMode.CLASSPATH) .setCustomEngine(WitEngine.class); diff --git a/hutool-extra/src/test/resources/templates/jetbrick_test.jetx b/hutool-extra/src/test/resources/templates/jetbrick_test.jetx new file mode 100755 index 000000000..a471f9f6f --- /dev/null +++ b/hutool-extra/src/test/resources/templates/jetbrick_test.jetx @@ -0,0 +1 @@ +你好,${name} diff --git a/hutool-http/pom.xml b/hutool-http/pom.xml index b4a57da4c..1bc8fe8ee 100644 --- a/hutool-http/pom.xml +++ b/hutool-http/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.20 + 5.7.21 hutool-http diff --git a/hutool-http/src/main/java/cn/hutool/http/server/HttpServerBase.java b/hutool-http/src/main/java/cn/hutool/http/server/HttpServerBase.java index 3c2b62fc5..ab0ba5920 100644 --- a/hutool-http/src/main/java/cn/hutool/http/server/HttpServerBase.java +++ b/hutool-http/src/main/java/cn/hutool/http/server/HttpServerBase.java @@ -4,6 +4,7 @@ import cn.hutool.core.util.CharsetUtil; import com.sun.net.httpserver.HttpContext; import com.sun.net.httpserver.HttpExchange; +import java.io.Closeable; import java.nio.charset.Charset; /** @@ -12,7 +13,7 @@ import java.nio.charset.Charset; * @author looly * @since 5.2.6 */ -public class HttpServerBase { +public class HttpServerBase implements Closeable { final static Charset DEFAULT_CHARSET = CharsetUtil.CHARSET_UTF_8; @@ -45,4 +46,12 @@ public class HttpServerBase { public HttpContext getHttpContext() { return getHttpExchange().getHttpContext(); } + + /** + * 调用{@link HttpExchange#close()},关闭请求流和响应流 + */ + @Override + public void close() { + this.httpExchange.close(); + } } diff --git a/hutool-http/src/main/java/cn/hutool/http/server/handler/ActionHandler.java b/hutool-http/src/main/java/cn/hutool/http/server/handler/ActionHandler.java index 940a2c980..42f88c548 100644 --- a/hutool-http/src/main/java/cn/hutool/http/server/handler/ActionHandler.java +++ b/hutool-http/src/main/java/cn/hutool/http/server/handler/ActionHandler.java @@ -33,5 +33,6 @@ public class ActionHandler implements HttpHandler { new HttpServerRequest(httpExchange), new HttpServerResponse(httpExchange) ); + httpExchange.close(); } } diff --git a/hutool-json/pom.xml b/hutool-json/pom.xml index a41748fd4..5f4987380 100644 --- a/hutool-json/pom.xml +++ b/hutool-json/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.20 + 5.7.21 hutool-json diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONArray.java b/hutool-json/src/main/java/cn/hutool/json/JSONArray.java index c1d9e2492..d3973d55e 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONArray.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONArray.java @@ -23,8 +23,6 @@ import java.util.List; import java.util.ListIterator; import java.util.RandomAccess; -import static cn.hutool.json.JSONConverter.jsonConvert; - /** * JSON数组
* JSON数组是表示中括号括住的数据表现形式
@@ -246,7 +244,7 @@ public class JSONArray implements JSON, JSONGetter, List, Rando @Override public T getByPath(String expression, Class resultType) { - return jsonConvert(resultType, getByPath(expression), true); + return JSONConverter.jsonConvert(resultType, getByPath(expression), true); } @Override 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 cf36614e7..2d9279c1c 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONConfig.java @@ -1,6 +1,9 @@ package cn.hutool.json; +import cn.hutool.core.comparator.CompareUtil; + import java.io.Serializable; +import java.util.Comparator; /** * JSON配置项 @@ -15,6 +18,10 @@ public class JSONConfig implements Serializable { * 是否有序,顺序按照加入顺序排序,只针对JSONObject有效 */ private boolean order; + /** + * 键排序规则,{@code null}表示不排序,不排序情况下,如果{@link #order}为{@code true}按照加入顺序排序,否则按照hash排序 + */ + private Comparator keyComparator; /** * 是否忽略转换过程中的异常 */ @@ -70,6 +77,40 @@ public class JSONConfig implements Serializable { return this; } + /** + * 获取键排序规则
+ * 键排序规则,{@code null}表示不排序,不排序情况下,如果{@link #order}为{@code true}按照加入顺序排序,否则按照hash排序 + * + * @return 键排序规则 + * @since 5.7.21 + */ + public Comparator getKeyComparator() { + return this.keyComparator; + } + + /** + * 设置自然排序,即按照字母顺序排序 + * + * @return this + * @since 5.7.21 + */ + public JSONConfig setNatureKeyComparator() { + return setKeyComparator(CompareUtil.naturalComparator()); + } + + /** + * 设置键排序规则
+ * 键排序规则,{@code null}表示不排序,不排序情况下,如果{@link #order}为{@code true}按照加入顺序排序,否则按照hash排序 + * + * @param keyComparator 键排序规则 + * @return this + * @since 5.7.21 + */ + public JSONConfig setKeyComparator(Comparator keyComparator) { + this.keyComparator = keyComparator; + return this; + } + /** * 是否忽略转换过程中的异常 * diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONNull.java b/hutool-json/src/main/java/cn/hutool/json/JSONNull.java index 93599a3cf..452bc36e6 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONNull.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONNull.java @@ -7,10 +7,10 @@ import java.io.Serializable; /** * 用于定义{@code null},与Javascript中null相对应
* Java中的{@code null}值在js中表示为undefined。 - * @author Looly * + * @author Looly */ -public class JSONNull implements Serializable{ +public class JSONNull implements Serializable { private static final long serialVersionUID = 2633815155870764938L; /** @@ -35,7 +35,7 @@ public class JSONNull implements Serializable{ /** * Get the "null" string value. - *获得“null”字符串 + * 获得“null”字符串 * * @return The string "null". */ diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONObject.java b/hutool-json/src/main/java/cn/hutool/json/JSONObject.java index a7582edd4..cff35cd9e 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONObject.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONObject.java @@ -10,6 +10,7 @@ import cn.hutool.core.lang.Filter; import cn.hutool.core.lang.mutable.MutablePair; import cn.hutool.core.map.CaseInsensitiveLinkedMap; import cn.hutool.core.map.CaseInsensitiveMap; +import cn.hutool.core.map.CaseInsensitiveTreeMap; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ObjectUtil; @@ -25,10 +26,12 @@ import java.io.Writer; import java.math.BigDecimal; import java.math.BigInteger; import java.util.Collection; +import java.util.Comparator; import java.util.Enumeration; import java.util.Map; import java.util.ResourceBundle; import java.util.Set; +import java.util.TreeMap; /** * JSON对象
@@ -120,10 +123,21 @@ public class JSONObject implements JSON, JSONGetter, Map if (null == config) { config = JSONConfig.create(); } + final Comparator keyComparator = config.getKeyComparator(); if (config.isIgnoreCase()) { - this.rawHashMap = config.isOrder() ? new CaseInsensitiveLinkedMap<>(capacity) : new CaseInsensitiveMap<>(capacity); + if(null != keyComparator){ + // 比较器存在情况下,isOrder无效 + this.rawHashMap = new CaseInsensitiveTreeMap<>(keyComparator); + }else{ + this.rawHashMap = config.isOrder() ? new CaseInsensitiveLinkedMap<>(capacity) : new CaseInsensitiveMap<>(capacity); + } } else { - this.rawHashMap = MapUtil.newHashMap(capacity, config.isOrder()); + if(null != keyComparator){ + // 比较器存在情况下,isOrder无效 + this.rawHashMap = new TreeMap<>(keyComparator); + }else{ + this.rawHashMap = MapUtil.newHashMap(capacity, config.isOrder()); + } } this.config = config; } @@ -178,7 +192,8 @@ public class JSONObject implements JSON, JSONGetter, Map public JSONObject(Object source, boolean ignoreNullValue, boolean isOrder) { this(source, JSONConfig.create().setOrder(isOrder)// .setIgnoreCase((source instanceof CaseInsensitiveMap))// - .setIgnoreNullValue(ignoreNullValue)); + .setIgnoreNullValue(ignoreNullValue) + ); } /** @@ -592,9 +607,14 @@ public class JSONObject implements JSON, JSONGetter, Map final JSONWriter jsonWriter = JSONWriter.of(writer, indentFactor, indent, config) .beginObj(); this.forEach((key, value) -> { - final MutablePair pair = new MutablePair<>(key, value); - if (null == filter || filter.accept(pair)) { - jsonWriter.writeField(pair.getKey(), pair.getValue()); + if (null != filter){ + final MutablePair pair = new MutablePair<>(key, value); + if (filter.accept(pair)) { + // 使用修改后的键值对 + jsonWriter.writeField(pair.getKey(), pair.getValue()); + } + } else { + jsonWriter.writeField(key, value); } }); jsonWriter.end(); 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 6fb43e6db..59eb12d56 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java @@ -743,7 +743,7 @@ public class JSONUtil { return jsonConfig.isIgnoreNullValue() ? null : JSONNull.NULL; } if (object instanceof JSON // - || JSONNull.NULL.equals(object) // + || ObjectUtil.isNull(object) // || object instanceof JSONString // || object instanceof CharSequence // || object instanceof Number // diff --git a/hutool-json/src/main/java/cn/hutool/json/serialize/JSONWriter.java b/hutool-json/src/main/java/cn/hutool/json/serialize/JSONWriter.java index c57ef2488..3d6024387 100755 --- a/hutool-json/src/main/java/cn/hutool/json/serialize/JSONWriter.java +++ b/hutool-json/src/main/java/cn/hutool/json/serialize/JSONWriter.java @@ -249,22 +249,20 @@ public class JSONWriter extends Writer { * 此方法输出的值不包装引号。 * * @param number 数字 - * @return this */ - private JSONWriter writeNumberValue(Number number) { + private void writeNumberValue(Number number) { // since 5.6.2可配置是否去除末尾多余0,例如如果为true,5.0返回5 final boolean isStripTrailingZeros = null == config || config.isStripTrailingZeros(); - return writeRaw(NumberUtil.toStr(number, isStripTrailingZeros)); + writeRaw(NumberUtil.toStr(number, isStripTrailingZeros)); } /** * 写出Boolean值,直接写出true或false,不适用引号包装 * * @param value Boolean值 - * @return this */ - private JSONWriter writeBooleanValue(Boolean value) { - return writeRaw(value.toString()); + private void writeBooleanValue(Boolean value) { + writeRaw(value.toString()); } /** @@ -273,9 +271,8 @@ public class JSONWriter extends Writer { * 如果toJSONString()返回null,调用toString()方法并使用双引号包装。 * * @param jsonString {@link JSONString} - * @return this */ - private JSONWriter writeJSONStringValue(JSONString jsonString) { + private void writeJSONStringValue(JSONString jsonString) { String valueStr; try { valueStr = jsonString.toJSONString(); @@ -287,7 +284,6 @@ public class JSONWriter extends Writer { } else { writeStrValue(jsonString.toString()); } - return this; } /** @@ -297,30 +293,26 @@ public class JSONWriter extends Writer { * JSON字符串中不能包含控制字符和未经转义的引号和反斜杠 * * @param csq 字符串 - * @return this */ - private JSONWriter writeStrValue(String csq) { + private void writeStrValue(String csq) { try { JSONUtil.quote(csq, writer); } catch (IOException e) { throw new IORuntimeException(e); } - return this; } /** * 写出空格 * * @param count 空格数 - * @return this */ - private JSONWriter writeSpace(int count) { + private void writeSpace(int count) { if (indentFactor > 0) { for (int i = 0; i < count; i++) { writeRaw(CharUtil.SPACE); } } - return this; } /** diff --git a/hutool-json/src/test/java/cn/hutool/json/Issue2131Test.java b/hutool-json/src/test/java/cn/hutool/json/Issue2131Test.java new file mode 100755 index 000000000..826a8274a --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/Issue2131Test.java @@ -0,0 +1,62 @@ +package cn.hutool.json; + +import cn.hutool.core.collection.ListUtil; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; +import org.junit.Assert; +import org.junit.Test; + +import java.beans.Transient; +import java.util.List; +import java.util.stream.Stream; + +/** + * https://github.com/dromara/hutool/issues/2131
+ * 字段定义成final,意味着setCollections无效,因此JSON转Bean的时候无法调用setCollections注入,所以是空的。 + */ +public class Issue2131Test { + + @Test + public void strToBean() { + GoodsResponse goodsResponse = new GoodsResponse(); + GoodsItem apple = new GoodsItem().setGoodsId(1L).setGoodsName("apple").setChannel("wechat"); + GoodsItem pear = new GoodsItem().setGoodsId(2L).setGoodsName("pear").setChannel("jd"); + final List collections = goodsResponse.getCollections(); + Stream.of(apple, pear).forEach(collections::add); + + String jsonStr = JSONUtil.toJsonStr(goodsResponse); + final JSONObject jsonObject = JSONUtil.parseObj(jsonStr); + + GoodsResponse result = jsonObject.toBean(GoodsResponse.class); + Assert.assertEquals(0, result.getCollections().size()); + } + + @Data + static class BaseResponse { + + @SuppressWarnings("unused") + @Transient + public final boolean successful() { + return code == 200 || code == 201; + } + + private int code = 200; + private String message; + } + + @EqualsAndHashCode(callSuper = true) + @Data + static class GoodsResponse extends BaseResponse { + // 由于定义成了final形式,setXXX无效,导致无法注入。 + private final List collections = ListUtil.list(false); + } + + @Data + @Accessors(chain = true) + static class GoodsItem{ + private long goodsId; + private String goodsName; + private String channel; + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/Issue677Test.java b/hutool-json/src/test/java/cn/hutool/json/Issue677Test.java index 4cef2f657..b6a041318 100644 --- a/hutool-json/src/test/java/cn/hutool/json/Issue677Test.java +++ b/hutool-json/src/test/java/cn/hutool/json/Issue677Test.java @@ -19,7 +19,7 @@ public class Issue677Test { final String jsonStr = JSONUtil.toJsonStr(dto); final AuditResultDto auditResultDto = JSONUtil.toBean(jsonStr, AuditResultDto.class); - Assert.assertEquals("Mon Dec 15 00:00:00 CST 1969", auditResultDto.getDate().toString()); + Assert.assertEquals("Mon Dec 15 00:00:00 CST 1969", auditResultDto.getDate().toString().replace("GMT+08:00", "CST")); } @Data diff --git a/hutool-json/src/test/java/cn/hutool/json/IssueI4RBZ4Test.java b/hutool-json/src/test/java/cn/hutool/json/IssueI4RBZ4Test.java new file mode 100755 index 000000000..021fd3333 --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/IssueI4RBZ4Test.java @@ -0,0 +1,18 @@ +package cn.hutool.json; + +import org.junit.Assert; +import org.junit.Test; + +/** + * https://gitee.com/dromara/hutool/issues/I4RBZ4 + */ +public class IssueI4RBZ4Test { + + @Test + public void sortTest(){ + String jsonStr = "{\"id\":\"123\",\"array\":[1,2,3],\"outNum\":356,\"body\":{\"ava1\":\"20220108\",\"use\":1,\"ava2\":\"20230108\"},\"name\":\"John\"}"; + + final JSONObject jsonObject = JSONUtil.parseObj(jsonStr, JSONConfig.create().setNatureKeyComparator()); + Assert.assertEquals("{\"array\":[1,2,3],\"body\":{\"ava1\":\"20220108\",\"ava2\":\"20230108\",\"use\":1},\"id\":\"123\",\"name\":\"John\",\"outNum\":356}", jsonObject.toString()); + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/issues1881Test.java b/hutool-json/src/test/java/cn/hutool/json/Issues1881Test.java similarity index 74% rename from hutool-json/src/test/java/cn/hutool/json/issues1881Test.java rename to hutool-json/src/test/java/cn/hutool/json/Issues1881Test.java index 07cdee890..a8475bcff 100644 --- a/hutool-json/src/test/java/cn/hutool/json/issues1881Test.java +++ b/hutool-json/src/test/java/cn/hutool/json/Issues1881Test.java @@ -1,18 +1,19 @@ package cn.hutool.json; -import cn.hutool.core.lang.Console; import lombok.Data; import lombok.experimental.Accessors; +import org.junit.Assert; import org.junit.Test; import java.io.Serializable; import java.util.ArrayList; import java.util.List; -public class issues1881Test { +public class Issues1881Test { + @Accessors(chain = true) @Data - public class ThingsHolderContactVO implements Serializable { + static class ThingsHolderContactVO implements Serializable { private static final long serialVersionUID = -8727337936070932370L; private Long id; @@ -30,6 +31,6 @@ public class issues1881Test { holderContactVOList.add(new ThingsHolderContactVO().setId(1L).setName("1")); holderContactVOList.add(new ThingsHolderContactVO().setId(2L).setName("2")); - Console.log(JSONUtil.parseArray(holderContactVOList)); + Assert.assertEquals("[{\"name\":\"1\",\"id\":1},{\"name\":\"2\",\"id\":2}]", JSONUtil.parseArray(holderContactVOList).toString()); } } diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java index 3b18c8059..afa1616f0 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java @@ -30,7 +30,6 @@ import org.junit.Test; import java.math.BigDecimal; import java.sql.Timestamp; -import java.time.LocalDate; import java.util.Date; import java.util.HashMap; import java.util.List; @@ -658,6 +657,21 @@ public class JSONObjectTest { Assert.assertEquals("{\"b\":\"value2_edit\"}", s); } + @Test + public void toUnderLineCaseTest() { + JSONObject json1 = JSONUtil.createObj(JSONConfig.create().setOrder(true)) + .set("aKey", "value1") + .set("bJob", "value2") + .set("cGood", "value3") + .set("d", true); + + final String s = json1.toJSONString(0, (pair) -> { + pair.setKey(StrUtil.toUnderlineCase(pair.getKey())); + return true; + }); + Assert.assertEquals("{\"a_key\":\"value1\",\"b_job\":\"value2\",\"c_good\":\"value3\",\"d\":true}", s); + } + @Test public void nullToEmptyTest() { JSONObject json1 = JSONUtil.createObj(JSONConfig.create().setOrder(true).setIgnoreNullValue(false)) diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java index fc89eace0..9a6ae58e0 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONStrFormatterTest.java @@ -2,6 +2,7 @@ package cn.hutool.json; import cn.hutool.core.lang.Console; import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; /** @@ -33,6 +34,7 @@ public class JSONStrFormatterTest { } @Test + @Ignore public void formatTest4(){ String jsonStr = "{\"employees\":[{\"firstName\":\"Bill\",\"lastName\":\"Gates\"},{\"firstName\":\"George\",\"lastName\":\"Bush\"},{\"firstName\":\"Thomas\",\"lastName\":\"Carter\"}]}"; Console.log(JSONUtil.formatJsonStr(jsonStr)); diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java index a067554e0..8a99de73d 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONUtilTest.java @@ -36,7 +36,7 @@ public class JSONUtilTest { @Test(expected = JSONException.class) public void parseNumberTest() { JSONArray json = JSONUtil.parseArray(123L); - Console.log(json); + Assert.assertNotNull(json); } /** diff --git a/hutool-jwt/pom.xml b/hutool-jwt/pom.xml index 549b5b819..f46804cf3 100644 --- a/hutool-jwt/pom.xml +++ b/hutool-jwt/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.20 + 5.7.21 hutool-jwt @@ -18,7 +18,7 @@ - 1.69 + 1.70 diff --git a/hutool-log/pom.xml b/hutool-log/pom.xml index ea661e721..acdee8a97 100644 --- a/hutool-log/pom.xml +++ b/hutool-log/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.20 + 5.7.21 hutool-log @@ -24,7 +24,7 @@ 2.17.1 1.2 1.3.6 - 3.4.2.Final + 3.4.3.Final 0.43.0 diff --git a/hutool-log/src/main/java/cn/hutool/log/package-info.java b/hutool-log/src/main/java/cn/hutool/log/package-info.java index 1434801bb..974f4a437 100644 --- a/hutool-log/src/main/java/cn/hutool/log/package-info.java +++ b/hutool-log/src/main/java/cn/hutool/log/package-info.java @@ -1,8 +1,8 @@ /** * Hutool-log只是一个日志的通用门面,功能类似于Slf4j。
- * 根据加入ClassPath中的jar包动态检测日志实现的方式,使日志使用个更加便利灵活。 - * + * 根据加入ClassPath中的jar包动态检测日志实现的方式,使日志使用更加便利灵活。 + * * @author looly * */ -package cn.hutool.log; \ No newline at end of file +package cn.hutool.log; diff --git a/hutool-poi/pom.xml b/hutool-poi/pom.xml index 80d7ce519..6b7badbc8 100644 --- a/hutool-poi/pom.xml +++ b/hutool-poi/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.20 + 5.7.21 hutool-poi @@ -45,7 +45,7 @@ org.ofdrw ofdrw-full - 1.17.6 + 1.17.8 compile true diff --git a/hutool-script/pom.xml b/hutool-script/pom.xml index 7fa28a4f5..231a62ea5 100644 --- a/hutool-script/pom.xml +++ b/hutool-script/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.20 + 5.7.21 hutool-script diff --git a/hutool-setting/pom.xml b/hutool-setting/pom.xml index 7bd5fdce5..32b8e0006 100644 --- a/hutool-setting/pom.xml +++ b/hutool-setting/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.20 + 5.7.21 hutool-setting @@ -30,7 +30,7 @@ org.yaml snakeyaml - 1.29 + 1.30 true
diff --git a/hutool-socket/pom.xml b/hutool-socket/pom.xml index d8d684dfe..df5876770 100644 --- a/hutool-socket/pom.xml +++ b/hutool-socket/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.20 + 5.7.21 hutool-socket diff --git a/hutool-system/pom.xml b/hutool-system/pom.xml index 4105c3a27..3d0b084cc 100644 --- a/hutool-system/pom.xml +++ b/hutool-system/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.20 + 5.7.21 hutool-system @@ -30,7 +30,7 @@ com.github.oshi oshi-core - 6.0.0 + 6.1.0 provided diff --git a/hutool-system/src/main/java/cn/hutool/system/oshi/CpuInfo.java b/hutool-system/src/main/java/cn/hutool/system/oshi/CpuInfo.java index 531e06cc2..c9d3fdb47 100644 --- a/hutool-system/src/main/java/cn/hutool/system/oshi/CpuInfo.java +++ b/hutool-system/src/main/java/cn/hutool/system/oshi/CpuInfo.java @@ -1,5 +1,6 @@ package cn.hutool.system.oshi; +import cn.hutool.core.util.NumberUtil; import oshi.hardware.CentralProcessor; import java.text.DecimalFormat; @@ -31,7 +32,7 @@ public class CpuInfo { /** * CPU用户使用率 */ - private double used; + private double user; /** * CPU当前等待率 @@ -75,16 +76,16 @@ public class CpuInfo { * @param cpuNum CPU核心数 * @param toTal CPU总的使用率 * @param sys CPU系统使用率 - * @param used CPU用户使用率 + * @param user CPU用户使用率 * @param wait CPU当前等待率 * @param free CPU当前空闲率 * @param cpuModel CPU型号信息 */ - public CpuInfo(Integer cpuNum, double toTal, double sys, double used, double wait, double free, String cpuModel) { + public CpuInfo(Integer cpuNum, double toTal, double sys, double user, double wait, double free, String cpuModel) { this.cpuNum = cpuNum; this.toTal = toTal; this.sys = sys; - this.used = used; + this.user = user; this.wait = wait; this.free = free; this.cpuModel = cpuModel; @@ -114,12 +115,12 @@ public class CpuInfo { this.sys = sys; } - public double getUsed() { - return used; + public double getUser() { + return user; } - public void setUsed(double used) { - this.used = used; + public void setUser(double user) { + this.user = user; } public double getWait() { @@ -154,16 +155,25 @@ public class CpuInfo { this.ticks = ticks; } + /** + * 获取用户+系统的总的CPU使用率 + * + * @return 总CPU使用率 + */ + public double getUsed() { + return NumberUtil.sub(100, this.free); + } + @Override public String toString() { return "CpuInfo{" + "CPU核心数=" + cpuNum + ", CPU总的使用率=" + toTal + ", CPU系统使用率=" + sys + - ", CPU用户使用率=" + used + + ", CPU用户使用率=" + user + ", CPU当前等待率=" + wait + ", CPU当前空闲率=" + free + - ", CPU利用率=" + LOAD_FORMAT.format(100 - free) + + ", CPU利用率=" + getUsed() + ", CPU型号信息='" + cpuModel + '\'' + '}'; } @@ -185,7 +195,7 @@ public class CpuInfo { final long totalCpu = ticks.totalCpu(); this.toTal = totalCpu; this.sys = formatDouble(ticks.cSys, totalCpu); - this.used = formatDouble(ticks.user, totalCpu); + this.user = formatDouble(ticks.user, totalCpu); this.wait = formatDouble(ticks.ioWait, totalCpu); this.free = formatDouble(ticks.idle, totalCpu); } diff --git a/hutool-system/src/test/java/cn/hutool/system/OshiTest.java b/hutool-system/src/test/java/cn/hutool/system/OshiTest.java index 88abfdced..2bddf3b0b 100644 --- a/hutool-system/src/test/java/cn/hutool/system/OshiTest.java +++ b/hutool-system/src/test/java/cn/hutool/system/OshiTest.java @@ -1,8 +1,10 @@ package cn.hutool.system; +import cn.hutool.core.lang.Console; import cn.hutool.system.oshi.CpuInfo; import cn.hutool.system.oshi.OshiUtil; import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; import oshi.software.os.OSProcess; @@ -28,4 +30,12 @@ public class OshiTest { final OSProcess currentProcess = OshiUtil.getCurrentProcess(); Assert.assertEquals("java", currentProcess.getName()); } + + @Test + @Ignore + public void getUsedTest(){ + while (true){ + Console.log(OshiUtil.getCpuInfo().getUsed()); + } + } } diff --git a/pom.xml b/pom.xml index ec82d9de5..ba31d88c9 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.7.20 + 5.7.21 hutool Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。 https://github.com/dromara/hutool @@ -157,7 +157,7 @@ html xml - +