From 70976fd9fa36b61167084ce0a599443ea09059d8 Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 14 Apr 2022 01:59:37 +0800 Subject: [PATCH 01/52] 5.8.0.M4 --- CHANGELOG.md | 10 ++++++++++ README-EN.md | 6 +++--- README.md | 6 +++--- bin/version.txt | 2 +- docs/js/version.js | 2 +- hutool-all/pom.xml | 2 +- hutool-aop/pom.xml | 2 +- hutool-bloomFilter/pom.xml | 2 +- hutool-bom/pom.xml | 2 +- hutool-cache/pom.xml | 2 +- hutool-captcha/pom.xml | 2 +- hutool-core/pom.xml | 2 +- hutool-cron/pom.xml | 2 +- hutool-crypto/pom.xml | 2 +- hutool-db/pom.xml | 2 +- hutool-dfa/pom.xml | 2 +- hutool-extra/pom.xml | 2 +- hutool-http/pom.xml | 2 +- hutool-json/pom.xml | 2 +- hutool-jwt/pom.xml | 2 +- hutool-log/pom.xml | 2 +- hutool-poi/pom.xml | 2 +- hutool-script/pom.xml | 2 +- hutool-setting/pom.xml | 2 +- hutool-socket/pom.xml | 2 +- hutool-system/pom.xml | 2 +- pom.xml | 2 +- 27 files changed, 40 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7497445f6..4484899fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,16 @@ ------------------------------------------------------------------------------------------------------------- +# 5.8.0.M4 (2022-04-14) + +### ❌不兼容特性 + +### 🐣新特性 + +### 🐞Bug修复 + +------------------------------------------------------------------------------------------------------------- + # 5.8.0.M3 (2022-04-14) ### ❌不兼容特性 diff --git a/README-EN.md b/README-EN.md index 38f1421db..939bac4f8 100644 --- a/README-EN.md +++ b/README-EN.md @@ -144,18 +144,18 @@ We provide the T-Shirt and Sweater with Hutool Logo, please visit the shop: cn.hutool hutool-all - 5.8.0.M3 + 5.8.0.M4 ``` ### 🍐Gradle ``` -implementation 'cn.hutool:hutool-all:5.8.0.M3' +implementation 'cn.hutool:hutool-all:5.8.0.M4' ``` ## 📥Download -- [Maven Repo](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.8.0.M3/) +- [Maven Repo](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.8.0.M4/) > 🔔️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 0cb5c8751..91c9311af 100644 --- a/README.md +++ b/README.md @@ -144,20 +144,20 @@ Hutool的存在就是为了减少代码搜索成本,避免网络上参差不 cn.hutool hutool-all - 5.8.0.M3 + 5.8.0.M4 ``` ### 🍐Gradle ``` -implementation 'cn.hutool:hutool-all:5.8.0.M3' +implementation 'cn.hutool:hutool-all:5.8.0.M4' ``` ### 📥下载jar 点击以下链接,下载`hutool-all-X.X.X.jar`即可: -- [Maven中央库](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.8.0.M3/) +- [Maven中央库](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.8.0.M4/) > 🔔️注意 > Hutool 5.x支持JDK8+,对Android平台没有测试,不能保证所有工具类或工具方法可用。 diff --git a/bin/version.txt b/bin/version.txt index 5071809a6..b4fb92abd 100755 --- a/bin/version.txt +++ b/bin/version.txt @@ -1 +1 @@ -5.8.0.M3 +5.8.0.M4 diff --git a/docs/js/version.js b/docs/js/version.js index 75d77d7cd..2ca0f38dd 100644 --- a/docs/js/version.js +++ b/docs/js/version.js @@ -1 +1 @@ -var version = '5.8.0.M3' \ No newline at end of file +var version = '5.8.0.M4' \ No newline at end of file diff --git a/hutool-all/pom.xml b/hutool-all/pom.xml index c3a102940..1650512a3 100644 --- a/hutool-all/pom.xml +++ b/hutool-all/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.0.M3 + 5.8.0.M4 hutool-all diff --git a/hutool-aop/pom.xml b/hutool-aop/pom.xml index 1e7158462..f5f804a82 100644 --- a/hutool-aop/pom.xml +++ b/hutool-aop/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.0.M3 + 5.8.0.M4 hutool-aop diff --git a/hutool-bloomFilter/pom.xml b/hutool-bloomFilter/pom.xml index 9f51d6623..74d8596e1 100644 --- a/hutool-bloomFilter/pom.xml +++ b/hutool-bloomFilter/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.0.M3 + 5.8.0.M4 hutool-bloomFilter diff --git a/hutool-bom/pom.xml b/hutool-bom/pom.xml index fb25bc43c..9a400e8a6 100644 --- a/hutool-bom/pom.xml +++ b/hutool-bom/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.0.M3 + 5.8.0.M4 hutool-bom diff --git a/hutool-cache/pom.xml b/hutool-cache/pom.xml index fd4fcf49e..0bd5e426c 100644 --- a/hutool-cache/pom.xml +++ b/hutool-cache/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.0.M3 + 5.8.0.M4 hutool-cache diff --git a/hutool-captcha/pom.xml b/hutool-captcha/pom.xml index d25d11ff4..c556d45fe 100644 --- a/hutool-captcha/pom.xml +++ b/hutool-captcha/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.0.M3 + 5.8.0.M4 hutool-captcha diff --git a/hutool-core/pom.xml b/hutool-core/pom.xml index 32d3ae15a..22d50a2c0 100644 --- a/hutool-core/pom.xml +++ b/hutool-core/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.0.M3 + 5.8.0.M4 hutool-core diff --git a/hutool-cron/pom.xml b/hutool-cron/pom.xml index 51407bb01..a909049f1 100644 --- a/hutool-cron/pom.xml +++ b/hutool-cron/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.0.M3 + 5.8.0.M4 hutool-cron diff --git a/hutool-crypto/pom.xml b/hutool-crypto/pom.xml index 4585a82ed..78413bf0d 100644 --- a/hutool-crypto/pom.xml +++ b/hutool-crypto/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.0.M3 + 5.8.0.M4 hutool-crypto diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml index 888c5c678..fa75fee03 100644 --- a/hutool-db/pom.xml +++ b/hutool-db/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.0.M3 + 5.8.0.M4 hutool-db diff --git a/hutool-dfa/pom.xml b/hutool-dfa/pom.xml index 2cb07822c..0fe550242 100644 --- a/hutool-dfa/pom.xml +++ b/hutool-dfa/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.0.M3 + 5.8.0.M4 hutool-dfa diff --git a/hutool-extra/pom.xml b/hutool-extra/pom.xml index 690a964f2..38ae8c1d9 100644 --- a/hutool-extra/pom.xml +++ b/hutool-extra/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.0.M3 + 5.8.0.M4 hutool-extra diff --git a/hutool-http/pom.xml b/hutool-http/pom.xml index 6a12c56f2..d09e036c8 100644 --- a/hutool-http/pom.xml +++ b/hutool-http/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.0.M3 + 5.8.0.M4 hutool-http diff --git a/hutool-json/pom.xml b/hutool-json/pom.xml index ac5a0c52a..0e9901c56 100644 --- a/hutool-json/pom.xml +++ b/hutool-json/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.0.M3 + 5.8.0.M4 hutool-json diff --git a/hutool-jwt/pom.xml b/hutool-jwt/pom.xml index d0e72b426..a328d47e8 100644 --- a/hutool-jwt/pom.xml +++ b/hutool-jwt/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.0.M3 + 5.8.0.M4 hutool-jwt diff --git a/hutool-log/pom.xml b/hutool-log/pom.xml index ceb8fcd97..52d53c9e4 100644 --- a/hutool-log/pom.xml +++ b/hutool-log/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.0.M3 + 5.8.0.M4 hutool-log diff --git a/hutool-poi/pom.xml b/hutool-poi/pom.xml index b2433b99c..2fdd173d9 100644 --- a/hutool-poi/pom.xml +++ b/hutool-poi/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.0.M3 + 5.8.0.M4 hutool-poi diff --git a/hutool-script/pom.xml b/hutool-script/pom.xml index fa1c3ff8d..01b0b101a 100644 --- a/hutool-script/pom.xml +++ b/hutool-script/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.0.M3 + 5.8.0.M4 hutool-script diff --git a/hutool-setting/pom.xml b/hutool-setting/pom.xml index fb118b8ad..1ef625cb9 100644 --- a/hutool-setting/pom.xml +++ b/hutool-setting/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.0.M3 + 5.8.0.M4 hutool-setting diff --git a/hutool-socket/pom.xml b/hutool-socket/pom.xml index ff1e0edc3..d8044efb2 100644 --- a/hutool-socket/pom.xml +++ b/hutool-socket/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.0.M3 + 5.8.0.M4 hutool-socket diff --git a/hutool-system/pom.xml b/hutool-system/pom.xml index d4140ece8..41463988d 100644 --- a/hutool-system/pom.xml +++ b/hutool-system/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.0.M3 + 5.8.0.M4 hutool-system diff --git a/pom.xml b/pom.xml index c158c9f0e..e9793eb85 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.8.0.M3 + 5.8.0.M4 hutool Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。 https://github.com/dromara/hutool From 3e16ec4345575012abf02809e76f0b73f1481bac Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 14 Apr 2022 13:45:57 +0800 Subject: [PATCH 02/52] fix doc --- hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java b/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java index 2015bdc77..a215caac8 100755 --- a/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java @@ -184,7 +184,7 @@ public class HttpGlobalConfig implements Serializable { /** * 增加支持的METHOD方法
* 此方法通过注入方式修改{@link HttpURLConnection}中的methods静态属性,增加PATCH方法
- * see: https://stackoverflow.com/questions/25163131/httpurlconnection-invalid-http-method-patch + * see: https://stackoverflow.com/questions/25163131/httpurlconnection-invalid-http-method-patch * * @since 5.7.4 */ From 50abb8f6a6a23c418ddace8b850e809f2191a94b Mon Sep 17 00:00:00 2001 From: Looly Date: Fri, 15 Apr 2022 06:20:47 +0800 Subject: [PATCH 03/52] add method --- CHANGELOG.md | 3 ++- .../java/cn/hutool/core/bean/BeanUtil.java | 19 +++++++++++++++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4484899fd..04520ca7d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,11 +3,12 @@ ------------------------------------------------------------------------------------------------------------- -# 5.8.0.M4 (2022-04-14) +# 5.8.0.M4 (2022-04-15) ### ❌不兼容特性 ### 🐣新特性 +* 【core 】 BeanUtil增加toBean重载(pr#598@Gitee) ### 🐞Bug修复 diff --git a/hutool-core/src/main/java/cn/hutool/core/bean/BeanUtil.java b/hutool-core/src/main/java/cn/hutool/core/bean/BeanUtil.java index 3f0428f84..eabce8de0 100644 --- a/hutool-core/src/main/java/cn/hutool/core/bean/BeanUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/bean/BeanUtil.java @@ -30,6 +30,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.function.Consumer; +import java.util.function.Supplier; import java.util.stream.Collectors; /** @@ -548,10 +549,24 @@ public class BeanUtil { * @since 5.2.4 */ public static T toBean(Object source, Class clazz, CopyOptions options) { - if (null == source) { + return toBean(source, () -> ReflectUtil.newInstanceIfPossible(clazz), options); + } + + /** + * 对象或Map转Bean + * + * @param 转换的Bean类型 + * @param source Bean对象或Map + * @param targetSupplier 目标的Bean创建器 + * @param options 属性拷贝选项 + * @return Bean对象 + * @since 5.8.0 + */ + public static T toBean(Object source, Supplier targetSupplier, CopyOptions options) { + if (null == source || null == targetSupplier) { return null; } - final T target = ReflectUtil.newInstanceIfPossible(clazz); + final T target = targetSupplier.get(); copyProperties(source, target, options); return target; } From f23ec7eb160d86b9e1b9b5729b3d479309d321f2 Mon Sep 17 00:00:00 2001 From: Looly Date: Sat, 16 Apr 2022 08:13:37 +0800 Subject: [PATCH 04/52] fix code --- CHANGELOG.md | 0 .../java/cn/hutool/core/bean/BeanUtil.java | 0 .../java/cn/hutool/core/util/IdcardUtil.java | 4 ++-- .../hutool/core/text/csv/CsvWriterTest.java | 21 +++++++++++++++++++ .../cn/hutool/extra/mail/MailAccount.java | 2 -- .../main/java/cn/hutool/json/JSONUtil.java | 2 +- 6 files changed, 24 insertions(+), 5 deletions(-) mode change 100644 => 100755 CHANGELOG.md mode change 100644 => 100755 hutool-core/src/main/java/cn/hutool/core/bean/BeanUtil.java diff --git a/CHANGELOG.md b/CHANGELOG.md old mode 100644 new mode 100755 diff --git a/hutool-core/src/main/java/cn/hutool/core/bean/BeanUtil.java b/hutool-core/src/main/java/cn/hutool/core/bean/BeanUtil.java old mode 100644 new mode 100755 diff --git a/hutool-core/src/main/java/cn/hutool/core/util/IdcardUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/IdcardUtil.java index 7c760583f..9e1278ebc 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/IdcardUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/IdcardUtil.java @@ -15,11 +15,11 @@ import java.util.Objects; /** * 身份证相关工具类
- * see https://www.oschina.net/code/snippet_1611_2881 + * see https://www.oschina.net/code/snippet_1611_2881 * *

* 本工具并没有对行政区划代码做校验,如有需求,请参阅(2018年10月): - * http://www.mca.gov.cn/article/sj/xzqh/2018/201804-12/20181011221630.html + * http://www.mca.gov.cn/article/sj/xzqh/2018/201804-12/20181011221630.html *

* * @author Looly 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 e79c9b784..ed4416209 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 @@ -1,10 +1,15 @@ package cn.hutool.core.text.csv; import cn.hutool.core.io.FileUtil; +import cn.hutool.core.lang.Console; import cn.hutool.core.util.CharsetUtil; import org.junit.Ignore; import org.junit.Test; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + public class CsvWriterTest { @Test @@ -23,4 +28,20 @@ public class CsvWriterTest { writer.writeLine("李四", "男", "XX市XX区,01号"); writer.close(); } + + @Test + @Ignore + public void issue2255Test(){ + String fileName = "D:/test/" + new Random().nextInt(100) + "-a.csv"; + CsvWriter writer = CsvUtil.getWriter(fileName, CharsetUtil.CHARSET_UTF_8); + List list = new ArrayList<>(); + for (int i = 0; i < 10000; i++) { + list.add(i+""); + } + Console.log("{} : {}", fileName, list.size()); + for (String s : list) { + writer.writeLine(s); + } + writer.close(); + } } diff --git a/hutool-extra/src/main/java/cn/hutool/extra/mail/MailAccount.java b/hutool-extra/src/main/java/cn/hutool/extra/mail/MailAccount.java index dfa13507d..f2d3aef9a 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/mail/MailAccount.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/mail/MailAccount.java @@ -1,6 +1,5 @@ package cn.hutool.extra.mail; -import cn.hutool.core.lang.Console; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; @@ -579,7 +578,6 @@ public class MailAccount implements Serializable { // SSL if (null != this.sslEnable && this.sslEnable) { - Console.log("{} {}", SOCKET_FACTORY, socketFactoryClass); p.put(SSL_ENABLE, "true"); p.put(SOCKET_FACTORY, socketFactoryClass); p.put(SOCKET_FACTORY_FALLBACK, String.valueOf(this.socketFactoryFallback)); 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 7eca8d786..7938ed3c6 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java @@ -972,7 +972,7 @@ public class JSONUtil { /** * 转义不可见字符
- * 见:https://en.wikibooks.org/wiki/Unicode/Character_reference/0000-0FFF + * 见:https://en.wikibooks.org/wiki/Unicode/Character_reference/0000-0FFF * * @param c 字符 * @return 转义后的字符串 From f5a44bca732d2430211591b99314a0e52f7b55e6 Mon Sep 17 00:00:00 2001 From: Looly Date: Sat, 16 Apr 2022 08:47:11 +0800 Subject: [PATCH 05/52] fix code --- .../main/java/cn/hutool/core/date/Week.java | 2 +- .../cn/hutool/cron/pattern/CronPattern.java | 15 +++++++++++- .../cn/hutool/cron/pattern/PatternUtil.java | 23 +++++++++++++++++++ .../cron/pattern/matcher/PatternMatcher.java | 15 ++++++++++-- 4 files changed, 51 insertions(+), 4 deletions(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/date/Week.java b/hutool-core/src/main/java/cn/hutool/core/date/Week.java index c6cf0ee76..396a80df2 100644 --- a/hutool-core/src/main/java/cn/hutool/core/date/Week.java +++ b/hutool-core/src/main/java/cn/hutool/core/date/Week.java @@ -74,7 +74,7 @@ public enum Week { /** * 获得星期对应{@link Calendar} 中的Week值 * - * @return 星期对应{@link Calendar} 中的Week值 + * @return 星期对应 {@link Calendar} 中的Week值 */ public int getValue() { return this.value; diff --git a/hutool-cron/src/main/java/cn/hutool/cron/pattern/CronPattern.java b/hutool-cron/src/main/java/cn/hutool/cron/pattern/CronPattern.java index 5c0e7cffd..e5c466597 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/pattern/CronPattern.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/pattern/CronPattern.java @@ -5,6 +5,7 @@ import cn.hutool.core.date.CalendarUtil; import cn.hutool.cron.pattern.matcher.PatternMatcher; import cn.hutool.cron.pattern.parser.PatternParser; +import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Calendar; import java.util.GregorianCalendar; @@ -129,6 +130,18 @@ public class CronPattern { return match(PatternUtil.getFields(calendar, isMatchSecond)); } + /** + * 给定时间是否匹配定时任务表达式 + * + * @param dateTime 时间 + * @param isMatchSecond 是否匹配秒 + * @return 如果匹配返回 {@code true}, 否则返回 {@code false} + * @since 5.8.0 + */ + public boolean match(LocalDateTime dateTime, boolean isMatchSecond) { + return match(PatternUtil.getFields(dateTime, isMatchSecond)); + } + /** * 返回匹配到的下一个时间 * @@ -137,7 +150,7 @@ public class CronPattern { */ public Calendar nextMatchAfter(Calendar calendar) { Calendar next = nextMatchAfter(PatternUtil.getFields(calendar, true), calendar.getTimeZone()); - if(false == match(next, true)){ + if (false == match(next, true)) { next.set(Calendar.DAY_OF_MONTH, next.get(Calendar.DAY_OF_MONTH) + 1); next = CalendarUtil.beginOfDay(next); return nextMatchAfter(next); diff --git a/hutool-cron/src/main/java/cn/hutool/cron/pattern/PatternUtil.java b/hutool-cron/src/main/java/cn/hutool/cron/pattern/PatternUtil.java index 09f82d73a..63030b9de 100755 --- a/hutool-cron/src/main/java/cn/hutool/cron/pattern/PatternUtil.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/pattern/PatternUtil.java @@ -1,5 +1,8 @@ package cn.hutool.cron.pattern; +import cn.hutool.core.date.Week; + +import java.time.LocalDateTime; import java.util.Calendar; /** @@ -10,6 +13,26 @@ import java.util.Calendar; */ class PatternUtil { + /** + * 获取处理后的字段列表
+ * 月份从1开始,周从0开始 + * + * @param dateTime {@link Calendar} + * @param isMatchSecond 是否匹配秒,{@link false}则秒返回-1 + * @return 字段值列表 + * @since 5.8.0 + */ + static int[] getFields(LocalDateTime dateTime, boolean isMatchSecond) { + final int second = isMatchSecond ? dateTime.getSecond() : -1; + final int minute = dateTime.getMinute(); + final int hour = dateTime.getHour(); + final int dayOfMonth = dateTime.getDayOfMonth(); + final int month = dateTime.getMonthValue();// 月份从1开始 + final int dayOfWeek = Week.of(dateTime.getDayOfWeek()).getValue() - 1; // 星期从0开始,0和7都表示周日 + final int year = dateTime.getYear(); + return new int[]{second, minute, hour, dayOfMonth, month, dayOfWeek, year}; + } + /** * 获取处理后的字段列表
* 月份从1开始,周从0开始 diff --git a/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/PatternMatcher.java b/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/PatternMatcher.java index 908154e9b..bfc5712a1 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/PatternMatcher.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/PatternMatcher.java @@ -72,6 +72,17 @@ public class PatternMatcher { return match(fields[0], fields[1], fields[2], fields[3], fields[4], fields[5], fields[6]); } + /** + * 给定周的值是否匹配定时任务表达式对应部分 + * + * @param dayOfWeekValue dayOfMonth值,星期从0开始,0和7都表示周日 + * @return 如果匹配返回 {@code true}, 否则返回 {@code false} + * @since 5.8.0 + */ + public boolean matchWeek(int dayOfWeekValue) { + return matchers[5].match(dayOfWeekValue); + } + /** * 给定时间是否匹配定时任务表达式 * @@ -88,7 +99,7 @@ public class PatternMatcher { return ((second < 0) || matchers[0].match(second)) // 匹配秒(非秒匹配模式下始终返回true) && matchers[1].match(minute)// 匹配分 && matchers[2].match(hour)// 匹配时 - && isMatchDayOfMonth(matchers[3], dayOfMonth, month, Year.isLeap(year))// 匹配日 + && matchDayOfMonth(matchers[3], dayOfMonth, month, Year.isLeap(year))// 匹配日 && matchers[4].match(month) // 匹配月 && matchers[5].match(dayOfWeek)// 匹配周 && matchers[6].match(year);// 匹配年 @@ -103,7 +114,7 @@ public class PatternMatcher { * @param isLeapYear 是否闰年 * @return 是否匹配 */ - private static boolean isMatchDayOfMonth(PartMatcher matcher, int dayOfMonth, int month, boolean isLeapYear) { + private static boolean matchDayOfMonth(PartMatcher matcher, int dayOfMonth, int month, boolean isLeapYear) { return ((matcher instanceof DayOfMonthMatcher) // ? ((DayOfMonthMatcher) matcher).match(dayOfMonth, month, isLeapYear) // : matcher.match(dayOfMonth)); From aa48d3f2ffabdeacf61f0bcf52b81ca31dc04aee Mon Sep 17 00:00:00 2001 From: Looly Date: Sat, 16 Apr 2022 09:27:47 +0800 Subject: [PATCH 06/52] add parser --- CHANGELOG.md | 3 +- .../main/java/cn/hutool/json/JSONArray.java | 41 ++---- .../main/java/cn/hutool/json/JSONObject.java | 43 +------ .../main/java/cn/hutool/json/JSONParser.java | 119 ++++++++++++++++++ .../main/java/cn/hutool/json/JSONTokener.java | 2 +- 5 files changed, 136 insertions(+), 72 deletions(-) create mode 100755 hutool-json/src/main/java/cn/hutool/json/JSONParser.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 04520ca7d..7dd9e5c31 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,12 +3,13 @@ ------------------------------------------------------------------------------------------------------------- -# 5.8.0.M4 (2022-04-15) +# 5.8.0.M4 (2022-04-16) ### ❌不兼容特性 ### 🐣新特性 * 【core 】 BeanUtil增加toBean重载(pr#598@Gitee) +* 【json 】 新增JSONParser ### 🐞Bug修复 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 c5412c57d..55b367082 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONArray.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONArray.java @@ -385,7 +385,7 @@ public class JSONArray implements JSON, JSONGetter, List, Rando @Override public boolean add(Object e) { - return this.rawList.add(JSONUtil.wrap(e, this.config)); + return addRaw(JSONUtil.wrap(e, this.config)); } @Override @@ -592,6 +592,17 @@ public class JSONArray implements JSON, JSONGetter, List, Rando } // ------------------------------------------------------------------------------------------------- Private method start + /** + * 原始添加,添加的对象不做任何处理 + * + * @param obj 添加的对象 + * @return 是否加入成功 + * @since 5.8.0 + */ + protected boolean addRaw(Object obj) { + return this.rawList.add(obj); + } + /** * 初始化 * @@ -653,33 +664,7 @@ public class JSONArray implements JSON, JSONGetter, List, Rando * @param x {@link JSONTokener} */ private void init(JSONTokener x) { - if (x.nextClean() != '[') { - throw x.syntaxError("A JSONArray text must start with '['"); - } - if (x.nextClean() != ']') { - x.back(); - for (; ; ) { - if (x.nextClean() == ',') { - x.back(); - this.rawList.add(JSONNull.NULL); - } else { - x.back(); - this.rawList.add(x.nextValue()); - } - switch (x.nextClean()) { - case ',': - if (x.nextClean() == ']') { - return; - } - x.back(); - break; - case ']': - return; - default: - throw x.syntaxError("Expected a ',' or ']'"); - } - } - } + JSONParser.of(x).parseTo(this); } // ------------------------------------------------------------------------------------------------- Private method end } 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 ddb41a42e..d3a3311ae 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONObject.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONObject.java @@ -646,48 +646,7 @@ public class JSONObject extends MapWrapper implements JSON, JSON * @param x JSONTokener */ private void init(JSONTokener x) { - char c; - String key; - - if (x.nextClean() != '{') { - throw x.syntaxError("A JSONObject text must begin with '{'"); - } - while (true) { - c = x.nextClean(); - switch (c) { - case 0: - throw x.syntaxError("A JSONObject text must end with '}'"); - case '}': - return; - default: - x.back(); - key = x.nextValue().toString(); - } - - // The key is followed by ':'. - - c = x.nextClean(); - if (c != ':') { - throw x.syntaxError("Expected a ':' after a key"); - } - this.putOnce(key, x.nextValue()); - - // Pairs are separated by ','. - - switch (x.nextClean()) { - case ';': - case ',': - if (x.nextClean() == '}') { - return; - } - x.back(); - break; - case '}': - return; - default: - throw x.syntaxError("Expected a ',' or '}'"); - } - } + JSONParser.of(x).parseTo(this); } /** diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONParser.java b/hutool-json/src/main/java/cn/hutool/json/JSONParser.java new file mode 100755 index 000000000..16db58bbb --- /dev/null +++ b/hutool-json/src/main/java/cn/hutool/json/JSONParser.java @@ -0,0 +1,119 @@ +package cn.hutool.json; + +/** + * JSON字符串解析器 + * + * @author looly + * @since 5.8.0 + */ +public class JSONParser { + private final JSONTokener tokener; + + /** + * 创建JSONParser + * + * @param tokener {@link JSONTokener} + * @return JSONParser + */ + public static JSONParser of(JSONTokener tokener) { + return new JSONParser(tokener); + } + + /** + * 构造 + * + * @param tokener {@link JSONTokener} + */ + public JSONParser(JSONTokener tokener) { + this.tokener = tokener; + } + + /** + * 解析{@link JSONTokener}中的字符到目标的{@link JSONObject}中 + * + * @param jsonObject {@link JSONObject} + */ + public void parseTo(JSONObject jsonObject) { + final JSONTokener tokener = this.tokener; + + char c; + String key; + + if (tokener.nextClean() != '{') { + throw tokener.syntaxError("A JSONObject text must begin with '{'"); + } + while (true) { + c = tokener.nextClean(); + switch (c) { + case 0: + throw tokener.syntaxError("A JSONObject text must end with '}'"); + case '}': + return; + default: + tokener.back(); + key = tokener.nextValue().toString(); + } + + // The key is followed by ':'. + + c = tokener.nextClean(); + if (c != ':') { + throw tokener.syntaxError("Expected a ':' after a key"); + } + jsonObject.putOnce(key, tokener.nextValue()); + + // Pairs are separated by ','. + + switch (tokener.nextClean()) { + case ';': + case ',': + if (tokener.nextClean() == '}') { + return; + } + tokener.back(); + break; + case '}': + return; + default: + throw tokener.syntaxError("Expected a ',' or '}'"); + } + } + } + + /** + * 解析JSON字符串到{@link JSONArray}中 + * + * @param jsonArray {@link JSONArray} + */ + public void parseTo(JSONArray jsonArray) { + final JSONTokener x = this.tokener; + + if (x.nextClean() != '[') { + throw x.syntaxError("A JSONArray text must start with '['"); + } + if (x.nextClean() != ']') { + x.back(); + for (; ; ) { + if (x.nextClean() == ',') { + x.back(); + jsonArray.addRaw(JSONNull.NULL); + } else { + x.back(); + jsonArray.addRaw(x.nextValue()); + } + switch (x.nextClean()) { + case ',': + if (x.nextClean() == ']') { + return; + } + x.back(); + break; + case ']': + return; + default: + throw x.syntaxError("Expected a ',' or ']'"); + } + } + } + } +} diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONTokener.java b/hutool-json/src/main/java/cn/hutool/json/JSONTokener.java index fd5acd111..fbc1e58eb 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONTokener.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONTokener.java @@ -217,7 +217,7 @@ public class JSONTokener { * 返回当前位置到指定引号前的所有字符,反斜杠的转义符也会被处理。
* 标准的JSON是不允许使用单引号包含字符串的,但是此实现允许。 * - * @param quote 字符引号, 包括 "(双引号) 或 '(单引号)。 + * @param quote 字符引号, 包括 {@code "}(双引号) 或 {@code '}(单引号)。 * @return 截止到引号前的字符串 * @throws JSONException 出现无结束的字符串时抛出此异常 */ From 6acf0854d851291f8f94aad38a6501e33fc63242 Mon Sep 17 00:00:00 2001 From: Looly Date: Sat, 16 Apr 2022 11:44:01 +0800 Subject: [PATCH 07/52] add filter --- CHANGELOG.md | 2 + .../java/cn/hutool/json/InternalJSONUtil.java | 49 +++++- .../main/java/cn/hutool/json/JSONArray.java | 136 +++++++++------- .../main/java/cn/hutool/json/JSONObject.java | 151 +++++++++++------- .../main/java/cn/hutool/json/JSONParser.java | 21 ++- .../java/cn/hutool/json/JSONArrayTest.java | 28 +++- .../java/cn/hutool/json/JSONObjectTest.java | 22 +++ 7 files changed, 274 insertions(+), 135 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7dd9e5c31..7768b1bd0 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,10 +6,12 @@ # 5.8.0.M4 (2022-04-16) ### ❌不兼容特性 +* 【json 】 【可能兼容问题】JSONArray删除部分构造 ### 🐣新特性 * 【core 】 BeanUtil增加toBean重载(pr#598@Gitee) * 【json 】 新增JSONParser +* 【json 】 JSON新增在解析时的过滤方法(issue#I52O85@Gitee) ### 🐞Bug修复 diff --git a/hutool-json/src/main/java/cn/hutool/json/InternalJSONUtil.java b/hutool-json/src/main/java/cn/hutool/json/InternalJSONUtil.java index c71277e3a..0229c4e38 100644 --- a/hutool-json/src/main/java/cn/hutool/json/InternalJSONUtil.java +++ b/hutool-json/src/main/java/cn/hutool/json/InternalJSONUtil.java @@ -2,6 +2,10 @@ package cn.hutool.json; import cn.hutool.core.bean.copier.CopyOptions; import cn.hutool.core.convert.Convert; +import cn.hutool.core.lang.Filter; +import cn.hutool.core.lang.mutable.MutablePair; +import cn.hutool.core.map.CaseInsensitiveLinkedMap; +import cn.hutool.core.map.CaseInsensitiveTreeMap; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.CharUtil; import cn.hutool.core.util.NumberUtil; @@ -10,7 +14,10 @@ import cn.hutool.core.util.StrUtil; import java.math.BigDecimal; import java.util.Collection; +import java.util.Comparator; +import java.util.LinkedHashMap; import java.util.Map; +import java.util.TreeMap; /** * 内部JSON工具类,仅用于JSON内部使用 @@ -26,12 +33,14 @@ public final class InternalJSONUtil { * 如果对象是Number 且是 NaN or infinite,将抛出异常 * * @param obj 被检查的对象 + * @return 检测后的值 * @throws JSONException If o is a non-finite number. */ - static void testValidity(Object obj) throws JSONException { + static Object testValidity(Object obj) throws JSONException { if (false == ObjectUtil.isValidIfNumber(obj)) { throw new JSONException("JSON does not allow non-finite numbers."); } + return obj; } /** @@ -123,14 +132,15 @@ public final class InternalJSONUtil { /** * 将Property的键转化为JSON形式
- * 用于识别类似于:com.luxiaolei.package.hutool这类用点隔开的键 + * 用于识别类似于:com.luxiaolei.package.hutool这类用点隔开的键
+ * 注意:不允许重复键 * * @param jsonObject JSONObject * @param key 键 * @param value 值 * @return JSONObject */ - static JSONObject propertyPut(JSONObject jsonObject, Object key, Object value) { + static JSONObject propertyPut(JSONObject jsonObject, Object key, Object value, Filter> filter) { final String[] path = StrUtil.splitToArray(Convert.toStr(key), CharUtil.DOT); int last = path.length - 1; JSONObject target = jsonObject; @@ -139,11 +149,11 @@ public final class InternalJSONUtil { JSONObject nextTarget = target.getJSONObject(segment); if (nextTarget == null) { nextTarget = new JSONObject(target.getConfig()); - target.set(segment, nextTarget); + target.setOnce(segment, nextTarget, filter); } target = nextTarget; } - target.set(path[last], value); + target.setOnce(path[last], value, filter); return jsonObject; } @@ -180,4 +190,33 @@ public final class InternalJSONUtil { .setIgnoreNullValue(config.isIgnoreNullValue()) .setTransientSupport(config.isTransientSupport()); } + + /** + * 根据配置创建对应的原始Map + * + * @param capacity 初始大小 + * @param config JSON配置项,{@code null}则使用默认配置 + * @return Map + */ + static Map createRawMap(int capacity, JSONConfig config) { + Map rawHashMap; + if (null == config) { + config = JSONConfig.create(); + } + final Comparator keyComparator = config.getKeyComparator(); + if (config.isIgnoreCase()) { + if (null != keyComparator) { + rawHashMap = new CaseInsensitiveTreeMap<>(keyComparator); + } else { + rawHashMap = new CaseInsensitiveLinkedMap<>(capacity); + } + } else { + if (null != keyComparator) { + rawHashMap = new TreeMap<>(keyComparator); + } else { + rawHashMap = new LinkedHashMap<>(capacity); + } + } + return rawHashMap; + } } 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 55b367082..553bd02f6 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONArray.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONArray.java @@ -4,6 +4,8 @@ import cn.hutool.core.bean.BeanPath; import cn.hutool.core.collection.ArrayIter; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Filter; +import cn.hutool.core.lang.mutable.Mutable; +import cn.hutool.core.lang.mutable.MutableObj; import cn.hutool.core.lang.mutable.MutablePair; import cn.hutool.core.text.StrJoiner; import cn.hutool.core.util.ArrayUtil; @@ -52,7 +54,7 @@ public class JSONArray implements JSON, JSONGetter, List, Rando */ private final JSONConfig config; - // -------------------------------------------------------------------------------------------------------------------- Constructor start + // region Constructors /** * 构造
@@ -97,52 +99,6 @@ public class JSONArray implements JSON, JSONGetter, List, Rando this.config = ObjectUtil.defaultIfNull(config, JSONConfig::create); } - /** - * 构造
- * 将参数数组中的元素转换为JSON对应的对象加入到JSONArray中 - * - * @param list 初始化的JSON数组 - */ - public JSONArray(Iterable list) { - this(); - for (Object o : list) { - this.add(o); - } - } - - /** - * 构造
- * 将参数数组中的元素转换为JSON对应的对象加入到JSONArray中 - * - * @param list 初始化的JSON数组 - */ - public JSONArray(Collection list) { - this(list.size()); - this.addAll(list); - } - - /** - * 使用 {@link JSONTokener} 做为参数构造 - * - * @param x A {@link JSONTokener} - * @throws JSONException If there is a syntax error. - */ - public JSONArray(JSONTokener x) throws JSONException { - this(); - init(x); - } - - /** - * 从String构造(JSONArray字符串) - * - * @param source JSON数组字符串 - * @throws JSONException If there is a syntax error. - */ - public JSONArray(CharSequence source) throws JSONException { - this(); - init(source); - } - /** * 从对象构造,忽略{@code null}的值
* 支持以下类型的参数: @@ -194,10 +150,30 @@ public class JSONArray implements JSON, JSONGetter, List, Rando * @since 4.6.5 */ public JSONArray(Object object, JSONConfig jsonConfig) throws JSONException { - this(DEFAULT_CAPACITY, jsonConfig); - init(object); + this(object, jsonConfig, null); } - // -------------------------------------------------------------------------------------------------------------------- Constructor end + + /** + * 从对象构造
+ * 支持以下类型的参数: + * + *
+	 * 1. 数组
+	 * 2. {@link Iterable}对象
+	 * 3. JSON数组字符串
+	 * 
+ * + * @param object 数组或集合或JSON数组字符串 + * @param jsonConfig JSON选项 + * @param filter 键值对过滤编辑器,可以通过实现此接口,完成解析前对值的过滤和修改操作,{@code null}表示不过滤 + * @throws JSONException 非数组或集合 + * @since 5.8.0 + */ + public JSONArray(Object object, JSONConfig jsonConfig, Filter> filter) throws JSONException { + this(DEFAULT_CAPACITY, jsonConfig); + init(object, filter); + } + // endregion @Override public JSONConfig getConfig() { @@ -385,7 +361,7 @@ public class JSONArray implements JSON, JSONGetter, List, Rando @Override public boolean add(Object e) { - return addRaw(JSONUtil.wrap(e, this.config)); + return addRaw(JSONUtil.wrap(e, this.config), null); } @Override @@ -456,6 +432,28 @@ public class JSONArray implements JSON, JSONGetter, List, Rando */ @Override public Object set(int index, Object element) { + return set(index, element, null); + } + + /** + * 加入或者替换JSONArray中指定Index的值,如果index大于JSONArray的长度,将在指定index设置值,之前的位置填充JSONNull.Null + * + * @param index 位置 + * @param element 值对象. 可以是以下类型: Boolean, Double, Integer, JSONArray, JSONObject, Long, String, or the JSONNull.NULL. + * @param filter 过滤器,可以修改值,key(index)无法修改 + * @return 替换的值,即之前的值 + * @since 5.8.0 + */ + public Object set(int index, Object element, Filter> filter) { + // 添加前置过滤,通过MutablePair实现过滤、修改键值对等 + if (null != filter) { + final MutablePair pair = new MutablePair<>(index, element); + if (filter.accept(pair)) { + // 使用修改后的值 + element = pair.getValue(); + } + } + if (index >= size()) { add(index, element); } @@ -595,11 +593,23 @@ public class JSONArray implements JSON, JSONGetter, List, Rando /** * 原始添加,添加的对象不做任何处理 * - * @param obj 添加的对象 + * @param obj 添加的对象 + * @param filter 键值对过滤编辑器,可以通过实现此接口,完成解析前对值的过滤和修改操作,{@code null}表示不过滤 * @return 是否加入成功 * @since 5.8.0 */ - protected boolean addRaw(Object obj) { + protected boolean addRaw(Object obj, Filter> filter) { + // 添加前置过滤,通过MutablePair实现过滤、修改键值对等 + if (null != filter) { + final Mutable mutable = new MutableObj<>(obj); + if (filter.accept(mutable)) { + // 使用修改后的值 + obj = mutable.get(); + }else{ + // 键值对被过滤 + return false; + } + } return this.rawList.add(obj); } @@ -607,10 +617,11 @@ public class JSONArray implements JSON, JSONGetter, List, Rando * 初始化 * * @param source 数组或集合或JSON数组字符串 + * @param filter 键值对过滤编辑器,可以通过实现此接口,完成解析前对值的过滤和修改操作,{@code null}表示不过滤 * @throws JSONException 非数组或集合 */ @SuppressWarnings({"rawtypes", "unchecked"}) - private void init(Object source) throws JSONException { + private void init(Object source, Filter> filter) throws JSONException { if (null == source) { return; } @@ -621,9 +632,9 @@ public class JSONArray implements JSON, JSONGetter, List, Rando serializer.serialize(this, source); } else if (source instanceof CharSequence) { // JSON字符串 - init((CharSequence) source); + initFromStr((CharSequence) source, filter); } else if (source instanceof JSONTokener) { - init((JSONTokener) source); + initFromTokener((JSONTokener) source, filter); } else { Iterator iter; if (ArrayUtil.isArray(source)) {// 数组 @@ -641,7 +652,7 @@ public class JSONArray implements JSON, JSONGetter, List, Rando next = iter.next(); // 检查循环引用 if (next != source) { - this.add(next); + this.addRaw(JSONUtil.wrap(next, this.config), filter); } } } @@ -652,19 +663,20 @@ public class JSONArray implements JSON, JSONGetter, List, Rando * * @param source JSON字符串 */ - private void init(CharSequence source) { + private void initFromStr(CharSequence source, Filter> filter) { if (null != source) { - init(new JSONTokener(StrUtil.trim(source), this.config)); + initFromTokener(new JSONTokener(StrUtil.trim(source), this.config), filter); } } /** * 初始化 * - * @param x {@link JSONTokener} + * @param x {@link JSONTokener} + * @param filter 键值对过滤编辑器,可以通过实现此接口,完成解析前对值的过滤和修改操作,{@code null}表示不过滤 */ - private void init(JSONTokener x) { - JSONParser.of(x).parseTo(this); + private void initFromTokener(JSONTokener x, Filter> filter) { + JSONParser.of(x).parseTo(this, filter); } // ------------------------------------------------------------------------------------------------- Private method end } 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 d3a3311ae..55d0e4693 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONObject.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONObject.java @@ -6,9 +6,7 @@ import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.convert.Convert; 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.map.MapWrapper; import cn.hutool.core.util.ArrayUtil; @@ -26,12 +24,9 @@ import java.lang.reflect.Type; import java.math.BigDecimal; import java.math.BigInteger; import java.util.Collection; -import java.util.Comparator; import java.util.Enumeration; -import java.util.LinkedHashMap; import java.util.Map; import java.util.ResourceBundle; -import java.util.TreeMap; /** * JSON对象
@@ -119,8 +114,8 @@ public class JSONObject extends MapWrapper implements JSON, JSON * @since 4.1.19 */ public JSONObject(int capacity, JSONConfig config) { - super(createRaw(capacity, config)); - this.config = config; + super(InternalJSONUtil.createRawMap(capacity, ObjectUtil.defaultIfNull(config, JSONConfig.create()))); + this.config = ObjectUtil.defaultIfNull(config, JSONConfig.create()); } /** @@ -198,8 +193,30 @@ public class JSONObject extends MapWrapper implements JSON, JSON * @since 4.2.2 */ public JSONObject(Object source, JSONConfig config) { + this(source, config, null); + } + + /** + * 构建JSONObject,规则如下: + *
    + *
  1. value为Map,将键值对加入JSON对象
  2. + *
  3. value为JSON字符串(CharSequence),使用JSONTokener解析
  4. + *
  5. value为JSONTokener,直接解析
  6. + *
  7. value为普通JavaBean,如果为普通的JavaBean,调用其getters方法(getXXX或者isXXX)获得值,加入到JSON对象。例如:如果JavaBean对象中有个方法getName(),值为"张三",获得的键值对为:name: "张三"
  8. + *
+ *

+ * 如果给定值为Map,将键值对加入JSON对象;
+ * 如果为普通的JavaBean,调用其getters方法(getXXX或者isXXX)获得值,加入到JSON对象
+ * 例如:如果JavaBean对象中有个方法getName(),值为"张三",获得的键值对为:name: "张三" + * + * @param source JavaBean或者Map对象或者String + * @param config JSON配置文件,{@code null}则使用默认配置 + * @param filter 键值对过滤编辑器,可以通过实现此接口,完成解析前对键值对的过滤和修改操作,{@code null}表示不过滤 + * @since 5.8.0 + */ + public JSONObject(Object source, JSONConfig config, Filter> filter) { this(DEFAULT_CAPACITY, config); - init(source); + init(source, filter); } /** @@ -219,7 +236,7 @@ public class JSONObject extends MapWrapper implements JSON, JSON public JSONObject(Object obj, String... names) { this(); if (ArrayUtil.isEmpty(names)) { - init(obj); + init(obj, null); return; } @@ -346,17 +363,47 @@ public class JSONObject extends MapWrapper implements JSON, JSON * @throws JSONException 值是无穷数字抛出此异常 */ public JSONObject set(String key, Object value) throws JSONException { + return set(key, value, null, false); + } + + /** + * 设置键值对到JSONObject中,在忽略null模式下,如果值为{@code null},将此键移除 + * + * @param key 键 + * @param value 值对象. 可以是以下类型: Boolean, Double, Integer, JSONArray, JSONObject, Long, String, or the JSONNull.NULL. + * @param filter 键值对过滤编辑器,可以通过实现此接口,完成解析前对键值对的过滤和修改操作,{@code null}表示不过滤 + * @return this. + * @throws JSONException 值是无穷数字抛出此异常 + * @since 5.8.0 + */ + public JSONObject set(String key, Object value, Filter> filter, boolean checkDuplicate) throws JSONException { if (null == key) { return this; } + // 添加前置过滤,通过MutablePair实现过滤、修改键值对等 + if (null != filter) { + final MutablePair pair = new MutablePair<>(key, value); + if (filter.accept(pair)) { + // 使用修改后的键值对 + key = pair.getKey(); + value = pair.getValue(); + }else{ + // 键值对被过滤 + return this; + } + } + final boolean ignoreNullValue = this.config.isIgnoreNullValue(); if (ObjectUtil.isNull(value) && ignoreNullValue) { // 忽略值模式下如果值为空清除key this.remove(key); } else { - InternalJSONUtil.testValidity(value); - super.put(key, JSONUtil.wrap(value, this.config)); + if(checkDuplicate && containsKey(key)){ + throw new JSONException("Duplicate key \"{}\"", key); + } + + super.put(key, JSONUtil.wrap(InternalJSONUtil.testValidity(value), this.config)); } return this; } @@ -370,13 +417,20 @@ public class JSONObject extends MapWrapper implements JSON, JSON * @throws JSONException 值是无穷数字、键重复抛出异常 */ public JSONObject putOnce(String key, Object value) throws JSONException { - if (key != null) { - if (containsKey(key)) { - throw new JSONException("Duplicate key \"{}\"", key); - } - this.set(key, value); - } - return this; + return setOnce(key, value, null); + } + + /** + * 一次性Put 键值对,如果key已经存在抛出异常,如果键值中有null值,忽略 + * + * @param key 键 + * @param value 值对象,可以是以下类型: Boolean, Double, Integer, JSONArray, JSONObject, Long, String, or the JSONNull.NULL. + * @return this. + * @throws JSONException 值是无穷数字、键重复抛出异常 + * @since 5.8.0 + */ + public JSONObject setOnce(String key, Object value, Filter> filter) throws JSONException { + return set(key, value, filter, true); } /** @@ -564,9 +618,10 @@ public class JSONObject extends MapWrapper implements JSON, JSON * * * @param source JavaBean或者Map对象或者String + * @param filter 键值对过滤编辑器,可以通过实现此接口,完成解析前对键值对的过滤和修改操作 */ @SuppressWarnings({"rawtypes", "unchecked"}) - private void init(Object source) { + private void init(Object source, Filter> filter) { if (null == source) { return; } @@ -586,22 +641,22 @@ public class JSONObject extends MapWrapper implements JSON, JSON if (source instanceof Map) { // Map for (final Entry e : ((Map) source).entrySet()) { - this.set(Convert.toStr(e.getKey()), e.getValue()); + this.set(Convert.toStr(e.getKey()), e.getValue(), filter, false); } } else if (source instanceof Map.Entry) { final Map.Entry entry = (Map.Entry) source; - this.set(Convert.toStr(entry.getKey()), entry.getValue()); + this.set(Convert.toStr(entry.getKey()), entry.getValue(), filter, false); } else if (source instanceof CharSequence) { // 可能为JSON字符串 - init((CharSequence) source); + initFromStr((CharSequence) source, filter); } else if (source instanceof JSONTokener) { // JSONTokener - init((JSONTokener) source); + initFromTokener((JSONTokener) source, filter); } else if (source instanceof ResourceBundle) { // JSONTokener - init((ResourceBundle) source); + initFromResourceBundle((ResourceBundle) source, filter); } else if (BeanUtil.isReadableBean(source.getClass())) { - // 普通Bean + // 普通Bean,过滤器无效 this.populateMap(source); } else { // 不支持对象类型转换为JSONObject @@ -613,14 +668,15 @@ public class JSONObject extends MapWrapper implements JSON, JSON * 初始化 * * @param bundle ResourceBundle + * @param filter 键值对过滤编辑器,可以通过实现此接口,完成解析前对键值对的过滤和修改操作,{@code null}表示不过滤 * @since 5.3.1 */ - private void init(ResourceBundle bundle) { + private void initFromResourceBundle(ResourceBundle bundle, Filter> filter) { Enumeration keys = bundle.getKeys(); while (keys.hasMoreElements()) { String key = keys.nextElement(); if (key != null) { - InternalJSONUtil.propertyPut(this, key, bundle.getString(key)); + InternalJSONUtil.propertyPut(this, key, bundle.getString(key), filter); } } } @@ -629,53 +685,26 @@ public class JSONObject extends MapWrapper implements JSON, JSON * 初始化,可以判断字符串为JSON或者XML * * @param source JSON字符串 + * @param filter 键值对过滤编辑器,可以通过实现此接口,完成解析前对键值对的过滤和修改操作,{@code null}表示不过滤 */ - private void init(CharSequence source) { + private void initFromStr(CharSequence source, Filter> filter) { final String jsonStr = StrUtil.trim(source); if (StrUtil.startWith(jsonStr, '<')) { // 可能为XML XML.toJSONObject(this, jsonStr, false); return; } - init(new JSONTokener(StrUtil.trim(source), this.config)); + initFromTokener(new JSONTokener(StrUtil.trim(source), this.config), filter); } /** * 初始化 * - * @param x JSONTokener + * @param x JSONTokener + * @param filter 键值对过滤编辑器,可以通过实现此接口,完成解析前对键值对的过滤和修改操作 */ - private void init(JSONTokener x) { - JSONParser.of(x).parseTo(this); - } - - /** - * 根据配置创建对应的原始Map - * - * @param capacity 初始大小 - * @param config JSON配置项,{@code null}则使用默认配置 - * @return Map - */ - private static Map createRaw(int capacity, JSONConfig config) { - Map rawHashMap; - if (null == config) { - config = JSONConfig.create(); - } - final Comparator keyComparator = config.getKeyComparator(); - if (config.isIgnoreCase()) { - if (null != keyComparator) { - rawHashMap = new CaseInsensitiveTreeMap<>(keyComparator); - } else { - rawHashMap = new CaseInsensitiveLinkedMap<>(capacity); - } - } else { - if (null != keyComparator) { - rawHashMap = new TreeMap<>(keyComparator); - } else { - rawHashMap = new LinkedHashMap<>(capacity); - } - } - return rawHashMap; + private void initFromTokener(JSONTokener x, Filter> filter) { + JSONParser.of(x).parseTo(this, filter); } // ------------------------------------------------------------------------------------------------- Private method end } diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONParser.java b/hutool-json/src/main/java/cn/hutool/json/JSONParser.java index 16db58bbb..8fd6b8388 100755 --- a/hutool-json/src/main/java/cn/hutool/json/JSONParser.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONParser.java @@ -1,5 +1,9 @@ package cn.hutool.json; +import cn.hutool.core.lang.Filter; +import cn.hutool.core.lang.mutable.Mutable; +import cn.hutool.core.lang.mutable.MutablePair; + /** * JSON字符串解析器 * @@ -7,7 +11,6 @@ package cn.hutool.json; * @since 5.8.0 */ public class JSONParser { - private final JSONTokener tokener; /** * 创建JSONParser @@ -19,6 +22,8 @@ public class JSONParser { return new JSONParser(tokener); } + private final JSONTokener tokener; + /** * 构造 * @@ -28,12 +33,14 @@ public class JSONParser { this.tokener = tokener; } + // region parseTo /** * 解析{@link JSONTokener}中的字符到目标的{@link JSONObject}中 * * @param jsonObject {@link JSONObject} + * @param filter 键值对过滤编辑器,可以通过实现此接口,完成解析前对键值对的过滤和修改操作,{@code null}表示不过滤 */ - public void parseTo(JSONObject jsonObject) { + public void parseTo(JSONObject jsonObject, Filter> filter) { final JSONTokener tokener = this.tokener; char c; @@ -60,7 +67,8 @@ public class JSONParser { if (c != ':') { throw tokener.syntaxError("Expected a ':' after a key"); } - jsonObject.putOnce(key, tokener.nextValue()); + + jsonObject.setOnce(key, tokener.nextValue(), filter); // Pairs are separated by ','. @@ -85,7 +93,7 @@ public class JSONParser { * * @param jsonArray {@link JSONArray} */ - public void parseTo(JSONArray jsonArray) { + public void parseTo(JSONArray jsonArray, Filter> filter) { final JSONTokener x = this.tokener; if (x.nextClean() != '[') { @@ -96,10 +104,10 @@ public class JSONParser { for (; ; ) { if (x.nextClean() == ',') { x.back(); - jsonArray.addRaw(JSONNull.NULL); + jsonArray.addRaw(JSONNull.NULL, filter); } else { x.back(); - jsonArray.addRaw(x.nextValue()); + jsonArray.addRaw(x.nextValue(), filter); } switch (x.nextClean()) { case ',': @@ -116,4 +124,5 @@ public class JSONParser { } } } + // endregion } diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONArrayTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONArrayTest.java index 75a2cd9f8..433755265 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONArrayTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONArrayTest.java @@ -218,7 +218,7 @@ public class JSONArrayTest { } @Test - public void putTest(){ + public void putToIndexTest(){ final JSONArray jsonArray = new JSONArray(); jsonArray.put(3, "test"); // 第三个位置插入值,0~2都是null @@ -279,4 +279,30 @@ public class JSONArrayTest { Assert.assertEquals("[null]", array.toString()); } + + @Test + public void parseFilterTest() { + String jsonArr = "[{\"id\":111,\"name\":\"test1\"},{\"id\":112,\"name\":\"test2\"}]"; + //noinspection MismatchedQueryAndUpdateOfCollection + final JSONArray array = new JSONArray(jsonArr, null, (mutable) -> mutable.get().toString().contains("111")); + Assert.assertEquals(1, array.size()); + Assert.assertTrue(array.getJSONObject(0).containsKey("id")); + } + + @Test + public void parseFilterEditTest() { + String jsonArr = "[{\"id\":111,\"name\":\"test1\"},{\"id\":112,\"name\":\"test2\"}]"; + //noinspection MismatchedQueryAndUpdateOfCollection + final JSONArray array = new JSONArray(jsonArr, null, (mutable) -> { + final JSONObject o = new JSONObject(mutable.get()); + if("111".equals(o.getStr("id"))){ + o.set("name", "test1_edit"); + } + mutable.set(o); + return true; + }); + Assert.assertEquals(2, array.size()); + Assert.assertTrue(array.getJSONObject(0).containsKey("id")); + Assert.assertEquals("test1_edit", array.getJSONObject(0).get("name")); + } } 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 bd9440218..c39956d46 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java @@ -682,4 +682,26 @@ public class JSONObjectTest { }); Assert.assertEquals("{\"a\":\"\",\"b\":\"value2\"}", s); } + + @Test + public void parseFilterTest() { + String jsonStr = "{\"b\":\"value2\",\"c\":\"value3\",\"a\":\"value1\", \"d\": true, \"e\": null}"; + //noinspection MismatchedQueryAndUpdateOfCollection + JSONObject jsonObject = new JSONObject(jsonStr, null, (pair)-> "b".equals(pair.getKey())); + Assert.assertEquals(1, jsonObject.size()); + Assert.assertEquals("value2", jsonObject.get("b")); + } + + @Test + public void parseFilterEditTest() { + String jsonStr = "{\"b\":\"value2\",\"c\":\"value3\",\"a\":\"value1\", \"d\": true, \"e\": null}"; + //noinspection MismatchedQueryAndUpdateOfCollection + JSONObject jsonObject = new JSONObject(jsonStr, null, (pair)-> { + if("b".equals(pair.getKey())){ + pair.setValue(pair.getValue() + "_edit"); + } + return true; + }); + Assert.assertEquals("value2_edit", jsonObject.get("b")); + } } From 08da23b3d4de0e79f85c4e876aa900e96c6d51b2 Mon Sep 17 00:00:00 2001 From: Looly Date: Sat, 16 Apr 2022 12:05:50 +0800 Subject: [PATCH 08/52] fix doc --- .../java/cn/hutool/http/GlobalInterceptor.java | 2 ++ .../main/java/cn/hutool/http/HttpConfig.java | 3 +++ .../main/java/cn/hutool/json/JSONObject.java | 18 ++++++++++-------- .../main/java/cn/hutool/json/JSONParser.java | 6 ++++-- 4 files changed, 19 insertions(+), 10 deletions(-) diff --git a/hutool-http/src/main/java/cn/hutool/http/GlobalInterceptor.java b/hutool-http/src/main/java/cn/hutool/http/GlobalInterceptor.java index 98def3fc2..52ccc3ba4 100644 --- a/hutool-http/src/main/java/cn/hutool/http/GlobalInterceptor.java +++ b/hutool-http/src/main/java/cn/hutool/http/GlobalInterceptor.java @@ -17,6 +17,7 @@ public enum GlobalInterceptor { * 设置拦截器,用于在请求前重新编辑请求 * * @param interceptor 拦截器实现 + * @return this */ synchronized public GlobalInterceptor addRequestInterceptor(HttpInterceptor interceptor) { this.requestInterceptors.addChain(interceptor); @@ -27,6 +28,7 @@ public enum GlobalInterceptor { * 设置拦截器,用于在响应读取后完成编辑或读取 * * @param interceptor 拦截器实现 + * @return this */ synchronized public GlobalInterceptor addResponseInterceptor(HttpInterceptor interceptor) { this.responseInterceptors.addChain(interceptor); diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpConfig.java b/hutool-http/src/main/java/cn/hutool/http/HttpConfig.java index 9b289f7ed..8ca0d643b 100755 --- a/hutool-http/src/main/java/cn/hutool/http/HttpConfig.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpConfig.java @@ -244,6 +244,7 @@ public class HttpConfig { * 如果服务端未遵循这个规范或响应没有正常结束,会报EOF异常,此选项用于是否忽略这个异常。 * * @param ignoreEOFError 是否忽略响应读取时可能的EOF异常。 + * @return this * @since 5.7.20 */ public HttpConfig setIgnoreEOFError(boolean ignoreEOFError) { @@ -257,6 +258,7 @@ public class HttpConfig { * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 * * @param decodeUrl 是否忽略解码URL + * @return this */ public HttpConfig setDecodeUrl(boolean decodeUrl) { this.decodeUrl = decodeUrl; @@ -267,6 +269,7 @@ public class HttpConfig { * 设置拦截器,用于在请求前重新编辑请求 * * @param interceptor 拦截器实现 + * @return this */ public HttpConfig addRequestInterceptor(HttpInterceptor interceptor) { this.requestInterceptors.addChain(interceptor); 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 55d0e4693..53b7deb26 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONObject.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONObject.java @@ -369,9 +369,10 @@ public class JSONObject extends MapWrapper implements JSON, JSON /** * 设置键值对到JSONObject中,在忽略null模式下,如果值为{@code null},将此键移除 * - * @param key 键 - * @param value 值对象. 可以是以下类型: Boolean, Double, Integer, JSONArray, JSONObject, Long, String, or the JSONNull.NULL. - * @param filter 键值对过滤编辑器,可以通过实现此接口,完成解析前对键值对的过滤和修改操作,{@code null}表示不过滤 + * @param key 键 + * @param value 值对象. 可以是以下类型: Boolean, Double, Integer, JSONArray, JSONObject, Long, String, or the JSONNull.NULL. + * @param filter 键值对过滤编辑器,可以通过实现此接口,完成解析前对键值对的过滤和修改操作,{@code null}表示不过滤 + * @param checkDuplicate 是否检查重复键,如果为{@code true},则出现重复键时抛出{@link JSONException}异常 * @return this. * @throws JSONException 值是无穷数字抛出此异常 * @since 5.8.0 @@ -388,7 +389,7 @@ public class JSONObject extends MapWrapper implements JSON, JSON // 使用修改后的键值对 key = pair.getKey(); value = pair.getValue(); - }else{ + } else { // 键值对被过滤 return this; } @@ -399,7 +400,7 @@ public class JSONObject extends MapWrapper implements JSON, JSON // 忽略值模式下如果值为空清除key this.remove(key); } else { - if(checkDuplicate && containsKey(key)){ + if (checkDuplicate && containsKey(key)) { throw new JSONException("Duplicate key \"{}\"", key); } @@ -423,9 +424,10 @@ public class JSONObject extends MapWrapper implements JSON, JSON /** * 一次性Put 键值对,如果key已经存在抛出异常,如果键值中有null值,忽略 * - * @param key 键 - * @param value 值对象,可以是以下类型: Boolean, Double, Integer, JSONArray, JSONObject, Long, String, or the JSONNull.NULL. - * @return this. + * @param key 键 + * @param value 值对象,可以是以下类型: Boolean, Double, Integer, JSONArray, JSONObject, Long, String, or the JSONNull.NULL. + * @param filter 键值对过滤编辑器,可以通过实现此接口,完成解析前对键值对的过滤和修改操作,{@code null}表示不过滤 + * @return this * @throws JSONException 值是无穷数字、键重复抛出异常 * @since 5.8.0 */ diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONParser.java b/hutool-json/src/main/java/cn/hutool/json/JSONParser.java index 8fd6b8388..584de206d 100755 --- a/hutool-json/src/main/java/cn/hutool/json/JSONParser.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONParser.java @@ -34,11 +34,12 @@ public class JSONParser { } // region parseTo + /** * 解析{@link JSONTokener}中的字符到目标的{@link JSONObject}中 * * @param jsonObject {@link JSONObject} - * @param filter 键值对过滤编辑器,可以通过实现此接口,完成解析前对键值对的过滤和修改操作,{@code null}表示不过滤 + * @param filter 键值对过滤编辑器,可以通过实现此接口,完成解析前对键值对的过滤和修改操作,{@code null}表示不过滤 */ public void parseTo(JSONObject jsonObject, Filter> filter) { final JSONTokener tokener = this.tokener; @@ -91,7 +92,8 @@ public class JSONParser { /** * 解析JSON字符串到{@link JSONArray}中 * - * @param jsonArray {@link JSONArray} + * @param jsonArray {@link JSONArray + * @param filter 键值对过滤编辑器,可以通过实现此接口,完成解析前对值的过滤和修改操作,{@code null}表示不过滤 */ public void parseTo(JSONArray jsonArray, Filter> filter) { final JSONTokener x = this.tokener; From c6f0ea0e2fcc1f10c832018f9469bf901a21761e Mon Sep 17 00:00:00 2001 From: Looly Date: Sat, 16 Apr 2022 12:11:05 +0800 Subject: [PATCH 09/52] fix comment --- hutool-json/src/main/java/cn/hutool/json/JSONParser.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONParser.java b/hutool-json/src/main/java/cn/hutool/json/JSONParser.java index 584de206d..dddb21bdd 100755 --- a/hutool-json/src/main/java/cn/hutool/json/JSONParser.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONParser.java @@ -93,7 +93,7 @@ public class JSONParser { * 解析JSON字符串到{@link JSONArray}中 * * @param jsonArray {@link JSONArray - * @param filter 键值对过滤编辑器,可以通过实现此接口,完成解析前对值的过滤和修改操作,{@code null}表示不过滤 + * @param filter 键值对过滤编辑器,可以通过实现此接口,完成解析前对值的过滤和修改操作,{@code null} 表示不过滤 */ public void parseTo(JSONArray jsonArray, Filter> filter) { final JSONTokener x = this.tokener; From a8a866f35e5826745e95b4249ca2dad6d69de497 Mon Sep 17 00:00:00 2001 From: Looly Date: Sat, 16 Apr 2022 12:11:45 +0800 Subject: [PATCH 10/52] fix doc --- hutool-json/src/main/java/cn/hutool/json/JSONParser.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONParser.java b/hutool-json/src/main/java/cn/hutool/json/JSONParser.java index dddb21bdd..6eedd09c6 100755 --- a/hutool-json/src/main/java/cn/hutool/json/JSONParser.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONParser.java @@ -92,7 +92,7 @@ public class JSONParser { /** * 解析JSON字符串到{@link JSONArray}中 * - * @param jsonArray {@link JSONArray + * @param jsonArray {@link JSONArray} * @param filter 键值对过滤编辑器,可以通过实现此接口,完成解析前对值的过滤和修改操作,{@code null} 表示不过滤 */ public void parseTo(JSONArray jsonArray, Filter> filter) { From 7e36d0f0760ea2153a2fb02e4b768ba47743f9e6 Mon Sep 17 00:00:00 2001 From: Looly Date: Sun, 17 Apr 2022 08:53:43 +0800 Subject: [PATCH 11/52] add method --- CHANGELOG.md | 4 ++- .../cn/hutool/core/collection/CollUtil.java | 25 ++++++++++++++++ .../hutool/core/collection/UniqueKeySet.java | 25 ++++++++++++++++ .../cn/hutool/core/text/CharSequenceUtil.java | 6 ++-- .../java/cn/hutool/core/util/ArrayUtil.java | 29 ++++++++++++++++++ .../hutool/core/collection/CollUtilTest.java | 30 +++++++++++++++++++ .../cn/hutool/core/util/ArrayUtilTest.java | 13 ++++++++ 7 files changed, 128 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7768b1bd0..ec5c7c1d9 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ------------------------------------------------------------------------------------------------------------- -# 5.8.0.M4 (2022-04-16) +# 5.8.0.M4 (2022-04-17) ### ❌不兼容特性 * 【json 】 【可能兼容问题】JSONArray删除部分构造 @@ -12,8 +12,10 @@ * 【core 】 BeanUtil增加toBean重载(pr#598@Gitee) * 【json 】 新增JSONParser * 【json 】 JSON新增在解析时的过滤方法(issue#I52O85@Gitee) +* 【core 】 添加ArrayUtil.distinct、CollUtil.distinct重载(issue#2256@Github) ### 🐞Bug修复 +* 【core 】 修复StrUtil.firstNonX非static问题(issue#2257@Github) ------------------------------------------------------------------------------------------------------------- diff --git a/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java b/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java index b5a024124..cae324ed4 100644 --- a/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java @@ -1051,6 +1051,31 @@ public class CollUtil { } } + /** + * 根据函数生成的KEY去重集合,如根据Bean的某个或者某些字段完成去重。
+ * 去重可选是保留最先加入的值还是后加入的值 + * + * @param 集合元素类型 + * @param 唯一键类型 + * @param collection 集合 + * @param override 是否覆盖模式,如果为{@code true},加入的新值会覆盖相同key的旧值,否则会忽略新加值 + * @return {@link ArrayList} + * @since 5.8.0 + */ + public static List distinct(Collection collection, Function uniqueGenerator, boolean override) { + if (isEmpty(collection)) { + return new ArrayList<>(); + } + + final UniqueKeySet set = new UniqueKeySet<>(true, uniqueGenerator); + if (override) { + set.addAll(collection); + } else { + set.addAllIfAbsent(collection); + } + return new ArrayList<>(set); + } + /** * 截取列表的部分 * diff --git a/hutool-core/src/main/java/cn/hutool/core/collection/UniqueKeySet.java b/hutool-core/src/main/java/cn/hutool/core/collection/UniqueKeySet.java index f4fbcb15c..8f2aa4f66 100644 --- a/hutool-core/src/main/java/cn/hutool/core/collection/UniqueKeySet.java +++ b/hutool-core/src/main/java/cn/hutool/core/collection/UniqueKeySet.java @@ -42,6 +42,17 @@ public class UniqueKeySet extends AbstractSet implements Serializable { this(false, uniqueGenerator); } + /** + * 构造 + * + * @param uniqueGenerator 唯一键生成规则函数,用于生成对象对应的唯一键 + * @param c 初始化加入的集合 + * @since 5.8.0 + */ + public UniqueKeySet(Function uniqueGenerator, Collection c) { + this(false, uniqueGenerator, c); + } + /** * 构造 * @@ -52,6 +63,19 @@ public class UniqueKeySet extends AbstractSet implements Serializable { this(MapBuilder.create(isLinked), uniqueGenerator); } + /** + * 构造 + * + * @param isLinked 是否保持加入顺序 + * @param uniqueGenerator 唯一键生成规则函数,用于生成对象对应的唯一键 + * @param c 初始化加入的集合 + * @since 5.8.0 + */ + public UniqueKeySet(boolean isLinked, Function uniqueGenerator, Collection c) { + this(isLinked, uniqueGenerator); + addAll(c); + } + /** * 构造 * @@ -73,6 +97,7 @@ public class UniqueKeySet extends AbstractSet implements Serializable { this.map = builder.build(); this.uniqueGenerator = uniqueGenerator; } + //endregion @Override 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 529ed00dc..6c7c45382 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 @@ -3879,7 +3879,7 @@ public class CharSequenceUtil { * @since 5.4.1 */ @SuppressWarnings("unchecked") - public T firstNonNull(T... strs) { + public static T firstNonNull(T... strs) { return ArrayUtil.firstNonNull(strs); } @@ -3893,7 +3893,7 @@ public class CharSequenceUtil { * @since 5.4.1 */ @SuppressWarnings("unchecked") - public T firstNonEmpty(T... strs) { + public static T firstNonEmpty(T... strs) { return ArrayUtil.firstMatch(StrUtil::isNotEmpty, strs); } @@ -3907,7 +3907,7 @@ public class CharSequenceUtil { * @since 5.4.1 */ @SuppressWarnings("unchecked") - public T firstNonBlank(T... strs) { + public static T firstNonBlank(T... strs) { return ArrayUtil.firstMatch(StrUtil::isNotBlank, strs); } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ArrayUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ArrayUtil.java index 4ba720987..9e535b668 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ArrayUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ArrayUtil.java @@ -2,6 +2,7 @@ package cn.hutool.core.util; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.collection.UniqueKeySet; import cn.hutool.core.comparator.CompareUtil; import cn.hutool.core.exceptions.UtilException; import cn.hutool.core.lang.Assert; @@ -1633,6 +1634,34 @@ public class ArrayUtil extends PrimitiveArrayUtil { return toArray(set, (Class) getComponentType(array)); } + /** + * 去重数组中的元素,去重后生成新的数组,原数组不变
+ * 此方法通过{@link LinkedHashSet} 去重 + * + * @param 数组元素类型 + * @param 唯一键类型 + * @param array 数组 + * @param override 是否覆盖模式,如果为{@code true},加入的新值会覆盖相同key的旧值,否则会忽略新加值 + * @return 去重后的数组 + * @since 5.8.0 + */ + @SuppressWarnings("unchecked") + public static T[] distinct(T[] array, Function uniqueGenerator, boolean override) { + if (isEmpty(array)) { + return array; + } + + final UniqueKeySet set = new UniqueKeySet<>(true, uniqueGenerator); + if(override){ + Collections.addAll(set, array); + } else{ + for (T t : array) { + set.addIfAbsent(t); + } + } + return toArray(set, (Class) getComponentType(array)); + } + /** * 按照指定规则,将一种类型的数组转换为另一种类型 * diff --git a/hutool-core/src/test/java/cn/hutool/core/collection/CollUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/collection/CollUtilTest.java index ad0582e18..512b13e2d 100644 --- a/hutool-core/src/test/java/cn/hutool/core/collection/CollUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/collection/CollUtilTest.java @@ -878,6 +878,36 @@ public class CollUtilTest { Assert.assertEquals(people.get(1).getGender(), "小孩"); } + @Test + public void distinctTest(){ + final ArrayList distinct = CollUtil.distinct(ListUtil.of(5, 3, 10, 9, 0, 5, 10, 9)); + Assert.assertEquals(ListUtil.of(5, 3, 10, 9, 0), distinct); + } + + @Test + public void distinctByFunctionTest(){ + List people = Arrays.asList( + new Person("aa", 12, "man", 1), + new Person("bb", 13, "woman", 2), + new Person("cc", 14, "man", 3), + new Person("dd", 15, "woman", 4), + new Person("ee", 16, "woman", 5), + new Person("ff", 17, "man", 6) + ); + + // 覆盖模式下ff覆盖了aa,ee覆盖了bb + List distinct = CollUtil.distinct(people, Person::getGender, true); + Assert.assertEquals(2, distinct.size()); + Assert.assertEquals("ff", distinct.get(0).getName()); + Assert.assertEquals("ee", distinct.get(1).getName()); + + // 非覆盖模式下,保留了最早加入的aa和bb + distinct = CollUtil.distinct(people, Person::getGender, false); + Assert.assertEquals(2, distinct.size()); + Assert.assertEquals("aa", distinct.get(0).getName()); + Assert.assertEquals("bb", distinct.get(1).getName()); + } + @Data @AllArgsConstructor static class Person { diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java index 92f3603a8..de3f0d911 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java @@ -285,6 +285,19 @@ public class ArrayUtilTest { Assert.assertArrayEquals(new String[]{"aa", "bb", "cc", "dd"}, distinct); } + @Test + public void distinctByFunctionTest() { + String[] array = {"aa", "Aa", "BB", "bb"}; + + // 覆盖模式下,保留最后加入的两个元素 + String[] distinct = ArrayUtil.distinct(array, String::toLowerCase, true); + Assert.assertArrayEquals(new String[]{"Aa", "bb"}, distinct); + + // 忽略模式下,保留最早加入的两个元素 + distinct = ArrayUtil.distinct(array, String::toLowerCase, false); + Assert.assertArrayEquals(new String[]{"aa", "BB"}, distinct); + } + @Test public void toStingTest() { int[] a = {1, 3, 56, 6, 7}; From 992477f521893757adeff89dd204f2bab4280d80 Mon Sep 17 00:00:00 2001 From: Looly Date: Sun, 17 Apr 2022 16:19:16 +0800 Subject: [PATCH 12/52] add map --- CHANGELOG.md | 3 + .../cn/hutool/core/bean/BeanDescCache.java | 6 +- .../cn/hutool/core/bean/BeanInfoCache.java | 13 +- .../core/convert/impl/EnumConverter.java | 6 +- .../java/cn/hutool/core/lang/PatternPool.java | 6 +- .../java/cn/hutool/core/lang/SimpleCache.java | 11 +- .../java/cn/hutool/core/lang/Singleton.java | 6 +- .../cn/hutool/core/lang/func/LambdaUtil.java | 6 +- .../hutool/core/lang/intern/WeakInterner.java | 6 +- .../lang/reflect/ActualTypeMapperPool.java | 6 +- .../java/cn/hutool/core/map/CustomKeyMap.java | 77 +---- .../java/cn/hutool/core/map/FuncKeyMap.java | 5 +- .../main/java/cn/hutool/core/map/FuncMap.java | 73 ++++ .../java/cn/hutool/core/map/MapWrapper.java | 31 +- .../core/map/ReferenceConcurrentMap.java | 322 ++++++++++++++++++ .../java/cn/hutool/core/map/TransMap.java | 118 +++++++ .../cn/hutool/core/map/WeakConcurrentMap.java | 35 ++ .../cn/hutool/core/thread/lock/NoLock.java | 2 + .../core/thread/lock/NoReadWriteLock.java | 22 ++ .../cn/hutool/core/util/ClassLoaderUtil.java | 93 ++--- .../java/cn/hutool/core/util/ReflectUtil.java | 14 +- .../cn/hutool/core/lang/SimpleCacheTest.java | 2 +- .../java/cn/hutool/core/map/FuncMapTest.java | 22 ++ .../core/map/WeakConcurrentMapTest.java | 52 +++ .../hutool/extra/cglib/BeanCopierCache.java | 6 +- .../java/cn/hutool/script/ScriptUtil.java | 6 +- 26 files changed, 787 insertions(+), 162 deletions(-) create mode 100755 hutool-core/src/main/java/cn/hutool/core/map/FuncMap.java create mode 100755 hutool-core/src/main/java/cn/hutool/core/map/ReferenceConcurrentMap.java create mode 100755 hutool-core/src/main/java/cn/hutool/core/map/TransMap.java create mode 100755 hutool-core/src/main/java/cn/hutool/core/map/WeakConcurrentMap.java create mode 100755 hutool-core/src/main/java/cn/hutool/core/thread/lock/NoReadWriteLock.java create mode 100755 hutool-core/src/test/java/cn/hutool/core/map/FuncMapTest.java create mode 100755 hutool-core/src/test/java/cn/hutool/core/map/WeakConcurrentMapTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index ec5c7c1d9..3b7f33a87 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,9 +13,12 @@ * 【json 】 新增JSONParser * 【json 】 JSON新增在解析时的过滤方法(issue#I52O85@Gitee) * 【core 】 添加ArrayUtil.distinct、CollUtil.distinct重载(issue#2256@Github) +* 【core 】 添加TransMap、FuncMap、ReferenceConcurrentMap、WeakConcurrentMap ### 🐞Bug修复 * 【core 】 修复StrUtil.firstNonX非static问题(issue#2257@Github) +* 【core 】 修复SimpleCache线程安全问题 +* 【core 】 修复ClassLoaderUtil中可能的关联ClassLoader错位问题 ------------------------------------------------------------------------------------------------------------- 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 b41d030c2..fa29e1d94 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 @@ -1,7 +1,7 @@ package cn.hutool.core.bean; -import cn.hutool.core.lang.SimpleCache; import cn.hutool.core.lang.func.Func0; +import cn.hutool.core.map.WeakConcurrentMap; /** * Bean属性缓存
@@ -12,7 +12,7 @@ import cn.hutool.core.lang.func.Func0; public enum BeanDescCache { INSTANCE; - private final SimpleCache, BeanDesc> bdCache = new SimpleCache<>(); + private final WeakConcurrentMap, BeanDesc> bdCache = new WeakConcurrentMap<>(); /** * 获得属性名和{@link BeanDesc}Map映射 @@ -23,7 +23,7 @@ public enum BeanDescCache { * @since 5.4.2 */ public BeanDesc getBeanDesc(Class beanClass, Func0 supplier) { - return bdCache.get(beanClass, supplier); + return bdCache.computeIfAbsent(beanClass, (key)->supplier.callWithRuntimeException()); } /** 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 7a950ba0c..63fc224d5 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 @@ -1,7 +1,8 @@ package cn.hutool.core.bean; -import cn.hutool.core.lang.SimpleCache; import cn.hutool.core.lang.func.Func0; +import cn.hutool.core.map.ReferenceConcurrentMap; +import cn.hutool.core.map.WeakConcurrentMap; import java.beans.PropertyDescriptor; import java.util.Map; @@ -15,8 +16,8 @@ import java.util.Map; public enum BeanInfoCache { INSTANCE; - private final SimpleCache, Map> pdCache = new SimpleCache<>(); - private final SimpleCache, Map> ignoreCasePdCache = new SimpleCache<>(); + private final WeakConcurrentMap, Map> pdCache = new WeakConcurrentMap<>(); + private final WeakConcurrentMap, Map> ignoreCasePdCache = new WeakConcurrentMap<>(); /** * 获得属性名和{@link PropertyDescriptor}Map映射 @@ -42,7 +43,7 @@ public enum BeanInfoCache { Class beanClass, boolean ignoreCase, Func0> supplier) { - return getCache(ignoreCase).get(beanClass, supplier); + return getCache(ignoreCase).computeIfAbsent(beanClass, (key)->supplier.callWithRuntimeException()); } /** @@ -70,10 +71,10 @@ public enum BeanInfoCache { * 根据是否忽略字段名的大小写,返回不用Cache对象 * * @param ignoreCase 是否忽略大小写 - * @return SimpleCache + * @return {@link ReferenceConcurrentMap} * @since 5.4.1 */ - private SimpleCache, Map> getCache(boolean ignoreCase) { + private ReferenceConcurrentMap, Map> getCache(boolean ignoreCase) { return ignoreCase ? ignoreCasePdCache : pdCache; } } diff --git a/hutool-core/src/main/java/cn/hutool/core/convert/impl/EnumConverter.java b/hutool-core/src/main/java/cn/hutool/core/convert/impl/EnumConverter.java index ba9e2db96..d73a458ae 100644 --- a/hutool-core/src/main/java/cn/hutool/core/convert/impl/EnumConverter.java +++ b/hutool-core/src/main/java/cn/hutool/core/convert/impl/EnumConverter.java @@ -3,8 +3,8 @@ package cn.hutool.core.convert.impl; import cn.hutool.core.convert.AbstractConverter; import cn.hutool.core.convert.ConvertException; import cn.hutool.core.lang.EnumItem; -import cn.hutool.core.lang.SimpleCache; import cn.hutool.core.map.MapUtil; +import cn.hutool.core.map.WeakConcurrentMap; import cn.hutool.core.util.ClassUtil; import cn.hutool.core.util.EnumUtil; import cn.hutool.core.util.ModifierUtil; @@ -25,7 +25,7 @@ import java.util.stream.Collectors; public class EnumConverter extends AbstractConverter { private static final long serialVersionUID = 1L; - private static final SimpleCache, Map, Method>> VALUE_OF_METHOD_CACHE = new SimpleCache<>(); + private static final WeakConcurrentMap, Map, Method>> VALUE_OF_METHOD_CACHE = new WeakConcurrentMap<>(); private final Class enumClass; @@ -132,7 +132,7 @@ public class EnumConverter extends AbstractConverter { * @return 转换方法map,key为方法参数类型,value为方法 */ private static Map, Method> getMethodMap(Class enumClass) { - return VALUE_OF_METHOD_CACHE.get(enumClass, () -> Arrays.stream(enumClass.getMethods()) + return VALUE_OF_METHOD_CACHE.computeIfAbsent(enumClass, (key) -> Arrays.stream(enumClass.getMethods()) .filter(ModifierUtil::isStatic) .filter(m -> m.getReturnType() == enumClass) .filter(m -> m.getParameterCount() == 1) diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/PatternPool.java b/hutool-core/src/main/java/cn/hutool/core/lang/PatternPool.java index b22563f10..93501ea24 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/PatternPool.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/PatternPool.java @@ -1,5 +1,7 @@ package cn.hutool.core.lang; +import cn.hutool.core.map.WeakConcurrentMap; + import java.util.regex.Pattern; /** @@ -174,7 +176,7 @@ public class PatternPool { /** * Pattern池 */ - private static final SimpleCache POOL = new SimpleCache<>(); + private static final WeakConcurrentMap POOL = new WeakConcurrentMap<>(); /** * 先从Pattern池中查找正则对应的{@link Pattern},找不到则编译正则表达式并入池。 @@ -195,7 +197,7 @@ public class PatternPool { */ public static Pattern get(String regex, int flags) { final RegexWithFlag regexWithFlag = new RegexWithFlag(regex, flags); - return POOL.get(regexWithFlag, ()-> Pattern.compile(regex, flags)); + return POOL.computeIfAbsent(regexWithFlag, (key)-> Pattern.compile(regex, flags)); } /** diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/SimpleCache.java b/hutool-core/src/main/java/cn/hutool/core/lang/SimpleCache.java index 027026093..5bef6a15f 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/SimpleCache.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/SimpleCache.java @@ -4,6 +4,7 @@ import cn.hutool.core.collection.TransIter; import cn.hutool.core.lang.func.Func0; import cn.hutool.core.lang.mutable.Mutable; import cn.hutool.core.lang.mutable.MutableObj; +import cn.hutool.core.map.WeakConcurrentMap; import java.io.Serializable; import java.util.Iterator; @@ -11,12 +12,13 @@ import java.util.Map; import java.util.WeakHashMap; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.function.Predicate; /** - * 简单缓存,无超时实现,默认使用{@link WeakHashMap}实现缓存自动清理 + * 简单缓存,无超时实现,默认使用{@link WeakConcurrentMap}实现缓存自动清理 * * @param 键类型 * @param 值类型 @@ -30,7 +32,7 @@ public class SimpleCache implements Iterable>, Serializabl */ private final Map, V> rawMap; // 乐观读写锁 - private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); + private final ReadWriteLock lock = new ReentrantReadWriteLock(); /** * 写的时候每个key一把锁,降低锁的粒度 */ @@ -40,7 +42,7 @@ public class SimpleCache implements Iterable>, Serializabl * 构造,默认使用{@link WeakHashMap}实现缓存自动清理 */ public SimpleCache() { - this(new WeakHashMap<>()); + this(new WeakConcurrentMap<>()); } /** @@ -94,6 +96,9 @@ public class SimpleCache implements Iterable>, Serializabl */ public V get(K key, Predicate validPredicate, Func0 supplier) { V v = get(key); + if((null != validPredicate && false == validPredicate.test(v))){ + v = null; + } if (null == v && null != supplier) { //每个key单独获取一把锁,降低锁的粒度提高并发能力,see pr#1385@Github final Lock keyLock = keyLockMap.computeIfAbsent(key, k -> new ReentrantLock()); diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/Singleton.java b/hutool-core/src/main/java/cn/hutool/core/lang/Singleton.java index f74462ea7..a4f210100 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/Singleton.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/Singleton.java @@ -6,7 +6,7 @@ import cn.hutool.core.util.ClassUtil; import cn.hutool.core.util.ReflectUtil; import cn.hutool.core.util.StrUtil; -import java.util.HashMap; +import java.util.concurrent.ConcurrentHashMap; /** * 单例类
@@ -16,7 +16,7 @@ import java.util.HashMap; */ public final class Singleton { - private static final SimpleCache POOL = new SimpleCache<>(new HashMap<>()); + private static final ConcurrentHashMap POOL = new ConcurrentHashMap<>(); private Singleton() { } @@ -50,7 +50,7 @@ public final class Singleton { */ @SuppressWarnings("unchecked") public static T get(String key, Func0 supplier) { - return (T) POOL.get(key, supplier::call); + return (T) POOL.computeIfAbsent(key, (k)-> supplier.callWithRuntimeException()); } /** diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/func/LambdaUtil.java b/hutool-core/src/main/java/cn/hutool/core/lang/func/LambdaUtil.java index d9b3cbc15..da4bb61f9 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/func/LambdaUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/func/LambdaUtil.java @@ -1,7 +1,7 @@ package cn.hutool.core.lang.func; import cn.hutool.core.bean.BeanUtil; -import cn.hutool.core.lang.SimpleCache; +import cn.hutool.core.map.WeakConcurrentMap; import cn.hutool.core.util.ClassUtil; import cn.hutool.core.util.ReflectUtil; import cn.hutool.core.util.StrUtil; @@ -18,7 +18,7 @@ import java.lang.invoke.SerializedLambda; */ public class LambdaUtil { - private static final SimpleCache cache = new SimpleCache<>(); + private static final WeakConcurrentMap cache = new WeakConcurrentMap<>(); /** * 通过对象的方法或类的静态方法引用,获取lambda实现类 @@ -202,7 +202,7 @@ public class LambdaUtil { * @return 返回解析后的结果 */ private static SerializedLambda _resolve(Serializable func) { - return cache.get(func.getClass().getName(), () -> ReflectUtil.invoke(func, "writeReplace")); + return cache.computeIfAbsent(func.getClass().getName(), (key) -> ReflectUtil.invoke(func, "writeReplace")); } //endregion } diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/intern/WeakInterner.java b/hutool-core/src/main/java/cn/hutool/core/lang/intern/WeakInterner.java index a6b741d83..7f9b8615d 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/intern/WeakInterner.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/intern/WeakInterner.java @@ -1,6 +1,6 @@ package cn.hutool.core.lang.intern; -import cn.hutool.core.lang.SimpleCache; +import cn.hutool.core.map.WeakConcurrentMap; /** * 使用WeakHashMap(线程安全)存储对象的规范化对象,注意此对象需单例使用!
@@ -10,13 +10,13 @@ import cn.hutool.core.lang.SimpleCache; */ public class WeakInterner implements Interner{ - private final SimpleCache cache = new SimpleCache<>(); + private final WeakConcurrentMap cache = new WeakConcurrentMap<>(); @Override public T intern(T sample) { if(null == sample){ return null; } - return cache.get(sample, ()->sample); + return cache.computeIfAbsent(sample, (key)->sample); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/reflect/ActualTypeMapperPool.java b/hutool-core/src/main/java/cn/hutool/core/lang/reflect/ActualTypeMapperPool.java index 5bb5f6d5f..ea213ec1c 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/reflect/ActualTypeMapperPool.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/reflect/ActualTypeMapperPool.java @@ -1,7 +1,7 @@ package cn.hutool.core.lang.reflect; import cn.hutool.core.convert.Convert; -import cn.hutool.core.lang.SimpleCache; +import cn.hutool.core.map.WeakConcurrentMap; import cn.hutool.core.util.TypeUtil; import java.lang.reflect.ParameterizedType; @@ -18,7 +18,7 @@ import java.util.Map; */ public class ActualTypeMapperPool { - private static final SimpleCache> CACHE = new SimpleCache<>(); + private static final WeakConcurrentMap> CACHE = new WeakConcurrentMap<>(); /** * 获取泛型变量和泛型实际类型的对应关系Map @@ -27,7 +27,7 @@ public class ActualTypeMapperPool { * @return 泛型对应关系Map */ public static Map get(Type type) { - return CACHE.get(type, () -> createTypeMap(type)); + return CACHE.computeIfAbsent(type, (key) -> createTypeMap(type)); } /** diff --git a/hutool-core/src/main/java/cn/hutool/core/map/CustomKeyMap.java b/hutool-core/src/main/java/cn/hutool/core/map/CustomKeyMap.java index 04ed9d371..701f0d13e 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/CustomKeyMap.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/CustomKeyMap.java @@ -1,7 +1,6 @@ package cn.hutool.core.map; import java.util.Map; -import java.util.function.BiFunction; /** * 自定义键的Map,默认HashMap实现 @@ -11,7 +10,7 @@ import java.util.function.BiFunction; * @author Looly * @since 4.0.7 */ -public abstract class CustomKeyMap extends MapWrapper { +public abstract class CustomKeyMap extends TransMap { private static final long serialVersionUID = 4043263744224569870L; /** @@ -26,78 +25,8 @@ public abstract class CustomKeyMap extends MapWrapper { } @Override - public V get(Object key) { - return super.get(customKey(key)); - } - - @SuppressWarnings("unchecked") - @Override - public V put(K key, V value) { - return super.put((K) customKey(key), value); - } - - @Override - public void putAll(Map m) { - m.forEach(this::put); - } - - @Override - public boolean containsKey(Object key) { - return super.containsKey(customKey(key)); - } - - @Override - public V remove(Object key) { - return super.remove(customKey(key)); - } - - @Override - public boolean remove(Object key, Object value) { - return super.remove(customKey(key), value); - } - - @Override - public boolean replace(K key, V oldValue, V newValue) { + protected V customValue(Object value) { //noinspection unchecked - return super.replace((K) customKey(key), oldValue, newValue); + return (V)value; } - - @Override - public V replace(K key, V value) { - //noinspection unchecked - return super.replace((K) customKey(key), value); - } - - //---------------------------------------------------------------------------- Override default methods start - @Override - public V getOrDefault(Object key, V defaultValue) { - return super.getOrDefault(customKey(key), defaultValue); - } - - @Override - public V computeIfPresent(K key, BiFunction remappingFunction) { - //noinspection unchecked - return super.computeIfPresent((K) customKey(key), remappingFunction); - } - - @Override - public V compute(K key, BiFunction remappingFunction) { - //noinspection unchecked - return super.compute((K) customKey(key), remappingFunction); - } - - @Override - public V merge(K key, V value, BiFunction remappingFunction) { - //noinspection unchecked - return super.merge((K) customKey(key), value, remappingFunction); - } - //---------------------------------------------------------------------------- Override default methods end - - /** - * 自定义键 - * - * @param key KEY - * @return 自定义KEY - */ - protected abstract Object customKey(Object key); } diff --git a/hutool-core/src/main/java/cn/hutool/core/map/FuncKeyMap.java b/hutool-core/src/main/java/cn/hutool/core/map/FuncKeyMap.java index 0cc468d61..91a6f231a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/FuncKeyMap.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/FuncKeyMap.java @@ -38,10 +38,11 @@ public class FuncKeyMap extends CustomKeyMap { * @return 驼峰Key */ @Override - protected Object customKey(Object key) { + protected K customKey(Object key) { if (null != this.keyFunc) { return keyFunc.apply(key); } - return key; + //noinspection unchecked + return (K)key; } } diff --git a/hutool-core/src/main/java/cn/hutool/core/map/FuncMap.java b/hutool-core/src/main/java/cn/hutool/core/map/FuncMap.java new file mode 100755 index 000000000..7007a2822 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/map/FuncMap.java @@ -0,0 +1,73 @@ +package cn.hutool.core.map; + +import java.util.Map; +import java.util.function.Function; +import java.util.function.Supplier; + +/** + * 自定义键值函数风格的Map + * + * @param 键类型 + * @param 值类型 + * @author Looly + * @since 5.8.0 + */ +public class FuncMap extends TransMap { + private static final long serialVersionUID = 1L; + + private final Function keyFunc; + private final Function valueFunc; + + // ------------------------------------------------------------------------- Constructor start + + /** + * 构造
+ * 注意提供的Map中不能有键值对,否则可能导致自定义key失效 + * + * @param mapFactory Map,提供的空map + * @param keyFunc 自定义KEY的函数 + * @param valueFunc 自定义value函数 + */ + public FuncMap(Supplier> mapFactory, Function keyFunc, Function valueFunc) { + this(mapFactory.get(), keyFunc, valueFunc); + } + + /** + * 构造
+ * 注意提供的Map中不能有键值对,否则可能导致自定义key失效 + * + * @param emptyMap Map,提供的空map + * @param keyFunc 自定义KEY的函数 + * @param valueFunc 自定义value函数 + */ + public FuncMap(Map emptyMap, Function keyFunc, Function valueFunc) { + super(emptyMap); + this.keyFunc = keyFunc; + this.valueFunc = valueFunc; + } + // ------------------------------------------------------------------------- Constructor end + + /** + * 根据函数自定义键 + * + * @param key KEY + * @return 驼峰Key + */ + @Override + protected K customKey(Object key) { + if (null != this.keyFunc) { + return keyFunc.apply(key); + } + //noinspection unchecked + return (K) key; + } + + @Override + protected V customValue(Object value) { + if (null != this.valueFunc) { + return valueFunc.apply(value); + } + //noinspection unchecked + return (V) value; + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/map/MapWrapper.java b/hutool-core/src/main/java/cn/hutool/core/map/MapWrapper.java index 3c1ac5936..c54a581f2 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/MapWrapper.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/MapWrapper.java @@ -2,6 +2,9 @@ package cn.hutool.core.map; import cn.hutool.core.util.ObjectUtil; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; import java.io.Serializable; import java.util.Collection; import java.util.Iterator; @@ -11,6 +14,7 @@ import java.util.Set; import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.Function; +import java.util.function.Supplier; /** * Map包装类,通过包装一个已有Map实现特定功能。例如自定义Key的规则或Value规则 @@ -34,6 +38,17 @@ public class MapWrapper implements Map, Iterable>, S private Map raw; + /** + * 构造
+ * 通过传入一个Map从而确定Map的类型,子类需创建一个空的Map,而非传入一个已有Map,否则值可能会被修改 + * + * @param mapFactory 空Map创建工厂 + * @since 5.8.0 + */ + public MapWrapper(Supplier> mapFactory) { + this(mapFactory.get()); + } + /** * 构造 * @@ -199,11 +214,23 @@ public class MapWrapper implements Map, Iterable>, S @Override public MapWrapper clone() throws CloneNotSupportedException { - @SuppressWarnings("unchecked") - final MapWrapper clone = (MapWrapper) super.clone(); + @SuppressWarnings("unchecked") final MapWrapper clone = (MapWrapper) super.clone(); clone.raw = ObjectUtil.clone(raw); return clone; } //---------------------------------------------------------------------------- Override default methods end + + // region 序列化与反序列化重写 + private void writeObject(final ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + out.writeObject(this.raw); + } + + @SuppressWarnings("unchecked") + private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + raw = (Map) in.readObject(); + } + // endregion } diff --git a/hutool-core/src/main/java/cn/hutool/core/map/ReferenceConcurrentMap.java b/hutool-core/src/main/java/cn/hutool/core/map/ReferenceConcurrentMap.java new file mode 100755 index 000000000..a976ca30f --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/map/ReferenceConcurrentMap.java @@ -0,0 +1,322 @@ +package cn.hutool.core.map; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.func.Func0; +import cn.hutool.core.lang.func.VoidFunc1; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.ReferenceUtil; + +import java.io.Serializable; +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.SoftReference; +import java.lang.ref.WeakReference; +import java.util.AbstractMap; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentMap; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * 线程安全的ReferenceMap实现
+ * 参考:jdk.management.resource.internal.WeakKeyConcurrentHashMap + * + * @param 键类型 + * @param 值类型 + * @author looly + * @since 5.8.0 + */ +public class ReferenceConcurrentMap implements ConcurrentMap, Iterable>, Serializable { + + final ConcurrentMap, V> raw; + private final ReferenceQueue lastQueue; + private final ReferenceUtil.ReferenceType keyType; + /** + * 回收监听 + */ + private VoidFunc1> purgeListener; + + // region 构造 + + /** + * 构造 + * + * @param raw {@link ConcurrentMap}实现 + * @param referenceType Reference类型 + */ + public ReferenceConcurrentMap(ConcurrentMap, V> raw, ReferenceUtil.ReferenceType referenceType) { + this.raw = raw; + this.keyType = referenceType; + lastQueue = new ReferenceQueue<>(); + } + // endregion + + /** + * 设置对象回收清除监听 + * + * @param purgeListener 监听函数 + */ + public void setPurgeListener(VoidFunc1> purgeListener) { + this.purgeListener = purgeListener; + } + + @Override + public int size() { + this.purgeStaleKeys(); + return this.raw.size(); + } + + @Override + public boolean isEmpty() { + return 0 == size(); + } + + @Override + public V get(Object key) { + this.purgeStaleKeys(); + //noinspection unchecked + return this.raw.get(ofKey((K) key, null)); + } + + @Override + public boolean containsKey(Object key) { + this.purgeStaleKeys(); + //noinspection unchecked + return this.raw.containsKey(ofKey((K) key, null)); + } + + @Override + public boolean containsValue(Object value) { + this.purgeStaleKeys(); + return this.raw.containsValue(value); + } + + @Override + public V put(K key, V value) { + this.purgeStaleKeys(); + return this.raw.put(ofKey(key, this.lastQueue), value); + } + + @Override + public V putIfAbsent(K key, V value) { + this.purgeStaleKeys(); + return this.raw.putIfAbsent(ofKey(key, this.lastQueue), value); + } + + @Override + public void putAll(Map m) { + m.forEach(this::put); + } + + @Override + public V replace(K key, V value) { + this.purgeStaleKeys(); + return this.raw.replace(ofKey(key, this.lastQueue), value); + } + + @Override + public boolean replace(K key, V oldValue, V newValue) { + this.purgeStaleKeys(); + return this.raw.replace(ofKey(key, this.lastQueue), oldValue, newValue); + } + + @Override + public void replaceAll(BiFunction function) { + this.purgeStaleKeys(); + this.raw.replaceAll((kWeakKey, value) -> function.apply(kWeakKey.get(), value)); + } + + @Override + public V computeIfAbsent(K key, Function mappingFunction) { + this.purgeStaleKeys(); + return this.raw.computeIfAbsent(ofKey(key, this.lastQueue), kWeakKey -> mappingFunction.apply(key)); + } + + @Override + public V computeIfPresent(K key, BiFunction remappingFunction) { + this.purgeStaleKeys(); + return this.raw.computeIfPresent(ofKey(key, this.lastQueue), (kWeakKey, value) -> remappingFunction.apply(key, value)); + } + + /** + * 从缓存中获得对象,当对象不在缓存中或已经过期返回Func0回调产生的对象 + * + * @param key 键 + * @param supplier 如果不存在回调方法,用于生产值对象 + * @return 值对象 + */ + public V computeIfAbsent(K key, Func0 supplier) { + return computeIfAbsent(key, (keyParam) -> supplier.callWithRuntimeException()); + } + + @Override + public V remove(Object key) { + this.purgeStaleKeys(); + //noinspection unchecked + return this.raw.remove(ofKey((K) key, null)); + } + + @Override + public boolean remove(Object key, Object value) { + this.purgeStaleKeys(); + //noinspection unchecked + return this.raw.remove(ofKey((K) key, null), value); + } + + @Override + public void clear() { + this.raw.clear(); + //noinspection StatementWithEmptyBody + while (lastQueue.poll() != null) ; + } + + @Override + public Set keySet() { + // TODO 非高效方式的set转换,应该返回一个view + final Collection trans = CollUtil.trans(this.raw.keySet(), (reference) -> null == reference ? null : reference.get()); + return new HashSet<>(trans); + } + + @Override + public Collection values() { + this.purgeStaleKeys(); + return this.raw.values(); + } + + @Override + public Set> entrySet() { + this.purgeStaleKeys(); + return this.raw.entrySet().stream() + .map(entry -> new AbstractMap.SimpleImmutableEntry<>(entry.getKey().get(), entry.getValue())) + .collect(Collectors.toSet()); + } + + @Override + public void forEach(BiConsumer action) { + this.purgeStaleKeys(); + this.raw.forEach((key, value)-> action.accept(key.get(), value)); + } + + @Override + public Iterator> iterator() { + return entrySet().iterator(); + } + + @Override + public V compute(K key, BiFunction remappingFunction) { + this.purgeStaleKeys(); + return this.raw.compute(ofKey(key, this.lastQueue), (kWeakKey, value) -> remappingFunction.apply(key, value)); + } + + @Override + public V merge(K key, V value, BiFunction remappingFunction) { + this.purgeStaleKeys(); + return this.raw.merge(ofKey(key, this.lastQueue), value, remappingFunction); + } + + /** + * 清除被回收的键 + */ + private void purgeStaleKeys() { + Reference reference; + while ((reference = this.lastQueue.poll()) != null) { + this.raw.remove(reference); + if (null != purgeListener) { + purgeListener.callWithRuntimeException(reference); + } + } + } + + /** + * 根据Reference类型构建key对应的{@link Reference} + * + * @param key 键 + * @param queue {@link ReferenceQueue} + * @return {@link Reference} + */ + private Reference ofKey(K key, ReferenceQueue queue) { + switch (keyType) { + case WEAK: + return new WeakKey<>(key, queue); + case SOFT: + return new SoftKey<>(key, queue); + } + throw new IllegalArgumentException("Unsupported key type: " + keyType); + } + + /** + * 弱键 + * + * @param 键类型 + */ + private static class WeakKey extends WeakReference { + private final int hashCode; + + /** + * 构造 + * + * @param key 原始Key,不能为{@code null} + * @param queue {@link ReferenceQueue} + */ + WeakKey(K key, ReferenceQueue queue) { + super(key, queue); + hashCode = key.hashCode(); + } + + @Override + public int hashCode() { + return hashCode; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } else if (other instanceof WeakKey) { + return ObjectUtil.equals(((WeakKey) other).get(), get()); + } + return false; + } + } + + /** + * 弱键 + * + * @param 键类型 + */ + private static class SoftKey extends SoftReference { + private final int hashCode; + + /** + * 构造 + * + * @param key 原始Key,不能为{@code null} + * @param queue {@link ReferenceQueue} + */ + SoftKey(K key, ReferenceQueue queue) { + super(key, queue); + hashCode = key.hashCode(); + } + + @Override + public int hashCode() { + return hashCode; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } else if (other instanceof SoftKey) { + return ObjectUtil.equals(((SoftKey) other).get(), get()); + } + return false; + } + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/map/TransMap.java b/hutool-core/src/main/java/cn/hutool/core/map/TransMap.java new file mode 100755 index 000000000..2fa508c53 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/map/TransMap.java @@ -0,0 +1,118 @@ +package cn.hutool.core.map; + +import java.util.Map; +import java.util.function.BiFunction; +import java.util.function.Supplier; + +/** + * 自定义键和值转换的的Map
+ * 继承此类后,通过实现{@link #customKey(Object)}和{@link #customValue(Object)},按照给定规则加入到map或获取值。 + * + * @param 键类型 + * @param 值类型 + * @author Looly + * @since 5.8.0 + */ +public abstract class TransMap extends MapWrapper { + private static final long serialVersionUID = 1L; + + /** + * 构造
+ * 通过传入一个Map从而确定Map的类型,子类需创建一个空的Map,而非传入一个已有Map,否则值可能会被修改 + * + * @param mapFactory 空Map创建工厂 + * @since 5.8.0 + */ + public TransMap(Supplier> mapFactory) { + super(mapFactory); + } + + /** + * 构造
+ * 通过传入一个Map从而确定Map的类型,子类需创建一个空的Map,而非传入一个已有Map,否则值可能会被修改 + * + * @param emptyMap Map 被包装的Map,必须为空Map,否则自定义key会无效 + * @since 3.1.2 + */ + public TransMap(Map emptyMap) { + super(emptyMap); + } + + @Override + public V get(Object key) { + return super.get(customKey(key)); + } + + @Override + public V put(K key, V value) { + return super.put(customKey(key), customValue(value)); + } + + @Override + public void putAll(Map m) { + m.forEach(this::put); + } + + @Override + public boolean containsKey(Object key) { + return super.containsKey(customKey(key)); + } + + @Override + public V remove(Object key) { + return super.remove(customKey(key)); + } + + @Override + public boolean remove(Object key, Object value) { + return super.remove(customKey(key), customValue(value)); + } + + @Override + public boolean replace(K key, V oldValue, V newValue) { + return super.replace(customKey(key), customValue(oldValue), customValue(values())); + } + + @Override + public V replace(K key, V value) { + return super.replace(customKey(key), customValue(value)); + } + + //---------------------------------------------------------------------------- Override default methods start + @Override + public V getOrDefault(Object key, V defaultValue) { + return super.getOrDefault(customKey(key), customValue(defaultValue)); + } + + @Override + public V computeIfPresent(K key, BiFunction remappingFunction) { + return super.computeIfPresent(customKey(key), (k, v) -> remappingFunction.apply(customKey(k), customValue(v))); + } + + @Override + public V compute(K key, BiFunction remappingFunction) { + return super.compute(customKey(key), (k, v) -> remappingFunction.apply(customKey(k), customValue(v))); + } + + @Override + public V merge(K key, V value, BiFunction remappingFunction) { + return super.merge(customKey(key), customValue(value), (v1, v2) -> remappingFunction.apply(customValue(v1), customValue(v2))); + } + //---------------------------------------------------------------------------- Override default methods end + + /** + * 自定义键 + * + * @param key KEY + * @return 自定义KEY + */ + protected abstract K customKey(Object key); + + /** + * 自定义值 + * + * @param value 值 + * @return 自定义值 + */ + protected abstract V customValue(Object value); +} diff --git a/hutool-core/src/main/java/cn/hutool/core/map/WeakConcurrentMap.java b/hutool-core/src/main/java/cn/hutool/core/map/WeakConcurrentMap.java new file mode 100755 index 000000000..5cbbe1332 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/map/WeakConcurrentMap.java @@ -0,0 +1,35 @@ +package cn.hutool.core.map; + +import cn.hutool.core.util.ReferenceUtil; + +import java.lang.ref.Reference; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * 线程安全的WeakMap实现
+ * 参考:jdk.management.resource.internal.WeakKeyConcurrentHashMap + * + * @param 键类型 + * @param 值类型 + * @author looly + * @since 5.8.0 + */ +public class WeakConcurrentMap extends ReferenceConcurrentMap { + + /** + * 构造 + */ + public WeakConcurrentMap() { + this(new ConcurrentHashMap<>()); + } + + /** + * 构造 + * + * @param raw {@link ConcurrentMap}实现 + */ + public WeakConcurrentMap(ConcurrentMap, V> raw) { + super(raw, ReferenceUtil.ReferenceType.WEAK); + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/thread/lock/NoLock.java b/hutool-core/src/main/java/cn/hutool/core/thread/lock/NoLock.java index 7ef4b9d46..09d6b8648 100644 --- a/hutool-core/src/main/java/cn/hutool/core/thread/lock/NoLock.java +++ b/hutool-core/src/main/java/cn/hutool/core/thread/lock/NoLock.java @@ -12,6 +12,8 @@ import java.util.concurrent.locks.Lock; */ public class NoLock implements Lock{ + public static NoLock INSTANCE = new NoLock(); + @Override public void lock() { } diff --git a/hutool-core/src/main/java/cn/hutool/core/thread/lock/NoReadWriteLock.java b/hutool-core/src/main/java/cn/hutool/core/thread/lock/NoReadWriteLock.java new file mode 100755 index 000000000..ce84885df --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/thread/lock/NoReadWriteLock.java @@ -0,0 +1,22 @@ +package cn.hutool.core.thread.lock; + +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; + +/** + * 无锁的读写锁实现 + * + * @author looly + * @since 5.8.0 + */ +public class NoReadWriteLock implements ReadWriteLock { + @Override + public Lock readLock() { + return NoLock.INSTANCE; + } + + @Override + public Lock writeLock() { + return NoLock.INSTANCE; + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ClassLoaderUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ClassLoaderUtil.java index ef13c4f2e..2300fe107 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ClassLoaderUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ClassLoaderUtil.java @@ -4,7 +4,8 @@ import cn.hutool.core.convert.BasicType; import cn.hutool.core.exceptions.UtilException; import cn.hutool.core.lang.Assert; import cn.hutool.core.lang.JarClassLoader; -import cn.hutool.core.lang.SimpleCache; +import cn.hutool.core.lang.Pair; +import cn.hutool.core.map.WeakConcurrentMap; import cn.hutool.core.text.CharPool; import java.io.File; @@ -49,7 +50,7 @@ public class ClassLoaderUtil { * 原始类型名和其class对应表,例如:int =》 int.class */ private static final Map> PRIMITIVE_TYPE_NAME_MAP = new ConcurrentHashMap<>(32); - private static final SimpleCache> CLASS_CACHE = new SimpleCache<>(); + private static final WeakConcurrentMap, Class> CLASS_CACHE = new WeakConcurrentMap<>(); static { List> primitiveTypes = new ArrayList<>(32); @@ -179,7 +180,7 @@ public class ClassLoaderUtil { * * * @param name 类名 - * @param classLoader {@link ClassLoader},{@code null} 则使用系统默认ClassLoader + * @param classLoader {@link ClassLoader},{@code null} 则使用{@link #getClassLoader()}获取 * @param isInitialized 是否初始化类(调用static模块内容和初始化static属性) * @return 类名对应的类 * @throws UtilException 包装{@link ClassNotFoundException},没有类名对应的类时抛出此异常 @@ -189,49 +190,18 @@ public class ClassLoaderUtil { // 自动将包名中的"/"替换为"." name = name.replace(CharPool.SLASH, CharPool.DOT); + if(null == classLoader){ + classLoader = getClassLoader(); + } // 加载原始类型和缓存中的类 Class clazz = loadPrimitiveClass(name); if (clazz == null) { - clazz = CLASS_CACHE.get(name); + final String finalName = name; + final ClassLoader finalClassLoader = classLoader; + clazz = CLASS_CACHE.computeIfAbsent(Pair.of(name, classLoader), (key)-> doLoadClass(finalName, finalClassLoader, isInitialized)); } - if (clazz != null) { - return clazz; - } - - if (name.endsWith(ARRAY_SUFFIX)) { - // 对象数组"java.lang.String[]"风格 - final String elementClassName = name.substring(0, name.length() - ARRAY_SUFFIX.length()); - final Class elementClass = loadClass(elementClassName, classLoader, isInitialized); - clazz = Array.newInstance(elementClass, 0).getClass(); - } else if (name.startsWith(NON_PRIMITIVE_ARRAY_PREFIX) && name.endsWith(";")) { - // "[Ljava.lang.String;" 风格 - final String elementName = name.substring(NON_PRIMITIVE_ARRAY_PREFIX.length(), name.length() - 1); - final Class elementClass = loadClass(elementName, classLoader, isInitialized); - clazz = Array.newInstance(elementClass, 0).getClass(); - } else if (name.startsWith(INTERNAL_ARRAY_PREFIX)) { - // "[[I" 或 "[[Ljava.lang.String;" 风格 - final String elementName = name.substring(INTERNAL_ARRAY_PREFIX.length()); - final Class elementClass = loadClass(elementName, classLoader, isInitialized); - clazz = Array.newInstance(elementClass, 0).getClass(); - } else { - // 加载普通类 - if (null == classLoader) { - classLoader = getClassLoader(); - } - try { - clazz = Class.forName(name, isInitialized, classLoader); - } catch (ClassNotFoundException ex) { - // 尝试获取内部类,例如java.lang.Thread.State =》java.lang.Thread$State - clazz = tryLoadInnerClass(name, classLoader, isInitialized); - if (null == clazz) { - throw new UtilException(ex); - } - } - } - - // 加入缓存并返回 - return CLASS_CACHE.put(name, clazz); + return clazz; } /** @@ -311,6 +281,47 @@ public class ClassLoaderUtil { } // ----------------------------------------------------------------------------------- Private method start + /** + * 加载非原始类类,无缓存 + * @param name 类名 + * @param classLoader {@link ClassLoader} + * @param isInitialized 是否初始化 + * @return 类 + */ + private static Class doLoadClass(String name, ClassLoader classLoader, boolean isInitialized){ + Class clazz; + if (name.endsWith(ARRAY_SUFFIX)) { + // 对象数组"java.lang.String[]"风格 + final String elementClassName = name.substring(0, name.length() - ARRAY_SUFFIX.length()); + final Class elementClass = loadClass(elementClassName, classLoader, isInitialized); + clazz = Array.newInstance(elementClass, 0).getClass(); + } else if (name.startsWith(NON_PRIMITIVE_ARRAY_PREFIX) && name.endsWith(";")) { + // "[Ljava.lang.String;" 风格 + final String elementName = name.substring(NON_PRIMITIVE_ARRAY_PREFIX.length(), name.length() - 1); + final Class elementClass = loadClass(elementName, classLoader, isInitialized); + clazz = Array.newInstance(elementClass, 0).getClass(); + } else if (name.startsWith(INTERNAL_ARRAY_PREFIX)) { + // "[[I" 或 "[[Ljava.lang.String;" 风格 + final String elementName = name.substring(INTERNAL_ARRAY_PREFIX.length()); + final Class elementClass = loadClass(elementName, classLoader, isInitialized); + clazz = Array.newInstance(elementClass, 0).getClass(); + } else { + // 加载普通类 + if (null == classLoader) { + classLoader = getClassLoader(); + } + try { + clazz = Class.forName(name, isInitialized, classLoader); + } catch (ClassNotFoundException ex) { + // 尝试获取内部类,例如java.lang.Thread.State =》java.lang.Thread$State + clazz = tryLoadInnerClass(name, classLoader, isInitialized); + if (null == clazz) { + throw new UtilException(ex); + } + } + } + return clazz; + } /** * 尝试转换并加载内部类,例如java.lang.Thread.State =》java.lang.Thread$State diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ReflectUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ReflectUtil.java index 7b746bc6e..b9c3ab637 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ReflectUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ReflectUtil.java @@ -8,9 +8,9 @@ import cn.hutool.core.convert.Convert; import cn.hutool.core.exceptions.UtilException; import cn.hutool.core.lang.Assert; import cn.hutool.core.lang.Filter; -import cn.hutool.core.lang.SimpleCache; import cn.hutool.core.lang.reflect.MethodHandleUtil; import cn.hutool.core.map.MapUtil; +import cn.hutool.core.map.WeakConcurrentMap; import java.lang.reflect.AccessibleObject; import java.lang.reflect.Constructor; @@ -36,15 +36,15 @@ public class ReflectUtil { /** * 构造对象缓存 */ - private static final SimpleCache, Constructor[]> CONSTRUCTORS_CACHE = new SimpleCache<>(); + private static final WeakConcurrentMap, Constructor[]> CONSTRUCTORS_CACHE = new WeakConcurrentMap<>(); /** * 字段缓存 */ - private static final SimpleCache, Field[]> FIELDS_CACHE = new SimpleCache<>(); + private static final WeakConcurrentMap, Field[]> FIELDS_CACHE = new WeakConcurrentMap<>(); /** * 方法缓存 */ - private static final SimpleCache, Method[]> METHODS_CACHE = new SimpleCache<>(); + private static final WeakConcurrentMap, Method[]> METHODS_CACHE = new WeakConcurrentMap<>(); // --------------------------------------------------------------------------------------------------------- Constructor @@ -86,7 +86,7 @@ public class ReflectUtil { @SuppressWarnings("unchecked") public static Constructor[] getConstructors(Class beanClass) throws SecurityException { Assert.notNull(beanClass); - return (Constructor[]) CONSTRUCTORS_CACHE.get(beanClass, () -> getConstructorsDirectly(beanClass)); + return (Constructor[]) CONSTRUCTORS_CACHE.computeIfAbsent(beanClass, () -> getConstructorsDirectly(beanClass)); } /** @@ -175,7 +175,7 @@ public class ReflectUtil { */ public static Field[] getFields(Class beanClass) throws SecurityException { Assert.notNull(beanClass); - return FIELDS_CACHE.get(beanClass, () -> getFieldsDirectly(beanClass, true)); + return FIELDS_CACHE.computeIfAbsent(beanClass, () -> getFieldsDirectly(beanClass, true)); } @@ -651,7 +651,7 @@ public class ReflectUtil { */ public static Method[] getMethods(Class beanClass) throws SecurityException { Assert.notNull(beanClass); - return METHODS_CACHE.get(beanClass, + return METHODS_CACHE.computeIfAbsent(beanClass, () -> getMethodsDirectly(beanClass, true, true)); } diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/SimpleCacheTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/SimpleCacheTest.java index e32c78a90..8b15d28e6 100644 --- a/hutool-core/src/test/java/cn/hutool/core/lang/SimpleCacheTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/lang/SimpleCacheTest.java @@ -50,7 +50,7 @@ public class SimpleCacheTest { final SimpleCache cache = new SimpleCache<>(); final ConcurrencyTester tester = new ConcurrencyTester(9000); tester.test(()-> cache.get("aaa", ()-> { - ThreadUtil.sleep(1000); + ThreadUtil.sleep(200); return "aaaValue"; })); diff --git a/hutool-core/src/test/java/cn/hutool/core/map/FuncMapTest.java b/hutool-core/src/test/java/cn/hutool/core/map/FuncMapTest.java new file mode 100755 index 000000000..1a26d127a --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/map/FuncMapTest.java @@ -0,0 +1,22 @@ +package cn.hutool.core.map; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.HashMap; + +public class FuncMapTest { + + @Test + public void putGetTest(){ + final FuncMap map = new FuncMap<>(HashMap::new, + (key)->key.toString().toLowerCase(), + (value)->value.toString().toUpperCase()); + + map.put("aaa", "b"); + map.put("BBB", "c"); + + Assert.assertEquals("B", map.get("aaa")); + Assert.assertEquals("C", map.get("bbb")); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/map/WeakConcurrentMapTest.java b/hutool-core/src/test/java/cn/hutool/core/map/WeakConcurrentMapTest.java new file mode 100755 index 000000000..7824d1918 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/map/WeakConcurrentMapTest.java @@ -0,0 +1,52 @@ +package cn.hutool.core.map; + +import cn.hutool.core.thread.ConcurrencyTester; +import cn.hutool.core.thread.ThreadUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.RandomUtil; +import org.junit.Assert; +import org.junit.Test; + +public class WeakConcurrentMapTest { + + @Test + public void putAndGetTest(){ + final WeakConcurrentMap map = new WeakConcurrentMap<>(); + Object + key1 = new Object(), value1 = new Object(), + key2 = new Object(), value2 = new Object(), + key3 = new Object(), value3 = new Object(), + key4 = new Object(), value4 = new Object(); + map.put(key1, value1); + map.put(key2, value2); + map.put(key3, value3); + map.put(key4, value4); + + Assert.assertEquals(value1, map.get(key1)); + Assert.assertEquals(value2, map.get(key2)); + Assert.assertEquals(value3, map.get(key3)); + Assert.assertEquals(value4, map.get(key4)); + + // 清空引用 + //noinspection UnusedAssignment + key1 = null; + //noinspection UnusedAssignment + key2 = null; + + System.gc(); + ThreadUtil.sleep(200L); + + Assert.assertEquals(2, map.size()); + } + + @Test + public void getConcurrencyTest(){ + final WeakConcurrentMap cache = new WeakConcurrentMap<>(); + final ConcurrencyTester tester = new ConcurrencyTester(9000); + tester.test(()-> cache.computeIfAbsent("aaa" + RandomUtil.randomInt(2), (key)-> "aaaValue")); + + Assert.assertTrue(tester.getInterval() > 0); + String value = ObjectUtil.defaultIfNull(cache.get("aaa0"), cache.get("aaa1")); + Assert.assertEquals("aaaValue", value); + } +} diff --git a/hutool-extra/src/main/java/cn/hutool/extra/cglib/BeanCopierCache.java b/hutool-extra/src/main/java/cn/hutool/extra/cglib/BeanCopierCache.java index a2a012a10..e40712d1c 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/cglib/BeanCopierCache.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/cglib/BeanCopierCache.java @@ -1,6 +1,6 @@ package cn.hutool.extra.cglib; -import cn.hutool.core.lang.SimpleCache; +import cn.hutool.core.map.WeakConcurrentMap; import cn.hutool.core.util.StrUtil; import net.sf.cglib.beans.BeanCopier; import net.sf.cglib.core.Converter; @@ -18,7 +18,7 @@ public enum BeanCopierCache { */ INSTANCE; - private final SimpleCache cache = new SimpleCache<>(); + private final WeakConcurrentMap cache = new WeakConcurrentMap<>(); /** * 获得类与转换器生成的key在{@link BeanCopier}的Map中对应的元素 @@ -43,7 +43,7 @@ public enum BeanCopierCache { */ public BeanCopier get(Class srcClass, Class targetClass, boolean useConverter) { final String key = genKey(srcClass, targetClass, useConverter); - return cache.get(key, () -> BeanCopier.create(srcClass, targetClass, useConverter)); + return cache.computeIfAbsent(key, () -> BeanCopier.create(srcClass, targetClass, useConverter)); } /** diff --git a/hutool-script/src/main/java/cn/hutool/script/ScriptUtil.java b/hutool-script/src/main/java/cn/hutool/script/ScriptUtil.java index bc6965790..de674545c 100644 --- a/hutool-script/src/main/java/cn/hutool/script/ScriptUtil.java +++ b/hutool-script/src/main/java/cn/hutool/script/ScriptUtil.java @@ -1,6 +1,6 @@ package cn.hutool.script; -import cn.hutool.core.lang.SimpleCache; +import cn.hutool.core.map.WeakConcurrentMap; import cn.hutool.core.util.StrUtil; import javax.script.Bindings; @@ -20,7 +20,7 @@ import javax.script.ScriptException; public class ScriptUtil { private static final ScriptEngineManager MANAGER = new ScriptEngineManager(); - private static final SimpleCache CACHE = new SimpleCache<>(); + private static final WeakConcurrentMap CACHE = new WeakConcurrentMap<>(); /** * 获得单例的{@link ScriptEngine} 实例 @@ -29,7 +29,7 @@ public class ScriptUtil { * @return {@link ScriptEngine} 实例 */ public static ScriptEngine getScript(String nameOrExtOrMime) { - return CACHE.get(nameOrExtOrMime, () -> createScript(nameOrExtOrMime)); + return CACHE.computeIfAbsent(nameOrExtOrMime, () -> createScript(nameOrExtOrMime)); } /** From 7bd5aaab7ca6a2696ad864d2d4755439db7136dc Mon Sep 17 00:00:00 2001 From: Looly Date: Sun, 17 Apr 2022 16:36:28 +0800 Subject: [PATCH 13/52] fix code --- .../src/main/java/cn/hutool/cache/impl/WeakCache.java | 4 ++-- pom.xml | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/hutool-cache/src/main/java/cn/hutool/cache/impl/WeakCache.java b/hutool-cache/src/main/java/cn/hutool/cache/impl/WeakCache.java index 90df20303..4d32b7ed6 100644 --- a/hutool-cache/src/main/java/cn/hutool/cache/impl/WeakCache.java +++ b/hutool-cache/src/main/java/cn/hutool/cache/impl/WeakCache.java @@ -1,6 +1,6 @@ package cn.hutool.cache.impl; -import java.util.WeakHashMap; +import cn.hutool.core.map.WeakConcurrentMap; /** * 弱引用缓存
@@ -22,6 +22,6 @@ public class WeakCache extends TimedCache{ * @param timeout 超时时常,单位毫秒,-1或0表示无限制 */ public WeakCache(long timeout) { - super(timeout, new WeakHashMap<>()); + super(timeout, new WeakConcurrentMap<>()); } } diff --git a/pom.xml b/pom.xml index e9793eb85..4d394ac24 100644 --- a/pom.xml +++ b/pom.xml @@ -97,6 +97,7 @@ ${compile.version} ${compile.version} + -Xlint:unchecked From c57ecead276ef2b9684a3a37b0cd235c3382082c Mon Sep 17 00:00:00 2001 From: Looly Date: Sun, 17 Apr 2022 17:56:53 +0800 Subject: [PATCH 14/52] fix code --- .../java/cn/hutool/cache/impl/WeakCache.java | 16 ++++++++++++++++ .../test/java/cn/hutool/cache/WeakCacheTest.java | 4 +++- .../hutool/core/map/ReferenceConcurrentMap.java | 10 +++++----- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/hutool-cache/src/main/java/cn/hutool/cache/impl/WeakCache.java b/hutool-cache/src/main/java/cn/hutool/cache/impl/WeakCache.java index 4d32b7ed6..a6fb23690 100644 --- a/hutool-cache/src/main/java/cn/hutool/cache/impl/WeakCache.java +++ b/hutool-cache/src/main/java/cn/hutool/cache/impl/WeakCache.java @@ -1,7 +1,12 @@ package cn.hutool.cache.impl; +import cn.hutool.cache.CacheListener; +import cn.hutool.core.lang.Opt; +import cn.hutool.core.lang.mutable.Mutable; import cn.hutool.core.map.WeakConcurrentMap; +import java.lang.ref.Reference; + /** * 弱引用缓存
* 对于一个给定的键,其映射的存在并不阻止垃圾回收器对该键的丢弃,这就使该键成为可终止的,被终止,然后被回收。
@@ -24,4 +29,15 @@ public class WeakCache extends TimedCache{ public WeakCache(long timeout) { super(timeout, new WeakConcurrentMap<>()); } + + @Override + public WeakCache setListener(CacheListener listener) { + super.setListener(listener); + + final WeakConcurrentMap, CacheObj> map = (WeakConcurrentMap, CacheObj>) this.cacheMap; + // WeakKey回收之后,key对应的值已经是null了,因此此处的key也为null + map.setPurgeListener((key, value)-> listener.onRemove(Opt.ofNullable(key).map(Reference::get).map(Mutable::get).get(), value.getValue())); + + return this; + } } diff --git a/hutool-cache/src/test/java/cn/hutool/cache/WeakCacheTest.java b/hutool-cache/src/test/java/cn/hutool/cache/WeakCacheTest.java index cdf0404b3..f61ed77b7 100644 --- a/hutool-cache/src/test/java/cn/hutool/cache/WeakCacheTest.java +++ b/hutool-cache/src/test/java/cn/hutool/cache/WeakCacheTest.java @@ -26,11 +26,13 @@ public class WeakCacheTest { @Ignore public void removeByGcTest(){ // https://gitee.com/dromara/hutool/issues/I51O7M - // 经过GC, WeakCache cache = new WeakCache<>(-1); cache.put("a", "1"); cache.put("b", "2"); + + // 监听 Assert.assertEquals(2, cache.size()); + cache.setListener(Console::log); // GC测试 int i=0; diff --git a/hutool-core/src/main/java/cn/hutool/core/map/ReferenceConcurrentMap.java b/hutool-core/src/main/java/cn/hutool/core/map/ReferenceConcurrentMap.java index a976ca30f..99e80a877 100755 --- a/hutool-core/src/main/java/cn/hutool/core/map/ReferenceConcurrentMap.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/ReferenceConcurrentMap.java @@ -2,7 +2,6 @@ package cn.hutool.core.map; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.func.Func0; -import cn.hutool.core.lang.func.VoidFunc1; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ReferenceUtil; @@ -40,7 +39,7 @@ public class ReferenceConcurrentMap implements ConcurrentMap, Iterab /** * 回收监听 */ - private VoidFunc1> purgeListener; + private BiConsumer, V> purgeListener; // region 构造 @@ -62,7 +61,7 @@ public class ReferenceConcurrentMap implements ConcurrentMap, Iterab * * @param purgeListener 监听函数 */ - public void setPurgeListener(VoidFunc1> purgeListener) { + public void setPurgeListener(BiConsumer, V> purgeListener) { this.purgeListener = purgeListener; } @@ -225,10 +224,11 @@ public class ReferenceConcurrentMap implements ConcurrentMap, Iterab */ private void purgeStaleKeys() { Reference reference; + V value; while ((reference = this.lastQueue.poll()) != null) { - this.raw.remove(reference); + value = this.raw.remove(reference); if (null != purgeListener) { - purgeListener.callWithRuntimeException(reference); + purgeListener.accept(reference, value); } } } From 3b8682528a151dfbfb640cfb9e607924ad19f55e Mon Sep 17 00:00:00 2001 From: Looly Date: Mon, 18 Apr 2022 20:31:21 +0800 Subject: [PATCH 15/52] add README --- hutool-core/README.md | 114 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100755 hutool-core/README.md diff --git a/hutool-core/README.md b/hutool-core/README.md new file mode 100755 index 000000000..360f5efbc --- /dev/null +++ b/hutool-core/README.md @@ -0,0 +1,114 @@ +

+ +

+

+ 🍬A set of tools that keep Java sweet. +

+

+ 👉 https://hutool.cn/ 👈 +

+ +## 📚Hutool-core 模块介绍 + +`Hutool-core`提供了最常使用的基础工具类,包括集合、Map、IO、线程、Bean、图片处理、线程并发等便捷工具。 + +------------------------------------------------------------------------------- + +## 🛠️包含内容 + +### 注解(annotation) + +提供了注解工具类,以及一些注解封装。如CombinationAnnotationElement组合注解以及Alias别名注解等。 + +### bean(bean) + +提供了Bean工具类,以及Bean属性解析、Bean拷贝、动态Bean等。 + +### 构建器(builder) + +抽象了Builder接口,提供建造者模式的封装,并默认提供了包括equals封装、Bean构建封装、比较器封装等。 + +### 克隆(clone) + +提供Cloneable接口,明确clonse方法,并提供默认实现类。 + +### 编码(codec) + +提供了BaseN编码(Base16、Base32、Base58、Base62、Base64)编码实现。并提供了包括BCD、PunyCode、百分号编码的实现。 +同时提供了包括莫尔斯电码、凯撒密码、RotN这类有趣功能的实现。 + +### 集合(collection) + +集合中主要是提供了针对`Iterator`实现类的工具封装方法`IterUtil`和集合类封装的工具类`CollUtil`,并提供了一些特别的集合封装。 + +### 比较器(comparator) + +主要是一些比较器的实现,如Bean字段比较器、自定义函数比较器、版本比较器等。 + +### 动态编译(compiler) + +提供`javax.tools.JavaCompiler`的包装简化服务,形成源码动态编译工具类`CompilerUtil`,完成代码动态编译及热部署。 + +### 压缩(compress) + +主要针对`java.util.zip`中的相关类封装工具,提供Zip、Gzip、Zlib等格式的压缩解压缩封装,为`ZipUtil`提供服务。 + +### 转换(convert) + +“万能”转换器,提供整套的类型转换方式。通过`Converter`接口和`ConverterRegistry`转换登记中心,完成任意数据类型转换和自定义转换。 + +### 日期时间(date) + +提供`Date`、`Calendar`、`java.time`相关API的工具化封装。包括时间解析、格式化、偏移等。 + +### 异常(exceptions) + +提供异常工具`ExceptionUtil`,以及一些工具内部使用的异常。 + +### getter接口(getter) + +提供各种类型的get操作接口封装。 + +### 图片(img) + +提供图片、绘图、字体等工具封装,并提供GIF生成器和解析器实现。 + +### IO流和文件(io) + +提供IO流工具、文件工具、文件类型工具等,并提供流拷贝、Checksum、文件监听功能实现。 + +### 语言特性(lang) + +超级大杂项,提供一些设计模式的抽象实现(如单例模式`Singleton`),还有正则、Id生成器、函数、Hash算法、可变对象、树形结构、字典等。 + +### Map(map) + +提供Map工具类和各类Map实现封装,如行列键的Table实现、自定义键值对转换的Map、线程安全的WeakMap实现等。 + +### 数学(math) + +提供简单数学计算封装,如排列组合、货币类等。 + +### 网络(net) + +提供网络相关工具封装,以及Ip地址工具类、SSL工具类、URL编码解码等。 + +### StreamAPI封装(stream) + +提供简单的Stream相关封装。 + +### Swing和AWT(swing) + +提供桌面应用API的工具封装,如启动应用、控制键盘鼠标操作、截屏等功能。 + +### 文本字符串(text) + +提供强大的字符串文本封装,包括字符转换、字符串查找、字符串替换、字符串切分、Unicode工具等,并提供CSV格式封装。 + +### 线程及并发(thread) + +线程并发封装,包括线程工具、锁工具、`CompletableFuture`封装工具、线程池构建等。 + +### 工具杂项(util) + +提供其他不便归类的杂项工具类。如数组、编码、字符、Class、坐标系、身份证、组织机构代码、脱敏、枚举、转义、XML、进制转换、随机数、反射、正则、SPI等各种工具。 \ No newline at end of file From 1c273c7d4c7e4f9f8b229465edd3dbb5cccf535a Mon Sep 17 00:00:00 2001 From: Looly Date: Mon, 18 Apr 2022 20:39:22 +0800 Subject: [PATCH 16/52] fix readme --- hutool-core/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hutool-core/README.md b/hutool-core/README.md index 360f5efbc..f7cf33d14 100755 --- a/hutool-core/README.md +++ b/hutool-core/README.md @@ -18,7 +18,7 @@ ### 注解(annotation) -提供了注解工具类,以及一些注解封装。如CombinationAnnotationElement组合注解以及Alias别名注解等。 +提供了注解工具类,以及一些注解封装。如`CombinationAnnotationElement`组合注解以及Alias别名注解等。 ### bean(bean) @@ -30,7 +30,7 @@ ### 克隆(clone) -提供Cloneable接口,明确clonse方法,并提供默认实现类。 +提供`Cloneable`接口,明确`clone`方法,并提供默认实现类。 ### 编码(codec) From 571d76f578f6a017d814ae9015141b32c610ecd4 Mon Sep 17 00:00:00 2001 From: Looly Date: Mon, 18 Apr 2022 21:31:02 +0800 Subject: [PATCH 17/52] add ObjectMapper --- CHANGELOG.md | 3 +- .../main/java/cn/hutool/json/JSONArray.java | 76 +----- .../main/java/cn/hutool/json/JSONObject.java | 135 +--------- .../java/cn/hutool/json/ObjectMapper.java | 236 ++++++++++++++++++ 4 files changed, 247 insertions(+), 203 deletions(-) create mode 100755 hutool-json/src/main/java/cn/hutool/json/ObjectMapper.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b7f33a87..8bc455df6 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ------------------------------------------------------------------------------------------------------------- -# 5.8.0.M4 (2022-04-17) +# 5.8.0.M4 (2022-04-18) ### ❌不兼容特性 * 【json 】 【可能兼容问题】JSONArray删除部分构造 @@ -14,6 +14,7 @@ * 【json 】 JSON新增在解析时的过滤方法(issue#I52O85@Gitee) * 【core 】 添加ArrayUtil.distinct、CollUtil.distinct重载(issue#2256@Github) * 【core 】 添加TransMap、FuncMap、ReferenceConcurrentMap、WeakConcurrentMap +* 【json 】 添加ObjectMapper ### 🐞Bug修复 * 【core 】 修复StrUtil.firstNonX非static问题(issue#2257@Github) 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 553bd02f6..809d4c652 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONArray.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONArray.java @@ -1,19 +1,13 @@ package cn.hutool.json; import cn.hutool.core.bean.BeanPath; -import cn.hutool.core.collection.ArrayIter; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Filter; import cn.hutool.core.lang.mutable.Mutable; import cn.hutool.core.lang.mutable.MutableObj; import cn.hutool.core.lang.mutable.MutablePair; import cn.hutool.core.text.StrJoiner; -import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ObjectUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.core.util.TypeUtil; -import cn.hutool.json.serialize.GlobalSerializeMapping; -import cn.hutool.json.serialize.JSONSerializer; import cn.hutool.json.serialize.JSONWriter; import java.io.StringWriter; @@ -171,7 +165,7 @@ public class JSONArray implements JSON, JSONGetter, List, Rando */ public JSONArray(Object object, JSONConfig jsonConfig, Filter> filter) throws JSONException { this(DEFAULT_CAPACITY, jsonConfig); - init(object, filter); + ObjectMapper.of(object).map(this, filter); } // endregion @@ -588,7 +582,6 @@ public class JSONArray implements JSON, JSONGetter, List, Rando clone.rawList = ObjectUtil.clone(this.rawList); return clone; } - // ------------------------------------------------------------------------------------------------- Private method start /** * 原始添加,添加的对象不做任何处理 @@ -612,71 +605,4 @@ public class JSONArray implements JSON, JSONGetter, List, Rando } return this.rawList.add(obj); } - - /** - * 初始化 - * - * @param source 数组或集合或JSON数组字符串 - * @param filter 键值对过滤编辑器,可以通过实现此接口,完成解析前对值的过滤和修改操作,{@code null}表示不过滤 - * @throws JSONException 非数组或集合 - */ - @SuppressWarnings({"rawtypes", "unchecked"}) - private void init(Object source, Filter> filter) throws JSONException { - if (null == source) { - return; - } - - final JSONSerializer serializer = GlobalSerializeMapping.getSerializer(source.getClass()); - if (null != serializer && JSONArray.class.equals(TypeUtil.getTypeArgument(serializer.getClass()))) { - // 自定义序列化 - serializer.serialize(this, source); - } else if (source instanceof CharSequence) { - // JSON字符串 - initFromStr((CharSequence) source, filter); - } else if (source instanceof JSONTokener) { - initFromTokener((JSONTokener) source, filter); - } else { - Iterator iter; - if (ArrayUtil.isArray(source)) {// 数组 - iter = new ArrayIter<>(source); - } else if (source instanceof Iterator) {// Iterator - iter = ((Iterator) source); - } else if (source instanceof Iterable) {// Iterable - iter = ((Iterable) source).iterator(); - } else { - throw new JSONException("JSONArray initial value should be a string or collection or array."); - } - - Object next; - while (iter.hasNext()) { - next = iter.next(); - // 检查循环引用 - if (next != source) { - this.addRaw(JSONUtil.wrap(next, this.config), filter); - } - } - } - } - - /** - * 初始化 - * - * @param source JSON字符串 - */ - private void initFromStr(CharSequence source, Filter> filter) { - if (null != source) { - initFromTokener(new JSONTokener(StrUtil.trim(source), this.config), filter); - } - } - - /** - * 初始化 - * - * @param x {@link JSONTokener} - * @param filter 键值对过滤编辑器,可以通过实现此接口,完成解析前对值的过滤和修改操作,{@code null}表示不过滤 - */ - private void initFromTokener(JSONTokener x, Filter> filter) { - JSONParser.of(x).parseTo(this, filter); - } - // ------------------------------------------------------------------------------------------------- Private method end } 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 53b7deb26..921d19df8 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONObject.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONObject.java @@ -1,9 +1,7 @@ package cn.hutool.json; import cn.hutool.core.bean.BeanPath; -import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.collection.CollectionUtil; -import cn.hutool.core.convert.Convert; import cn.hutool.core.lang.Filter; import cn.hutool.core.lang.mutable.MutablePair; import cn.hutool.core.map.CaseInsensitiveMap; @@ -12,10 +10,6 @@ import cn.hutool.core.map.MapWrapper; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ReflectUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.json.serialize.GlobalSerializeMapping; -import cn.hutool.json.serialize.JSONObjectSerializer; -import cn.hutool.json.serialize.JSONSerializer; import cn.hutool.json.serialize.JSONWriter; import java.io.StringWriter; @@ -24,9 +18,7 @@ import java.lang.reflect.Type; import java.math.BigDecimal; import java.math.BigInteger; import java.util.Collection; -import java.util.Enumeration; import java.util.Map; -import java.util.ResourceBundle; /** * JSON对象
@@ -216,7 +208,7 @@ public class JSONObject extends MapWrapper implements JSON, JSON */ public JSONObject(Object source, JSONConfig config, Filter> filter) { this(DEFAULT_CAPACITY, config); - init(source, filter); + ObjectMapper.of(source).map(this, filter); } /** @@ -230,26 +222,26 @@ public class JSONObject extends MapWrapper implements JSON, JSON * KEY或VALUE任意一个为null则不加入,字段不存在也不加入
* 若names列表为空,则字段全部加入 * - * @param obj 包含需要字段的Bean对象或者Map对象 - * @param names 需要构建JSONObject的字段名列表 + * @param source 包含需要字段的Bean对象或者Map对象 + * @param names 需要构建JSONObject的字段名列表 */ - public JSONObject(Object obj, String... names) { + public JSONObject(Object source, String... names) { this(); if (ArrayUtil.isEmpty(names)) { - init(obj, null); + ObjectMapper.of(source).map(this, null); return; } - if (obj instanceof Map) { + if (source instanceof Map) { Object value; for (String name : names) { - value = ((Map) obj).get(name); + value = ((Map) source).get(name); this.putOnce(name, value); } } else { for (String name : names) { try { - this.putOpt(name, ReflectUtil.getFieldValue(obj, name)); + this.putOpt(name, ReflectUtil.getFieldValue(source, name)); } catch (Exception ignore) { // ignore } @@ -598,115 +590,4 @@ public class JSONObject extends MapWrapper implements JSON, JSON clone.config = this.config; return clone; } - - // ------------------------------------------------------------------------------------------------- Private method start - - /** - * Bean对象转Map - * - * @param bean Bean对象 - */ - private void populateMap(Object bean) { - BeanUtil.beanToMap(bean, this, InternalJSONUtil.toCopyOptions(config)); - } - - /** - * 初始化 - *
    - *
  1. value为Map,将键值对加入JSON对象
  2. - *
  3. value为JSON字符串(CharSequence),使用JSONTokener解析
  4. - *
  5. value为JSONTokener,直接解析
  6. - *
  7. value为普通JavaBean,如果为普通的JavaBean,调用其getters方法(getXXX或者isXXX)获得值,加入到JSON对象。例如:如果JavaBean对象中有个方法getName(),值为"张三",获得的键值对为:name: "张三"
  8. - *
- * - * @param source JavaBean或者Map对象或者String - * @param filter 键值对过滤编辑器,可以通过实现此接口,完成解析前对键值对的过滤和修改操作 - */ - @SuppressWarnings({"rawtypes", "unchecked"}) - private void init(Object source, Filter> filter) { - if (null == source) { - return; - } - - // 自定义序列化 - final JSONSerializer serializer = GlobalSerializeMapping.getSerializer(source.getClass()); - if (serializer instanceof JSONObjectSerializer) { - serializer.serialize(this, source); - return; - } - - if (ArrayUtil.isArray(source) || source instanceof JSONArray) { - // 不支持集合类型转换为JSONObject - throw new JSONException("Unsupported type [{}] to JSONObject!", source.getClass()); - } - - if (source instanceof Map) { - // Map - for (final Entry e : ((Map) source).entrySet()) { - this.set(Convert.toStr(e.getKey()), e.getValue(), filter, false); - } - } else if (source instanceof Map.Entry) { - final Map.Entry entry = (Map.Entry) source; - this.set(Convert.toStr(entry.getKey()), entry.getValue(), filter, false); - } else if (source instanceof CharSequence) { - // 可能为JSON字符串 - initFromStr((CharSequence) source, filter); - } else if (source instanceof JSONTokener) { - // JSONTokener - initFromTokener((JSONTokener) source, filter); - } else if (source instanceof ResourceBundle) { - // JSONTokener - initFromResourceBundle((ResourceBundle) source, filter); - } else if (BeanUtil.isReadableBean(source.getClass())) { - // 普通Bean,过滤器无效 - this.populateMap(source); - } else { - // 不支持对象类型转换为JSONObject - throw new JSONException("Unsupported type [{}] to JSONObject!", source.getClass()); - } - } - - /** - * 初始化 - * - * @param bundle ResourceBundle - * @param filter 键值对过滤编辑器,可以通过实现此接口,完成解析前对键值对的过滤和修改操作,{@code null}表示不过滤 - * @since 5.3.1 - */ - private void initFromResourceBundle(ResourceBundle bundle, Filter> filter) { - Enumeration keys = bundle.getKeys(); - while (keys.hasMoreElements()) { - String key = keys.nextElement(); - if (key != null) { - InternalJSONUtil.propertyPut(this, key, bundle.getString(key), filter); - } - } - } - - /** - * 初始化,可以判断字符串为JSON或者XML - * - * @param source JSON字符串 - * @param filter 键值对过滤编辑器,可以通过实现此接口,完成解析前对键值对的过滤和修改操作,{@code null}表示不过滤 - */ - private void initFromStr(CharSequence source, Filter> filter) { - final String jsonStr = StrUtil.trim(source); - if (StrUtil.startWith(jsonStr, '<')) { - // 可能为XML - XML.toJSONObject(this, jsonStr, false); - return; - } - initFromTokener(new JSONTokener(StrUtil.trim(source), this.config), filter); - } - - /** - * 初始化 - * - * @param x JSONTokener - * @param filter 键值对过滤编辑器,可以通过实现此接口,完成解析前对键值对的过滤和修改操作 - */ - private void initFromTokener(JSONTokener x, Filter> filter) { - JSONParser.of(x).parseTo(this, filter); - } - // ------------------------------------------------------------------------------------------------- Private method end } diff --git a/hutool-json/src/main/java/cn/hutool/json/ObjectMapper.java b/hutool-json/src/main/java/cn/hutool/json/ObjectMapper.java new file mode 100755 index 000000000..d55146e91 --- /dev/null +++ b/hutool-json/src/main/java/cn/hutool/json/ObjectMapper.java @@ -0,0 +1,236 @@ +package cn.hutool.json; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.ArrayIter; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.lang.Filter; +import cn.hutool.core.lang.mutable.Mutable; +import cn.hutool.core.lang.mutable.MutablePair; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.core.util.TypeUtil; +import cn.hutool.json.serialize.GlobalSerializeMapping; +import cn.hutool.json.serialize.JSONObjectSerializer; +import cn.hutool.json.serialize.JSONSerializer; + +import java.util.Enumeration; +import java.util.Iterator; +import java.util.Map; +import java.util.ResourceBundle; + +/** + * 对象和JSON映射器,用于转换对象为JSON,支持: + *
    + *
  • Map 转 JSONObject,将键值对加入JSON对象
  • + *
  • Map.Entry 转 JSONObject
  • + *
  • CharSequence 转 JSONObject,使用JSONTokener解析
  • + *
  • JSONTokener 转 JSONObject,直接解析
  • + *
  • ResourceBundle 转 JSONObject
  • + *
  • Bean 转 JSONObject,调用其getters方法(getXXX或者isXXX)获得值,加入到JSON对象。例如:如果JavaBean对象中有个方法getName(),值为"张三",获得的键值对为:name: "张三"
  • + *
+ * + * @author looly + * @since 5.8.0 + */ +public class ObjectMapper { + + /** + * 创建ObjectMapper + * + * @param source 来源对象 + * @return ObjectMapper + */ + public static ObjectMapper of(Object source) { + return new ObjectMapper(source); + } + + private final Object source; + + /** + * 构造 + * + * @param source 来源对象 + */ + public ObjectMapper(Object source) { + this.source = source; + } + + /** + * 将给定对象转换为{@link JSONObject} + * + * @param jsonObject 目标{@link JSONObject} + * @param filter 键值对过滤编辑器,可以通过实现此接口,完成解析前对键值对的过滤和修改操作 + */ + @SuppressWarnings({"rawtypes", "unchecked"}) + public void map(JSONObject jsonObject, Filter> filter) { + final Object source = this.source; + if (null == source) { + return; + } + + // 自定义序列化 + final JSONSerializer serializer = GlobalSerializeMapping.getSerializer(source.getClass()); + if (serializer instanceof JSONObjectSerializer) { + serializer.serialize(jsonObject, source); + return; + } + + if (ArrayUtil.isArray(source) || source instanceof JSONArray) { + // 不支持集合类型转换为JSONObject + throw new JSONException("Unsupported type [{}] to JSONObject!", source.getClass()); + } + + if (source instanceof Map) { + // Map + for (final Map.Entry e : ((Map) source).entrySet()) { + jsonObject.set(Convert.toStr(e.getKey()), e.getValue(), filter, false); + } + } else if (source instanceof Map.Entry) { + final Map.Entry entry = (Map.Entry) source; + jsonObject.set(Convert.toStr(entry.getKey()), entry.getValue(), filter, false); + } else if (source instanceof CharSequence) { + // 可能为JSON字符串 + mapFromStr((CharSequence) source, jsonObject, filter); + } else if (source instanceof JSONTokener) { + // JSONTokener + mapFromTokener((JSONTokener) source, jsonObject, filter); + } else if (source instanceof ResourceBundle) { + // JSONTokener + mapFromResourceBundle((ResourceBundle) source, jsonObject, filter); + } else if (BeanUtil.isReadableBean(source.getClass())) { + // 普通Bean + // TODO 过滤器对Bean无效,需补充。 + mapFromBean(source, jsonObject); + } else { + // 不支持对象类型转换为JSONObject + throw new JSONException("Unsupported type [{}] to JSONObject!", source.getClass()); + } + } + + /** + * 初始化 + * + * @param jsonArray 目标{@link JSONArray} + * @param filter 键值对过滤编辑器,可以通过实现此接口,完成解析前对值的过滤和修改操作,{@code null}表示不过滤 + * @throws JSONException 非数组或集合 + */ + @SuppressWarnings({"rawtypes", "unchecked"}) + public void map(JSONArray jsonArray, Filter> filter) throws JSONException { + final Object source = this.source; + if (null == source) { + return; + } + + final JSONSerializer serializer = GlobalSerializeMapping.getSerializer(source.getClass()); + if (null != serializer && JSONArray.class.equals(TypeUtil.getTypeArgument(serializer.getClass()))) { + // 自定义序列化 + serializer.serialize(jsonArray, source); + } else if (source instanceof CharSequence) { + // JSON字符串 + mapFromStr((CharSequence) source, jsonArray, filter); + } else if (source instanceof JSONTokener) { + mapFromTokener((JSONTokener) source, jsonArray, filter); + } else { + Iterator iter; + if (ArrayUtil.isArray(source)) {// 数组 + iter = new ArrayIter<>(source); + } else if (source instanceof Iterator) {// Iterator + iter = ((Iterator) source); + } else if (source instanceof Iterable) {// Iterable + iter = ((Iterable) source).iterator(); + } else { + throw new JSONException("JSONArray initial value should be a string or collection or array."); + } + + final JSONConfig config = jsonArray.getConfig(); + Object next; + while (iter.hasNext()) { + next = iter.next(); + // 检查循环引用 + if (next != source) { + jsonArray.addRaw(JSONUtil.wrap(next, config), filter); + } + } + } + } + + /** + * 从{@link ResourceBundle}转换 + * + * @param bundle ResourceBundle + * @param jsonObject {@link JSONObject} + * @param filter 键值对过滤编辑器,可以通过实现此接口,完成解析前对键值对的过滤和修改操作,{@code null}表示不过滤 + * @since 5.3.1 + */ + private static void mapFromResourceBundle(ResourceBundle bundle, JSONObject jsonObject, Filter> filter) { + Enumeration keys = bundle.getKeys(); + while (keys.hasMoreElements()) { + String key = keys.nextElement(); + if (key != null) { + InternalJSONUtil.propertyPut(jsonObject, key, bundle.getString(key), filter); + } + } + } + + /** + * 从字符串转换 + * + * @param source JSON字符串 + * @param jsonObject {@link JSONObject} + * @param filter 键值对过滤编辑器,可以通过实现此接口,完成解析前对键值对的过滤和修改操作,{@code null}表示不过滤 + */ + private static void mapFromStr(CharSequence source, JSONObject jsonObject, Filter> filter) { + final String jsonStr = StrUtil.trim(source); + if (StrUtil.startWith(jsonStr, '<')) { + // 可能为XML + XML.toJSONObject(jsonObject, jsonStr, false); + return; + } + mapFromTokener(new JSONTokener(StrUtil.trim(source), jsonObject.getConfig()), jsonObject, filter); + } + + /** + * 初始化 + * + * @param source JSON字符串 + * @param jsonArray {@link JSONArray} + * @param filter 值过滤编辑器,可以通过实现此接口,完成解析前对值的过滤和修改操作,{@code null}表示不过滤 + */ + private void mapFromStr(CharSequence source, JSONArray jsonArray, Filter> filter) { + if (null != source) { + mapFromTokener(new JSONTokener(StrUtil.trim(source), jsonArray.getConfig()), jsonArray, filter); + } + } + + /** + * 从{@link JSONTokener}转换 + * + * @param x JSONTokener + * @param jsonObject {@link JSONObject} + * @param filter 键值对过滤编辑器,可以通过实现此接口,完成解析前对键值对的过滤和修改操作 + */ + private static void mapFromTokener(JSONTokener x, JSONObject jsonObject, Filter> filter) { + JSONParser.of(x).parseTo(jsonObject, filter); + } + + /** + * 初始化 + * + * @param x {@link JSONTokener} + * @param jsonArray {@link JSONArray} + * @param filter 值过滤编辑器,可以通过实现此接口,完成解析前对值的过滤和修改操作,{@code null}表示不过滤 + */ + private static void mapFromTokener(JSONTokener x, JSONArray jsonArray, Filter> filter) { + JSONParser.of(x).parseTo(jsonArray, filter); + } + + /** + * 从Bean转换 + * + * @param bean Bean对象 + * @param jsonObject {@link JSONObject} + */ + private static void mapFromBean(Object bean, JSONObject jsonObject) { + BeanUtil.beanToMap(bean, jsonObject, InternalJSONUtil.toCopyOptions(jsonObject.getConfig())); + } +} From 5a1ae69c21b3fea42323a13615f1d87340c84261 Mon Sep 17 00:00:00 2001 From: Looly Date: Mon, 18 Apr 2022 21:50:33 +0800 Subject: [PATCH 18/52] fix code --- .../main/java/cn/hutool/json/JSONTokener.java | 6 ++-- .../java/cn/hutool/json/ObjectMapper.java | 21 ++++++++++-- .../java/cn/hutool/json/JSONObjectTest.java | 34 +++++++++++++++++++ 3 files changed, 56 insertions(+), 5 deletions(-) diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONTokener.java b/hutool-json/src/main/java/cn/hutool/json/JSONTokener.java index fbc1e58eb..08415273f 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONTokener.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONTokener.java @@ -1,11 +1,11 @@ package cn.hutool.json; +import cn.hutool.core.io.IoUtil; import cn.hutool.core.util.StrUtil; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; -import java.io.InputStreamReader; import java.io.Reader; import java.io.StringReader; @@ -67,13 +67,13 @@ public class JSONTokener { } /** - * 从InputStream中构建 + * 从InputStream中构建,使用UTF-8编码 * * @param inputStream InputStream * @param config JSON配置 */ public JSONTokener(InputStream inputStream, JSONConfig config) throws JSONException { - this(new InputStreamReader(inputStream), config); + this(IoUtil.getUtf8Reader(inputStream), config); } /** diff --git a/hutool-json/src/main/java/cn/hutool/json/ObjectMapper.java b/hutool-json/src/main/java/cn/hutool/json/ObjectMapper.java index d55146e91..6d7f9761e 100755 --- a/hutool-json/src/main/java/cn/hutool/json/ObjectMapper.java +++ b/hutool-json/src/main/java/cn/hutool/json/ObjectMapper.java @@ -3,6 +3,7 @@ package cn.hutool.json; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.collection.ArrayIter; import cn.hutool.core.convert.Convert; +import cn.hutool.core.io.IoUtil; import cn.hutool.core.lang.Filter; import cn.hutool.core.lang.mutable.Mutable; import cn.hutool.core.lang.mutable.MutablePair; @@ -13,6 +14,8 @@ import cn.hutool.json.serialize.GlobalSerializeMapping; import cn.hutool.json.serialize.JSONObjectSerializer; import cn.hutool.json.serialize.JSONSerializer; +import java.io.InputStream; +import java.io.Reader; import java.util.Enumeration; import java.util.Iterator; import java.util.Map; @@ -24,6 +27,8 @@ import java.util.ResourceBundle; *
  • Map 转 JSONObject,将键值对加入JSON对象
  • *
  • Map.Entry 转 JSONObject
  • *
  • CharSequence 转 JSONObject,使用JSONTokener解析
  • + *
  • {@link Reader} 转 JSONObject,使用JSONTokener解析
  • + *
  • {@link InputStream} 转 JSONObject,使用JSONTokener解析
  • *
  • JSONTokener 转 JSONObject,直接解析
  • *
  • ResourceBundle 转 JSONObject
  • *
  • Bean 转 JSONObject,调用其getters方法(getXXX或者isXXX)获得值,加入到JSON对象。例如:如果JavaBean对象中有个方法getName(),值为"张三",获得的键值对为:name: "张三"
  • @@ -75,7 +80,7 @@ public class ObjectMapper { return; } - if (ArrayUtil.isArray(source) || source instanceof JSONArray) { + if (source instanceof JSONArray) { // 不支持集合类型转换为JSONObject throw new JSONException("Unsupported type [{}] to JSONObject!", source.getClass()); } @@ -91,6 +96,12 @@ public class ObjectMapper { } else if (source instanceof CharSequence) { // 可能为JSON字符串 mapFromStr((CharSequence) source, jsonObject, filter); + } else if (source instanceof Reader) { + mapFromTokener(new JSONTokener((Reader) source, jsonObject.getConfig()), jsonObject, filter); + } else if (source instanceof InputStream) { + mapFromTokener(new JSONTokener((InputStream) source, jsonObject.getConfig()), jsonObject, filter); + } else if (source instanceof byte[]) { + mapFromTokener(new JSONTokener(IoUtil.toStream((byte[]) source), jsonObject.getConfig()), jsonObject, filter); } else if (source instanceof JSONTokener) { // JSONTokener mapFromTokener((JSONTokener) source, jsonObject, filter); @@ -111,7 +122,7 @@ public class ObjectMapper { * 初始化 * * @param jsonArray 目标{@link JSONArray} - * @param filter 键值对过滤编辑器,可以通过实现此接口,完成解析前对值的过滤和修改操作,{@code null}表示不过滤 + * @param filter 键值对过滤编辑器,可以通过实现此接口,完成解析前对值的过滤和修改操作,{@code null}表示不过滤 * @throws JSONException 非数组或集合 */ @SuppressWarnings({"rawtypes", "unchecked"}) @@ -128,6 +139,12 @@ public class ObjectMapper { } else if (source instanceof CharSequence) { // JSON字符串 mapFromStr((CharSequence) source, jsonArray, filter); + }else if (source instanceof Reader) { + mapFromTokener(new JSONTokener((Reader) source, jsonArray.getConfig()), jsonArray, filter); + } else if (source instanceof InputStream) { + mapFromTokener(new JSONTokener((InputStream) source, jsonArray.getConfig()), jsonArray, filter); + } else if (source instanceof byte[]) { + mapFromTokener(new JSONTokener(IoUtil.toStream((byte[]) source), jsonArray.getConfig()), jsonArray, filter); } else if (source instanceof JSONTokener) { mapFromTokener((JSONTokener) source, jsonArray, filter); } else { 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 c39956d46..10aa84e14 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java @@ -28,7 +28,10 @@ import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; +import java.io.ByteArrayInputStream; +import java.io.StringReader; import java.math.BigDecimal; +import java.nio.charset.StandardCharsets; import java.sql.Timestamp; import java.util.Date; import java.util.HashMap; @@ -143,6 +146,37 @@ public class JSONObjectTest { Assert.assertEquals(new JSONArray(), json.getJSONObject("data").getJSONArray("cards")); } + @Test + public void parseBytesTest() { + String jsonStr = "{'msg':'这里还没有内容','data':{'cards':[]},'ok':0}"; + //noinspection MismatchedQueryAndUpdateOfCollection + JSONObject json = new JSONObject(jsonStr.getBytes(StandardCharsets.UTF_8)); + Assert.assertEquals(new Integer(0), json.getInt("ok")); + Assert.assertEquals(new JSONArray(), json.getJSONObject("data").getJSONArray("cards")); + } + + @Test + public void parseReaderTest() { + String jsonStr = "{'msg':'这里还没有内容','data':{'cards':[]},'ok':0}"; + final StringReader stringReader = new StringReader(jsonStr); + + //noinspection MismatchedQueryAndUpdateOfCollection + JSONObject json = new JSONObject(stringReader); + Assert.assertEquals(new Integer(0), json.getInt("ok")); + Assert.assertEquals(new JSONArray(), json.getJSONObject("data").getJSONArray("cards")); + } + + @Test + public void parseInputStreamTest() { + String jsonStr = "{'msg':'这里还没有内容','data':{'cards':[]},'ok':0}"; + final ByteArrayInputStream in = new ByteArrayInputStream(jsonStr.getBytes(StandardCharsets.UTF_8)); + + //noinspection MismatchedQueryAndUpdateOfCollection + JSONObject json = new JSONObject(in); + Assert.assertEquals(new Integer(0), json.getInt("ok")); + Assert.assertEquals(new JSONArray(), json.getJSONObject("data").getJSONArray("cards")); + } + @Test @Ignore public void parseStringWithBomTest() { From 6885c1264f73fb7aeaec66307a981962cd36b879 Mon Sep 17 00:00:00 2001 From: Looly Date: Mon, 18 Apr 2022 21:52:44 +0800 Subject: [PATCH 19/52] add changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8bc455df6..62e550dd2 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ ### ❌不兼容特性 * 【json 】 【可能兼容问题】JSONArray删除部分构造 +* 【json 】 【可能兼容问题】JSONTokener使用InputStream作为源时,由系统编码变更为UTF-8 ### 🐣新特性 * 【core 】 BeanUtil增加toBean重载(pr#598@Gitee) From 2cd6aa50d0c5bba35630a38c42a8544b9f504d95 Mon Sep 17 00:00:00 2001 From: Looly Date: Mon, 18 Apr 2022 22:00:44 +0800 Subject: [PATCH 20/52] update dependency --- hutool-db/pom.xml | 14 +++++++------- hutool-extra/pom.xml | 6 +++--- hutool-log/pom.xml | 2 +- hutool-poi/pom.xml | 2 +- hutool-system/pom.xml | 2 +- pom.xml | 2 +- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml index fa75fee03..7180a8009 100644 --- a/hutool-db/pom.xml +++ b/hutool-db/pom.xml @@ -20,14 +20,14 @@ 0.9.5.5 2.9.0 - 10.0.16 - 1.2.8 + 10.0.20 + 1.2.9 2.4.13 - 4.5.0 + 4.5.1 3.36.0.3 2.5.2 - 4.2.1 + 4.2.2 @@ -81,7 +81,7 @@ com.github.chris2018998 beecp - 3.3.4 + 3.3.5 slf4j-api @@ -148,7 +148,7 @@ org.postgresql postgresql - 42.3.3 + 42.3.4 test @@ -166,7 +166,7 @@ com.h2database h2 - 2.1.210 + 2.1.212 test diff --git a/hutool-extra/pom.xml b/hutool-extra/pom.xml index 38ae8c1d9..7e15e0fa2 100644 --- a/hutool-extra/pom.xml +++ b/hutool-extra/pom.xml @@ -249,7 +249,7 @@ com.hankcs hanlp - portable-1.8.2 + portable-1.8.3 true @@ -411,7 +411,7 @@ com.googlecode.aviator aviator - 5.3.0 + 5.3.1 compile true @@ -446,7 +446,7 @@ org.springframework spring-expression - 5.3.18 + 5.3.19 compile true diff --git a/hutool-log/pom.xml b/hutool-log/pom.xml index 52d53c9e4..dc7ea99af 100644 --- a/hutool-log/pom.xml +++ b/hutool-log/pom.xml @@ -25,7 +25,7 @@ 1.2 1.3.6 3.4.3.Final - 0.43.0 + 0.43.2 diff --git a/hutool-poi/pom.xml b/hutool-poi/pom.xml index 2fdd173d9..b4bac8f58 100644 --- a/hutool-poi/pom.xml +++ b/hutool-poi/pom.xml @@ -45,7 +45,7 @@ org.ofdrw ofdrw-full - 1.17.13 + 1.17.14 compile true diff --git a/hutool-system/pom.xml b/hutool-system/pom.xml index 41463988d..231612c22 100644 --- a/hutool-system/pom.xml +++ b/hutool-system/pom.xml @@ -30,7 +30,7 @@ com.github.oshi oshi-core - 6.1.5 + 6.1.6 provided diff --git a/pom.xml b/pom.xml index 4d394ac24..c7126ef60 100644 --- a/pom.xml +++ b/pom.xml @@ -44,7 +44,7 @@ 8 4.13.2 - 1.18.22 + 1.18.24 From 3ef5ea6c8664ed6e43312e877a671849ae4de242 Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 19 Apr 2022 18:50:56 +0800 Subject: [PATCH 21/52] fix bug --- CHANGELOG.md | 3 ++- hutool-extra/src/main/java/cn/hutool/extra/ssh/Sftp.java | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 62e550dd2..2559566a7 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ------------------------------------------------------------------------------------------------------------- -# 5.8.0.M4 (2022-04-18) +# 5.8.0.M4 (2022-04-19) ### ❌不兼容特性 * 【json 】 【可能兼容问题】JSONArray删除部分构造 @@ -21,6 +21,7 @@ * 【core 】 修复StrUtil.firstNonX非static问题(issue#2257@Github) * 【core 】 修复SimpleCache线程安全问题 * 【core 】 修复ClassLoaderUtil中可能的关联ClassLoader错位问题 +* 【extra 】 修复Sftp错误内容解析大小写问题(issue#I53GPI@Gitee) ------------------------------------------------------------------------------------------------------------- diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ssh/Sftp.java b/hutool-extra/src/main/java/cn/hutool/extra/ssh/Sftp.java index 60d633dca..e993b0824 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/ssh/Sftp.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/ssh/Sftp.java @@ -340,7 +340,7 @@ public class Sftp extends AbstractFtp { } catch (SftpException e) { final String msg = e.getMessage(); // issue#I4P9ED@Gitee - if (msg.contains("No such file") || msg.contains("does not exist")) { + if (StrUtil.containsAnyIgnoreCase(msg, "No such file", "does not exist")) { // 文件不存在直接返回false // pr#378@Gitee return false; From befd56bbdb37020a21fe2e85b8011b75278d84b7 Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 19 Apr 2022 19:04:38 +0800 Subject: [PATCH 22/52] add test --- .../src/main/java/cn/hutool/core/date/DateUtil.java | 4 ++-- .../test/java/cn/hutool/core/date/DateUtilTest.java | 11 +++++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) 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 3eecb55c7..6fd4c4230 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 @@ -1813,7 +1813,7 @@ public class DateUtil extends CalendarUtil { /** * HH:mm:ss 时间格式字符串转为秒数
    - * 参考:https://github.com/iceroot + * 参考:https://github.com/iceroot * * @param timeStr 字符串时分秒(HH:mm:ss)格式 * @return 时分秒转换后的秒数 @@ -1836,7 +1836,7 @@ public class DateUtil extends CalendarUtil { /** * 秒数转为时间格式(HH:mm:ss)
    - * 参考:https://github.com/iceroot + * 参考:https://github.com/iceroot * * @param seconds 需要转换的秒数 * @return 转换后的字符串 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 b9de72b6f..7874b5cd9 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 @@ -1029,6 +1029,7 @@ public class DateUtilTest { } @Test + @SuppressWarnings("ConstantConditions") public void isOverlapTest() { DateTime oneStartTime = DateUtil.parse("2022-01-01 10:10:10"); DateTime oneEndTime = DateUtil.parse("2022-01-01 11:10:10"); @@ -1055,6 +1056,16 @@ public class DateUtilTest { Assert.assertFalse(DateUtil.isOverlap(realStartTime1,realEndTime1,startTime,endTime)); Assert.assertFalse(DateUtil.isOverlap(startTime,endTime,realStartTime1,realEndTime1)); + } + @Test + public void isInTest(){ + String sourceStr = "2022-04-19 00:00:00"; + String startTimeStr = "2022-04-19 00:00:00"; + String endTimeStr = "2022-04-19 23:59:59"; + boolean between = DateUtil.isIn(DateUtil.parse(startTimeStr), + DateUtil.parse(endTimeStr), + DateUtil.parse(sourceStr)); + Assert.assertTrue(between); } } From 7380d5ce9118c2ad81ffb2c80bd76ff7b0fd5a66 Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 19 Apr 2022 19:10:38 +0800 Subject: [PATCH 23/52] add test --- .../cn/hutool/core/text/CharSequenceUtilTest.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) 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 60e8d8099..2672c628b 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 @@ -148,4 +148,16 @@ public class CharSequenceUtilTest { Assert.assertEquals("ABCde", CharSequenceUtil.removeSuffixIgnoreCase("ABCde", "ABCdef")); Assert.assertNull(CharSequenceUtil.removeSuffixIgnoreCase(null, "ABCdef")); } + + @Test + public void trimToNullTest(){ + String a = " "; + Assert.assertNull(CharSequenceUtil.trimToNull(a)); + + a = ""; + Assert.assertNull(CharSequenceUtil.trimToNull(a)); + + a = null; + Assert.assertNull(CharSequenceUtil.trimToNull(a)); + } } From 126b1c76b66490d85f3265fd0fc897c59e7bc3c6 Mon Sep 17 00:00:00 2001 From: duandazhi Date: Wed, 20 Apr 2022 15:44:47 +0800 Subject: [PATCH 24/52] =?UTF-8?q?CHINESE=5FNAME=E4=B8=AD=E5=9B=BD=E4=BA=BA?= =?UTF-8?q?=E5=A7=93=E5=90=8D=EF=BC=8C=E6=AD=A3=E5=88=99=E6=94=BE=E5=AE=BD?= =?UTF-8?q?=E9=99=90=E5=88=B6=EF=BC=8C=E5=90=A6=E5=88=99=EF=BC=9A=E7=94=9F?= =?UTF-8?q?=E5=83=BB=E5=AD=97=E7=9A=84=E5=A7=93=E5=90=8D=E6=97=A0=E6=B3=95?= =?UTF-8?q?=E9=80=9A=E8=BF=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hutool-core/src/main/java/cn/hutool/core/lang/RegexPool.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/RegexPool.java b/hutool-core/src/main/java/cn/hutool/core/lang/RegexPool.java index fb092d95e..7258c79dd 100755 --- a/hutool-core/src/main/java/cn/hutool/core/lang/RegexPool.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/RegexPool.java @@ -200,6 +200,7 @@ public interface RegexPool { * ---------- * * 总结中文姓名:2-60位,只能是中文和维吾尔族的点· + * 放宽汉字范围:如生僻姓名 刘欣䶮yǎn */ - String CHINESE_NAME = "^[\u4E00-\u9FFF·]{2,60}$"; + String CHINESE_NAME = "^[\u2E80-\u9FFF·]{2,60}$"; } From d46086e0dd7c953ed5dc4cf0b5b31fed3502f754 Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 21 Apr 2022 17:39:42 +0800 Subject: [PATCH 25/52] change re --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2559566a7..8c22915e8 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,19 +3,20 @@ ------------------------------------------------------------------------------------------------------------- -# 5.8.0.M4 (2022-04-19) +# 5.8.0.M4 (2022-04-21) ### ❌不兼容特性 * 【json 】 【可能兼容问题】JSONArray删除部分构造 * 【json 】 【可能兼容问题】JSONTokener使用InputStream作为源时,由系统编码变更为UTF-8 -### 🐣新特性 +### 🐣新特性21 * 【core 】 BeanUtil增加toBean重载(pr#598@Gitee) * 【json 】 新增JSONParser * 【json 】 JSON新增在解析时的过滤方法(issue#I52O85@Gitee) * 【core 】 添加ArrayUtil.distinct、CollUtil.distinct重载(issue#2256@Github) * 【core 】 添加TransMap、FuncMap、ReferenceConcurrentMap、WeakConcurrentMap * 【json 】 添加ObjectMapper +* 【core 】 CHINESE_NAME正则条件放宽(pr#599@Gitee) ### 🐞Bug修复 * 【core 】 修复StrUtil.firstNonX非static问题(issue#2257@Github) From 49667afbe93b3484c11d6580c587d4a6274b8dbf Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 21 Apr 2022 17:53:02 +0800 Subject: [PATCH 26/52] fix doc --- hutool-http/src/main/java/cn/hutool/http/HttpRequest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java index 0d882d7db..c7257e90e 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java @@ -308,7 +308,7 @@ public class HttpRequest extends HttpBase { * 它会验证 SSL 服务器在数字证书中返回的主机名是否与用于连接 SSL 服务器的 URL 主机名相匹配。如果主机名不匹配,则删除此连接。
    * 因此weblogic不支持https的sni协议的主机名验证,此时需要将此值设置为sun.net.www.protocol.https.Handler对象。 *

    - * 相关issue见:https://gitee.com/dromara/hutool/issues/IMD1X + * 相关issue见:https://gitee.com/dromara/hutool/issues/IMD1X * * @param urlHandler {@link URLStreamHandler} * @return this From 7ad9253250856e24cbdf201e46191616cdc96332 Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 21 Apr 2022 18:02:19 +0800 Subject: [PATCH 27/52] fix doc --- hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 e6980037e..90114dc82 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 @@ -3246,7 +3246,7 @@ public class FileUtil extends PathUtil { /** * 可读的文件大小
    - * 参考 http://stackoverflow.com/questions/3263892/format-file-size-as-mb-gb-etc + * 参考 http://stackoverflow.com/questions/3263892/format-file-size-as-mb-gb-etc * * @param size Long类型大小 * @return 大小 From fe0f5527f64517985088fc188a2f9e5ee379b337 Mon Sep 17 00:00:00 2001 From: Looly Date: Sat, 23 Apr 2022 10:31:17 +0800 Subject: [PATCH 28/52] add JakartaServletUtil --- CHANGELOG.md | 5 +- hutool-aop/pom.xml | 2 +- hutool-extra/pom.xml | 9 +- .../extra/servlet/JakartaServletUtil.java | 643 ++++++++++++++++++ .../cn/hutool/extra/servlet/ServletUtil.java | 2 +- .../hutool/extra/servlet/ServletUtilTest.java | 22 +- 6 files changed, 676 insertions(+), 7 deletions(-) create mode 100755 hutool-extra/src/main/java/cn/hutool/extra/servlet/JakartaServletUtil.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c22915e8..bbbb54dfc 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,13 +3,13 @@ ------------------------------------------------------------------------------------------------------------- -# 5.8.0.M4 (2022-04-21) +# 5.8.0.M4 (2022-04-23) ### ❌不兼容特性 * 【json 】 【可能兼容问题】JSONArray删除部分构造 * 【json 】 【可能兼容问题】JSONTokener使用InputStream作为源时,由系统编码变更为UTF-8 -### 🐣新特性21 +### 🐣新特性 * 【core 】 BeanUtil增加toBean重载(pr#598@Gitee) * 【json 】 新增JSONParser * 【json 】 JSON新增在解析时的过滤方法(issue#I52O85@Gitee) @@ -17,6 +17,7 @@ * 【core 】 添加TransMap、FuncMap、ReferenceConcurrentMap、WeakConcurrentMap * 【json 】 添加ObjectMapper * 【core 】 CHINESE_NAME正则条件放宽(pr#599@Gitee) +* 【extra 】 增加JakartaServletUtil(issue#2271@Github) ### 🐞Bug修复 * 【core 】 修复StrUtil.firstNonX非static问题(issue#2257@Github) diff --git a/hutool-aop/pom.xml b/hutool-aop/pom.xml index f5f804a82..7b022cc28 100644 --- a/hutool-aop/pom.xml +++ b/hutool-aop/pom.xml @@ -19,7 +19,7 @@ 3.3.0 - 5.3.17 + 5.3.19 diff --git a/hutool-extra/pom.xml b/hutool-extra/pom.xml index 7e15e0fa2..976fa3759 100644 --- a/hutool-extra/pom.xml +++ b/hutool-extra/pom.xml @@ -53,6 +53,13 @@ provided true + + jakarta.servlet + jakarta.servlet-api + 5.0.0 + provided + true + @@ -398,7 +405,7 @@ ch.qos.logback logback-classic - 1.2.9 + 1.2.11 test diff --git a/hutool-extra/src/main/java/cn/hutool/extra/servlet/JakartaServletUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/servlet/JakartaServletUtil.java new file mode 100755 index 000000000..46e13be75 --- /dev/null +++ b/hutool-extra/src/main/java/cn/hutool/extra/servlet/JakartaServletUtil.java @@ -0,0 +1,643 @@ +package cn.hutool.extra.servlet; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.bean.copier.CopyOptions; +import cn.hutool.core.bean.copier.ValueProvider; +import cn.hutool.core.collection.ArrayIter; +import cn.hutool.core.collection.IterUtil; +import cn.hutool.core.exceptions.UtilException; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.map.CaseInsensitiveMap; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.net.NetUtil; +import cn.hutool.core.net.multipart.MultipartFormData; +import cn.hutool.core.net.multipart.UploadSetting; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.ReflectUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.core.util.URLUtil; + +import jakarta.servlet.ServletOutputStream; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.BufferedInputStream; +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintWriter; +import java.io.Writer; +import java.lang.reflect.Type; +import java.nio.charset.Charset; +import java.util.Collections; +import java.util.Date; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; + +/** + * Servlet相关工具类封装 + * + * @author looly + * @since 3.2.0 + */ +public class JakartaServletUtil { + + public static final String METHOD_DELETE = "DELETE"; + public static final String METHOD_HEAD = "HEAD"; + public static final String METHOD_GET = "GET"; + public static final String METHOD_OPTIONS = "OPTIONS"; + public static final String METHOD_POST = "POST"; + public static final String METHOD_PUT = "PUT"; + public static final String METHOD_TRACE = "TRACE"; + + // --------------------------------------------------------- getParam start + + /** + * 获得所有请求参数 + * + * @param request 请求对象{@link ServletRequest} + * @return Map + */ + public static Map getParams(ServletRequest request) { + final Map map = request.getParameterMap(); + return Collections.unmodifiableMap(map); + } + + /** + * 获得所有请求参数 + * + * @param request 请求对象{@link ServletRequest} + * @return Map + */ + public static Map getParamMap(ServletRequest request) { + Map params = new HashMap<>(); + for (Map.Entry entry : getParams(request).entrySet()) { + params.put(entry.getKey(), ArrayUtil.join(entry.getValue(), StrUtil.COMMA)); + } + return params; + } + + /** + * 获取请求体
    + * 调用该方法后,getParam方法将失效 + * + * @param request {@link ServletRequest} + * @return 获得请求体 + * @since 4.0.2 + */ + public static String getBody(ServletRequest request) { + try (final BufferedReader reader = request.getReader()) { + return IoUtil.read(reader); + } catch (IOException e) { + throw new IORuntimeException(e); + } + } + + /** + * 获取请求体byte[]
    + * 调用该方法后,getParam方法将失效 + * + * @param request {@link ServletRequest} + * @return 获得请求体byte[] + * @since 4.0.2 + */ + public static byte[] getBodyBytes(ServletRequest request) { + try { + return IoUtil.readBytes(request.getInputStream()); + } catch (IOException e) { + throw new IORuntimeException(e); + } + } + // --------------------------------------------------------- getParam end + + // --------------------------------------------------------- fillBean start + + /** + * ServletRequest 参数转Bean + * + * @param Bean类型 + * @param request ServletRequest + * @param bean Bean + * @param copyOptions 注入时的设置 + * @return Bean + * @since 3.0.4 + */ + public static T fillBean(final ServletRequest request, T bean, CopyOptions copyOptions) { + final String beanName = StrUtil.lowerFirst(bean.getClass().getSimpleName()); + return BeanUtil.fillBean(bean, new ValueProvider() { + @Override + public Object value(String key, Type valueType) { + String[] values = request.getParameterValues(key); + if (ArrayUtil.isEmpty(values)) { + values = request.getParameterValues(beanName + StrUtil.DOT + key); + if (ArrayUtil.isEmpty(values)) { + return null; + } + } + + if (1 == values.length) { + // 单值表单直接返回这个值 + return values[0]; + } else { + // 多值表单返回数组 + return values; + } + } + + @Override + public boolean containsKey(String key) { + // 对于Servlet来说,返回值null意味着无此参数 + return (null != request.getParameter(key)) || (null != request.getParameter(beanName + StrUtil.DOT + key)); + } + }, copyOptions); + } + + /** + * ServletRequest 参数转Bean + * + * @param Bean类型 + * @param request {@link ServletRequest} + * @param bean Bean + * @param isIgnoreError 是否忽略注入错误 + * @return Bean + */ + public static T fillBean(ServletRequest request, T bean, boolean isIgnoreError) { + return fillBean(request, bean, CopyOptions.create().setIgnoreError(isIgnoreError)); + } + + /** + * ServletRequest 参数转Bean + * + * @param Bean类型 + * @param request ServletRequest + * @param beanClass Bean Class + * @param isIgnoreError 是否忽略注入错误 + * @return Bean + */ + public static T toBean(ServletRequest request, Class beanClass, boolean isIgnoreError) { + return fillBean(request, ReflectUtil.newInstanceIfPossible(beanClass), isIgnoreError); + } + // --------------------------------------------------------- fillBean end + + /** + * 获取客户端IP + * + *

    + * 默认检测的Header: + * + *

    +	 * 1、X-Forwarded-For
    +	 * 2、X-Real-IP
    +	 * 3、Proxy-Client-IP
    +	 * 4、WL-Proxy-Client-IP
    +	 * 
    + * + *

    + * otherHeaderNames参数用于自定义检测的Header
    + * 需要注意的是,使用此方法获取的客户IP地址必须在Http服务器(例如Nginx)中配置头信息,否则容易造成IP伪造。 + *

    + * + * @param request 请求对象{@link HttpServletRequest} + * @param otherHeaderNames 其他自定义头文件,通常在Http服务器(例如Nginx)中配置 + * @return IP地址 + */ + public static String getClientIP(HttpServletRequest request, String... otherHeaderNames) { + String[] headers = {"X-Forwarded-For", "X-Real-IP", "Proxy-Client-IP", "WL-Proxy-Client-IP", "HTTP_CLIENT_IP", "HTTP_X_FORWARDED_FOR"}; + if (ArrayUtil.isNotEmpty(otherHeaderNames)) { + headers = ArrayUtil.addAll(headers, otherHeaderNames); + } + + return getClientIPByHeader(request, headers); + } + + /** + * 获取客户端IP + * + *

    + * headerNames参数用于自定义检测的Header
    + * 需要注意的是,使用此方法获取的客户IP地址必须在Http服务器(例如Nginx)中配置头信息,否则容易造成IP伪造。 + *

    + * + * @param request 请求对象{@link HttpServletRequest} + * @param headerNames 自定义头,通常在Http服务器(例如Nginx)中配置 + * @return IP地址 + * @since 4.4.1 + */ + public static String getClientIPByHeader(HttpServletRequest request, String... headerNames) { + String ip; + for (String header : headerNames) { + ip = request.getHeader(header); + if (false == NetUtil.isUnknown(ip)) { + return NetUtil.getMultistageReverseProxyIp(ip); + } + } + + ip = request.getRemoteAddr(); + return NetUtil.getMultistageReverseProxyIp(ip); + } + + /** + * 获得MultiPart表单内容,多用于获得上传的文件 在同一次请求中,此方法只能被执行一次! + * + * @param request {@link ServletRequest} + * @return MultipartFormData + * @throws IORuntimeException IO异常 + * @since 4.0.2 + */ + public static MultipartFormData getMultipart(ServletRequest request) throws IORuntimeException { + return getMultipart(request, new UploadSetting()); + } + + /** + * 获得multipart/form-data 表单内容
    + * 包括文件和普通表单数据
    + * 在同一次请求中,此方法只能被执行一次! + * + * @param request {@link ServletRequest} + * @param uploadSetting 上传文件的设定,包括最大文件大小、保存在内存的边界大小、临时目录、扩展名限定等 + * @return MultiPart表单 + * @throws IORuntimeException IO异常 + * @since 4.0.2 + */ + public static MultipartFormData getMultipart(ServletRequest request, UploadSetting uploadSetting) throws IORuntimeException { + final MultipartFormData formData = new MultipartFormData(uploadSetting); + try { + formData.parseRequestStream(request.getInputStream(), CharsetUtil.charset(request.getCharacterEncoding())); + } catch (IOException e) { + throw new IORuntimeException(e); + } + + return formData; + } + + // --------------------------------------------------------- Header start + + /** + * 获取请求所有的头(header)信息 + * + * @param request 请求对象{@link HttpServletRequest} + * @return header值 + * @since 4.6.2 + */ + public static Map getHeaderMap(HttpServletRequest request) { + final Map headerMap = new HashMap<>(); + + final Enumeration names = request.getHeaderNames(); + String name; + while (names.hasMoreElements()) { + name = names.nextElement(); + headerMap.put(name, request.getHeader(name)); + } + + return headerMap; + } + + + /** + * 忽略大小写获得请求header中的信息 + * + * @param request 请求对象{@link HttpServletRequest} + * @param nameIgnoreCase 忽略大小写头信息的KEY + * @return header值 + */ + public static String getHeaderIgnoreCase(HttpServletRequest request, String nameIgnoreCase) { + final Enumeration names = request.getHeaderNames(); + String name; + while (names.hasMoreElements()) { + name = names.nextElement(); + if (name != null && name.equalsIgnoreCase(nameIgnoreCase)) { + return request.getHeader(name); + } + } + + return null; + } + + /** + * 获得请求header中的信息 + * + * @param request 请求对象{@link HttpServletRequest} + * @param name 头信息的KEY + * @param charsetName 字符集 + * @return header值 + */ + public static String getHeader(HttpServletRequest request, String name, String charsetName) { + return getHeader(request, name, CharsetUtil.charset(charsetName)); + } + + /** + * 获得请求header中的信息 + * + * @param request 请求对象{@link HttpServletRequest} + * @param name 头信息的KEY + * @param charset 字符集 + * @return header值 + * @since 4.6.2 + */ + public static String getHeader(HttpServletRequest request, String name, Charset charset) { + final String header = request.getHeader(name); + if (null != header) { + return CharsetUtil.convert(header, CharsetUtil.CHARSET_ISO_8859_1, charset); + } + return null; + } + + /** + * 客户浏览器是否为IE + * + * @param request 请求对象{@link HttpServletRequest} + * @return 客户浏览器是否为IE + */ + public static boolean isIE(HttpServletRequest request) { + String userAgent = getHeaderIgnoreCase(request, "User-Agent"); + if (StrUtil.isNotBlank(userAgent)) { + //noinspection ConstantConditions + userAgent = userAgent.toUpperCase(); + return userAgent.contains("MSIE") || userAgent.contains("TRIDENT"); + } + return false; + } + + /** + * 是否为GET请求 + * + * @param request 请求对象{@link HttpServletRequest} + * @return 是否为GET请求 + */ + public static boolean isGetMethod(HttpServletRequest request) { + return METHOD_GET.equalsIgnoreCase(request.getMethod()); + } + + /** + * 是否为POST请求 + * + * @param request 请求对象{@link HttpServletRequest} + * @return 是否为POST请求 + */ + public static boolean isPostMethod(HttpServletRequest request) { + return METHOD_POST.equalsIgnoreCase(request.getMethod()); + } + + /** + * 是否为Multipart类型表单,此类型表单用于文件上传 + * + * @param request 请求对象{@link HttpServletRequest} + * @return 是否为Multipart类型表单,此类型表单用于文件上传 + */ + public static boolean isMultipart(HttpServletRequest request) { + if (false == isPostMethod(request)) { + return false; + } + + String contentType = request.getContentType(); + if (StrUtil.isBlank(contentType)) { + return false; + } + return contentType.toLowerCase().startsWith("multipart/"); + } + // --------------------------------------------------------- Header end + + // --------------------------------------------------------- Cookie start + + /** + * 获得指定的Cookie + * + * @param httpServletRequest {@link HttpServletRequest} + * @param name cookie名 + * @return Cookie对象 + */ + public static Cookie getCookie(HttpServletRequest httpServletRequest, String name) { + return readCookieMap(httpServletRequest).get(name); + } + + /** + * 将cookie封装到Map里面 + * + * @param httpServletRequest {@link HttpServletRequest} + * @return Cookie map + */ + public static Map readCookieMap(HttpServletRequest httpServletRequest) { + final Cookie[] cookies = httpServletRequest.getCookies(); + if (ArrayUtil.isEmpty(cookies)) { + return MapUtil.empty(); + } + + return IterUtil.toMap( + new ArrayIter<>(httpServletRequest.getCookies()), + new CaseInsensitiveMap<>(), + Cookie::getName); + } + + /** + * 设定返回给客户端的Cookie + * + * @param response 响应对象{@link HttpServletResponse} + * @param cookie Servlet Cookie对象 + */ + public static void addCookie(HttpServletResponse response, Cookie cookie) { + response.addCookie(cookie); + } + + /** + * 设定返回给客户端的Cookie + * + * @param response 响应对象{@link HttpServletResponse} + * @param name Cookie名 + * @param value Cookie值 + */ + public static void addCookie(HttpServletResponse response, String name, String value) { + response.addCookie(new Cookie(name, value)); + } + + /** + * 设定返回给客户端的Cookie + * + * @param response 响应对象{@link HttpServletResponse} + * @param name cookie名 + * @param value cookie值 + * @param maxAgeInSeconds -1: 关闭浏览器清除Cookie. 0: 立即清除Cookie. >0 : Cookie存在的秒数. + * @param path Cookie的有效路径 + * @param domain the domain name within which this cookie is visible; form is according to RFC 2109 + */ + public static void addCookie(HttpServletResponse response, String name, String value, int maxAgeInSeconds, String path, String domain) { + Cookie cookie = new Cookie(name, value); + if (domain != null) { + cookie.setDomain(domain); + } + cookie.setMaxAge(maxAgeInSeconds); + cookie.setPath(path); + addCookie(response, cookie); + } + + /** + * 设定返回给客户端的Cookie
    + * Path: "/"
    + * No Domain + * + * @param response 响应对象{@link HttpServletResponse} + * @param name cookie名 + * @param value cookie值 + * @param maxAgeInSeconds -1: 关闭浏览器清除Cookie. 0: 立即清除Cookie. >0 : Cookie存在的秒数. + */ + public static void addCookie(HttpServletResponse response, String name, String value, int maxAgeInSeconds) { + addCookie(response, name, value, maxAgeInSeconds, "/", null); + } + + // --------------------------------------------------------- Cookie end + // --------------------------------------------------------- Response start + + /** + * 获得PrintWriter + * + * @param response 响应对象{@link HttpServletResponse} + * @return 获得PrintWriter + * @throws IORuntimeException IO异常 + */ + public static PrintWriter getWriter(HttpServletResponse response) throws IORuntimeException { + try { + return response.getWriter(); + } catch (IOException e) { + throw new IORuntimeException(e); + } + } + + /** + * 返回数据给客户端 + * + * @param response 响应对象{@link HttpServletResponse} + * @param text 返回的内容 + * @param contentType 返回的类型 + */ + public static void write(HttpServletResponse response, String text, String contentType) { + response.setContentType(contentType); + Writer writer = null; + try { + writer = response.getWriter(); + writer.write(text); + writer.flush(); + } catch (IOException e) { + throw new UtilException(e); + } finally { + IoUtil.close(writer); + } + } + + /** + * 返回文件给客户端 + * + * @param response 响应对象{@link HttpServletResponse} + * @param file 写出的文件对象 + * @since 4.1.15 + */ + public static void write(HttpServletResponse response, File file) { + final String fileName = file.getName(); + final String contentType = ObjectUtil.defaultIfNull(FileUtil.getMimeType(fileName), "application/octet-stream"); + BufferedInputStream in = null; + try { + in = FileUtil.getInputStream(file); + write(response, in, contentType, fileName); + } finally { + IoUtil.close(in); + } + } + + /** + * 返回数据给客户端 + * + * @param response 响应对象{@link HttpServletResponse} + * @param in 需要返回客户端的内容 + * @param contentType 返回的类型,可以使用{@link FileUtil#getMimeType(String)}获取对应扩展名的MIME信息 + *
      + *
    • application/pdf
    • + *
    • application/vnd.ms-excel
    • + *
    • application/msword
    • + *
    • application/vnd.ms-powerpoint
    • + *
    + * docx、xlsx 这种 office 2007 格式 设置 MIME;网页里面docx 文件是没问题,但是下载下来了之后就变成doc格式了 + * 参考:https://my.oschina.net/shixiaobao17145/blog/32489 + *
      + *
    • MIME_EXCELX_TYPE = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
    • + *
    • MIME_PPTX_TYPE = "application/vnd.openxmlformats-officedocument.presentationml.presentation";
    • + *
    • MIME_WORDX_TYPE = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
    • + *
    • MIME_STREAM_TYPE = "application/octet-stream;charset=utf-8"; #原始字节流
    • + *
    + * @param fileName 文件名,自动添加双引号 + * @since 4.1.15 + */ + public static void write(HttpServletResponse response, InputStream in, String contentType, String fileName) { + final String charset = ObjectUtil.defaultIfNull(response.getCharacterEncoding(), CharsetUtil.UTF_8); + response.setHeader("Content-Disposition", StrUtil.format("attachment;filename=\"{}\"", + URLUtil.encode(fileName, CharsetUtil.charset(charset)))); + response.setContentType(contentType); + write(response, in); + } + + /** + * 返回数据给客户端 + * + * @param response 响应对象{@link HttpServletResponse} + * @param in 需要返回客户端的内容 + * @param contentType 返回的类型 + */ + public static void write(HttpServletResponse response, InputStream in, String contentType) { + response.setContentType(contentType); + write(response, in); + } + + /** + * 返回数据给客户端 + * + * @param response 响应对象{@link HttpServletResponse} + * @param in 需要返回客户端的内容 + */ + public static void write(HttpServletResponse response, InputStream in) { + write(response, in, IoUtil.DEFAULT_BUFFER_SIZE); + } + + /** + * 返回数据给客户端 + * + * @param response 响应对象{@link HttpServletResponse} + * @param in 需要返回客户端的内容 + * @param bufferSize 缓存大小 + */ + public static void write(HttpServletResponse response, InputStream in, int bufferSize) { + ServletOutputStream out = null; + try { + out = response.getOutputStream(); + IoUtil.copy(in, out, bufferSize); + } catch (IOException e) { + throw new UtilException(e); + } finally { + IoUtil.close(out); + IoUtil.close(in); + } + } + + /** + * 设置响应的Header + * + * @param response 响应对象{@link HttpServletResponse} + * @param name 名 + * @param value 值,可以是String,Date, int + */ + public static void setHeader(HttpServletResponse response, String name, Object value) { + if (value instanceof String) { + response.setHeader(name, (String) value); + } else if (Date.class.isAssignableFrom(value.getClass())) { + response.setDateHeader(name, ((Date) value).getTime()); + } else if (value instanceof Integer || "int".equalsIgnoreCase(value.getClass().getSimpleName())) { + response.setIntHeader(name, (int) value); + } else { + response.setHeader(name, value.toString()); + } + } + // --------------------------------------------------------- Response end +} diff --git a/hutool-extra/src/main/java/cn/hutool/extra/servlet/ServletUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/servlet/ServletUtil.java index 5c1391b17..e76942fd2 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/servlet/ServletUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/servlet/ServletUtil.java @@ -561,7 +561,7 @@ public class ServletUtil { *
  • application/vnd.ms-powerpoint
  • * * docx、xlsx 这种 office 2007 格式 设置 MIME;网页里面docx 文件是没问题,但是下载下来了之后就变成doc格式了 - * 参考:https://my.oschina.net/shixiaobao17145/blog/32489 + * 参考:https://my.oschina.net/shixiaobao17145/blog/32489 *
      *
    • MIME_EXCELX_TYPE = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
    • *
    • MIME_PPTX_TYPE = "application/vnd.openxmlformats-officedocument.presentationml.presentation";
    • diff --git a/hutool-extra/src/test/java/cn/hutool/extra/servlet/ServletUtilTest.java b/hutool-extra/src/test/java/cn/hutool/extra/servlet/ServletUtilTest.java index 82dd8651e..444b3d9fb 100644 --- a/hutool-extra/src/test/java/cn/hutool/extra/servlet/ServletUtilTest.java +++ b/hutool-extra/src/test/java/cn/hutool/extra/servlet/ServletUtilTest.java @@ -1,5 +1,6 @@ package cn.hutool.extra.servlet; +import cn.hutool.core.util.StrUtil; import org.junit.Ignore; import org.junit.Test; @@ -11,8 +12,8 @@ import java.nio.charset.StandardCharsets; * ServletUtil工具类测试 * * @author dazer - * @date 2021/3/24 15:02 * @see ServletUtil + * @see JakartaServletUtil */ public class ServletUtilTest { @@ -20,7 +21,7 @@ public class ServletUtilTest { @Ignore public void writeTest() { HttpServletResponse response = null; - byte[] bytes = "地球是我们共同的家园,需要大家珍惜.".getBytes(StandardCharsets.UTF_8); + byte[] bytes = StrUtil.utf8Bytes("地球是我们共同的家园,需要大家珍惜."); //下载文件 // 这里没法直接测试,直接写到这里,方便调用; @@ -32,4 +33,21 @@ public class ServletUtilTest { ServletUtil.write(response, new ByteArrayInputStream(bytes), contentType, fileName); } } + + @Test + @Ignore + public void jakartaWriteTest() { + jakarta.servlet.http.HttpServletResponse response = null; + byte[] bytes = StrUtil.utf8Bytes("地球是我们共同的家园,需要大家珍惜."); + + //下载文件 + // 这里没法直接测试,直接写到这里,方便调用; + //noinspection ConstantConditions + if (response != null) { + String fileName = "签名文件.pdf"; + String contentType = "application/pdf";// application/octet-stream、image/jpeg、image/gif + response.setCharacterEncoding(StandardCharsets.UTF_8.name()); // 必须设置否则乱码; 但是 safari乱码 + JakartaServletUtil.write(response, new ByteArrayInputStream(bytes), contentType, fileName); + } + } } From 4e12aa52c9ed1d843110b8dd3d56e2cc14285fde Mon Sep 17 00:00:00 2001 From: Looly Date: Sun, 24 Apr 2022 10:59:02 +0800 Subject: [PATCH 29/52] add test --- .../cn/hutool/poi/excel/IssueI53OSTTest.java | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100755 hutool-poi/src/test/java/cn/hutool/poi/excel/IssueI53OSTTest.java diff --git a/hutool-poi/src/test/java/cn/hutool/poi/excel/IssueI53OSTTest.java b/hutool-poi/src/test/java/cn/hutool/poi/excel/IssueI53OSTTest.java new file mode 100755 index 000000000..0609d18f2 --- /dev/null +++ b/hutool-poi/src/test/java/cn/hutool/poi/excel/IssueI53OSTTest.java @@ -0,0 +1,35 @@ +package cn.hutool.poi.excel; + +import cn.hutool.core.lang.Console; +import org.junit.Ignore; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Sax方式读取合并单元格,只有第一个单元格有值,其余为null + */ +public class IssueI53OSTTest { + + @Test + @Ignore + public void readTest(){ + Map result = new HashMap<>(); + List header = new ArrayList<>(); + + ExcelUtil.readBySax("d:/test/sax_merge.xlsx", -1, (sheetIndex, rowIndex, rowCells) -> { + if(rowIndex == 0){ + header.addAll(rowCells); + return; + } + for (int i = 0; i < rowCells.size(); i++) { + result.put((String) header.get(i), rowCells.get(i)); + } + Console.log(result); + result.clear(); + }); + } +} From 913e97c2728d906057450bec796e44ee54390bcc Mon Sep 17 00:00:00 2001 From: Looly Date: Sun, 24 Apr 2022 11:37:37 +0800 Subject: [PATCH 30/52] add tesrt --- .../java/cn/hutool/core/bean/BeanUtilTest.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/hutool-core/src/test/java/cn/hutool/core/bean/BeanUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/bean/BeanUtilTest.java index 6e9a687b7..89de82f85 100644 --- a/hutool-core/src/test/java/cn/hutool/core/bean/BeanUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/bean/BeanUtilTest.java @@ -777,4 +777,20 @@ public class BeanUtilTest { public static class Test2 { private List strList; } + + @Test + public void issuesI53O9JTest(){ + Map map = new HashMap<>(); + map.put("statusIdUpdateTime", ""); + + WkCrmCustomer customer = new WkCrmCustomer(); + BeanUtil.copyProperties(map, customer); + + Assert.assertNull(customer.getStatusIdUpdateTime()); + } + + @Data + public static class WkCrmCustomer{ + private LocalDateTime statusIdUpdateTime; + } } From 37f53e985687bcd2116ad109b066050448292057 Mon Sep 17 00:00:00 2001 From: Looly Date: Sun, 24 Apr 2022 13:36:23 +0800 Subject: [PATCH 31/52] fix dumplicate bug --- .../java/cn/hutool/poi/excel/ExcelWriter.java | 57 ++++++++++++------- 1 file changed, 35 insertions(+), 22 deletions(-) diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelWriter.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelWriter.java index f2320a141..84bd7b77e 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelWriter.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelWriter.java @@ -8,6 +8,8 @@ import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.io.IoUtil; import cn.hutool.core.lang.Assert; import cn.hutool.core.map.MapUtil; +import cn.hutool.core.map.multi.RowKeyTable; +import cn.hutool.core.map.multi.Table; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.StrUtil; @@ -39,7 +41,6 @@ import java.util.Comparator; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; -import java.util.Map.Entry; import java.util.Set; import java.util.TreeMap; import java.util.concurrent.ConcurrentHashMap; @@ -1045,24 +1046,35 @@ public class ExcelWriter extends ExcelBase { return passCurrentRow(); } - final Map aliasMap = aliasMap(rowMap); - + final Table aliasTable = aliasTable(rowMap); if (isWriteKeyAsHead) { - writeHeadRow(aliasMap.keySet()); + // 写出标题行,并记录标题别名和列号的关系 + writeHeadRow(aliasTable.columnKeySet()); + // 记录原数据key对应列号 + int i = 0; + for (Object key : aliasTable.rowKeySet()) { + this.headLocationCache.putIfAbsent(StrUtil.toString(key), i); + i++; + } } // 如果已经写出标题行,根据标题行找对应的值写入 if (MapUtil.isNotEmpty(this.headLocationCache)) { final Row row = RowUtil.getOrCreateRow(this.sheet, this.currentRow.getAndIncrement()); Integer location; - for (Entry entry : aliasMap.entrySet()) { - location = this.headLocationCache.get(StrUtil.toString(entry.getKey())); + for (Table.Cell cell : aliasTable) { + // 首先查找原名对应的列号 + location = this.headLocationCache.get(StrUtil.toString(cell.getRowKey())); + if(null == location){ + // 未找到,则查找别名对应的列号 + location = this.headLocationCache.get(StrUtil.toString(cell.getColumnKey())); + } if (null != location) { - CellUtil.setCellValue(CellUtil.getOrCreateCell(row, location), entry.getValue(), this.styleSet, false); + CellUtil.setCellValue(CellUtil.getOrCreateCell(row, location), cell.getValue(), this.styleSet, false); } } } else { - writeRow(aliasMap.values()); + writeRow(aliasTable.values()); } return this; } @@ -1328,23 +1340,24 @@ public class ExcelWriter extends ExcelBase { * @param rowMap 一行数据 * @return 别名列表 */ - private Map aliasMap(Map rowMap) { + private Table aliasTable(Map rowMap) { + final Table filteredTable = new RowKeyTable<>(new LinkedHashMap<>(), LinkedHashMap::new); if (MapUtil.isEmpty(this.headerAlias)) { - return rowMap; + rowMap.forEach((key, value)-> filteredTable.put(key, key, value)); + }else{ + rowMap.forEach((key, value)->{ + final String aliasName = this.headerAlias.get(StrUtil.toString(key)); + if (null != aliasName) { + // 别名键值对加入 + filteredTable.put(key, aliasName, value); + } else if (false == this.onlyAlias) { + // 保留无别名设置的键值对 + filteredTable.put(key, key, value); + } + }); } - final Map filteredMap = MapUtil.newHashMap(rowMap.size(), true); - rowMap.forEach((key, value)->{ - final String aliasName = this.headerAlias.get(StrUtil.toString(key)); - if (null != aliasName) { - // 别名键值对加入 - filteredMap.put(aliasName, value); - } else if (false == this.onlyAlias) { - // 保留无别名设置的键值对 - filteredMap.put(key, value); - } - }); - return filteredMap; + return filteredTable; } /** From ca03fb3411997389a66f2bacc496c34fb715272d Mon Sep 17 00:00:00 2001 From: Looly Date: Sun, 24 Apr 2022 14:21:20 +0800 Subject: [PATCH 32/52] add test --- CHANGELOG.md | 3 +- .../java/cn/hutool/core/map/TableMap.java | 24 ++++++- .../cn/hutool/core/map/multi/RowKeyTable.java | 12 ++++ .../java/cn/hutool/core/map/multi/Table.java | 22 ++++++ .../java/cn/hutool/poi/excel/ExcelWriter.java | 5 +- .../cn/hutool/poi/excel/Issue2221Test.java | 70 ++++++++++++++++++- 6 files changed, 130 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bbbb54dfc..619887e0d 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ------------------------------------------------------------------------------------------------------------- -# 5.8.0.M4 (2022-04-23) +# 5.8.0.M4 (2022-04-24) ### ❌不兼容特性 * 【json 】 【可能兼容问题】JSONArray删除部分构造 @@ -18,6 +18,7 @@ * 【json 】 添加ObjectMapper * 【core 】 CHINESE_NAME正则条件放宽(pr#599@Gitee) * 【extra 】 增加JakartaServletUtil(issue#2271@Github) +* 【poi 】 ExcelWriter支持重复别名的数据写出(issue#I53APY@Gitee) ### 🐞Bug修复 * 【core 】 修复StrUtil.firstNonX非static问题(issue#2257@Github) diff --git a/hutool-core/src/main/java/cn/hutool/core/map/TableMap.java b/hutool-core/src/main/java/cn/hutool/core/map/TableMap.java index f397b043a..326637a0a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/TableMap.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/TableMap.java @@ -27,9 +27,18 @@ import java.util.Set; public class TableMap implements Map, Iterable>, Serializable { private static final long serialVersionUID = 1L; + private static final int DEFAULT_CAPACITY = 10; + private final List keys; private final List values; + /** + * 构造 + */ + public TableMap() { + this(DEFAULT_CAPACITY); + } + /** * 构造 * @@ -85,11 +94,12 @@ public class TableMap implements Map, Iterable>, Ser /** * 根据value获得对应的key,只返回找到的第一个value对应的key值 + * * @param value 值 * @return 键 * @since 5.3.3 */ - public K getKey(V value){ + public K getKey(V value) { final int index = values.indexOf(value); if (index > -1 && index < keys.size()) { return keys.get(index); @@ -160,7 +170,17 @@ public class TableMap implements Map, Iterable>, Ser @Override public Set keySet() { - return new HashSet<>(keys); + return new HashSet<>(this.keys); + } + + /** + * 获取所有键,可重复,不可修改 + * + * @return 键列表 + * @since 5.8.0 + */ + public List keys() { + return Collections.unmodifiableList(this.keys); } @Override diff --git a/hutool-core/src/main/java/cn/hutool/core/map/multi/RowKeyTable.java b/hutool-core/src/main/java/cn/hutool/core/map/multi/RowKeyTable.java index 8c55c8c93..0b4bdda7e 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/multi/RowKeyTable.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/multi/RowKeyTable.java @@ -9,8 +9,11 @@ import cn.hutool.core.map.MapUtil; import java.util.AbstractMap; import java.util.AbstractSet; +import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; import java.util.Iterator; +import java.util.List; import java.util.Map; import java.util.Set; @@ -198,6 +201,15 @@ public class RowKeyTable extends AbsTable { //endregion //region getColumn + @Override + public List columnKeys() { + final Collection> values = this.raw.values(); + final List result = new ArrayList<>(values.size() * 16); + for (Map map : values) { + map.forEach((key, value)->{result.add(key);}); + } + return result; + } @Override public Map getColumn(C columnKey) { diff --git a/hutool-core/src/main/java/cn/hutool/core/map/multi/Table.java b/hutool-core/src/main/java/cn/hutool/core/map/multi/Table.java index e0fb1c5f4..d60905e29 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/multi/Table.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/multi/Table.java @@ -1,10 +1,13 @@ package cn.hutool.core.map.multi; +import cn.hutool.core.collection.ListUtil; import cn.hutool.core.lang.Opt; import cn.hutool.core.lang.func.Consumer3; import cn.hutool.core.map.MapUtil; +import java.util.ArrayList; import java.util.Collection; +import java.util.List; import java.util.Map; import java.util.Set; @@ -101,6 +104,25 @@ public interface Table extends Iterable> { return Opt.ofNullable(columnMap()).map(Map::keySet).get(); } + /** + * 返回所有列的key,列的key如果实现Map是可重复key,则返回对应不去重的List。 + * + * @return 列set + * @since 5.8.0 + */ + default List columnKeys() { + final Map> columnMap = columnMap(); + if(MapUtil.isEmpty(columnMap)){ + return ListUtil.empty(); + } + + final List result = new ArrayList<>(columnMap.size()); + for (Map.Entry> cMapEntry : columnMap.entrySet()) { + result.add(cMapEntry.getKey()); + } + return result; + } + /** * 返回列-行对应的map * diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelWriter.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelWriter.java index 84bd7b77e..7087c14c0 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelWriter.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelWriter.java @@ -8,6 +8,7 @@ import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.io.IoUtil; import cn.hutool.core.lang.Assert; import cn.hutool.core.map.MapUtil; +import cn.hutool.core.map.TableMap; import cn.hutool.core.map.multi.RowKeyTable; import cn.hutool.core.map.multi.Table; import cn.hutool.core.util.CharsetUtil; @@ -1049,7 +1050,7 @@ public class ExcelWriter extends ExcelBase { final Table aliasTable = aliasTable(rowMap); if (isWriteKeyAsHead) { // 写出标题行,并记录标题别名和列号的关系 - writeHeadRow(aliasTable.columnKeySet()); + writeHeadRow(aliasTable.columnKeys()); // 记录原数据key对应列号 int i = 0; for (Object key : aliasTable.rowKeySet()) { @@ -1341,7 +1342,7 @@ public class ExcelWriter extends ExcelBase { * @return 别名列表 */ private Table aliasTable(Map rowMap) { - final Table filteredTable = new RowKeyTable<>(new LinkedHashMap<>(), LinkedHashMap::new); + final Table filteredTable = new RowKeyTable<>(new LinkedHashMap<>(), TableMap::new); if (MapUtil.isEmpty(this.headerAlias)) { rowMap.forEach((key, value)-> filteredTable.put(key, key, value)); }else{ diff --git a/hutool-poi/src/test/java/cn/hutool/poi/excel/Issue2221Test.java b/hutool-poi/src/test/java/cn/hutool/poi/excel/Issue2221Test.java index 241cb3424..8b26a38eb 100644 --- a/hutool-poi/src/test/java/cn/hutool/poi/excel/Issue2221Test.java +++ b/hutool-poi/src/test/java/cn/hutool/poi/excel/Issue2221Test.java @@ -2,6 +2,10 @@ package cn.hutool.poi.excel; import cn.hutool.core.collection.ListUtil; import cn.hutool.core.map.MapUtil; +import cn.hutool.poi.excel.style.StyleUtil; +import org.apache.poi.ss.usermodel.FillPatternType; +import org.apache.poi.ss.usermodel.Font; +import org.apache.poi.ss.usermodel.IndexedColors; import org.junit.Ignore; import org.junit.Test; @@ -10,9 +14,12 @@ import java.util.Map; public class Issue2221Test { + /** + * 设置重复别名的时候,通过原key获取写出位置 + */ @Test @Ignore - public void writeDuplicateHeaderAliasTest(){ + public void writeDuplicateHeaderAliasTest() { final ExcelWriter writer = ExcelUtil.getWriter("d:/test/duplicateAlias.xlsx"); // 设置别名 writer.addHeaderAlias("androidLc", "安卓"); @@ -28,4 +35,65 @@ public class Issue2221Test { writer.write(data, true); writer.close(); } + + @Test + @Ignore + public void writeDuplicateHeaderAliasTest2(){ + // 获取写Excel的流 + ExcelWriter writer = ExcelUtil.getBigWriter("d:/test/duplicateAlias2.xlsx"); + + // 设置头部的背景颜色 + StyleUtil.setColor(writer.getHeadCellStyle(), IndexedColors.GREY_50_PERCENT, FillPatternType.SOLID_FOREGROUND); + + //设置全局字体 + Font font = writer.createFont(); + font.setFontName("Microsoft YaHei"); + writer.getStyleSet().setFont(font, false); + + // 设置头部的字体为白颜色 + Font headerFont = writer.createFont(); + headerFont.setColor(IndexedColors.WHITE.getIndex()); + writer.getHeadCellStyle().setFont(headerFont); + + // 跳过多少行 + writer.passRows(1); + + // 冻结多少行 + writer.setFreezePane(2); + + // 设置别名 + writer.addHeaderAlias("date", "日期"); + writer.addHeaderAlias("androidLc", "安卓"); + writer.addHeaderAlias("iosLc", "iOS"); + writer.addHeaderAlias("androidAc", " 安卓"); + writer.addHeaderAlias("iosAc", " iOS"); + writer.setOnlyAlias(true); + + // 设置合并的单元格 + writer.merge(0, 1, 0, 0, "日期", true); + writer.merge(0, 0, 1, 2, "运行次数", true); + writer.merge(0, 0, 3, 4, "新增人数", true); + + // 写入数据 + List> data = ListUtil.of( + MapUtil.ofEntries( + MapUtil.entry("date", "2022-01-01"), + MapUtil.entry("androidLc", "1次"), + MapUtil.entry("iosLc", "2次"), + MapUtil.entry("androidAc", "3次"), + MapUtil.entry("iosAc", "4人")), + MapUtil.ofEntries( + MapUtil.entry("date", "2022-01-02"), + MapUtil.entry("androidLc", "5次"), + MapUtil.entry("iosLc", "6次"), + MapUtil.entry("androidAc", "7次"), + MapUtil.entry("iosAc", "8人")) + ); + + // 自动尺寸 + writer.autoSizeColumnAll(); + + writer.write(data, true); + writer.close(); + } } From 1bed8794997bed092b12b190dec5c1f4c890f155 Mon Sep 17 00:00:00 2001 From: Looly Date: Mon, 25 Apr 2022 13:13:11 +0800 Subject: [PATCH 33/52] add Hashids --- CHANGELOG.md | 3 +- .../java/cn/hutool/core/codec/Hashids.java | 506 ++++++++++++++++++ .../core/lang/reflect/LookupFactory.java | 4 +- .../cn/hutool/core/codec/HashidsTest.java | 20 + 4 files changed, 529 insertions(+), 4 deletions(-) create mode 100755 hutool-core/src/main/java/cn/hutool/core/codec/Hashids.java create mode 100755 hutool-core/src/test/java/cn/hutool/core/codec/HashidsTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 619887e0d..0debc4469 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ------------------------------------------------------------------------------------------------------------- -# 5.8.0.M4 (2022-04-24) +# 5.8.0.M4 (2022-04-25) ### ❌不兼容特性 * 【json 】 【可能兼容问题】JSONArray删除部分构造 @@ -19,6 +19,7 @@ * 【core 】 CHINESE_NAME正则条件放宽(pr#599@Gitee) * 【extra 】 增加JakartaServletUtil(issue#2271@Github) * 【poi 】 ExcelWriter支持重复别名的数据写出(issue#I53APY@Gitee) +* 【core 】 增加Hashids(issue#I53APY@Gitee) ### 🐞Bug修复 * 【core 】 修复StrUtil.firstNonX非static问题(issue#2257@Github) diff --git a/hutool-core/src/main/java/cn/hutool/core/codec/Hashids.java b/hutool-core/src/main/java/cn/hutool/core/codec/Hashids.java new file mode 100755 index 000000000..95c97def7 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/codec/Hashids.java @@ -0,0 +1,506 @@ +package cn.hutool.core.codec; + +import java.math.BigInteger; +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.LongStream; + +/** + * Hashids 协议实现,以实现: + *
        + *
      • 生成简短、唯一、大小写敏感并无序的hash值
      • + *
      • 自然数字的Hash值
      • + *
      • 可以设置不同的盐,具有保密性
      • + *
      • 可配置的hash长度
      • + *
      • 递增的输入产生的输出无法预测
      • + *
      + * + *

      + * 来自:https://github.com/davidafsilva/java-hashids + *

      + * + *

      + * {@code Hashids}可以将数字或者16进制字符串转为短且唯一不连续的字符串,采用双向编码实现,比如,它可以将347之类的数字转换为yr8之类的字符串,也可以将yr8之类的字符串重新解码为347之类的数字。
      + * 此编码算法主要是解决爬虫类应用对连续ID爬取问题,将有序的ID转换为无序的Hashids,而且一一对应。 + *

      + * + * @author david + */ +public class Hashids implements Encoder, Decoder { + + private static final int LOTTERY_MOD = 100; + private static final double GUARD_THRESHOLD = 12; + private static final double SEPARATOR_THRESHOLD = 3.5; + // 最小编解码字符串 + private static final int MIN_ALPHABET_LENGTH = 16; + private static final Pattern HEX_VALUES_PATTERN = Pattern.compile("[\\w\\W]{1,12}"); + + // 默认编解码字符串 + public static final char[] DEFAULT_ALPHABET = { + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + '1', '2', '3', '4', '5', '6', '7', '8', '9', '0' + }; + // 默认分隔符 + private static final char[] DEFAULT_SEPARATORS = { + 'c', 'f', 'h', 'i', 's', 't', 'u', 'C', 'F', 'H', 'I', 'S', 'T', 'U' + }; + + // algorithm properties + private final char[] alphabet; + // 多个数字编解码的分界符 + private final char[] separators; + private final Set separatorsSet; + private final char[] salt; + // 补齐至 minLength 长度添加的字符列表 + private final char[] guards; + // 编码后最小的字符长度 + private final int minLength; + + // region create + + /** + * 根据参数值,创建{@code Hashids},使用默认{@link #DEFAULT_ALPHABET}作为字母表,不限制最小长度 + * + * @param salt 加盐值 + * @return {@code Hashids} + */ + public static Hashids create(final char[] salt) { + return create(salt, DEFAULT_ALPHABET, -1); + } + + /** + * 根据参数值,创建{@code Hashids},使用默认{@link #DEFAULT_ALPHABET}作为字母表 + * + * @param salt 加盐值 + * @param minLength 限制最小长度,-1表示不限制 + * @return {@code Hashids} + */ + public static Hashids create(final char[] salt, final int minLength) { + return create(salt, DEFAULT_ALPHABET, minLength); + } + + /** + * 根据参数值,创建{@code Hashids} + * + * @param salt 加盐值 + * @param alphabet hash字母表 + * @param minLength 限制最小长度,-1表示不限制 + * @return {@code Hashids} + */ + public static Hashids create(final char[] salt, final char[] alphabet, final int minLength) { + return new Hashids(salt, alphabet, minLength); + } + // endregion + + /** + * 构造 + * + * @param salt 加盐值 + * @param alphabet hash字母表 + * @param minLength 限制最小长度,-1表示不限制 + */ + public Hashids(final char[] salt, final char[] alphabet, final int minLength) { + this.minLength = minLength; + this.salt = Arrays.copyOf(salt, salt.length); + + // filter and shuffle separators + char[] tmpSeparators = shuffle(filterSeparators(DEFAULT_SEPARATORS, alphabet), this.salt); + + // validate and filter the alphabet + char[] tmpAlphabet = validateAndFilterAlphabet(alphabet, tmpSeparators); + + // check separator threshold + if (tmpSeparators.length == 0 || + ((double) (tmpAlphabet.length / tmpSeparators.length)) > SEPARATOR_THRESHOLD) { + final int minSeparatorsSize = (int) Math.ceil(tmpAlphabet.length / SEPARATOR_THRESHOLD); + // check minimum size of separators + if (minSeparatorsSize > tmpSeparators.length) { + // fill separators from alphabet + final int missingSeparators = minSeparatorsSize - tmpSeparators.length; + tmpSeparators = Arrays.copyOf(tmpSeparators, tmpSeparators.length + missingSeparators); + System.arraycopy(tmpAlphabet, 0, tmpSeparators, + tmpSeparators.length - missingSeparators, missingSeparators); + System.arraycopy(tmpAlphabet, 0, tmpSeparators, + tmpSeparators.length - missingSeparators, missingSeparators); + tmpAlphabet = Arrays.copyOfRange(tmpAlphabet, missingSeparators, tmpAlphabet.length); + } + } + + // shuffle the current alphabet + shuffle(tmpAlphabet, this.salt); + + // check guards + this.guards = new char[(int) Math.ceil(tmpAlphabet.length / GUARD_THRESHOLD)]; + if (alphabet.length < 3) { + System.arraycopy(tmpSeparators, 0, guards, 0, guards.length); + this.separators = Arrays.copyOfRange(tmpSeparators, guards.length, tmpSeparators.length); + this.alphabet = tmpAlphabet; + } else { + System.arraycopy(tmpAlphabet, 0, guards, 0, guards.length); + this.separators = tmpSeparators; + this.alphabet = Arrays.copyOfRange(tmpAlphabet, guards.length, tmpAlphabet.length); + } + + // create the separators set + separatorsSet = IntStream.range(0, separators.length) + .mapToObj(idx -> separators[idx]) + .collect(Collectors.toSet()); + } + + /** + * 编码给定的16进制数字 + * + * @param hexNumbers 16进制数字 + * @return 编码后的值, {@code null} if {@code numbers} 是 {@code null}. + * @throws IllegalArgumentException 数字不支持抛出此异常 + */ + public String encodeFromHex(final String hexNumbers) { + if (hexNumbers == null) { + return null; + } + + // remove the prefix, if present + final String hex = hexNumbers.startsWith("0x") || hexNumbers.startsWith("0X") ? + hexNumbers.substring(2) : hexNumbers; + + // get the associated long value and encode it + LongStream values = LongStream.empty(); + final Matcher matcher = HEX_VALUES_PATTERN.matcher(hex); + while (matcher.find()) { + final long value = new BigInteger("1" + matcher.group(), 16).longValue(); + values = LongStream.concat(values, LongStream.of(value)); + } + + return encode(values.toArray()); + } + + /** + * 编码给定的数字数组 + * + * @param numbers 数字数组 + * @return 编码后的值, {@code null} if {@code numbers} 是 {@code null}. + * @throws IllegalArgumentException 数字不支持抛出此异常 + */ + @Override + public String encode(final long... numbers) { + if (numbers == null) { + return null; + } + + // copy alphabet + final char[] currentAlphabet = Arrays.copyOf(alphabet, alphabet.length); + + // determine the lottery number + final long lotteryId = LongStream.range(0, numbers.length) + .reduce(0, (state, i) -> { + final long number = numbers[(int) i]; + if (number < 0) { + throw new IllegalArgumentException("invalid number: " + number); + } + return state + number % (i + LOTTERY_MOD); + }); + final char lottery = currentAlphabet[(int) (lotteryId % currentAlphabet.length)]; + + // encode each number + final StringBuilder global = new StringBuilder(); + IntStream.range(0, numbers.length) + .forEach(idx -> { + // derive alphabet + deriveNewAlphabet(currentAlphabet, salt, lottery); + + // encode + final int initialLength = global.length(); + translate(numbers[idx], currentAlphabet, global, initialLength); + + // prepend the lottery + if (idx == 0) { + global.insert(0, lottery); + } + + // append the separator, if more numbers are pending encoding + if (idx + 1 < numbers.length) { + long n = numbers[idx] % (global.charAt(initialLength) + 1); + global.append(separators[(int) (n % separators.length)]); + } + }); + + // add the guards, if there's any space left + if (minLength > global.length()) { + int guardIdx = (int) ((lotteryId + lottery) % guards.length); + global.insert(0, guards[guardIdx]); + if (minLength > global.length()) { + guardIdx = (int) ((lotteryId + global.charAt(2)) % guards.length); + global.append(guards[guardIdx]); + } + } + + // add the necessary padding + int paddingLeft = minLength - global.length(); + while (paddingLeft > 0) { + shuffle(currentAlphabet, Arrays.copyOf(currentAlphabet, currentAlphabet.length)); + + final int alphabetHalfSize = currentAlphabet.length / 2; + final int initialSize = global.length(); + if (paddingLeft > currentAlphabet.length) { + // entire alphabet with the current encoding in the middle of it + int offset = alphabetHalfSize + (currentAlphabet.length % 2 == 0 ? 0 : 1); + + global.insert(0, currentAlphabet, alphabetHalfSize, offset); + global.insert(offset + initialSize, currentAlphabet, 0, alphabetHalfSize); + // decrease the padding left + paddingLeft -= currentAlphabet.length; + } else { + // calculate the excess + final int excess = currentAlphabet.length + global.length() - minLength; + final int secondHalfStartOffset = alphabetHalfSize + Math.floorDiv(excess, 2); + final int secondHalfLength = currentAlphabet.length - secondHalfStartOffset; + final int firstHalfLength = paddingLeft - secondHalfLength; + + global.insert(0, currentAlphabet, secondHalfStartOffset, secondHalfLength); + global.insert(secondHalfLength + initialSize, currentAlphabet, 0, firstHalfLength); + + paddingLeft = 0; + } + } + + return global.toString(); + } + + //------------------------- + // Decode + //------------------------- + + /** + * 解码Hash值为16进制数字 + * + * @param hash hash值 + * @return 解码后的16进制值, {@code null} if {@code numbers} 是 {@code null}. + * @throws IllegalArgumentException if the hash is invalid. + */ + public String decodeToHex(final String hash) { + if (hash == null) { + return null; + } + + final StringBuilder sb = new StringBuilder(); + Arrays.stream(decode(hash)) + .mapToObj(Long::toHexString) + .forEach(hex -> sb.append(hex, 1, hex.length())); + return sb.toString(); + } + + /** + * 解码Hash值为数字数组 + * + * @param hash hash值 + * @return 解码后的16进制值, {@code null} if {@code numbers} 是 {@code null}. + * @throws IllegalArgumentException if the hash is invalid. + */ + @Override + public long[] decode(final String hash) { + if (hash == null) { + return null; + } + + // create a set of the guards + final Set guardsSet = IntStream.range(0, guards.length) + .mapToObj(idx -> guards[idx]) + .collect(Collectors.toSet()); + // count the total guards used + final int[] guardsIdx = IntStream.range(0, hash.length()) + .filter(idx -> guardsSet.contains(hash.charAt(idx))) + .toArray(); + // get the start/end index base on the guards count + final int startIdx, endIdx; + if (guardsIdx.length > 0) { + startIdx = guardsIdx[0] + 1; + endIdx = guardsIdx.length > 1 ? guardsIdx[1] : hash.length(); + } else { + startIdx = 0; + endIdx = hash.length(); + } + + LongStream decoded = LongStream.empty(); + // parse the hash + if (hash.length() > 0) { + final char lottery = hash.charAt(startIdx); + + // create the initial accumulation string + final int length = hash.length() - guardsIdx.length - 1; + StringBuilder block = new StringBuilder(length); + + // create the base salt + final char[] decodeSalt = new char[alphabet.length]; + decodeSalt[0] = lottery; + final int saltLength = salt.length >= alphabet.length ? alphabet.length - 1 : salt.length; + System.arraycopy(salt, 0, decodeSalt, 1, saltLength); + final int saltLeft = alphabet.length - saltLength - 1; + + // copy alphabet + final char[] currentAlphabet = Arrays.copyOf(alphabet, alphabet.length); + + for (int i = startIdx + 1; i < endIdx; i++) { + if (false == separatorsSet.contains(hash.charAt(i))) { + block.append(hash.charAt(i)); + // continue if we have not reached the end, yet + if (i < endIdx - 1) { + continue; + } + } + + if (block.length() > 0) { + // create the salt + if (saltLeft > 0) { + System.arraycopy(currentAlphabet, 0, decodeSalt, + alphabet.length - saltLeft, saltLeft); + } + + // shuffle the alphabet + shuffle(currentAlphabet, decodeSalt); + + // prepend the decoded value + final long n = translate(block.toString().toCharArray(), currentAlphabet); + decoded = LongStream.concat(decoded, LongStream.of(n)); + + // create a new block + block = new StringBuilder(length); + } + } + } + + // validate the hash + final long[] decodedValue = decoded.toArray(); + if (!Objects.equals(hash, encode(decodedValue))) { + throw new IllegalArgumentException("invalid hash: " + hash); + } + + return decodedValue; + } + + private StringBuilder translate(final long n, final char[] alphabet, + final StringBuilder sb, final int start) { + long input = n; + do { + // prepend the chosen char + sb.insert(start, alphabet[(int) (input % alphabet.length)]); + + // trim the input + input = input / alphabet.length; + } while (input > 0); + + return sb; + } + + private long translate(final char[] hash, final char[] alphabet) { + long number = 0; + + final Map alphabetMapping = IntStream.range(0, alphabet.length) + .mapToObj(idx -> new Object[]{alphabet[idx], idx}) + .collect(Collectors.groupingBy(arr -> (Character) arr[0], + Collectors.mapping(arr -> (Integer) arr[1], + Collectors.reducing(null, (a, b) -> a == null ? b : a)))); + + for (int i = 0; i < hash.length; ++i) { + number += alphabetMapping.computeIfAbsent(hash[i], k -> { + throw new IllegalArgumentException("Invalid alphabet for hash"); + }) * (long) Math.pow(alphabet.length, hash.length - i - 1); + } + + return number; + } + + private char[] deriveNewAlphabet(final char[] alphabet, final char[] salt, final char lottery) { + // create the new salt + final char[] newSalt = new char[alphabet.length]; + + // 1. lottery + newSalt[0] = lottery; + int spaceLeft = newSalt.length - 1; + int offset = 1; + // 2. salt + if (salt.length > 0 && spaceLeft > 0) { + int length = Math.min(salt.length, spaceLeft); + System.arraycopy(salt, 0, newSalt, offset, length); + spaceLeft -= length; + offset += length; + } + // 3. alphabet + if (spaceLeft > 0) { + System.arraycopy(alphabet, 0, newSalt, offset, spaceLeft); + } + + // shuffle + return shuffle(alphabet, newSalt); + } + + private char[] validateAndFilterAlphabet(final char[] alphabet, final char[] separators) { + // validate size + if (alphabet.length < MIN_ALPHABET_LENGTH) { + throw new IllegalArgumentException(String.format("alphabet must contain at least %d unique " + + "characters: %d", MIN_ALPHABET_LENGTH, alphabet.length)); + } + + final Set seen = new LinkedHashSet<>(alphabet.length); + final Set invalid = IntStream.range(0, separators.length) + .mapToObj(idx -> separators[idx]) + .collect(Collectors.toSet()); + + // add to seen set (without duplicates) + IntStream.range(0, alphabet.length) + .forEach(i -> { + if (alphabet[i] == ' ') { + throw new IllegalArgumentException(String.format("alphabet must not contain spaces: " + + "index %d", i)); + } + final Character c = alphabet[i]; + if (!invalid.contains(c)) { + seen.add(c); + } + }); + + // create a new alphabet without the duplicates + final char[] uniqueAlphabet = new char[seen.size()]; + int idx = 0; + for (char c : seen) { + uniqueAlphabet[idx++] = c; + } + return uniqueAlphabet; + } + + @SuppressWarnings("SameParameterValue") + private char[] filterSeparators(final char[] separators, final char[] alphabet) { + final Set valid = IntStream.range(0, alphabet.length) + .mapToObj(idx -> alphabet[idx]) + .collect(Collectors.toSet()); + + return IntStream.range(0, separators.length) + .mapToObj(idx -> (separators[idx])) + .filter(valid::contains) + // ugly way to convert back to char[] + .map(c -> Character.toString(c)) + .collect(Collectors.joining()) + .toCharArray(); + } + + private char[] shuffle(final char[] alphabet, final char[] salt) { + for (int i = alphabet.length - 1, v = 0, p = 0, j, z; salt.length > 0 && i > 0; i--, v++) { + v %= salt.length; + p += z = salt[v]; + j = (z + v + p) % i; + final char tmp = alphabet[j]; + alphabet[j] = alphabet[i]; + alphabet[i] = tmp; + } + return alphabet; + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/reflect/LookupFactory.java b/hutool-core/src/main/java/cn/hutool/core/lang/reflect/LookupFactory.java index 48aa5ff88..5ae8392fa 100755 --- a/hutool-core/src/main/java/cn/hutool/core/lang/reflect/LookupFactory.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/reflect/LookupFactory.java @@ -13,9 +13,7 @@ import java.lang.reflect.Method; * 时会出现权限不够问题,抛出"no private access for invokespecial"异常,因此针对JDK8及JDK9+分别封装lookup方法。 * * 参考: - *
        - *
      • https://blog.csdn.net/u013202238/article/details/108687086
      • - *
      + *

      https://blog.csdn.net/u013202238/article/details/108687086

      * * @author looly * @since 5.7.7 diff --git a/hutool-core/src/test/java/cn/hutool/core/codec/HashidsTest.java b/hutool-core/src/test/java/cn/hutool/core/codec/HashidsTest.java new file mode 100755 index 000000000..458cde5b1 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/codec/HashidsTest.java @@ -0,0 +1,20 @@ +package cn.hutool.core.codec; + +import org.junit.Assert; +import org.junit.Test; + +public class HashidsTest { + @Test + public void hexEncodeDecode() { + final Hashids hashids = Hashids.create("my awesome salt".toCharArray()); + final String encoded1 = hashids.encodeFromHex("507f1f77bcf86cd799439011"); + final String encoded2 = hashids.encodeFromHex("0x507f1f77bcf86cd799439011"); + final String encoded3 = hashids.encodeFromHex("0X507f1f77bcf86cd799439011"); + + Assert.assertEquals("R2qnd2vkOJTXm7XV7yq4", encoded1); + Assert.assertEquals(encoded1, encoded2); + Assert.assertEquals(encoded1, encoded3); + final String decoded = hashids.decodeToHex(encoded1); + Assert.assertEquals("507f1f77bcf86cd799439011", decoded); + } +} From cce26f9bb0d8f054719665f5c09e0d9b8e074abb Mon Sep 17 00:00:00 2001 From: Looly Date: Mon, 25 Apr 2022 15:53:36 +0800 Subject: [PATCH 34/52] fix comment --- .../src/main/java/cn/hutool/core/net/RFC3986.java | 10 +++++----- .../template/engine/jetbrick/loader/package-info.java | 7 +++++++ 2 files changed, 12 insertions(+), 5 deletions(-) create mode 100755 hutool-extra/src/main/java/cn/hutool/extra/template/engine/jetbrick/loader/package-info.java diff --git a/hutool-core/src/main/java/cn/hutool/core/net/RFC3986.java b/hutool-core/src/main/java/cn/hutool/core/net/RFC3986.java index 2c10260a4..d3811b162 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/RFC3986.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/RFC3986.java @@ -3,8 +3,8 @@ package cn.hutool.core.net; import cn.hutool.core.codec.PercentCodec; /** - * rfc3986 : https://www.ietf.org/rfc/rfc3986.html 编码实现
      - * 定义见:https://www.ietf.org/rfc/rfc3986.html#appendix-A + * RFC3986 编码实现
      + * 定义见:https://www.ietf.org/rfc/rfc3986.html#appendix-A * * @author looly * @since 5.7.16 @@ -23,13 +23,13 @@ public class RFC3986 { /** * reserved = gen-delims / sub-delims
      - * see:https://www.ietf.org/rfc/rfc3986.html#section-2.2 + * see:https://www.ietf.org/rfc/rfc3986.html#section-2.2 */ public static final PercentCodec RESERVED = GEN_DELIMS.orNew(SUB_DELIMS); /** * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
      - * see: https://www.ietf.org/rfc/rfc3986.html#section-2.3 + * see: https://www.ietf.org/rfc/rfc3986.html#section-2.3 */ public static final PercentCodec UNRESERVED = PercentCodec.of(unreservedChars()); @@ -40,7 +40,7 @@ public class RFC3986 { /** * segment = pchar
      - * see: https://www.ietf.org/rfc/rfc3986.html#section-3.3 + * see: https://www.ietf.org/rfc/rfc3986.html#section-3.3 */ public static final PercentCodec SEGMENT = PCHAR; /** diff --git a/hutool-extra/src/main/java/cn/hutool/extra/template/engine/jetbrick/loader/package-info.java b/hutool-extra/src/main/java/cn/hutool/extra/template/engine/jetbrick/loader/package-info.java new file mode 100755 index 000000000..5870a7422 --- /dev/null +++ b/hutool-extra/src/main/java/cn/hutool/extra/template/engine/jetbrick/loader/package-info.java @@ -0,0 +1,7 @@ +/** + * jetbrick-template实现,特殊资源加载器
      + * 模板引擎介绍见:https://github.com/subchen/jetbrick-template-2x + * + * @author looly + */ +package cn.hutool.extra.template.engine.jetbrick.loader; From 940d1cbe31e689700f69d1404a837d26979b71f9 Mon Sep 17 00:00:00 2001 From: Looly Date: Mon, 25 Apr 2022 16:04:49 +0800 Subject: [PATCH 35/52] fix code --- .../src/main/java/cn/hutool/core/text/StrMatcher.java | 9 +++++---- .../src/main/java/cn/hutool/core/text/StrTemplate.java | 4 ++++ .../test/java/cn/hutool/core/text/StrMatcherTest.java | 3 +-- 3 files changed, 10 insertions(+), 6 deletions(-) create mode 100755 hutool-core/src/main/java/cn/hutool/core/text/StrTemplate.java diff --git a/hutool-core/src/main/java/cn/hutool/core/text/StrMatcher.java b/hutool-core/src/main/java/cn/hutool/core/text/StrMatcher.java index e5bbc60ab..1b5bac02d 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/StrMatcher.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/StrMatcher.java @@ -82,7 +82,7 @@ public class StrMatcher { char c = 0; char pre; boolean inVar = false; - StrBuilder part = StrUtil.strBuilder(); + StringBuilder part = StrUtil.builder(); for (int i = 0; i < length; i++) { pre = c; c = pattern.charAt(i); @@ -92,16 +92,17 @@ public class StrMatcher { // 变量结束 inVar = false; patterns.add(part.toString()); - part.clear(); + part.setLength(0); } } else if ('{' == c && '$' == pre) { // 变量开始 inVar = true; - final String preText = part.subString(0, part.length() - 1); + final String preText = part.substring(0, part.length() - 1); if (StrUtil.isNotEmpty(preText)) { patterns.add(preText); } - part.reset().append(pre).append(c); + part.setLength(0); + part.append(pre).append(c); } else { // 普通字符 part.append(c); diff --git a/hutool-core/src/main/java/cn/hutool/core/text/StrTemplate.java b/hutool-core/src/main/java/cn/hutool/core/text/StrTemplate.java new file mode 100755 index 000000000..5293e7918 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/text/StrTemplate.java @@ -0,0 +1,4 @@ +package cn.hutool.core.text; + +public class StrTemplate { +} diff --git a/hutool-core/src/test/java/cn/hutool/core/text/StrMatcherTest.java b/hutool-core/src/test/java/cn/hutool/core/text/StrMatcherTest.java index 53720fda8..d5f88f953 100644 --- a/hutool-core/src/test/java/cn/hutool/core/text/StrMatcherTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/text/StrMatcherTest.java @@ -1,6 +1,5 @@ package cn.hutool.core.text; -import cn.hutool.core.lang.Console; import org.junit.Assert; import org.junit.Test; @@ -34,7 +33,7 @@ public class StrMatcherTest { // 当有无匹配项的时候,按照全不匹配对待 final StrMatcher strMatcher = new StrMatcher("${name}经过${year}年"); final Map match = strMatcher.match("小明经过20年,成长为一个大人。"); - Console.log(match); + //Console.log(match); Assert.assertEquals("小明", match.get("name")); Assert.assertEquals("20", match.get("year")); } From 82f5b2b154b5e16bdf031d4108697492b8fed304 Mon Sep 17 00:00:00 2001 From: Looly Date: Mon, 25 Apr 2022 17:23:31 +0800 Subject: [PATCH 36/52] gts --- CHANGELOG.md | 1 + .../java/cn/hutool/core/text/StrMatcher.java | 4 +- .../java/cn/hutool/core/text/StrTemplate.java | 4 -- .../java/cn/hutool/core/util/ClassUtil.java | 52 +++++++++++++------ .../java/cn/hutool/core/util/ReflectUtil.java | 38 ++++++++++---- .../cn/hutool/core/util/ReflectUtilTest.java | 29 +++++++++++ 6 files changed, 95 insertions(+), 33 deletions(-) delete mode 100755 hutool-core/src/main/java/cn/hutool/core/text/StrTemplate.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 0debc4469..a1afd293c 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ * 【extra 】 增加JakartaServletUtil(issue#2271@Github) * 【poi 】 ExcelWriter支持重复别名的数据写出(issue#I53APY@Gitee) * 【core 】 增加Hashids(issue#I53APY@Gitee) +* 【core 】 ReflectUtil.newInstanceIfPossible添加枚举、数组等类型的默认实现 ### 🐞Bug修复 * 【core 】 修复StrUtil.firstNonX非static问题(issue#2257@Github) diff --git a/hutool-core/src/main/java/cn/hutool/core/text/StrMatcher.java b/hutool-core/src/main/java/cn/hutool/core/text/StrMatcher.java index 1b5bac02d..ccc59f87f 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/StrMatcher.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/StrMatcher.java @@ -35,6 +35,7 @@ public class StrMatcher { /** * 匹配并提取匹配到的内容 + * * @param text 被匹配的文本 * @return 匹配的map,key为变量名,value为匹配到的值 */ @@ -49,7 +50,7 @@ public class StrMatcher { key = StrUtil.sub(part, 2, part.length() - 1); } else { to = text.indexOf(part, from); - if(to < 0){ + if (to < 0) { //普通字符串未匹配到,说明整个模式不能匹配,返回空 return MapUtil.empty(); } @@ -73,6 +74,7 @@ public class StrMatcher { /** * 解析表达式 + * * @param pattern 表达式,使用${XXXX}作为变量占位符 * @return 表达式 */ diff --git a/hutool-core/src/main/java/cn/hutool/core/text/StrTemplate.java b/hutool-core/src/main/java/cn/hutool/core/text/StrTemplate.java deleted file mode 100755 index 5293e7918..000000000 --- a/hutool-core/src/main/java/cn/hutool/core/text/StrTemplate.java +++ /dev/null @@ -1,4 +0,0 @@ -package cn.hutool.core.text; - -public class StrTemplate { -} diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ClassUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ClassUtil.java index a25d41504..e697fed49 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ClassUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ClassUtil.java @@ -999,26 +999,44 @@ public class ClassUtil { * @since 3.0.8 */ public static Object getDefaultValue(Class clazz) { + // 原始类型 if (clazz.isPrimitive()) { - if (long.class == clazz) { - return 0L; - } else if (int.class == clazz) { - return 0; - } else if (short.class == clazz) { - return (short) 0; - } else if (char.class == clazz) { - return (char) 0; - } else if (byte.class == clazz) { - return (byte) 0; - } else if (double.class == clazz) { - return 0D; - } else if (float.class == clazz) { - return 0f; - } else if (boolean.class == clazz) { - return false; - } + return getPrimitiveDefaultValue(clazz); } + return null; + } + /** + * 获取指定原始类型分的默认值
      + * 默认值规则为: + * + *
      +	 * 1、如果为原始类型,返回0
      +	 * 2、非原始类型返回{@code null}
      +	 * 
      + * + * @param clazz 类 + * @return 默认值 + * @since 5.8.0 + */ + public static Object getPrimitiveDefaultValue(Class clazz) { + if (long.class == clazz) { + return 0L; + } else if (int.class == clazz) { + return 0; + } else if (short.class == clazz) { + return (short) 0; + } else if (char.class == clazz) { + return (char) 0; + } else if (byte.class == clazz) { + return (byte) 0; + } else if (double.class == clazz) { + return 0D; + } else if (float.class == clazz) { + return 0f; + } else if (boolean.class == clazz) { + return false; + } return null; } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ReflectUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ReflectUtil.java index b9c3ab637..39d94196e 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ReflectUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ReflectUtil.java @@ -13,6 +13,7 @@ import cn.hutool.core.map.MapUtil; import cn.hutool.core.map.WeakConcurrentMap; import java.lang.reflect.AccessibleObject; +import java.lang.reflect.Array; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; @@ -864,30 +865,45 @@ public class ReflectUtil { * * * @param 对象类型 - * @param beanClass 被构造的类 + * @param type 被构造的类 * @return 构造后的对象,构造失败返回{@code null} */ @SuppressWarnings("unchecked") - public static T newInstanceIfPossible(Class beanClass) { - Assert.notNull(beanClass); + public static T newInstanceIfPossible(Class type) { + Assert.notNull(type); + + // 原始类型 + if(type.isPrimitive()){ + return (T) ClassUtil.getPrimitiveDefaultValue(type); + } // 某些特殊接口的实例化按照默认实现进行 - if (beanClass.isAssignableFrom(AbstractMap.class)) { - beanClass = (Class) HashMap.class; - } else if (beanClass.isAssignableFrom(List.class)) { - beanClass = (Class) ArrayList.class; - } else if (beanClass.isAssignableFrom(Set.class)) { - beanClass = (Class) HashSet.class; + if (type.isAssignableFrom(AbstractMap.class)) { + type = (Class) HashMap.class; + } else if (type.isAssignableFrom(List.class)) { + type = (Class) ArrayList.class; + } else if (type.isAssignableFrom(Set.class)) { + type = (Class) HashSet.class; } try { - return newInstance(beanClass); + return newInstance(type); } catch (Exception e) { // ignore // 默认构造不存在的情况下查找其它构造 } - final Constructor[] constructors = getConstructors(beanClass); + // 枚举 + if (type.isEnum()) { + return type.getEnumConstants()[0]; + } + + // 数组 + if (type.isArray()) { + return (T) Array.newInstance(type.getComponentType(), 0); + } + + final Constructor[] constructors = getConstructors(type); Class[] parameterTypes; for (Constructor constructor : constructors) { parameterTypes = constructor.getParameterTypes(); diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java index 6affbabb6..7792804a8 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java @@ -2,6 +2,7 @@ package cn.hutool.core.util; import cn.hutool.core.date.DateUtil; import cn.hutool.core.date.TimeInterval; +import cn.hutool.core.date.Week; import cn.hutool.core.lang.Console; import cn.hutool.core.lang.test.bean.ExamInfoDict; import cn.hutool.core.util.ClassUtilTest.TestSubClass; @@ -12,6 +13,8 @@ import org.junit.Test; import java.lang.reflect.Field; import java.lang.reflect.Method; +import java.util.Collection; +import java.util.Map; /** * 反射工具类单元测试 @@ -202,10 +205,13 @@ public class ReflectUtilTest { } interface TestInterface1 { + @SuppressWarnings("unused") void getA(); + @SuppressWarnings("unused") void getB(); + @SuppressWarnings("unused") default void getC() { } @@ -220,6 +226,7 @@ public class ReflectUtilTest { void get3(); } + @SuppressWarnings("InnerClassMayBeStatic") class C1 implements TestInterface2 { @Override @@ -239,4 +246,26 @@ public class ReflectUtilTest { } } + + @Test + public void newInstanceIfPossibleTest(){ + //noinspection ConstantConditions + int intValue = ReflectUtil.newInstanceIfPossible(int.class); + Assert.assertEquals(0, intValue); + + Integer integer = ReflectUtil.newInstanceIfPossible(Integer.class); + Assert.assertEquals(new Integer(0), integer); + + Map map = ReflectUtil.newInstanceIfPossible(Map.class); + Assert.assertNotNull(map); + + Collection collection = ReflectUtil.newInstanceIfPossible(Collection.class); + Assert.assertNotNull(collection); + + Week week = ReflectUtil.newInstanceIfPossible(Week.class); + Assert.assertEquals(Week.SUNDAY, week); + + int[] intArray = ReflectUtil.newInstanceIfPossible(int[].class); + Assert.assertArrayEquals(new int[0], intArray); + } } From d3f7fdcdd58db6d6bb9262aa66b00a9352da2c46 Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 26 Apr 2022 11:16:23 +0800 Subject: [PATCH 37/52] add test --- .../java/cn/hutool/core/io/FileTypeUtilTest.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/hutool-core/src/test/java/cn/hutool/core/io/FileTypeUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/io/FileTypeUtilTest.java index b4c85a1aa..4ea9bddb6 100644 --- a/hutool-core/src/test/java/cn/hutool/core/io/FileTypeUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/io/FileTypeUtilTest.java @@ -5,7 +5,9 @@ import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; +import java.io.BufferedInputStream; import java.io.File; +import java.io.IOException; /** * 文件类型判断单元测试 @@ -62,4 +64,15 @@ public class FileTypeUtilTest { Assert.assertEquals("xlsx", type); } + @Test + @Ignore + public void getTypeFromInputStream() throws IOException { + File file = FileUtil.file("d:/test/pic.jpg"); + final BufferedInputStream inputStream = FileUtil.getInputStream(file); + inputStream.mark(0); + String type = FileTypeUtil.getType(inputStream); + + inputStream.reset(); + } + } From 43c0cf0a0b0756f978860b87f78fc327fbf28fd0 Mon Sep 17 00:00:00 2001 From: xiongyi <1170313408@qq.com> Date: Tue, 26 Apr 2022 16:07:35 +0800 Subject: [PATCH 38/52] =?UTF-8?q?Tailer=E5=BD=93=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E5=86=85=E5=AE=B9=E4=B8=BA=E7=A9=BA=E6=97=B6=EF=BC=8C=E4=BC=9A?= =?UTF-8?q?=E6=8A=A5=E5=BC=82=E5=B8=B8=EF=BC=8C=E6=B7=BB=E5=8A=A0=E9=98=B2?= =?UTF-8?q?=E6=AD=A2=E7=A9=BA=E6=8C=87=E9=92=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hutool-core/src/main/java/cn/hutool/core/io/file/Tailer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/io/file/Tailer.java b/hutool-core/src/main/java/cn/hutool/core/io/file/Tailer.java index 93cbfe2be..bc4305c40 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/file/Tailer.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/file/Tailer.java @@ -153,7 +153,7 @@ public class Tailer implements Serializable { Stack stack = new Stack<>(); long start = this.randomAccessFile.getFilePointer(); - long nextEnd = len - 1; + long nextEnd = (len - 1) < 0 ? 0 : len - 1; this.randomAccessFile.seek(nextEnd); int c; int currentLine = 0; From 562e77e438bd71934b49b84df8f82e4282e0af44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=B8=BF=E8=BE=BE?= Date: Tue, 26 Apr 2022 17:35:43 +0800 Subject: [PATCH 39/52] =?UTF-8?q?fixed=207dbc9e9=20from=20https://gitee.co?= =?UTF-8?q?m/yixi-dlmu/hutool/pulls/603=20=E6=96=B0=E5=A2=9E=E5=8F=AF?= =?UTF-8?q?=E8=8E=B7=E5=8F=96=E9=87=8D=E5=A4=8D=E6=80=A7=E6=B3=A8=E8=A7=A3?= =?UTF-8?q?=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/annotation/AnnotationUtil.java | 51 ++++++++++++++++--- .../core/annotation/AnnotationUtilTest.java | 8 +++ .../annotation/RepeatAnnotationForTest.java | 16 ++++++ 3 files changed, 68 insertions(+), 7 deletions(-) create mode 100644 hutool-core/src/test/java/cn/hutool/core/annotation/RepeatAnnotationForTest.java diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationUtil.java b/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationUtil.java index be44f080a..c84de0073 100644 --- a/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationUtil.java @@ -4,18 +4,14 @@ import cn.hutool.core.exceptions.UtilException; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ReflectUtil; -import java.lang.annotation.Annotation; -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Inherited; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; +import java.lang.annotation.*; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; +import java.util.Set; /** * 注解工具类
      @@ -50,6 +46,47 @@ public class AnnotationUtil { return (null == annotationEle) ? null : (isToCombination ? toCombination(annotationEle) : annotationEle).getAnnotations(); } + /** + * 获取可重复的注解列表 + * 用于带有同一个注解的多个组合注解 + * + * @param annotationEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission + * @param annotationType 注解类型 + * @return 注解列表 + * @param 注解值类型 + */ + public static Set getRepeatedAnnotations(AnnotatedElement annotationEle, Class annotationType) { + if (!Annotation.class.isAssignableFrom(annotationType)){ + return null; + } + Set annotationList = new HashSet<>(); + recursion(annotationList, annotationEle.getAnnotations(), annotationType); + return annotationList; + } + + /** + * 递归获取注解列表 + * + * @param list 注解结果集 + * @param annotations 注解参数集 + * @param annotationType 注解类型 + * @param 注解值类型 + */ + private static void recursion(Set list, Annotation[] annotations, Class annotationType) { + for (Annotation annotation : annotations) { + Class clazz = annotation.getClass(); + if (annotationType.isAssignableFrom(clazz)) { + list.add((T) annotation); + } else if (!Retention.class.isAssignableFrom(clazz) + && !Target.class.isAssignableFrom(clazz) + && !Documented.class.isAssignableFrom(clazz) + && !Repeatable.class.isAssignableFrom(clazz) + && !Inherited.class.isAssignableFrom(clazz)) { + recursion(list, annotation.annotationType().getAnnotations(), annotationType); + } + } + } + /** * 获取指定注解 * diff --git a/hutool-core/src/test/java/cn/hutool/core/annotation/AnnotationUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/annotation/AnnotationUtilTest.java index 3340dcf37..c3077a626 100644 --- a/hutool-core/src/test/java/cn/hutool/core/annotation/AnnotationUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/annotation/AnnotationUtilTest.java @@ -3,7 +3,14 @@ package cn.hutool.core.annotation; import org.junit.Assert; import org.junit.Test; +import java.util.Set; + public class AnnotationUtilTest { + @Test + public void getRepeatAnnotationValueTest(){ + Set annotations = AnnotationUtil.getRepeatedAnnotations(ClassWithAnnotation.class, AnnotationForTest.class); + Assert.assertTrue(annotations != null && annotations.size() == 2); + } @Test public void getAnnotationValueTest() { @@ -23,6 +30,7 @@ public class AnnotationUtilTest { } @AnnotationForTest("测试") + @RepeatAnnotationForTest static class ClassWithAnnotation{ public void test(){ diff --git a/hutool-core/src/test/java/cn/hutool/core/annotation/RepeatAnnotationForTest.java b/hutool-core/src/test/java/cn/hutool/core/annotation/RepeatAnnotationForTest.java new file mode 100644 index 000000000..cc71c68da --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/annotation/RepeatAnnotationForTest.java @@ -0,0 +1,16 @@ +package cn.hutool.core.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author hongda.li 2022-04-26 17:09 + */ +@AnnotationForTest("repeat-annotation") +@Retention(RetentionPolicy.RUNTIME) +// Target注解决定MyAnnotation注解可以加在哪些成分上,如加在类身上,或者属性身上,或者方法身上等成分 +@Target({ ElementType.METHOD, ElementType.TYPE }) +public @interface RepeatAnnotationForTest { +} From 835e450c3cdb5ea983f49187ab0e1373e4f03707 Mon Sep 17 00:00:00 2001 From: Looly Date: Wed, 27 Apr 2022 09:31:29 +0800 Subject: [PATCH 40/52] add StrTemplate --- .../src/main/java/cn/hutool/core/text/StrTemplate.java | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100755 hutool-core/src/main/java/cn/hutool/core/text/StrTemplate.java diff --git a/hutool-core/src/main/java/cn/hutool/core/text/StrTemplate.java b/hutool-core/src/main/java/cn/hutool/core/text/StrTemplate.java new file mode 100755 index 000000000..3fd386cf4 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/text/StrTemplate.java @@ -0,0 +1,8 @@ +package cn.hutool.core.text; + +public class StrTemplate { + public static final String DEFAULT_MACRO_PREFIX = "$"; + public static final String DEFAULT_MACRO_START = "${"; + public static final String DEFAULT_MACRO_END = "}"; + +} From 1c3f98f8892a622351662fe7bd13a25902a1e33d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=B8=BF=E8=BE=BE?= Date: Wed, 27 Apr 2022 10:23:44 +0800 Subject: [PATCH 41/52] =?UTF-8?q?fixed=208f736f7=20from=20https://gitee.co?= =?UTF-8?q?m/yixi-dlmu/hutool/pulls/606=20=E6=96=B0=E5=A2=9E=E7=B1=BBSprin?= =?UTF-8?q?gBoot=E7=9A=84=E5=BD=A9=E8=89=B2=E6=97=A5=E5=BF=97=E8=BE=93?= =?UTF-8?q?=E5=87=BA=E9=A3=8E=E6=A0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../log/dialect/console/ConsoleColorLog.java | 197 ++++++++++++++++++ .../console/ConsoleColorLogFactory.java | 24 +++ .../cn/hutool/log/test/StaticLogTest.java | 17 ++ 3 files changed, 238 insertions(+) create mode 100644 hutool-log/src/main/java/cn/hutool/log/dialect/console/ConsoleColorLog.java create mode 100644 hutool-log/src/main/java/cn/hutool/log/dialect/console/ConsoleColorLogFactory.java diff --git a/hutool-log/src/main/java/cn/hutool/log/dialect/console/ConsoleColorLog.java b/hutool-log/src/main/java/cn/hutool/log/dialect/console/ConsoleColorLog.java new file mode 100644 index 000000000..f99404376 --- /dev/null +++ b/hutool-log/src/main/java/cn/hutool/log/dialect/console/ConsoleColorLog.java @@ -0,0 +1,197 @@ +package cn.hutool.log.dialect.console; + +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.ClassUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.log.AbstractLog; +import cn.hutool.log.level.Level; + +import java.util.Date; + +/** + * @author hongda.li 2022-04-27 09:55 + */ +public class ConsoleColorLog extends AbstractLog { + + //-----------------------------------可供定制的不同级别的日志颜色代码--------------------------- + + private static int ERROR = 31; + private static int INFO = 32; + private static int DEBUG = 32; + private static int WARN = 33; + private static int TRACE = 35; + + public static void setErrorColor(int errorColor) { + ConsoleColorLog.ERROR = errorColor; + } + + public static void setInfoColor(int infoColor) { + ConsoleColorLog.INFO = infoColor; + } + + public static void setDebugColor(int debugColor) { + ConsoleColorLog.DEBUG = debugColor; + } + + public static void setWarnColor(int warnColor) { + ConsoleColorLog.WARN = warnColor; + } + + public static void setTraceColor(int traceColor) { + ConsoleColorLog.TRACE = traceColor; + } + + /** + * 控制台打印类名的颜色代码 + */ + private static final int CLASSNAME = 36; + + /** + * 控制台打印时间的颜色代码 + */ + private static final int TIME = 37; + + /** + * 控制台打印正常信息的颜色代码 + */ + private static final int NONE = 38; + + /** + * 系统换行符 + */ + private static final String LINE_SEPARATOR = System.lineSeparator(); + + /** + * 日志名称 + */ + private final String name; + + /** + * 日志级别 + */ + private static Level currentLevel = Level.DEBUG; + + /** + * 构造 + * + * @param name 类名 + */ + public ConsoleColorLog(String name) { + this.name = name; + } + + /** + * 构造 + * + * @param clazz 类 + */ + public ConsoleColorLog(Class clazz) { + this.name = (null == clazz) ? StrUtil.NULL : clazz.getName(); + } + + @Override + public String getName() { + return this.name; + } + + /** + * 设置自定义的日志显示级别 + * + * @param customLevel 自定义级别 + */ + public static void setLevel(Level customLevel) { + Assert.notNull(customLevel); + currentLevel = customLevel; + } + + @Override + public synchronized void log(String fqcn, Level level, Throwable t, String format, Object... arguments) { + if (!isEnabled(level)) { + return; + } + System.out.format("\33[%d;2m%s", TIME, DatePattern.NORM_DATETIME_MS_FORMAT.format(new Date())); + switch (level) { + case DEBUG: + System.out.format("\33[%d;2m%-8s", DEBUG, " DEBUG"); + System.out.format("\33[%d;2m%s", DEBUG, " --- "); + break; + case WARN: + System.out.format("\33[%d;2m%-8s", WARN, " WARN"); + System.out.format("\33[%d;2m%s", WARN, " --- "); + break; + case ERROR: + System.out.format("\33[%d;2m%-8s", ERROR, " ERROR"); + System.out.format("\33[%d;2m%s", ERROR, " --- "); + break; + case INFO: + System.out.format("\33[%d;2m%-8s", INFO, " INFO"); + System.out.format("\33[%d;2m%s", INFO, " --- "); + break; + case TRACE: + System.out.format("\33[%d;2m%-8s", TRACE, " TRACE"); + System.out.format("\33[%d;2m%s", TRACE, " --- "); + break; + default: + } + System.out.format("\33[%d;2m%-35s", CLASSNAME, "[" + ClassUtil.getShortClassName(name) + "]"); + System.out.format("\33[%d;2m%s", NONE, " : " + StrUtil.format(format, arguments)); + System.out.format("%s", LINE_SEPARATOR); + System.out.format("\33[%d;2m%s", NONE, ""); + } + + @Override + public boolean isDebugEnabled() { + return isEnabled(Level.DEBUG); + } + + @Override + public void debug(String fqcn, Throwable t, String format, Object... arguments) { + log(fqcn, Level.DEBUG, t, format, arguments); + } + + @Override + public boolean isErrorEnabled() { + return isEnabled(Level.ERROR); + } + + @Override + public void error(String fqcn, Throwable t, String format, Object... arguments) { + log(fqcn, Level.ERROR, t, format, arguments); + } + + @Override + public boolean isInfoEnabled() { + return isEnabled(Level.INFO); + } + + @Override + public void info(String fqcn, Throwable t, String format, Object... arguments) { + log(fqcn, Level.INFO, t, format, arguments); + } + + @Override + public boolean isTraceEnabled() { + return isEnabled(Level.TRACE); + } + + @Override + public void trace(String fqcn, Throwable t, String format, Object... arguments) { + log(fqcn, Level.TRACE, t, format, arguments); + } + + @Override + public boolean isWarnEnabled() { + return isEnabled(Level.WARN); + } + + @Override + public void warn(String fqcn, Throwable t, String format, Object... arguments) { + log(fqcn, Level.WARN, t, format, arguments); + } + + @Override + public boolean isEnabled(Level level) { + return currentLevel.compareTo(level) <= 0; + } +} diff --git a/hutool-log/src/main/java/cn/hutool/log/dialect/console/ConsoleColorLogFactory.java b/hutool-log/src/main/java/cn/hutool/log/dialect/console/ConsoleColorLogFactory.java new file mode 100644 index 000000000..8bcb415ef --- /dev/null +++ b/hutool-log/src/main/java/cn/hutool/log/dialect/console/ConsoleColorLogFactory.java @@ -0,0 +1,24 @@ +package cn.hutool.log.dialect.console; + +import cn.hutool.log.Log; +import cn.hutool.log.LogFactory; + +/** + * @author hongda.li 2022-04-27 09:55 + */ +public class ConsoleColorLogFactory extends LogFactory { + + public ConsoleColorLogFactory(){ + super("Hutool Console Color Logging"); + } + + @Override + public Log createLog(String name) { + return new ConsoleColorLog(name); + } + + @Override + public Log createLog(Class clazz) { + return new ConsoleColorLog(clazz); + } +} diff --git a/hutool-log/src/test/java/cn/hutool/log/test/StaticLogTest.java b/hutool-log/src/test/java/cn/hutool/log/test/StaticLogTest.java index a93e83585..67bcdbbe6 100644 --- a/hutool-log/src/test/java/cn/hutool/log/test/StaticLogTest.java +++ b/hutool-log/src/test/java/cn/hutool/log/test/StaticLogTest.java @@ -1,5 +1,9 @@ package cn.hutool.log.test; +import cn.hutool.log.LogFactory; +import cn.hutool.log.dialect.console.ConsoleColorLog; +import cn.hutool.log.dialect.console.ConsoleColorLogFactory; +import cn.hutool.log.dialect.console.ConsoleLogFactory; import org.junit.Test; import cn.hutool.log.StaticLog; @@ -7,7 +11,20 @@ import cn.hutool.log.StaticLog; public class StaticLogTest { @Test public void test() { + LogFactory.setCurrentLogFactory(ConsoleLogFactory.class); StaticLog.debug("This is static {} log", "debug"); StaticLog.info("This is static {} log", "info"); } + + @Test + public void colorTest(){ + LogFactory.setCurrentLogFactory(ConsoleColorLogFactory.class); + StaticLog.debug("This is static {} log", "debug"); + StaticLog.info("This is static {} log", "info"); + StaticLog.error("This is static {} log", "error"); + StaticLog.warn("This is static {} log", "warn"); + StaticLog.trace("This is static {} log", "trace"); + ConsoleColorLog.setWarnColor(31); + StaticLog.warn("This is static {} log", "warn"); + } } From f118bf8e7e29fca713fb221487081d4075cdda03 Mon Sep 17 00:00:00 2001 From: Looly Date: Wed, 27 Apr 2022 11:22:31 +0800 Subject: [PATCH 42/52] add predicate for CombinationAnnotationElement --- CHANGELOG.md | 3 +- .../core/annotation/AnnotationUtil.java | 99 ++++++++++++------- .../CombinationAnnotationElement.java | 75 +++++++++++--- .../core/annotation/AnnotationForTest.java | 2 +- .../core/annotation/AnnotationUtilTest.java | 18 +++- .../annotation/RepeatAnnotationForTest.java | 0 6 files changed, 143 insertions(+), 54 deletions(-) mode change 100644 => 100755 hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationUtil.java mode change 100644 => 100755 hutool-core/src/test/java/cn/hutool/core/annotation/AnnotationUtilTest.java mode change 100644 => 100755 hutool-core/src/test/java/cn/hutool/core/annotation/RepeatAnnotationForTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index a1afd293c..deb498086 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ------------------------------------------------------------------------------------------------------------- -# 5.8.0.M4 (2022-04-25) +# 5.8.0.M4 (2022-04-27) ### ❌不兼容特性 * 【json 】 【可能兼容问题】JSONArray删除部分构造 @@ -21,6 +21,7 @@ * 【poi 】 ExcelWriter支持重复别名的数据写出(issue#I53APY@Gitee) * 【core 】 增加Hashids(issue#I53APY@Gitee) * 【core 】 ReflectUtil.newInstanceIfPossible添加枚举、数组等类型的默认实现 +* 【core 】 CombinationAnnotationElement增加过滤(pr#605@Gitee) ### 🐞Bug修复 * 【core 】 修复StrUtil.firstNonX非static问题(issue#2257@Github) diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationUtil.java b/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationUtil.java old mode 100644 new mode 100755 index c84de0073..a75affb8d --- a/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationUtil.java @@ -4,14 +4,19 @@ import cn.hutool.core.exceptions.UtilException; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ReflectUtil; -import java.lang.annotation.*; +import java.lang.annotation.Annotation; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.HashMap; -import java.util.HashSet; import java.util.Map; -import java.util.Set; +import java.util.function.Predicate; /** * 注解工具类
      @@ -39,52 +44,74 @@ public class AnnotationUtil { * 获取指定注解 * * @param annotationEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission - * @param isToCombination 是否为转换为组合注解 + * @param isToCombination 是否为转换为组合注解,组合注解可以递归获取注解的注解 * @return 注解对象 */ public static Annotation[] getAnnotations(AnnotatedElement annotationEle, boolean isToCombination) { - return (null == annotationEle) ? null : (isToCombination ? toCombination(annotationEle) : annotationEle).getAnnotations(); + return getAnnotations(annotationEle, isToCombination, (Predicate) null); } /** - * 获取可重复的注解列表 - * 用于带有同一个注解的多个组合注解 + * 获取组合注解 * - * @param annotationEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission - * @param annotationType 注解类型 - * @return 注解列表 - * @param 注解值类型 + * @param 注解类型 + * @param annotationEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission + * @param annotationType 限定的 + * @return 注解对象数组 + * @since 5.8.0 */ - public static Set getRepeatedAnnotations(AnnotatedElement annotationEle, Class annotationType) { - if (!Annotation.class.isAssignableFrom(annotationType)){ + public static T[] getCombinationAnnotations(AnnotatedElement annotationEle, Class annotationType) { + return getAnnotations(annotationEle, true, annotationType); + } + + /** + * 获取指定注解 + * + * @param 注解类型 + * @param annotationEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission + * @param isToCombination 是否为转换为组合注解,组合注解可以递归获取注解的注解 + * @param annotationType 限定的 + * @return 注解对象数组 + * @since 5.8.0 + */ + public static T[] getAnnotations(AnnotatedElement annotationEle, boolean isToCombination, Class annotationType) { + final Annotation[] annotations = getAnnotations(annotationEle, isToCombination, + (annotation -> null == annotationType || annotationType.isAssignableFrom(annotation.getClass()))); + + final T[] result = ArrayUtil.newArray(annotationType, annotations.length); + for (int i = 0; i < annotations.length; i++) { + //noinspection unchecked + result[i] = (T) annotations[i]; + } + return result; + } + + /** + * 获取指定注解 + * + * @param annotationEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission + * @param isToCombination 是否为转换为组合注解,组合注解可以递归获取注解的注解 + * @param predicate 过滤器,{@link Predicate#test(Object)}返回{@code true}保留,否则不保留 + * @return 注解对象 + * @since 5.8.0 + */ + public static Annotation[] getAnnotations(AnnotatedElement annotationEle, boolean isToCombination, Predicate predicate) { + if (null == annotationEle) { return null; } - Set annotationList = new HashSet<>(); - recursion(annotationList, annotationEle.getAnnotations(), annotationType); - return annotationList; - } - /** - * 递归获取注解列表 - * - * @param list 注解结果集 - * @param annotations 注解参数集 - * @param annotationType 注解类型 - * @param 注解值类型 - */ - private static void recursion(Set list, Annotation[] annotations, Class annotationType) { - for (Annotation annotation : annotations) { - Class clazz = annotation.getClass(); - if (annotationType.isAssignableFrom(clazz)) { - list.add((T) annotation); - } else if (!Retention.class.isAssignableFrom(clazz) - && !Target.class.isAssignableFrom(clazz) - && !Documented.class.isAssignableFrom(clazz) - && !Repeatable.class.isAssignableFrom(clazz) - && !Inherited.class.isAssignableFrom(clazz)) { - recursion(list, annotation.annotationType().getAnnotations(), annotationType); + if (isToCombination) { + if (null == predicate) { + return toCombination(annotationEle).getAnnotations(); } + return CombinationAnnotationElement.of(annotationEle, predicate).getAnnotations(); } + + final Annotation[] result = annotationEle.getAnnotations(); + if (null == predicate) { + return result; + } + return ArrayUtil.filter(result, predicate::test); } /** diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/CombinationAnnotationElement.java b/hutool-core/src/main/java/cn/hutool/core/annotation/CombinationAnnotationElement.java index 90c892be0..542793f79 100644 --- a/hutool-core/src/main/java/cn/hutool/core/annotation/CombinationAnnotationElement.java +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/CombinationAnnotationElement.java @@ -1,6 +1,7 @@ package cn.hutool.core.annotation; import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.map.TableMap; import java.io.Serializable; import java.lang.annotation.Annotation; @@ -11,22 +12,36 @@ import java.lang.annotation.Target; import java.lang.reflect.AnnotatedElement; import java.util.Arrays; import java.util.Collection; -import java.util.HashMap; import java.util.Map; import java.util.Set; +import java.util.function.Predicate; /** * 组合注解 对JDK的原生注解机制做一个增强,支持类似Spring的组合注解。
      * 核心实现使用了递归获取指定元素上的注解以及注解的注解,以实现复合注解的获取。 * - * @author Succy,Looly + * @author Succy, Looly * @since 4.0.9 **/ public class CombinationAnnotationElement implements AnnotatedElement, Serializable { private static final long serialVersionUID = 1L; - /** 元注解 */ + /** + * 创建CombinationAnnotationElement + * + * @param element 需要解析注解的元素:可以是Class、Method、Field、Constructor、ReflectPermission + * @param predicate 过滤器,{@link Predicate#test(Object)}返回{@code true}保留,否则不保留 + * @return CombinationAnnotationElement + * @since 5.8.0 + */ + public static CombinationAnnotationElement of(AnnotatedElement element, Predicate predicate) { + return new CombinationAnnotationElement(element, predicate); + } + + /** + * 元注解 + */ private static final Set> META_ANNOTATIONS = CollUtil.newHashSet(Target.class, // Retention.class, // Inherited.class, // @@ -36,10 +51,18 @@ public class CombinationAnnotationElement implements AnnotatedElement, Serializa Deprecated.class// ); - /** 注解类型与注解对象对应表 */ + /** + * 注解类型与注解对象对应表 + */ private Map, Annotation> annotationMap; - /** 直接注解类型与注解对象对应表 */ + /** + * 直接注解类型与注解对象对应表 + */ private Map, Annotation> declaredAnnotationMap; + /** + * 过滤器 + */ + private final Predicate predicate; /** * 构造 @@ -47,6 +70,18 @@ public class CombinationAnnotationElement implements AnnotatedElement, Serializa * @param element 需要解析注解的元素:可以是Class、Method、Field、Constructor、ReflectPermission */ public CombinationAnnotationElement(AnnotatedElement element) { + this(element, null); + } + + /** + * 构造 + * + * @param element 需要解析注解的元素:可以是Class、Method、Field、Constructor、ReflectPermission + * @param predicate 过滤器,{@link Predicate#test(Object)}返回{@code true}保留,否则不保留 + * @since 5.8.0 + */ + public CombinationAnnotationElement(AnnotatedElement element, Predicate predicate) { + this.predicate = predicate; init(element); } @@ -81,14 +116,14 @@ public class CombinationAnnotationElement implements AnnotatedElement, Serializa */ private void init(AnnotatedElement element) { final Annotation[] declaredAnnotations = element.getDeclaredAnnotations(); - this.declaredAnnotationMap = new HashMap<>(); + this.declaredAnnotationMap = new TableMap<>(); parseDeclared(declaredAnnotations); final Annotation[] annotations = element.getAnnotations(); - if(Arrays.equals(declaredAnnotations, annotations)) { + if (Arrays.equals(declaredAnnotations, annotations)) { this.annotationMap = this.declaredAnnotationMap; - }else { - this.annotationMap = new HashMap<>(); + } else { + this.annotationMap = new TableMap<>(); parse(annotations); } } @@ -104,7 +139,10 @@ public class CombinationAnnotationElement implements AnnotatedElement, Serializa for (Annotation annotation : annotations) { annotationType = annotation.annotationType(); if (false == META_ANNOTATIONS.contains(annotationType)) { - declaredAnnotationMap.put(annotationType, annotation); + if(test(annotation)){ + declaredAnnotationMap.put(annotationType, annotation); + } + // 测试不通过的注解,不影响继续递归 parseDeclared(annotationType.getDeclaredAnnotations()); } } @@ -120,9 +158,22 @@ public class CombinationAnnotationElement implements AnnotatedElement, Serializa for (Annotation annotation : annotations) { annotationType = annotation.annotationType(); if (false == META_ANNOTATIONS.contains(annotationType)) { - annotationMap.put(annotationType, annotation); + if(test(annotation)){ + annotationMap.put(annotationType, annotation); + } + // 测试不通过的注解,不影响继续递归 parse(annotationType.getAnnotations()); } } } -} \ No newline at end of file + + /** + * 检查给定的注解是否符合过滤条件 + * + * @param annotation 注解对象 + * @return 是否符合条件 + */ + private boolean test(Annotation annotation) { + return null == this.predicate || this.predicate.test(annotation); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/annotation/AnnotationForTest.java b/hutool-core/src/test/java/cn/hutool/core/annotation/AnnotationForTest.java index 83210ae90..f109e836d 100644 --- a/hutool-core/src/test/java/cn/hutool/core/annotation/AnnotationForTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/annotation/AnnotationForTest.java @@ -7,7 +7,7 @@ import java.lang.annotation.Target; /** * 用于单元测试的注解类
      - * 注解类相关说明见:https://www.cnblogs.com/xdp-gacl/p/3622275.html + * 注解类相关说明见:https://www.cnblogs.com/xdp-gacl/p/3622275.html * * @author looly */ diff --git a/hutool-core/src/test/java/cn/hutool/core/annotation/AnnotationUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/annotation/AnnotationUtilTest.java old mode 100644 new mode 100755 index c3077a626..167b62d2c --- a/hutool-core/src/test/java/cn/hutool/core/annotation/AnnotationUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/annotation/AnnotationUtilTest.java @@ -3,13 +3,23 @@ package cn.hutool.core.annotation; import org.junit.Assert; import org.junit.Test; -import java.util.Set; +import java.lang.annotation.Annotation; public class AnnotationUtilTest { + @Test - public void getRepeatAnnotationValueTest(){ - Set annotations = AnnotationUtil.getRepeatedAnnotations(ClassWithAnnotation.class, AnnotationForTest.class); - Assert.assertTrue(annotations != null && annotations.size() == 2); + public void getCombinationAnnotationsTest(){ + Annotation[] annotations = AnnotationUtil.getAnnotations(ClassWithAnnotation.class, true); + Assert.assertNotNull(annotations); + Assert.assertEquals(3, annotations.length); + } + + @Test + public void getCombinationAnnotationsWithClassTest(){ + AnnotationForTest[] annotations = AnnotationUtil.getCombinationAnnotations(ClassWithAnnotation.class, AnnotationForTest.class); + Assert.assertNotNull(annotations); + Assert.assertEquals(2, annotations.length); + Assert.assertEquals("测试", annotations[0].value()); } @Test diff --git a/hutool-core/src/test/java/cn/hutool/core/annotation/RepeatAnnotationForTest.java b/hutool-core/src/test/java/cn/hutool/core/annotation/RepeatAnnotationForTest.java old mode 100644 new mode 100755 From c2cdbb16f8e3722f45ec1e9709fba7f07b081aae Mon Sep 17 00:00:00 2001 From: Looly Date: Wed, 27 Apr 2022 11:25:04 +0800 Subject: [PATCH 43/52] fix bug --- CHANGELOG.md | 1 + hutool-core/src/main/java/cn/hutool/core/io/file/Tailer.java | 0 2 files changed, 1 insertion(+) mode change 100644 => 100755 hutool-core/src/main/java/cn/hutool/core/io/file/Tailer.java diff --git a/CHANGELOG.md b/CHANGELOG.md index deb498086..fb8b9529d 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,7 @@ * 【core 】 修复SimpleCache线程安全问题 * 【core 】 修复ClassLoaderUtil中可能的关联ClassLoader错位问题 * 【extra 】 修复Sftp错误内容解析大小写问题(issue#I53GPI@Gitee) +* 【core 】 修复Tailer当文件内容为空时,会报异常问题(pr#602@Gitee) ------------------------------------------------------------------------------------------------------------- diff --git a/hutool-core/src/main/java/cn/hutool/core/io/file/Tailer.java b/hutool-core/src/main/java/cn/hutool/core/io/file/Tailer.java old mode 100644 new mode 100755 From 8e797bd9c1bb013a737df241386be4f1b5ad655f Mon Sep 17 00:00:00 2001 From: Looly Date: Wed, 27 Apr 2022 13:49:31 +0800 Subject: [PATCH 44/52] add ansi --- CHANGELOG.md | 1880 +---------------- CHANGELOG_5.0-5.7.md | 1878 ++++++++++++++++ .../hutool/core/lang/ansi/Ansi8BitColor.java | 83 + .../hutool/core/lang/ansi/AnsiBackground.java | 109 + .../cn/hutool/core/lang/ansi/AnsiColor.java | 109 + .../cn/hutool/core/lang/ansi/AnsiElement.java | 18 + .../cn/hutool/core/lang/ansi/AnsiEncoder.java | 65 + .../cn/hutool/core/lang/ansi/AnsiStyle.java | 49 + .../hutool/core/lang/ansi/package-info.java | 6 + .../java/cn/hutool/core/util/NumberUtil.java | 4 +- .../java/cn/hutool/core/lang/ConsoleTest.java | 5 + .../core/lang/ansi/AnsiEncoderTest.java | 13 + 12 files changed, 2339 insertions(+), 1880 deletions(-) create mode 100755 CHANGELOG_5.0-5.7.md create mode 100755 hutool-core/src/main/java/cn/hutool/core/lang/ansi/Ansi8BitColor.java create mode 100755 hutool-core/src/main/java/cn/hutool/core/lang/ansi/AnsiBackground.java create mode 100755 hutool-core/src/main/java/cn/hutool/core/lang/ansi/AnsiColor.java create mode 100755 hutool-core/src/main/java/cn/hutool/core/lang/ansi/AnsiElement.java create mode 100755 hutool-core/src/main/java/cn/hutool/core/lang/ansi/AnsiEncoder.java create mode 100755 hutool-core/src/main/java/cn/hutool/core/lang/ansi/AnsiStyle.java create mode 100755 hutool-core/src/main/java/cn/hutool/core/lang/ansi/package-info.java create mode 100755 hutool-core/src/test/java/cn/hutool/core/lang/ansi/AnsiEncoderTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index fb8b9529d..bb996b424 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ * 【core 】 增加Hashids(issue#I53APY@Gitee) * 【core 】 ReflectUtil.newInstanceIfPossible添加枚举、数组等类型的默认实现 * 【core 】 CombinationAnnotationElement增加过滤(pr#605@Gitee) +* 【all 】 精简CHANGELOG ### 🐞Bug修复 * 【core 】 修复StrUtil.firstNonX非static问题(issue#2257@Github) @@ -153,1881 +154,4 @@ * 【core 】 FileUtil.getMimeType增加rar、7z支持(issue#I4ZBN0@Gitee) * 【json 】 JSON修复transient设置无效问题(issue#2212@Github) * 【core 】 修复IterUtil.getElementType获取结果为null的问题(issue#2222@Github) -* 【core 】 修复农历转公历在闰月时错误(issue#I4ZSGJ@Gitee) - -------------------------------------------------------------------------------------------------------------- -# 5.7.22 (2022-03-01) - -### 🐣新特性 -* 【poi 】 ExcelUtil.readBySax增加对POI-5.2.0的兼容性(issue#I4TJF4@Gitee) -* 【extra 】 Ftp增加构造(issue#I4TKXP@Gitee) -* 【core 】 GenericBuilder支持Map构建(pr#540@Github) -* 【json 】 新增TemporalAccessorSerializer -* 【core 】 使多个xxxBuilder实现Builder接口,扩展CheckedUtil(pr#545@Gitee) -* 【core 】 CheckedUtil删除第二个参数为RuntimeException的方法 -* 【core 】 FileUtil增加getTotalLines方法 -* 【db 】 MetaUtil增加getTableMeta重载(issue#2157@Github) -* 【http 】 增加HttpGlobalConfig.setDecodeUrl(issue#I4U8YQ@Gitee) -* 【core 】 增加Base58(pr#2162@Github) -* 【core 】 增加AntPathMatcher(issue#I4T7K5@Gitee) -* 【core 】 StrJoiner修改toString策略,调用不再修改Appendable -* 【core 】 StrJoiner增加length和merge方法 -* 【core 】 CRC16增加getHexValue方法(issue#I4VO3U@Gitee) - -### 🐞Bug修复 -* 【cache 】 修复ReentrantCache.toString方法线程不安全问题(issue#2140@Github) -* 【core 】 修复SystemPropsUtil.getInt返回long问题(pr#546@Gitee) -* 【crypto 】 修复SM2.getD前导0问题(pr#2149@Github) -* 【core 】 修复ChineseDate在1970年之前农历差一天问题(issue#I4UTPK@Gitee) -* 【core 】 修复CoordinateUtil精准问题及转换bug(pr#551@Gitee) -* 【json 】 修复JSONObject解析XML后没有返回的bug(issue#2160@Github) -* 【extra 】 修复GanymedUtil错误信息读取位置问题(issue#I4VDZ2@Gitee) - -------------------------------------------------------------------------------------------------------------- -# 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没有关闭问题 -* 【http 】 修复Action中HttpExchange没有关闭问题 - -------------------------------------------------------------------------------------------------------------- -# 5.7.20 (2022-01-20) - -### 🐣新特性 -* 【core 】 增加对null值友好的groupingBy操作的Collector实现,可指定map类型(pr#498@Gitee) -* 【core 】 增加KetamaHash(issue#2084@Github) -* 【crypto 】 增加SignUtil -* 【json 】 JSONGetter增加getBeanList方法 -* 【core 】 ObjectUtil 添加三个defaultIfXxxx方法,用于节省CPU及内存损耗(pr#2094@Github) -* 【db 】 增加单条数据原生upsert语义支持(pr#501@Gitee) -* 【core 】 在CollectorUtil提交Collectors.toMap的对null友好实现,避免NPE(pr#502@Gitee) -* 【http 】 增加HttpGlobalConfig.setIgnoreEOFError(issue#2092@Github) -* 【core 】 RandomUtil.randomStringWithoutStr排除字符串兼容大写字母(pr#503@Gitee) -* 【core 】 LocalDateTime增加isOverlap方法(pr#512@Gitee) -* 【core 】 Ipv4Util.getBeginIpLong、getEndIpLong改为public(pr#508@Gitee) -* -### 🐞Bug修复 -* 【core 】 修复setter重载导致匹配错误(issue#2082@Github) -* 【core 】 修复RegexPool汉字匹配范围小问题(pr#2081@Github) -* 【core 】 修复OS中的拼写错误(pr#500@Gitee) -* 【core 】 修复CustomKeyMap的merge失效问题(issue#2086@Github) -* 【core 】 修复FileUtil.appendLines换行问题(issue#I4QCEZ@Gitee) -* 【core 】 修复java.time.Month解析问题(issue#2090@Github) -* 【core 】 修复PathUtil.moveContent移动覆盖导致的问题(issue#I4QV0L@Gitee) -* 【core 】 修复Opt.ofTry中并发环境下线程安全问题(pr#504@Gitee) -* 【core 】 修复PatternFinder中end边界判断问题(issue#2099@Github) -* 【core 】 修复格式化为中文日期时,0被处理为空串(pr#507@Gitee) -* 【core 】 修复UrlPath转义冒号问题(issue#I4RA42@Gitee) - -------------------------------------------------------------------------------------------------------------- -# 5.7.19 (2022-01-07) - -### 🐣新特性 -* 【db 】 优化Condition参数拆分(pr#2046@Github) -* 【core 】 优化ArrayUtil.isAllEmpty性能(pr#2045@Github) -* 【core 】 CharSequenceUtil.replace方法支持增补字符(pr#2041@Github) -* 【extra 】 增加SshjSftp(pr#493@Gitee) -* 【core 】 增加CheckedUtil(pr#491@Gitee) -* 【extra 】 增加Sftp.isDir中的抛异常判断条件(issues#I4P9ED@Gitee) - -### 🐞Bug修复 -* 【http 】 HttpUtil重定向次数失效问题(issue#I4O28Q@Gitee) -* 【core 】 修复UrlPath空白path多/问题(issue#I49KAL@Gitee) -* 【core 】 修复ServletUtil写出文件时未添加双引号导致逗号等特殊符号引起的问题(issue#I4P1BF@Gitee) -* 【core 】 NumberUtil增加equals重载解决long传入判断问题(pr#2064@Github) -* 【core 】 修复CsvParser行号有误问题(pr#2065@Github) -* 【http 】 修复HttpRequest.of无法自动添加http前缀问题(issue#I4PEYL@Gitee) -* 【core 】 修复 `CharSequenceUtil.brief(str, maxLength)` 方法字符串越界问题,以及 `maxLength` 部分值时结果与预期不符的问题(pr#2068@Github) -* 【core 】 修复NamingCase中转换下划线字母+数字转换问题(issue#2070@Github) -* 【core 】 修复split空判断不一致问题(pr#496@Gitee) -* 【crypto 】 修复SM2.getDHex()前导0丢失,然后导致获取密钥错误(pr#2073@Github) -* 【core 】 修复关于Calculator.conversion()方法EmptyStackException的bug(pr#2076@Github) -* 【core 】 修复StrUtil.subBetweenAll循环bug(issue#I4PT3M@Gitee) - -------------------------------------------------------------------------------------------------------------- -# 5.7.18 (2021-12-25) - -### 🐣新特性 -* 【core 】 新增CollStreamUtil.groupKeyValue(pr#479@Gitee) -* 【core 】 新增DatePattern.createFormatter(pr#483@Gitee) -* 【core 】 增加IdUtil.getSnowflakeNextId(pr#485@Gitee) -* 【log 】 log4j2的编译依赖改为api,core为test依赖(pr#2019@Github) -* 【core 】 Img.scale缩小默认使用平滑模式,增加scale方法重载可选模式(issue#I4MY6X@Gitee) -* 【core 】 excel添加写入图片的方法(pr#486@Gitee) -* 【core 】 增加CollStreamUtil.groupBy(pr#484@Gitee) -* 【core 】 增加CollUtil.setValueByMap(pr#482@Gitee) -* 【core 】 LocalDateTimeUtil增加endOfDay重载(issue#2025@Github) -* 【core 】 IoCopier增加setFlushEveryBuffer方法(issue#2022@Github) -* -### 🐞Bug修复 -* 【core 】 LineReadWatcher#onModify文件清空判断问题(issue#2013@Github) -* 【core 】 修复4位bytes转换float问题(issue#I4M0E4@Gitee) -* 【core 】 修复CharSequenceUtil.replace问题(issue#I4M16G@Gitee) -* 【json 】 修复JSONObject 初始化大小值未被使用问题(issue#2016@Github) -* 【core 】 修复StrUtil.startWith都为null返回错误问题(issue#I4MV7Q@Gitee) -* 【core 】 修复PasswdStrength检测问题(issue#I4N48X@Gitee) -* 【core 】 修复UserAgentUtil解析EdgA无法识别问题(issue#I4MCBP@Gitee) -* 【extra 】 修复Archiver路径前带/问题(issue#I4NS0F@Gitee) -* 【extra 】 修复getMainColor方法中参数rgbFilters无效问题(pr#2034@Github) -* 【core 】 修复ChineseDate无法区分闰月问题(issue#I4NQQW@Gitee) -* 【core 】 修复BeanDesc大小写误判问题(issue#2009@Github) - -------------------------------------------------------------------------------------------------------------- -# 5.7.17 (2021-12-09) - -### 🐣新特性 -* 【core 】 增加AsyncUtil(pr#457@Gitee) -* 【http 】 增加HttpResource(issue#1943@Github) -* 【http 】 增加BytesBody、FormUrlEncodedBody -* 【cron 】 TaskTable.remove增加返回值(issue#I4HX3B@Gitee) -* 【core 】 Tree增加filter、filterNew、cloneTree、hasChild方法(issue#I4HFC6@Gitee) -* 【poi 】 增加ColumnSheetReader及ExcelReader.readColumn,支持读取某一列 -* 【core 】 IdCardUtil.isValidCard不再自动trim(issue#I4I04O@Gitee) -* 【core 】 改进TextFinder,支持限制结束位置及反向查找模式 -* 【core 】 Opt增加部分方法(pr#459@Gitee) -* 【core 】 增加DefaultCloneable(pr#459@Gitee) -* 【core 】 CollStreamUtil增加是否并行的重载(pr#467@Gitee) -* 【core 】 ResourceClassLoader增加缓存(pr#1959@Github) -* 【crypto 】 增加CipherWrapper,增加setRandom(issue#1958@Github) -* 【core 】 Opt增加ofTry方法(pr#1956@Github) -* 【core 】 DateUtil.toIntSecond标记为弃用(issue#I4JHPR@Gitee) -* 【db 】 Db.executeBatch标记一个重载为弃用(issue#I4JIPH@Gitee) -* 【core 】 增加CharSequenceUtil.subPreGbk重载(issue#I4JO2E@Gitee) -* 【core 】 ReflectUtil.getMethod排除桥接方法(pr#1965@Github) -* 【http 】 completeFileNameFromHeader在使用path为路径时,自动解码(issue#I4K0FS@Gitee) -* 【core 】 CopyOptions增加override配置(issue#I4JQ1N@Gitee) -* 【poi 】 SheetRidReader可以获取所有sheet名(issue#I4JA3M@Gitee) -* 【core 】 AsyncUtil.waitAny增加返回值(pr#473@Gitee) -* 【core 】 Calculator.compare改为private(issue#1982@Github) -* 【core 】 NumberUtil增加isOdd、isEven方法(pr#474@Gitee) -* 【http 】 增加HttpGlobalConfig.setBoundary,删除MultipartBody.BOUNDARY和getContentType(issue#I4KSLY@Gitee) -* 【core 】 DateTime增加setMinimalDaysInFirstWeek(issue#1988@Github) -* 【db 】 Db增加query重载,可支持自定义PreparedStatement,从而支持游标(issue#I4JXWN@Gitee) -* 【cache 】 CacheObj增加getExpiredTime等方法(issue#I4LE80@Gitee) -* 【extra 】 Ftp增加backToPwd方法(issue#2004@Github) -* 【core 】 CollStreamUtil修改集合中null处理问题(pr#478@Gitee) -* -### 🐞Bug修复 -* 【core 】 修复FileResource构造fileName参数无效问题(issue#1942@Github) -* 【cache 】 修复WeakCache键值强关联导致的无法回收问题(issue#1953@Github) -* 【core 】 修复ZipUtil相对路径父路径获取null问题(issue#1961@Github) -* 【http 】 修复HttpUtil.normalizeParams未判空导致的问题(issue#1975@Github) -* 【poi 】 修复读取日期类型的自定义样式单元格时间结果为1899年问题(pr#1977@Github) -* 【poi 】 修复SoapClient参数未使用问题 -* 【core 】 修复HashUtil.cityHash128参数未使用问题 -* 【core 】 修复DateUtil.formatChineseDate显示问题(issue#I4KK5F@Gitee) -* 【poi 】 修复CellUtil.setCellValueStyle空导致值无法写入问题(issue#1995@Github) -* 【poi 】 修复CellUtil.setComment参数设置错误问题 -* 【core 】 修复QueryBuilder解析路径导致的错误(issue#1989@Github) -* 【core 】 修复DateTime.between中DateUnit无效问题 -* 【poi 】 修复StyleUtil.getFormat非static问题(issue#I4LGNP@Gitee) -* 【crypto 】 修复SM2.getD返回bytes包含符号位的问题(issue#2001@Github) - -------------------------------------------------------------------------------------------------------------- - -# 5.7.16 (2021-11-07) - -### 🐣新特性 -* 【core 】 增加DateTime.toLocalDateTime -* 【core 】 CharSequenceUtil增加normalize方法(pr#444@Gitee) -* 【core 】 MailAccount增加setEncodefilename()方法,可选是否编码附件的文件名(issue#I4F160@Gitee) -* 【core 】 MailAccount中charset增加null时的默认规则 -* 【core 】 NumberUtil.compare修正注释说明(issue#I4FAJ1@Gitee) -* 【core 】 增加RFC3986类 -* 【extra 】 Sftp增加put和upload重载(issue#I4FGDH@Gitee) -* 【core 】 TemporalUtil增加toChronoUnit、toTimeUnit方法(issue#I4FGDH@Gitee) -* 【core 】 StopWatch增加prettyPrint重载(issue#1910@Github) -* 【core 】 修改RegexPool中Ipv4正则 -* 【json 】 Filter改为MutablePair,以便编辑键值对(issue#1921@Github) -* 【core 】 Opt增加peeks方法(pr#445@Gitee) -* 【extra 】 MailAccount中user默认值改为邮箱全称(issue#I4FYVY@Gitee) -* 【core 】 增加CoordinateUtil(pr#446@Gitee) -* 【core 】 DateUtil增加rangeToList重载(pr#1925@Github) -* 【core 】 CollUtil增加safeContains方法(pr#1926@Github) -* 【core 】 ActualTypeMapperPool增加getStrKeyMap方法(pr#447@Gitee) -* 【core 】 TreeUtil增加walk方法(pr#1932@Gitee) -* 【crypto 】 SmUtil增加sm3WithSalt(pr#454@Gitee) -* 【http 】 增加HttpInterceptor(issue#I4H1ZV@Gitee) -* 【core 】 Opt增加flattedMap(issue#I4H1ZV@Gitee) - -### 🐞Bug修复 -* 【core 】 修复UrlBuilder.addPath歧义问题(issue#1912@Github) -* 【core 】 修复StrBuilder中总长度计算问题(issue#I4F9L7@Gitee) -* 【core 】 修复CharSequenceUtil.wrapIfMissing预定义长度计算问题(issue#I4FDZ2@Gitee) -* 【poi 】 修复合并单元格为日期时,导出单元格数据为数字问题(issue#1911@Github) -* 【core 】 修复CompilerUtil.getFileManager参数没有使用的问题(issue#I4FIO6@Gitee) -* 【core 】 修复NetUtil.isInRange的cidr判断问题(pr#1917@Github) -* 【core 】 修复RegexPool中对URL正则匹配问题(issue#I4GRKD@Gitee) -* 【core 】 修复UrlQuery对于application/x-www-form-urlencoded问题(issue#1931@Github) - -------------------------------------------------------------------------------------------------------------- - -# 5.7.15 (2021-10-21) - -### 🐣新特性 -* 【db 】 Db.quietSetAutoCommit增加判空(issue#I4D75B@Gitee) -* 【core 】 增加RingIndexUtil(pr#438@Gitee) -* 【core 】 Assert增加checkBetween重载(pr#436@Gitee) -* 【core 】 ReUtil增加命名分组重载(pr#439@Gitee) -* 【json 】 toString和writer增加Filter(issue#I4DQNQ@Gitee) -* 【core 】 ContentType增加build重载(pr#1898@Github) -* 【bom 】 支持scope=import方式引入(issue#1561@Github) -* 【core 】 新增Hash接口,HashXXX继承此接口 -* 【core 】 ZipUtil增加append方法(pr#441@Gitee) -* 【core 】 CollUtil增加重载(issue#I4E9FS@Gitee) -* 【core 】 CopyOptions新增setFieldValueEditor(issue#I4E08T@Gitee) -* 【core 】 增加SystemPropsUtil(issue#1918@Gitee) -* 【core 】 增加`hutool.date.lenient`系统属性(issue#1918@Gitee) - -### 🐞Bug修复 -* 【core 】 修复CollUtil.isEqualList两个null返回错误问题(issue#1885@Github) -* 【poi 】 修复ExcelWriter多余调试信息导致的问题(issue#1884@Github) -* 【poi 】 修复TemporalAccessorUtil.toInstant使用DateTimeFormatter导致问题(issue#1891@Github) -* 【poi 】 修复sheet.getRow(y)为null导致的问题(issue#1893@Github) -* 【cache 】 修复LRUCache线程安全问题(issue#1895@Github) -* 【crypto 】 修复KeyUtil异常信息参数丢失问题(issue#1902@Github) -* 【core 】 修复StrUtil.split和splittoArray不一致问题(issue#I4ELU5@Github) -* 【core 】 修复SymmetricCrypto未关闭CipherOutputStream导致的问题(issue#I4EMST@Gitee) -* 【core 】 修复QueryBuilder对/转义问题(issue#1904@Github) - -------------------------------------------------------------------------------------------------------------- - -# 5.7.14 (2021-10-09) - -### 🐣新特性 -* 【extra 】 修复HttpCookie设置cookies的方法,不符合RFC6265规范问题(issue#I4B70D@Gitee) -* 【http 】 优化Browser版本正则判断 -* 【setting】 增加YamlUtil -* 【extra 】 SenvenZExtractor改名为SevenZExtractor,增加getFirst、get方法 -* 【core 】 DateConverter修改返回java.util.Date而非DateTime(issue#I4BOAP@Gitee) -* 【core 】 增加IterableIter、ComputeIter -* 【core 】 CsvConfig增加disableComment方法(issue#1842@Github) -* 【core 】 DateTime构造和DateUtil.parse可选是否宽松模式(issue#1849@Github) -* 【core 】 TreeBuilder增加部分根节点set方法(issue#1848@Github) -* 【core 】 优化Base64.isBase64方法:减少一次多余的判断(pr#1860@Github) -* 【cache 】 优化FIFOCache未设置过期策略时,无需遍历判断过期对象(pr#425@Gitee) -* 【core 】 增加Opt类(pr#426@Gitee) -* 【core 】 Week增加of重载,支持DayOfWek(pr#1872@Github) -* 【poi 】 优化read,避免多次创建CopyOptions(issue#1875@Github) -* 【core 】 优化CsvReader,实现可控遍历(pr#1873@Github) -* 【core 】 优化Base64.isBase64判断(pr#1879@Github) -* 【core 】 新增StrFormatter.formatWith(pr#430@Gitee) - -### 🐞Bug修复 -* 【http 】 修复HttpCookie设置cookies的方法,不符合RFC6265规范问题(pr#418@Gitee) -* 【http 】 修复Extractor中filter无效问题 -* 【json 】 修复JSONGetter.getJSONArray判断null的问题(issue#I4C15H@Gitee) -* 【db 】 修复Condition没占位符的情况下sql没引号问题(issue#1846@Github) -* 【cache 】 修复FIFOCache中remove回调无效问题(pr#1856@Github) -* 【json 】 修复JSONArray.set中,index为0报错问题(issue#1858@Github) -* 【core 】 修复FileUtil.checkSlip中getCanonicalPath异常引起的问题(issue#1858@Github) - -------------------------------------------------------------------------------------------------------------- - -# 5.7.13 (2021-09-17) - -### 🐣新特性 -* 【core 】 CsvReadConfig增加trimField选项(issue#I49M0C@Gitee) -* 【http 】 HttpBase增加clearHeaders方法(issue#I49P23@Gitee) -* 【core 】 CsvWriter的write和writeBeans参数改为Iterable(issue#I49O4S@Gitee) -* 【core 】 BitStatusUtil添加来源声明(issue#1824@Github) -* 【core 】 UrlQuery.build增加重载,支持可选是否转义(issue#I4AIX1@Gitee) -* 【core 】 ListUtil增加swapTo和swapElement方法(pr#416@Gitee) -* 【poi 】 ExcelWriter支持Hyperlink(issue#I49QAL@Gitee) -* -### 🐞Bug修复 -* 【core 】 修复FuncKey函数无效问题 -* 【core 】 修复ImgUtil.copyImage读取网络URL后宽高报错问题(issue#1821@Github) -* 【core 】 修复StrJoiner.append配置丢失问题(issue#I49K1L@Gitee) -* 【core 】 修复EscapeUtil特殊字符的hex长度不足导致的问题(issue#I49JU8@Gitee) -* 【core 】 修复UrlBuilder对Fragment部分编码问题(issue#I49KAL@Gitee) -* 【core 】 修复Enum转换的bug(issue#I49VZB@Gitee) -* 【json 】 修复JSONUtil.parse对于MapWrapper识别问题 -* 【core 】 修复IdcardUtil.isValidCard判断问题(issue#I4AJ8S@Gitee) - -------------------------------------------------------------------------------------------------------------- - -# 5.7.12 (2021-09-09) - -### 🐣新特性 -* 【system 】 OshiUtil增加getCurrentProcess方法 -* 【extra 】 SpringUtil增加getApplicationName、publishEvent方法(issue#I485NZ@Gitee) -* 【core 】 BeanUtil.getProperty增加判空(issue#I488HA@Gitee) -* 【core 】 OptionalBean弃用(pr#1182@Github) -* 【setting】 Setting、Props持有URL改为持有Resource(pr#1182@Github) -* 【json 】 JSONUtil.toJsonStr增加重载,支持JSONConfig(issue#I48H5L@Gitee) -* 【crypto 】 SymmetricCrypto增加setMode方法,update采用累加模式(pr#1642@Github) -* 【core 】 ZipReader支持Filter -* 【all 】 Sftp、Ftp、HttpDownloader增加download重载,支持避免传输文件损坏(pr#407@Gitee) -* 【crypto 】 AES修改构造的IvParameterSpec为AlgorithmParameterSpec(issue#1814@Gitee) -* 【crypto 】 增加FPE、ZUC(issue#1814@Gitee) - -### 🐞Bug修复 -* 【core 】 修复ListUtil.split方法越界问题(issue#I48Q0P@Gitee) -* 【core 】 修复QrCode的isTryHarder、isPureBarcode设置无效问题(issue#1815@Github) -* 【core 】 修复DatePattern.CHINESE_DATE_FORMATTER错误问题(issue#I48ZE3@Gitee) -* 【core 】 修复ListUtil.split错误问题 -* 【core 】 修复NumberUtil.parseNumber长数字越界问题(issue#1818@Github) - -------------------------------------------------------------------------------------------------------------- - -# 5.7.11 (2021-08-31) - -### 🐣新特性 -* 【crypto 】 修改SymmetricCrypto初始化逻辑 -* 【core 】 FileTypeUtil增加对wps编辑的docx的识别(issue#I47JGH@Gitee) -* 【core 】 Money修改构造,0表示读取所有分(issue#1796@Github) -* 【json 】 增加JSONXMLParser和JSONXMLSerializer -* 【json 】 XML支持自定义内容标签(issue#I47TV8@Gitee) -### 🐞Bug修复 -* 【cron 】 **重要**修复Scheduler启动默认线程池为null的bug(issue#I47PZW@Gitee) - -------------------------------------------------------------------------------------------------------------- - -# 5.7.10 (2021-08-26) - -### 🐣新特性 -* 【core 】 增加NamingCase类 -* 【core 】 ListUtil增加page方法重载(pr#1761@Github) -* 【crypto 】 增加ASN1Util -* 【core 】 CsvConfig改为泛型形式 -* 【core 】 增加Partition -* 【http 】 SoapClient.sendForResponse改为public(issue#I466NN@Gitee) -* 【core 】 XmlUtil增加append重载(issue#I466Q0@Gitee) -* 【poi 】 增加EscapeStrCellSetter(issue#I466ZZ@Gitee) -* 【poi 】 ExcelBase增加renameSheet、cloneSheet(issue#I466ZZ@Gitee) -* 【core 】 ListUtil增加splitAvg方法(pr#397@Gitee) -* 【poi 】 Excel07SaxReader支持数字类型sheet名称、支持sheetName:名称前缀(issue#I46OMA@Gitee) -* 【extra 】 Mail增加build方法(issue#I46LGE@Gitee) -* 【core 】 XmlUtil增加beanToXml重载,支持忽略null -* 【core 】 添加NullComparator、FuncComparator(issue#I471X7@Gitee) -* 【core 】 LambdaUtil添加getFieldName(issue#I4750U@Gitee) -* 【cron 】 Scheduler增加setThreadExecutor(issue#I47A6N@Gitee) -* 【core 】 CharsetDetector增加detect重载,支持自定义缓存大小(issue#I478E5@Gitee) -* 【core 】 增加PartitionIter(pr#402@Gitee) -* 【all 】 增加异常爬栈开关(pr#403@Gitee) -* 【core 】 优化Combination中C(n,n)的逻辑(pr#1792@Github) -* 【core 】 Csv读写支持别名(issue#1791@Github) - -### 🐞Bug修复 -* 【core 】 修复MapUtil.sort比较器不一致返回原map的问题(issue#I46AQJ@Gitee) -* 【core 】 修复JSONSupport默认循环引用导致的问题(issue#1779@Github) -* 【poi 】 修复ExcelUtil.readBySax资源没有释放问题(issue#1789@Github) - -------------------------------------------------------------------------------------------------------------- - -# 5.7.9 (2021-08-16) - -### 🐣新特性 -* 【extra 】 FileUtil增加moveContent方法(issue#I45H30@Gitee) -* 【extra 】 JschPool.getSession获取时检查是否连接状态(issue#I45N5I@Gitee) -* -### 🐞Bug修复 -* 【extra 】 修复TinyPinyinEngine空构造造成可能的误判问题 -* 【http 】 修复在gzip模式下Content-Length服务端设置异常导致的问题(issue#1766@Github) -* 【db 】 修复PooledDataSource关闭逻辑错误问题 - -------------------------------------------------------------------------------------------------------------- - -# 5.7.8 (2021-08-11) - -### 🐣新特性 -* 【core 】 MapProxy支持return this的setter方法(pr#392@Gitee) -* 【core 】 BeeDSFactory移除sqlite事务修复代码,新版本BeeCP已修复 -* 【core 】 增加compress包,扩充Zip操作灵活性 -* 【json 】 增加JSONBeanParser -* 【poi 】 增加CellSetter,可以自定义单元格值写出 -* 【poi 】 CsvReader增加readFromStr(pr#1755@Github) -* 【socket 】 SocketUtil增加connection方法 -* 【extra 】 JschUtil增加bindPort重载方法(issue#I44UTH@Github) -* 【core 】 DefaultTrustManager改为继承X509ExtendedTrustManager -* 【core 】 增加IoCopier - -### 🐞Bug修复 -* 【core 】 改进NumberChineseFormatter算法,补充完整单元测试,解决零问题 -* 【core 】 修复Img变换操作图片格式问题(issue#I44JRB@Gitee) - -------------------------------------------------------------------------------------------------------------- - -# 5.7.7 (2021-08-02) - -### 🐣新特性 -* 【core 】 增加LookupFactory和MethodHandleUtil(issue#I42TVY@Gitee) -* 【core 】 改进RegexPool.TEL支持无-号码(pr#387@Gitee) -* 【core 】 PhoneUtil中新增获取固话号码中区号,以及固话号码中号码的方法(pr#387@Gitee) -* 【json 】 JSONGetter增加getLocalDateTime方法(pr#387@Gitee) -* 【core 】 增加JNDIUtil(issue#1727@Github) -* 【core 】 NetUtil增加getDnsInfo方法(issue#1727@Github) -* 【core 】 SpringUtil增加unregisterBean方法(pr#388@Gitee) -* 【core 】 优化TextSimilarity公共子串算法(issue#I42A6V@Gitee) -* 【core 】 优化DateUtil.parse对UTC附带时区字符串解析(issue#I437AP@Gitee) - -### 🐞Bug修复 -* 【jwt 】 修复JWTUtil中几个方法非static的问题(issue#1735@Github) -* 【core 】 修复SpringUtil无法处理autowired问题(pr#388@Gitee) -* 【core 】 修复AbsCollValueMap中常量拼写错误(pr#1736@Github) -* 【core 】 修复FileUtil.del在文件只读情况下无法删除的问题(pr#389@Gitee) -* 【core 】 修复FileUtil.move在不同分区下失败的问题(pr#390@Gitee) -* 【core 】 修复FileUtil.copy强制覆盖参数无效问题 -* 【core 】 修复NumberChineseFormatter转换金额多零问题(issue#1739@Github) - -------------------------------------------------------------------------------------------------------------- - -# 5.7.6 (2021-07-28) - -### 🐣新特性 -* 【core 】 增加FieldsComparator(pr#374@Gitee) -* 【core 】 FileUtil.del采用Files.delete实现 -* 【core 】 改进Base64.isBase64方法增加等号判断(issue#1710@Github) -* 【core 】 Sftp增加syncUpload方法(pr#375@Gitee) -* 【core 】 改进NetUtil.getLocalHost逻辑(issue#1717@Github) -* 【core 】 UseragentUtil增加QQ、alipay、taobao、uc等浏览器识别支持(issue#1719@Github) -* 【http 】 HttpRequest.form方法判断集合增强(pr#381@Gitee) -* 【core 】 NumberUtil增加calculate方法 -* 【core 】 优化TextSimilarity.longestCommonSubstring性能(issue#I42A6V@Gitee) -* 【core 】 MultipartRequestInputStream改为使用long以支持大文件(issue#I428AN@Gitee) -* 【core 】 RobotUtil增加getDelay、getRobot方法(pr#1725@Github) -* 【json 】 JSON输出支持ignoreNull(issue#1728@Github) -* 【core 】 DateUtil和LocalDateTimeUtil增加isWeekend方法(issue#I42N5A@Gitee) - -### 🐞Bug修复 -* 【core 】 修复RobotUtil双击右键问题(pr#1721@Github) -* 【core 】 修复FileTypeUtil判断wps修改过的xlsx误判为jar的问题(pr#380@Gitee) -* 【core 】 修复Sftp.isDir异常bug(pr#378@Gitee) -* 【core 】 修复BeanUtil.copyProperties集合元素复制成功,读取失败的问题(issue#I41WKP@Gitee) -* 【core 】 修复NumberChineseFormatter.chineseToNumber十位数错误(issue#1726@github) -* 【poi 】 修复BeanSheetReader.read中字段对象为空导致的报错(issue#1729@Github) -* 【core 】 修复DateConverter转换java.sql.Date问题(issue#1729@Github) -* 【extra 】 修复CompressUtil中部分方法非static的问题(pr#385@Gitee) -* 【core 】 修复ByteUtil转换端序错误问题(pr#384@Gitee) -* 【core 】 修复UserAgentUtil判断浏览器顺序问题(issue#I42LYW@Gitee) - -------------------------------------------------------------------------------------------------------------- - -# 5.7.5 (2021-07-19) - -### 🐣新特性 -* 【core 】 DateUtil增加ceiling重载,可选是否归零毫秒 -* 【core 】 IterUtil增加firstMatch方法 -* 【core 】 增加NanoId -* 【core 】 MapBuilder增加put方法(pr#367@Gitee) -* 【core 】 StrUtil.insert支持负数index -* 【core 】 Calculator类支持取模运算(issue#I40DUW@Gitee) -* 【core 】 增加Base64.isBase64方法(issue#1710@Github) -* 【core 】 ManifestUtil新增方法getManifest(Class cls)(pr#370@Gitee) -* 【extra 】 AbstractFtp增加isDir方法(issue#1716@Github) -* 【core 】 修改FileUtil异常信息内容(pr#1713@Github) - -### 🐞Bug修复 -* 【core 】 修复FileUtil.normalize处理上级路径的问题(issue#I3YPEH@Gitee) -* 【core 】 修复ClassScanner扫描空包遗漏问题 -* 【core 】 修复FastDatePrinter歧义问题(pr#366@Gitee) -* 【core 】 修复DateUtil.format格式化Instant报错问题(issue#I40CY2@Gitee) -* 【core 】 修复StrUtil.toUnderlineCase大写问题(issue#I40CGS@Gitee) -* 【jwt 】 修复JWT.validate报错问题(issue#I40MR2@Gitee) -* 【core 】 修复StrUtil.brief越界问题 - -------------------------------------------------------------------------------------------------------------- - -# 5.7.4 (2021-07-10) - -### 🐣新特性 -* 【crypto 】 SmUtil.sm4统一返回类型(issue#I3YKD4@Gitee) -* 【core 】 修改MapUtil.get传入null返回默认值而非null(issue#I3YKBC@Gitee) -* 【core 】 HexUtil增加hexToLong、hexToInt(issue#I3YQEV@Gitee) -* 【core 】 CsvWriter增加writer.write(csvData)的方法重载(pr#353@Gitee) -* 【core 】 新增AbsCollValueMap(issue#I3YXF0@Gitee) -* 【crypto 】 HOTP缓存改为8位,新增方法(pr#356@Gitee) -* 【setting】 Props增加toProperties方法(issue#1701@Github) -* 【http 】 UserAgent增加getOsVersion方法(issue#I3YZUQ@Gitee) -* 【jwt 】 JWT增加validate方法(issue#I3YDM4@Gitee) -* 【core 】 CscReader支持指定读取开始行号和结束行号(issue#I3ZMZL@Gitee) - -### 🐞Bug修复 -* 【core 】 修复RadixUtil.decode非static问题(issue#I3YPEH@Gitee) -* 【core 】 修复EqualsBuilder数组判断问题(pr#1694@Github) -* 【setting】 修复Props中Charset对象无法序列化的问题(pr#1694@Github) -* 【db 】 修复PageResult首页判断逻辑问题(issue#1699@Github) -* 【core 】 修复IdcardUtil可能数组越界问题(pr#1702@Github) -* 【core 】 修复FastByteArrayOutputStream索引越界问题(issue#I402ZP@Github) - -------------------------------------------------------------------------------------------------------------- - -# 5.7.3 (2021-06-29) - -### 🐣新特性 -* 【core 】 增加Convert.toSet方法(issue#I3XFG2@Gitee) -* 【core 】 CsvWriter增加writeBeans方法(pr#345@Gitee) -* 【core 】 新增JAXBUtil(pr#346@Gitee) -* 【poi 】 ExcelWriter新增setColumnStyleIfHasData和setRowStyleIfHasData(pr#347@Gitee) -* 【json 】 用户自定义日期时间格式时,解析也读取此格式 -* 【core 】 增加可自定义日期格式GlobalCustomFormat -* 【jwt 】 JWT修改默认有序,并规定payload日期格式为秒数 -* 【json 】 增加JSONWriter -* 【core 】 IdUtil增加getWorkerId和getDataCenterId(issueI3Y5NI@Gitee) -* 【core 】 JWTValidator增加leeway重载 -* 【core 】 增加RegexPool(issue#I3W9ZF@Gitee) - -### 🐞Bug修复 -* 【json 】 修复XML转义字符的问题(issue#I3XH09@Gitee) -* 【core 】 修复FormatCache中循环引用异常(pr#1673@Github) -* 【core 】 修复IdcardUtil.getIdcardInfo.getProvinceCode获取为汉字的问题(issue#I3XP4Q@Gitee) -* 【core 】 修复CollUtil.subtract使用非标准Set等空指针问题(issue#I3XN1Z@Gitee) -* 【core 】 修复SqlFormatter部分SQL空指针问题(issue#I3XS44@Gitee) -* 【core 】 修复DateRange计算问题(issue#I3Y1US@Gitee) -* 【core 】 修复BeanCopier中setFieldNameEditor失效问题(pr#349@Gitee) -* 【core 】 修复ArrayUtil.indexOfSub查找bug(issue#1683@Github) -* 【core 】 修复Node的权重比较空指针问题(issue#1681@Github) -* 【core 】 修复UrlQuery传入无参数路径解析问题(issue#1688@Github) - -------------------------------------------------------------------------------------------------------------- - -# 5.7.2 (2021-06-20) - -### 🐣新特性 -* 【core 】 增加UserPassAuthenticator -* 【db 】 获取分组数据源时,移除公共属性项 -* 【core 】 增加StrJoiner -* 【core 】 增加TreeBuilder -* 【core 】 IterUtil增加getFirstNonNull方法 -* 【core 】 NumberUtil判空改为isBlank(issue#1664@Github) -* 【jwt 】 增加JWTValidator、RegisteredPayload -* 【db 】 增加Phoenix方言(issue#1656@Github) - -### 🐞Bug修复 -* 【db 】 修复Oracle下别名错误造成的SQL语法啊错误(issue#I3VTQW@Gitee) -* 【core 】 修复ConcurrencyTester重复使用时开始测试未清空之前任务的问题(issue#I3VSDO@Gitee) -* 【poi 】 修复使用BigWriter写出,ExcelWriter修改单元格值失败的问题(issue#I3VSDO@Gitee) -* 【jwt 】 修复Hmac算法下生成签名是hex的问题(issue#I3W6IP@Gitee) -* 【core 】 修复TreeUtil.build中deep失效问题(issue#1661@Github) -* 【json 】 修复XmlUtil.xmlToBean判断问题(issue#1663@Github) - -------------------------------------------------------------------------------------------------------------- - -# 5.7.1 (2021-06-16) - -### 🐣新特性 -* 【db 】 NamedSql支持in操作(issue#1652@Github) -* 【all 】 JWT模块加入到all和bom包中(issue#1654@Github) -* 【core 】 CollUtil删除所有Map相关操作 -* 【all 】 **重要!** 删除过期方法 -* 【core 】 增加IterChian类 - -### 🐞Bug修复 - -------------------------------------------------------------------------------------------------------------- - -# 5.7.0 (2021-06-15) - -### 🐣新特性 -* 【jwt 】 添加JWT模块,实现了JWT的创建、解析和验证 -* 【crypto 】 SymmetricCrypto增加update方法(pr#1642@Github) -* 【crypto 】 MacEngine增加接口update,doFinal,reset等接口 -* 【core 】 StrSpliter更名为StrSplitter -* 【core 】 NumberUtil的decimalFormat增加数字检查 -* 【http 】 HttpBase的httpVersion方法设置为无效(issue#1644@Github) -* 【extra 】 Sftp增加download重载(issue#I3VBSL@Gitee) -* 【cache 】 修改FIFOCache初始大小(issue#1647@Github) - -### 🐞Bug修复 -* 【db 】 修复count方法丢失参数问题(issue#I3VBSL@Gitee) -* 【db 】 修复SpringUtil工具在`@PostConstruct` 注解标注的方法下失效问题(pr#341@Gitee) -* 【json 】 修复JSONUtil.parse方法未判断有序问题(issue#I3VHVY@Gitee) -* 【json 】 修复JSONArray.put越界无法加入问题(issue#I3VMLU@Gitee) - -------------------------------------------------------------------------------------------------------------- - -# 5.6.7 (2021-06-08) - -### 🐣新特性 -* 【core 】 CharSequenceUtil增加join重载(issue#I3TFJ5@Gitee) -* 【http 】 HttpRequest增加form方法重载(pr#337@Gitee) -* 【http 】 ImgUtil增加getMainColor方法(pr#338@Gitee) -* 【core 】 改进TreeUtil.buid算法性能(pr#1594@Github) -* 【core 】 CsvConfig的setXXX返回this(issue#I3UIQF@Gitee) -* 【all 】 增加jmh基准测试 -* 【core 】 增加StreamUtil和CollectorUtil -* 【poi 】 增加content-type(pr#1639@Github) - -### 🐞Bug修复 -* 【core 】 修复FileUtil.normalize去掉末尾空格问题(issue#1603@Github) -* 【core 】 修复CharsetDetector流关闭问题(issue#1603@Github) -* 【core 】 修复RuntimeUtil.exec引号内空格被切分的问题(issue#I3UAYB@Gitee) - -------------------------------------------------------------------------------------------------------------- - -# 5.6.6 (2021-05-26) - -### 🐣新特性 -* 【cron 】 增加时间轮简单实现 -* 【core 】 BeanUtil.copyToList增加重载(pr#321@Gitee) -* 【core 】 SyncFinisher增加stop方法(issue#1578@Github) -* 【cache 】 CacheObj默认方法改为protected(issue#I3RIEI@Gitee) -* 【core 】 FileUtil.isEmpty不存在时返回true(issue#1582@Github) -* 【core 】 PhoneUtil增加中国澳门和中国台湾手机号校检方法(pr#331@Gitee) -* 【db 】 分页查询,自定义sql查询,添加参数(pr#332@Gitee) -* 【core 】 IdCardUtil.isValidCard增加非空判断 -* 【json 】 JSONObject构造增加SortedMap判断(pr#333@Gitee) -* 【core 】 Tuple增加部分方法(pr#333@Gitee) -* 【log 】 增加LogTube支持 -* 【core 】 增加BitStatusUtil(pr#1600@Github) - -### 🐞Bug修复 -* 【core 】 修复XmlUtil中omitXmlDeclaration参数无效问题(issue#1581@Github) -* 【core 】 修复NumberUtil.decimalFormat参数传错的问题(issue#I3SDS3@Gitee) -* 【json 】 修复JSONArray.put方法不能覆盖值的问题 -* 【poi 】 修复sax方式读取xls无法根据sheet名称获取数据(issue#I3S4NH@Gitee) -* 【core 】 修复路径中多个~都被替换的问题(pr#1599@Github) -* 【core 】 修复CRC16构造非public问题(issue#1601@Github) - -------------------------------------------------------------------------------------------------------------- -# 5.6.5 (2021-05-08) - -### 🐣新特性 -* 【http 】 HttpUtil增加closeCookie方法 -* 【core 】 NumberUtil增加方法decimalFormat重载(issue#I3OSA2@Gitee) -* 【extra 】 Ftp的remoteVerificationEnabled改为false(issue#I3OSA2@Gitee) -* 【core 】 MaskBit增加掩码反向转换的方法getMaskBit()(pr#1563@Github) -* 【core 】 ReUtil等增加indexOf、delLast等方法(pr#1555@Github) -* 【poi 】 ExcelWriter增加writeSecHeadRow,增加合并单元格边框颜色样式(pr#318@Gitee) - -### 🐞Bug修复 -* 【core 】 修复createScheduledExecutor单位不是毫秒的问题(issue#I3OYIW@Gitee) -* 【core 】 修复Tailer无stop问题(issue#I3PQLQ@Gitee) -* 【core 】 修复空白excel读取报错问题(issue#1552@Github) -* 【extra 】 修复Sftp.mkDirs报错问题(issue#1536@Github) -* 【core 】 修复Bcrypt不支持$2y$盐前缀问题(pr#1560@Github) -* 【system 】 修复isWindows8拼写问题(pr#1557@Github) -* 【db 】 修复MongoDS默认分组参数失效问题(issue#1548@Github) -* 【core 】 修复UrlPath编码的字符问题导致的URL编码异常(issue#1537@Github) - -------------------------------------------------------------------------------------------------------------- - -# 5.6.4 (2021-04-25) - -### 🐣新特性 -* 【core 】 DatePattern补充DateTimeFormatter(pr#308@Gitee) -* 【core 】 DateUtil.compare增加支持给定格式比较(pr#310@Gitee) -* 【core 】 BeanUtil增加edit方法(issue#I3J6BG@Gitee) -* 【db 】 Column中加入columnDef字段默认值(issue#I3J6BG@Gitee) -* 【core 】 BeanUtil增加copyToList方法(issue#1526@Github) -* 【extra 】 MailAccount增加customProperty可以用户自定义属性(pr#317@Gitee) -* 【system 】 SystemUtil.getUserInfo()中所有平台路径统一末尾加/(issue#I3NM39@Gitee) -* 【http 】 新增HttpDownloader,默认开启自动跳转(issue#I3NM39@Gitee) - -### 🐞Bug修复 -* 【db 】 修复SQL分页时未使用别名导致的错误,同时count时取消order by子句(issue#I3IJ8X@Gitee) -* 【extra 】 修复Sftp.reconnectIfTimeout方法判断错误(issue#1524@Github) -* 【core 】 修复NumberChineseFormatter转数字问题(issue#I3IS3S@Gitee) - -------------------------------------------------------------------------------------------------------------- - -# 5.6.3 (2021-04-10) - -### 🐣新特性 -* 【core 】 修改数字转换的实现,增加按照指定端序转换(pr#1492@Github) -* 【core 】 修改拆分byte数组时最后一组长度的规则(pr#1494@Github) -* 【core 】 新增根据日期获取节气(pr#1496@Github) -* 【core 】 mapToBean()添加对布尔值is前缀的识别(pr#294@Gitee) -* 【core 】 农历十月十一月改为寒月和冬月(pr#301@Gitee) -* 【core 】 增加港澳台电话正则(pr#301@Gitee) -* 【core 】 增加银行卡号脱敏(pr#301@Gitee) -* 【cache 】 使用LongAddr代替AtomicLong(pr#301@Gitee) -* 【cache 】 EnumUtil使用LinkedHashMap(pr#304@Gitee) -* 【crypto 】 SymmetricCrypto支持大量数据加密解密(pr#1497@Gitee) -* 【http 】 SoapClient增加针对不同协议的头信息(pr#305@Gitee) -* 【http 】 HttpRequest支持307、308状态码识别(issue#1504@Github) -* 【core 】 CharUtil.isBlankChar增加\u0000判断(pr#1505@Github) -* 【extra 】 添加Houbb Pinyin支持(pr#1506@Github) -* 【core 】 添加LambdaUtil(pr#295@Gitee) -* 【core 】 添加StrPool和CharPool -* 【extra 】 CglibUtil增加toBean和fillBean方法 -* 【db 】 增加DriverNamePool - -### 🐞Bug修复 -* 【core 】 修复Validator.isUrl()传空返回true(issue#I3ETTY@Gitee) -* 【db 】 修复数据库driver根据url的判断识别错误问题(issue#I3EWBI@Gitee) -* 【json 】 修复JSONStrFormatter换行多余空行问题(issue#I3FA8B@Gitee) -* 【core 】 修复UrlPath中的+被转义为空格%20的问题(issue#1501@Github) -* 【core 】 修复DateUtil.parse方法对UTC时间毫秒少于3位不识别问题(issue#1503@Github) - -------------------------------------------------------------------------------------------------------------- - -# 5.6.2 (2021-03-28) - -### 🐣新特性 -* 【core 】 Validator增加车架号(车辆识别码)验证、驾驶证(驾驶证档案编号)的正则校验(pr#280@Gitee) -* 【core 】 CopyOptions增加propertiesFilter(pr#281@Gitee) -* 【extra 】 增加Wit模板引擎支持 -* 【core 】 增加DesensitizedUtil(pr#282@Gitee) -* 【core 】 增加DateTime字符串构造(issue#I3CQZG@Gitee) -* 【core 】 修改ArrayUtil代码风格(pr#287@Gitee) -* 【json 】 JSONConfig增加setStripTrailingZeros配置(issue#I3DJI8@Gitee) -* 【db 】 升级兼容BeeCP3.x - -### 🐞Bug修复 -* 【core 】 修复FileTypeUtil中OFD格式判断问题(pr#1489@Github) -* 【core 】 修复CamelCaseLinkedMap和CaseInsensitiveLinkedMap的Linked失效问题(pr#1490@Github) -* 【core 】 修复UrlPath中=被转义的问题 - -------------------------------------------------------------------------------------------------------------- - -# 5.6.1 (2021-03-18) - -### 🐣新特性 -* 【crypto 】 SecureUtil去除final修饰符(issue#1474@Github) -* 【core 】 IoUtil增加lineIter方法 -* 【core 】 新增函数式懒加载加载器(pr#275@Gitee) -* 【http 】 UserAgentUtil增加miniProgram判断(issue#1475@Github) -* 【db 】 增加Ignite数据库驱动识别 -* 【core 】 DateUtil.parse支持带毫秒的UTC时间 -* 【core 】 IdcardUtil.Idcard增加toString(pr#1487@Github) -* 【core 】 ChineseDate增加getGregorianXXX方法(issue#1481@Github) - -### 🐞Bug修复 -* 【core 】 修复IoUtil.readBytes的FileInputStream中isClose参数失效问题(issue#I3B7UD@Gitee) -* 【core 】 修复DataUnit中KB不大写的问题 -* 【json 】 修复JSONUtil.getByPath类型错误问题(issue#I3BSDF@Gitee) -* 【core 】 修复BeanUtil.toBean提供null未返回null的问题(issue#I3BQPV@Gitee) -* 【core 】 修复ModifierUtil#modifiersToInt中逻辑判断问题(issue#1486@Github) - -------------------------------------------------------------------------------------------------------------- - -# 5.6.0 (2021-03-12) - -### 🐣新特性 -* 【poi 】 重要:不再兼容POI-3.x,增加兼容POI-5.x(issue#I35J6B@Gitee) -* 【core 】 FileTypeUtil使用长匹配优先(pr#1457@Github) -* 【core 】 IterUtil和CollUtil增加isEqualList方法(issue#I3A3PY@Gitee) -* 【crypto 】 增加PBKDF2(issue#1416@Github) -* 【core 】 增加FuncKeyMap(issue#1402@Github) -* 【core 】 增加StrMatcher(issue#1379@Github) -* 【core 】 NumberUtil增加factorial针对BigInterger方法(issue#1379@Github) -* 【core 】 TreeNode增加equals方法(issue#1467@Github) -* 【core 】 增加汉字转阿拉伯数字Convert.chineseToNumber(pr#1469@Github) -* 【json 】 JSONUtil增加getByPath方法支持默认值(issue#1470@Github) -* 【crypto 】 SecureUtil增加hmacSha256方法(pr#1473@Github) -* 【core 】 FileTypeUtil判断流增加文件名辅助判断(pr#1471@Github) - -### 🐞Bug修复 -* 【socket 】 修复Client创建失败资源未释放问题。 -* 【core 】 修复DataSizeUtil中EB单位错误问题(issue#I39O7I@Gitee) -* 【core 】 修复BeanDesc.isMatchSetter的ignoreCase未使用问题(issue#I3AXIJ@Gitee) -* 【core 】 修复CRC16Checksum中(issue#I3AXIJ@Gitee) -* 【core 】 修复UrlQuery中对空key解析丢失问题(issue#I3B3J6@Gitee) - -------------------------------------------------------------------------------------------------------------- - -# 5.5.9 (2021-02-26) - -### 🐣新特性 -* 【crypto 】 PemUtil.readPemKey支持EC(pr#1366@Github) -* 【extra 】 Ftp等cd方法增加同步(issue#1397@Github) -* 【core 】 StrUtil增加endWithAnyIgnoreCase(issue#I37I0B@Gitee) -* 【crypto 】 Sm2增加getD和getQ方法(issue#I37Z4C@Gitee) -* 【cache 】 AbstractCache增加keySet方法(issue#I37Z4C@Gitee) -* 【core 】 NumberWordFormatter增加formatSimple方法(pr#1436@Github) -* 【crypto 】 增加读取openSSL生成的sm2私钥 -* 【crypto 】 增加众多方法,SM2兼容各类密钥格式(issue#I37Z75@Gitee) - -### 🐞Bug修复 -* 【json 】 JSONUtil.isJson方法改变trim策略,解决特殊空白符导致判断失败问题 -* 【json 】 修复SQLEXception导致的栈溢出(issue#1399@Github) -* 【extra 】 修复Ftp中异常参数没有传入问题(issue#1397@Github) -* 【crypto 】 修复Sm2使用D构造空指针问题(issue#I37Z4C@Gitee) -* 【poi 】 修复ExcelPicUtil中图表报错问题(issue#I38857@Gitee) -* 【core 】 修复ListUtil.page方法返回空列表无法编辑问题(issue#1415@Github) -* 【core 】 修复ListUtil.sub中step不通结果不一致问题(issue#1409@Github) -* 【db 】 修复Condition转换参数值时未转换数字异常(issue#I38LTM@Gitee) - -------------------------------------------------------------------------------------------------------------- - -# 5.5.8 (2021-01-30) - -### 🐣新特性 -* 【extra 】 增加自动装配SpringUtil类(pr#1366@Github) -* 【extra 】 ArrayUtil增加map方法重载 -* 【crypto 】 AsymmetricAlgorithm增加RSA_ECB("RSA/ECB/NoPadding")(issue#1368@Github) -* 【core 】 补充StrUtil.padXXX注释(issue#I2E1S7@Gitee) -* 【core 】 修改上传文件检查逻辑 -* 【core 】 修正LocalDateTimeUtil.offset方法注释问题(issue#I2EEXC@Gitee) -* 【extra 】 VelocityEngine的getRowEngine改为getRawEngine(issue#I2EGRG@Gitee) -* 【cache 】 缓存降低锁的粒度,提高并发能力(pr#1385@Github) -* 【core 】 SimpleCache缓存降低锁的粒度,提高并发能力(pr#1385@Github) -* 【core 】 增加RadixUtil(pr#260@Gitee) -* 【core 】 BeanUtil.getFieldValue支持获取字段集合(pr#254@Gitee) -* 【core 】 DateConvert转换失败默认抛出异常(issue#I2M5GN@Gitee) -* 【http 】 HttpServerRequest增加getParam方法 -* 【http 】 RootAction增加可选name参数,返回指定文件名称 -* 【db 】 支持人大金仓8的驱动识别 -* 【db 】 ThreadUtil增加createScheduledExecutor和schedule方法(issue#I2NUTC@Gitee) -* 【core 】 ImgUtil增加getImage方法(issue#I2DU1Z@Gitee) -* 【core 】 DateUtil.beginOfHour(pr#269@Gitee) -* 【core 】 MapUtil增加sortByValue(pr#259@Gitee) -* 【core 】 TypeUtil修正hasTypeVeriable为hasTypeVariable -* 【core 】 RandomUtil.getRandom改为new SecureRandom,避免阻塞 - -### 🐞Bug修复 -* 【core 】 修复FileUtil.move以及PathUtil.copy等无法自动创建父目录的问题(issue#I2CKTI@Gitee) -* 【core 】 修复Console.input读取不全问题(pr#263@Gitee) -* 【core 】 修复URLUtil.encodeAll未检查空指针问题(issue#I2CNPS@Gitee) -* 【core 】 修复UrlBuilder.of的query中含有?丢失问题(issue#I2CNPS@Gitee) -* 【crypto 】 修复BCrypt.checkpw报错问题(issue#1377@Github) -* 【extra 】 修复Fftp中cd失败导致的问题(issue#1371@Github) -* 【poi 】 修复ExcelWriter.merge注释问题(issue#I2DNPG@Gitee) -* 【core 】 修复CsvReader读取注释行错误问题(issue#I2D87I@Gitee) - -------------------------------------------------------------------------------------------------------------- - -# 5.5.7 (2021-01-07) - -### 🐣新特性 -* 【core 】 DynaBean.create增加重载方法(pr#245@Gitee) -* 【core 】 IdcardUtil增加重载是否忽略大小写(issue#1348@Github) -* 【poi 】 SheetRidReader增加getRidByIndex方法(issue#1342@Github) -* 【extra 】 MailAccount增加sslProtocols配置项(issue#IZN95@Gitee) -* 【extra 】 MailUtil增加getSession方法 -* 【setting】 新增setByGroup和putByGroup,set和put标记为过期(issue#I2C42H@Gitee) -* 【crypto 】 修改SymmetricAlgorithm注释(issue#1360@Github) -* 【all 】 pom中将META-INF/maven下全部exclude(pr#1355@Github) -* 【http 】 SimpleServer中增加addFilter等方法,并使用全局线程池 -* 【core 】 CollUtil.forEach 增加null 判断(pr#250@Gitee) -* 【extra 】 FtpConfig增加serverLanguageCode和systemKey配置,Ftp.download增加重载(pr#248@Gitee) - -### 🐞Bug修复 -* 【core 】 修复CsvReader读取双引号未转义问题(issue#I2BMP1@Gitee) -* 【json 】 JSONUtil.parse修复config无效问题(issue#1363@Github) -* 【http 】 修复SimpleServer返回响应内容Content-Length不正确的问题(issue#1358@Github) -* 【http 】 修复Https请求部分环境下报证书验证异常问题(issue#I2C1BZ@Gitee) - -------------------------------------------------------------------------------------------------------------- - -# 5.5.6 (2020-12-29) - -### 🐣新特性 -* 【core 】 手机号工具类 座机正则表达式统一管理(pr#243@Gitee) -* 【extra 】 Mail增加setDebugOutput方法(issue#1335@Gitee) - -### 🐞Bug修复 -* 【core 】 修复ZipUtil.unzip从流解压关闭问题(issue#I2B0S1@Gitee) -* 【poi 】 修复Excel07Writer写出表格错乱问题(issue#I2B57B@Gitee) -* 【poi 】 修复SheetRidReader读取字段错误问题(issue#1342@Github) -* 【core 】 修复FileUtil.getMimeType不支持css和js(issue#1341@Github) - -------------------------------------------------------------------------------------------------------------- - -# 5.5.5 (2020-12-27) - -### 🐣新特性 -* 【core 】 URLUtil.normalize新增重载(pr#233@Gitee) -* 【core 】 PathUtil增加isSub和toAbsNormal方法 -* 【db 】 RedisDS实现序列化接口(pr#1323@Github) -* 【poi 】 StyleUtil增加getFormat方法(pr#235@Gitee) -* 【poi 】 增加ExcelDateUtil更多日期格式支持(issue#1316@Github) -* 【core 】 NumberUtil.toBigDecimal支持各类数字格式,如1,234.56等(issue#1334@Github) -* 【core 】 NumberUtil增加parseXXX方法(issue#1334@Github) -* 【poi 】 Excel07SaxReader支持通过sheetName读取(issue#I2AOSE@Gitee) - -### 🐞Bug修复 -* 【core 】 FileUtil.isSub相对路径判断问题(pr#1315@Github) -* 【core 】 TreeUtil增加空判定(issue#I2ACCW@Gitee) -* 【db 】 解决Hive获取表名失败问题(issue#I2AGLU@Gitee) -* 【core 】 修复DateUtil.parse未使用严格模式导致结果不正常的问题(issue#1332@Github) -* 【core 】 修复RuntimeUtil.getUsableMemory非static问题(issue#I2AQ2M@Gitee) -* 【core 】 修复ArrayUtil.equals方法严格判断问题(issue#I2AO8B@Gitee) -* 【poi 】 修复SheetRidReader在获取rid时读取错误问题(issue#I2AOQW@Gitee) -* 【core 】 修复强依赖了POI的问题(issue#1336@Github) - -------------------------------------------------------------------------------------------------------------- - -# 5.5.4 (2020-12-16) - -### 🐣新特性 -### 🐞Bug修复 -* 【core 】 修复IoUtil.readBytes的问题 - -------------------------------------------------------------------------------------------------------------- - -# 5.5.3 (2020-12-11) - -### 🐣新特性 -* 【core 】 IdcardUtil增加行政区划83(issue#1277@Github) -* 【core 】 multipart中int改为long,解决大文件上传越界问题(issue#I27WZ3@Gitee) -* 【core 】 ListUtil.page增加检查(pr#224@Gitee) -* 【db 】 Db增加使用sql的page方法(issue#247@Gitee) -* 【cache 】 CacheObj的isExpired()逻辑修改(issue#1295@Github) -* 【json 】 JSONStrFormater改为JSONStrFormatter -* 【dfa 】 增加FoundWord(pr#1290@Github) -* 【core 】 增加Segment(pr#1290@Github) -* 【core 】 增加CharSequenceUtil -* 【poi 】 Excel07SaxReader拆分出SheetDataSaxHandler -* 【core 】 CollUtil.addAll增加判空(pr#228@Gitee) -* 【core 】 修正DateUtil.betweenXXX注释错误(issue#I28XGW@Gitee) -* 【core 】 增加NioUtil -* 【core 】 增加GanymedUtil -* 【poi 】 增加OFD支持,OfdWriter -* 【poi 】 修复NumberUtil属性拼写错误(pr#1311@Github) -* 【core 】 MapUtil增加getQuietly方法(issue#I29IWO@Gitee) - -### 🐞Bug修复 -* 【cache 】 修复Cache中get重复misCount计数问题(issue#1281@Github) -* 【poi 】 修复sax读取自定义格式单元格无法识别日期类型的问题(issue#1283@Github) -* 【core 】 修复CollUtil.get越界问题(issue#1292@Github) -* 【core 】 修复TemporalAccessorUtil无法格式化LocalDate带时间问题(issue#1289@Github) -* 【json 】 修复自定义日期格式的LocalDateTime没有包装引号问题(issue#1289@Github) -* 【cache 】 get中unlock改为unlockRead(issue#1294@Github) -* 【db 】 修复表名包含点导致的问题(issue#1300@Github) -* 【poi 】 修复xdr:row标签导致的问题(issue#1297@Github) -* 【core 】 修复FileUtil.loopFiles使用FileFilter无效问题(issue#I28V48@Gitee) -* 【extra 】 修复JschUtil.execByShell返回空的问题(issue#1067@Github) -* 【poi 】 修复特殊的excel使用sax读取时未读到值的问题(issue#1303@Github) -* 【http 】 修复HttpUtil类条件判断错误(pr#232@Gitee) - -------------------------------------------------------------------------------------------------------------- - -# 5.5.2 (2020-12-01) - -### 🐣新特性 -* 【crypto 】 KeyUtil增加重载,AES构造增加重载(issue#I25NNZ@Gitee) -* 【json 】 JSONUtil增加toList重载(issue#1228@Github) -* 【core 】 新增CollStreamUtil(issue#1228@Github) -* 【extra 】 新增Rhino表达式执行引擎(pr#1229@Github) -* 【crypto 】 增加判空(issue#1230@Github) -* 【core 】 xml.setXmlStandalone(true)格式优化(pr#1234@Github) -* 【core 】 AnnotationUtil增加setValue方法(pr#1250@Github) -* 【core 】 ZipUtil增加get方法(issue#I27CUF@Gitee) -* 【cache 】 对CacheObj等变量使用volatile关键字 -* 【core 】 Base64增加encodeWithoutPadding方法(issue#I26J16@Gitee) -* 【core 】 ExceptionUtil增加message消息包装为运行时异常的方法(pr#1253@Gitee) -* 【core 】 DatePattern增加年月格式化常量(pr#220@Gitee) -* 【core 】 ArrayUtil增加shuffle方法(pr#1255@Github) -* 【core 】 ArrayUtil部分方法分离至PrimitiveArrayUtil -* 【crypto 】 opt改为otp包(issue#1257@Github) -* 【cache 】 增加CacheListener(issue#1257@Github) -* 【core 】 TimeInterval支持分组(issue#1238@Github) -* 【core 】 增加compile包(pr#1243@Github) -* 【core 】 增加ResourceClassLoader、CharSequenceResource、FileObjectResource -* 【core 】 修改IoUtil.read(Reader)逻辑默认关闭Reader -* 【core 】 ZipUtil增加Zip方法(pr#222@Gitee) -* 【all 】 增加Hutool.getAllUtils和printAllUtils方法 -* 【core 】 增加PunyCode(issue#1268@Gitee) -* 【core 】 ArrayUtil增加isSorted方法(pr#1271@Github) -* 【captcha】 增加GifCaptcha(pr#1273@Github) -* 【core 】 增加SSLUtil、SSLContextBuilder - -### 🐞Bug修复 -* 【cron 】 修复CronTimer可能死循环的问题(issue#1224@Github) -* 【core 】 修复Calculator.conversion单个数字越界问题(issue#1222@Github) -* 【poi 】 修复ExcelUtil.getSaxReader使用非MarkSupport流报错问题(issue#1225@Github) -* 【core 】 修复HexUtil.format问题(issue#I268XT@Gitee) -* 【core 】 修复ZipUtil判断压缩文件是否位于压缩目录内的逻辑有误的问题(issue#1251@Github) -* 【json 】 修复JSONObject.accumulate问题 -* 【poi 】 修复部分xlsx文件sax方式解析空指针问题(issue#1265@Github) -* 【core 】 修复PatternPool中邮编的正则(issue#1274@Github) - -------------------------------------------------------------------------------------------------------------- - -# 5.5.1 (2020-11-16) - -### 🐣新特性 -* 【core 】 增加CopyVisitor和DelVisitor - -### 🐞Bug修复 -* 【core 】 修复在Linux下FileUtil.move失败问题(issue#I254Y3@Gitee) -* 【http 】 修复UrlUtil和UrlBuilder中多个/被替换问题(issue#I25MZL@Gitee) - -------------------------------------------------------------------------------------------------------------- - -# 5.5.0 (2020-11-14) - -### 大版本特性 -* 【extra 】 增加jakarta.validation-api封装:ValidationUtil(pr#207@Gitee) -* 【extra 】 增加表达式引擎封装:ExpressionUtil(pr#1203@Github) -* 【extra 】 新增基于Apache-FtpServer封装:SimpleFtpServer -* 【extra 】 新增基于Commons-Compress封装:CompressUtil - -### 🐣新特性 -* 【core 】 NumberUtil.parseInt等支持123,2.00这类数字(issue#I23ORQ@Gitee) -* 【core 】 增加ArrayUtil.isSub、indexOfSub、lastIndexOfSub方法(issue#I23O1K@Gitee) -* 【core 】 反射调用支持传递参数的值为null(pr#1205@Github) -* 【core 】 HexUtil增加format方法(issue#I245NF@Gitee) -* 【poi 】 ExcelWriter增加setCurrentRowToEnd方法(issue#I24A2R@Gitee) -* 【core 】 ExcelWriter增加setCurrentRowToEnd方法(issue#I24A2R@Gitee) -* 【core 】 增加enum转数字支持(issue#I24QZY@Gitee) -* 【core 】 NumberUtil.toBigDecimal空白符转换为0(issue#I24MRP@Gitee) -* 【core 】 CollUtil和IterUtil增加size方法(pr#208@Gitee) -* 【poi 】 ExcelReader的read方法读取空单元格增加CellEditor处理(issue#1213@Github) - -### 🐞Bug修复 -* 【core 】 修复DateUtil.current使用System.nanoTime的问题(issue#1198@Github) -* 【core 】 修复Excel03SaxReader判断日期出错问题(issue#I23M9H@Gitee) -* 【core 】 修复ClassUtil.getTypeArgument方法在判断泛型时导致的问题(issue#1207@Github) -* 【core 】 修复Ipv4Util分隔符问题(issue#I24A9I@Gitee) -* 【core 】 修复Ipv4Util.longToIp的问题 -* 【poi 】 修复Excel07SaxReader读取公式的错误的问题(issue#I23VFL@Gitee) -* 【http 】 修复HttpUtil.isHttp判断问题(pr#1208@Github) -* 【http 】 修复Snowflake时间回拨导致ID重复的bug(issue#1206@Github) -* 【core 】 修复StrUtil.lastIndexOf查找位于首位的字符串找不到的bug(issue#I24RSV@Gitee) -* 【poi 】 修复BigExcelWriter的autoSizeColumnAll问题(pr#1221@Github) -* 【core 】 修复StrUtil.subBetweenAll不支持相同字符的问题(pr#1217@Github) - -------------------------------------------------------------------------------------------------------------- - -# 5.4.7 (2020-10-31) - -### 🐣新特性 -* 【core 】 增加OptionalBean(pr#1182@Github) -* 【core 】 Ganzhi增加方法(issue#1186@Github) -* 【core 】 CollUtil增加forEach重载(issue#I22NA4@Gitee) -* 【core 】 CollUtil.map忽略空值改规则为原数组中的元素和处理后的元素都会忽略空值(issue#I22N08@Gitee) -* 【http 】 增加SoapClient增加addSOAPHeader重载 -* 【http 】 ArrayUtil增加containsAll方法 -* 【core 】 增加CharsetDetector -* 【cron 】 增加CronTask,监听支持获取id(issue#I23315@Gitee) - -### 🐞Bug修复 -* 【core 】 修复BeanUtil.beanToMap方法中editor返回null没有去掉的问题 -* 【core 】 修复ImgUtil.toBufferedImage颜色模式的问题(issue#1194@Github) -* 【cron 】 修复TimeZone设置无效的问题(issue#I23315@Gitee) - -------------------------------------------------------------------------------------------------------------- - -# 5.4.6 (2020-10-23) - -### 🐣新特性 -* 【http 】 HttpRequest增加basicProxyAuth方法(issue#I1YQGM@Gitee) -* 【core 】 NumberUtil.toStr修改逻辑,去掉BigDecimal的科学计数表示(pr#196@Gitee) -* 【core 】 ListUtil.page第一页页码使用PageUtil(pr#198@Gitee) -* 【http 】 增加微信、企业微信ua识别(pr#1179@Github) -* 【core 】 ObjectUtil增加defaultIfXXX(pr#199@Gitee) -* 【json 】 JSONObject构建时不支持的对象类型抛出异常 - -### 🐞Bug修复 -* 【core 】 修复ChineseDate没有忽略时分秒导致计算错误问题(issue#I1YW12@Gitee) -* 【core 】 修复FileUtil中,copyFile方法断言判断参数传递错误(issue#I1Z2NY@Gitee) -* 【core 】 修复BeanDesc读取父类属性覆盖子类属性导致的问题(pr#1175@Github) -* 【aop 】 修复SimpleAspect一个重载导致的问题,去掉重载的after方法(issue#I1YUG9@Gitee) -* 【poi 】 修复03 sax读取日期问题(issue#I1Z83N@Gitee) -* 【core 】 修复FileUtil.size软链导致的问题(pr#200@Gitee) -* 【core 】 修复JSONObject构造时传入JSONArray结果出错问题(issue#I22FDS@Gitee) - -------------------------------------------------------------------------------------------------------------- - -# 5.4.5 (2020-10-18) - -### 🐣新特性 -* 【core 】 ConsoleTable代码优化(pr#190@Gitee) -* 【http 】 HttpRequest增加setProxy重载(pr#190@Gitee) -* 【core 】 XmlUtil.cleanComment(pr#191@Gitee) -* 【core 】 ArrayUtil.unWrap增加默认值(pr#1149@Github) -* 【core 】 ArrayUtil.indexOf修改double的equals判断(pr#1147@Github) -* 【core 】 优化StrUtil中部分参数校验以及逻辑处理(pr#1144@Github) -* 【core 】 简化CreditCode逻辑去除无用Character.toUpperCase(pr#1145@Github) -* 【core 】 NumberUtil增加generateRandomNumber重载,可自定义seed(issue#I1XTUT@Gitee) -* 【core 】 DataSizeUtil支持小数(pr#1158@Github) -* 【core 】 完善注释(pr#193@Gitee) -* 【core 】 优化Combination.countAll(pr#1159@Github) -* 【core 】 优化针对list的split方法(pr#194@Gitee) -* 【poi 】 ExcelWriter增加setRowStyle方法 -* 【core 】 Assert增加函数接口(pr#1166@Github) -* 【core 】 新增AtomicIntegerArray、AtomicLongArray转换 -* 【extra 】 PinyinUtil新增Bopomofo4j支持 -* 【core 】 新增TemporalUtil工具类,新增时间相关方法 - -### 🐞Bug修复 -* 【core 】 解决农历判断节日未判断大小月导致的问题(issue#I1XHSF@Gitee) -* 【core 】 解决ListUtil计算总量可能的int溢出问题(pr#1150@Github) -* 【json 】 解决JSON中转换为double小数精度丢失问题(pr#192@Gitee) -* 【core 】 修复CaseInsensitiveMap的remove等方法并没有忽略大小写的问题(pr#1163@Gitee) -* 【poi 】 修复合并单元格值读取错误的问题 -* 【poi 】 修复NamedSql解析形如col::numeric出错问题(issue#I1YHBX@Gitee) -* 【core 】 修复计算相差天数导致的问题 - -------------------------------------------------------------------------------------------------------------- - -# 5.4.4 (2020-09-28) - -### 🐣新特性 -* 【core 】 ServiceLoaderUtil改为使用contextClassLoader(pr#183@Gitee) -* 【core 】 NetUtil增加getLocalHostName(pr#1103@Github) -* 【extra 】 FTP增加stat方法(issue#I1W346@Gitee) -* 【core 】 Convert.toNumber支持类似12.2F这种形式字符串转换(issue#I1VYLJ@Gitee) -* 【core 】 使用静态变量替换999等(issue#I1W8IB@Gitee) -* 【core 】 URLUtil自动trim(issue#I1W803@Gitee) -* 【crypto 】 RC4增加ecrypt(pr#1108@Github) -* 【core 】 CharUtil and StrUtil增加@(pr#1106@Github) -* 【extra 】 优化EMOJ查询逻辑(pr#1112@Github) -* 【extra 】 优化CollUtil交并集结果集合设置初始化大小,避免扩容成本(pr#1110@Github) -* 【core 】 优化PageUtil彩虹算法(issue#1110@Github) -* 【core 】 IoUtil增加readUtf8方法 -* 【core 】 优化全局邮箱账户初始化逻辑(pr#1114@Github) -* 【http 】 SoapClient增加addSOAPHeader方法 -* 【http 】 完善StrUtil的注释(pr#186@Gitee) -* 【aop 】 去除调试日志(issue#1116@Github) -* 【core 】 增加'反转义(pr#1121@Github) -* 【poi 】 增加SheetReader和XXXRowHandler(issue#I1WHJP@Gitee) -* 【dfa 】 增加过滤符号(pr#1122@Github) -* 【dfa 】 SensitiveUtil增加setCharFilter方法(pr#1123@Github) -* 【all 】 优化常量大小写规范(pr#188@Gitee) -* 【core 】 优化NumberUtil中针对BigDecimal的一些处理逻辑(pr#1127@Github) -* 【core 】 NumberUtil.factorial注释明确(pr#1126@Github) -* 【core 】 NumberUtil增加isPowerOfTwo方法(pr#1132@Github) -* 【core 】 优化BooleanUtil的校验逻辑(pr#1137@Github) -* 【poi 】 改进sax方式读取逻辑,支持sheetId(issue#1141@Github) -* 【core 】 XmlUtil增加readBySax方法 - -### 🐞Bug修复 -* 【crypto 】 修复SM2验签后无法解密问题(issue#I1W0VP@Gitee) -* 【core 】 修复新建默认TreeSet没有默认比较器导致的问题(issue#1101@Github) -* 【core 】 修复Linux下使用Windows路径分隔符导致的解压错误(issue#I1MW0E@Gitee) -* 【core 】 修复Word07Writer写出map问题(issue#I1W49R@Gitee) -* 【script 】 修复函数库脚本执行问题 -* 【core 】 修复RGB随机颜色的上限值不对且API重复(pr#1136@Gihub) - -------------------------------------------------------------------------------------------------------------- - -# 5.4.3 (2020-09-16) - -### 🐣新特性 -* 【core 】 使用静态的of方法来new对象(pr#177@Gitee) -* 【setting】 Setting增加store无参方法(issue#1072@Github) -* 【setting】 StatementUtil增加null缓存(pr#1076@Github) -* 【core 】 扩充Console功能,支持可变参数(issue#1077@Github) -* 【crypto 】 增加ECKeyUtil(issue#I1UOF5@Gitee) -* 【core 】 增加TransXXX(issue#I1TU1Y@Gitee) -* 【core 】 增加Generator -* 【db 】 Column增加是否主键、保留位数等字段 -* 【cache 】 Cache接口增加get重载(issue#1080@Github) -* 【core 】 增加Interner和InternUtil(issue#I1TU1Y@Gitee) -* 【core 】 增加Calculator(issue#1090@Github) -* 【core 】 IdcardUtil增加getIdcardInfo方法(issue#1092@Github) -* 【core 】 改进ObjectUtil.equal,支持BigDecimal判断 -* 【core 】 ArrayConverter增加可选是否忽略错误(issue#I1VNYQ@Gitee) -* 【db 】 增加ConditionBuilder -* 【setting】 Setting和Props增加create方法 -* 【log 】 增加TinyLog2支持(issue#1094@Github) - -### 🐞Bug修复 -* 【core 】 修复Dict.of错误(issue#I1UUO5@Gitee) -* 【core 】 修复UrlBuilder地址参数问题(issue#I1UWCA@Gitee) -* 【core 】 修复StrUtil.toSymbolCase转换问题(issue#1075@Github) -* 【log 】 修复打印null对象显示{msg}异常问题(issue#1084@Github) -* 【extra 】 修复ServletUtil.getReader中未关闭的问题 -* 【extra 】 修复QrCodeUtil在新版本zxing报错问题(issue#1088@Github) -* 【core 】 修复LocalDateTimeUtil.parse无法解析yyyyMMddHHmmssSSS的bug(issue#1082@Github) -* 【core 】 修复VersionComparator.equals递归调用问题(issue#1093@Github) - -------------------------------------------------------------------------------------------------------------- - -# 5.4.2 (2020-09-09) - -### 🐣新特性 -* 【core 】 lock放在try外边(pr#1050@Github) -* 【core 】 MailUtil增加错误信息(issue#I1TAKJ@Gitee) -* 【core 】 JschUtil添加远程转发功能(pr#171@Gitee) -* 【db 】 AbstractDb增加executeBatch重载(issue#1053@Github) -* 【extra 】 新增方便引入SpringUtil的注解@EnableSpringUtil(pr#172@Gitee) -* 【poi 】 RowUtil增加插入和删除行(pr#1060@Github) -* 【extra 】 SpringUtil增加注册bean(pr#174@Gitee) -* 【core 】 修改NetUtil.getMacAddress避免空指针(issue#1057@Github) -* 【core 】 增加EnumItem接口,枚举扩展转换,增加SPI自定义转换(pr#173@Github) -* 【core 】 TypeUtil增加getActualType,增加ActualTypeMapperPool类(issue#I1TBWH@Gitee) -* 【extra 】 QRConfig中添加qrVersion属性(pr#1068@Github) -* 【core 】 ArrayUtil增加equals方法 -* 【core 】 BeanDesc增加方法 -* 【core 】 增加@PropIgnore注解(issue#I1U846@Gitee) - -### 🐞Bug修复 -* 【core 】 重新整理农历节假日,解决一个pr过来的玩笑导致的问题 -* 【poi 】 修复ExcelFileUtil.isXls判断问题(pr#1055@Github) -* 【poi 】 修复CglibUtil.copyList参数错误导致的问题 -* 【http 】 修复GET请求附带body导致变POST的问题 -* 【core 】 修复double相等判断问题(pr#175@Gitee) -* 【core 】 修复DateSizeUtil.format越界问题(issue#1069@Github) -* 【core 】 修复ChineseDate.getChineseMonth问题(issue#I1UG72@Gitee) - -------------------------------------------------------------------------------------------------------------- - -# 5.4.1 (2020-08-29) - -### 🐣新特性 -* 【core 】 StrUtil增加firstNonXXX方法(issue#1020@Github) -* 【core 】 BeanCopier修改规则,可选bean拷贝空字段报错问题(pr#160@Gitee) -* 【http 】 HttpUtil增加downloadFileFromUrl(pr#1023@Github) -* 【core 】 增加toEpochMilli方法 -* 【core 】 Validator修改isCitizenId校验(pr#1032@Github) -* 【core 】 增加PathUtil和FileNameUtil,分离FileUtil中部分方法 -* 【core 】 改造IndexedComparator,增加InstanceComparator -* 【extra 】 增加CglibUtil -* 【core 】 增加Ipv4Util(pr#161@Gitee) -* 【core 】 增加CalendarUtil和DateUtil增加isSameMonth方法(pr#161@Gitee) -* 【core 】 Dict增加of方法(issue#1035@Github) -* 【core 】 StrUtil.wrapAll方法不明确修改改为wrapAllWithPair(issue#1042@Github) -* 【core 】 EnumUtil.getEnumAt负数返回null(pr#167@Gitee) -* 【core 】 ChineseDate增加天干地支和转换为公历方法(pr#169@Gitee) -* 【core 】 Img增加stroke描边方法(issue#1033@Github) - -### 🐞Bug修复# -* 【poi 】 修复ExcelBase.isXlsx方法判断问题(issue#I1S502@Gitee) -* 【poi 】 修复Excel03SaxReader日期方法判断问题(pr#1026@Github) -* 【core 】 修复StrUtil.indexOf空指针问题(issue#1038@Github) -* 【extra 】 修复VelocityEngine编码问题和路径前缀问题(issue#I1T0IG@Gitee) - -------------------------------------------------------------------------------------------------------------- - -# 5.4.0 (2020-08-06) - -### 🐣新特性 -* 【socket】 对NioServer和NioClient改造(pr#992@Github) -* 【core 】 StrUtil增加filter方法(pr#149@Gitee) -* 【core 】 DateUtil增加beginOfWeek重载 -* 【core 】 将有歧义的BeanUtil.mapToBean方法置为过期(使用toBean方法) -* 【core 】 添加WatchAction(对Watcher的抽象) -* 【core 】 修改UUID正则,更加严谨(issue#I1Q1IW@Gitee) -* 【core 】 ArrayUtil增加isAllNull方法(issue#1004@Github) -* 【core 】 CollUtil增加contains方法(pr#152@Gitee) -* 【core 】 ArrayUtil增加isAllNotNull方法(pr#1008@Github) -* 【poi 】 closeAfterRead参数无效,方法设为过期(issue#1007@Github) -* 【core 】 CollUtil中部分方法返回null变更为返回empty -* 【all 】 添加英文README(pr#153@Gitee) -* 【extra 】 SpringUtil增加getBean(TypeReference)(pr#1009@Github) -* 【core 】 Assert增加方法,支持自定义异常处理(pr#154@Gitee) -* 【core 】 BooleanConverter增加数字转换规则(issue#I1R2AB@Gitee) -* 【poi 】 sax方式读取增加一个sheet结束的回调(issue#155@Gitee) -* 【db 】 增加BeeCP连接池支持 -* 【core 】 改进Img.pressImage方法,避免变色问题(issue#1001@Github) - -### 🐞Bug修复# -* 【core 】 修复原始类型转换时,转换失败没有抛出异常的问题 -* 【core 】 修复BeanUtil.mapToBean中bean的class非空构造无法实例化问题 -* 【core 】 修复NamedSql多个连续变量出现替换问题 -* 【core 】 修复Bean重名字段(大小写区别)获取数据出错的问题(issue#I1QBQ4@Gitee) -* 【http 】 修复SimpleServer响应头无效问题(issue#1006@Github) -* 【core 】 修复ThreadLocalRandom共享seed导致获取随机数一样的问题(pr#151@Gitee) - -------------------------------------------------------------------------------------------------------------- - -# 5.3.11 (2020-08-01) - -### 🐣新特性 -* 【captcha】 AbstractCaptcha增加getImageBase64Data方法(pr#985@Github) -* 【core 】 增加PhoneUtil(pr#990@Github) -* 【core 】 改进Img,目标图片类型未定义使用源图片类型(issue#I1PB0B@Gitee) -* 【json 】 JSONConfig增加Transient选项(issue#I1PLHN@Gitee) -* 【core 】 MapUtil增加getXXX的默认值重载(issue#I1PTGI@Gitee) -* 【core 】 CalendarUtil增加parseByPatterns方法(issue#993@Github) - -### 🐞Bug修复# - -------------------------------------------------------------------------------------------------------------- - -## 5.3.10 (2020-07-23) - -### 🐣新特性 -* 【db 】 增加DbUtil.setReturnGeneratedKeyGlobal(issue#I1NM0K@Gitee) -* 【core 】 增加DataSize和DataSizeUtil(issue#967@Github) -* 【core 】 ImgUtil增加异常,避免空指针(issue#I1NKXG@Gitee) -* 【core 】 增加CRC16算法若干(pr#963@Github) -* 【core 】 LocalDateTimeUtil增加format等方法(pr#140@Gitee) -* 【http 】 UserAgentUtil增加Android原生浏览器识别(pr#975@Github) -* 【crypto 】 增加ECIES算法类(issue#979@Github) -* 【crypto 】 CollUtil增加padLeft和padRight方法(pr#141@Gitee) -* 【core 】 IdCardUtil香港身份证去除首字母校验(issue#I1OOTB@Gitee) - -### 🐞Bug修复 -* 【core 】 修复ZipUtil中finish位于循环内的问题(issue#961@Github) -* 【core 】 修复CollUtil.page未越界检查的问题(issue#I1O2LR@Gitee) -* 【core 】 修复StrUtil.removeAny的bug(issue#977@Github) - -------------------------------------------------------------------------------------------------------------- - -## 5.3.9 (2020-07-12) - -### 🐣新特性 -* 【core 】 DateUtil增加formatChineseDate(pr#932@Github) -* 【core 】 ArrayUtil.isEmpty修改逻辑(pr#948@Github) -* 【core 】 增强StrUtil中空判断后返回数据性能(pr#949@Github) -* 【core 】 deprecate掉millsecond,改为millisecond(issue#I1M9P8@Gitee) -* 【core 】 增加LocalDateTimeUtil(issue#I1KUVC@Gitee) -* 【core 】 Month增加getLastDay方法 -* 【core 】 ChineseDate支持到2099年 - -### 🐞Bug修复 -* 【core 】 修复NumberUtil.partValue有余数问题(issue#I1KX66@Gitee) -* 【core 】 修复BeanUtil.isEmpty不能忽略static字段问题(issue#I1KZI6@Gitee) -* 【core 】 修复StrUtil.brief长度问题(pr#930@Github) -* 【socket 】 修复AioSession构造超时无效问题(pr#941@Github) -* 【setting】 修复GroupSet.contains错误(pr#943@Github) -* 【core 】 修复ZipUtil没有调用finish问题(issue#944@Github) -* 【extra 】 修复Ftp中ArrayList长度为负问题(pr#136@Github) -* 【core 】 修复Dict中putAll大小写问题(issue#I1MU5B@Gitee) -* 【core 】 修复POI中sax读取数字判断错误问题(issue#931@Github) -* 【core 】 修复DateUtil.endOfQuarter错误问题(issue#I1NGZ7@Gitee) -* 【core 】 修复URL中有空格转为+问题(issue#I1NGW4@Gitee) -* 【core 】 修复CollUtil.intersectionDistinct空集合结果错误问题 -* 【core 】 修复ChineseDate在1996年计算错误问题(issue#I1N96I@Gitee) - -------------------------------------------------------------------------------------------------------------- - -## 5.3.8 (2020-06-16) - -### 🐣新特性 -* 【core 】 增加ISO8601日期格式(issue#904@Github) -* 【setting】 Props异常规则修改(issue#907@Github) -* 【setting】 增加GIF支持 -* 【core 】 复制创建一个Bean对象, 并忽略某些属性(pr#130@Gitee) -* 【core 】 DateUtil.parse支持更多日期格式(issue#I1KHTB@Gitee) -* 【crypto 】 增加获取密钥空指针的检查(issue#925@Github) -* 【core 】 增加StrUtil.removeAny方法(issue#923@Github) -* 【db 】 增加部分Connection参数支持(issue#924@Github) -* 【core 】 FileUtil增加别名方法(pr#926@Github) -* 【poi 】 ExcelReader中增加read重载,提供每个单元格单独处理的方法(issue#I1JZTL@Gitee) - -### 🐞Bug修复 -* 【json 】 修复append方法导致的JSONConfig传递失效问题(issue#906@Github) -* 【core 】 修复CollUtil.subtractToList判断错误(pr#915@Github) -* 【poi 】 修复WordWriter写表格问题(pr#914@Github) -* 【core 】 修复IoUtil.readBytes缓存数组长度问题(issue#I1KIUE@Gitee) -* 【core 】 修复BigExcelWriter多次flush导致的问题(issue#920@Github) -* 【extra 】 绕过Pinyin4j最后一个分隔符失效的bug(issue#921@Github) - -------------------------------------------------------------------------------------------------------------- - -## 5.3.7 (2020-06-03) - -### 🐣新特性 -* 【core 】 ThreadFactoryBuilder的setUncaughtExceptionHandler返回this(issue#I1J4YJ@Gitee) - -### 🐞Bug修复 -* 【core 】 修复DateUtil.parse解析2020-5-8 3:12:13错误问题(issue#I1IZA3@Gitee) -* 【core 】 修复Img.pressImg大小无效问题(issue#I1HSWU@Gitee) -* 【core 】 修复CronUtil.stop没有清除任务的问题(issue#I1JACI@Gitee) - -------------------------------------------------------------------------------------------------------------- -## 5.3.6 (2020-05-30) - -### 🐣新特性 -* 【core 】 NumberConverter Long类型增加日期转换(pr#872@Github) -* 【all 】 StrUtil and SymmetricCrypto注释修正(pr#873@Github) -* 【core 】 CsvReader支持返回Bean(issue#869@Github) -* 【core 】 Snowflake循环等待下一个时间时避免长时间循环,加入对时钟倒退的判断(pr#874@Github) -* 【extra 】 新增 QRCode base64 编码形式返回(pr#878@Github) -* 【core 】 ImgUtil增加toBase64DateUri,URLUtil增加getDataUri方法 -* 【core 】 IterUtil添加List转Map的工具方法(pr#123@Gitee) -* 【core 】 BeanValueProvider转换失败时,返回原数据,而非null -* 【core 】 支持BeanUtil.toBean(object, Map.class)转换(issue#I1I4HC@Gitee) -* 【core 】 MapUtil和CollUtil增加clear方法(issue#I1I4HC@Gitee) -* 【core 】 增加FontUtil,可定义pressText是否从中间(issue#I1HSWU@Gitee) -* 【http 】 SoapClient支持自定义请求头(issue#I1I0AO@Gitee) -* 【script 】 ScriptUtil增加evalInvocable和invoke方法(issue#I1HHCP@Gitee) -* 【core 】 ImgUtil增加去除背景色的方法(pr#124@Gitee) -* 【system 】 OshiUtil增加获取CPU使用率的方法(pr#124@Gitee) -* 【crypto 】 AsymmetricAlgorithm去除EC(issue#887@Github) -* 【cache 】 超时缓存使用的线程池大小默认为1(issue#890@Github) -* 【poi 】 ExcelSaxReader支持handleCell方法 -* 【core 】 Snowflake容忍2秒内的时间回拨(issue#I1IGDX@Gitee) -* 【core 】 StrUtil增加isAllNotEmpty、isAllNotBlank方法(pr#895@Github) -* 【core 】 DateUtil增加dayOfYear方法(pr#895@Github) -* 【core 】 DateUtil增加dayOfYear方法(pr#895@Github) -* 【http 】 HttpUtil增加downloadBytes方法(pr#895@Github) -* 【core 】 isMactchRegex失效标记,增加isMatchRegex(issue#I1IPJG@Gitee) -* 【core 】 优化Validator.isChinese -* 【core 】 ArrayUtil.addAll增加原始类型支持(issue#898@Github) -* 【core 】 DateUtil.parse支持2020-1-1这类日期解析(issue#I1HGWW@Github) - -### 🐞Bug修复 -* 【core 】 修复SimpleCache死锁问题(issue#I1HOKB@Gitee) -* 【core 】 修复SemaphoreRunnable释放问题(issue#I1HLQQ@Gitee) -* 【poi 】 修复Sax方式读取Excel行号错误问题(issue#882@Github) -* 【poi 】 修复Sax方式读取Excel日期类型数据03和07不一致问题(issue#I1HL1C@Gitee) -* 【poi 】 修复CamelCaseLinkedMap构造错误(issue#I1IZ30@Gitee) - -------------------------------------------------------------------------------------------------------------- - -## 5.3.5 (2020-05-13) - -### 🐣新特性 -* 【core 】 增加CollUtil.map方法 -* 【extra 】 增加Sftp.lsEntries方法,Ftp和Sftp增加recursiveDownloadFolder(pr#121@Gitee) -* 【system 】 OshiUtil增加getNetworkIFs方法 -* 【core 】 CollUtil增加unionDistinct、unionAll方法(pr#122@Gitee) -* 【core 】 增加IoUtil.readObj重载,通过ValidateObjectInputStream由用户自定义安全检查。 -* 【http 】 改造HttpRequest中文件上传部分,增加MultipartBody类 - -### 🐞Bug修复 -* 【core 】 修复IoUtil.readObj中反序列化安全检查导致的一些问题,去掉安全检查。 -* 【http 】 修复SimpleServer文件访问404问题(issue#I1GZI3@Gitee) -* 【core 】 修复BeanCopier中循环引用逻辑问题(issue#I1H2VN@Gitee) - -------------------------------------------------------------------------------------------------------------- - -## 5.3.4 (2020-05-10) - -### 🐣新特性 -* 【core 】 增加URLUtil.getContentLength方法(issue#I1GB1Z@Gitee) -* 【extra 】 增加PinyinUtil(issue#I1GMIV@Gitee) - -### 🐞Bug修复 -* 【extra 】 修复Ftp设置超时问题(issue#I1GMTQ@Gitee) -* 【core 】 修复TreeUtil根据id查找子节点时的NPE问题(pr#120@Gitee) -* 【core 】 修复BeanUtil.copyProperties中Alias注解无效问题(issue#I1GK3M@Gitee) -* 【core 】 修复CollUtil.containsAll空集合判断问题(issue#I1G9DE@Gitee) -* 【core 】 修复XmlUtil.xmlToBean失败问题(issue#865@Github) - -------------------------------------------------------------------------------------------------------------- - -## 5.3.3 (2020-05-05) - -### 🐣新特性 -* 【core 】 ImgUtil.createImage支持背景透明(issue#851@Github) -* 【json 】 更改JSON转字符串时" cls)(pr#370@Gitee) +* 【extra 】 AbstractFtp增加isDir方法(issue#1716@Github) +* 【core 】 修改FileUtil异常信息内容(pr#1713@Github) + +### 🐞Bug修复 +* 【core 】 修复FileUtil.normalize处理上级路径的问题(issue#I3YPEH@Gitee) +* 【core 】 修复ClassScanner扫描空包遗漏问题 +* 【core 】 修复FastDatePrinter歧义问题(pr#366@Gitee) +* 【core 】 修复DateUtil.format格式化Instant报错问题(issue#I40CY2@Gitee) +* 【core 】 修复StrUtil.toUnderlineCase大写问题(issue#I40CGS@Gitee) +* 【jwt 】 修复JWT.validate报错问题(issue#I40MR2@Gitee) +* 【core 】 修复StrUtil.brief越界问题 + +------------------------------------------------------------------------------------------------------------- + +# 5.7.4 (2021-07-10) + +### 🐣新特性 +* 【crypto 】 SmUtil.sm4统一返回类型(issue#I3YKD4@Gitee) +* 【core 】 修改MapUtil.get传入null返回默认值而非null(issue#I3YKBC@Gitee) +* 【core 】 HexUtil增加hexToLong、hexToInt(issue#I3YQEV@Gitee) +* 【core 】 CsvWriter增加writer.write(csvData)的方法重载(pr#353@Gitee) +* 【core 】 新增AbsCollValueMap(issue#I3YXF0@Gitee) +* 【crypto 】 HOTP缓存改为8位,新增方法(pr#356@Gitee) +* 【setting】 Props增加toProperties方法(issue#1701@Github) +* 【http 】 UserAgent增加getOsVersion方法(issue#I3YZUQ@Gitee) +* 【jwt 】 JWT增加validate方法(issue#I3YDM4@Gitee) +* 【core 】 CscReader支持指定读取开始行号和结束行号(issue#I3ZMZL@Gitee) + +### 🐞Bug修复 +* 【core 】 修复RadixUtil.decode非static问题(issue#I3YPEH@Gitee) +* 【core 】 修复EqualsBuilder数组判断问题(pr#1694@Github) +* 【setting】 修复Props中Charset对象无法序列化的问题(pr#1694@Github) +* 【db 】 修复PageResult首页判断逻辑问题(issue#1699@Github) +* 【core 】 修复IdcardUtil可能数组越界问题(pr#1702@Github) +* 【core 】 修复FastByteArrayOutputStream索引越界问题(issue#I402ZP@Github) + +------------------------------------------------------------------------------------------------------------- + +# 5.7.3 (2021-06-29) + +### 🐣新特性 +* 【core 】 增加Convert.toSet方法(issue#I3XFG2@Gitee) +* 【core 】 CsvWriter增加writeBeans方法(pr#345@Gitee) +* 【core 】 新增JAXBUtil(pr#346@Gitee) +* 【poi 】 ExcelWriter新增setColumnStyleIfHasData和setRowStyleIfHasData(pr#347@Gitee) +* 【json 】 用户自定义日期时间格式时,解析也读取此格式 +* 【core 】 增加可自定义日期格式GlobalCustomFormat +* 【jwt 】 JWT修改默认有序,并规定payload日期格式为秒数 +* 【json 】 增加JSONWriter +* 【core 】 IdUtil增加getWorkerId和getDataCenterId(issueI3Y5NI@Gitee) +* 【core 】 JWTValidator增加leeway重载 +* 【core 】 增加RegexPool(issue#I3W9ZF@Gitee) + +### 🐞Bug修复 +* 【json 】 修复XML转义字符的问题(issue#I3XH09@Gitee) +* 【core 】 修复FormatCache中循环引用异常(pr#1673@Github) +* 【core 】 修复IdcardUtil.getIdcardInfo.getProvinceCode获取为汉字的问题(issue#I3XP4Q@Gitee) +* 【core 】 修复CollUtil.subtract使用非标准Set等空指针问题(issue#I3XN1Z@Gitee) +* 【core 】 修复SqlFormatter部分SQL空指针问题(issue#I3XS44@Gitee) +* 【core 】 修复DateRange计算问题(issue#I3Y1US@Gitee) +* 【core 】 修复BeanCopier中setFieldNameEditor失效问题(pr#349@Gitee) +* 【core 】 修复ArrayUtil.indexOfSub查找bug(issue#1683@Github) +* 【core 】 修复Node的权重比较空指针问题(issue#1681@Github) +* 【core 】 修复UrlQuery传入无参数路径解析问题(issue#1688@Github) + +------------------------------------------------------------------------------------------------------------- + +# 5.7.2 (2021-06-20) + +### 🐣新特性 +* 【core 】 增加UserPassAuthenticator +* 【db 】 获取分组数据源时,移除公共属性项 +* 【core 】 增加StrJoiner +* 【core 】 增加TreeBuilder +* 【core 】 IterUtil增加getFirstNonNull方法 +* 【core 】 NumberUtil判空改为isBlank(issue#1664@Github) +* 【jwt 】 增加JWTValidator、RegisteredPayload +* 【db 】 增加Phoenix方言(issue#1656@Github) + +### 🐞Bug修复 +* 【db 】 修复Oracle下别名错误造成的SQL语法啊错误(issue#I3VTQW@Gitee) +* 【core 】 修复ConcurrencyTester重复使用时开始测试未清空之前任务的问题(issue#I3VSDO@Gitee) +* 【poi 】 修复使用BigWriter写出,ExcelWriter修改单元格值失败的问题(issue#I3VSDO@Gitee) +* 【jwt 】 修复Hmac算法下生成签名是hex的问题(issue#I3W6IP@Gitee) +* 【core 】 修复TreeUtil.build中deep失效问题(issue#1661@Github) +* 【json 】 修复XmlUtil.xmlToBean判断问题(issue#1663@Github) + +------------------------------------------------------------------------------------------------------------- + +# 5.7.1 (2021-06-16) + +### 🐣新特性 +* 【db 】 NamedSql支持in操作(issue#1652@Github) +* 【all 】 JWT模块加入到all和bom包中(issue#1654@Github) +* 【core 】 CollUtil删除所有Map相关操作 +* 【all 】 **重要!** 删除过期方法 +* 【core 】 增加IterChian类 + +### 🐞Bug修复 + +------------------------------------------------------------------------------------------------------------- + +# 5.7.0 (2021-06-15) + +### 🐣新特性 +* 【jwt 】 添加JWT模块,实现了JWT的创建、解析和验证 +* 【crypto 】 SymmetricCrypto增加update方法(pr#1642@Github) +* 【crypto 】 MacEngine增加接口update,doFinal,reset等接口 +* 【core 】 StrSpliter更名为StrSplitter +* 【core 】 NumberUtil的decimalFormat增加数字检查 +* 【http 】 HttpBase的httpVersion方法设置为无效(issue#1644@Github) +* 【extra 】 Sftp增加download重载(issue#I3VBSL@Gitee) +* 【cache 】 修改FIFOCache初始大小(issue#1647@Github) + +### 🐞Bug修复 +* 【db 】 修复count方法丢失参数问题(issue#I3VBSL@Gitee) +* 【db 】 修复SpringUtil工具在`@PostConstruct` 注解标注的方法下失效问题(pr#341@Gitee) +* 【json 】 修复JSONUtil.parse方法未判断有序问题(issue#I3VHVY@Gitee) +* 【json 】 修复JSONArray.put越界无法加入问题(issue#I3VMLU@Gitee) + +------------------------------------------------------------------------------------------------------------- + +# 5.6.7 (2021-06-08) + +### 🐣新特性 +* 【core 】 CharSequenceUtil增加join重载(issue#I3TFJ5@Gitee) +* 【http 】 HttpRequest增加form方法重载(pr#337@Gitee) +* 【http 】 ImgUtil增加getMainColor方法(pr#338@Gitee) +* 【core 】 改进TreeUtil.buid算法性能(pr#1594@Github) +* 【core 】 CsvConfig的setXXX返回this(issue#I3UIQF@Gitee) +* 【all 】 增加jmh基准测试 +* 【core 】 增加StreamUtil和CollectorUtil +* 【poi 】 增加content-type(pr#1639@Github) + +### 🐞Bug修复 +* 【core 】 修复FileUtil.normalize去掉末尾空格问题(issue#1603@Github) +* 【core 】 修复CharsetDetector流关闭问题(issue#1603@Github) +* 【core 】 修复RuntimeUtil.exec引号内空格被切分的问题(issue#I3UAYB@Gitee) + +------------------------------------------------------------------------------------------------------------- + +# 5.6.6 (2021-05-26) + +### 🐣新特性 +* 【cron 】 增加时间轮简单实现 +* 【core 】 BeanUtil.copyToList增加重载(pr#321@Gitee) +* 【core 】 SyncFinisher增加stop方法(issue#1578@Github) +* 【cache 】 CacheObj默认方法改为protected(issue#I3RIEI@Gitee) +* 【core 】 FileUtil.isEmpty不存在时返回true(issue#1582@Github) +* 【core 】 PhoneUtil增加中国澳门和中国台湾手机号校检方法(pr#331@Gitee) +* 【db 】 分页查询,自定义sql查询,添加参数(pr#332@Gitee) +* 【core 】 IdCardUtil.isValidCard增加非空判断 +* 【json 】 JSONObject构造增加SortedMap判断(pr#333@Gitee) +* 【core 】 Tuple增加部分方法(pr#333@Gitee) +* 【log 】 增加LogTube支持 +* 【core 】 增加BitStatusUtil(pr#1600@Github) + +### 🐞Bug修复 +* 【core 】 修复XmlUtil中omitXmlDeclaration参数无效问题(issue#1581@Github) +* 【core 】 修复NumberUtil.decimalFormat参数传错的问题(issue#I3SDS3@Gitee) +* 【json 】 修复JSONArray.put方法不能覆盖值的问题 +* 【poi 】 修复sax方式读取xls无法根据sheet名称获取数据(issue#I3S4NH@Gitee) +* 【core 】 修复路径中多个~都被替换的问题(pr#1599@Github) +* 【core 】 修复CRC16构造非public问题(issue#1601@Github) + +------------------------------------------------------------------------------------------------------------- +# 5.6.5 (2021-05-08) + +### 🐣新特性 +* 【http 】 HttpUtil增加closeCookie方法 +* 【core 】 NumberUtil增加方法decimalFormat重载(issue#I3OSA2@Gitee) +* 【extra 】 Ftp的remoteVerificationEnabled改为false(issue#I3OSA2@Gitee) +* 【core 】 MaskBit增加掩码反向转换的方法getMaskBit()(pr#1563@Github) +* 【core 】 ReUtil等增加indexOf、delLast等方法(pr#1555@Github) +* 【poi 】 ExcelWriter增加writeSecHeadRow,增加合并单元格边框颜色样式(pr#318@Gitee) + +### 🐞Bug修复 +* 【core 】 修复createScheduledExecutor单位不是毫秒的问题(issue#I3OYIW@Gitee) +* 【core 】 修复Tailer无stop问题(issue#I3PQLQ@Gitee) +* 【core 】 修复空白excel读取报错问题(issue#1552@Github) +* 【extra 】 修复Sftp.mkDirs报错问题(issue#1536@Github) +* 【core 】 修复Bcrypt不支持$2y$盐前缀问题(pr#1560@Github) +* 【system 】 修复isWindows8拼写问题(pr#1557@Github) +* 【db 】 修复MongoDS默认分组参数失效问题(issue#1548@Github) +* 【core 】 修复UrlPath编码的字符问题导致的URL编码异常(issue#1537@Github) + +------------------------------------------------------------------------------------------------------------- + +# 5.6.4 (2021-04-25) + +### 🐣新特性 +* 【core 】 DatePattern补充DateTimeFormatter(pr#308@Gitee) +* 【core 】 DateUtil.compare增加支持给定格式比较(pr#310@Gitee) +* 【core 】 BeanUtil增加edit方法(issue#I3J6BG@Gitee) +* 【db 】 Column中加入columnDef字段默认值(issue#I3J6BG@Gitee) +* 【core 】 BeanUtil增加copyToList方法(issue#1526@Github) +* 【extra 】 MailAccount增加customProperty可以用户自定义属性(pr#317@Gitee) +* 【system 】 SystemUtil.getUserInfo()中所有平台路径统一末尾加/(issue#I3NM39@Gitee) +* 【http 】 新增HttpDownloader,默认开启自动跳转(issue#I3NM39@Gitee) + +### 🐞Bug修复 +* 【db 】 修复SQL分页时未使用别名导致的错误,同时count时取消order by子句(issue#I3IJ8X@Gitee) +* 【extra 】 修复Sftp.reconnectIfTimeout方法判断错误(issue#1524@Github) +* 【core 】 修复NumberChineseFormatter转数字问题(issue#I3IS3S@Gitee) + +------------------------------------------------------------------------------------------------------------- + +# 5.6.3 (2021-04-10) + +### 🐣新特性 +* 【core 】 修改数字转换的实现,增加按照指定端序转换(pr#1492@Github) +* 【core 】 修改拆分byte数组时最后一组长度的规则(pr#1494@Github) +* 【core 】 新增根据日期获取节气(pr#1496@Github) +* 【core 】 mapToBean()添加对布尔值is前缀的识别(pr#294@Gitee) +* 【core 】 农历十月十一月改为寒月和冬月(pr#301@Gitee) +* 【core 】 增加港澳台电话正则(pr#301@Gitee) +* 【core 】 增加银行卡号脱敏(pr#301@Gitee) +* 【cache 】 使用LongAddr代替AtomicLong(pr#301@Gitee) +* 【cache 】 EnumUtil使用LinkedHashMap(pr#304@Gitee) +* 【crypto 】 SymmetricCrypto支持大量数据加密解密(pr#1497@Gitee) +* 【http 】 SoapClient增加针对不同协议的头信息(pr#305@Gitee) +* 【http 】 HttpRequest支持307、308状态码识别(issue#1504@Github) +* 【core 】 CharUtil.isBlankChar增加\u0000判断(pr#1505@Github) +* 【extra 】 添加Houbb Pinyin支持(pr#1506@Github) +* 【core 】 添加LambdaUtil(pr#295@Gitee) +* 【core 】 添加StrPool和CharPool +* 【extra 】 CglibUtil增加toBean和fillBean方法 +* 【db 】 增加DriverNamePool + +### 🐞Bug修复 +* 【core 】 修复Validator.isUrl()传空返回true(issue#I3ETTY@Gitee) +* 【db 】 修复数据库driver根据url的判断识别错误问题(issue#I3EWBI@Gitee) +* 【json 】 修复JSONStrFormatter换行多余空行问题(issue#I3FA8B@Gitee) +* 【core 】 修复UrlPath中的+被转义为空格%20的问题(issue#1501@Github) +* 【core 】 修复DateUtil.parse方法对UTC时间毫秒少于3位不识别问题(issue#1503@Github) + +------------------------------------------------------------------------------------------------------------- + +# 5.6.2 (2021-03-28) + +### 🐣新特性 +* 【core 】 Validator增加车架号(车辆识别码)验证、驾驶证(驾驶证档案编号)的正则校验(pr#280@Gitee) +* 【core 】 CopyOptions增加propertiesFilter(pr#281@Gitee) +* 【extra 】 增加Wit模板引擎支持 +* 【core 】 增加DesensitizedUtil(pr#282@Gitee) +* 【core 】 增加DateTime字符串构造(issue#I3CQZG@Gitee) +* 【core 】 修改ArrayUtil代码风格(pr#287@Gitee) +* 【json 】 JSONConfig增加setStripTrailingZeros配置(issue#I3DJI8@Gitee) +* 【db 】 升级兼容BeeCP3.x + +### 🐞Bug修复 +* 【core 】 修复FileTypeUtil中OFD格式判断问题(pr#1489@Github) +* 【core 】 修复CamelCaseLinkedMap和CaseInsensitiveLinkedMap的Linked失效问题(pr#1490@Github) +* 【core 】 修复UrlPath中=被转义的问题 + +------------------------------------------------------------------------------------------------------------- + +# 5.6.1 (2021-03-18) + +### 🐣新特性 +* 【crypto 】 SecureUtil去除final修饰符(issue#1474@Github) +* 【core 】 IoUtil增加lineIter方法 +* 【core 】 新增函数式懒加载加载器(pr#275@Gitee) +* 【http 】 UserAgentUtil增加miniProgram判断(issue#1475@Github) +* 【db 】 增加Ignite数据库驱动识别 +* 【core 】 DateUtil.parse支持带毫秒的UTC时间 +* 【core 】 IdcardUtil.Idcard增加toString(pr#1487@Github) +* 【core 】 ChineseDate增加getGregorianXXX方法(issue#1481@Github) + +### 🐞Bug修复 +* 【core 】 修复IoUtil.readBytes的FileInputStream中isClose参数失效问题(issue#I3B7UD@Gitee) +* 【core 】 修复DataUnit中KB不大写的问题 +* 【json 】 修复JSONUtil.getByPath类型错误问题(issue#I3BSDF@Gitee) +* 【core 】 修复BeanUtil.toBean提供null未返回null的问题(issue#I3BQPV@Gitee) +* 【core 】 修复ModifierUtil#modifiersToInt中逻辑判断问题(issue#1486@Github) + +------------------------------------------------------------------------------------------------------------- + +# 5.6.0 (2021-03-12) + +### 🐣新特性 +* 【poi 】 重要:不再兼容POI-3.x,增加兼容POI-5.x(issue#I35J6B@Gitee) +* 【core 】 FileTypeUtil使用长匹配优先(pr#1457@Github) +* 【core 】 IterUtil和CollUtil增加isEqualList方法(issue#I3A3PY@Gitee) +* 【crypto 】 增加PBKDF2(issue#1416@Github) +* 【core 】 增加FuncKeyMap(issue#1402@Github) +* 【core 】 增加StrMatcher(issue#1379@Github) +* 【core 】 NumberUtil增加factorial针对BigInterger方法(issue#1379@Github) +* 【core 】 TreeNode增加equals方法(issue#1467@Github) +* 【core 】 增加汉字转阿拉伯数字Convert.chineseToNumber(pr#1469@Github) +* 【json 】 JSONUtil增加getByPath方法支持默认值(issue#1470@Github) +* 【crypto 】 SecureUtil增加hmacSha256方法(pr#1473@Github) +* 【core 】 FileTypeUtil判断流增加文件名辅助判断(pr#1471@Github) + +### 🐞Bug修复 +* 【socket 】 修复Client创建失败资源未释放问题。 +* 【core 】 修复DataSizeUtil中EB单位错误问题(issue#I39O7I@Gitee) +* 【core 】 修复BeanDesc.isMatchSetter的ignoreCase未使用问题(issue#I3AXIJ@Gitee) +* 【core 】 修复CRC16Checksum中(issue#I3AXIJ@Gitee) +* 【core 】 修复UrlQuery中对空key解析丢失问题(issue#I3B3J6@Gitee) + +------------------------------------------------------------------------------------------------------------- + +# 5.5.9 (2021-02-26) + +### 🐣新特性 +* 【crypto 】 PemUtil.readPemKey支持EC(pr#1366@Github) +* 【extra 】 Ftp等cd方法增加同步(issue#1397@Github) +* 【core 】 StrUtil增加endWithAnyIgnoreCase(issue#I37I0B@Gitee) +* 【crypto 】 Sm2增加getD和getQ方法(issue#I37Z4C@Gitee) +* 【cache 】 AbstractCache增加keySet方法(issue#I37Z4C@Gitee) +* 【core 】 NumberWordFormatter增加formatSimple方法(pr#1436@Github) +* 【crypto 】 增加读取openSSL生成的sm2私钥 +* 【crypto 】 增加众多方法,SM2兼容各类密钥格式(issue#I37Z75@Gitee) + +### 🐞Bug修复 +* 【json 】 JSONUtil.isJson方法改变trim策略,解决特殊空白符导致判断失败问题 +* 【json 】 修复SQLEXception导致的栈溢出(issue#1399@Github) +* 【extra 】 修复Ftp中异常参数没有传入问题(issue#1397@Github) +* 【crypto 】 修复Sm2使用D构造空指针问题(issue#I37Z4C@Gitee) +* 【poi 】 修复ExcelPicUtil中图表报错问题(issue#I38857@Gitee) +* 【core 】 修复ListUtil.page方法返回空列表无法编辑问题(issue#1415@Github) +* 【core 】 修复ListUtil.sub中step不通结果不一致问题(issue#1409@Github) +* 【db 】 修复Condition转换参数值时未转换数字异常(issue#I38LTM@Gitee) + +------------------------------------------------------------------------------------------------------------- + +# 5.5.8 (2021-01-30) + +### 🐣新特性 +* 【extra 】 增加自动装配SpringUtil类(pr#1366@Github) +* 【extra 】 ArrayUtil增加map方法重载 +* 【crypto 】 AsymmetricAlgorithm增加RSA_ECB("RSA/ECB/NoPadding")(issue#1368@Github) +* 【core 】 补充StrUtil.padXXX注释(issue#I2E1S7@Gitee) +* 【core 】 修改上传文件检查逻辑 +* 【core 】 修正LocalDateTimeUtil.offset方法注释问题(issue#I2EEXC@Gitee) +* 【extra 】 VelocityEngine的getRowEngine改为getRawEngine(issue#I2EGRG@Gitee) +* 【cache 】 缓存降低锁的粒度,提高并发能力(pr#1385@Github) +* 【core 】 SimpleCache缓存降低锁的粒度,提高并发能力(pr#1385@Github) +* 【core 】 增加RadixUtil(pr#260@Gitee) +* 【core 】 BeanUtil.getFieldValue支持获取字段集合(pr#254@Gitee) +* 【core 】 DateConvert转换失败默认抛出异常(issue#I2M5GN@Gitee) +* 【http 】 HttpServerRequest增加getParam方法 +* 【http 】 RootAction增加可选name参数,返回指定文件名称 +* 【db 】 支持人大金仓8的驱动识别 +* 【db 】 ThreadUtil增加createScheduledExecutor和schedule方法(issue#I2NUTC@Gitee) +* 【core 】 ImgUtil增加getImage方法(issue#I2DU1Z@Gitee) +* 【core 】 DateUtil.beginOfHour(pr#269@Gitee) +* 【core 】 MapUtil增加sortByValue(pr#259@Gitee) +* 【core 】 TypeUtil修正hasTypeVeriable为hasTypeVariable +* 【core 】 RandomUtil.getRandom改为new SecureRandom,避免阻塞 + +### 🐞Bug修复 +* 【core 】 修复FileUtil.move以及PathUtil.copy等无法自动创建父目录的问题(issue#I2CKTI@Gitee) +* 【core 】 修复Console.input读取不全问题(pr#263@Gitee) +* 【core 】 修复URLUtil.encodeAll未检查空指针问题(issue#I2CNPS@Gitee) +* 【core 】 修复UrlBuilder.of的query中含有?丢失问题(issue#I2CNPS@Gitee) +* 【crypto 】 修复BCrypt.checkpw报错问题(issue#1377@Github) +* 【extra 】 修复Fftp中cd失败导致的问题(issue#1371@Github) +* 【poi 】 修复ExcelWriter.merge注释问题(issue#I2DNPG@Gitee) +* 【core 】 修复CsvReader读取注释行错误问题(issue#I2D87I@Gitee) + +------------------------------------------------------------------------------------------------------------- + +# 5.5.7 (2021-01-07) + +### 🐣新特性 +* 【core 】 DynaBean.create增加重载方法(pr#245@Gitee) +* 【core 】 IdcardUtil增加重载是否忽略大小写(issue#1348@Github) +* 【poi 】 SheetRidReader增加getRidByIndex方法(issue#1342@Github) +* 【extra 】 MailAccount增加sslProtocols配置项(issue#IZN95@Gitee) +* 【extra 】 MailUtil增加getSession方法 +* 【setting】 新增setByGroup和putByGroup,set和put标记为过期(issue#I2C42H@Gitee) +* 【crypto 】 修改SymmetricAlgorithm注释(issue#1360@Github) +* 【all 】 pom中将META-INF/maven下全部exclude(pr#1355@Github) +* 【http 】 SimpleServer中增加addFilter等方法,并使用全局线程池 +* 【core 】 CollUtil.forEach 增加null 判断(pr#250@Gitee) +* 【extra 】 FtpConfig增加serverLanguageCode和systemKey配置,Ftp.download增加重载(pr#248@Gitee) + +### 🐞Bug修复 +* 【core 】 修复CsvReader读取双引号未转义问题(issue#I2BMP1@Gitee) +* 【json 】 JSONUtil.parse修复config无效问题(issue#1363@Github) +* 【http 】 修复SimpleServer返回响应内容Content-Length不正确的问题(issue#1358@Github) +* 【http 】 修复Https请求部分环境下报证书验证异常问题(issue#I2C1BZ@Gitee) + +------------------------------------------------------------------------------------------------------------- + +# 5.5.6 (2020-12-29) + +### 🐣新特性 +* 【core 】 手机号工具类 座机正则表达式统一管理(pr#243@Gitee) +* 【extra 】 Mail增加setDebugOutput方法(issue#1335@Gitee) + +### 🐞Bug修复 +* 【core 】 修复ZipUtil.unzip从流解压关闭问题(issue#I2B0S1@Gitee) +* 【poi 】 修复Excel07Writer写出表格错乱问题(issue#I2B57B@Gitee) +* 【poi 】 修复SheetRidReader读取字段错误问题(issue#1342@Github) +* 【core 】 修复FileUtil.getMimeType不支持css和js(issue#1341@Github) + +------------------------------------------------------------------------------------------------------------- + +# 5.5.5 (2020-12-27) + +### 🐣新特性 +* 【core 】 URLUtil.normalize新增重载(pr#233@Gitee) +* 【core 】 PathUtil增加isSub和toAbsNormal方法 +* 【db 】 RedisDS实现序列化接口(pr#1323@Github) +* 【poi 】 StyleUtil增加getFormat方法(pr#235@Gitee) +* 【poi 】 增加ExcelDateUtil更多日期格式支持(issue#1316@Github) +* 【core 】 NumberUtil.toBigDecimal支持各类数字格式,如1,234.56等(issue#1334@Github) +* 【core 】 NumberUtil增加parseXXX方法(issue#1334@Github) +* 【poi 】 Excel07SaxReader支持通过sheetName读取(issue#I2AOSE@Gitee) + +### 🐞Bug修复 +* 【core 】 FileUtil.isSub相对路径判断问题(pr#1315@Github) +* 【core 】 TreeUtil增加空判定(issue#I2ACCW@Gitee) +* 【db 】 解决Hive获取表名失败问题(issue#I2AGLU@Gitee) +* 【core 】 修复DateUtil.parse未使用严格模式导致结果不正常的问题(issue#1332@Github) +* 【core 】 修复RuntimeUtil.getUsableMemory非static问题(issue#I2AQ2M@Gitee) +* 【core 】 修复ArrayUtil.equals方法严格判断问题(issue#I2AO8B@Gitee) +* 【poi 】 修复SheetRidReader在获取rid时读取错误问题(issue#I2AOQW@Gitee) +* 【core 】 修复强依赖了POI的问题(issue#1336@Github) + +------------------------------------------------------------------------------------------------------------- + +# 5.5.4 (2020-12-16) + +### 🐣新特性 +### 🐞Bug修复 +* 【core 】 修复IoUtil.readBytes的问题 + +------------------------------------------------------------------------------------------------------------- + +# 5.5.3 (2020-12-11) + +### 🐣新特性 +* 【core 】 IdcardUtil增加行政区划83(issue#1277@Github) +* 【core 】 multipart中int改为long,解决大文件上传越界问题(issue#I27WZ3@Gitee) +* 【core 】 ListUtil.page增加检查(pr#224@Gitee) +* 【db 】 Db增加使用sql的page方法(issue#247@Gitee) +* 【cache 】 CacheObj的isExpired()逻辑修改(issue#1295@Github) +* 【json 】 JSONStrFormater改为JSONStrFormatter +* 【dfa 】 增加FoundWord(pr#1290@Github) +* 【core 】 增加Segment(pr#1290@Github) +* 【core 】 增加CharSequenceUtil +* 【poi 】 Excel07SaxReader拆分出SheetDataSaxHandler +* 【core 】 CollUtil.addAll增加判空(pr#228@Gitee) +* 【core 】 修正DateUtil.betweenXXX注释错误(issue#I28XGW@Gitee) +* 【core 】 增加NioUtil +* 【core 】 增加GanymedUtil +* 【poi 】 增加OFD支持,OfdWriter +* 【poi 】 修复NumberUtil属性拼写错误(pr#1311@Github) +* 【core 】 MapUtil增加getQuietly方法(issue#I29IWO@Gitee) + +### 🐞Bug修复 +* 【cache 】 修复Cache中get重复misCount计数问题(issue#1281@Github) +* 【poi 】 修复sax读取自定义格式单元格无法识别日期类型的问题(issue#1283@Github) +* 【core 】 修复CollUtil.get越界问题(issue#1292@Github) +* 【core 】 修复TemporalAccessorUtil无法格式化LocalDate带时间问题(issue#1289@Github) +* 【json 】 修复自定义日期格式的LocalDateTime没有包装引号问题(issue#1289@Github) +* 【cache 】 get中unlock改为unlockRead(issue#1294@Github) +* 【db 】 修复表名包含点导致的问题(issue#1300@Github) +* 【poi 】 修复xdr:row标签导致的问题(issue#1297@Github) +* 【core 】 修复FileUtil.loopFiles使用FileFilter无效问题(issue#I28V48@Gitee) +* 【extra 】 修复JschUtil.execByShell返回空的问题(issue#1067@Github) +* 【poi 】 修复特殊的excel使用sax读取时未读到值的问题(issue#1303@Github) +* 【http 】 修复HttpUtil类条件判断错误(pr#232@Gitee) + +------------------------------------------------------------------------------------------------------------- + +# 5.5.2 (2020-12-01) + +### 🐣新特性 +* 【crypto 】 KeyUtil增加重载,AES构造增加重载(issue#I25NNZ@Gitee) +* 【json 】 JSONUtil增加toList重载(issue#1228@Github) +* 【core 】 新增CollStreamUtil(issue#1228@Github) +* 【extra 】 新增Rhino表达式执行引擎(pr#1229@Github) +* 【crypto 】 增加判空(issue#1230@Github) +* 【core 】 xml.setXmlStandalone(true)格式优化(pr#1234@Github) +* 【core 】 AnnotationUtil增加setValue方法(pr#1250@Github) +* 【core 】 ZipUtil增加get方法(issue#I27CUF@Gitee) +* 【cache 】 对CacheObj等变量使用volatile关键字 +* 【core 】 Base64增加encodeWithoutPadding方法(issue#I26J16@Gitee) +* 【core 】 ExceptionUtil增加message消息包装为运行时异常的方法(pr#1253@Gitee) +* 【core 】 DatePattern增加年月格式化常量(pr#220@Gitee) +* 【core 】 ArrayUtil增加shuffle方法(pr#1255@Github) +* 【core 】 ArrayUtil部分方法分离至PrimitiveArrayUtil +* 【crypto 】 opt改为otp包(issue#1257@Github) +* 【cache 】 增加CacheListener(issue#1257@Github) +* 【core 】 TimeInterval支持分组(issue#1238@Github) +* 【core 】 增加compile包(pr#1243@Github) +* 【core 】 增加ResourceClassLoader、CharSequenceResource、FileObjectResource +* 【core 】 修改IoUtil.read(Reader)逻辑默认关闭Reader +* 【core 】 ZipUtil增加Zip方法(pr#222@Gitee) +* 【all 】 增加Hutool.getAllUtils和printAllUtils方法 +* 【core 】 增加PunyCode(issue#1268@Gitee) +* 【core 】 ArrayUtil增加isSorted方法(pr#1271@Github) +* 【captcha】 增加GifCaptcha(pr#1273@Github) +* 【core 】 增加SSLUtil、SSLContextBuilder + +### 🐞Bug修复 +* 【cron 】 修复CronTimer可能死循环的问题(issue#1224@Github) +* 【core 】 修复Calculator.conversion单个数字越界问题(issue#1222@Github) +* 【poi 】 修复ExcelUtil.getSaxReader使用非MarkSupport流报错问题(issue#1225@Github) +* 【core 】 修复HexUtil.format问题(issue#I268XT@Gitee) +* 【core 】 修复ZipUtil判断压缩文件是否位于压缩目录内的逻辑有误的问题(issue#1251@Github) +* 【json 】 修复JSONObject.accumulate问题 +* 【poi 】 修复部分xlsx文件sax方式解析空指针问题(issue#1265@Github) +* 【core 】 修复PatternPool中邮编的正则(issue#1274@Github) + +------------------------------------------------------------------------------------------------------------- + +# 5.5.1 (2020-11-16) + +### 🐣新特性 +* 【core 】 增加CopyVisitor和DelVisitor + +### 🐞Bug修复 +* 【core 】 修复在Linux下FileUtil.move失败问题(issue#I254Y3@Gitee) +* 【http 】 修复UrlUtil和UrlBuilder中多个/被替换问题(issue#I25MZL@Gitee) + +------------------------------------------------------------------------------------------------------------- + +# 5.5.0 (2020-11-14) + +### 大版本特性 +* 【extra 】 增加jakarta.validation-api封装:ValidationUtil(pr#207@Gitee) +* 【extra 】 增加表达式引擎封装:ExpressionUtil(pr#1203@Github) +* 【extra 】 新增基于Apache-FtpServer封装:SimpleFtpServer +* 【extra 】 新增基于Commons-Compress封装:CompressUtil + +### 🐣新特性 +* 【core 】 NumberUtil.parseInt等支持123,2.00这类数字(issue#I23ORQ@Gitee) +* 【core 】 增加ArrayUtil.isSub、indexOfSub、lastIndexOfSub方法(issue#I23O1K@Gitee) +* 【core 】 反射调用支持传递参数的值为null(pr#1205@Github) +* 【core 】 HexUtil增加format方法(issue#I245NF@Gitee) +* 【poi 】 ExcelWriter增加setCurrentRowToEnd方法(issue#I24A2R@Gitee) +* 【core 】 ExcelWriter增加setCurrentRowToEnd方法(issue#I24A2R@Gitee) +* 【core 】 增加enum转数字支持(issue#I24QZY@Gitee) +* 【core 】 NumberUtil.toBigDecimal空白符转换为0(issue#I24MRP@Gitee) +* 【core 】 CollUtil和IterUtil增加size方法(pr#208@Gitee) +* 【poi 】 ExcelReader的read方法读取空单元格增加CellEditor处理(issue#1213@Github) + +### 🐞Bug修复 +* 【core 】 修复DateUtil.current使用System.nanoTime的问题(issue#1198@Github) +* 【core 】 修复Excel03SaxReader判断日期出错问题(issue#I23M9H@Gitee) +* 【core 】 修复ClassUtil.getTypeArgument方法在判断泛型时导致的问题(issue#1207@Github) +* 【core 】 修复Ipv4Util分隔符问题(issue#I24A9I@Gitee) +* 【core 】 修复Ipv4Util.longToIp的问题 +* 【poi 】 修复Excel07SaxReader读取公式的错误的问题(issue#I23VFL@Gitee) +* 【http 】 修复HttpUtil.isHttp判断问题(pr#1208@Github) +* 【http 】 修复Snowflake时间回拨导致ID重复的bug(issue#1206@Github) +* 【core 】 修复StrUtil.lastIndexOf查找位于首位的字符串找不到的bug(issue#I24RSV@Gitee) +* 【poi 】 修复BigExcelWriter的autoSizeColumnAll问题(pr#1221@Github) +* 【core 】 修复StrUtil.subBetweenAll不支持相同字符的问题(pr#1217@Github) + +------------------------------------------------------------------------------------------------------------- + +# 5.4.7 (2020-10-31) + +### 🐣新特性 +* 【core 】 增加OptionalBean(pr#1182@Github) +* 【core 】 Ganzhi增加方法(issue#1186@Github) +* 【core 】 CollUtil增加forEach重载(issue#I22NA4@Gitee) +* 【core 】 CollUtil.map忽略空值改规则为原数组中的元素和处理后的元素都会忽略空值(issue#I22N08@Gitee) +* 【http 】 增加SoapClient增加addSOAPHeader重载 +* 【http 】 ArrayUtil增加containsAll方法 +* 【core 】 增加CharsetDetector +* 【cron 】 增加CronTask,监听支持获取id(issue#I23315@Gitee) + +### 🐞Bug修复 +* 【core 】 修复BeanUtil.beanToMap方法中editor返回null没有去掉的问题 +* 【core 】 修复ImgUtil.toBufferedImage颜色模式的问题(issue#1194@Github) +* 【cron 】 修复TimeZone设置无效的问题(issue#I23315@Gitee) + +------------------------------------------------------------------------------------------------------------- + +# 5.4.6 (2020-10-23) + +### 🐣新特性 +* 【http 】 HttpRequest增加basicProxyAuth方法(issue#I1YQGM@Gitee) +* 【core 】 NumberUtil.toStr修改逻辑,去掉BigDecimal的科学计数表示(pr#196@Gitee) +* 【core 】 ListUtil.page第一页页码使用PageUtil(pr#198@Gitee) +* 【http 】 增加微信、企业微信ua识别(pr#1179@Github) +* 【core 】 ObjectUtil增加defaultIfXXX(pr#199@Gitee) +* 【json 】 JSONObject构建时不支持的对象类型抛出异常 + +### 🐞Bug修复 +* 【core 】 修复ChineseDate没有忽略时分秒导致计算错误问题(issue#I1YW12@Gitee) +* 【core 】 修复FileUtil中,copyFile方法断言判断参数传递错误(issue#I1Z2NY@Gitee) +* 【core 】 修复BeanDesc读取父类属性覆盖子类属性导致的问题(pr#1175@Github) +* 【aop 】 修复SimpleAspect一个重载导致的问题,去掉重载的after方法(issue#I1YUG9@Gitee) +* 【poi 】 修复03 sax读取日期问题(issue#I1Z83N@Gitee) +* 【core 】 修复FileUtil.size软链导致的问题(pr#200@Gitee) +* 【core 】 修复JSONObject构造时传入JSONArray结果出错问题(issue#I22FDS@Gitee) + +------------------------------------------------------------------------------------------------------------- + +# 5.4.5 (2020-10-18) + +### 🐣新特性 +* 【core 】 ConsoleTable代码优化(pr#190@Gitee) +* 【http 】 HttpRequest增加setProxy重载(pr#190@Gitee) +* 【core 】 XmlUtil.cleanComment(pr#191@Gitee) +* 【core 】 ArrayUtil.unWrap增加默认值(pr#1149@Github) +* 【core 】 ArrayUtil.indexOf修改double的equals判断(pr#1147@Github) +* 【core 】 优化StrUtil中部分参数校验以及逻辑处理(pr#1144@Github) +* 【core 】 简化CreditCode逻辑去除无用Character.toUpperCase(pr#1145@Github) +* 【core 】 NumberUtil增加generateRandomNumber重载,可自定义seed(issue#I1XTUT@Gitee) +* 【core 】 DataSizeUtil支持小数(pr#1158@Github) +* 【core 】 完善注释(pr#193@Gitee) +* 【core 】 优化Combination.countAll(pr#1159@Github) +* 【core 】 优化针对list的split方法(pr#194@Gitee) +* 【poi 】 ExcelWriter增加setRowStyle方法 +* 【core 】 Assert增加函数接口(pr#1166@Github) +* 【core 】 新增AtomicIntegerArray、AtomicLongArray转换 +* 【extra 】 PinyinUtil新增Bopomofo4j支持 +* 【core 】 新增TemporalUtil工具类,新增时间相关方法 + +### 🐞Bug修复 +* 【core 】 解决农历判断节日未判断大小月导致的问题(issue#I1XHSF@Gitee) +* 【core 】 解决ListUtil计算总量可能的int溢出问题(pr#1150@Github) +* 【json 】 解决JSON中转换为double小数精度丢失问题(pr#192@Gitee) +* 【core 】 修复CaseInsensitiveMap的remove等方法并没有忽略大小写的问题(pr#1163@Gitee) +* 【poi 】 修复合并单元格值读取错误的问题 +* 【poi 】 修复NamedSql解析形如col::numeric出错问题(issue#I1YHBX@Gitee) +* 【core 】 修复计算相差天数导致的问题 + +------------------------------------------------------------------------------------------------------------- + +# 5.4.4 (2020-09-28) + +### 🐣新特性 +* 【core 】 ServiceLoaderUtil改为使用contextClassLoader(pr#183@Gitee) +* 【core 】 NetUtil增加getLocalHostName(pr#1103@Github) +* 【extra 】 FTP增加stat方法(issue#I1W346@Gitee) +* 【core 】 Convert.toNumber支持类似12.2F这种形式字符串转换(issue#I1VYLJ@Gitee) +* 【core 】 使用静态变量替换999等(issue#I1W8IB@Gitee) +* 【core 】 URLUtil自动trim(issue#I1W803@Gitee) +* 【crypto 】 RC4增加ecrypt(pr#1108@Github) +* 【core 】 CharUtil and StrUtil增加@(pr#1106@Github) +* 【extra 】 优化EMOJ查询逻辑(pr#1112@Github) +* 【extra 】 优化CollUtil交并集结果集合设置初始化大小,避免扩容成本(pr#1110@Github) +* 【core 】 优化PageUtil彩虹算法(issue#1110@Github) +* 【core 】 IoUtil增加readUtf8方法 +* 【core 】 优化全局邮箱账户初始化逻辑(pr#1114@Github) +* 【http 】 SoapClient增加addSOAPHeader方法 +* 【http 】 完善StrUtil的注释(pr#186@Gitee) +* 【aop 】 去除调试日志(issue#1116@Github) +* 【core 】 增加'反转义(pr#1121@Github) +* 【poi 】 增加SheetReader和XXXRowHandler(issue#I1WHJP@Gitee) +* 【dfa 】 增加过滤符号(pr#1122@Github) +* 【dfa 】 SensitiveUtil增加setCharFilter方法(pr#1123@Github) +* 【all 】 优化常量大小写规范(pr#188@Gitee) +* 【core 】 优化NumberUtil中针对BigDecimal的一些处理逻辑(pr#1127@Github) +* 【core 】 NumberUtil.factorial注释明确(pr#1126@Github) +* 【core 】 NumberUtil增加isPowerOfTwo方法(pr#1132@Github) +* 【core 】 优化BooleanUtil的校验逻辑(pr#1137@Github) +* 【poi 】 改进sax方式读取逻辑,支持sheetId(issue#1141@Github) +* 【core 】 XmlUtil增加readBySax方法 + +### 🐞Bug修复 +* 【crypto 】 修复SM2验签后无法解密问题(issue#I1W0VP@Gitee) +* 【core 】 修复新建默认TreeSet没有默认比较器导致的问题(issue#1101@Github) +* 【core 】 修复Linux下使用Windows路径分隔符导致的解压错误(issue#I1MW0E@Gitee) +* 【core 】 修复Word07Writer写出map问题(issue#I1W49R@Gitee) +* 【script 】 修复函数库脚本执行问题 +* 【core 】 修复RGB随机颜色的上限值不对且API重复(pr#1136@Gihub) + +------------------------------------------------------------------------------------------------------------- + +# 5.4.3 (2020-09-16) + +### 🐣新特性 +* 【core 】 使用静态的of方法来new对象(pr#177@Gitee) +* 【setting】 Setting增加store无参方法(issue#1072@Github) +* 【setting】 StatementUtil增加null缓存(pr#1076@Github) +* 【core 】 扩充Console功能,支持可变参数(issue#1077@Github) +* 【crypto 】 增加ECKeyUtil(issue#I1UOF5@Gitee) +* 【core 】 增加TransXXX(issue#I1TU1Y@Gitee) +* 【core 】 增加Generator +* 【db 】 Column增加是否主键、保留位数等字段 +* 【cache 】 Cache接口增加get重载(issue#1080@Github) +* 【core 】 增加Interner和InternUtil(issue#I1TU1Y@Gitee) +* 【core 】 增加Calculator(issue#1090@Github) +* 【core 】 IdcardUtil增加getIdcardInfo方法(issue#1092@Github) +* 【core 】 改进ObjectUtil.equal,支持BigDecimal判断 +* 【core 】 ArrayConverter增加可选是否忽略错误(issue#I1VNYQ@Gitee) +* 【db 】 增加ConditionBuilder +* 【setting】 Setting和Props增加create方法 +* 【log 】 增加TinyLog2支持(issue#1094@Github) + +### 🐞Bug修复 +* 【core 】 修复Dict.of错误(issue#I1UUO5@Gitee) +* 【core 】 修复UrlBuilder地址参数问题(issue#I1UWCA@Gitee) +* 【core 】 修复StrUtil.toSymbolCase转换问题(issue#1075@Github) +* 【log 】 修复打印null对象显示{msg}异常问题(issue#1084@Github) +* 【extra 】 修复ServletUtil.getReader中未关闭的问题 +* 【extra 】 修复QrCodeUtil在新版本zxing报错问题(issue#1088@Github) +* 【core 】 修复LocalDateTimeUtil.parse无法解析yyyyMMddHHmmssSSS的bug(issue#1082@Github) +* 【core 】 修复VersionComparator.equals递归调用问题(issue#1093@Github) + +------------------------------------------------------------------------------------------------------------- + +# 5.4.2 (2020-09-09) + +### 🐣新特性 +* 【core 】 lock放在try外边(pr#1050@Github) +* 【core 】 MailUtil增加错误信息(issue#I1TAKJ@Gitee) +* 【core 】 JschUtil添加远程转发功能(pr#171@Gitee) +* 【db 】 AbstractDb增加executeBatch重载(issue#1053@Github) +* 【extra 】 新增方便引入SpringUtil的注解@EnableSpringUtil(pr#172@Gitee) +* 【poi 】 RowUtil增加插入和删除行(pr#1060@Github) +* 【extra 】 SpringUtil增加注册bean(pr#174@Gitee) +* 【core 】 修改NetUtil.getMacAddress避免空指针(issue#1057@Github) +* 【core 】 增加EnumItem接口,枚举扩展转换,增加SPI自定义转换(pr#173@Github) +* 【core 】 TypeUtil增加getActualType,增加ActualTypeMapperPool类(issue#I1TBWH@Gitee) +* 【extra 】 QRConfig中添加qrVersion属性(pr#1068@Github) +* 【core 】 ArrayUtil增加equals方法 +* 【core 】 BeanDesc增加方法 +* 【core 】 增加@PropIgnore注解(issue#I1U846@Gitee) + +### 🐞Bug修复 +* 【core 】 重新整理农历节假日,解决一个pr过来的玩笑导致的问题 +* 【poi 】 修复ExcelFileUtil.isXls判断问题(pr#1055@Github) +* 【poi 】 修复CglibUtil.copyList参数错误导致的问题 +* 【http 】 修复GET请求附带body导致变POST的问题 +* 【core 】 修复double相等判断问题(pr#175@Gitee) +* 【core 】 修复DateSizeUtil.format越界问题(issue#1069@Github) +* 【core 】 修复ChineseDate.getChineseMonth问题(issue#I1UG72@Gitee) + +------------------------------------------------------------------------------------------------------------- + +# 5.4.1 (2020-08-29) + +### 🐣新特性 +* 【core 】 StrUtil增加firstNonXXX方法(issue#1020@Github) +* 【core 】 BeanCopier修改规则,可选bean拷贝空字段报错问题(pr#160@Gitee) +* 【http 】 HttpUtil增加downloadFileFromUrl(pr#1023@Github) +* 【core 】 增加toEpochMilli方法 +* 【core 】 Validator修改isCitizenId校验(pr#1032@Github) +* 【core 】 增加PathUtil和FileNameUtil,分离FileUtil中部分方法 +* 【core 】 改造IndexedComparator,增加InstanceComparator +* 【extra 】 增加CglibUtil +* 【core 】 增加Ipv4Util(pr#161@Gitee) +* 【core 】 增加CalendarUtil和DateUtil增加isSameMonth方法(pr#161@Gitee) +* 【core 】 Dict增加of方法(issue#1035@Github) +* 【core 】 StrUtil.wrapAll方法不明确修改改为wrapAllWithPair(issue#1042@Github) +* 【core 】 EnumUtil.getEnumAt负数返回null(pr#167@Gitee) +* 【core 】 ChineseDate增加天干地支和转换为公历方法(pr#169@Gitee) +* 【core 】 Img增加stroke描边方法(issue#1033@Github) + +### 🐞Bug修复# +* 【poi 】 修复ExcelBase.isXlsx方法判断问题(issue#I1S502@Gitee) +* 【poi 】 修复Excel03SaxReader日期方法判断问题(pr#1026@Github) +* 【core 】 修复StrUtil.indexOf空指针问题(issue#1038@Github) +* 【extra 】 修复VelocityEngine编码问题和路径前缀问题(issue#I1T0IG@Gitee) + +------------------------------------------------------------------------------------------------------------- + +# 5.4.0 (2020-08-06) + +### 🐣新特性 +* 【socket】 对NioServer和NioClient改造(pr#992@Github) +* 【core 】 StrUtil增加filter方法(pr#149@Gitee) +* 【core 】 DateUtil增加beginOfWeek重载 +* 【core 】 将有歧义的BeanUtil.mapToBean方法置为过期(使用toBean方法) +* 【core 】 添加WatchAction(对Watcher的抽象) +* 【core 】 修改UUID正则,更加严谨(issue#I1Q1IW@Gitee) +* 【core 】 ArrayUtil增加isAllNull方法(issue#1004@Github) +* 【core 】 CollUtil增加contains方法(pr#152@Gitee) +* 【core 】 ArrayUtil增加isAllNotNull方法(pr#1008@Github) +* 【poi 】 closeAfterRead参数无效,方法设为过期(issue#1007@Github) +* 【core 】 CollUtil中部分方法返回null变更为返回empty +* 【all 】 添加英文README(pr#153@Gitee) +* 【extra 】 SpringUtil增加getBean(TypeReference)(pr#1009@Github) +* 【core 】 Assert增加方法,支持自定义异常处理(pr#154@Gitee) +* 【core 】 BooleanConverter增加数字转换规则(issue#I1R2AB@Gitee) +* 【poi 】 sax方式读取增加一个sheet结束的回调(issue#155@Gitee) +* 【db 】 增加BeeCP连接池支持 +* 【core 】 改进Img.pressImage方法,避免变色问题(issue#1001@Github) + +### 🐞Bug修复# +* 【core 】 修复原始类型转换时,转换失败没有抛出异常的问题 +* 【core 】 修复BeanUtil.mapToBean中bean的class非空构造无法实例化问题 +* 【core 】 修复NamedSql多个连续变量出现替换问题 +* 【core 】 修复Bean重名字段(大小写区别)获取数据出错的问题(issue#I1QBQ4@Gitee) +* 【http 】 修复SimpleServer响应头无效问题(issue#1006@Github) +* 【core 】 修复ThreadLocalRandom共享seed导致获取随机数一样的问题(pr#151@Gitee) + +------------------------------------------------------------------------------------------------------------- + +# 5.3.11 (2020-08-01) + +### 🐣新特性 +* 【captcha】 AbstractCaptcha增加getImageBase64Data方法(pr#985@Github) +* 【core 】 增加PhoneUtil(pr#990@Github) +* 【core 】 改进Img,目标图片类型未定义使用源图片类型(issue#I1PB0B@Gitee) +* 【json 】 JSONConfig增加Transient选项(issue#I1PLHN@Gitee) +* 【core 】 MapUtil增加getXXX的默认值重载(issue#I1PTGI@Gitee) +* 【core 】 CalendarUtil增加parseByPatterns方法(issue#993@Github) + +### 🐞Bug修复# + +------------------------------------------------------------------------------------------------------------- + +## 5.3.10 (2020-07-23) + +### 🐣新特性 +* 【db 】 增加DbUtil.setReturnGeneratedKeyGlobal(issue#I1NM0K@Gitee) +* 【core 】 增加DataSize和DataSizeUtil(issue#967@Github) +* 【core 】 ImgUtil增加异常,避免空指针(issue#I1NKXG@Gitee) +* 【core 】 增加CRC16算法若干(pr#963@Github) +* 【core 】 LocalDateTimeUtil增加format等方法(pr#140@Gitee) +* 【http 】 UserAgentUtil增加Android原生浏览器识别(pr#975@Github) +* 【crypto 】 增加ECIES算法类(issue#979@Github) +* 【crypto 】 CollUtil增加padLeft和padRight方法(pr#141@Gitee) +* 【core 】 IdCardUtil香港身份证去除首字母校验(issue#I1OOTB@Gitee) + +### 🐞Bug修复 +* 【core 】 修复ZipUtil中finish位于循环内的问题(issue#961@Github) +* 【core 】 修复CollUtil.page未越界检查的问题(issue#I1O2LR@Gitee) +* 【core 】 修复StrUtil.removeAny的bug(issue#977@Github) + +------------------------------------------------------------------------------------------------------------- + +## 5.3.9 (2020-07-12) + +### 🐣新特性 +* 【core 】 DateUtil增加formatChineseDate(pr#932@Github) +* 【core 】 ArrayUtil.isEmpty修改逻辑(pr#948@Github) +* 【core 】 增强StrUtil中空判断后返回数据性能(pr#949@Github) +* 【core 】 deprecate掉millsecond,改为millisecond(issue#I1M9P8@Gitee) +* 【core 】 增加LocalDateTimeUtil(issue#I1KUVC@Gitee) +* 【core 】 Month增加getLastDay方法 +* 【core 】 ChineseDate支持到2099年 + +### 🐞Bug修复 +* 【core 】 修复NumberUtil.partValue有余数问题(issue#I1KX66@Gitee) +* 【core 】 修复BeanUtil.isEmpty不能忽略static字段问题(issue#I1KZI6@Gitee) +* 【core 】 修复StrUtil.brief长度问题(pr#930@Github) +* 【socket 】 修复AioSession构造超时无效问题(pr#941@Github) +* 【setting】 修复GroupSet.contains错误(pr#943@Github) +* 【core 】 修复ZipUtil没有调用finish问题(issue#944@Github) +* 【extra 】 修复Ftp中ArrayList长度为负问题(pr#136@Github) +* 【core 】 修复Dict中putAll大小写问题(issue#I1MU5B@Gitee) +* 【core 】 修复POI中sax读取数字判断错误问题(issue#931@Github) +* 【core 】 修复DateUtil.endOfQuarter错误问题(issue#I1NGZ7@Gitee) +* 【core 】 修复URL中有空格转为+问题(issue#I1NGW4@Gitee) +* 【core 】 修复CollUtil.intersectionDistinct空集合结果错误问题 +* 【core 】 修复ChineseDate在1996年计算错误问题(issue#I1N96I@Gitee) + +------------------------------------------------------------------------------------------------------------- + +## 5.3.8 (2020-06-16) + +### 🐣新特性 +* 【core 】 增加ISO8601日期格式(issue#904@Github) +* 【setting】 Props异常规则修改(issue#907@Github) +* 【setting】 增加GIF支持 +* 【core 】 复制创建一个Bean对象, 并忽略某些属性(pr#130@Gitee) +* 【core 】 DateUtil.parse支持更多日期格式(issue#I1KHTB@Gitee) +* 【crypto 】 增加获取密钥空指针的检查(issue#925@Github) +* 【core 】 增加StrUtil.removeAny方法(issue#923@Github) +* 【db 】 增加部分Connection参数支持(issue#924@Github) +* 【core 】 FileUtil增加别名方法(pr#926@Github) +* 【poi 】 ExcelReader中增加read重载,提供每个单元格单独处理的方法(issue#I1JZTL@Gitee) + +### 🐞Bug修复 +* 【json 】 修复append方法导致的JSONConfig传递失效问题(issue#906@Github) +* 【core 】 修复CollUtil.subtractToList判断错误(pr#915@Github) +* 【poi 】 修复WordWriter写表格问题(pr#914@Github) +* 【core 】 修复IoUtil.readBytes缓存数组长度问题(issue#I1KIUE@Gitee) +* 【core 】 修复BigExcelWriter多次flush导致的问题(issue#920@Github) +* 【extra 】 绕过Pinyin4j最后一个分隔符失效的bug(issue#921@Github) + +------------------------------------------------------------------------------------------------------------- + +## 5.3.7 (2020-06-03) + +### 🐣新特性 +* 【core 】 ThreadFactoryBuilder的setUncaughtExceptionHandler返回this(issue#I1J4YJ@Gitee) + +### 🐞Bug修复 +* 【core 】 修复DateUtil.parse解析2020-5-8 3:12:13错误问题(issue#I1IZA3@Gitee) +* 【core 】 修复Img.pressImg大小无效问题(issue#I1HSWU@Gitee) +* 【core 】 修复CronUtil.stop没有清除任务的问题(issue#I1JACI@Gitee) + +------------------------------------------------------------------------------------------------------------- +## 5.3.6 (2020-05-30) + +### 🐣新特性 +* 【core 】 NumberConverter Long类型增加日期转换(pr#872@Github) +* 【all 】 StrUtil and SymmetricCrypto注释修正(pr#873@Github) +* 【core 】 CsvReader支持返回Bean(issue#869@Github) +* 【core 】 Snowflake循环等待下一个时间时避免长时间循环,加入对时钟倒退的判断(pr#874@Github) +* 【extra 】 新增 QRCode base64 编码形式返回(pr#878@Github) +* 【core 】 ImgUtil增加toBase64DateUri,URLUtil增加getDataUri方法 +* 【core 】 IterUtil添加List转Map的工具方法(pr#123@Gitee) +* 【core 】 BeanValueProvider转换失败时,返回原数据,而非null +* 【core 】 支持BeanUtil.toBean(object, Map.class)转换(issue#I1I4HC@Gitee) +* 【core 】 MapUtil和CollUtil增加clear方法(issue#I1I4HC@Gitee) +* 【core 】 增加FontUtil,可定义pressText是否从中间(issue#I1HSWU@Gitee) +* 【http 】 SoapClient支持自定义请求头(issue#I1I0AO@Gitee) +* 【script 】 ScriptUtil增加evalInvocable和invoke方法(issue#I1HHCP@Gitee) +* 【core 】 ImgUtil增加去除背景色的方法(pr#124@Gitee) +* 【system 】 OshiUtil增加获取CPU使用率的方法(pr#124@Gitee) +* 【crypto 】 AsymmetricAlgorithm去除EC(issue#887@Github) +* 【cache 】 超时缓存使用的线程池大小默认为1(issue#890@Github) +* 【poi 】 ExcelSaxReader支持handleCell方法 +* 【core 】 Snowflake容忍2秒内的时间回拨(issue#I1IGDX@Gitee) +* 【core 】 StrUtil增加isAllNotEmpty、isAllNotBlank方法(pr#895@Github) +* 【core 】 DateUtil增加dayOfYear方法(pr#895@Github) +* 【core 】 DateUtil增加dayOfYear方法(pr#895@Github) +* 【http 】 HttpUtil增加downloadBytes方法(pr#895@Github) +* 【core 】 isMactchRegex失效标记,增加isMatchRegex(issue#I1IPJG@Gitee) +* 【core 】 优化Validator.isChinese +* 【core 】 ArrayUtil.addAll增加原始类型支持(issue#898@Github) +* 【core 】 DateUtil.parse支持2020-1-1这类日期解析(issue#I1HGWW@Github) + +### 🐞Bug修复 +* 【core 】 修复SimpleCache死锁问题(issue#I1HOKB@Gitee) +* 【core 】 修复SemaphoreRunnable释放问题(issue#I1HLQQ@Gitee) +* 【poi 】 修复Sax方式读取Excel行号错误问题(issue#882@Github) +* 【poi 】 修复Sax方式读取Excel日期类型数据03和07不一致问题(issue#I1HL1C@Gitee) +* 【poi 】 修复CamelCaseLinkedMap构造错误(issue#I1IZ30@Gitee) + +------------------------------------------------------------------------------------------------------------- + +## 5.3.5 (2020-05-13) + +### 🐣新特性 +* 【core 】 增加CollUtil.map方法 +* 【extra 】 增加Sftp.lsEntries方法,Ftp和Sftp增加recursiveDownloadFolder(pr#121@Gitee) +* 【system 】 OshiUtil增加getNetworkIFs方法 +* 【core 】 CollUtil增加unionDistinct、unionAll方法(pr#122@Gitee) +* 【core 】 增加IoUtil.readObj重载,通过ValidateObjectInputStream由用户自定义安全检查。 +* 【http 】 改造HttpRequest中文件上传部分,增加MultipartBody类 + +### 🐞Bug修复 +* 【core 】 修复IoUtil.readObj中反序列化安全检查导致的一些问题,去掉安全检查。 +* 【http 】 修复SimpleServer文件访问404问题(issue#I1GZI3@Gitee) +* 【core 】 修复BeanCopier中循环引用逻辑问题(issue#I1H2VN@Gitee) + +------------------------------------------------------------------------------------------------------------- + +## 5.3.4 (2020-05-10) + +### 🐣新特性 +* 【core 】 增加URLUtil.getContentLength方法(issue#I1GB1Z@Gitee) +* 【extra 】 增加PinyinUtil(issue#I1GMIV@Gitee) + +### 🐞Bug修复 +* 【extra 】 修复Ftp设置超时问题(issue#I1GMTQ@Gitee) +* 【core 】 修复TreeUtil根据id查找子节点时的NPE问题(pr#120@Gitee) +* 【core 】 修复BeanUtil.copyProperties中Alias注解无效问题(issue#I1GK3M@Gitee) +* 【core 】 修复CollUtil.containsAll空集合判断问题(issue#I1G9DE@Gitee) +* 【core 】 修复XmlUtil.xmlToBean失败问题(issue#865@Github) + +------------------------------------------------------------------------------------------------------------- + +## 5.3.3 (2020-05-05) + +### 🐣新特性 +* 【core 】 ImgUtil.createImage支持背景透明(issue#851@Github) +* 【json 】 更改JSON转字符串时" + *
        + *
      • 0-7: 标准颜色(同ESC [ 30–37 m)
      • + *
      • 8-15: 高强度颜色(同ESC [ 90–97 m)
      • + *
      • 16-231(6 × 6 × 6 共 216色): 16 + 36 × r + 6 × g + b (0 ≤ r, g, b ≤ 5)
      • + *
      • 232-255: 从黑到白的24阶灰度色
      • + *
      + * + *

      来自Spring Boot

      + * + * @author Toshiaki Maki, Phillip Webb + * @see #foreground(int) + * @see #background(int) + * @since 5.8.0 + */ +public final class Ansi8BitColor implements AnsiElement { + + private static final String PREFIX_FORE = "38;5;"; + private static final String PREFIX_BACK = "48;5;"; + + /** + * 前景色ANSI颜色实例 + * + * @param code 颜色代码(0-255) + * @return 前景色ANSI颜色实例 + */ + public static Ansi8BitColor foreground(int code) { + return new Ansi8BitColor(PREFIX_FORE, code); + } + + /** + * 背景色ANSI颜色实例 + * + * @param code 颜色代码(0-255) + * @return 背景色ANSI颜色实例 + */ + public static Ansi8BitColor background(int code) { + return new Ansi8BitColor(PREFIX_BACK, code); + } + + private final String prefix; + private final int code; + + /** + * 构造 + * + * @param prefix 前缀 + * @param code 颜色代码(0-255) + * @throws IllegalArgumentException 颜色代码不在0~255范围内 + */ + private Ansi8BitColor(String prefix, int code) { + Assert.isTrue(code >= 0 && code <= 255, "Code must be between 0 and 255"); + this.prefix = prefix; + this.code = code; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + Ansi8BitColor other = (Ansi8BitColor) obj; + return this.prefix.equals(other.prefix) && this.code == other.code; + } + + @Override + public int hashCode() { + return this.prefix.hashCode() * 31 + this.code; + } + + @Override + public String toString() { + return this.prefix + this.code; + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/ansi/AnsiBackground.java b/hutool-core/src/main/java/cn/hutool/core/lang/ansi/AnsiBackground.java new file mode 100755 index 000000000..deb7a9acb --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/lang/ansi/AnsiBackground.java @@ -0,0 +1,109 @@ +package cn.hutool.core.lang.ansi; + +/** + * ANSI背景颜色枚举 + * + *

      来自Spring Boot

      + * + * @author Phillip Webb, Geoffrey Chandler + * @since 5.8.0 + */ +public enum AnsiBackground implements AnsiElement { + + /** + * 默认背景色 + */ + DEFAULT("49"), + + /** + * 黑色 + */ + BLACK("40"), + + /** + * 红 + */ + RED("41"), + + /** + * 绿 + */ + GREEN("42"), + + /** + * 黄 + */ + YELLOW("43"), + + /** + * 蓝 + */ + BLUE("44"), + + /** + * 品红 + */ + MAGENTA("45"), + + /** + * 青 + */ + CYAN("46"), + + /** + * 白 + */ + WHITE("47"), + + /** + * 亮黑 + */ + BRIGHT_BLACK("100"), + + /** + * 亮红 + */ + BRIGHT_RED("101"), + + /** + * 亮绿 + */ + BRIGHT_GREEN("102"), + + /** + * 亮黄 + */ + BRIGHT_YELLOW("103"), + + /** + * 亮蓝 + */ + BRIGHT_BLUE("104"), + + /** + * 亮品红 + */ + BRIGHT_MAGENTA("105"), + + /** + * 亮青 + */ + BRIGHT_CYAN("106"), + + /** + * 亮白 + */ + BRIGHT_WHITE("107"); + + private final String code; + + AnsiBackground(String code) { + this.code = code; + } + + @Override + public String toString() { + return this.code; + } + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/ansi/AnsiColor.java b/hutool-core/src/main/java/cn/hutool/core/lang/ansi/AnsiColor.java new file mode 100755 index 000000000..d478ffb39 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/lang/ansi/AnsiColor.java @@ -0,0 +1,109 @@ +package cn.hutool.core.lang.ansi; + +/** + * ANSI标准颜色 + * + *

      来自Spring Boot

      + * + * @author Phillip Webb, Geoffrey Chandler + * @since 5.8.0 + */ +public enum AnsiColor implements AnsiElement { + + /** + * 默认前景色 + */ + DEFAULT("39"), + + /** + * 黑 + */ + BLACK("30"), + + /** + * 红 + */ + RED("31"), + + /** + * 绿 + */ + GREEN("32"), + + /** + * 黄 + */ + YELLOW("33"), + + /** + * 蓝 + */ + BLUE("34"), + + /** + * 品红 + */ + MAGENTA("35"), + + /** + * 青 + */ + CYAN("36"), + + /** + * 白 + */ + WHITE("37"), + + /** + * 亮黑 + */ + BRIGHT_BLACK("90"), + + /** + * 亮红 + */ + BRIGHT_RED("91"), + + /** + * 亮绿 + */ + BRIGHT_GREEN("92"), + + /** + * 亮黄 + */ + BRIGHT_YELLOW("93"), + + /** + * 亮蓝 + */ + BRIGHT_BLUE("94"), + + /** + * 亮品红 + */ + BRIGHT_MAGENTA("95"), + + /** + * 亮青 + */ + BRIGHT_CYAN("96"), + + /** + * 亮白 + */ + BRIGHT_WHITE("97"); + + private final String code; + + AnsiColor(String code) { + this.code = code; + } + + @Override + public String toString() { + return this.code; + } + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/ansi/AnsiElement.java b/hutool-core/src/main/java/cn/hutool/core/lang/ansi/AnsiElement.java new file mode 100755 index 000000000..d09024efd --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/lang/ansi/AnsiElement.java @@ -0,0 +1,18 @@ +package cn.hutool.core.lang.ansi; + +/** + * ANSI可转义节点接口,实现为ANSI颜色等 + * + *

      来自Spring Boot

      + * + * @author Phillip Webb + */ +public interface AnsiElement { + + /** + * @return ANSI转义编码 + */ + @Override + String toString(); + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/ansi/AnsiEncoder.java b/hutool-core/src/main/java/cn/hutool/core/lang/ansi/AnsiEncoder.java new file mode 100755 index 000000000..5a9dc5f5c --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/lang/ansi/AnsiEncoder.java @@ -0,0 +1,65 @@ +package cn.hutool.core.lang.ansi; + +/** + * 生成ANSI格式的编码输出 + * + * @author Phillip Webb + * @since 1.0.0 + */ +public abstract class AnsiEncoder { + + private static final String ENCODE_JOIN = ";"; + private static final String ENCODE_START = "\033["; + private static final String ENCODE_END = "m"; + private static final String RESET = "0;" + AnsiColor.DEFAULT; + + /** + * 创建ANSI字符串,参数中的{@link AnsiElement}会被转换为编码形式。 + * + * @param elements 节点数组 + * @return ANSI字符串 + */ + public static String encode(Object... elements) { + final StringBuilder sb = new StringBuilder(); + buildEnabled(sb, elements); + return sb.toString(); + } + + /** + * 追加需要需转义的节点 + * + * @param sb {@link StringBuilder} + * @param elements 节点列表 + */ + private static void buildEnabled(StringBuilder sb, Object[] elements) { + boolean writingAnsi = false; + boolean containsEncoding = false; + for (Object element : elements) { + if (null == element) { + continue; + } + if (element instanceof AnsiElement) { + containsEncoding = true; + if (writingAnsi) { + sb.append(ENCODE_JOIN); + } else { + sb.append(ENCODE_START); + writingAnsi = true; + } + } else { + if (writingAnsi) { + sb.append(ENCODE_END); + writingAnsi = false; + } + } + sb.append(element); + } + + // 恢复默认 + if (containsEncoding) { + sb.append(writingAnsi ? ENCODE_JOIN : ENCODE_START); + sb.append(RESET); + sb.append(ENCODE_END); + } + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/ansi/AnsiStyle.java b/hutool-core/src/main/java/cn/hutool/core/lang/ansi/AnsiStyle.java new file mode 100755 index 000000000..b95487d15 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/lang/ansi/AnsiStyle.java @@ -0,0 +1,49 @@ +package cn.hutool.core.lang.ansi; + +/** + * ANSI文本样式风格枚举 + * + *

      来自Spring Boot

      + * + * @author Phillip Webb + * @since 5.8.0 + */ +public enum AnsiStyle implements AnsiElement { + + /** + * 重置/正常 + */ + NORMAL("0"), + + /** + * 粗体或增加强度 + */ + BOLD("1"), + + /** + * 弱化(降低强度) + */ + FAINT("2"), + + /** + * 斜体 + */ + ITALIC("3"), + + /** + * 下划线 + */ + UNDERLINE("4"); + + private final String code; + + AnsiStyle(String code) { + this.code = code; + } + + @Override + public String toString() { + return this.code; + } + +} diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/ansi/package-info.java b/hutool-core/src/main/java/cn/hutool/core/lang/ansi/package-info.java new file mode 100755 index 000000000..e1a6f4d1e --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/lang/ansi/package-info.java @@ -0,0 +1,6 @@ +/** + * 命令行终端中ANSI 转义序列相关封装,如ANSI颜色等 + * + * @author spring, looly + */ +package cn.hutool.core.lang.ansi; diff --git a/hutool-core/src/main/java/cn/hutool/core/util/NumberUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/NumberUtil.java index 48e179d2a..3deccb27a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/NumberUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/NumberUtil.java @@ -2541,7 +2541,7 @@ public class NumberUtil { /** * int值转byte数组,使用大端字节序(高位字节在前,低位字节在后)
      - * 见:http://www.ruanyifeng.com/blog/2016/11/byte-order.html + * 见:http://www.ruanyifeng.com/blog/2016/11/byte-order.html * * @param value 值 * @return byte数组 @@ -2560,7 +2560,7 @@ public class NumberUtil { /** * byte数组转int,使用大端字节序(高位字节在前,低位字节在后)
      - * 见:http://www.ruanyifeng.com/blog/2016/11/byte-order.html + * 见:http://www.ruanyifeng.com/blog/2016/11/byte-order.html * * @param bytes byte数组 * @return int diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/ConsoleTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/ConsoleTest.java index ee667974b..01a92e70f 100644 --- a/hutool-core/src/test/java/cn/hutool/core/lang/ConsoleTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/lang/ConsoleTest.java @@ -75,4 +75,9 @@ public class ConsoleTest { } } + @Test + public void printColorTest(){ + System.out.print("\33[30;1m A \u001b[31;2m B \u001b[32;1m C \u001b[33;1m D \u001b[0m"); + } + } diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/ansi/AnsiEncoderTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/ansi/AnsiEncoderTest.java new file mode 100755 index 000000000..710c5c0f0 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/lang/ansi/AnsiEncoderTest.java @@ -0,0 +1,13 @@ +package cn.hutool.core.lang.ansi; + +import org.junit.Assert; +import org.junit.Test; + +public class AnsiEncoderTest { + + @Test + public void encodeTest(){ + final String encode = AnsiEncoder.encode(AnsiColor.GREEN, "Hutool test"); + Assert.assertEquals("\u001B[32mHutool test\u001B[0;39m", encode); + } +} From 2d9bc10cecb8656a5adb2ad475689f365ebe9aed Mon Sep 17 00:00:00 2001 From: Fxiao <40653617+Fxiao1@users.noreply.github.com> Date: Wed, 27 Apr 2022 14:55:29 +0800 Subject: [PATCH 45/52] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E4=BA=86=20Ftp.existFi?= =?UTF-8?q?le(String=20path)=20=E7=9A=84=E6=96=B9=E6=B3=95=E6=8F=8F?= =?UTF-8?q?=E8=BF=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 该方法应该是“判断ftp服务器目录内是否还有子元素(目录或文件)”而不是“判断ftp服务器文件是否存在”,旧的方法描述具有误导性,让人误以为这个方法是判断指定的path是否存在 --- hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java b/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java index 607dd2c14..05182e6b5 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java @@ -418,7 +418,7 @@ public class Ftp extends AbstractFtp { } /** - * 判断ftp服务器文件是否存在 + * 判断ftp服务器目录内是否还有子元素(目录或文件) * * @param path 文件路径 * @return 是否存在 From 3c6067d5bf6ceea41b50ada17f562c01ec18c726 Mon Sep 17 00:00:00 2001 From: Looly Date: Wed, 27 Apr 2022 14:57:46 +0800 Subject: [PATCH 46/52] add ConsoleColorLog --- CHANGELOG.md | 2 + .../log/dialect/console/ConsoleColorLog.java | 188 ++++-------------- .../console/ConsoleColorLogFactory.java | 7 +- .../log/dialect/console/ConsoleLog.java | 2 +- .../hutool/log/dialect/tinylog/TinyLog2.java | 16 +- .../cn/hutool/log/test/StaticLogTest.java | 6 +- 6 files changed, 56 insertions(+), 165 deletions(-) mode change 100644 => 100755 hutool-log/src/main/java/cn/hutool/log/dialect/console/ConsoleColorLog.java mode change 100644 => 100755 hutool-log/src/main/java/cn/hutool/log/dialect/console/ConsoleColorLogFactory.java mode change 100644 => 100755 hutool-log/src/test/java/cn/hutool/log/test/StaticLogTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index bb996b424..5d77228fa 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,8 @@ * 【core 】 ReflectUtil.newInstanceIfPossible添加枚举、数组等类型的默认实现 * 【core 】 CombinationAnnotationElement增加过滤(pr#605@Gitee) * 【all 】 精简CHANGELOG +* 【core 】 新增AnsiEncoder +* 【log 】 新增彩色日式输出风格ConsoleColorLog(pr#607@Gitee) ### 🐞Bug修复 * 【core 】 修复StrUtil.firstNonX非static问题(issue#2257@Github) diff --git a/hutool-log/src/main/java/cn/hutool/log/dialect/console/ConsoleColorLog.java b/hutool-log/src/main/java/cn/hutool/log/dialect/console/ConsoleColorLog.java old mode 100644 new mode 100755 index f99404376..3f5495ea8 --- a/hutool-log/src/main/java/cn/hutool/log/dialect/console/ConsoleColorLog.java +++ b/hutool-log/src/main/java/cn/hutool/log/dialect/console/ConsoleColorLog.java @@ -1,76 +1,61 @@ package cn.hutool.log.dialect.console; -import cn.hutool.core.date.DatePattern; -import cn.hutool.core.lang.Assert; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.lang.ansi.AnsiColor; +import cn.hutool.core.lang.ansi.AnsiEncoder; import cn.hutool.core.util.ClassUtil; import cn.hutool.core.util.StrUtil; -import cn.hutool.log.AbstractLog; import cn.hutool.log.level.Level; -import java.util.Date; +import java.util.function.Function; /** - * @author hongda.li 2022-04-27 09:55 + * 利用System.out.println()打印彩色日志 + * + * @author hongda.li, looly + * @since 5.8.0 */ -public class ConsoleColorLog extends AbstractLog { - - //-----------------------------------可供定制的不同级别的日志颜色代码--------------------------- - - private static int ERROR = 31; - private static int INFO = 32; - private static int DEBUG = 32; - private static int WARN = 33; - private static int TRACE = 35; - - public static void setErrorColor(int errorColor) { - ConsoleColorLog.ERROR = errorColor; - } - - public static void setInfoColor(int infoColor) { - ConsoleColorLog.INFO = infoColor; - } - - public static void setDebugColor(int debugColor) { - ConsoleColorLog.DEBUG = debugColor; - } - - public static void setWarnColor(int warnColor) { - ConsoleColorLog.WARN = warnColor; - } - - public static void setTraceColor(int traceColor) { - ConsoleColorLog.TRACE = traceColor; - } +public class ConsoleColorLog extends ConsoleLog { /** * 控制台打印类名的颜色代码 */ - private static final int CLASSNAME = 36; + private static final AnsiColor COLOR_CLASSNAME = AnsiColor.CYAN; /** * 控制台打印时间的颜色代码 */ - private static final int TIME = 37; + private static final AnsiColor COLOR_TIME = AnsiColor.WHITE; /** * 控制台打印正常信息的颜色代码 */ - private static final int NONE = 38; + private static final AnsiColor COLOR_NONE = AnsiColor.DEFAULT; + + private static Function colorFactory = (level -> { + switch (level) { + case DEBUG: + case INFO: + return AnsiColor.GREEN; + case WARN: + return AnsiColor.YELLOW; + case ERROR: + return AnsiColor.RED; + case TRACE: + return AnsiColor.MAGENTA; + default: + return COLOR_NONE; + } + }); /** - * 系统换行符 + * 设置颜色工厂,根据日志级别,定义不同的颜色 + * + * @param colorFactory 颜色工厂函数 */ - private static final String LINE_SEPARATOR = System.lineSeparator(); - - /** - * 日志名称 - */ - private final String name; - - /** - * 日志级别 - */ - private static Level currentLevel = Level.DEBUG; + public static void setColorFactory(Function colorFactory) { + ConsoleColorLog.colorFactory = colorFactory; + } /** * 构造 @@ -78,7 +63,7 @@ public class ConsoleColorLog extends AbstractLog { * @param name 类名 */ public ConsoleColorLog(String name) { - this.name = name; + super(name); } /** @@ -87,111 +72,16 @@ public class ConsoleColorLog extends AbstractLog { * @param clazz 类 */ public ConsoleColorLog(Class clazz) { - this.name = (null == clazz) ? StrUtil.NULL : clazz.getName(); - } - - @Override - public String getName() { - return this.name; - } - - /** - * 设置自定义的日志显示级别 - * - * @param customLevel 自定义级别 - */ - public static void setLevel(Level customLevel) { - Assert.notNull(customLevel); - currentLevel = customLevel; + super(clazz); } @Override public synchronized void log(String fqcn, Level level, Throwable t, String format, Object... arguments) { - if (!isEnabled(level)) { + if (false == isEnabled(level)) { return; } - System.out.format("\33[%d;2m%s", TIME, DatePattern.NORM_DATETIME_MS_FORMAT.format(new Date())); - switch (level) { - case DEBUG: - System.out.format("\33[%d;2m%-8s", DEBUG, " DEBUG"); - System.out.format("\33[%d;2m%s", DEBUG, " --- "); - break; - case WARN: - System.out.format("\33[%d;2m%-8s", WARN, " WARN"); - System.out.format("\33[%d;2m%s", WARN, " --- "); - break; - case ERROR: - System.out.format("\33[%d;2m%-8s", ERROR, " ERROR"); - System.out.format("\33[%d;2m%s", ERROR, " --- "); - break; - case INFO: - System.out.format("\33[%d;2m%-8s", INFO, " INFO"); - System.out.format("\33[%d;2m%s", INFO, " --- "); - break; - case TRACE: - System.out.format("\33[%d;2m%-8s", TRACE, " TRACE"); - System.out.format("\33[%d;2m%s", TRACE, " --- "); - break; - default: - } - System.out.format("\33[%d;2m%-35s", CLASSNAME, "[" + ClassUtil.getShortClassName(name) + "]"); - System.out.format("\33[%d;2m%s", NONE, " : " + StrUtil.format(format, arguments)); - System.out.format("%s", LINE_SEPARATOR); - System.out.format("\33[%d;2m%s", NONE, ""); - } - @Override - public boolean isDebugEnabled() { - return isEnabled(Level.DEBUG); - } - - @Override - public void debug(String fqcn, Throwable t, String format, Object... arguments) { - log(fqcn, Level.DEBUG, t, format, arguments); - } - - @Override - public boolean isErrorEnabled() { - return isEnabled(Level.ERROR); - } - - @Override - public void error(String fqcn, Throwable t, String format, Object... arguments) { - log(fqcn, Level.ERROR, t, format, arguments); - } - - @Override - public boolean isInfoEnabled() { - return isEnabled(Level.INFO); - } - - @Override - public void info(String fqcn, Throwable t, String format, Object... arguments) { - log(fqcn, Level.INFO, t, format, arguments); - } - - @Override - public boolean isTraceEnabled() { - return isEnabled(Level.TRACE); - } - - @Override - public void trace(String fqcn, Throwable t, String format, Object... arguments) { - log(fqcn, Level.TRACE, t, format, arguments); - } - - @Override - public boolean isWarnEnabled() { - return isEnabled(Level.WARN); - } - - @Override - public void warn(String fqcn, Throwable t, String format, Object... arguments) { - log(fqcn, Level.WARN, t, format, arguments); - } - - @Override - public boolean isEnabled(Level level) { - return currentLevel.compareTo(level) <= 0; + final String template = AnsiEncoder.encode(COLOR_TIME, "[%s]", colorFactory.apply(level), "[%-5s]%s", COLOR_CLASSNAME, "%-30s: ", COLOR_NONE, "%s%n"); + System.out.format(template, DateUtil.now(), level.name(), " - ", ClassUtil.getShortClassName(getName()), StrUtil.format(format, arguments)); } } diff --git a/hutool-log/src/main/java/cn/hutool/log/dialect/console/ConsoleColorLogFactory.java b/hutool-log/src/main/java/cn/hutool/log/dialect/console/ConsoleColorLogFactory.java old mode 100644 new mode 100755 index 8bcb415ef..bfe514bac --- a/hutool-log/src/main/java/cn/hutool/log/dialect/console/ConsoleColorLogFactory.java +++ b/hutool-log/src/main/java/cn/hutool/log/dialect/console/ConsoleColorLogFactory.java @@ -4,11 +4,14 @@ import cn.hutool.log.Log; import cn.hutool.log.LogFactory; /** - * @author hongda.li 2022-04-27 09:55 + * 利用System.out.println()打印彩色日志 + * + * @author hongda.li + * @since 5.8.0 */ public class ConsoleColorLogFactory extends LogFactory { - public ConsoleColorLogFactory(){ + public ConsoleColorLogFactory() { super("Hutool Console Color Logging"); } diff --git a/hutool-log/src/main/java/cn/hutool/log/dialect/console/ConsoleLog.java b/hutool-log/src/main/java/cn/hutool/log/dialect/console/ConsoleLog.java index 4abbe2cfe..71b030971 100644 --- a/hutool-log/src/main/java/cn/hutool/log/dialect/console/ConsoleLog.java +++ b/hutool-log/src/main/java/cn/hutool/log/dialect/console/ConsoleLog.java @@ -141,4 +141,4 @@ public class ConsoleLog extends AbstractLog { public boolean isEnabled(Level level) { return currentLevel.compareTo(level) <= 0; } -} \ No newline at end of file +} diff --git a/hutool-log/src/main/java/cn/hutool/log/dialect/tinylog/TinyLog2.java b/hutool-log/src/main/java/cn/hutool/log/dialect/tinylog/TinyLog2.java index 49de876d4..cebbfb021 100644 --- a/hutool-log/src/main/java/cn/hutool/log/dialect/tinylog/TinyLog2.java +++ b/hutool-log/src/main/java/cn/hutool/log/dialect/tinylog/TinyLog2.java @@ -12,7 +12,7 @@ import org.tinylog.provider.ProviderRegistry; /** * tinylog log.
      - * + * * @author Looly * */ @@ -25,12 +25,12 @@ public class TinyLog2 extends AbstractLog { private final int level; private final String name; private static final LoggingProvider provider = ProviderRegistry.getLoggingProvider(); + // ------------------------------------------------------------------------- Constructor + private static final MessageFormatter formatter = new AdvancedMessageFormatter( Configuration.getLocale(), Configuration.isEscapingEnabled() ); - - // ------------------------------------------------------------------------- Constructor public TinyLog2(Class clazz) { this(null == clazz ? StrUtil.NULL : clazz.getName()); } @@ -104,12 +104,12 @@ public class TinyLog2 extends AbstractLog { public void log(String fqcn, cn.hutool.log.level.Level level, Throwable t, String format, Object... arguments) { logIfEnabled(fqcn, toTinyLevel(level), t, format, arguments); } - + @Override public boolean isEnabled(cn.hutool.log.level.Level level) { return this.level <= toTinyLevel(level).ordinal(); } - + /** * 在对应日志级别打开情况下打印日志 * @param fqcn 完全限定类名(Fully Qualified Class Name),用于定位日志位置 @@ -125,10 +125,10 @@ public class TinyLog2 extends AbstractLog { } provider.log(DEPTH, null, level, t, formatter, StrUtil.toString(format), arguments); } - + /** * 将Hutool的Level等级转换为Tinylog的Level等级 - * + * * @param level Hutool的Level等级 * @return Tinylog的Level * @since 4.0.3 @@ -162,7 +162,7 @@ public class TinyLog2 extends AbstractLog { /** * 如果最后一个参数为异常参数,则获取之,否则返回null - * + * * @param arguments 参数 * @return 最后一个异常参数 * @since 4.0.3 diff --git a/hutool-log/src/test/java/cn/hutool/log/test/StaticLogTest.java b/hutool-log/src/test/java/cn/hutool/log/test/StaticLogTest.java old mode 100644 new mode 100755 index 67bcdbbe6..aeac9854f --- a/hutool-log/src/test/java/cn/hutool/log/test/StaticLogTest.java +++ b/hutool-log/src/test/java/cn/hutool/log/test/StaticLogTest.java @@ -1,13 +1,11 @@ package cn.hutool.log.test; import cn.hutool.log.LogFactory; -import cn.hutool.log.dialect.console.ConsoleColorLog; +import cn.hutool.log.StaticLog; import cn.hutool.log.dialect.console.ConsoleColorLogFactory; import cn.hutool.log.dialect.console.ConsoleLogFactory; import org.junit.Test; -import cn.hutool.log.StaticLog; - public class StaticLogTest { @Test public void test() { @@ -24,7 +22,5 @@ public class StaticLogTest { StaticLog.error("This is static {} log", "error"); StaticLog.warn("This is static {} log", "warn"); StaticLog.trace("This is static {} log", "trace"); - ConsoleColorLog.setWarnColor(31); - StaticLog.warn("This is static {} log", "warn"); } } From f969a019869af7f5632330ca3cdccf7e0f446d4a Mon Sep 17 00:00:00 2001 From: Looly Date: Wed, 27 Apr 2022 14:58:53 +0800 Subject: [PATCH 47/52] fix comment --- hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java b/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java old mode 100644 new mode 100755 From 537d72323c7e75216e715380448336d0a6b7a50f Mon Sep 17 00:00:00 2001 From: Looly Date: Wed, 27 Apr 2022 15:05:51 +0800 Subject: [PATCH 48/52] fix comment --- .../main/java/cn/hutool/core/lang/caller/CallerUtil.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/caller/CallerUtil.java b/hutool-core/src/main/java/cn/hutool/core/lang/caller/CallerUtil.java index cca4fbe4d..f51eebaef 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/caller/CallerUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/caller/CallerUtil.java @@ -35,13 +35,13 @@ public class CallerUtil { * 调用者层级关系: * *
      -	 * 0 {@link CallerUtil}
      -	 * 1 调用{@link CallerUtil}中方法的类
      +	 * 0 CallerUtil
      +	 * 1 调用CallerUtil中方法的类
       	 * 2 调用者的调用者
       	 * ...
       	 * 
      * - * @param depth 层级。0表示{@link CallerUtil}本身,1表示调用{@link CallerUtil}的类,2表示调用者的调用者,依次类推 + * @param depth 层级。0表示CallerUtil本身,1表示调用CallerUtil的类,2表示调用者的调用者,依次类推 * @return 第几级调用者 */ public static Class getCaller(int depth) { From f4764f776ef891ca3f6eae434b0d4e5aa96e9766 Mon Sep 17 00:00:00 2001 From: Looly Date: Wed, 27 Apr 2022 15:47:34 +0800 Subject: [PATCH 49/52] update dependency --- hutool-db/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml index 7180a8009..36571a8e3 100644 --- a/hutool-db/pom.xml +++ b/hutool-db/pom.xml @@ -23,7 +23,7 @@ 10.0.20 1.2.9 2.4.13 - 4.5.1 + 4.6.0 3.36.0.3 2.5.2 From 7d1a482fd15db4e788e5189609a4cd88f143277b Mon Sep 17 00:00:00 2001 From: Looly Date: Wed, 27 Apr 2022 15:58:20 +0800 Subject: [PATCH 50/52] update dependency --- hutool-db/pom.xml | 2 +- hutool-extra/pom.xml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml index 36571a8e3..2cfc0667c 100644 --- a/hutool-db/pom.xml +++ b/hutool-db/pom.xml @@ -142,7 +142,7 @@ mysql mysql-connector-java - 8.0.28 + 8.0.29 test diff --git a/hutool-extra/pom.xml b/hutool-extra/pom.xml index 976fa3759..640233309 100644 --- a/hutool-extra/pom.xml +++ b/hutool-extra/pom.xml @@ -26,12 +26,12 @@ 3.0.15.RELEASE 1.6.2 0.1.55 - 0.32.0 + 0.33.0 3.4.1 3.8.0 5.1.1 4.0.1 - 2.6.6 + 2.6.7 3.3.0 From 416c73e05b6f6fdd759b96cdb6847ca8a82bae2d Mon Sep 17 00:00:00 2001 From: Looly Date: Wed, 27 Apr 2022 15:59:07 +0800 Subject: [PATCH 51/52] remove --- .../src/main/java/cn/hutool/core/text/StrTemplate.java | 8 -------- 1 file changed, 8 deletions(-) delete mode 100755 hutool-core/src/main/java/cn/hutool/core/text/StrTemplate.java diff --git a/hutool-core/src/main/java/cn/hutool/core/text/StrTemplate.java b/hutool-core/src/main/java/cn/hutool/core/text/StrTemplate.java deleted file mode 100755 index 3fd386cf4..000000000 --- a/hutool-core/src/main/java/cn/hutool/core/text/StrTemplate.java +++ /dev/null @@ -1,8 +0,0 @@ -package cn.hutool.core.text; - -public class StrTemplate { - public static final String DEFAULT_MACRO_PREFIX = "$"; - public static final String DEFAULT_MACRO_START = "${"; - public static final String DEFAULT_MACRO_END = "}"; - -} From 3896b9d6753fd4038d9221264d386f39c1333544 Mon Sep 17 00:00:00 2001 From: Looly Date: Wed, 27 Apr 2022 16:02:57 +0800 Subject: [PATCH 52/52] fix code --- README-EN.md | 0 README.md | 0 docs/js/version.js | 0 hutool-all/pom.xml | 0 hutool-aop/pom.xml | 0 hutool-bloomFilter/pom.xml | 0 hutool-bom/pom.xml | 0 hutool-cache/pom.xml | 0 hutool-cache/src/main/java/cn/hutool/cache/impl/WeakCache.java | 0 hutool-cache/src/test/java/cn/hutool/cache/WeakCacheTest.java | 0 hutool-captcha/pom.xml | 0 hutool-core/pom.xml | 0 .../cn/hutool/core/annotation/CombinationAnnotationElement.java | 0 hutool-core/src/main/java/cn/hutool/core/bean/BeanDescCache.java | 0 hutool-core/src/main/java/cn/hutool/core/bean/BeanInfoCache.java | 0 hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java | 0 .../src/main/java/cn/hutool/core/collection/UniqueKeySet.java | 0 .../src/main/java/cn/hutool/core/convert/impl/EnumConverter.java | 0 hutool-core/src/main/java/cn/hutool/core/date/DateUtil.java | 0 hutool-core/src/main/java/cn/hutool/core/date/Week.java | 0 hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java | 0 hutool-core/src/main/java/cn/hutool/core/lang/PatternPool.java | 0 hutool-core/src/main/java/cn/hutool/core/lang/SimpleCache.java | 0 hutool-core/src/main/java/cn/hutool/core/lang/Singleton.java | 0 .../src/main/java/cn/hutool/core/lang/caller/CallerUtil.java | 0 .../src/main/java/cn/hutool/core/lang/func/LambdaUtil.java | 0 .../src/main/java/cn/hutool/core/lang/intern/WeakInterner.java | 0 .../java/cn/hutool/core/lang/reflect/ActualTypeMapperPool.java | 0 hutool-core/src/main/java/cn/hutool/core/map/CustomKeyMap.java | 0 hutool-core/src/main/java/cn/hutool/core/map/FuncKeyMap.java | 0 hutool-core/src/main/java/cn/hutool/core/map/MapWrapper.java | 0 hutool-core/src/main/java/cn/hutool/core/map/TableMap.java | 0 .../src/main/java/cn/hutool/core/map/multi/RowKeyTable.java | 0 hutool-core/src/main/java/cn/hutool/core/map/multi/Table.java | 0 hutool-core/src/main/java/cn/hutool/core/net/RFC3986.java | 0 .../src/main/java/cn/hutool/core/text/CharSequenceUtil.java | 0 hutool-core/src/main/java/cn/hutool/core/text/StrMatcher.java | 0 hutool-core/src/main/java/cn/hutool/core/thread/lock/NoLock.java | 0 hutool-core/src/main/java/cn/hutool/core/util/ArrayUtil.java | 0 .../src/main/java/cn/hutool/core/util/ClassLoaderUtil.java | 0 hutool-core/src/main/java/cn/hutool/core/util/ClassUtil.java | 0 hutool-core/src/main/java/cn/hutool/core/util/IdcardUtil.java | 0 hutool-core/src/main/java/cn/hutool/core/util/NumberUtil.java | 0 hutool-core/src/main/java/cn/hutool/core/util/ReflectUtil.java | 0 .../test/java/cn/hutool/core/annotation/AnnotationForTest.java | 0 hutool-core/src/test/java/cn/hutool/core/bean/BeanUtilTest.java | 0 .../src/test/java/cn/hutool/core/collection/CollUtilTest.java | 0 hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java | 0 hutool-core/src/test/java/cn/hutool/core/io/FileTypeUtilTest.java | 0 hutool-core/src/test/java/cn/hutool/core/lang/ConsoleTest.java | 0 .../src/test/java/cn/hutool/core/lang/SimpleCacheTest.java | 0 .../src/test/java/cn/hutool/core/text/CharSequenceUtilTest.java | 0 hutool-core/src/test/java/cn/hutool/core/text/StrMatcherTest.java | 0 .../src/test/java/cn/hutool/core/text/csv/CsvWriterTest.java | 0 hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java | 0 .../src/test/java/cn/hutool/core/util/ReflectUtilTest.java | 0 hutool-cron/pom.xml | 0 hutool-cron/src/main/java/cn/hutool/cron/pattern/CronPattern.java | 0 .../main/java/cn/hutool/cron/pattern/matcher/PatternMatcher.java | 0 hutool-crypto/pom.xml | 0 hutool-db/pom.xml | 0 hutool-dfa/pom.xml | 0 hutool-extra/pom.xml | 0 .../src/main/java/cn/hutool/extra/cglib/BeanCopierCache.java | 0 hutool-extra/src/main/java/cn/hutool/extra/mail/MailAccount.java | 0 .../src/main/java/cn/hutool/extra/servlet/ServletUtil.java | 0 hutool-extra/src/main/java/cn/hutool/extra/ssh/Sftp.java | 0 .../src/test/java/cn/hutool/extra/servlet/ServletUtilTest.java | 0 hutool-http/pom.xml | 0 hutool-http/src/main/java/cn/hutool/http/GlobalInterceptor.java | 0 hutool-http/src/main/java/cn/hutool/http/HttpRequest.java | 0 hutool-json/pom.xml | 0 hutool-json/src/main/java/cn/hutool/json/InternalJSONUtil.java | 0 hutool-json/src/main/java/cn/hutool/json/JSONArray.java | 0 hutool-json/src/main/java/cn/hutool/json/JSONObject.java | 0 hutool-json/src/main/java/cn/hutool/json/JSONTokener.java | 0 hutool-json/src/main/java/cn/hutool/json/JSONUtil.java | 0 hutool-json/src/test/java/cn/hutool/json/JSONArrayTest.java | 0 hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java | 0 hutool-jwt/pom.xml | 0 hutool-log/pom.xml | 0 .../src/main/java/cn/hutool/log/dialect/console/ConsoleLog.java | 0 .../src/main/java/cn/hutool/log/dialect/tinylog/TinyLog2.java | 0 hutool-poi/pom.xml | 0 hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelWriter.java | 0 hutool-poi/src/test/java/cn/hutool/poi/excel/Issue2221Test.java | 0 hutool-script/pom.xml | 0 hutool-script/src/main/java/cn/hutool/script/ScriptUtil.java | 0 hutool-setting/pom.xml | 0 hutool-socket/pom.xml | 0 hutool-system/pom.xml | 0 pom.xml | 0 92 files changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 README-EN.md mode change 100644 => 100755 README.md mode change 100644 => 100755 docs/js/version.js mode change 100644 => 100755 hutool-all/pom.xml mode change 100644 => 100755 hutool-aop/pom.xml mode change 100644 => 100755 hutool-bloomFilter/pom.xml mode change 100644 => 100755 hutool-bom/pom.xml mode change 100644 => 100755 hutool-cache/pom.xml mode change 100644 => 100755 hutool-cache/src/main/java/cn/hutool/cache/impl/WeakCache.java mode change 100644 => 100755 hutool-cache/src/test/java/cn/hutool/cache/WeakCacheTest.java mode change 100644 => 100755 hutool-captcha/pom.xml mode change 100644 => 100755 hutool-core/pom.xml mode change 100644 => 100755 hutool-core/src/main/java/cn/hutool/core/annotation/CombinationAnnotationElement.java mode change 100644 => 100755 hutool-core/src/main/java/cn/hutool/core/bean/BeanDescCache.java mode change 100644 => 100755 hutool-core/src/main/java/cn/hutool/core/bean/BeanInfoCache.java mode change 100644 => 100755 hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java mode change 100644 => 100755 hutool-core/src/main/java/cn/hutool/core/collection/UniqueKeySet.java mode change 100644 => 100755 hutool-core/src/main/java/cn/hutool/core/convert/impl/EnumConverter.java mode change 100644 => 100755 hutool-core/src/main/java/cn/hutool/core/date/DateUtil.java mode change 100644 => 100755 hutool-core/src/main/java/cn/hutool/core/date/Week.java mode change 100644 => 100755 hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java mode change 100644 => 100755 hutool-core/src/main/java/cn/hutool/core/lang/PatternPool.java mode change 100644 => 100755 hutool-core/src/main/java/cn/hutool/core/lang/SimpleCache.java mode change 100644 => 100755 hutool-core/src/main/java/cn/hutool/core/lang/Singleton.java mode change 100644 => 100755 hutool-core/src/main/java/cn/hutool/core/lang/caller/CallerUtil.java mode change 100644 => 100755 hutool-core/src/main/java/cn/hutool/core/lang/func/LambdaUtil.java mode change 100644 => 100755 hutool-core/src/main/java/cn/hutool/core/lang/intern/WeakInterner.java mode change 100644 => 100755 hutool-core/src/main/java/cn/hutool/core/lang/reflect/ActualTypeMapperPool.java mode change 100644 => 100755 hutool-core/src/main/java/cn/hutool/core/map/CustomKeyMap.java mode change 100644 => 100755 hutool-core/src/main/java/cn/hutool/core/map/FuncKeyMap.java mode change 100644 => 100755 hutool-core/src/main/java/cn/hutool/core/map/MapWrapper.java mode change 100644 => 100755 hutool-core/src/main/java/cn/hutool/core/map/TableMap.java mode change 100644 => 100755 hutool-core/src/main/java/cn/hutool/core/map/multi/RowKeyTable.java mode change 100644 => 100755 hutool-core/src/main/java/cn/hutool/core/map/multi/Table.java mode change 100644 => 100755 hutool-core/src/main/java/cn/hutool/core/net/RFC3986.java mode change 100644 => 100755 hutool-core/src/main/java/cn/hutool/core/text/CharSequenceUtil.java mode change 100644 => 100755 hutool-core/src/main/java/cn/hutool/core/text/StrMatcher.java mode change 100644 => 100755 hutool-core/src/main/java/cn/hutool/core/thread/lock/NoLock.java mode change 100644 => 100755 hutool-core/src/main/java/cn/hutool/core/util/ArrayUtil.java mode change 100644 => 100755 hutool-core/src/main/java/cn/hutool/core/util/ClassLoaderUtil.java mode change 100644 => 100755 hutool-core/src/main/java/cn/hutool/core/util/ClassUtil.java mode change 100644 => 100755 hutool-core/src/main/java/cn/hutool/core/util/IdcardUtil.java mode change 100644 => 100755 hutool-core/src/main/java/cn/hutool/core/util/NumberUtil.java mode change 100644 => 100755 hutool-core/src/main/java/cn/hutool/core/util/ReflectUtil.java mode change 100644 => 100755 hutool-core/src/test/java/cn/hutool/core/annotation/AnnotationForTest.java mode change 100644 => 100755 hutool-core/src/test/java/cn/hutool/core/bean/BeanUtilTest.java mode change 100644 => 100755 hutool-core/src/test/java/cn/hutool/core/collection/CollUtilTest.java mode change 100644 => 100755 hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java mode change 100644 => 100755 hutool-core/src/test/java/cn/hutool/core/io/FileTypeUtilTest.java mode change 100644 => 100755 hutool-core/src/test/java/cn/hutool/core/lang/ConsoleTest.java mode change 100644 => 100755 hutool-core/src/test/java/cn/hutool/core/lang/SimpleCacheTest.java mode change 100644 => 100755 hutool-core/src/test/java/cn/hutool/core/text/CharSequenceUtilTest.java mode change 100644 => 100755 hutool-core/src/test/java/cn/hutool/core/text/StrMatcherTest.java mode change 100644 => 100755 hutool-core/src/test/java/cn/hutool/core/text/csv/CsvWriterTest.java mode change 100644 => 100755 hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java mode change 100644 => 100755 hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java mode change 100644 => 100755 hutool-cron/pom.xml mode change 100644 => 100755 hutool-cron/src/main/java/cn/hutool/cron/pattern/CronPattern.java mode change 100644 => 100755 hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/PatternMatcher.java mode change 100644 => 100755 hutool-crypto/pom.xml mode change 100644 => 100755 hutool-db/pom.xml mode change 100644 => 100755 hutool-dfa/pom.xml mode change 100644 => 100755 hutool-extra/pom.xml mode change 100644 => 100755 hutool-extra/src/main/java/cn/hutool/extra/cglib/BeanCopierCache.java mode change 100644 => 100755 hutool-extra/src/main/java/cn/hutool/extra/mail/MailAccount.java mode change 100644 => 100755 hutool-extra/src/main/java/cn/hutool/extra/servlet/ServletUtil.java mode change 100644 => 100755 hutool-extra/src/main/java/cn/hutool/extra/ssh/Sftp.java mode change 100644 => 100755 hutool-extra/src/test/java/cn/hutool/extra/servlet/ServletUtilTest.java mode change 100644 => 100755 hutool-http/pom.xml mode change 100644 => 100755 hutool-http/src/main/java/cn/hutool/http/GlobalInterceptor.java mode change 100644 => 100755 hutool-http/src/main/java/cn/hutool/http/HttpRequest.java mode change 100644 => 100755 hutool-json/pom.xml mode change 100644 => 100755 hutool-json/src/main/java/cn/hutool/json/InternalJSONUtil.java mode change 100644 => 100755 hutool-json/src/main/java/cn/hutool/json/JSONArray.java mode change 100644 => 100755 hutool-json/src/main/java/cn/hutool/json/JSONObject.java mode change 100644 => 100755 hutool-json/src/main/java/cn/hutool/json/JSONTokener.java mode change 100644 => 100755 hutool-json/src/main/java/cn/hutool/json/JSONUtil.java mode change 100644 => 100755 hutool-json/src/test/java/cn/hutool/json/JSONArrayTest.java mode change 100644 => 100755 hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java mode change 100644 => 100755 hutool-jwt/pom.xml mode change 100644 => 100755 hutool-log/pom.xml mode change 100644 => 100755 hutool-log/src/main/java/cn/hutool/log/dialect/console/ConsoleLog.java mode change 100644 => 100755 hutool-log/src/main/java/cn/hutool/log/dialect/tinylog/TinyLog2.java mode change 100644 => 100755 hutool-poi/pom.xml mode change 100644 => 100755 hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelWriter.java mode change 100644 => 100755 hutool-poi/src/test/java/cn/hutool/poi/excel/Issue2221Test.java mode change 100644 => 100755 hutool-script/pom.xml mode change 100644 => 100755 hutool-script/src/main/java/cn/hutool/script/ScriptUtil.java mode change 100644 => 100755 hutool-setting/pom.xml mode change 100644 => 100755 hutool-socket/pom.xml mode change 100644 => 100755 hutool-system/pom.xml mode change 100644 => 100755 pom.xml diff --git a/README-EN.md b/README-EN.md old mode 100644 new mode 100755 diff --git a/README.md b/README.md old mode 100644 new mode 100755 diff --git a/docs/js/version.js b/docs/js/version.js old mode 100644 new mode 100755 diff --git a/hutool-all/pom.xml b/hutool-all/pom.xml old mode 100644 new mode 100755 diff --git a/hutool-aop/pom.xml b/hutool-aop/pom.xml old mode 100644 new mode 100755 diff --git a/hutool-bloomFilter/pom.xml b/hutool-bloomFilter/pom.xml old mode 100644 new mode 100755 diff --git a/hutool-bom/pom.xml b/hutool-bom/pom.xml old mode 100644 new mode 100755 diff --git a/hutool-cache/pom.xml b/hutool-cache/pom.xml old mode 100644 new mode 100755 diff --git a/hutool-cache/src/main/java/cn/hutool/cache/impl/WeakCache.java b/hutool-cache/src/main/java/cn/hutool/cache/impl/WeakCache.java old mode 100644 new mode 100755 diff --git a/hutool-cache/src/test/java/cn/hutool/cache/WeakCacheTest.java b/hutool-cache/src/test/java/cn/hutool/cache/WeakCacheTest.java old mode 100644 new mode 100755 diff --git a/hutool-captcha/pom.xml b/hutool-captcha/pom.xml old mode 100644 new mode 100755 diff --git a/hutool-core/pom.xml b/hutool-core/pom.xml old mode 100644 new mode 100755 diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/CombinationAnnotationElement.java b/hutool-core/src/main/java/cn/hutool/core/annotation/CombinationAnnotationElement.java old mode 100644 new mode 100755 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 old mode 100644 new mode 100755 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 old mode 100644 new mode 100755 diff --git a/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java b/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java old mode 100644 new mode 100755 diff --git a/hutool-core/src/main/java/cn/hutool/core/collection/UniqueKeySet.java b/hutool-core/src/main/java/cn/hutool/core/collection/UniqueKeySet.java old mode 100644 new mode 100755 diff --git a/hutool-core/src/main/java/cn/hutool/core/convert/impl/EnumConverter.java b/hutool-core/src/main/java/cn/hutool/core/convert/impl/EnumConverter.java old mode 100644 new mode 100755 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 old mode 100644 new mode 100755 diff --git a/hutool-core/src/main/java/cn/hutool/core/date/Week.java b/hutool-core/src/main/java/cn/hutool/core/date/Week.java old mode 100644 new mode 100755 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 old mode 100644 new mode 100755 diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/PatternPool.java b/hutool-core/src/main/java/cn/hutool/core/lang/PatternPool.java old mode 100644 new mode 100755 diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/SimpleCache.java b/hutool-core/src/main/java/cn/hutool/core/lang/SimpleCache.java old mode 100644 new mode 100755 diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/Singleton.java b/hutool-core/src/main/java/cn/hutool/core/lang/Singleton.java old mode 100644 new mode 100755 diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/caller/CallerUtil.java b/hutool-core/src/main/java/cn/hutool/core/lang/caller/CallerUtil.java old mode 100644 new mode 100755 diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/func/LambdaUtil.java b/hutool-core/src/main/java/cn/hutool/core/lang/func/LambdaUtil.java old mode 100644 new mode 100755 diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/intern/WeakInterner.java b/hutool-core/src/main/java/cn/hutool/core/lang/intern/WeakInterner.java old mode 100644 new mode 100755 diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/reflect/ActualTypeMapperPool.java b/hutool-core/src/main/java/cn/hutool/core/lang/reflect/ActualTypeMapperPool.java old mode 100644 new mode 100755 diff --git a/hutool-core/src/main/java/cn/hutool/core/map/CustomKeyMap.java b/hutool-core/src/main/java/cn/hutool/core/map/CustomKeyMap.java old mode 100644 new mode 100755 diff --git a/hutool-core/src/main/java/cn/hutool/core/map/FuncKeyMap.java b/hutool-core/src/main/java/cn/hutool/core/map/FuncKeyMap.java old mode 100644 new mode 100755 diff --git a/hutool-core/src/main/java/cn/hutool/core/map/MapWrapper.java b/hutool-core/src/main/java/cn/hutool/core/map/MapWrapper.java old mode 100644 new mode 100755 diff --git a/hutool-core/src/main/java/cn/hutool/core/map/TableMap.java b/hutool-core/src/main/java/cn/hutool/core/map/TableMap.java old mode 100644 new mode 100755 diff --git a/hutool-core/src/main/java/cn/hutool/core/map/multi/RowKeyTable.java b/hutool-core/src/main/java/cn/hutool/core/map/multi/RowKeyTable.java old mode 100644 new mode 100755 diff --git a/hutool-core/src/main/java/cn/hutool/core/map/multi/Table.java b/hutool-core/src/main/java/cn/hutool/core/map/multi/Table.java old mode 100644 new mode 100755 diff --git a/hutool-core/src/main/java/cn/hutool/core/net/RFC3986.java b/hutool-core/src/main/java/cn/hutool/core/net/RFC3986.java old mode 100644 new mode 100755 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 old mode 100644 new mode 100755 diff --git a/hutool-core/src/main/java/cn/hutool/core/text/StrMatcher.java b/hutool-core/src/main/java/cn/hutool/core/text/StrMatcher.java old mode 100644 new mode 100755 diff --git a/hutool-core/src/main/java/cn/hutool/core/thread/lock/NoLock.java b/hutool-core/src/main/java/cn/hutool/core/thread/lock/NoLock.java old mode 100644 new mode 100755 diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ArrayUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ArrayUtil.java old mode 100644 new mode 100755 diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ClassLoaderUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ClassLoaderUtil.java old mode 100644 new mode 100755 diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ClassUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ClassUtil.java old mode 100644 new mode 100755 diff --git a/hutool-core/src/main/java/cn/hutool/core/util/IdcardUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/IdcardUtil.java old mode 100644 new mode 100755 diff --git a/hutool-core/src/main/java/cn/hutool/core/util/NumberUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/NumberUtil.java old mode 100644 new mode 100755 diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ReflectUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ReflectUtil.java old mode 100644 new mode 100755 diff --git a/hutool-core/src/test/java/cn/hutool/core/annotation/AnnotationForTest.java b/hutool-core/src/test/java/cn/hutool/core/annotation/AnnotationForTest.java old mode 100644 new mode 100755 diff --git a/hutool-core/src/test/java/cn/hutool/core/bean/BeanUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/bean/BeanUtilTest.java old mode 100644 new mode 100755 diff --git a/hutool-core/src/test/java/cn/hutool/core/collection/CollUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/collection/CollUtilTest.java old mode 100644 new mode 100755 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 old mode 100644 new mode 100755 diff --git a/hutool-core/src/test/java/cn/hutool/core/io/FileTypeUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/io/FileTypeUtilTest.java old mode 100644 new mode 100755 diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/ConsoleTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/ConsoleTest.java old mode 100644 new mode 100755 diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/SimpleCacheTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/SimpleCacheTest.java old mode 100644 new mode 100755 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 old mode 100644 new mode 100755 diff --git a/hutool-core/src/test/java/cn/hutool/core/text/StrMatcherTest.java b/hutool-core/src/test/java/cn/hutool/core/text/StrMatcherTest.java old mode 100644 new mode 100755 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 old mode 100644 new mode 100755 diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ArrayUtilTest.java old mode 100644 new mode 100755 diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ReflectUtilTest.java old mode 100644 new mode 100755 diff --git a/hutool-cron/pom.xml b/hutool-cron/pom.xml old mode 100644 new mode 100755 diff --git a/hutool-cron/src/main/java/cn/hutool/cron/pattern/CronPattern.java b/hutool-cron/src/main/java/cn/hutool/cron/pattern/CronPattern.java old mode 100644 new mode 100755 diff --git a/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/PatternMatcher.java b/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/PatternMatcher.java old mode 100644 new mode 100755 diff --git a/hutool-crypto/pom.xml b/hutool-crypto/pom.xml old mode 100644 new mode 100755 diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml old mode 100644 new mode 100755 diff --git a/hutool-dfa/pom.xml b/hutool-dfa/pom.xml old mode 100644 new mode 100755 diff --git a/hutool-extra/pom.xml b/hutool-extra/pom.xml old mode 100644 new mode 100755 diff --git a/hutool-extra/src/main/java/cn/hutool/extra/cglib/BeanCopierCache.java b/hutool-extra/src/main/java/cn/hutool/extra/cglib/BeanCopierCache.java old mode 100644 new mode 100755 diff --git a/hutool-extra/src/main/java/cn/hutool/extra/mail/MailAccount.java b/hutool-extra/src/main/java/cn/hutool/extra/mail/MailAccount.java old mode 100644 new mode 100755 diff --git a/hutool-extra/src/main/java/cn/hutool/extra/servlet/ServletUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/servlet/ServletUtil.java old mode 100644 new mode 100755 diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ssh/Sftp.java b/hutool-extra/src/main/java/cn/hutool/extra/ssh/Sftp.java old mode 100644 new mode 100755 diff --git a/hutool-extra/src/test/java/cn/hutool/extra/servlet/ServletUtilTest.java b/hutool-extra/src/test/java/cn/hutool/extra/servlet/ServletUtilTest.java old mode 100644 new mode 100755 diff --git a/hutool-http/pom.xml b/hutool-http/pom.xml old mode 100644 new mode 100755 diff --git a/hutool-http/src/main/java/cn/hutool/http/GlobalInterceptor.java b/hutool-http/src/main/java/cn/hutool/http/GlobalInterceptor.java old mode 100644 new mode 100755 diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java old mode 100644 new mode 100755 diff --git a/hutool-json/pom.xml b/hutool-json/pom.xml old mode 100644 new mode 100755 diff --git a/hutool-json/src/main/java/cn/hutool/json/InternalJSONUtil.java b/hutool-json/src/main/java/cn/hutool/json/InternalJSONUtil.java old mode 100644 new mode 100755 diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONArray.java b/hutool-json/src/main/java/cn/hutool/json/JSONArray.java old mode 100644 new mode 100755 diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONObject.java b/hutool-json/src/main/java/cn/hutool/json/JSONObject.java old mode 100644 new mode 100755 diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONTokener.java b/hutool-json/src/main/java/cn/hutool/json/JSONTokener.java old mode 100644 new mode 100755 diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java b/hutool-json/src/main/java/cn/hutool/json/JSONUtil.java old mode 100644 new mode 100755 diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONArrayTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONArrayTest.java old mode 100644 new mode 100755 diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java old mode 100644 new mode 100755 diff --git a/hutool-jwt/pom.xml b/hutool-jwt/pom.xml old mode 100644 new mode 100755 diff --git a/hutool-log/pom.xml b/hutool-log/pom.xml old mode 100644 new mode 100755 diff --git a/hutool-log/src/main/java/cn/hutool/log/dialect/console/ConsoleLog.java b/hutool-log/src/main/java/cn/hutool/log/dialect/console/ConsoleLog.java old mode 100644 new mode 100755 diff --git a/hutool-log/src/main/java/cn/hutool/log/dialect/tinylog/TinyLog2.java b/hutool-log/src/main/java/cn/hutool/log/dialect/tinylog/TinyLog2.java old mode 100644 new mode 100755 diff --git a/hutool-poi/pom.xml b/hutool-poi/pom.xml old mode 100644 new mode 100755 diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelWriter.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelWriter.java old mode 100644 new mode 100755 diff --git a/hutool-poi/src/test/java/cn/hutool/poi/excel/Issue2221Test.java b/hutool-poi/src/test/java/cn/hutool/poi/excel/Issue2221Test.java old mode 100644 new mode 100755 diff --git a/hutool-script/pom.xml b/hutool-script/pom.xml old mode 100644 new mode 100755 diff --git a/hutool-script/src/main/java/cn/hutool/script/ScriptUtil.java b/hutool-script/src/main/java/cn/hutool/script/ScriptUtil.java old mode 100644 new mode 100755 diff --git a/hutool-setting/pom.xml b/hutool-setting/pom.xml old mode 100644 new mode 100755 diff --git a/hutool-socket/pom.xml b/hutool-socket/pom.xml old mode 100644 new mode 100755 diff --git a/hutool-system/pom.xml b/hutool-system/pom.xml old mode 100644 new mode 100755 diff --git a/pom.xml b/pom.xml old mode 100644 new mode 100755