From 3cf00894fa8700e2809d160be9547c8e8406f53e Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 1 Mar 2022 13:23:05 +0800 Subject: [PATCH 01/57] prepare 5.7.23 --- CHANGELOG.md | 6 ++++++ 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, 36 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2136dba910..06e6039f8b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ # 🚀Changelog +------------------------------------------------------------------------------------------------------------- +# 5.7.23 (2022-03-01) + +### 🐣新特性 +### 🐞Bug修复 + ------------------------------------------------------------------------------------------------------------- # 5.7.22 (2022-03-01) diff --git a/README-EN.md b/README-EN.md index 75fcfed120..05475e644e 100644 --- a/README-EN.md +++ b/README-EN.md @@ -142,18 +142,18 @@ We provide the T-Shirt and Sweater with Hutool Logo, please visit the shop: cn.hutool hutool-all - 5.7.22 + 5.7.23 ``` ### 🍐Gradle ``` -implementation 'cn.hutool:hutool-all:5.7.22' +implementation 'cn.hutool:hutool-all:5.7.23' ``` ## 📥Download -- [Maven Repo](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.7.22/) +- [Maven Repo](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.7.23/) > 🔔️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 4516e5e0de..f8834f52a6 100644 --- a/README.md +++ b/README.md @@ -142,20 +142,20 @@ Hutool的存在就是为了减少代码搜索成本,避免网络上参差不 cn.hutool hutool-all - 5.7.22 + 5.7.23 ``` ### 🍐Gradle ``` -implementation 'cn.hutool:hutool-all:5.7.22' +implementation 'cn.hutool:hutool-all:5.7.23' ``` ### 📥下载jar 点击以下链接,下载`hutool-all-X.X.X.jar`即可: -- [Maven中央库](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.7.22/) +- [Maven中央库](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.7.23/) > 🔔️注意 > Hutool 5.x支持JDK8+,对Android平台没有测试,不能保证所有工具类或工具方法可用。 diff --git a/bin/version.txt b/bin/version.txt index e2ff09a282..a5c3e01d2d 100755 --- a/bin/version.txt +++ b/bin/version.txt @@ -1 +1 @@ -5.7.22 +5.7.23 diff --git a/docs/js/version.js b/docs/js/version.js index e637f5f205..37b3ee602c 100644 --- a/docs/js/version.js +++ b/docs/js/version.js @@ -1 +1 @@ -var version = '5.7.22' \ No newline at end of file +var version = '5.7.23' \ No newline at end of file diff --git a/hutool-all/pom.xml b/hutool-all/pom.xml index a516862eee..d99b05ea1d 100644 --- a/hutool-all/pom.xml +++ b/hutool-all/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.22 + 5.7.23-SNAPSHOT hutool-all diff --git a/hutool-aop/pom.xml b/hutool-aop/pom.xml index f9dc30ef04..9a53b9232e 100644 --- a/hutool-aop/pom.xml +++ b/hutool-aop/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.22 + 5.7.23-SNAPSHOT hutool-aop diff --git a/hutool-bloomFilter/pom.xml b/hutool-bloomFilter/pom.xml index 4abc55f45a..6820aa9968 100644 --- a/hutool-bloomFilter/pom.xml +++ b/hutool-bloomFilter/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.22 + 5.7.23-SNAPSHOT hutool-bloomFilter diff --git a/hutool-bom/pom.xml b/hutool-bom/pom.xml index 3490b7ceed..35c3ab9aa9 100644 --- a/hutool-bom/pom.xml +++ b/hutool-bom/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.22 + 5.7.23-SNAPSHOT hutool-bom diff --git a/hutool-cache/pom.xml b/hutool-cache/pom.xml index 298a97a6c2..430d0b0925 100644 --- a/hutool-cache/pom.xml +++ b/hutool-cache/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.22 + 5.7.23-SNAPSHOT hutool-cache diff --git a/hutool-captcha/pom.xml b/hutool-captcha/pom.xml index 17564bff02..b2dbcf27fb 100644 --- a/hutool-captcha/pom.xml +++ b/hutool-captcha/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.22 + 5.7.23-SNAPSHOT hutool-captcha diff --git a/hutool-core/pom.xml b/hutool-core/pom.xml index 9c225e9c78..6ca1dd7894 100644 --- a/hutool-core/pom.xml +++ b/hutool-core/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.22 + 5.7.23-SNAPSHOT hutool-core diff --git a/hutool-cron/pom.xml b/hutool-cron/pom.xml index 94c4538cc9..190721d023 100644 --- a/hutool-cron/pom.xml +++ b/hutool-cron/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.22 + 5.7.23-SNAPSHOT hutool-cron diff --git a/hutool-crypto/pom.xml b/hutool-crypto/pom.xml index cff3649a96..8dbd928e28 100644 --- a/hutool-crypto/pom.xml +++ b/hutool-crypto/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.22 + 5.7.23-SNAPSHOT hutool-crypto diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml index c8c5ccff73..2d586bf0bc 100644 --- a/hutool-db/pom.xml +++ b/hutool-db/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.22 + 5.7.23-SNAPSHOT hutool-db diff --git a/hutool-dfa/pom.xml b/hutool-dfa/pom.xml index 65737363bb..c16db7ae21 100644 --- a/hutool-dfa/pom.xml +++ b/hutool-dfa/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.22 + 5.7.23-SNAPSHOT hutool-dfa diff --git a/hutool-extra/pom.xml b/hutool-extra/pom.xml index adff1d26e6..b86f833cfe 100644 --- a/hutool-extra/pom.xml +++ b/hutool-extra/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.22 + 5.7.23-SNAPSHOT hutool-extra diff --git a/hutool-http/pom.xml b/hutool-http/pom.xml index 5f686c62e4..f3853f53c3 100644 --- a/hutool-http/pom.xml +++ b/hutool-http/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.22 + 5.7.23-SNAPSHOT hutool-http diff --git a/hutool-json/pom.xml b/hutool-json/pom.xml index 7c769ba2ba..12f8201be0 100644 --- a/hutool-json/pom.xml +++ b/hutool-json/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.22 + 5.7.23-SNAPSHOT hutool-json diff --git a/hutool-jwt/pom.xml b/hutool-jwt/pom.xml index 32213fa80b..4836888d00 100644 --- a/hutool-jwt/pom.xml +++ b/hutool-jwt/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.22 + 5.7.23-SNAPSHOT hutool-jwt diff --git a/hutool-log/pom.xml b/hutool-log/pom.xml index 68e3b1ccc3..3169cdc7ec 100644 --- a/hutool-log/pom.xml +++ b/hutool-log/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.22 + 5.7.23-SNAPSHOT hutool-log diff --git a/hutool-poi/pom.xml b/hutool-poi/pom.xml index e9f5652911..c737053c72 100644 --- a/hutool-poi/pom.xml +++ b/hutool-poi/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.22 + 5.7.23-SNAPSHOT hutool-poi diff --git a/hutool-script/pom.xml b/hutool-script/pom.xml index fd29dd9a61..35b6f525be 100644 --- a/hutool-script/pom.xml +++ b/hutool-script/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.22 + 5.7.23-SNAPSHOT hutool-script diff --git a/hutool-setting/pom.xml b/hutool-setting/pom.xml index d7f1a860e9..8cf940d71c 100644 --- a/hutool-setting/pom.xml +++ b/hutool-setting/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.22 + 5.7.23-SNAPSHOT hutool-setting diff --git a/hutool-socket/pom.xml b/hutool-socket/pom.xml index 838df72ec3..7659cce359 100644 --- a/hutool-socket/pom.xml +++ b/hutool-socket/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.22 + 5.7.23-SNAPSHOT hutool-socket diff --git a/hutool-system/pom.xml b/hutool-system/pom.xml index 76f6ae1d0e..0f28acc207 100644 --- a/hutool-system/pom.xml +++ b/hutool-system/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.22 + 5.7.23-SNAPSHOT hutool-system diff --git a/pom.xml b/pom.xml index 771da27957..4800b8a1f6 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.7.22 + 5.7.23-SNAPSHOT hutool Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。 https://github.com/dromara/hutool -- Gitee From 0e5329850a7eda0e558ed20854e58a90d70db7e8 Mon Sep 17 00:00:00 2001 From: VampireAchao Date: Tue, 1 Mar 2022 22:59:28 +0800 Subject: [PATCH 02/57] =?UTF-8?q?=E8=8E=B7=E5=8F=96=E5=88=AB=E5=90=8D?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=90=8E=E7=9A=84=E6=B3=A8=E8=A7=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/annotation/AnnotationUtil.java | 32 +++++++++++++++---- .../core/annotation/AnnotationForTest.java | 6 ++-- .../core/annotation/AnnotationUtilTest.java | 10 ++++++ 3 files changed, 39 insertions(+), 9 deletions(-) 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 1049a517ae..071cf1b13d 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 @@ -2,15 +2,11 @@ package cn.hutool.core.annotation; import cn.hutool.core.exceptions.UtilException; 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 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; @@ -215,4 +211,26 @@ public class AnnotationUtil { final Map memberValues = (Map) ReflectUtil.getFieldValue(Proxy.getInvocationHandler(annotation), "memberValues"); memberValues.put(annotationField, value); } + + /** + * 获取别名支持后的注解 + * + * @param annotationEle 被注解的类 + * @param annotationType 注解类型Class + * @param 注解类型 + * @return 别名支持后的注解 + */ + @SuppressWarnings("unchecked") + public static T getAnnotationAlias(AnnotatedElement annotationEle, Class annotationType) { + T annotation = getAnnotation(annotationEle, annotationType); + Object o = Proxy.newProxyInstance(annotationType.getClassLoader(), new Class[]{annotationType}, (proxy, method, args) -> { + Alias alias = method.getAnnotation(Alias.class); + if (ObjectUtil.isNotNull(alias) && StrUtil.isNotBlank(alias.value())) { + Method aliasMethod = annotationType.getMethod(alias.value()); + return ReflectUtil.invoke(annotation, aliasMethod); + } + return method.invoke(args); + }); + return (T) o; + } } 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 6b41de3adb..83210ae901 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 @@ -10,7 +10,6 @@ import java.lang.annotation.Target; * 注解类相关说明见:https://www.cnblogs.com/xdp-gacl/p/3622275.html * * @author looly - * */ // Retention注解决定MyAnnotation注解的生命周期 @Retention(RetentionPolicy.RUNTIME) @@ -23,5 +22,8 @@ public @interface AnnotationForTest { * * @return 属性值 */ - String value(); + String value() default ""; + + @Alias("value") + String retry() default ""; } 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 b565cbcb6c..3340dcf37b 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 @@ -12,6 +12,16 @@ public class AnnotationUtilTest { } + @Test + public void getAnnotationSyncAlias() { + // 直接获取 + Assert.assertEquals("", ClassWithAnnotation.class.getAnnotation(AnnotationForTest.class).retry()); + + // 加别名适配 + AnnotationForTest annotation = AnnotationUtil.getAnnotationAlias(ClassWithAnnotation.class, AnnotationForTest.class); + Assert.assertEquals("测试", annotation.retry()); + } + @AnnotationForTest("测试") static class ClassWithAnnotation{ public void test(){ -- Gitee From 0c3ef87647ea97cc3301649b13fd550272c14452 Mon Sep 17 00:00:00 2001 From: jiazhengquan <2466896229@qq.com> Date: Thu, 3 Mar 2022 10:27:08 +0800 Subject: [PATCH 03/57] =?UTF-8?q?=E9=92=88=E5=AF=B9issue->ObjectUtil.hasNu?= =?UTF-8?q?ll(Object...=20objs)=E5=9C=A8=E5=A4=84=E7=90=86null=E6=97=B6?= =?UTF-8?q?=E6=9C=89bug=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hutool-core/src/main/java/cn/hutool/core/util/ObjectUtil.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ObjectUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ObjectUtil.java index 8fe082b437..5c9fc00dcb 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ObjectUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ObjectUtil.java @@ -625,7 +625,7 @@ public class ObjectUtil { * @see ArrayUtil#hasNull(Object[]) */ public static boolean hasNull(Object... objs) { - return ArrayUtil.hasNull(objs); + return objs == null || ArrayUtil.hasNull(objs); } /** -- Gitee From 3c19a939f097081bfd90fce6a5034b9e7f11063a Mon Sep 17 00:00:00 2001 From: jiazhengquan <2466896229@qq.com> Date: Thu, 3 Mar 2022 14:26:41 +0800 Subject: [PATCH 04/57] =?UTF-8?q?=E9=92=88=E5=AF=B9issue->ObjectUtil.hasNu?= =?UTF-8?q?ll(Object...=20objs)=E5=9C=A8=E5=A4=84=E7=90=86null=E6=97=B6?= =?UTF-8?q?=E6=9C=89bug=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hutool-core/src/main/java/cn/hutool/core/util/ArrayUtil.java | 2 +- hutool-core/src/main/java/cn/hutool/core/util/ObjectUtil.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ArrayUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ArrayUtil.java index b0c9cbacb0..8c948e8a92 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 @@ -122,7 +122,7 @@ public class ArrayUtil extends PrimitiveArrayUtil { } } } - return false; + return array == null; } /** diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ObjectUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ObjectUtil.java index 5c9fc00dcb..8fe082b437 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ObjectUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ObjectUtil.java @@ -625,7 +625,7 @@ public class ObjectUtil { * @see ArrayUtil#hasNull(Object[]) */ public static boolean hasNull(Object... objs) { - return objs == null || ArrayUtil.hasNull(objs); + return ArrayUtil.hasNull(objs); } /** -- Gitee From 8b49984594b11b4bf320376909e5a744c4bfab3d Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 3 Mar 2022 17:15:35 +0800 Subject: [PATCH 05/57] change map type --- CHANGELOG.md | 3 ++- hutool-http/src/main/java/cn/hutool/http/HttpRequest.java | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 06e6039f8b..df16f1b201 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,10 @@ # 🚀Changelog ------------------------------------------------------------------------------------------------------------- -# 5.7.23 (2022-03-01) +# 5.7.23 (2022-03-03) ### 🐣新特性 +* 【http 】 HttpRequest.form采用TableMap方式(issue#I4W427@gitee) ### 🐞Bug修复 ------------------------------------------------------------------------------------------------------------- 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 864363e19d..927c8bd4d9 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java @@ -9,6 +9,7 @@ import cn.hutool.core.io.resource.MultiFileResource; import cn.hutool.core.io.resource.Resource; import cn.hutool.core.lang.Assert; import cn.hutool.core.map.MapUtil; +import cn.hutool.core.map.TableMap; import cn.hutool.core.net.SSLUtil; import cn.hutool.core.net.url.UrlBuilder; import cn.hutool.core.util.ArrayUtil; @@ -32,7 +33,6 @@ import java.net.Proxy; import java.net.URLStreamHandler; import java.nio.charset.Charset; import java.util.Collection; -import java.util.LinkedHashMap; import java.util.Map; import java.util.function.Consumer; @@ -1314,7 +1314,7 @@ public class HttpRequest extends HttpBase { return this; } if (null == this.form) { - this.form = new LinkedHashMap<>(); + this.form = new TableMap<>(16); } this.form.put(name, value); return this; -- Gitee From 4c1a729e978755b7b472b1676650dc75f3a9614d Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 3 Mar 2022 17:24:00 +0800 Subject: [PATCH 06/57] fix bug --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index df16f1b201..940921f732 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ ### 🐣新特性 * 【http 】 HttpRequest.form采用TableMap方式(issue#I4W427@gitee) ### 🐞Bug修复 +* 【core 】 修复ObjectUtil.hasNull传入null返回true的问题(pr#555@gitee) ------------------------------------------------------------------------------------------------------------- # 5.7.22 (2022-03-01) -- Gitee From d1dea5c88ef600fd8d7d3a0a412b98ad1637df58 Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 3 Mar 2022 21:52:30 +0800 Subject: [PATCH 07/57] fix bug --- .../cn/hutool/core/collection/CollUtil.java | 14 ++--------- .../main/java/cn/hutool/core/map/MapUtil.java | 25 +++++-------------- .../java/cn/hutool/core/map/MapWrapper.java | 13 +++++++++- .../java/cn/hutool/core/util/ReflectUtil.java | 2 +- .../java/cn/hutool/core/map/MapUtilTest.java | 18 +++++++++++++ 5 files changed, 39 insertions(+), 33 deletions(-) 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 dd22d5275b..21120cd252 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 @@ -1178,20 +1178,10 @@ public class CollUtil { return collection; } - Collection collection2 = ObjectUtil.clone(collection); - if (null == collection2) { - // 不支持clone - collection2 = create(collection.getClass()); - } - if (isEmpty(collection2)) { + final Collection collection2 = create(collection.getClass()); + if (isEmpty(collection)) { return collection2; } - try { - collection2.clear(); - } catch (UnsupportedOperationException e) { - // 克隆后的对象不支持清空,说明为不可变集合对象,使用默认的ArrayList保存结果 - collection2 = new ArrayList<>(); - } T modified; for (T t : collection) { diff --git a/hutool-core/src/main/java/cn/hutool/core/map/MapUtil.java b/hutool-core/src/main/java/cn/hutool/core/map/MapUtil.java index b0fb792505..194777224c 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/MapUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/MapUtil.java @@ -7,7 +7,6 @@ import cn.hutool.core.lang.Filter; import cn.hutool.core.lang.Pair; import cn.hutool.core.lang.TypeReference; import cn.hutool.core.util.ArrayUtil; -import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ReflectUtil; import cn.hutool.core.util.StrUtil; @@ -623,25 +622,19 @@ public class MapUtil { * @param editor 编辑器接口 * @return 编辑后的Map */ + @SuppressWarnings("unchecked") public static Map edit(Map map, Editor> editor) { if (null == map || null == editor) { return map; } - Map map2 = ObjectUtil.clone(map); + Map map2 = ReflectUtil.newInstanceIfPossible(map.getClass()); if(null == map2){ - // 不支持clone map2 = new HashMap<>(map.size(), 1f); } - if (isEmpty(map2)) { + if (isEmpty(map)) { return map2; } - try { - map2.clear(); - } catch (UnsupportedOperationException e) { - // 克隆后的对象不支持清空,说明为不可变集合对象,使用默认的ArrayList保存结果 - map2 = new HashMap<>(map.size(), 1f); - } Entry modified; for (Entry entry : map.entrySet()) { @@ -690,20 +683,14 @@ public class MapUtil { if(null == map || null == keys){ return map; } - Map map2 = ObjectUtil.clone(map); + + Map map2 = ReflectUtil.newInstanceIfPossible(map.getClass()); if(null == map2){ - // 不支持clone map2 = new HashMap<>(map.size(), 1f); } - if (isEmpty(map2)) { + if (isEmpty(map)) { return map2; } - try { - map2.clear(); - } catch (UnsupportedOperationException e) { - // 克隆后的对象不支持清空,说明为不可变集合对象,使用默认的ArrayList保存结果 - map2 = new HashMap<>(); - } for (K key : keys) { if (map.containsKey(key)) { 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 000b15ea6f..bfd81dd86d 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 @@ -1,5 +1,7 @@ package cn.hutool.core.map; +import cn.hutool.core.util.ObjectUtil; + import java.io.Serializable; import java.util.Collection; import java.util.Iterator; @@ -30,7 +32,7 @@ public class MapWrapper implements Map, Iterable>, S */ protected static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 - private final Map raw; + private Map raw; /** * 构造 @@ -199,5 +201,14 @@ public class MapWrapper implements Map, Iterable>, S public V merge(K key, V value, BiFunction remappingFunction) { return raw.merge(key, value, remappingFunction); } + + @Override + public MapWrapper clone() throws CloneNotSupportedException { + @SuppressWarnings("unchecked") + final MapWrapper clone = (MapWrapper) super.clone(); + clone.raw = ObjectUtil.clone(raw); + return clone; + } + //---------------------------------------------------------------------------- Override default methods end } 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 6f31764337..43440bb321 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 @@ -848,7 +848,7 @@ public class ReflectUtil { * * @param 对象类型 * @param beanClass 被构造的类 - * @return 构造后的对象 + * @return 构造后的对象,构造失败返回{@code null} */ @SuppressWarnings("unchecked") public static T newInstanceIfPossible(Class beanClass) { diff --git a/hutool-core/src/test/java/cn/hutool/core/map/MapUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/map/MapUtilTest.java index 19fdc83166..ed47980fed 100644 --- a/hutool-core/src/test/java/cn/hutool/core/map/MapUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/map/MapUtilTest.java @@ -26,6 +26,24 @@ public class MapUtilTest { Assert.assertEquals("4", map2.get("d")); } + @Test + public void filterMapWrapperTest() { + Map map = MapUtil.newHashMap(); + map.put("a", "1"); + map.put("b", "2"); + map.put("c", "3"); + map.put("d", "4"); + + final Map camelCaseMap = MapUtil.toCamelCaseMap(map); + + Map map2 = MapUtil.filter(camelCaseMap, t -> Convert.toInt(t.getValue()) % 2 == 0); + + Assert.assertEquals(2, map2.size()); + + Assert.assertEquals("2", map2.get("b")); + Assert.assertEquals("4", map2.get("d")); + } + @Test public void filterContainsTest() { Map map = MapUtil.newHashMap(); -- Gitee From ccc9f2568ffdff028e09107e0bbe879b89817b30 Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 3 Mar 2022 23:54:47 +0800 Subject: [PATCH 08/57] add AnnotationProxy --- CHANGELOG.md | 1 + .../core/annotation/AnnotationProxy.java | 88 +++++++++++++++++++ .../core/annotation/AnnotationUtil.java | 29 +++--- .../java/cn/hutool/core/util/ReflectUtil.java | 3 +- 4 files changed, 104 insertions(+), 17 deletions(-) create mode 100644 hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationProxy.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 940921f732..2cf4af00a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ ### 🐣新特性 * 【http 】 HttpRequest.form采用TableMap方式(issue#I4W427@gitee) +* 【core 】 AnnotationUtil增加getAnnotationAlias方法(pr#554@gitee) ### 🐞Bug修复 * 【core 】 修复ObjectUtil.hasNull传入null返回true的问题(pr#555@gitee) diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationProxy.java b/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationProxy.java new file mode 100644 index 0000000000..a61ff66681 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationProxy.java @@ -0,0 +1,88 @@ +package cn.hutool.core.annotation; + +import cn.hutool.core.util.ReflectUtil; +import cn.hutool.core.util.StrUtil; + +import java.io.Serializable; +import java.lang.annotation.Annotation; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; + +/** + * 注解代理
+ * 通过代理指定注解,可以自定义调用注解的方法逻辑,如支持{@link Alias} 注解 + * + * @param 注解类型 + * @since 5.7.23 + */ +public class AnnotationProxy implements Annotation, InvocationHandler, Serializable { + private static final long serialVersionUID = 1L; + + private final T annotation; + private final Class type; + private final Map attributes; + + /** + * 构造 + * + * @param annotation 注解 + */ + public AnnotationProxy(T annotation) { + this.annotation = annotation; + //noinspection unchecked + this.type = (Class) annotation.annotationType(); + this.attributes = initAttributes(); + } + + + @Override + public Class annotationType() { + return null; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + + // 注解别名 + Alias alias = method.getAnnotation(Alias.class); + if(null != alias){ + final String name = alias.value(); + if(StrUtil.isNotBlank(name)){ + if(false == attributes.containsKey(name)){ + throw new IllegalArgumentException(StrUtil.format("No method for alias: [{}]", name)); + } + return attributes.get(name); + } + } + + final Object value = attributes.get(method.getName()); + if (value != null) { + return value; + } + return method.invoke(this, args); + } + + /** + * 初始化注解的属性
+ * 此方法预先调用所有注解的方法,将注解方法值缓存于attributes中 + * + * @return 属性(方法结果)映射 + */ + private Map initAttributes() { + final Method[] methods = ReflectUtil.getMethods(this.type); + final Map attributes = new HashMap<>(methods.length, 1); + + for (Method method : methods) { + // 跳过匿名内部类自动生成的方法 + if (method.isSynthetic()) { + continue; + } + + attributes.put(method.getName(), ReflectUtil.invoke(this.annotation, method)); + } + + return attributes; + } +} 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 071cf1b13d..be44f080a5 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 @@ -2,11 +2,15 @@ package cn.hutool.core.annotation; import cn.hutool.core.exceptions.UtilException; 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 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; @@ -38,7 +42,7 @@ public class AnnotationUtil { /** * 获取指定注解 * - * @param annotationEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission + * @param annotationEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission * @param isToCombination 是否为转换为组合注解 * @return 注解对象 */ @@ -201,9 +205,9 @@ public class AnnotationUtil { /** * 设置新的注解的属性(字段)值 * - * @param annotation 注解对象 + * @param annotation 注解对象 * @param annotationField 注解属性(字段)名称 - * @param value 要更新的属性值 + * @param value 要更新的属性值 * @since 5.5.2 */ @SuppressWarnings({"rawtypes", "unchecked"}) @@ -219,18 +223,11 @@ public class AnnotationUtil { * @param annotationType 注解类型Class * @param 注解类型 * @return 别名支持后的注解 + * @since 5.7.23 */ @SuppressWarnings("unchecked") public static T getAnnotationAlias(AnnotatedElement annotationEle, Class annotationType) { - T annotation = getAnnotation(annotationEle, annotationType); - Object o = Proxy.newProxyInstance(annotationType.getClassLoader(), new Class[]{annotationType}, (proxy, method, args) -> { - Alias alias = method.getAnnotation(Alias.class); - if (ObjectUtil.isNotNull(alias) && StrUtil.isNotBlank(alias.value())) { - Method aliasMethod = annotationType.getMethod(alias.value()); - return ReflectUtil.invoke(annotation, aliasMethod); - } - return method.invoke(args); - }); - return (T) o; + final T annotation = getAnnotation(annotationEle, annotationType); + return (T) Proxy.newProxyInstance(annotationType.getClassLoader(), new Class[]{annotationType}, new AnnotationProxy<>(annotation)); } } 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 43440bb321..de406bfbfd 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 @@ -386,7 +386,8 @@ public class ReflectUtil { } /** - * 获得指定类过滤后的Public方法列表 + * 获得指定类过滤后的Public方法列表
+ * TODO 6.x此方法更改返回Method[] * * @param clazz 查找方法的类 * @param filter 过滤器 -- Gitee From ae8849c6651f0373cf92f13df09a9c485356e73e Mon Sep 17 00:00:00 2001 From: Looly Date: Fri, 4 Mar 2022 00:09:56 +0800 Subject: [PATCH 09/57] change extName --- CHANGELOG.md | 15 ++++++++------- .../java/cn/hutool/core/io/file/FileNameUtil.java | 5 +++++ .../test/java/cn/hutool/core/io/FileUtilTest.java | 4 ++++ 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2cf4af00a0..fcd3d51bd8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,20 +2,21 @@ # 🚀Changelog ------------------------------------------------------------------------------------------------------------- -# 5.7.23 (2022-03-03) +# 5.7.23 (2022-03-04) ### 🐣新特性 -* 【http 】 HttpRequest.form采用TableMap方式(issue#I4W427@gitee) -* 【core 】 AnnotationUtil增加getAnnotationAlias方法(pr#554@gitee) +* 【http 】 HttpRequest.form采用TableMap方式(issue#I4W427@Gitee) +* 【core 】 AnnotationUtil增加getAnnotationAlias方法(pr#554@Gitee) +* 【core 】 FileUtil.extName增加对tar.gz特殊处理(issue#I4W5FS@Gitee) ### 🐞Bug修复 -* 【core 】 修复ObjectUtil.hasNull传入null返回true的问题(pr#555@gitee) +* 【core 】 修复ObjectUtil.hasNull传入null返回true的问题(pr#555@Gitee) ------------------------------------------------------------------------------------------------------------- # 5.7.22 (2022-03-01) ### 🐣新特性 -* 【poi 】 ExcelUtil.readBySax增加对POI-5.2.0的兼容性(issue#I4TJF4@gitee) -* 【extra 】 Ftp增加构造(issue#I4TKXP@gitee) +* 【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) @@ -547,7 +548,7 @@ * 【json 】 增加JSONWriter * 【core 】 IdUtil增加getWorkerId和getDataCenterId(issueI3Y5NI@Gitee) * 【core 】 JWTValidator增加leeway重载 -* 【core 】 增加RegexPool(issue#I3W9ZF@gitee) +* 【core 】 增加RegexPool(issue#I3W9ZF@Gitee) ### 🐞Bug修复 * 【json 】 修复XML转义字符的问题(issue#I3XH09@Gitee) diff --git a/hutool-core/src/main/java/cn/hutool/core/io/file/FileNameUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/file/FileNameUtil.java index 6dca23e7d2..07436e1518 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/file/FileNameUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/file/FileNameUtil.java @@ -222,6 +222,11 @@ public class FileNameUtil { if (index == -1) { return StrUtil.EMPTY; } else { + // issue#I4W5FS@Gitee + if(fileName.endsWith("tar.gz")){ + return "tar.gz"; + } + String ext = fileName.substring(index + 1); // 扩展名中不能包含路径相关的符号 return StrUtil.containsAny(ext, UNIX_SEPARATOR, WINDOWS_SEPARATOR) ? StrUtil.EMPTY : ext; diff --git a/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java index d70490e8ba..4e72c827fb 100644 --- a/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java @@ -392,6 +392,10 @@ public class FileUtilTest { path = FileUtil.isWindows() ? "d:\\aaa\\bbb\\cc\\fff.xlsx" : "~/Desktop/hutool/fff.xlsx"; mainName = FileUtil.extName(path); Assert.assertEquals("xlsx", mainName); + + path = FileUtil.isWindows() ? "d:\\aaa\\bbb\\cc\\fff.tar.gz" : "~/Desktop/hutool/fff.tar.gz"; + mainName = FileUtil.extName(path); + Assert.assertEquals("tar.gz", mainName); } @Test -- Gitee From 919dbca570536a57df14f08a3a05604bcd3b45ec Mon Sep 17 00:00:00 2001 From: jiazhengquan <2466896229@qq.com> Date: Fri, 4 Mar 2022 11:13:39 +0800 Subject: [PATCH 10/57] =?UTF-8?q?=E9=92=88=E5=AF=B9issue#I4W5FS@Gitee?= =?UTF-8?q?=E5=AD=98=E5=9C=A8=E5=85=B6=E4=BB=96=E7=89=B9=E6=AE=8A=E6=83=85?= =?UTF-8?q?=E5=86=B5=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/cn/hutool/core/io/file/FileNameUtil.java | 11 +++++++++-- .../src/test/java/cn/hutool/core/io/FileUtilTest.java | 8 ++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/io/file/FileNameUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/file/FileNameUtil.java index 07436e1518..2d8ef52bba 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/file/FileNameUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/file/FileNameUtil.java @@ -42,6 +42,11 @@ public class FileNameUtil { */ private static final Pattern FILE_NAME_INVALID_PATTERN_WIN = Pattern.compile("[\\\\/:*?\"<>|]"); + /** + * 特殊后缀 + */ + private static final CharSequence[] SPECIAL_SUFFIX = {"tar.bz2", "tar.Z", "tar.gz"}; + // -------------------------------------------------------------------------------------------- name start @@ -223,8 +228,10 @@ public class FileNameUtil { return StrUtil.EMPTY; } else { // issue#I4W5FS@Gitee - if(fileName.endsWith("tar.gz")){ - return "tar.gz"; + int secondToLastIndex = fileName.substring(0, index).lastIndexOf(StrUtil.DOT); + String substr = fileName.substring(secondToLastIndex == -1 ? index : secondToLastIndex + 1); + if (StrUtil.containsAny(substr, SPECIAL_SUFFIX)) { + return substr; } String ext = fileName.substring(index + 1); diff --git a/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java index 4e72c827fb..4486757058 100644 --- a/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java @@ -396,6 +396,14 @@ public class FileUtilTest { path = FileUtil.isWindows() ? "d:\\aaa\\bbb\\cc\\fff.tar.gz" : "~/Desktop/hutool/fff.tar.gz"; mainName = FileUtil.extName(path); Assert.assertEquals("tar.gz", mainName); + + path = FileUtil.isWindows() ? "d:\\aaa\\bbb\\cc\\fff.tar.Z" : "~/Desktop/hutool/fff.tar.Z"; + mainName = FileUtil.extName(path); + Assert.assertEquals("tar.Z", mainName); + + path = FileUtil.isWindows() ? "d:\\aaa\\bbb\\cc\\fff.tar.bz2" : "~/Desktop/hutool/fff.tar.bz2"; + mainName = FileUtil.extName(path); + Assert.assertEquals("tar.bz2", mainName); } @Test -- Gitee From 8f8abcd91846191b2983d057def0147cab5361f3 Mon Sep 17 00:00:00 2001 From: jiazhengquan <2466896229@qq.com> Date: Fri, 4 Mar 2022 11:39:58 +0800 Subject: [PATCH 11/57] =?UTF-8?q?=E9=92=88=E5=AF=B9issue#I4W5FS@Gitee?= =?UTF-8?q?=E5=AD=98=E5=9C=A8=E5=85=B6=E4=BB=96=E7=89=B9=E6=AE=8A=E6=83=85?= =?UTF-8?q?=E5=86=B5=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/cn/hutool/core/io/file/FileNameUtil.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/io/file/FileNameUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/file/FileNameUtil.java index 2d8ef52bba..e7f58b9a0a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/file/FileNameUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/file/FileNameUtil.java @@ -45,7 +45,7 @@ public class FileNameUtil { /** * 特殊后缀 */ - private static final CharSequence[] SPECIAL_SUFFIX = {"tar.bz2", "tar.Z", "tar.gz"}; + private static final CharSequence[] SPECIAL_SUFFIX = {"tar.bz2", "tar.Z", "tar.gz", ".tar.xz"}; // -------------------------------------------------------------------------------------------- name start -- Gitee From 8dae97d691431fe9884d1d6918295c633074b9b8 Mon Sep 17 00:00:00 2001 From: jiazhengquan <2466896229@qq.com> Date: Fri, 4 Mar 2022 14:25:32 +0800 Subject: [PATCH 12/57] =?UTF-8?q?=E9=92=88=E5=AF=B9issue#I4W5FS@Gitee?= =?UTF-8?q?=E5=AD=98=E5=9C=A8=E5=85=B6=E4=BB=96=E7=89=B9=E6=AE=8A=E6=83=85?= =?UTF-8?q?=E5=86=B5=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/cn/hutool/core/io/file/FileNameUtil.java | 2 +- hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/io/file/FileNameUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/file/FileNameUtil.java index e7f58b9a0a..5670f43832 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/file/FileNameUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/file/FileNameUtil.java @@ -45,7 +45,7 @@ public class FileNameUtil { /** * 特殊后缀 */ - private static final CharSequence[] SPECIAL_SUFFIX = {"tar.bz2", "tar.Z", "tar.gz", ".tar.xz"}; + private static final CharSequence[] SPECIAL_SUFFIX = {"tar.bz2", "tar.Z", "tar.gz", "tar.xz"}; // -------------------------------------------------------------------------------------------- name start diff --git a/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java index 4486757058..8a50a76413 100644 --- a/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java @@ -404,6 +404,10 @@ public class FileUtilTest { path = FileUtil.isWindows() ? "d:\\aaa\\bbb\\cc\\fff.tar.bz2" : "~/Desktop/hutool/fff.tar.bz2"; mainName = FileUtil.extName(path); Assert.assertEquals("tar.bz2", mainName); + + path = FileUtil.isWindows() ? "d:\\aaa\\bbb\\cc\\fff.tar.xz" : "~/Desktop/hutool/fff.tar.xz"; + mainName = FileUtil.extName(path); + Assert.assertEquals("tar.xz", mainName); } @Test -- Gitee From 899f1384f6b3bcd62b626e4fdc46289b3d94dc43 Mon Sep 17 00:00:00 2001 From: Looly Date: Sat, 5 Mar 2022 22:47:07 +0800 Subject: [PATCH 13/57] add test --- .../hutool/crypto/test/symmetric/TEATest.java | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 hutool-crypto/src/test/java/cn/hutool/crypto/test/symmetric/TEATest.java diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/symmetric/TEATest.java b/hutool-crypto/src/test/java/cn/hutool/crypto/test/symmetric/TEATest.java new file mode 100644 index 0000000000..8a9d008d6e --- /dev/null +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/test/symmetric/TEATest.java @@ -0,0 +1,39 @@ +package cn.hutool.crypto.test.symmetric; + +import cn.hutool.crypto.symmetric.SymmetricCrypto; +import org.junit.Assert; +import org.junit.Test; + +/** + * TEA(Tiny Encryption Algorithm)和 XTEA算法单元测试 + */ +public class TEATest { + + @Test + public void teaTest(){ + String data = "测试的加密数据 by Hutool"; + + // 密钥必须为128bit + final SymmetricCrypto tea = new SymmetricCrypto("TEA", "MyPassword123456".getBytes()); + final byte[] encrypt = tea.encrypt(data); + + // 解密 + final String decryptStr = tea.decryptStr(encrypt); + + Assert.assertEquals(data, decryptStr); + } + + @Test + public void xteaTest(){ + String data = "测试的加密数据 by Hutool"; + + // 密钥必须为128bit + final SymmetricCrypto tea = new SymmetricCrypto("XTEA", "MyPassword123456".getBytes()); + final byte[] encrypt = tea.encrypt(data); + + // 解密 + final String decryptStr = tea.decryptStr(encrypt); + + Assert.assertEquals(data, decryptStr); + } +} -- Gitee From 115871a355b1c7b212fe52b6c80d127dba14d263 Mon Sep 17 00:00:00 2001 From: VampireAchao Date: Sat, 5 Mar 2022 23:43:27 +0800 Subject: [PATCH 14/57] =?UTF-8?q?=E4=BF=AE=E5=A4=8DAnnotationProxy#annotat?= =?UTF-8?q?ionType=E8=8E=B7=E5=8F=96=E4=B8=8D=E5=88=B0=E6=B3=A8=E8=A7=A3?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/cn/hutool/core/annotation/AnnotationProxy.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationProxy.java b/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationProxy.java index a61ff66681..bcf2ba4b58 100644 --- a/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationProxy.java +++ b/hutool-core/src/main/java/cn/hutool/core/annotation/AnnotationProxy.java @@ -39,7 +39,7 @@ public class AnnotationProxy implements Annotation, Invoca @Override public Class annotationType() { - return null; + return type; } @Override -- Gitee From 580e2e9fbe1d3cc43bdc7f6ebaa0f28c96344a05 Mon Sep 17 00:00:00 2001 From: Looly Date: Sat, 5 Mar 2022 23:49:27 +0800 Subject: [PATCH 15/57] add XXTEA --- CHANGELOG.md | 3 +- .../cn/hutool/crypto/symmetric/XXTEA.java | 157 ++++++++++++++++++ .../hutool/crypto/test/symmetric/TEATest.java | 19 ++- 3 files changed, 176 insertions(+), 3 deletions(-) create mode 100644 hutool-crypto/src/main/java/cn/hutool/crypto/symmetric/XXTEA.java diff --git a/CHANGELOG.md b/CHANGELOG.md index fcd3d51bd8..74943728c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,12 +2,13 @@ # 🚀Changelog ------------------------------------------------------------------------------------------------------------- -# 5.7.23 (2022-03-04) +# 5.7.23 (2022-03-05) ### 🐣新特性 * 【http 】 HttpRequest.form采用TableMap方式(issue#I4W427@Gitee) * 【core 】 AnnotationUtil增加getAnnotationAlias方法(pr#554@Gitee) * 【core 】 FileUtil.extName增加对tar.gz特殊处理(issue#I4W5FS@Gitee) +* 【crypto 】 增加XXTEA实现(issue#I4WH2X@Gitee) ### 🐞Bug修复 * 【core 】 修复ObjectUtil.hasNull传入null返回true的问题(pr#555@Gitee) diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/symmetric/XXTEA.java b/hutool-crypto/src/main/java/cn/hutool/crypto/symmetric/XXTEA.java new file mode 100644 index 0000000000..4f948cd4f9 --- /dev/null +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/symmetric/XXTEA.java @@ -0,0 +1,157 @@ +package cn.hutool.crypto.symmetric; + +import cn.hutool.core.io.IoUtil; + +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Serializable; + +/** + * XXTEA(Corrected Block Tiny Encryption Algorithm)算法实现
+ * 来自:https://github.com/xxtea/xxtea-java + * + * @author Ma Bingyao + */ +public class XXTEA implements SymmetricEncryptor, SymmetricDecryptor, Serializable { + private static final long serialVersionUID = 1L; + + private static final int DELTA = 0x9E3779B9; + + private final byte[] key; + + /** + * 构造 + * + * @param key 密钥,16位 + */ + public XXTEA(byte[] key) { + this.key = key; + } + + @Override + public byte[] encrypt(byte[] data) { + if (data.length == 0) { + return data; + } + return toByteArray(encrypt( + toIntArray(data, true), + toIntArray(fixKey(key), false)), false); + } + + @Override + public void encrypt(InputStream data, OutputStream out, boolean isClose) { + IoUtil.write(out, isClose, encrypt(IoUtil.readBytes(data))); + } + + @Override + public byte[] decrypt(byte[] data) { + if (data.length == 0) { + return data; + } + return toByteArray(decrypt( + toIntArray(data, false), + toIntArray(fixKey(key), false)), true); + } + + @Override + public void decrypt(InputStream data, OutputStream out, boolean isClose) { + IoUtil.write(out, isClose, decrypt(IoUtil.readBytes(data))); + } + + //region Private Method + private static int[] encrypt(int[] v, int[] k) { + int n = v.length - 1; + + if (n < 1) { + return v; + } + int p, q = 6 + 52 / (n + 1); + int z = v[n], y, sum = 0, e; + + while (q-- > 0) { + sum = sum + DELTA; + e = sum >>> 2 & 3; + for (p = 0; p < n; p++) { + y = v[p + 1]; + z = v[p] += mx(sum, y, z, p, e, k); + } + y = v[0]; + z = v[n] += mx(sum, y, z, p, e, k); + } + return v; + } + + private static int[] decrypt(int[] v, int[] k) { + int n = v.length - 1; + + if (n < 1) { + return v; + } + int p, q = 6 + 52 / (n + 1); + int z, y = v[0], sum = q * DELTA, e; + + while (sum != 0) { + e = sum >>> 2 & 3; + for (p = n; p > 0; p--) { + z = v[p - 1]; + y = v[p] -= mx(sum, y, z, p, e, k); + } + z = v[n]; + y = v[0] -= mx(sum, y, z, p, e, k); + sum = sum - DELTA; + } + return v; + } + + private static int mx(int sum, int y, int z, int p, int e, int[] k) { + return (z >>> 5 ^ y << 2) + (y >>> 3 ^ z << 4) ^ (sum ^ y) + (k[p & 3 ^ e] ^ z); + } + + private static byte[] fixKey(byte[] key) { + if (key.length == 16) { + return key; + } + byte[] fixedkey = new byte[16]; + System.arraycopy(key, 0, fixedkey, 0, Math.min(key.length, 16)); + return fixedkey; + } + + private static int[] toIntArray(byte[] data, boolean includeLength) { + int n = (((data.length & 3) == 0) + ? (data.length >>> 2) + : ((data.length >>> 2) + 1)); + int[] result; + + if (includeLength) { + result = new int[n + 1]; + result[n] = data.length; + } else { + result = new int[n]; + } + n = data.length; + for (int i = 0; i < n; ++i) { + result[i >>> 2] |= (0x000000ff & data[i]) << ((i & 3) << 3); + } + return result; + } + + private static byte[] toByteArray(int[] data, boolean includeLength) { + int n = data.length << 2; + + if (includeLength) { + int m = data[data.length - 1]; + n -= 4; + if ((m < n - 3) || (m > n)) { + return null; + } + n = m; + } + byte[] result = new byte[n]; + + for (int i = 0; i < n; ++i) { + result[i] = (byte) (data[i >>> 2] >>> ((i & 3) << 3)); + } + return result; + } + //endregion +} diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/symmetric/TEATest.java b/hutool-crypto/src/test/java/cn/hutool/crypto/test/symmetric/TEATest.java index 8a9d008d6e..53916926a2 100644 --- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/symmetric/TEATest.java +++ b/hutool-crypto/src/test/java/cn/hutool/crypto/test/symmetric/TEATest.java @@ -1,6 +1,7 @@ package cn.hutool.crypto.test.symmetric; import cn.hutool.crypto.symmetric.SymmetricCrypto; +import cn.hutool.crypto.symmetric.XXTEA; import org.junit.Assert; import org.junit.Test; @@ -10,7 +11,7 @@ import org.junit.Test; public class TEATest { @Test - public void teaTest(){ + public void teaTest() { String data = "测试的加密数据 by Hutool"; // 密钥必须为128bit @@ -24,7 +25,7 @@ public class TEATest { } @Test - public void xteaTest(){ + public void xteaTest() { String data = "测试的加密数据 by Hutool"; // 密钥必须为128bit @@ -36,4 +37,18 @@ public class TEATest { Assert.assertEquals(data, decryptStr); } + + @Test + public void xxteaTest() { + String data = "测试的加密数据 by Hutool"; + + // 密钥必须为128bit + final XXTEA tea = new XXTEA("MyPassword123456".getBytes()); + final byte[] encrypt = tea.encrypt(data); + + // 解密 + final String decryptStr = tea.decryptStr(encrypt); + + Assert.assertEquals(data, decryptStr); + } } -- Gitee From 4b22f7cc74c1ba6155228e745bd79c9a46b1eb9f Mon Sep 17 00:00:00 2001 From: Looly Date: Sun, 6 Mar 2022 19:22:29 +0800 Subject: [PATCH 16/57] fix code --- .../java/cn/hutool/script/ScriptRuntimeException.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/hutool-script/src/main/java/cn/hutool/script/ScriptRuntimeException.java b/hutool-script/src/main/java/cn/hutool/script/ScriptRuntimeException.java index c609ebbf7b..48b62e0854 100644 --- a/hutool-script/src/main/java/cn/hutool/script/ScriptRuntimeException.java +++ b/hutool-script/src/main/java/cn/hutool/script/ScriptRuntimeException.java @@ -42,11 +42,11 @@ public class ScriptRuntimeException extends RuntimeException { } /** - * Creates a ScriptException with message, filename and linenumber to be used in error messages. + * Creates a {@code ScriptException} with message, filename and linenumber to be used in error messages. * * @param message The string to use in the message - * @param fileName The file or resource name describing the location of a script error causing the ScriptException to be thrown. - * @param lineNumber A line number describing the location of a script error causing the ScriptException to be thrown. + * @param fileName The file or resource name describing the location of a script error causing the {@code ScriptException} to be thrown. + * @param lineNumber A line number describing the location of a script error causing the {@code ScriptException} to be thrown. */ public ScriptRuntimeException(String message, String fileName, int lineNumber) { super(message); @@ -55,7 +55,7 @@ public class ScriptRuntimeException extends RuntimeException { } /** - * ScriptException constructor specifying message, filename, line number and column number. + * {@code ScriptException} constructor specifying message, filename, line number and column number. * * @param message The message. * @param fileName The filename -- Gitee From 1e113ef83f071485ec24fa5c0bff920f566e29bb Mon Sep 17 00:00:00 2001 From: Looly Date: Sun, 6 Mar 2022 21:01:08 +0800 Subject: [PATCH 17/57] fix code --- .../java/cn/hutool/core/bean/BeanUtil.java | 4 +- .../hutool/core/map/CamelCaseLinkedMap.java | 20 +--------- .../java/cn/hutool/core/map/CamelCaseMap.java | 38 ++++++++++--------- .../core/map/CaseInsensitiveLinkedMap.java | 16 +------- .../hutool/core/map/CaseInsensitiveMap.java | 31 ++++++++------- .../core/map/CaseInsensitiveTreeMap.java | 18 +-------- .../java/cn/hutool/core/map/CustomKeyMap.java | 6 +-- .../hutool/core/map/FixedLinkedHashMap.java | 3 +- .../java/cn/hutool/core/map/FuncKeyMap.java | 9 +++-- .../java/cn/hutool/core/map/MapBuilder.java | 13 ++++++- .../java/cn/hutool/core/map/MapWrapper.java | 5 --- .../cn/hutool/core/bean/BeanUtilTest.java | 16 ++++---- 12 files changed, 75 insertions(+), 104 deletions(-) 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 b66b15f0df..84e7c59178 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 @@ -225,8 +225,8 @@ public class BeanUtil { */ private static Map internalGetPropertyDescriptorMap(Class clazz, boolean ignoreCase) throws BeanException { final PropertyDescriptor[] propertyDescriptors = getPropertyDescriptors(clazz); - final Map map = ignoreCase ? new CaseInsensitiveMap<>(propertyDescriptors.length, 1) - : new HashMap<>((int) (propertyDescriptors.length), 1); + final Map map = ignoreCase ? new CaseInsensitiveMap<>(propertyDescriptors.length, 1f) + : new HashMap<>(propertyDescriptors.length, 1); for (PropertyDescriptor propertyDescriptor : propertyDescriptors) { map.put(propertyDescriptor.getName(), propertyDescriptor); diff --git a/hutool-core/src/main/java/cn/hutool/core/map/CamelCaseLinkedMap.java b/hutool-core/src/main/java/cn/hutool/core/map/CamelCaseLinkedMap.java index fbd2b776ce..639bfbfccd 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/CamelCaseLinkedMap.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/CamelCaseLinkedMap.java @@ -1,7 +1,5 @@ package cn.hutool.core.map; -import cn.hutool.core.util.StrUtil; - import java.util.LinkedHashMap; import java.util.Map; @@ -15,7 +13,7 @@ import java.util.Map; * @param 值类型 * @since 4.0.7 */ -public class CamelCaseLinkedMap extends CustomKeyMap { +public class CamelCaseLinkedMap extends CamelCaseMap { private static final long serialVersionUID = 4043263744224569870L; // ------------------------------------------------------------------------- Constructor start @@ -48,7 +46,7 @@ public class CamelCaseLinkedMap extends CustomKeyMap { * 构造 * * @param loadFactor 加载因子 - * @param m Map + * @param m Map,数据会被默认拷贝到一个新的LinkedHashMap中 */ public CamelCaseLinkedMap(float loadFactor, Map m) { this(m.size(), loadFactor); @@ -65,18 +63,4 @@ public class CamelCaseLinkedMap extends CustomKeyMap { super(new LinkedHashMap<>(initialCapacity, loadFactor)); } // ------------------------------------------------------------------------- Constructor end - - /** - * 将Key转为驼峰风格,如果key为字符串的话 - * - * @param key KEY - * @return 驼峰Key - */ - @Override - protected Object customKey(Object key) { - if (key instanceof CharSequence) { - key = StrUtil.toCamelCase(key.toString()); - } - return key; - } } diff --git a/hutool-core/src/main/java/cn/hutool/core/map/CamelCaseMap.java b/hutool-core/src/main/java/cn/hutool/core/map/CamelCaseMap.java index ea1642d141..4759b72028 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/CamelCaseMap.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/CamelCaseMap.java @@ -1,24 +1,24 @@ package cn.hutool.core.map; +import cn.hutool.core.util.StrUtil; + import java.util.HashMap; import java.util.Map; -import cn.hutool.core.util.StrUtil; - /** * 驼峰Key风格的Map
* 对KEY转换为驼峰,get("int_value")和get("intValue")获得的值相同,put进入的值也会被覆盖 * - * @author Looly - * * @param 键类型 * @param 值类型 + * @author Looly * @since 4.0.7 */ -public class CamelCaseMap extends CustomKeyMap { +public class CamelCaseMap extends FuncKeyMap { private static final long serialVersionUID = 4043263744224569870L; // ------------------------------------------------------------------------- Constructor start + /** * 构造 */ @@ -48,7 +48,7 @@ public class CamelCaseMap extends CustomKeyMap { * 构造 * * @param loadFactor 加载因子 - * @param m Map + * @param m 初始Map,数据会被默认拷贝到一个新的HashMap中 */ public CamelCaseMap(float loadFactor, Map m) { this(m.size(), loadFactor); @@ -59,24 +59,26 @@ public class CamelCaseMap extends CustomKeyMap { * 构造 * * @param initialCapacity 初始大小 - * @param loadFactor 加载因子 + * @param loadFactor 加载因子 */ public CamelCaseMap(int initialCapacity, float loadFactor) { - super(new HashMap<>(initialCapacity, loadFactor)); + this(MapBuilder.create(new HashMap<>(initialCapacity, loadFactor))); } - // ------------------------------------------------------------------------- Constructor end /** - * 将Key转为驼峰风格,如果key为字符串的话 + * 构造
+ * 注意此构造将传入的Map作为被包装的Map,针对任何修改,传入的Map都会被同样修改。 * - * @param key KEY - * @return 驼峰Key + * @param emptyMapBuilder Map构造器,必须构造空的Map */ - @Override - protected Object customKey(Object key) { - if (key instanceof CharSequence) { - key = StrUtil.toCamelCase(key.toString()); - } - return key; + CamelCaseMap(MapBuilder emptyMapBuilder) { + super(emptyMapBuilder.build(), (key) -> { + if (key instanceof CharSequence) { + key = StrUtil.toCamelCase(key.toString()); + } + //noinspection unchecked + return (K) key; + }); } + // ------------------------------------------------------------------------- Constructor end } diff --git a/hutool-core/src/main/java/cn/hutool/core/map/CaseInsensitiveLinkedMap.java b/hutool-core/src/main/java/cn/hutool/core/map/CaseInsensitiveLinkedMap.java index 51cdd2561a..5be588ef37 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/CaseInsensitiveLinkedMap.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/CaseInsensitiveLinkedMap.java @@ -13,7 +13,7 @@ import java.util.Map; * @param 值类型 * @since 3.3.1 */ -public class CaseInsensitiveLinkedMap extends CustomKeyMap { +public class CaseInsensitiveLinkedMap extends CaseInsensitiveMap { private static final long serialVersionUID = 4043263744224569870L; // ------------------------------------------------------------------------- Constructor start @@ -64,18 +64,4 @@ public class CaseInsensitiveLinkedMap extends CustomKeyMap { super(new LinkedHashMap<>(initialCapacity, loadFactor)); } // ------------------------------------------------------------------------- Constructor end - - /** - * 将Key转为小写 - * - * @param key KEY - * @return 小写KEY - */ - @Override - protected Object customKey(Object key) { - if (key instanceof CharSequence) { - key = key.toString().toLowerCase(); - } - return key; - } } diff --git a/hutool-core/src/main/java/cn/hutool/core/map/CaseInsensitiveMap.java b/hutool-core/src/main/java/cn/hutool/core/map/CaseInsensitiveMap.java index 0c892b2680..40f805ad0d 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/CaseInsensitiveMap.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/CaseInsensitiveMap.java @@ -13,7 +13,7 @@ import java.util.Map; * @param 值类型 * @since 3.0.2 */ -public class CaseInsensitiveMap extends CustomKeyMap { +public class CaseInsensitiveMap extends FuncKeyMap { private static final long serialVersionUID = 4043263744224569870L; //------------------------------------------------------------------------- Constructor start @@ -34,9 +34,10 @@ public class CaseInsensitiveMap extends CustomKeyMap { } /** - * 构造 + * 构造
+ * 注意此构造将传入的Map作为被包装的Map,针对任何修改,传入的Map都会被同样修改。 * - * @param m Map + * @param m 被包装的自定义Map创建器 */ public CaseInsensitiveMap(Map m) { this(DEFAULT_LOAD_FACTOR, m); @@ -61,21 +62,23 @@ public class CaseInsensitiveMap extends CustomKeyMap { * @param loadFactor 加载因子 */ public CaseInsensitiveMap(int initialCapacity, float loadFactor) { - super(new HashMap<>(initialCapacity, loadFactor)); + this(MapBuilder.create(new HashMap<>(initialCapacity, loadFactor))); } - //------------------------------------------------------------------------- Constructor end /** - * 将Key转为小写 + * 构造
+ * 注意此构造将传入的Map作为被包装的Map,针对任何修改,传入的Map都会被同样修改。 * - * @param key KEY - * @return 小写KEY + * @param emptyMapBuilder 被包装的自定义Map创建器 */ - @Override - protected Object customKey(Object key) { - if (key instanceof CharSequence) { - key = key.toString().toLowerCase(); - } - return key; + CaseInsensitiveMap(MapBuilder emptyMapBuilder) { + super(emptyMapBuilder.build(), (key)->{ + if (key instanceof CharSequence) { + key = key.toString().toLowerCase(); + } + //noinspection unchecked + return (K) key; + }); } + //------------------------------------------------------------------------- Constructor end } diff --git a/hutool-core/src/main/java/cn/hutool/core/map/CaseInsensitiveTreeMap.java b/hutool-core/src/main/java/cn/hutool/core/map/CaseInsensitiveTreeMap.java index 7f8b065f63..69f3ec538f 100755 --- a/hutool-core/src/main/java/cn/hutool/core/map/CaseInsensitiveTreeMap.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/CaseInsensitiveTreeMap.java @@ -15,7 +15,7 @@ import java.util.TreeMap; * @param 值类型 * @since 3.3.1 */ -public class CaseInsensitiveTreeMap extends CustomKeyMap { +public class CaseInsensitiveTreeMap extends CaseInsensitiveMap { private static final long serialVersionUID = 4043263744224569870L; // ------------------------------------------------------------------------- Constructor start @@ -40,7 +40,7 @@ public class CaseInsensitiveTreeMap extends CustomKeyMap { /** * 构造 * - * @param m Map + * @param m Map,初始Map,键值对会被复制到新的TreeMap中 * @since 3.1.2 */ public CaseInsensitiveTreeMap(SortedMap m) { @@ -56,18 +56,4 @@ public class CaseInsensitiveTreeMap extends CustomKeyMap { super(new TreeMap<>(comparator)); } // ------------------------------------------------------------------------- Constructor end - - /** - * 将Key转为小写 - * - * @param key KEY - * @return 小写KEY - */ - @Override - protected Object customKey(Object key) { - if (key instanceof CharSequence) { - key = key.toString().toLowerCase(); - } - return key; - } } diff --git a/hutool-core/src/main/java/cn/hutool/core/map/CustomKeyMap.java b/hutool-core/src/main/java/cn/hutool/core/map/CustomKeyMap.java index 4584943a5e..04ed9d371e 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 @@ -18,11 +18,11 @@ public abstract class CustomKeyMap extends MapWrapper { * 构造
* 通过传入一个Map从而确定Map的类型,子类需创建一个空的Map,而非传入一个已有Map,否则值可能会被修改 * - * @param m Map 被包装的Map + * @param emptyMap Map 被包装的Map,必须为空Map,否则自定义key会无效 * @since 3.1.2 */ - public CustomKeyMap(Map m) { - super(m); + public CustomKeyMap(Map emptyMap) { + super(emptyMap); } @Override diff --git a/hutool-core/src/main/java/cn/hutool/core/map/FixedLinkedHashMap.java b/hutool-core/src/main/java/cn/hutool/core/map/FixedLinkedHashMap.java index 1559fde7a9..5ddaaa6f95 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/FixedLinkedHashMap.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/FixedLinkedHashMap.java @@ -3,7 +3,8 @@ package cn.hutool.core.map; import java.util.LinkedHashMap; /** - * 固定大小的{@link LinkedHashMap} 实现 + * 固定大小的{@link LinkedHashMap} 实现
+ * 注意此类非线程安全,由于{@link #get(Object)}操作会修改链表的顺序结构,因此也不可以使用读写锁。 * * @author looly * 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 e5ca6a719a..0cc468d617 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 @@ -19,13 +19,14 @@ public class FuncKeyMap extends CustomKeyMap { // ------------------------------------------------------------------------- Constructor start /** - * 构造 + * 构造
+ * 注意提供的Map中不能有键值对,否则可能导致自定义key失效 * - * @param m Map + * @param emptyMap Map,提供的空map * @param keyFunc 自定义KEY的函数 */ - public FuncKeyMap(Map m, Function keyFunc) { - super(m); + public FuncKeyMap(Map emptyMap, Function keyFunc) { + super(emptyMap); this.keyFunc = keyFunc; } // ------------------------------------------------------------------------- Constructor end diff --git a/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java b/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java index 5840d92042..2f605d71e0 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/MapBuilder.java @@ -13,7 +13,7 @@ import java.util.function.Supplier; * @param Value类型 * @since 3.1.1 */ -public class MapBuilder implements Builder> { +public class MapBuilder implements Builder> { private static final long serialVersionUID = 1L; private final Map map; @@ -120,6 +120,17 @@ public class MapBuilder implements Builder> { return this; } + /** + * 清空Map + * + * @return this + * @since 5.7.23 + */ + public MapBuilder clear() { + this.map.clear(); + return this; + } + /** * 创建后的map * 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 bfd81dd86d..3c1ac59363 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 @@ -88,7 +88,6 @@ public class MapWrapper implements Map, Iterable>, S } @Override - @SuppressWarnings("NullableProblems") public void putAll(Map m) { raw.putAll(m); } @@ -99,25 +98,21 @@ public class MapWrapper implements Map, Iterable>, S } @Override - @SuppressWarnings("NullableProblems") public Collection values() { return raw.values(); } @Override - @SuppressWarnings("NullableProblems") public Set keySet() { return raw.keySet(); } @Override - @SuppressWarnings("NullableProblems") public Set> entrySet() { return raw.entrySet(); } @Override - @SuppressWarnings("NullableProblems") public Iterator> iterator() { return this.entrySet().iterator(); } 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 a01dc608be..f311903132 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 @@ -5,6 +5,7 @@ import cn.hutool.core.bean.copier.CopyOptions; import cn.hutool.core.bean.copier.ValueProvider; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.ListUtil; +import cn.hutool.core.map.MapBuilder; import cn.hutool.core.map.MapUtil; import cn.hutool.core.thread.ThreadUtil; import cn.hutool.core.util.ArrayUtil; @@ -78,14 +79,15 @@ public class BeanUtilTest { @Test public void fillBeanWithMapIgnoreCaseTest() { - HashMap map = MapUtil.newHashMap(); - map.put("Name", "Joe"); - map.put("aGe", 12); - map.put("openId", "DFDFSDFWERWER"); + Map map = MapBuilder.create() + .put("Name", "Joe") + .put("aGe", 12) + .put("openId", "DFDFSDFWERWER") + .build(); SubPerson person = BeanUtil.fillBeanWithMapIgnoreCase(map, new SubPerson(), false); - Assert.assertEquals(person.getName(), "Joe"); - Assert.assertEquals(person.getAge(), 12); - Assert.assertEquals(person.getOpenid(), "DFDFSDFWERWER"); + Assert.assertEquals("Joe", person.getName()); + Assert.assertEquals(12, person.getAge()); + Assert.assertEquals("DFDFSDFWERWER", person.getOpenid()); } @Test -- Gitee From 04dc6d2b730170d5b8ed68f830febea36eec4945 Mon Sep 17 00:00:00 2001 From: Looly Date: Sun, 6 Mar 2022 21:08:15 +0800 Subject: [PATCH 18/57] fix code --- hutool-core/src/main/java/cn/hutool/core/text/StrBuilder.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/text/StrBuilder.java b/hutool-core/src/main/java/cn/hutool/core/text/StrBuilder.java index a09dc06810..18a18bd92a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/StrBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/StrBuilder.java @@ -8,7 +8,8 @@ import java.io.Serializable; import java.util.Arrays; /** - * 可复用的字符串生成器,非线程安全 + * 可复用的字符串生成器,非线程安全
+ * TODO 6.x移除此类,java8的StringBuilder非常完善了,无需重写。 * * @author Looly * @since 4.0.0 -- Gitee From 820db7fa3226683fc91311520aab87674fe82302 Mon Sep 17 00:00:00 2001 From: Looly Date: Mon, 7 Mar 2022 02:39:05 +0800 Subject: [PATCH 19/57] addTable --- .../cn/hutool/core/builder/EqualsBuilder.java | 122 ++++---- .../cn/hutool/core/map/multi/RowKeyTable.java | 272 ++++++++++++++++++ .../java/cn/hutool/core/map/multi/Table.java | 253 ++++++++++++++++ .../hutool/core/map/multi/package-info.java | 4 +- 4 files changed, 588 insertions(+), 63 deletions(-) create mode 100644 hutool-core/src/main/java/cn/hutool/core/map/multi/RowKeyTable.java create mode 100644 hutool-core/src/main/java/cn/hutool/core/map/multi/Table.java diff --git a/hutool-core/src/main/java/cn/hutool/core/builder/EqualsBuilder.java b/hutool-core/src/main/java/cn/hutool/core/builder/EqualsBuilder.java index bb7c7e46cd..e68ccdf387 100644 --- a/hutool-core/src/main/java/cn/hutool/core/builder/EqualsBuilder.java +++ b/hutool-core/src/main/java/cn/hutool/core/builder/EqualsBuilder.java @@ -70,7 +70,7 @@ public class EqualsBuilder implements Builder { * Converters value pair into a register pair. *

* - * @param lhs this object + * @param lhs {@code this} object * @param rhs the other object * @return the pair */ @@ -82,15 +82,15 @@ public class EqualsBuilder implements Builder { /** *

- * Returns true if the registry contains the given object pair. + * Returns {@code true} if the registry contains the given object pair. * Used by the reflection methods to avoid infinite loops. * Objects might be swapped therefore a check is needed if the object pair * is registered in given or swapped order. *

* - * @param lhs this object to lookup in registry + * @param lhs {@code this} object to lookup in registry * @param rhs the other object to lookup on registry - * @return boolean true if the registry contains the given object. + * @return boolean {@code true} if the registry contains the given object. * @since 3.0 */ static boolean isRegistered(final Object lhs, final Object rhs) { @@ -108,7 +108,7 @@ public class EqualsBuilder implements Builder { * Used by the reflection methods to avoid infinite loops. *

* - * @param lhs this object to register + * @param lhs {@code this} object to register * @param rhs the other object to register */ static void register(final Object lhs, final Object rhs) { @@ -131,7 +131,7 @@ public class EqualsBuilder implements Builder { *

* Used by the reflection methods to avoid infinite loops. * - * @param lhs this object to unregister + * @param lhs {@code this} object to unregister * @param rhs the other object to unregister * @since 3.0 */ @@ -170,7 +170,7 @@ public class EqualsBuilder implements Builder { * @param lhs 此对象 * @param rhs 另一个对象 * @param excludeFields 排除的字段集合,如果有不参与计算equals的字段加入此集合即可 - * @return 两个对象是否equals,是返回true + * @return 两个对象是否equals,是返回{@code true} */ public static boolean reflectionEquals(final Object lhs, final Object rhs, final Collection excludeFields) { return reflectionEquals(lhs, rhs, ArrayUtil.toArray(excludeFields, String.class)); @@ -182,62 +182,62 @@ public class EqualsBuilder implements Builder { * @param lhs 此对象 * @param rhs 另一个对象 * @param excludeFields 排除的字段集合,如果有不参与计算equals的字段加入此集合即可 - * @return 两个对象是否equals,是返回true + * @return 两个对象是否equals,是返回{@code true} */ public static boolean reflectionEquals(final Object lhs, final Object rhs, final String... excludeFields) { return reflectionEquals(lhs, rhs, false, null, excludeFields); } /** - *

This method uses reflection to determine if the two Objects + *

This method uses reflection to determine if the two {@code Object}s * are equal.

* - *

It uses AccessibleObject.setAccessible to gain access to private + *

It uses {@code AccessibleObject.setAccessible} to gain access to private * fields. This means that it will throw a security exception if run under * a security manager, if the permissions are not set up correctly. It is also * not as efficient as testing explicitly. Non-primitive fields are compared using - * equals().

+ * {@code equals()}.

* - *

If the TestTransients parameter is set to true, transient + *

If the TestTransients parameter is set to {@code true}, transient * members will be tested, otherwise they are ignored, as they are likely - * derived fields, and not part of the value of the Object.

+ * derived fields, and not part of the value of the {@code Object}.

* *

Static fields will not be tested. Superclass fields will be included.

* - * @param lhs this object + * @param lhs {@code this} object * @param rhs the other object * @param testTransients whether to include transient fields - * @return true if the two Objects have tested equals. + * @return {@code true} if the two Objects have tested equals. */ public static boolean reflectionEquals(final Object lhs, final Object rhs, final boolean testTransients) { return reflectionEquals(lhs, rhs, testTransients, null); } /** - *

This method uses reflection to determine if the two Objects + *

This method uses reflection to determine if the two {@code Object}s * are equal.

* - *

It uses AccessibleObject.setAccessible to gain access to private + *

It uses {@code AccessibleObject.setAccessible} to gain access to private * fields. This means that it will throw a security exception if run under * a security manager, if the permissions are not set up correctly. It is also * not as efficient as testing explicitly. Non-primitive fields are compared using - * equals().

+ * {@code equals()}.

* - *

If the testTransients parameter is set to true, transient + *

If the testTransients parameter is set to {@code true}, transient * members will be tested, otherwise they are ignored, as they are likely - * derived fields, and not part of the value of the Object.

+ * derived fields, and not part of the value of the {@code Object}.

* *

Static fields will not be included. Superclass fields will be appended * up to and including the specified superclass. A null superclass is treated * as java.lang.Object.

* - * @param lhs this object + * @param lhs {@code this} object * @param rhs the other object * @param testTransients whether to include transient fields * @param reflectUpToClass the superclass to reflect up to (inclusive), - * may be null + * may be {@code null} * @param excludeFields array of field names to exclude from testing - * @return true if the two Objects have tested equals. + * @return {@code true} if the two Objects have tested equals. * @since 2.0 */ public static boolean reflectionEquals(final Object lhs, final Object rhs, final boolean testTransients, final Class reflectUpToClass, @@ -343,9 +343,9 @@ public class EqualsBuilder implements Builder { //------------------------------------------------------------------------- /** - *

Adds the result of super.equals() to this builder.

+ *

Adds the result of {@code super.equals()} to this builder.

* - * @param superEquals the result of calling super.equals() + * @param superEquals the result of calling {@code super.equals()} * @return EqualsBuilder - used to chain calls. * @since 2.0 */ @@ -360,8 +360,8 @@ public class EqualsBuilder implements Builder { //------------------------------------------------------------------------- /** - *

Test if two Objects are equal using their - * equals method.

+ *

Test if two {@code Object}s are equal using their + * {@code equals} method.

* * @param lhs the left hand object * @param rhs the right hand object @@ -388,11 +388,11 @@ public class EqualsBuilder implements Builder { /** *

- * Test if two long s are equal. + * Test if two {@code long} s are equal. *

* - * @param lhs the left hand long - * @param rhs the right hand long + * @param lhs the left hand {@code long} + * @param rhs the right hand {@code long} * @return EqualsBuilder - used to chain calls. */ public EqualsBuilder append(final long lhs, final long rhs) { @@ -404,10 +404,10 @@ public class EqualsBuilder implements Builder { } /** - *

Test if two ints are equal.

+ *

Test if two {@code int}s are equal.

* - * @param lhs the left hand int - * @param rhs the right hand int + * @param lhs the left hand {@code int} + * @param rhs the right hand {@code int} * @return EqualsBuilder - used to chain calls. */ public EqualsBuilder append(final int lhs, final int rhs) { @@ -419,10 +419,10 @@ public class EqualsBuilder implements Builder { } /** - *

Test if two shorts are equal.

+ *

Test if two {@code short}s are equal.

* - * @param lhs the left hand short - * @param rhs the right hand short + * @param lhs the left hand {@code short} + * @param rhs the right hand {@code short} * @return EqualsBuilder - used to chain calls. */ public EqualsBuilder append(final short lhs, final short rhs) { @@ -434,10 +434,10 @@ public class EqualsBuilder implements Builder { } /** - *

Test if two chars are equal.

+ *

Test if two {@code char}s are equal.

* - * @param lhs the left hand char - * @param rhs the right hand char + * @param lhs the left hand {@code char} + * @param rhs the right hand {@code char} * @return EqualsBuilder - used to chain calls. */ public EqualsBuilder append(final char lhs, final char rhs) { @@ -449,10 +449,10 @@ public class EqualsBuilder implements Builder { } /** - *

Test if two bytes are equal.

+ *

Test if two {@code byte}s are equal.

* - * @param lhs the left hand byte - * @param rhs the right hand byte + * @param lhs the left hand {@code byte} + * @param rhs the right hand {@code byte} * @return EqualsBuilder - used to chain calls. */ public EqualsBuilder append(final byte lhs, final byte rhs) { @@ -464,16 +464,16 @@ public class EqualsBuilder implements Builder { } /** - *

Test if two doubles are equal by testing that the - * pattern of bits returned by doubleToLong are equal.

+ *

Test if two {@code double}s are equal by testing that the + * pattern of bits returned by {@code doubleToLong} are equal.

* - *

This handles NaNs, Infinities, and -0.0.

+ *

This handles NaNs, Infinities, and {@code -0.0}.

* *

It is compatible with the hash code generated by - * HashCodeBuilder.

+ * {@code HashCodeBuilder}.

* - * @param lhs the left hand double - * @param rhs the right hand double + * @param lhs the left hand {@code double} + * @param rhs the right hand {@code double} * @return EqualsBuilder - used to chain calls. */ public EqualsBuilder append(final double lhs, final double rhs) { @@ -484,16 +484,16 @@ public class EqualsBuilder implements Builder { } /** - *

Test if two floats are equal byt testing that the + *

Test if two {@code float}s are equal byt testing that the * pattern of bits returned by doubleToLong are equal.

* - *

This handles NaNs, Infinities, and -0.0.

+ *

This handles NaNs, Infinities, and {@code -0.0}.

* *

It is compatible with the hash code generated by - * HashCodeBuilder.

+ * {@code HashCodeBuilder}.

* - * @param lhs the left hand float - * @param rhs the right hand float + * @param lhs the left hand {@code float} + * @param rhs the right hand {@code float} * @return EqualsBuilder - used to chain calls. */ public EqualsBuilder append(final float lhs, final float rhs) { @@ -504,10 +504,10 @@ public class EqualsBuilder implements Builder { } /** - *

Test if two booleanss are equal.

+ *

Test if two {@code booleans}s are equal.

* - * @param lhs the left hand boolean - * @param rhs the right hand boolean + * @param lhs the left hand {@code boolean} + * @param rhs the right hand {@code boolean} * @return EqualsBuilder - used to chain calls. */ public EqualsBuilder append(final boolean lhs, final boolean rhs) { @@ -519,7 +519,7 @@ public class EqualsBuilder implements Builder { } /** - *

Returns true if the fields that have been checked + *

Returns {@code true} if the fields that have been checked * are all equal.

* * @return boolean @@ -529,11 +529,11 @@ public class EqualsBuilder implements Builder { } /** - *

Returns true if the fields that have been checked + *

Returns {@code true} if the fields that have been checked * are all equal.

* - * @return true if all of the fields that have been checked - * are equal, false otherwise. + * @return {@code true} if all of the fields that have been checked + * are equal, {@code false} otherwise. * @since 3.0 */ @Override @@ -542,7 +542,7 @@ public class EqualsBuilder implements Builder { } /** - * Sets the isEquals value. + * Sets the {@code isEquals} value. * * @param isEquals The value to set. * @return this 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 new file mode 100644 index 0000000000..0e88548968 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/map/multi/RowKeyTable.java @@ -0,0 +1,272 @@ +package cn.hutool.core.map.multi; + +import cn.hutool.core.collection.IterUtil; +import cn.hutool.core.collection.TransIter; +import cn.hutool.core.util.ObjectUtil; +import com.sun.istack.internal.Nullable; + +import java.io.Serializable; +import java.util.AbstractCollection; +import java.util.AbstractSet; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.function.Supplier; + +public class RowKeyTable implements Table { + + final Map> raw; + final Supplier> supplier; + + public RowKeyTable(Map> raw) { + this(raw, HashMap::new); + } + + public RowKeyTable(Map> raw, Supplier> columnMapSupplier) { + this.raw = raw; + this.supplier = null == columnMapSupplier ? HashMap::new : columnMapSupplier; + } + + @Override + public Map> rowMap() { + return raw; + } + + @Override + public Map> columnMap() { + // TODO 实现columnMap + throw new UnsupportedOperationException("TODO implement this method"); + } + + @Override + public Collection values() { + return this.values; + } + + @Override + public Set> cellSet() { + return this.cellSet; + } + + @Override + public V put(R rowKey, C columnKey, V value) { + return raw.computeIfAbsent(rowKey, (key) -> supplier.get()).put(columnKey, value); + } + + @Override + public void putAll(Table table) { + if (null != table) { + for (Table.Cell cell : table.cellSet()) { + put(cell.getRowKey(), cell.getColumnKey(), cell.getValue()); + } + } + } + + @Override + public V remove(R rowKey, C columnKey) { + final Map map = getRow(rowKey); + if (null == map) { + return null; + } + final V value = map.remove(columnKey); + if (map.isEmpty()) { + raw.remove(rowKey); + } + return value; + } + + @Override + public boolean isEmpty() { + return raw.isEmpty(); + } + + @Override + public void clear() { + this.raw.clear(); + } + + @Override + public Iterator> iterator() { + return new CellIterator(); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (obj == this) { + return true; + } else if (obj instanceof Table) { + final Table that = (Table) obj; + return this.cellSet().equals(that.cellSet()); + } else { + return false; + } + } + + @Override + public int hashCode() { + return cellSet().hashCode(); + } + + @Override + public String toString() { + return rowMap().toString(); + } + + /** + * 基于{@link Cell}的{@link Iterator}实现 + */ + private class CellIterator implements Iterator> { + final Iterator>> rowIterator = raw.entrySet().iterator(); + Map.Entry> rowEntry; + Iterator> columnIterator = IterUtil.empty(); + + @Override + public boolean hasNext() { + return rowIterator.hasNext() || columnIterator.hasNext(); + } + + @Override + public Cell next() { + if (false == columnIterator.hasNext()) { + rowEntry = rowIterator.next(); + columnIterator = rowEntry.getValue().entrySet().iterator(); + } + final Map.Entry columnEntry = columnIterator.next(); + return new SimpleCell<>(rowEntry.getKey(), columnEntry.getKey(), columnEntry.getValue()); + } + + @Override + public void remove() { + columnIterator.remove(); + if (rowEntry.getValue().isEmpty()) { + rowIterator.remove(); + } + } + } + + /** + * 简单{@link Cell} 实现 + * + * @param 行类型 + * @param 列类型 + * @param 值类型 + */ + private static class SimpleCell implements Cell, Serializable { + private static final long serialVersionUID = 1L; + + private final R rowKey; + private final C columnKey; + private final V value; + + SimpleCell(@Nullable R rowKey, @Nullable C columnKey, @Nullable V value) { + this.rowKey = rowKey; + this.columnKey = columnKey; + this.value = value; + } + + @Override + public R getRowKey() { + return rowKey; + } + + @Override + public C getColumnKey() { + return columnKey; + } + + @Override + public V getValue() { + return value; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof Cell) { + Cell other = (Cell) obj; + return ObjectUtil.equal(rowKey, other.getRowKey()) + && ObjectUtil.equal(columnKey, other.getColumnKey()) + && ObjectUtil.equal(value, other.getValue()); + } + return false; + } + + @Override + public int hashCode() { + return Objects.hash(rowKey, columnKey, value); + } + + @Override + public String toString() { + return "(" + rowKey + "," + columnKey + ")=" + value; + } + } + + private final Collection values = new AbstractCollection() { + @Override + public Iterator iterator() { + return new TransIter<>(cellSet().iterator(), Cell::getValue); + } + + @Override + public boolean contains(Object o) { + //noinspection unchecked + return containsValue((V) o); + } + + @Override + public void clear() { + RowKeyTable.this.clear(); + } + + @Override + public int size() { + return RowKeyTable.this.size(); + } + }; + + private final Set> cellSet = new AbstractSet>() { + @Override + public boolean contains(Object o) { + if (o instanceof Cell) { + @SuppressWarnings("unchecked") final + Cell cell = (Cell) o; + Map row = getRow(cell.getRowKey()); + if (null != row) { + return ObjectUtil.equals(row.get(cell.getColumnKey()), cell.getValue()); + } + } + return false; + } + + @Override + public boolean remove(Object o) { + if (contains(o)) { + @SuppressWarnings("unchecked") + final Cell cell = (Cell) o; + RowKeyTable.this.remove(cell.getRowKey(), cell.getColumnKey()); + } + return false; + } + + @Override + public void clear() { + RowKeyTable.this.clear(); + } + + @Override + public Iterator> iterator() { + return new CellIterator(); + } + + @Override + public int size() { + return RowKeyTable.this.size(); + } + }; +} 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 new file mode 100644 index 0000000000..cd14029a5b --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/map/multi/Table.java @@ -0,0 +1,253 @@ +package cn.hutool.core.map.multi; + +import cn.hutool.core.lang.Opt; +import cn.hutool.core.lang.func.Consumer3; +import cn.hutool.core.map.MapUtil; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +/** + * 表格数据结构定义
+ * 此结构类似于Guava的Table接口,使用两个键映射到一个值,类似于表格结构。 + * + * @param 行键类型 + * @param 列键类型 + * @param 值类型 + * @since 5.7.23 + */ +public interface Table extends Iterable> { + + /** + * 是否包含指定行列的映射
+ * 行和列任意一个不存在都会返回{@code false},如果行和列都存在,值为{@code null},也会返回{@code true} + * + * @param rowKey 行键 + * @param columnKey 列键 + * @return 是否包含映射 + */ + default boolean contains(R rowKey, C columnKey) { + return Opt.ofNullable(getRow(rowKey)).map((map) -> map.containsKey(columnKey)).get(); + } + + //region Row + + /** + * 行是否存在 + * + * @param rowKey 行键 + * @return 行是否存在 + */ + default boolean containsRow(R rowKey) { + return Opt.ofNullable(rowMap()).map((map) -> map.containsKey(rowKey)).get(); + } + + /** + * 获取行 + * + * @param rowKey 行键 + * @return 行映射,返回的键为列键,值为表格的值 + */ + default Map getRow(R rowKey) { + return Opt.ofNullable(rowMap()).map((map) -> map.get(rowKey)).get(); + } + + /** + * 返回所有行的key,行的key不可重复 + * + * @return 行键 + */ + default Set rowKeySet() { + return Opt.ofNullable(rowMap()).map(Map::keySet).get(); + } + + /** + * 返回行列对应的Map + * + * @return map,键为行键,值为列和值的对应map + */ + Map> rowMap(); + //endregion + + //region Column + + /** + * 列是否存在 + * + * @param columnKey 列键 + * @return 列是否存在 + */ + default boolean containsColumn(C columnKey) { + return Opt.ofNullable(columnMap()).map((map) -> map.containsKey(columnKey)).get(); + } + + /** + * 获取列 + * + * @param columnKey 列键 + * @return 列映射,返回的键为行键,值为表格的值 + */ + default Map getColumn(C columnKey) { + return Opt.ofNullable(columnMap()).map((map) -> map.get(columnKey)).get(); + } + + /** + * 返回所有列的key,列的key不可重复 + * + * @return 列set + */ + default Set columnKeySet() { + return Opt.ofNullable(columnMap()).map(Map::keySet).get(); + } + + /** + * 返回列-行对应的map + * + * @return map,键为列键,值为行和值的对应map + */ + Map> columnMap(); + //endregion + + //region value + + /** + * 指定值是否存在 + * + * @param value 值 + * @return 值 + */ + default boolean containsValue(V value){ + final Collection> rows = Opt.ofNullable(rowMap()).map(Map::values).get(); + if(null != rows){ + for (Map row : rows) { + if (row.containsValue(value)) { + return true; + } + } + } + return false; + } + + /** + * 获取指定值 + * + * @param rowKey 行键 + * @param columnKey 列键 + * @return 值,如果值不存在,返回{@code null} + */ + default V get(R rowKey, C columnKey) { + return Opt.ofNullable(getRow(rowKey)).map((map) -> map.get(columnKey)).get(); + } + + /** + * 所有行列值的集合 + * + * @return 值的集合 + */ + Collection values(); + //endregion + + /** + * 所有单元格集合 + * + * @return 单元格集合 + */ + Set> cellSet(); + + /** + * 为表格指定行列赋值,如果不存在,创建之,存在则替换之,返回原值 + * + * @param rowKey 行键 + * @param columnKey 列键 + * @param value 值 + * @return 原值,不存在返回{@code null} + */ + V put(R rowKey, C columnKey, V value); + + /** + * 批量加入 + * + * @param table 其他table + */ + void putAll(Table table); + + /** + * 移除指定值 + * + * @param rowKey 行键 + * @param columnKey 列键 + * @return 移除的值,如果值不存在,返回{@code null} + */ + V remove(R rowKey, C columnKey); + + /** + * 表格是否为空 + * + * @return 是否为空 + */ + boolean isEmpty(); + + /** + * 表格大小,一般为单元格的个数 + * + * @return 表格大小 + */ + default int size(){ + final Map> rowMap = rowMap(); + if(MapUtil.isEmpty(rowMap)){ + return 0; + } + int size = 0; + for (Map map : rowMap.values()) { + size += map.size(); + } + return size; + } + + /** + * 清空表格 + */ + void clear(); + + /** + * 遍历表格的单元格,处理值 + * + * @param consumer 单元格值处理器 + */ + default void forEach(Consumer3 consumer) { + for (Cell cell : this) { + consumer.accept(cell.getRowKey(), cell.getColumnKey(), cell.getValue()); + } + } + + /** + * 单元格,用于表示一个单元格的行、列和值 + * + * @param 行键类型 + * @param 列键类型 + * @param 值类型 + */ + interface Cell { + /** + * 获取行键 + * + * @return 行键 + */ + R getRowKey(); + + /** + * 获取列键 + * + * @return 列键 + */ + C getColumnKey(); + + /** + * 获取值 + * + * @return 值 + */ + V getValue(); + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/map/multi/package-info.java b/hutool-core/src/main/java/cn/hutool/core/map/multi/package-info.java index b0a2bcd20c..2bf5b685f3 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/multi/package-info.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/multi/package-info.java @@ -1,7 +1,7 @@ /** - * 列表类型值的Map实现 + * 多参数类型的Map实现,包括集合类型值的Map和Table * * @author looly * */ -package cn.hutool.core.map.multi; \ No newline at end of file +package cn.hutool.core.map.multi; -- Gitee From 01af68cd0d68561a1f46f856d4ff5dc5a95c20c9 Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 8 Mar 2022 01:15:13 +0800 Subject: [PATCH 20/57] add Table --- CHANGELOG.md | 4 +- .../cn/hutool/core/collection/IterUtil.java | 15 + .../java/cn/hutool/core/map/AbsEntry.java | 46 +++ .../java/cn/hutool/core/map/SimpleEntry.java | 31 ++ .../java/cn/hutool/core/map/TableMap.java | 49 +-- .../cn/hutool/core/map/multi/AbsTable.java | 236 +++++++++++++ .../cn/hutool/core/map/multi/RowKeyTable.java | 325 +++++++++--------- .../java/cn/hutool/core/map/multi/Table.java | 8 +- .../cn/hutool/core/map/RowKeyTableTest.java | 39 +++ 9 files changed, 535 insertions(+), 218 deletions(-) create mode 100644 hutool-core/src/main/java/cn/hutool/core/map/AbsEntry.java create mode 100644 hutool-core/src/main/java/cn/hutool/core/map/SimpleEntry.java create mode 100644 hutool-core/src/main/java/cn/hutool/core/map/multi/AbsTable.java create mode 100644 hutool-core/src/test/java/cn/hutool/core/map/RowKeyTableTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 74943728c0..4c9993b9fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,13 +2,15 @@ # 🚀Changelog ------------------------------------------------------------------------------------------------------------- -# 5.7.23 (2022-03-05) +# 5.7.23 (2022-03-08) ### 🐣新特性 * 【http 】 HttpRequest.form采用TableMap方式(issue#I4W427@Gitee) * 【core 】 AnnotationUtil增加getAnnotationAlias方法(pr#554@Gitee) * 【core 】 FileUtil.extName增加对tar.gz特殊处理(issue#I4W5FS@Gitee) * 【crypto 】 增加XXTEA实现(issue#I4WH2X@Gitee) +* 【core 】 增加Table实现(issue#2179@Github) +* ### 🐞Bug修复 * 【core 】 修复ObjectUtil.hasNull传入null返回true的问题(pr#555@Gitee) diff --git a/hutool-core/src/main/java/cn/hutool/core/collection/IterUtil.java b/hutool-core/src/main/java/cn/hutool/core/collection/IterUtil.java index f03451bc32..2634eefae2 100644 --- a/hutool-core/src/main/java/cn/hutool/core/collection/IterUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/collection/IterUtil.java @@ -906,4 +906,19 @@ public class IterUtil { // 当两个Iterable长度不一致时返回false return false == (it1.hasNext() || it2.hasNext()); } + + /** + * 清空指定{@link Iterator},此方法遍历后调用{@link Iterator#remove()}移除每个元素 + * + * @param iterator {@link Iterator} + * @since 5.7.23 + */ + public static void clear(Iterator iterator) { + if (null != iterator) { + while (iterator.hasNext()) { + iterator.next(); + iterator.remove(); + } + } + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/map/AbsEntry.java b/hutool-core/src/main/java/cn/hutool/core/map/AbsEntry.java new file mode 100644 index 0000000000..a2ab5885fe --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/map/AbsEntry.java @@ -0,0 +1,46 @@ +package cn.hutool.core.map; + +import cn.hutool.core.util.ObjectUtil; + +import java.util.Map; + +/** + * 抽象的{@link Map.Entry}实现,来自Guava
+ * 实现了默认的{@link #equals(Object)}、{@link #hashCode()}、{@link #toString()}方法。
+ * 默认{@link #setValue(Object)}抛出异常。 + * + * @param 键类型 + * @param 值类型 + * @author Guava + * @since 5.7.23 + */ +public abstract class AbsEntry implements Map.Entry { + + @Override + public V setValue(V value) { + throw new UnsupportedOperationException("Entry is read only."); + } + + @Override + public boolean equals(Object object) { + if (object instanceof Map.Entry) { + final Map.Entry that = (Map.Entry) object; + return ObjectUtil.equals(this.getKey(), that.getKey()) + && ObjectUtil.equals(this.getValue(), that.getValue()); + } + return false; + } + + @Override + public int hashCode() { + //copy from 1.8 HashMap.Node + K k = getKey(); + V v = getValue(); + return ((k == null) ? 0 : k.hashCode()) ^ ((v == null) ? 0 : v.hashCode()); + } + + @Override + public String toString() { + return getKey() + "=" + getValue(); + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/map/SimpleEntry.java b/hutool-core/src/main/java/cn/hutool/core/map/SimpleEntry.java new file mode 100644 index 0000000000..e414636c0f --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/map/SimpleEntry.java @@ -0,0 +1,31 @@ +package cn.hutool.core.map; + +/** + * {@link java.util.Map.Entry}简单实现。
+ * 键值对使用不可变字段表示。 + * + * @param 键类型 + * @param 值类型 + * @author looly + * @since 5.7.23 + */ +public class SimpleEntry extends AbsEntry { + + private final K key; + private final V value; + + public SimpleEntry(K key, V value) { + this.key = key; + this.value = value; + } + + @Override + public K getKey() { + return key; + } + + @Override + public V getValue() { + return value; + } +} 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 596ec51605..783f1e98a3 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 @@ -13,7 +13,6 @@ import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Set; /** @@ -173,7 +172,7 @@ public class TableMap implements Map, Iterable>, Ser public Set> entrySet() { final Set> hashSet = new LinkedHashSet<>(); for (int i = 0; i < size(); i++) { - hashSet.add(new Entry<>(keys.get(i), values.get(i))); + hashSet.add(new SimpleEntry<>(keys.get(i), values.get(i))); } return hashSet; } @@ -191,7 +190,7 @@ public class TableMap implements Map, Iterable>, Ser @Override public Map.Entry next() { - return new Entry<>(keysIter.next(), valuesIter.next()); + return new SimpleEntry<>(keysIter.next(), valuesIter.next()); } @Override @@ -209,48 +208,4 @@ public class TableMap implements Map, Iterable>, Ser ", values=" + values + '}'; } - - private static class Entry implements Map.Entry { - - private final K key; - private final V value; - - public Entry(K key, V value) { - this.key = key; - this.value = value; - } - - @Override - public K getKey() { - return key; - } - - @Override - public V getValue() { - return value; - } - - @Override - public V setValue(V value) { - throw new UnsupportedOperationException("setValue not supported."); - } - - @Override - public final boolean equals(Object o) { - if (o == this) - return true; - if (o instanceof Map.Entry) { - Map.Entry e = (Map.Entry) o; - return Objects.equals(key, e.getKey()) && - Objects.equals(value, e.getValue()); - } - return false; - } - - @Override - public int hashCode() { - //copy from 1.8 HashMap.Node - return Objects.hashCode(key) ^ Objects.hashCode(value); - } - } } diff --git a/hutool-core/src/main/java/cn/hutool/core/map/multi/AbsTable.java b/hutool-core/src/main/java/cn/hutool/core/map/multi/AbsTable.java new file mode 100644 index 0000000000..a98e402867 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/map/multi/AbsTable.java @@ -0,0 +1,236 @@ +package cn.hutool.core.map.multi; + +import cn.hutool.core.collection.IterUtil; +import cn.hutool.core.collection.TransIter; +import cn.hutool.core.util.ObjectUtil; + +import java.io.Serializable; +import java.util.AbstractCollection; +import java.util.AbstractSet; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +/** + * 抽象{@link Table}接口实现
+ * 默认实现了: + *
    + *
  • {@link #equals(Object)}
  • + *
  • {@link #hashCode()}
  • + *
  • {@link #toString()}
  • + *
  • {@link #values()}
  • + *
  • {@link #cellSet()}
  • + *
  • {@link #iterator()}
  • + *
+ * + * @param 行类型 + * @param 列类型 + * @param 值类型 + * @author Guava, Looly + * @since 5.7.23 + */ +public abstract class AbsTable implements Table { + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } else if (obj instanceof Table) { + final Table that = (Table) obj; + return this.cellSet().equals(that.cellSet()); + } else { + return false; + } + } + + @Override + public int hashCode() { + return cellSet().hashCode(); + } + + @Override + public String toString() { + return rowMap().toString(); + } + + //region values + @Override + public Collection values() { + Collection result = values; + return (result == null) ? values = new Values() : result; + } + + private Collection values; + private class Values extends AbstractCollection { + @Override + public Iterator iterator() { + return new TransIter<>(cellSet().iterator(), Cell::getValue); + } + + @Override + public boolean contains(Object o) { + //noinspection unchecked + return containsValue((V) o); + } + + @Override + public void clear() { + AbsTable.this.clear(); + } + + @Override + public int size() { + return AbsTable.this.size(); + } + } + //endregion + + //region cellSet + @Override + public Set> cellSet() { + Set> result = cellSet; + return (result == null) ? cellSet = new CellSet() : result; + } + + private Set> cellSet; + + private class CellSet extends AbstractSet> { + @Override + public boolean contains(Object o) { + if (o instanceof Cell) { + @SuppressWarnings("unchecked") final Cell cell = (Cell) o; + Map row = getRow(cell.getRowKey()); + if (null != row) { + return ObjectUtil.equals(row.get(cell.getColumnKey()), cell.getValue()); + } + } + return false; + } + + @Override + public boolean remove(Object o) { + if (contains(o)) { + @SuppressWarnings("unchecked") final Cell cell = (Cell) o; + AbsTable.this.remove(cell.getRowKey(), cell.getColumnKey()); + } + return false; + } + + @Override + public void clear() { + AbsTable.this.clear(); + } + + @Override + public Iterator> iterator() { + return new AbsTable.CellIterator(); + } + + @Override + public int size() { + return AbsTable.this.size(); + } + } + //endregion + + //region iterator + @Override + public Iterator> iterator() { + return new CellIterator(); + } + + /** + * 基于{@link Cell}的{@link Iterator}实现 + */ + private class CellIterator implements Iterator> { + final Iterator>> rowIterator = rowMap().entrySet().iterator(); + Map.Entry> rowEntry; + Iterator> columnIterator = IterUtil.empty(); + + @Override + public boolean hasNext() { + return rowIterator.hasNext() || columnIterator.hasNext(); + } + + @Override + public Cell next() { + if (false == columnIterator.hasNext()) { + rowEntry = rowIterator.next(); + columnIterator = rowEntry.getValue().entrySet().iterator(); + } + final Map.Entry columnEntry = columnIterator.next(); + return new SimpleCell<>(rowEntry.getKey(), columnEntry.getKey(), columnEntry.getValue()); + } + + @Override + public void remove() { + columnIterator.remove(); + if (rowEntry.getValue().isEmpty()) { + rowIterator.remove(); + } + } + } + //endregion + + /** + * 简单{@link Cell} 实现 + * + * @param 行类型 + * @param 列类型 + * @param 值类型 + */ + private static class SimpleCell implements Cell, Serializable { + private static final long serialVersionUID = 1L; + + private final R rowKey; + private final C columnKey; + private final V value; + + SimpleCell(R rowKey, C columnKey, V value) { + this.rowKey = rowKey; + this.columnKey = columnKey; + this.value = value; + } + + @Override + public R getRowKey() { + return rowKey; + } + + @Override + public C getColumnKey() { + return columnKey; + } + + @Override + public V getValue() { + return value; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof Cell) { + Cell other = (Cell) obj; + return ObjectUtil.equal(rowKey, other.getRowKey()) + && ObjectUtil.equal(columnKey, other.getColumnKey()) + && ObjectUtil.equal(value, other.getValue()); + } + return false; + } + + @Override + public int hashCode() { + return Objects.hash(rowKey, columnKey, value); + } + + @Override + public String toString() { + return "(" + rowKey + "," + columnKey + ")=" + value; + } + } +} 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 0e88548968..6094300ac2 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 @@ -1,68 +1,75 @@ package cn.hutool.core.map.multi; +import cn.hutool.core.builder.Builder; +import cn.hutool.core.collection.ComputeIter; import cn.hutool.core.collection.IterUtil; import cn.hutool.core.collection.TransIter; -import cn.hutool.core.util.ObjectUtil; -import com.sun.istack.internal.Nullable; +import cn.hutool.core.map.AbsEntry; +import cn.hutool.core.map.SimpleEntry; -import java.io.Serializable; -import java.util.AbstractCollection; +import java.util.AbstractMap; import java.util.AbstractSet; -import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.Map; -import java.util.Objects; import java.util.Set; -import java.util.function.Supplier; -public class RowKeyTable implements Table { +/** + * 将行的键作为主键的{@link Table}实现
+ * 此结构为: 行=(列=值) + * + * @param 行类型 + * @param 列类型 + * @param 值类型 + * @author Guava, Looly + * @since 5.7.23 + */ +public class RowKeyTable extends AbsTable { final Map> raw; - final Supplier> supplier; + /** + * 列的Map创建器,用于定义Table中Value对应Map类型 + */ + final Builder> columnBuilder; + + //region 构造 + + /** + * 构造 + */ + public RowKeyTable() { + this(new HashMap<>()); + } + /** + * 构造 + * + * @param raw 原始Map + */ public RowKeyTable(Map> raw) { this(raw, HashMap::new); } - public RowKeyTable(Map> raw, Supplier> columnMapSupplier) { + /** + * 构造 + * + * @param raw 原始Map + * @param columnMapBuilder 列的map创建器 + */ + public RowKeyTable(Map> raw, Builder> columnMapBuilder) { this.raw = raw; - this.supplier = null == columnMapSupplier ? HashMap::new : columnMapSupplier; + this.columnBuilder = null == columnMapBuilder ? HashMap::new : columnMapBuilder; } + //endregion @Override public Map> rowMap() { return raw; } - @Override - public Map> columnMap() { - // TODO 实现columnMap - throw new UnsupportedOperationException("TODO implement this method"); - } - - @Override - public Collection values() { - return this.values; - } - - @Override - public Set> cellSet() { - return this.cellSet; - } - @Override public V put(R rowKey, C columnKey, V value) { - return raw.computeIfAbsent(rowKey, (key) -> supplier.get()).put(columnKey, value); - } - - @Override - public void putAll(Table table) { - if (null != table) { - for (Table.Cell cell : table.cellSet()) { - put(cell.getRowKey(), cell.getColumnKey(), cell.getValue()); - } - } + return raw.computeIfAbsent(rowKey, (key) -> columnBuilder.build()).put(columnKey, value); } @Override @@ -89,184 +96,164 @@ public class RowKeyTable implements Table { } @Override - public Iterator> iterator() { - return new CellIterator(); - } - - @Override - public boolean equals(@Nullable Object obj) { - if (obj == this) { - return true; - } else if (obj instanceof Table) { - final Table that = (Table) obj; - return this.cellSet().equals(that.cellSet()); - } else { + public boolean containsColumn(C columnKey) { + if (columnKey == null) { return false; } + for (Map map : raw.values()) { + if (null != map && map.containsKey(columnKey)) { + return true; + } + } + return false; } + //region columnMap @Override - public int hashCode() { - return cellSet().hashCode(); - } - - @Override - public String toString() { - return rowMap().toString(); + public Map> columnMap() { + Map> result = columnMap; + return (result == null) ? columnMap = new ColumnMap() : result; } - /** - * 基于{@link Cell}的{@link Iterator}实现 - */ - private class CellIterator implements Iterator> { - final Iterator>> rowIterator = raw.entrySet().iterator(); - Map.Entry> rowEntry; - Iterator> columnIterator = IterUtil.empty(); + private Map> columnMap; + private class ColumnMap extends AbstractMap> { @Override - public boolean hasNext() { - return rowIterator.hasNext() || columnIterator.hasNext(); + public Set>> entrySet() { + return new ColumnMapEntrySet(); } + } + + private class ColumnMapEntrySet extends AbstractSet>> { + private final Set columnKeySet = columnKeySet(); @Override - public Cell next() { - if (false == columnIterator.hasNext()) { - rowEntry = rowIterator.next(); - columnIterator = rowEntry.getValue().entrySet().iterator(); - } - final Map.Entry columnEntry = columnIterator.next(); - return new SimpleCell<>(rowEntry.getKey(), columnEntry.getKey(), columnEntry.getValue()); + public Iterator>> iterator() { + return new TransIter<>(columnKeySet.iterator(), + c -> new SimpleEntry<>(c, getColumn(c))); } @Override - public void remove() { - columnIterator.remove(); - if (rowEntry.getValue().isEmpty()) { - rowIterator.remove(); - } + public int size() { + return columnKeySet.size(); } } + //endregion - /** - * 简单{@link Cell} 实现 - * - * @param 行类型 - * @param 列类型 - * @param 值类型 - */ - private static class SimpleCell implements Cell, Serializable { - private static final long serialVersionUID = 1L; - private final R rowKey; - private final C columnKey; - private final V value; + //region columnKeySet + @Override + public Set columnKeySet() { + Set result = columnKeySet; + return (result == null) ? columnKeySet = new ColumnKeySet() : result; + } - SimpleCell(@Nullable R rowKey, @Nullable C columnKey, @Nullable V value) { - this.rowKey = rowKey; - this.columnKey = columnKey; - this.value = value; - } + private Set columnKeySet; - @Override - public R getRowKey() { - return rowKey; - } + private class ColumnKeySet extends AbstractSet { @Override - public C getColumnKey() { - return columnKey; + public Iterator iterator() { + return new ColumnKeyIterator(); } @Override - public V getValue() { - return value; + public int size() { + return IterUtil.size(iterator()); } + } + + private class ColumnKeyIterator extends ComputeIter { + final Map seen = columnBuilder.build(); + final Iterator> mapIterator = raw.values().iterator(); + Iterator> entryIterator = IterUtil.empty(); @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } - if (obj instanceof Cell) { - Cell other = (Cell) obj; - return ObjectUtil.equal(rowKey, other.getRowKey()) - && ObjectUtil.equal(columnKey, other.getColumnKey()) - && ObjectUtil.equal(value, other.getValue()); + protected C computeNext() { + while (true) { + if (entryIterator.hasNext()) { + Map.Entry entry = entryIterator.next(); + if (false == seen.containsKey(entry.getKey())) { + seen.put(entry.getKey(), entry.getValue()); + return entry.getKey(); + } + } else if (mapIterator.hasNext()) { + entryIterator = mapIterator.next().entrySet().iterator(); + } else { + return null; + } } - return false; } + } + //endregion - @Override - public int hashCode() { - return Objects.hash(rowKey, columnKey, value); - } + //region getColumn - @Override - public String toString() { - return "(" + rowKey + "," + columnKey + ")=" + value; - } + @Override + public Map getColumn(C columnKey) { + return new Column(columnKey); } - private final Collection values = new AbstractCollection() { - @Override - public Iterator iterator() { - return new TransIter<>(cellSet().iterator(), Cell::getValue); - } + private class Column extends AbstractMap { + final C columnKey; - @Override - public boolean contains(Object o) { - //noinspection unchecked - return containsValue((V) o); + Column(C columnKey) { + this.columnKey = columnKey; } @Override - public void clear() { - RowKeyTable.this.clear(); + public Set> entrySet() { + return new EntrySet(); } - @Override - public int size() { - return RowKeyTable.this.size(); - } - }; + private class EntrySet extends AbstractSet> { - private final Set> cellSet = new AbstractSet>() { - @Override - public boolean contains(Object o) { - if (o instanceof Cell) { - @SuppressWarnings("unchecked") final - Cell cell = (Cell) o; - Map row = getRow(cell.getRowKey()); - if (null != row) { - return ObjectUtil.equals(row.get(cell.getColumnKey()), cell.getValue()); - } + @Override + public Iterator> iterator() { + return new EntrySetIterator(); } - return false; - } - @Override - public boolean remove(Object o) { - if (contains(o)) { - @SuppressWarnings("unchecked") - final Cell cell = (Cell) o; - RowKeyTable.this.remove(cell.getRowKey(), cell.getColumnKey()); + @Override + public int size() { + int size = 0; + for (Map map : raw.values()) { + if (map.containsKey(columnKey)) { + size++; + } + } + return size; } - return false; } - @Override - public void clear() { - RowKeyTable.this.clear(); - } - - @Override - public Iterator> iterator() { - return new CellIterator(); - } - - @Override - public int size() { - return RowKeyTable.this.size(); + private class EntrySetIterator extends ComputeIter> { + final Iterator>> iterator = raw.entrySet().iterator(); + + @Override + protected Entry computeNext() { + while (iterator.hasNext()) { + final Entry> entry = iterator.next(); + if (entry.getValue().containsKey(columnKey)) { + return new AbsEntry() { + @Override + public R getKey() { + return entry.getKey(); + } + + @Override + public V getValue() { + return entry.getValue().get(columnKey); + } + + @Override + public V setValue(V value) { + return entry.getValue().put(columnKey, value); + } + }; + } + } + return null; + } } - }; + } + //endregion } 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 cd14029a5b..e0fb1c5f4f 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 @@ -170,7 +170,13 @@ public interface Table extends Iterable> { * * @param table 其他table */ - void putAll(Table table); + default void putAll(Table table){ + if (null != table) { + for (Table.Cell cell : table.cellSet()) { + put(cell.getRowKey(), cell.getColumnKey(), cell.getValue()); + } + } + } /** * 移除指定值 diff --git a/hutool-core/src/test/java/cn/hutool/core/map/RowKeyTableTest.java b/hutool-core/src/test/java/cn/hutool/core/map/RowKeyTableTest.java new file mode 100644 index 0000000000..fdbaca3986 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/map/RowKeyTableTest.java @@ -0,0 +1,39 @@ +package cn.hutool.core.map; + +import cn.hutool.core.map.multi.RowKeyTable; +import cn.hutool.core.map.multi.Table; +import org.junit.Assert; +import org.junit.Test; + +import java.util.Map; + +public class RowKeyTableTest { + + @Test + public void putGetTest(){ + final Table table = new RowKeyTable<>(); + table.put(1, 2, 3); + table.put(1, 6, 4); + + Assert.assertEquals(new Integer(3), table.get(1, 2)); + Assert.assertNull(table.get(1, 3)); + + //判断row和column确定的二维点是否存在 + Assert.assertTrue(table.contains(1, 2)); + Assert.assertFalse(table.contains(1, 3)); + + //判断列 + Assert.assertTrue(table.containsColumn(2)); + Assert.assertFalse(table.containsColumn(3)); + + // 判断行 + Assert.assertTrue(table.containsRow(1)); + Assert.assertFalse(table.containsRow(2)); + + + // 获取列 + Map column = table.getColumn(6); + Assert.assertEquals(1, column.size()); + Assert.assertEquals(new Integer(4), column.get(1)); + } +} -- Gitee From 2cfb33af9eeac7869e180c496ac1c427f77fa903 Mon Sep 17 00:00:00 2001 From: jiazhengquan <2466896229@qq.com> Date: Tue, 8 Mar 2022 18:01:07 +0800 Subject: [PATCH 21/57] =?UTF-8?q?=E9=92=88=E5=AF=B9issue#I4WUWR=E5=88=9D?= =?UTF-8?q?=E7=89=88=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/cn/hutool/core/util/ReflectUtil.java | 33 ++++++++++++++----- 1 file changed, 25 insertions(+), 8 deletions(-) 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 de406bfbfd..4f9ded9d2c 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 @@ -663,18 +663,35 @@ public class ReflectUtil { Assert.notNull(beanClass); Method[] allMethods = null; + Class[] searchInterfaces = null, parentInterfaces = null; Class searchType = beanClass; Method[] declaredMethods; - while (searchType != null) { - declaredMethods = searchType.getDeclaredMethods(); - if (null == allMethods) { - allMethods = declaredMethods; - } else { - allMethods = ArrayUtil.append(allMethods, declaredMethods); + while (searchType != null || searchInterfaces != null) { + if (searchType != null) { + declaredMethods = searchType.getDeclaredMethods(); + if (null == allMethods) { + allMethods = declaredMethods; + } else { + allMethods = ArrayUtil.append(allMethods, declaredMethods); + } + Class[] interfaces = searchType.getInterfaces(); + for (Class element : interfaces) { + allMethods = ArrayUtil.append(allMethods, element.getDeclaredMethods()); + parentInterfaces = ArrayUtil.addAll(element.getInterfaces()); + } + searchInterfaces = parentInterfaces; + Class[] classes = searchInterfaces.length == 0 ? null : searchInterfaces; + searchInterfaces = withSuperClassMethods ? classes : null; + searchType = withSuperClassMethods ? searchType.getSuperclass() : null; + } + if (searchInterfaces != null) { + for (Class searchInterface : searchInterfaces) { + allMethods = ArrayUtil.append(allMethods, searchInterface.getDeclaredMethods()); + parentInterfaces = ArrayUtil.addAll(searchInterface.getInterfaces()); + } + searchInterfaces = parentInterfaces.length == 0 ? null : parentInterfaces; } - searchType = withSuperClassMethods ? searchType.getSuperclass() : null; } - return allMethods; } -- Gitee From 5eac491cd4427a15b39efed4eaa3f00a5a9c5cfc Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 8 Mar 2022 19:00:27 +0800 Subject: [PATCH 22/57] fix code --- .../java/cn/hutool/core/convert/NumberChineseFormatter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/convert/NumberChineseFormatter.java b/hutool-core/src/main/java/cn/hutool/core/convert/NumberChineseFormatter.java index d6da1d22b5..2a315d38dc 100644 --- a/hutool-core/src/main/java/cn/hutool/core/convert/NumberChineseFormatter.java +++ b/hutool-core/src/main/java/cn/hutool/core/convert/NumberChineseFormatter.java @@ -188,7 +188,7 @@ public class NumberChineseFormatter { Assert.checkBetween(amount, -999, 999, "Number support only: (-999 ~ 999)!"); final String chinese = thousandToChinese(amount, isUseTraditional); - if(amount < 20 && amount > 10){ + if(amount < 20 && amount >= 10){ // "十一"而非"一十一" return chinese.substring(1); } -- Gitee From 8a8242409748029b52c250e1c0951c0ff30ff423 Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 8 Mar 2022 19:21:07 +0800 Subject: [PATCH 23/57] fix convert bug --- CHANGELOG.md | 3 ++- .../core/convert/ConverterRegistry.java | 4 ++++ .../core/convert/impl/NumberConverter.java | 4 ++-- .../cn/hutool/core/convert/ConvertTest.java | 22 +++++++++++++++++++ 4 files changed, 30 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 74943728c0..b7b36fb559 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ # 🚀Changelog ------------------------------------------------------------------------------------------------------------- -# 5.7.23 (2022-03-05) +# 5.7.23 (2022-03-08) ### 🐣新特性 * 【http 】 HttpRequest.form采用TableMap方式(issue#I4W427@Gitee) @@ -11,6 +11,7 @@ * 【crypto 】 增加XXTEA实现(issue#I4WH2X@Gitee) ### 🐞Bug修复 * 【core 】 修复ObjectUtil.hasNull传入null返回true的问题(pr#555@Gitee) +* 【core 】 修复NumberConverter对数字转换的问题(issue#I4WPF4@Gitee) ------------------------------------------------------------------------------------------------------------- # 5.7.22 (2022-03-01) diff --git a/hutool-core/src/main/java/cn/hutool/core/convert/ConverterRegistry.java b/hutool-core/src/main/java/cn/hutool/core/convert/ConverterRegistry.java index 96b19c52f0..bbc1fd3470 100644 --- a/hutool-core/src/main/java/cn/hutool/core/convert/ConverterRegistry.java +++ b/hutool-core/src/main/java/cn/hutool/core/convert/ConverterRegistry.java @@ -77,6 +77,8 @@ import java.util.concurrent.atomic.AtomicIntegerArray; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLongArray; import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.atomic.DoubleAdder; +import java.util.concurrent.atomic.LongAdder; /** * 转换器登记中心 @@ -389,11 +391,13 @@ public class ConverterRegistry implements Serializable { defaultConverterMap.put(Integer.class, new NumberConverter(Integer.class)); defaultConverterMap.put(AtomicInteger.class, new NumberConverter(AtomicInteger.class));// since 3.0.8 defaultConverterMap.put(Long.class, new NumberConverter(Long.class)); + defaultConverterMap.put(LongAdder.class, new NumberConverter(LongAdder.class)); defaultConverterMap.put(AtomicLong.class, new NumberConverter(AtomicLong.class));// since 3.0.8 defaultConverterMap.put(Byte.class, new NumberConverter(Byte.class)); defaultConverterMap.put(Short.class, new NumberConverter(Short.class)); defaultConverterMap.put(Float.class, new NumberConverter(Float.class)); defaultConverterMap.put(Double.class, new NumberConverter(Double.class)); + defaultConverterMap.put(DoubleAdder.class, new NumberConverter(DoubleAdder.class)); defaultConverterMap.put(Character.class, new CharacterConverter()); defaultConverterMap.put(Boolean.class, new BooleanConverter()); defaultConverterMap.put(AtomicBoolean.class, new AtomicBooleanConverter());// since 3.0.8 diff --git a/hutool-core/src/main/java/cn/hutool/core/convert/impl/NumberConverter.java b/hutool-core/src/main/java/cn/hutool/core/convert/impl/NumberConverter.java index 36242cab9e..421628c91e 100644 --- a/hutool-core/src/main/java/cn/hutool/core/convert/impl/NumberConverter.java +++ b/hutool-core/src/main/java/cn/hutool/core/convert/impl/NumberConverter.java @@ -186,7 +186,7 @@ public class NumberConverter extends AbstractConverter { return StrUtil.isBlank(valueStr) ? null : NumberUtil.parseFloat(valueStr); } else if (Double.class == targetType) { if (value instanceof Number) { - return ((Number) value).doubleValue(); + return NumberUtil.toDouble((Number) value); } else if (value instanceof Boolean) { return BooleanUtil.toDoubleObj((Boolean) value); } @@ -194,7 +194,7 @@ public class NumberConverter extends AbstractConverter { return StrUtil.isBlank(valueStr) ? null : NumberUtil.parseDouble(valueStr); } else if (DoubleAdder.class == targetType) { //jdk8 新增 - final Number number = convert(value, Long.class, toStrFunc); + final Number number = convert(value, Double.class, toStrFunc); if (null != number) { final DoubleAdder doubleAdder = new DoubleAdder(); doubleAdder.add(number.doubleValue()); diff --git a/hutool-core/src/test/java/cn/hutool/core/convert/ConvertTest.java b/hutool-core/src/test/java/cn/hutool/core/convert/ConvertTest.java index 402140e680..18cd78b144 100644 --- a/hutool-core/src/test/java/cn/hutool/core/convert/ConvertTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/convert/ConvertTest.java @@ -24,6 +24,7 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicIntegerArray; import java.util.concurrent.atomic.AtomicLongArray; +import java.util.concurrent.atomic.DoubleAdder; /** * 类型转换工具单元测试 @@ -361,4 +362,25 @@ public class ConvertTest { final float f = Convert.toFloat(value); Assert.assertEquals(406.1F, f, 2); } + + @Test + public void floatToDoubleTest(){ + float a = 0.45f; + double b = Convert.toDouble(a); + Assert.assertEquals(a, b, 5); + } + + @Test + public void floatToDoubleAddrTest(){ + float a = 0.45f; + final DoubleAdder adder = Convert.convert(DoubleAdder.class, a); + Assert.assertEquals(a, adder.doubleValue(), 5); + } + + @Test + public void doubleToFloatTest(){ + double a = 0.45f; + float b = Convert.toFloat(a); + Assert.assertEquals(a, b, 5); + } } -- Gitee From 9229eea85f77432dca67c8703efc48fd2e4466d5 Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 8 Mar 2022 20:52:43 +0800 Subject: [PATCH 24/57] fix bug --- CHANGELOG.md | 1 + .../cn/hutool/core/util/ModifierUtil.java | 72 ++++++++++++++----- .../java/cn/hutool/core/util/ReflectUtil.java | 20 ++++-- .../core/lang/test/bean/ExamInfoDict.java | 50 ++----------- .../cn/hutool/core/util/ReflectUtilTest.java | 63 +++++++++++++++- 5 files changed, 134 insertions(+), 72 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dd037ca02d..3fd0cf5e63 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ ### 🐞Bug修复 * 【core 】 修复ObjectUtil.hasNull传入null返回true的问题(pr#555@Gitee) * 【core 】 修复NumberConverter对数字转换的问题(issue#I4WPF4@Gitee) +* 【core 】 修复ReflectUtil.getMethods获取接口方法问题(issue#I4WUWR@Gitee) ------------------------------------------------------------------------------------------------------------- # 5.7.22 (2022-03-01) diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ModifierUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ModifierUtil.java index f2b3f389ff..dec42bed06 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ModifierUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ModifierUtil.java @@ -20,35 +20,60 @@ public class ModifierUtil { * @since 4.0.5 */ public enum ModifierType { - /** public修饰符,所有类都能访问 */ + /** + * public修饰符,所有类都能访问 + */ PUBLIC(Modifier.PUBLIC), - /** private修饰符,只能被自己访问和修改 */ + /** + * private修饰符,只能被自己访问和修改 + */ PRIVATE(Modifier.PRIVATE), - /** protected修饰符,自身、子类及同一个包中类可以访问 */ + /** + * protected修饰符,自身、子类及同一个包中类可以访问 + */ PROTECTED(Modifier.PROTECTED), - /** static修饰符,(静态修饰符)指定变量被所有对象共享,即所有实例都可以使用该变量。变量属于这个类 */ + /** + * static修饰符,(静态修饰符)指定变量被所有对象共享,即所有实例都可以使用该变量。变量属于这个类 + */ STATIC(Modifier.STATIC), - /** final修饰符,最终修饰符,指定此变量的值不能变,使用在方法上表示不能被重载 */ + /** + * final修饰符,最终修饰符,指定此变量的值不能变,使用在方法上表示不能被重载 + */ FINAL(Modifier.FINAL), - /** synchronized,同步修饰符,在多个线程中,该修饰符用于在运行前,对他所属的方法加锁,以防止其他线程的访问,运行结束后解锁。 */ + /** + * synchronized,同步修饰符,在多个线程中,该修饰符用于在运行前,对他所属的方法加锁,以防止其他线程的访问,运行结束后解锁。 + */ SYNCHRONIZED(Modifier.SYNCHRONIZED), - /** (易失修饰符)指定该变量可以同时被几个线程控制和修改 */ + /** + * (易失修饰符)指定该变量可以同时被几个线程控制和修改 + */ VOLATILE(Modifier.VOLATILE), - /** (过度修饰符)指定该变量是系统保留,暂无特别作用的临时性变量,序列化时忽略 */ + /** + * (过度修饰符)指定该变量是系统保留,暂无特别作用的临时性变量,序列化时忽略 + */ TRANSIENT(Modifier.TRANSIENT), - /** native,本地修饰符。指定此方法的方法体是用其他语言在程序外部编写的。 */ + /** + * native,本地修饰符。指定此方法的方法体是用其他语言在程序外部编写的。 + */ NATIVE(Modifier.NATIVE), - /** abstract,将一个类声明为抽象类,没有实现的方法,需要子类提供方法实现。 */ + /** + * abstract,将一个类声明为抽象类,没有实现的方法,需要子类提供方法实现。 + */ ABSTRACT(Modifier.ABSTRACT), - /** strictfp,一旦使用了关键字strictfp来声明某个类、接口或者方法时,那么在这个关键字所声明的范围内所有浮点运算都是精确的,符合IEEE-754规范的。 */ + /** + * strictfp,一旦使用了关键字strictfp来声明某个类、接口或者方法时,那么在这个关键字所声明的范围内所有浮点运算都是精确的,符合IEEE-754规范的。 + */ STRICT(Modifier.STRICT); - /** 修饰符枚举对应的int修饰符值 */ + /** + * 修饰符枚举对应的int修饰符值 + */ private final int value; /** * 构造 + * * @param modifier 修饰符int表示,见{@link Modifier} */ ModifierType(int modifier) { @@ -57,6 +82,7 @@ public class ModifierUtil { /** * 获取修饰符枚举对应的int修饰符值,值见{@link Modifier} + * * @return 修饰符枚举对应的int修饰符值 */ public int getValue() { @@ -67,7 +93,7 @@ public class ModifierUtil { /** * 是否同时存在一个或多个修饰符(可能有多个修饰符,如果有指定的修饰符则返回true) * - * @param clazz 类 + * @param clazz 类 * @param modifierTypes 修饰符枚举 * @return 是否有指定修饰符,如果有返回true,否则false,如果提供参数为null返回false */ @@ -81,7 +107,7 @@ public class ModifierUtil { /** * 是否同时存在一个或多个修饰符(可能有多个修饰符,如果有指定的修饰符则返回true) * - * @param constructor 构造方法 + * @param constructor 构造方法 * @param modifierTypes 修饰符枚举 * @return 是否有指定修饰符,如果有返回true,否则false,如果提供参数为null返回false */ @@ -95,7 +121,7 @@ public class ModifierUtil { /** * 是否同时存在一个或多个修饰符(可能有多个修饰符,如果有指定的修饰符则返回true) * - * @param method 方法 + * @param method 方法 * @param modifierTypes 修饰符枚举 * @return 是否有指定修饰符,如果有返回true,否则false,如果提供参数为null返回false */ @@ -109,7 +135,7 @@ public class ModifierUtil { /** * 是否同时存在一个或多个修饰符(可能有多个修饰符,如果有指定的修饰符则返回true) * - * @param field 字段 + * @param field 字段 * @param modifierTypes 修饰符枚举 * @return 是否有指定修饰符,如果有返回true,否则false,如果提供参数为null返回false */ @@ -226,15 +252,27 @@ public class ModifierUtil { return clazz.isSynthetic(); } + /** + * 是否抽象方法 + * + * @param method 方法 + * @return 是否抽象方法 + * @since 5.7.23 + */ + public static boolean isAbstract(Method method) { + return hasModifier(method, ModifierType.ABSTRACT); + } //-------------------------------------------------------------------------------------------------------- Private method start + /** * 多个修饰符做“与”操作,表示同时存在多个修饰符 + * * @param modifierTypes 修饰符列表,元素不能为空 * @return “与”之后的修饰符 */ private static int modifiersToInt(ModifierType... modifierTypes) { int modifier = modifierTypes[0].getValue(); - for(int i = 1; i < modifierTypes.length; i++) { + for (int i = 1; i < modifierTypes.length; i++) { modifier |= modifierTypes[i].getValue(); } return modifier; 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 de406bfbfd..4e4a78faf8 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 @@ -652,30 +652,36 @@ public class ReflectUtil { } /** - * 获得一个类中所有方法列表,直接反射获取,无缓存 + * 获得一个类中所有方法列表,直接反射获取,无缓存
+ * 接口获取方法和默认方法 * - * @param beanClass 类 - * @param withSuperClassMethods 是否包括父类的方法列表 + * @param beanClass 类或接口 + * @param withSupers 是否包括父类或接口的方法列表 * @return 方法列表 * @throws SecurityException 安全检查异常 */ - public static Method[] getMethodsDirectly(Class beanClass, boolean withSuperClassMethods) throws SecurityException { + public static Method[] getMethodsDirectly(Class beanClass, boolean withSupers) throws SecurityException { Assert.notNull(beanClass); + if(beanClass.isInterface()){ + // 对于接口,直接调用Class.getMethods方法获取所有方法,因为接口都是public方法 + return withSupers ? beanClass.getMethods() : beanClass.getDeclaredMethods(); + } + Method[] allMethods = null; Class searchType = beanClass; Method[] declaredMethods; - while (searchType != null) { + while (searchType != null && searchType != Object.class) { declaredMethods = searchType.getDeclaredMethods(); if (null == allMethods) { allMethods = declaredMethods; } else { allMethods = ArrayUtil.append(allMethods, declaredMethods); } - searchType = withSuperClassMethods ? searchType.getSuperclass() : null; + searchType = (withSupers && false == searchType.isInterface()) ? searchType.getSuperclass() : null; } - return allMethods; + return ArrayUtil.append(allMethods); } /** diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/test/bean/ExamInfoDict.java b/hutool-core/src/test/java/cn/hutool/core/lang/test/bean/ExamInfoDict.java index a39e91238b..b5068f2d8a 100644 --- a/hutool-core/src/test/java/cn/hutool/core/lang/test/bean/ExamInfoDict.java +++ b/hutool-core/src/test/java/cn/hutool/core/lang/test/bean/ExamInfoDict.java @@ -1,16 +1,18 @@ package cn.hutool.core.lang.test.bean; +import lombok.Data; + import java.io.Serializable; -import java.util.Objects; /** - * + * * @author 质量过关 * */ +@Data public class ExamInfoDict implements Serializable { private static final long serialVersionUID = 3640936499125004525L; - + // 主键 private Integer id; // 可当作题号 // 试题类型 客观题 0主观题 1 @@ -18,49 +20,7 @@ public class ExamInfoDict implements Serializable { // 试题是否作答 private Integer answerIs; - public Integer getId() { - return id; - } public Integer getId(Integer defaultValue) { return this.id == null ? defaultValue : this.id; } - public void setId(Integer id) { - this.id = id; - } - - public Integer getExamType() { - return examType; - } - public void setExamType(Integer examType) { - this.examType = examType; - } - - public Integer getAnswerIs() { - return answerIs; - } - public void setAnswerIs(Integer answerIs) { - this.answerIs = answerIs; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - ExamInfoDict that = (ExamInfoDict) o; - return Objects.equals(id, that.id) && Objects.equals(examType, that.examType) && Objects.equals(answerIs, that.answerIs); - } - - @Override - public int hashCode() { - return Objects.hash(id, examType, answerIs); - } - - @Override - public String toString() { - return "ExamInfoDict{" + "id=" + id + ", examType=" + examType + ", answerIs=" + answerIs + '}'; - } } 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 9a90c8fe38..bb9dca5f3b 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 @@ -116,7 +116,7 @@ public class ReflectUtilTest { @Ignore public void getMethodBenchTest(){ // 预热 - getMethod(TestBenchClass.class, false, "getH"); + getMethodWithReturnTypeCheck(TestBenchClass.class, false, "getH"); final TimeInterval timer = DateUtil.timer(); timer.start(); @@ -127,7 +127,7 @@ public class ReflectUtilTest { timer.restart(); for (int i = 0; i < 100000000; i++) { - getMethod(TestBenchClass.class, false, "getH"); + getMethodWithReturnTypeCheck(TestBenchClass.class, false, "getH"); } Console.log(timer.interval()); } @@ -150,7 +150,7 @@ public class ReflectUtilTest { private String n; } - public static Method getMethod(Class clazz, boolean ignoreCase, String methodName, Class... paramTypes) throws SecurityException { + public static Method getMethodWithReturnTypeCheck(Class clazz, boolean ignoreCase, String methodName, Class... paramTypes) throws SecurityException { if (null == clazz || StrUtil.isBlank(methodName)) { return null; } @@ -169,4 +169,61 @@ public class ReflectUtilTest { } return res; } + + @Test + public void getMethodsFromClassExtends(){ + // 继承情况下,需解决方法去重问题 + final Method[] methods = ReflectUtil.getMethods(C2.class); + Assert.assertEquals(2, methods.length); + } + + @Test + public void getMethodsFromInterfaceTest(){ + // 对于接口,直接调用Class.getMethods方法获取所有方法,因为接口都是public方法 + // 因此此处得到包括TestInterface1、TestInterface2、TestInterface3中一共4个方法 + final Method[] methods = ReflectUtil.getMethods(TestInterface3.class); + Assert.assertEquals(4, methods.length); + + // 接口里,调用getMethods和getPublicMethods效果相同 + final Method[] publicMethods = ReflectUtil.getPublicMethods(TestInterface3.class); + Assert.assertArrayEquals(methods, publicMethods); + } + + interface TestInterface1{ + void getA(); + void getB(); + + default void getC(){ + + } + } + + interface TestInterface2 extends TestInterface1{ + @Override + void getB(); + } + + interface TestInterface3 extends TestInterface2{ + void get3(); + } + + class C1 implements TestInterface2{ + + @Override + public void getA() { + + } + + @Override + public void getB() { + + } + } + + class C2 extends C1{ + @Override + public void getA() { + + } + } } -- Gitee From 56d490e2ac5ef147acfacafde9a63df4e997669d Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 8 Mar 2022 22:50:58 +0800 Subject: [PATCH 25/57] fix doc --- .../main/java/cn/hutool/core/lang/ObjectId.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/ObjectId.java b/hutool-core/src/main/java/cn/hutool/core/lang/ObjectId.java index e4a8c06fa5..d8c7c69551 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/ObjectId.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/ObjectId.java @@ -22,6 +22,21 @@ import java.util.concurrent.atomic.AtomicInteger; * 4. INC 自增计数器。确保同一秒内产生objectId的唯一性。 * * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
时间戳机器ID进程ID自增计数器
4323
+ * * 参考:http://blog.csdn.net/qxc1281/article/details/54021882 * * @author looly -- Gitee From 8f0f3354e3f78fea471f72d24dd1b28d0ff77245 Mon Sep 17 00:00:00 2001 From: Looly Date: Wed, 9 Mar 2022 00:58:10 +0800 Subject: [PATCH 26/57] add UniqueKeySet --- CHANGELOG.md | 3 +- .../hutool/core/collection/UniqueKeySet.java | 152 ++++++++++++++++++ .../java/cn/hutool/core/util/ReflectUtil.java | 89 +++++++--- .../core/collection/UniqueKeySetTest.java | 34 ++++ .../cn/hutool/core/util/ReflectUtilTest.java | 39 +++-- 5 files changed, 285 insertions(+), 32 deletions(-) create mode 100644 hutool-core/src/main/java/cn/hutool/core/collection/UniqueKeySet.java create mode 100644 hutool-core/src/test/java/cn/hutool/core/collection/UniqueKeySetTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 3fd0cf5e63..dfefa113bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ # 🚀Changelog ------------------------------------------------------------------------------------------------------------- -# 5.7.23 (2022-03-08) +# 5.7.23 (2022-03-09) ### 🐣新特性 * 【http 】 HttpRequest.form采用TableMap方式(issue#I4W427@Gitee) @@ -10,6 +10,7 @@ * 【core 】 FileUtil.extName增加对tar.gz特殊处理(issue#I4W5FS@Gitee) * 【crypto 】 增加XXTEA实现(issue#I4WH2X@Gitee) * 【core 】 增加Table实现(issue#2179@Github) +* 【core 】 增加UniqueKeySet(issue#I4WUWR@Gitee) * ### 🐞Bug修复 * 【core 】 修复ObjectUtil.hasNull传入null返回true的问题(pr#555@Gitee) 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 new file mode 100644 index 0000000000..f4fbcb15c8 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/collection/UniqueKeySet.java @@ -0,0 +1,152 @@ +package cn.hutool.core.collection; + +import cn.hutool.core.map.MapBuilder; +import cn.hutool.core.util.ObjectUtil; + +import java.io.Serializable; +import java.util.AbstractSet; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.function.Function; + +/** + * 唯一键的Set
+ * 通过自定义唯一键,通过{@link #uniqueGenerator}生成节点对象对应的键作为Map的key,确定唯一
+ * 此Set与HashSet不同的是,HashSet依赖于{@link Object#equals(Object)}确定唯一
+ * 但是很多时候我们无法对对象进行修改,此时在外部定义一个唯一规则,即可完成去重。 + *
+ * {@code Set set = new UniqueKeySet<>(UniqueTestBean::getId);}
+ * 
+ * + * @param 唯一键类型 + * @param 值对象 + * @author looly + * @since 5.7.23 + */ +public class UniqueKeySet extends AbstractSet implements Serializable { + private static final long serialVersionUID = 1L; + + private Map map; + private final Function uniqueGenerator; + + //region 构造 + + /** + * 构造 + * + * @param uniqueGenerator 唯一键生成规则函数,用于生成对象对应的唯一键 + */ + public UniqueKeySet(Function uniqueGenerator) { + this(false, uniqueGenerator); + } + + /** + * 构造 + * + * @param isLinked 是否保持加入顺序 + * @param uniqueGenerator 唯一键生成规则函数,用于生成对象对应的唯一键 + */ + public UniqueKeySet(boolean isLinked, Function uniqueGenerator) { + this(MapBuilder.create(isLinked), uniqueGenerator); + } + + /** + * 构造 + * + * @param initialCapacity 初始容量 + * @param loadFactor 增长因子 + * @param uniqueGenerator 唯一键生成规则函数,用于生成对象对应的唯一键 + */ + public UniqueKeySet(int initialCapacity, float loadFactor, Function uniqueGenerator) { + this(MapBuilder.create(new HashMap<>(initialCapacity, loadFactor)), uniqueGenerator); + } + + /** + * 构造 + * + * @param builder 初始Map,定义了Map类型 + * @param uniqueGenerator 唯一键生成规则函数,用于生成对象对应的唯一键 + */ + public UniqueKeySet(MapBuilder builder, Function uniqueGenerator) { + this.map = builder.build(); + this.uniqueGenerator = uniqueGenerator; + } + //endregion + + @Override + public Iterator iterator() { + return map.values().iterator(); + } + + @Override + public int size() { + return map.size(); + } + + @Override + public boolean isEmpty() { + return map.isEmpty(); + } + + @Override + public boolean contains(Object o) { + //noinspection unchecked + return map.containsKey(this.uniqueGenerator.apply((V) o)); + } + + @Override + public boolean add(V v) { + return null == map.put(this.uniqueGenerator.apply(v), v); + } + + /** + * 加入值,如果值已经存在,则忽略之 + * + * @param v 值 + * @return 是否成功加入 + */ + public boolean addIfAbsent(V v) { + return null == map.putIfAbsent(this.uniqueGenerator.apply(v), v); + } + + /** + * 加入集合中所有的值,如果值已经存在,则忽略之 + * + * @param c 集合 + * @return 是否有一个或多个被加入成功 + */ + public boolean addAllIfAbsent(Collection c) { + boolean modified = false; + for (V v : c) + if (addIfAbsent(v)) { + modified = true; + } + return modified; + } + + @Override + public boolean remove(Object o) { + //noinspection unchecked + return null != map.remove(this.uniqueGenerator.apply((V) o)); + } + + @Override + public void clear() { + map.clear(); + } + + @Override + @SuppressWarnings("unchecked") + public UniqueKeySet clone() { + try { + UniqueKeySet newSet = (UniqueKeySet) super.clone(); + newSet.map = ObjectUtil.clone(this.map); + return newSet; + } catch (CloneNotSupportedException e) { + throw new InternalError(e); + } + } + +} 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 4e4a78faf8..7b746bc6ec 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 @@ -3,6 +3,7 @@ package cn.hutool.core.util; import cn.hutool.core.annotation.Alias; import cn.hutool.core.bean.NullWrapperBean; import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.collection.UniqueKeySet; import cn.hutool.core.convert.Convert; import cn.hutool.core.exceptions.UtilException; import cn.hutool.core.lang.Assert; @@ -17,6 +18,7 @@ import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.AbstractMap; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -347,11 +349,12 @@ public class ReflectUtil { /** * 是否为父类引用字段
* 当字段所在类是对象子类时(对象中定义的非static的class),会自动生成一个以"this$0"为名称的字段,指向父类对象 + * * @param field 字段 * @return 是否为父类引用字段 * @since 5.7.20 */ - public static boolean isOuterClassField(Field field){ + public static boolean isOuterClassField(Field field) { return "this$0".equals(field.getName()); } @@ -648,40 +651,47 @@ public class ReflectUtil { */ public static Method[] getMethods(Class beanClass) throws SecurityException { Assert.notNull(beanClass); - return METHODS_CACHE.get(beanClass, () -> getMethodsDirectly(beanClass, true)); + return METHODS_CACHE.get(beanClass, + () -> getMethodsDirectly(beanClass, true, true)); } /** * 获得一个类中所有方法列表,直接反射获取,无缓存
- * 接口获取方法和默认方法 + * 接口获取方法和默认方法,获取的方法包括: + *
    + *
  • 本类中的所有方法(包括static方法)
  • + *
  • 父类中的所有方法(包括static方法)
  • + *
  • Object中(包括static方法)
  • + *
* - * @param beanClass 类或接口 - * @param withSupers 是否包括父类或接口的方法列表 + * @param beanClass 类或接口 + * @param withSupers 是否包括父类或接口的方法列表 + * @param withMethodFromObject 是否包括Object中的方法 * @return 方法列表 * @throws SecurityException 安全检查异常 */ - public static Method[] getMethodsDirectly(Class beanClass, boolean withSupers) throws SecurityException { + public static Method[] getMethodsDirectly(Class beanClass, boolean withSupers, boolean withMethodFromObject) throws SecurityException { Assert.notNull(beanClass); - if(beanClass.isInterface()){ + if (beanClass.isInterface()) { // 对于接口,直接调用Class.getMethods方法获取所有方法,因为接口都是public方法 return withSupers ? beanClass.getMethods() : beanClass.getDeclaredMethods(); } - Method[] allMethods = null; + final UniqueKeySet result = new UniqueKeySet<>(true, ReflectUtil::getUniqueKey); Class searchType = beanClass; - Method[] declaredMethods; - while (searchType != null && searchType != Object.class) { - declaredMethods = searchType.getDeclaredMethods(); - if (null == allMethods) { - allMethods = declaredMethods; - } else { - allMethods = ArrayUtil.append(allMethods, declaredMethods); + while (searchType != null) { + if (false == withMethodFromObject && Object.class == searchType) { + break; } + result.addAllIfAbsent(Arrays.asList(searchType.getDeclaredMethods())); + result.addAllIfAbsent(getDefaultMethodsFromInterface(searchType)); + + searchType = (withSupers && false == searchType.isInterface()) ? searchType.getSuperclass() : null; } - return ArrayUtil.append(allMethods); + return result.toArray(new Method[0]); } /** @@ -777,10 +787,10 @@ public class ReflectUtil { String name = method.getName(); // 跳过getClass这个特殊方法 - if("getClass".equals(name)){ + if ("getClass".equals(name)) { return false; } - if(ignoreCase){ + if (ignoreCase) { name = name.toLowerCase(); } switch (parameterCount) { @@ -1044,4 +1054,47 @@ public class ReflectUtil { } return accessibleObject; } + + /** + * 获取方法的唯一键,结构为: + *
+	 *     返回类型#方法名:参数1类型,参数2类型...
+	 * 
+ * + * @param method 方法 + * @return 方法唯一键 + */ + private static String getUniqueKey(Method method) { + final StringBuilder sb = new StringBuilder(); + sb.append(method.getReturnType().getName()).append('#'); + sb.append(method.getName()); + Class[] parameters = method.getParameterTypes(); + for (int i = 0; i < parameters.length; i++) { + if (i == 0) { + sb.append(':'); + } else { + sb.append(','); + } + sb.append(parameters[i].getName()); + } + return sb.toString(); + } + + /** + * 获取类对应接口中的非抽象方法(default方法) + * + * @param clazz 类 + * @return 方法列表 + */ + private static List getDefaultMethodsFromInterface(Class clazz) { + List result = new ArrayList<>(); + for (Class ifc : clazz.getInterfaces()) { + for (Method m : ifc.getMethods()) { + if (false == ModifierUtil.isAbstract(m)) { + result.add(m); + } + } + } + return result; + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/collection/UniqueKeySetTest.java b/hutool-core/src/test/java/cn/hutool/core/collection/UniqueKeySetTest.java new file mode 100644 index 0000000000..9dd45058fc --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/collection/UniqueKeySetTest.java @@ -0,0 +1,34 @@ +package cn.hutool.core.collection; + +import cn.hutool.core.lang.Console; +import lombok.AllArgsConstructor; +import lombok.Data; +import org.junit.Assert; +import org.junit.Test; + +import java.util.Set; + +public class UniqueKeySetTest { + + @Test + public void addTest(){ + Set set = new UniqueKeySet<>(UniqueTestBean::getId); + set.add(new UniqueTestBean("id1", "张三", "地球")); + set.add(new UniqueTestBean("id2", "李四", "火星")); + // id重复,替换之前的元素 + set.add(new UniqueTestBean("id2", "王五", "木星")); + + // 后两个ID重复 + Assert.assertEquals(2, set.size()); + + set.forEach(Console::log); + } + + @Data + @AllArgsConstructor + static class UniqueTestBean{ + private String id; + private String name; + private String address; + } +} 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 bb9dca5f3b..6affbabb61 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 @@ -23,7 +23,7 @@ public class ReflectUtilTest { @Test public void getMethodsTest() { Method[] methods = ReflectUtil.getMethods(ExamInfoDict.class); - Assert.assertEquals(22, methods.length); + Assert.assertEquals(20, methods.length); //过滤器测试 methods = ReflectUtil.getMethods(ExamInfoDict.class, t -> Integer.class.equals(t.getReturnType())); @@ -35,7 +35,7 @@ public class ReflectUtilTest { //null过滤器测试 methods = ReflectUtil.getMethods(ExamInfoDict.class, null); - Assert.assertEquals(22, methods.length); + Assert.assertEquals(20, methods.length); final Method method2 = methods[0]; Assert.assertNotNull(method2); } @@ -114,7 +114,7 @@ public class ReflectUtilTest { @Test @Ignore - public void getMethodBenchTest(){ + public void getMethodBenchTest() { // 预热 getMethodWithReturnTypeCheck(TestBenchClass.class, false, "getH"); @@ -171,14 +171,26 @@ public class ReflectUtilTest { } @Test - public void getMethodsFromClassExtends(){ + public void getMethodsFromClassExtends() { // 继承情况下,需解决方法去重问题 - final Method[] methods = ReflectUtil.getMethods(C2.class); - Assert.assertEquals(2, methods.length); + Method[] methods = ReflectUtil.getMethods(C2.class); + Assert.assertEquals(15, methods.length); + + // 排除Object中的方法 + // 3个方法包括类 + methods = ReflectUtil.getMethodsDirectly(C2.class, true, false); + Assert.assertEquals(3, methods.length); + + // getA属于本类 + Assert.assertEquals("public void cn.hutool.core.util.ReflectUtilTest$C2.getA()", methods[0].toString()); + // getB属于父类 + Assert.assertEquals("public void cn.hutool.core.util.ReflectUtilTest$C1.getB()", methods[1].toString()); + // getC属于接口中的默认方法 + Assert.assertEquals("public default void cn.hutool.core.util.ReflectUtilTest$TestInterface1.getC()", methods[2].toString()); } @Test - public void getMethodsFromInterfaceTest(){ + public void getMethodsFromInterfaceTest() { // 对于接口,直接调用Class.getMethods方法获取所有方法,因为接口都是public方法 // 因此此处得到包括TestInterface1、TestInterface2、TestInterface3中一共4个方法 final Method[] methods = ReflectUtil.getMethods(TestInterface3.class); @@ -189,25 +201,26 @@ public class ReflectUtilTest { Assert.assertArrayEquals(methods, publicMethods); } - interface TestInterface1{ + interface TestInterface1 { void getA(); + void getB(); - default void getC(){ + default void getC() { } } - interface TestInterface2 extends TestInterface1{ + interface TestInterface2 extends TestInterface1 { @Override void getB(); } - interface TestInterface3 extends TestInterface2{ + interface TestInterface3 extends TestInterface2 { void get3(); } - class C1 implements TestInterface2{ + class C1 implements TestInterface2 { @Override public void getA() { @@ -220,7 +233,7 @@ public class ReflectUtilTest { } } - class C2 extends C1{ + class C2 extends C1 { @Override public void getA() { -- Gitee From 1aeb56564f38826d575e63e4e52298b9784c484e Mon Sep 17 00:00:00 2001 From: jiazhengquan <2466896229@qq.com> Date: Wed, 9 Mar 2022 11:25:52 +0800 Subject: [PATCH 27/57] =?UTF-8?q?=E9=92=88=E5=AF=B9issue#I4WUWR=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/cn/hutool/core/util/ReflectUtil.java | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) 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 4f9ded9d2c..dbad1e91de 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 @@ -663,34 +663,34 @@ public class ReflectUtil { Assert.notNull(beanClass); Method[] allMethods = null; - Class[] searchInterfaces = null, parentInterfaces = null; Class searchType = beanClass; + Class[] tempInterfaceType, interfaceType; Method[] declaredMethods; - while (searchType != null || searchInterfaces != null) { - if (searchType != null) { + if (searchType.isInterface()) { + allMethods = searchType.getDeclaredMethods(); + interfaceType = searchType.getInterfaces(); + while (ArrayUtil.isNotEmpty(interfaceType)) { + tempInterfaceType = interfaceType; + for (int i = 0; i < tempInterfaceType.length; i++) { + Class temp = tempInterfaceType[i]; + allMethods = ArrayUtil.append(allMethods, temp.getDeclaredMethods()); + if (i == 0 || ArrayUtil.isEmpty(interfaceType)) { + interfaceType = temp.getInterfaces(); + } else { + interfaceType = ArrayUtil.append(interfaceType, temp.getInterfaces()); + } + } + } + } else { + while (searchType != null) { declaredMethods = searchType.getDeclaredMethods(); if (null == allMethods) { allMethods = declaredMethods; } else { allMethods = ArrayUtil.append(allMethods, declaredMethods); } - Class[] interfaces = searchType.getInterfaces(); - for (Class element : interfaces) { - allMethods = ArrayUtil.append(allMethods, element.getDeclaredMethods()); - parentInterfaces = ArrayUtil.addAll(element.getInterfaces()); - } - searchInterfaces = parentInterfaces; - Class[] classes = searchInterfaces.length == 0 ? null : searchInterfaces; - searchInterfaces = withSuperClassMethods ? classes : null; searchType = withSuperClassMethods ? searchType.getSuperclass() : null; } - if (searchInterfaces != null) { - for (Class searchInterface : searchInterfaces) { - allMethods = ArrayUtil.append(allMethods, searchInterface.getDeclaredMethods()); - parentInterfaces = ArrayUtil.addAll(searchInterface.getInterfaces()); - } - searchInterfaces = parentInterfaces.length == 0 ? null : parentInterfaces; - } } return allMethods; } -- Gitee From b8bfce490baabb963d5f4ebc076593e4fef26321 Mon Sep 17 00:00:00 2001 From: Looly Date: Wed, 9 Mar 2022 12:05:09 +0800 Subject: [PATCH 28/57] fix comment --- .../src/main/java/cn/hutool/crypto/symmetric/XXTEA.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/symmetric/XXTEA.java b/hutool-crypto/src/main/java/cn/hutool/crypto/symmetric/XXTEA.java index 4f948cd4f9..d413dce9d7 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/symmetric/XXTEA.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/symmetric/XXTEA.java @@ -10,7 +10,7 @@ import java.io.Serializable; * XXTEA(Corrected Block Tiny Encryption Algorithm)算法实现
* 来自:https://github.com/xxtea/xxtea-java * - * @author Ma Bingyao + * @author Ma Bingyao */ public class XXTEA implements SymmetricEncryptor, SymmetricDecryptor, Serializable { private static final long serialVersionUID = 1L; -- Gitee From 10d5cabf976bba2c4eba3b66d6dbda080baffd6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9F=A9=E5=B8=85?= Date: Wed, 9 Mar 2022 15:48:31 +0800 Subject: [PATCH 29/57] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=A4=87=E7=94=A8?= =?UTF-8?q?=E6=96=87=E6=A1=A3=E5=9C=B0=E5=9D=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README-EN.md | 2 ++ README.md | 2 ++ 2 files changed, 4 insertions(+) diff --git a/README-EN.md b/README-EN.md index 05475e644e..7456a08f77 100644 --- a/README-EN.md +++ b/README-EN.md @@ -111,6 +111,8 @@ Each module can be introduced individually, or all modules can be introduced by [📘Chinese documentation](https://www.hutool.cn/docs/) +[📘Chinese back-up documentation](https://plus.hutool.cn/docs/#/) + [📙API](https://apidoc.gitee.com/dromara/hutool/) [🎬Video](https://www.bilibili.com/video/BV1bQ4y1M7d9?p=2) diff --git a/README.md b/README.md index f8834f52a6..409e4e22d9 100644 --- a/README.md +++ b/README.md @@ -107,6 +107,8 @@ Hutool的存在就是为了减少代码搜索成本,避免网络上参差不 [📘中文文档](https://www.hutool.cn/docs/) +[📘中文备用文档](https://plus.hutool.cn/docs/#/) + [📙参考API](https://apidoc.gitee.com/dromara/hutool/) [🎬视频介绍](https://www.bilibili.com/video/BV1bQ4y1M7d9?p=2) -- Gitee From aec10a7714a72ee038a9fa497e4e3726cfff1fc4 Mon Sep 17 00:00:00 2001 From: gxz <514190950@qq.com> Date: Wed, 9 Mar 2022 17:03:17 +0800 Subject: [PATCH 30/57] =?UTF-8?q?=E4=BF=AE=E6=94=B9Bug:=E5=BD=93FileCopier?= =?UTF-8?q?=E7=9A=84=E7=9B=AE=E6=A0=87=E6=96=87=E4=BB=B6=E6=98=AF=E7=9B=B8?= =?UTF-8?q?=E5=AF=B9=E8=B7=AF=E5=BE=84=E7=9A=84=E6=97=B6=E5=80=99=EF=BC=8C?= =?UTF-8?q?=E5=A4=8D=E5=88=B6=E4=BC=9A=E5=87=BA=E7=8E=B0=E5=BC=82=E5=B8=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cn/hutool/core/io/file/FileCopier.java | 2 +- .../cn/hutool/core/io/FileCopierTest.java | 20 ++++++++++++++----- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/io/file/FileCopier.java b/hutool-core/src/main/java/cn/hutool/core/io/file/FileCopier.java index fbdc4938f8..6f133f2e37 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/file/FileCopier.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/file/FileCopier.java @@ -268,7 +268,7 @@ public class FileCopier extends SrcToDestCopier{ }else { //路径不存在则创建父目录 //noinspection ResultOfMethodCallIgnored - dest.getParentFile().mkdirs(); + dest.getAbsoluteFile().getParentFile().mkdirs(); } final ArrayList optionList = new ArrayList<>(2); diff --git a/hutool-core/src/test/java/cn/hutool/core/io/FileCopierTest.java b/hutool-core/src/test/java/cn/hutool/core/io/FileCopierTest.java index 80c50288de..97f7026388 100644 --- a/hutool-core/src/test/java/cn/hutool/core/io/FileCopierTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/io/FileCopierTest.java @@ -5,41 +5,51 @@ import org.junit.Test; import cn.hutool.core.io.file.FileCopier; +import java.io.File; + /** * 文件拷贝单元测试 * @author Looly * */ public class FileCopierTest { - + @Test @Ignore public void dirCopyTest() { FileCopier copier = FileCopier.create("D:\\Java", "e:/eclipse/eclipse2.zip"); copier.copy(); } - + @Test @Ignore public void dirCopyTest2() { //测试带.的文件夹复制 FileCopier copier = FileCopier.create("D:\\workspace\\java\\.metadata", "D:\\workspace\\java\\.metadata\\temp"); copier.copy(); - + FileUtil.copy("D:\\workspace\\java\\looly\\hutool\\.git", "D:\\workspace\\java\\temp", true); } - + @Test(expected = IORuntimeException.class) public void dirCopySubTest() { //测试父目录复制到子目录报错 FileCopier copier = FileCopier.create("D:\\workspace\\java\\.metadata", "D:\\workspace\\java\\.metadata\\temp"); copier.copy(); } - + @Test @Ignore public void copyFileToDirTest() { FileCopier copier = FileCopier.create("d:/GReen_Soft/XshellXftpPortable.zip", "c:/hp/"); copier.copy(); } + + @Test + @Ignore + public void copyFileByRelativePath(){ + // 当复制的目标文件位置是相对路径的时候可以通过 + FileCopier copier = FileCopier.create(new File("pom.xml"),new File("aaa.txt")); + copier.copy(); + } } -- Gitee From 11bf574aaed4aaaca7a448729dfdfac4dfdfb3fc Mon Sep 17 00:00:00 2001 From: VampireAchao Date: Wed, 9 Mar 2022 22:36:23 +0800 Subject: [PATCH 31/57] =?UTF-8?q?=E5=8D=87=E7=BA=A7MongoDB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hutool-db/pom.xml | 12 +---- .../cn/hutool/db/nosql/mongo/MongoDS.java | 44 +++++++++++-------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml index 2d586bf0bc..c80ea176d8 100644 --- a/hutool-db/pom.xml +++ b/hutool-db/pom.xml @@ -23,7 +23,7 @@ 10.0.14 1.2.8 2.4.13 - 3.12.10 + 4.5.0 3.36.0.3 2.5.2 @@ -97,20 +97,12 @@ true - - org.mongodb - mongo-java-driver - ${mongo.version} - true - - redis.clients diff --git a/hutool-db/src/main/java/cn/hutool/db/nosql/mongo/MongoDS.java b/hutool-db/src/main/java/cn/hutool/db/nosql/mongo/MongoDS.java index 8158f0d33a..1c7a4c70f7 100644 --- a/hutool-db/src/main/java/cn/hutool/db/nosql/mongo/MongoDS.java +++ b/hutool-db/src/main/java/cn/hutool/db/nosql/mongo/MongoDS.java @@ -6,24 +6,28 @@ import cn.hutool.core.util.StrUtil; import cn.hutool.db.DbRuntimeException; import cn.hutool.log.Log; import cn.hutool.setting.Setting; -import com.mongodb.MongoClient; -import com.mongodb.MongoClientOptions; -import com.mongodb.MongoClientOptions.Builder; +import com.mongodb.MongoClientSettings; import com.mongodb.MongoCredential; +import com.mongodb.MongoDriverInformation; import com.mongodb.ServerAddress; +import com.mongodb.client.MongoClient; import com.mongodb.client.MongoCollection; import com.mongodb.client.MongoDatabase; +import com.mongodb.client.internal.MongoClientImpl; +import com.mongodb.connection.ConnectionPoolSettings; +import com.mongodb.connection.SocketSettings; import org.bson.Document; import java.io.Closeable; import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import java.util.concurrent.TimeUnit; /** * MongoDB工具类 * * @author xiaoleilu - * */ public class MongoDS implements Closeable { private final static Log log = Log.get(); @@ -146,11 +150,11 @@ public class MongoDS implements Closeable { final MongoCredential credentail = createCredentail(group); try { - if (null == credentail) { - mongo = new MongoClient(serverAddress, buildMongoClientOptions(group)); - } else { - mongo = new MongoClient(serverAddress, credentail, buildMongoClientOptions(group)); + MongoClientSettings.Builder clusterSettingsBuilder = MongoClientSettings.builder().applyToClusterSettings(b -> b.hosts(Collections.singletonList(serverAddress))); + if (null != credentail) { + clusterSettingsBuilder.credential(credentail); } + mongo = new MongoClientImpl(clusterSettingsBuilder.build(), MongoDriverInformation.builder().build()); } catch (Exception e) { throw new DbRuntimeException(StrUtil.format("Init MongoDB pool with connection to [{}] error!", serverAddress), e); } @@ -192,11 +196,11 @@ public class MongoDS implements Closeable { final MongoCredential credentail = createCredentail(StrUtil.EMPTY); try { - if (null == credentail) { - mongo = new MongoClient(addrList, buildMongoClientOptions(StrUtil.EMPTY)); - } else { - mongo = new MongoClient(addrList, credentail, buildMongoClientOptions(StrUtil.EMPTY)); + MongoClientSettings.Builder clusterSettingsBuilder = MongoClientSettings.builder().applyToClusterSettings(b -> b.hosts(addrList)); + if (null != credentail) { + clusterSettingsBuilder.credential(credentail); } + mongo = new MongoClientImpl(clusterSettingsBuilder.build(), MongoDriverInformation.builder().build()); } catch (Exception e) { log.error(e, "Init MongoDB connection error!"); return; @@ -248,6 +252,7 @@ public class MongoDS implements Closeable { } // --------------------------------------------------------------------------- Private method start + /** * 创建ServerAddress对象,会读取配置文件中的相关信息 * @@ -322,8 +327,8 @@ public class MongoDS implements Closeable { * @param group 分组,当分组对应的选项不存在时会读取根选项,如果也不存在使用默认值 * @return MongoClientOptions */ - private MongoClientOptions buildMongoClientOptions(String group) { - return buildMongoClientOptions(MongoClientOptions.builder(), group).build(); + private MongoClientSettings buildMongoClientOptions(String group) { + return buildMongoClientOptions(MongoClientSettings.builder(), group).build(); } /** @@ -332,7 +337,7 @@ public class MongoDS implements Closeable { * @param group 分组,当分组对应的选项不存在时会读取根选项,如果也不存在使用默认值 * @return Builder */ - private Builder buildMongoClientOptions(Builder builder, String group) { + private MongoClientSettings.Builder buildMongoClientOptions(MongoClientSettings.Builder builder, String group) { if (setting == null) { return builder; } @@ -348,8 +353,9 @@ public class MongoDS implements Closeable { if (StrUtil.isBlank(group) == false && connectionsPerHost == null) { connectionsPerHost = setting.getInt("connectionsPerHost"); } + ConnectionPoolSettings.Builder connectionPoolSettingsBuilder = ConnectionPoolSettings.builder(); if (connectionsPerHost != null) { - builder.connectionsPerHost(connectionsPerHost); + connectionPoolSettingsBuilder.maxConnecting(connectionsPerHost); log.debug("MongoDB connectionsPerHost: {}", connectionsPerHost); } @@ -359,9 +365,10 @@ public class MongoDS implements Closeable { setting.getInt("connectTimeout"); } if (connectTimeout != null) { - builder.connectTimeout(connectTimeout); + connectionPoolSettingsBuilder.maxWaitTime(connectTimeout, TimeUnit.MILLISECONDS); log.debug("MongoDB connectTimeout: {}", connectTimeout); } + builder.applyToConnectionPoolSettings(b -> b.applySettings(connectionPoolSettingsBuilder.build())); // 套接字超时时间;该值会被传递给Socket.setSoTimeout(int)。默以为0(无穷) --int Integer socketTimeout = setting.getInt(group + "socketTimeout"); @@ -369,7 +376,8 @@ public class MongoDS implements Closeable { setting.getInt("socketTimeout"); } if (socketTimeout != null) { - builder.socketTimeout(socketTimeout); + SocketSettings socketSettings = SocketSettings.builder().connectTimeout(socketTimeout, TimeUnit.MILLISECONDS).build(); + builder.applyToSocketSettings(b -> b.applySettings(socketSettings)); log.debug("MongoDB socketTimeout: {}", socketTimeout); } -- Gitee From 206de3bad0493dbe699065aa6ce2855bfa107368 Mon Sep 17 00:00:00 2001 From: MCP Date: Thu, 10 Mar 2022 15:45:43 +0800 Subject: [PATCH 32/57] =?UTF-8?q?=E9=98=BF=E6=8B=89=E4=BC=AF=E6=95=B0?= =?UTF-8?q?=E5=AD=97=E8=BD=AC=E6=8D=A2=E6=88=90=E4=B8=AD=E6=96=87=E5=AF=B9?= =?UTF-8?q?=E5=8F=91=E7=A5=A8=E7=A5=A8=E9=9D=A2=E9=87=91=E9=A2=9D=E8=BD=AC?= =?UTF-8?q?=E6=8D=A2=E7=9A=84=E6=89=A9=E5=B1=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/convert/NumberChineseFormatter.java | 107 +++++++++++------- 1 file changed, 64 insertions(+), 43 deletions(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/convert/NumberChineseFormatter.java b/hutool-core/src/main/java/cn/hutool/core/convert/NumberChineseFormatter.java index 2a315d38dc..5f8a7b52fc 100644 --- a/hutool-core/src/main/java/cn/hutool/core/convert/NumberChineseFormatter.java +++ b/hutool-core/src/main/java/cn/hutool/core/convert/NumberChineseFormatter.java @@ -40,7 +40,6 @@ public class NumberChineseFormatter { new ChineseUnit('亿', 1_0000_0000, true), }; - /** * 阿拉伯数字转换成中文,小数点后四舍五入保留两位. 使用于整数、小数的转换. * @@ -53,15 +52,25 @@ public class NumberChineseFormatter { } /** - * 阿拉伯数字转换成中文,小数点后四舍五入保留两位. 使用于整数、小数的转换. + * 阿拉伯数字转换成中文. + * + *

主要是对发票票面金额转换的扩展 + *

如:-12.32 + *

发票票面转换为:(负数)壹拾贰圆叁角贰分 + *

而非:负壹拾贰元叁角贰分 + *

共两点不同:1、(负数) 而非 负;2、圆 而非 元 + * 2022/3/9 * * @param amount 数字 * @param isUseTraditional 是否使用繁体 - * @param isMoneyMode 是否为金额模式 - * @return 中文 + * @param isMoneyMode 是否金额模式 + * @param negativeName 负号转换名称 如:负、(负数) + * @param unitName 单位名称 如:元、圆 + * @return java.lang.String + * @author machuanpeng */ - public static String format(double amount, boolean isUseTraditional, boolean isMoneyMode) { - if(0 == amount){ + public static String format(double amount, boolean isUseTraditional, boolean isMoneyMode, String negativeName, String unitName) { + if (0 == amount) { return "零"; } Assert.checkBetween(amount, -99_9999_9999_9999.99, 99_9999_9999_9999.99, @@ -71,7 +80,7 @@ public class NumberChineseFormatter { // 负数 if (amount < 0) { - chineseStr.append("负"); + chineseStr.append(StrUtil.isNullOrUndefined(negativeName) ? "负" : negativeName); amount = -amount; } @@ -82,44 +91,44 @@ public class NumberChineseFormatter { yuan = yuan / 10; // 元 - if(false == isMoneyMode || 0 != yuan){ + if (false == isMoneyMode || 0 != yuan) { // 金额模式下,无需“零元” chineseStr.append(longToChinese(yuan, isUseTraditional)); - if(isMoneyMode){ - chineseStr.append("元"); + if (isMoneyMode) { + chineseStr.append(StrUtil.isNullOrUndefined(unitName) ? "元" : unitName); } } - if(0 == jiao && 0 == fen){ + if (0 == jiao && 0 == fen) { //无小数部分的金额结尾 - if(isMoneyMode){ + if (isMoneyMode) { chineseStr.append("整"); } return chineseStr.toString(); } // 小数部分 - if(false == isMoneyMode){ + if (false == isMoneyMode) { chineseStr.append("点"); } // 角 - if(0 == yuan && 0 == jiao){ + if (0 == yuan && 0 == jiao) { // 元和角都为0时,只有非金额模式下补“零” - if(false == isMoneyMode){ + if (false == isMoneyMode) { chineseStr.append("零"); } - }else{ + } else { chineseStr.append(numberToChinese(jiao, isUseTraditional)); - if(isMoneyMode && 0 != jiao){ + if (isMoneyMode && 0 != jiao) { chineseStr.append("角"); } } // 分 - if(0 != fen){ + if (0 != fen) { chineseStr.append(numberToChinese(fen, isUseTraditional)); - if(isMoneyMode){ + if (isMoneyMode) { chineseStr.append("分"); } } @@ -127,16 +136,28 @@ public class NumberChineseFormatter { return chineseStr.toString(); } + /** + * 阿拉伯数字转换成中文,小数点后四舍五入保留两位. 使用于整数、小数的转换. + * + * @param amount 数字 + * @param isUseTraditional 是否使用繁体 + * @param isMoneyMode 是否为金额模式 + * @return 中文 + */ + public static String format(double amount, boolean isUseTraditional, boolean isMoneyMode) { + return format(amount, isUseTraditional, isMoneyMode, "负", "元"); + } + /** * 阿拉伯数字(支持正负整数)转换成中文 * - * @param amount 数字 + * @param amount 数字 * @param isUseTraditional 是否使用繁体 * @return 中文 * @since 5.7.17 */ - public static String format(long amount, boolean isUseTraditional){ - if(0 == amount){ + public static String format(long amount, boolean isUseTraditional) { + if (0 == amount) { return "零"; } Assert.checkBetween(amount, -99_9999_9999_9999.99, 99_9999_9999_9999.99, @@ -179,16 +200,16 @@ public class NumberChineseFormatter { * 格式化-999~999之间的数字
* 这个方法显示10~19以下的数字时使用"十一"而非"一十一"。 * - * @param amount 数字 + * @param amount 数字 * @param isUseTraditional 是否使用繁体 * @return 中文 * @since 5.7.17 */ - public static String formatThousand(int amount, boolean isUseTraditional){ + public static String formatThousand(int amount, boolean isUseTraditional) { Assert.checkBetween(amount, -999, 999, "Number support only: (-999 ~ 999)!"); final String chinese = thousandToChinese(amount, isUseTraditional); - if(amount < 20 && amount >= 10){ + if (amount < 20 && amount >= 10) { // "十一"而非"一十一" return chinese.substring(1); } @@ -218,7 +239,7 @@ public class NumberChineseFormatter { * @return 中文 */ private static String longToChinese(long amount, boolean isUseTraditional) { - if(0 == amount){ + if (0 == amount) { return "零"; } @@ -235,11 +256,11 @@ public class NumberChineseFormatter { // 千 partValue = parts[0]; - if(partValue > 0){ + if (partValue > 0) { partChinese = thousandToChinese(partValue, isUseTraditional); chineseStr.insert(0, partChinese); - if(partValue < 1000){ + if (partValue < 1000) { // 和万位之间空0,则补零,如一万零三百 addPreZero(chineseStr); } @@ -247,26 +268,26 @@ public class NumberChineseFormatter { // 万 partValue = parts[1]; - if(partValue > 0){ - if((partValue % 10 == 0 && parts[0] > 0)){ + if (partValue > 0) { + if ((partValue % 10 == 0 && parts[0] > 0)) { // 如果"万"的个位是0,则补零,如十万零八千 addPreZero(chineseStr); } partChinese = thousandToChinese(partValue, isUseTraditional); chineseStr.insert(0, partChinese + "万"); - if(partValue < 1000){ + if (partValue < 1000) { // 和亿位之间空0,则补零,如一亿零三百万 addPreZero(chineseStr); } - } else{ + } else { addPreZero(chineseStr); } // 亿 partValue = parts[2]; - if(partValue > 0){ - if((partValue % 10 == 0 && parts[1] > 0)){ + if (partValue > 0) { + if ((partValue % 10 == 0 && parts[1] > 0)) { // 如果"万"的个位是0,则补零,如十万零八千 addPreZero(chineseStr); } @@ -274,25 +295,25 @@ public class NumberChineseFormatter { partChinese = thousandToChinese(partValue, isUseTraditional); chineseStr.insert(0, partChinese + "亿"); - if(partValue < 1000){ + if (partValue < 1000) { // 和万亿位之间空0,则补零,如一万亿零三百亿 addPreZero(chineseStr); } - } else{ + } else { addPreZero(chineseStr); } // 万亿 partValue = parts[3]; - if(partValue > 0){ - if(parts[2] == 0){ + if (partValue > 0) { + if (parts[2] == 0) { chineseStr.insert(0, "亿"); } partChinese = thousandToChinese(partValue, isUseTraditional); chineseStr.insert(0, partChinese + "万"); } - if(StrUtil.isNotEmpty(chineseStr) && '零' == chineseStr.charAt(0)){ + if (StrUtil.isNotEmpty(chineseStr) && '零' == chineseStr.charAt(0)) { return chineseStr.substring(1); } @@ -386,7 +407,7 @@ public class NumberChineseFormatter { } else { // 非节单位,和单位前的单数字组合为值 int unitNumber = number; - if(0 == number && 0 == i){ + if (0 == number && 0 == i) { // issue#1726,对于单位开头的数组,默认赋予1 // 十二 -> 一十二 // 百二 -> 一百二 @@ -502,12 +523,12 @@ public class NumberChineseFormatter { } } - private static void addPreZero(StringBuilder chineseStr){ - if(StrUtil.isEmpty(chineseStr)){ + private static void addPreZero(StringBuilder chineseStr) { + if (StrUtil.isEmpty(chineseStr)) { return; } final char c = chineseStr.charAt(0); - if('零' != c){ + if ('零' != c) { chineseStr.insert(0, '零'); } } -- Gitee From 04ae795ce651589223a85e89246bc9bd08c82504 Mon Sep 17 00:00:00 2001 From: jiazhengquan <2466896229@qq.com> Date: Fri, 11 Mar 2022 09:50:37 +0800 Subject: [PATCH 33/57] =?UTF-8?q?=E9=92=88=E5=AF=B9issue#I4X9TT=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hutool-core/src/main/java/cn/hutool/core/text/NamingCase.java | 2 +- hutool-core/src/test/java/cn/hutool/core/util/StrUtilTest.java | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/text/NamingCase.java b/hutool-core/src/main/java/cn/hutool/core/text/NamingCase.java index a35d78f1ee..a5c7a5d521 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/NamingCase.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/NamingCase.java @@ -97,7 +97,7 @@ public class NamingCase { // 后一个为大写,按照专有名词对待,如aBC -> a_BC } else { //前一个为大写 - if (null == nextChar || Character.isLowerCase(nextChar)) { + if (null != nextChar && Character.isLowerCase(nextChar)) { // 普通首字母大写,如ABcc -> A_bcc sb.append(symbol); c = Character.toLowerCase(c); diff --git a/hutool-core/src/test/java/cn/hutool/core/util/StrUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/StrUtilTest.java index ee1e1da509..2dcef2850a 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/StrUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/StrUtilTest.java @@ -422,6 +422,7 @@ public class StrUtilTest { .set("H2", "H2") .set("H#case", "H#case") .set("PNLabel", "PN_label") + .set("DEPT_NAME","DEPT_NAME") .forEach((key, value) -> Assert.assertEquals(value, StrUtil.toUnderlineCase(key))); } -- Gitee From 78f886075eb45b1fdeca88cc0ace0f9aefcc4ff5 Mon Sep 17 00:00:00 2001 From: jiazhengquan <2466896229@qq.com> Date: Fri, 11 Mar 2022 17:51:09 +0800 Subject: [PATCH 34/57] =?UTF-8?q?issue=20#I4XG4L=20=E5=88=9D=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/cn/hutool/core/util/ArrayUtil.java | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) 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 8c948e8a92..01e4ea721b 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 @@ -368,6 +368,43 @@ public class ArrayUtil extends PrimitiveArrayUtil { } } + /** + * 将元素值设置为数组的某个位置,根据元素顺序添加
+ * 当给定的index大于数组长度,则追加 + * + * @param 数组元素类型 + * @param buffer 已有数组 + * @param index 位置,大于长度追加,否则替换 + * @param values 新值 + * @return 新数组或原有数组 + */ + public static T[] replace(T[] buffer, int index, T... values) { + return index == 0 ? values : replaceBy(buffer, index, values); + } + + /** + * 将元素值设置为数组的某个位置,根据元素顺序添加
+ * 当给定的index大于数组长度,则追加 + * + * @param 数组元素类型 + * @param buffer 已有数组 + * @param index 位置,大于长度追加,否则替换 + * @param values 新值 + * @return 新数组或原有数组 + */ + public static T[] replaceBy(T[] buffer, int index, T... values) { + if (index < buffer.length && buffer.length - index - 1 >= values.length) { + for (int i = index; i < values.length; i++) { + Array.set(buffer, index, values[i]); + } + return buffer; + } else { + final T[] result = (T[]) Array.newInstance(buffer.getClass().getComponentType(), buffer.length - index - 1); + System.arraycopy(buffer, 0, result, 0, buffer.length - index - 1); + return append(result, values); + } + } + /** * 将新元素插入到到已有数组中的某个位置
* 添加新元素会生成一个新的数组,不影响原数组
-- Gitee From 55c9b0c37a6f124f3bd97e15f357de84cc2d7a36 Mon Sep 17 00:00:00 2001 From: jcsun <80867809@qq.com> Date: Sun, 13 Mar 2022 18:14:36 +0800 Subject: [PATCH 35/57] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=20Spring=20Task?= =?UTF-8?q?=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../extra/spring/config/SpringCronConfig.java | 31 +++++++++++++++++++ .../main/resources/META-INF/spring.factories | 3 +- 2 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 hutool-extra/src/main/java/cn/hutool/extra/spring/config/SpringCronConfig.java diff --git a/hutool-extra/src/main/java/cn/hutool/extra/spring/config/SpringCronConfig.java b/hutool-extra/src/main/java/cn/hutool/extra/spring/config/SpringCronConfig.java new file mode 100644 index 0000000000..1f31d08320 --- /dev/null +++ b/hutool-extra/src/main/java/cn/hutool/extra/spring/config/SpringCronConfig.java @@ -0,0 +1,31 @@ +package cn.hutool.extra.spring.config; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.TaskScheduler; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; + +/** + * 可自行配置任务线程池, 修改默认参数 + * + * @author JC + * @date 03/13 + */ +@Configuration +@EnableScheduling +public class SpringCronConfig { + @Bean + @ConditionalOnMissingBean(value = TaskScheduler.class) + public TaskScheduler taskScheduler() { + ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); + // 任务线程池初始化 + scheduler.setThreadNamePrefix("TaskScheduler-"); + scheduler.setPoolSize(Runtime.getRuntime().availableProcessors() / 3 + 1); + + // 保证能立刻丢弃运行中的任务 + scheduler.setRemoveOnCancelPolicy(true); + return scheduler; + } +} diff --git a/hutool-extra/src/main/resources/META-INF/spring.factories b/hutool-extra/src/main/resources/META-INF/spring.factories index 7b08ebdf82..06d39a3ecb 100644 --- a/hutool-extra/src/main/resources/META-INF/spring.factories +++ b/hutool-extra/src/main/resources/META-INF/spring.factories @@ -1,3 +1,4 @@ # Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ -cn.hutool.extra.spring.SpringUtil \ No newline at end of file +cn.hutool.extra.spring.SpringUtil,\ +cn.hutool.extra.spring.config.SpringCronConfig -- Gitee From c73ea53f8f504f0d6e46f7d5c308040158bb090d Mon Sep 17 00:00:00 2001 From: jcsun <80867809@qq.com> Date: Sun, 13 Mar 2022 18:30:34 +0800 Subject: [PATCH 36/57] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=20=E5=8A=A8=E6=80=81?= =?UTF-8?q?=E5=AE=9A=E6=97=B6=E4=BB=BB=E5=8A=A1=20=E5=B7=A5=E5=85=B7?= =?UTF-8?q?=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hutool/extra/spring/SpringCronUtil.java | 157 ++++++++++++++++++ .../main/resources/META-INF/spring.factories | 3 +- 2 files changed, 159 insertions(+), 1 deletion(-) create mode 100644 hutool-extra/src/main/java/cn/hutool/extra/spring/SpringCronUtil.java diff --git a/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringCronUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringCronUtil.java new file mode 100644 index 0000000000..01947029a5 --- /dev/null +++ b/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringCronUtil.java @@ -0,0 +1,157 @@ +package cn.hutool.extra.spring; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.IdUtil; +import org.springframework.scheduling.TaskScheduler; +import org.springframework.scheduling.support.CronTrigger; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ScheduledFuture; + +/** + * Spring 动态定时任务封装 + *

    + *
  1. 创建定时任务
  2. + *
  3. 修改定时任务
  4. + *
  5. 取消定时任务
  6. + *
  7. 高级操作
  8. + *
+ * + * @author JC + * @date 03/13 + */ +@Component +public class SpringCronUtil { + /** + * 任务调度器 + */ + private static TaskScheduler taskScheduler; + + /** + * ID 与 Future 绑定 + */ + private static final Map> TASK_FUTURE = MapUtil.newConcurrentHashMap(); + + /** + * ID 与 Runnable 绑定 + */ + private static final Map TASK_RUNNABLE = MapUtil.newConcurrentHashMap(); + + /** + * 加入定时任务 + * + * @param task 任务 + * @param expression 定时任务执行时间的cron表达式 + * @return 定时任务ID + */ + public static String schedule(Runnable task, String expression) { + String id = IdUtil.fastUUID(); + return schedule(id, task, expression); + } + + /** + * 加入定时任务 + * + * @param id 定时任务ID + * @param expression 定时任务执行时间的cron表达式 + * @param task 任务 + * @return 定时任务ID + */ + public static String schedule(Serializable id, Runnable task, String expression) { + ScheduledFuture schedule = taskScheduler.schedule(task, new CronTrigger(expression)); + TASK_FUTURE.put(id, schedule); + TASK_RUNNABLE.put(id, task); + return id.toString(); + } + + /** + * 修改定时任务 + * + * @param id 定时任务ID + * @param expression 定时任务执行时间的cron表达式 + * @return 是否修改成功,{@code false}表示未找到对应ID的任务 + */ + public static boolean update(Serializable id, String expression) { + if (!TASK_FUTURE.containsKey(id)) { + return false; + } + ScheduledFuture future = TASK_FUTURE.get(id); + if (future == null) { + return false; + } + future.cancel(true); + schedule(id, TASK_RUNNABLE.get(id), expression); + return true; + } + + /** + * 移除任务 + * + * @param schedulerId 任务ID + * @return 是否移除成功,{@code false}表示未找到对应ID的任务 + */ + public static boolean cancel(Serializable schedulerId) { + ScheduledFuture future = getScheduledFuture(schedulerId); + if (future == null) { + return false; + } + boolean cancel = future.cancel(false); + if (cancel) { + TASK_FUTURE.remove(schedulerId); + TASK_RUNNABLE.remove(schedulerId); + } + return cancel; + } + + @Resource + public void setTaskScheduler(TaskScheduler taskScheduler) { + SpringCronUtil.taskScheduler = taskScheduler; + } + + /** + * @return 获得Scheduler对象 + */ + public static TaskScheduler getScheduler() { + return taskScheduler; + } + + /** + * 可在项目中 进行细粒度控制 + * + * @return 获得ScheduledFuture对象 + */ + private static ScheduledFuture getScheduledFuture(Serializable id) { + return TASK_FUTURE.get(id); + } + + /** + * 获得当前运行的所有任务 + * + * @return 所有任务 + */ + public static List getAllTask() { + if (CollUtil.isNotEmpty(TASK_FUTURE.keySet())) { + return new ArrayList<>(TASK_FUTURE.keySet()); + } + return new ArrayList<>(); + } + + /** + * 取消所有的任务 + */ + public static void destroy() { + for (ScheduledFuture future : TASK_FUTURE.values()) { + if (future != null) { + future.cancel(true); + } + } + TASK_FUTURE.clear(); + TASK_RUNNABLE.clear(); + } +} diff --git a/hutool-extra/src/main/resources/META-INF/spring.factories b/hutool-extra/src/main/resources/META-INF/spring.factories index 06d39a3ecb..0f0032ef8a 100644 --- a/hutool-extra/src/main/resources/META-INF/spring.factories +++ b/hutool-extra/src/main/resources/META-INF/spring.factories @@ -1,4 +1,5 @@ # Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ cn.hutool.extra.spring.SpringUtil,\ -cn.hutool.extra.spring.config.SpringCronConfig +cn.hutool.extra.spring.config.SpringCronConfig,\ +cn.hutool.extra.spring.SpringCronUtil -- Gitee From 57212bfdbac019b6e3eb050271ca6ed973585580 Mon Sep 17 00:00:00 2001 From: jcsun <80867809@qq.com> Date: Sun, 13 Mar 2022 18:40:13 +0800 Subject: [PATCH 37/57] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=20junit=20=E6=B5=8B?= =?UTF-8?q?=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/cn/hutool/extra/spring/SpringCronUtil.java | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringCronUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringCronUtil.java index 01947029a5..0599a74fce 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringCronUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringCronUtil.java @@ -97,10 +97,10 @@ public class SpringCronUtil { * @return 是否移除成功,{@code false}表示未找到对应ID的任务 */ public static boolean cancel(Serializable schedulerId) { - ScheduledFuture future = getScheduledFuture(schedulerId); - if (future == null) { + if (!TASK_FUTURE.containsKey(schedulerId)) { return false; } + ScheduledFuture future = TASK_FUTURE.get(schedulerId); boolean cancel = future.cancel(false); if (cancel) { TASK_FUTURE.remove(schedulerId); @@ -121,15 +121,6 @@ public class SpringCronUtil { return taskScheduler; } - /** - * 可在项目中 进行细粒度控制 - * - * @return 获得ScheduledFuture对象 - */ - private static ScheduledFuture getScheduledFuture(Serializable id) { - return TASK_FUTURE.get(id); - } - /** * 获得当前运行的所有任务 * -- Gitee From 0fee267e4a2d2959b390fc59dd707cf6e3d05bd3 Mon Sep 17 00:00:00 2001 From: jcsun <80867809@qq.com> Date: Sun, 13 Mar 2022 18:40:21 +0800 Subject: [PATCH 38/57] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=20junit=20=E6=B5=8B?= =?UTF-8?q?=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../extra/spring/SpringCronUtilTest.java | 94 +++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 hutool-extra/src/test/java/cn/hutool/extra/spring/SpringCronUtilTest.java diff --git a/hutool-extra/src/test/java/cn/hutool/extra/spring/SpringCronUtilTest.java b/hutool-extra/src/test/java/cn/hutool/extra/spring/SpringCronUtilTest.java new file mode 100644 index 0000000000..a3eb1e6159 --- /dev/null +++ b/hutool-extra/src/test/java/cn/hutool/extra/spring/SpringCronUtilTest.java @@ -0,0 +1,94 @@ +package cn.hutool.extra.spring; + +import cn.hutool.core.date.DateUtil; +import cn.hutool.extra.spring.config.SpringCronConfig; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.junit.After; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.scheduling.TaskScheduler; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import java.io.Serializable; +import java.time.Duration; +import java.time.Instant; +import java.util.List; + +/** + * @author JC + * @date 03/13 + */ +@Slf4j +@RunWith(SpringJUnit4ClassRunner.class) +@SpringBootTest(classes = {SpringCronConfig.class, SpringCronUtil.class}) +public class SpringCronUtilTest { + /** + * 创建一个定时任务 + * 观察日志可进行验证 + */ + @Test + public void registerTask() { + String ID1 = SpringCronUtil.schedule(this::task, "0/1 * * * * ?"); + String ID2 = SpringCronUtil.schedule(888, this::task, "0/1 * * * * ?"); + log.info("taskId: {},{}", ID1, ID2); + } + + /** + * 修改一个定时任务 + */ + @Test + @SneakyThrows + public void updateTask() { + SpringCronUtil.schedule(888, this::task, "0/1 * * * * ?"); + Thread.sleep(5000); + boolean update = SpringCronUtil.update(888, "0/5 * * * * ?"); + log.info("update task result: {}", update); + } + + /** + * 取消一个定时任务 + */ + @Test + @SneakyThrows + public void cancelTask() { + SpringCronUtil.schedule(888, this::task, "0/1 * * * * ?"); + Thread.sleep(5000); + boolean cancel = SpringCronUtil.cancel(888); + log.info("cancel task result: {}", cancel); + } + + /** + * 高级用法 + * 参考: + */ + @Test + public void senior() { + TaskScheduler scheduler = SpringCronUtil.getScheduler(); + // 给定时间 开始, 间隔时间.. + scheduler.scheduleAtFixedRate(this::task, Instant.now(), Duration.ofMinutes(10)); + // ... + } + + /** + * 取消全部定时任务 + * 查看当前所有的任务 + */ + @After + @SneakyThrows + public void cancelAll() { + Thread.sleep(10000); + List allTask = SpringCronUtil.getAllTask(); + log.info("allTask: {}", allTask); + + SpringCronUtil.destroy(); + + allTask = SpringCronUtil.getAllTask(); + log.info("allTask: {}", allTask); + } + + private void task() { + log.info("information only.. (date:{})", DateUtil.now()); + } +} -- Gitee From b730ccf47070986956173fcca25a4e2d90bbe013 Mon Sep 17 00:00:00 2001 From: jcsun <80867809@qq.com> Date: Sun, 13 Mar 2022 18:44:31 +0800 Subject: [PATCH 39/57] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=20=E5=8F=82=E8=80=83?= =?UTF-8?q?=E9=93=BE=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/cn/hutool/extra/spring/SpringCronUtil.java | 1 + .../test/java/cn/hutool/extra/spring/SpringCronUtilTest.java | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringCronUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringCronUtil.java index 0599a74fce..29287bdd8a 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringCronUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringCronUtil.java @@ -22,6 +22,7 @@ import java.util.concurrent.ScheduledFuture; *
  • 取消定时任务
  • *
  • 高级操作
  • * + * 参考:Spring doc * * @author JC * @date 03/13 diff --git a/hutool-extra/src/test/java/cn/hutool/extra/spring/SpringCronUtilTest.java b/hutool-extra/src/test/java/cn/hutool/extra/spring/SpringCronUtilTest.java index a3eb1e6159..e4959da270 100644 --- a/hutool-extra/src/test/java/cn/hutool/extra/spring/SpringCronUtilTest.java +++ b/hutool-extra/src/test/java/cn/hutool/extra/spring/SpringCronUtilTest.java @@ -61,7 +61,7 @@ public class SpringCronUtilTest { /** * 高级用法 - * 参考: + * 参考:Spring doc */ @Test public void senior() { -- Gitee From 056b501e8e645c1eb991735e20a595b7bf3462b7 Mon Sep 17 00:00:00 2001 From: VampireAchao Date: Sun, 13 Mar 2022 21:34:59 +0800 Subject: [PATCH 40/57] =?UTF-8?q?=E6=8F=90=E4=BA=A4=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E7=94=A8=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/cn/hutool/db/nosql/MongoDBTest.java | 20 +++++++++++++++++++ .../src/test/resources/config/mongo.setting | 20 +++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 hutool-db/src/test/java/cn/hutool/db/nosql/MongoDBTest.java create mode 100644 hutool-db/src/test/resources/config/mongo.setting diff --git a/hutool-db/src/test/java/cn/hutool/db/nosql/MongoDBTest.java b/hutool-db/src/test/java/cn/hutool/db/nosql/MongoDBTest.java new file mode 100644 index 0000000000..278431b096 --- /dev/null +++ b/hutool-db/src/test/java/cn/hutool/db/nosql/MongoDBTest.java @@ -0,0 +1,20 @@ +package cn.hutool.db.nosql; + +import cn.hutool.db.nosql.mongo.MongoFactory; +import com.mongodb.client.MongoDatabase; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +/** + * @author VampireAchao + */ +public class MongoDBTest { + + @Test + @Ignore + public void redisDSTest() { + MongoDatabase db = MongoFactory.getDS("master").getDb("test"); + Assert.assertEquals("test", db.getName()); + } +} diff --git a/hutool-db/src/test/resources/config/mongo.setting b/hutool-db/src/test/resources/config/mongo.setting new file mode 100644 index 0000000000..dc5ae2b337 --- /dev/null +++ b/hutool-db/src/test/resources/config/mongo.setting @@ -0,0 +1,20 @@ +#每个主机答应的连接数(每个主机的连接池大小),当连接池被用光时,会被阻塞住 ,默以为10 --int +connectionsPerHost=100 +#线程队列数,它以connectionsPerHost值相乘的结果就是线程队列最大值。如果连接线程排满了队列就会抛出“Out of semaphores to get db”错误 --int +threadsAllowedToBlockForConnectionMultiplier=10 +#被阻塞线程从连接池获取连接的最长等待时间(ms) --int +maxWaitTime = 120000 +#在建立(打开)套接字连接时的超时时间(ms),默以为0(无穷) --int +connectTimeout=0 +#套接字超时时间;该值会被传递给Socket.setSoTimeout(int)。默以为0(无穷) --int +socketTimeout=0 +#是否打开长连接. defaults to false --boolean +socketKeepAlive=false + +#---------------------------------- MongoDB实例连接 +[master] +host = 127.0.0.1:27017 + +[slave] +host = 127.0.0.1:27018 +#----------------------------------------------------- -- Gitee From 9e2fa7d2b61293c246333ba07a1308628dbb1b1f Mon Sep 17 00:00:00 2001 From: VampireAchao Date: Sun, 13 Mar 2022 21:59:27 +0800 Subject: [PATCH 41/57] =?UTF-8?q?=E5=A4=AA=E6=A3=92=E4=BA=86=EF=BC=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hutool-db/pom.xml | 11 +- .../cn/hutool/db/nosql/mongo/MongoDS.java | 44 +- .../cn/hutool/db/nosql/mongo/MongoDS4.java | 404 ++++++++++++++++++ .../hutool/db/nosql/mongo/MongoFactory4.java | 120 ++++++ .../java/cn/hutool/db/nosql/MongoDBTest.java | 4 +- 5 files changed, 553 insertions(+), 30 deletions(-) create mode 100644 hutool-db/src/main/java/cn/hutool/db/nosql/mongo/MongoDS4.java create mode 100644 hutool-db/src/main/java/cn/hutool/db/nosql/mongo/MongoFactory4.java diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml index c80ea176d8..f14a0cc1b4 100644 --- a/hutool-db/pom.xml +++ b/hutool-db/pom.xml @@ -23,7 +23,8 @@ 10.0.14 1.2.8 2.4.13 - 4.5.0 + 3.12.10 + 4.5.0 3.36.0.3 2.5.2 @@ -99,10 +100,16 @@ org.mongodb - mongodb-driver-sync + mongo-java-driver ${mongo.version} true + + org.mongodb + mongodb-driver-sync + ${mongo4.version} + true + redis.clients diff --git a/hutool-db/src/main/java/cn/hutool/db/nosql/mongo/MongoDS.java b/hutool-db/src/main/java/cn/hutool/db/nosql/mongo/MongoDS.java index 1c7a4c70f7..8158f0d33a 100644 --- a/hutool-db/src/main/java/cn/hutool/db/nosql/mongo/MongoDS.java +++ b/hutool-db/src/main/java/cn/hutool/db/nosql/mongo/MongoDS.java @@ -6,28 +6,24 @@ import cn.hutool.core.util.StrUtil; import cn.hutool.db.DbRuntimeException; import cn.hutool.log.Log; import cn.hutool.setting.Setting; -import com.mongodb.MongoClientSettings; +import com.mongodb.MongoClient; +import com.mongodb.MongoClientOptions; +import com.mongodb.MongoClientOptions.Builder; import com.mongodb.MongoCredential; -import com.mongodb.MongoDriverInformation; import com.mongodb.ServerAddress; -import com.mongodb.client.MongoClient; import com.mongodb.client.MongoCollection; import com.mongodb.client.MongoDatabase; -import com.mongodb.client.internal.MongoClientImpl; -import com.mongodb.connection.ConnectionPoolSettings; -import com.mongodb.connection.SocketSettings; import org.bson.Document; import java.io.Closeable; import java.util.ArrayList; -import java.util.Collections; import java.util.List; -import java.util.concurrent.TimeUnit; /** * MongoDB工具类 * * @author xiaoleilu + * */ public class MongoDS implements Closeable { private final static Log log = Log.get(); @@ -150,11 +146,11 @@ public class MongoDS implements Closeable { final MongoCredential credentail = createCredentail(group); try { - MongoClientSettings.Builder clusterSettingsBuilder = MongoClientSettings.builder().applyToClusterSettings(b -> b.hosts(Collections.singletonList(serverAddress))); - if (null != credentail) { - clusterSettingsBuilder.credential(credentail); + if (null == credentail) { + mongo = new MongoClient(serverAddress, buildMongoClientOptions(group)); + } else { + mongo = new MongoClient(serverAddress, credentail, buildMongoClientOptions(group)); } - mongo = new MongoClientImpl(clusterSettingsBuilder.build(), MongoDriverInformation.builder().build()); } catch (Exception e) { throw new DbRuntimeException(StrUtil.format("Init MongoDB pool with connection to [{}] error!", serverAddress), e); } @@ -196,11 +192,11 @@ public class MongoDS implements Closeable { final MongoCredential credentail = createCredentail(StrUtil.EMPTY); try { - MongoClientSettings.Builder clusterSettingsBuilder = MongoClientSettings.builder().applyToClusterSettings(b -> b.hosts(addrList)); - if (null != credentail) { - clusterSettingsBuilder.credential(credentail); + if (null == credentail) { + mongo = new MongoClient(addrList, buildMongoClientOptions(StrUtil.EMPTY)); + } else { + mongo = new MongoClient(addrList, credentail, buildMongoClientOptions(StrUtil.EMPTY)); } - mongo = new MongoClientImpl(clusterSettingsBuilder.build(), MongoDriverInformation.builder().build()); } catch (Exception e) { log.error(e, "Init MongoDB connection error!"); return; @@ -252,7 +248,6 @@ public class MongoDS implements Closeable { } // --------------------------------------------------------------------------- Private method start - /** * 创建ServerAddress对象,会读取配置文件中的相关信息 * @@ -327,8 +322,8 @@ public class MongoDS implements Closeable { * @param group 分组,当分组对应的选项不存在时会读取根选项,如果也不存在使用默认值 * @return MongoClientOptions */ - private MongoClientSettings buildMongoClientOptions(String group) { - return buildMongoClientOptions(MongoClientSettings.builder(), group).build(); + private MongoClientOptions buildMongoClientOptions(String group) { + return buildMongoClientOptions(MongoClientOptions.builder(), group).build(); } /** @@ -337,7 +332,7 @@ public class MongoDS implements Closeable { * @param group 分组,当分组对应的选项不存在时会读取根选项,如果也不存在使用默认值 * @return Builder */ - private MongoClientSettings.Builder buildMongoClientOptions(MongoClientSettings.Builder builder, String group) { + private Builder buildMongoClientOptions(Builder builder, String group) { if (setting == null) { return builder; } @@ -353,9 +348,8 @@ public class MongoDS implements Closeable { if (StrUtil.isBlank(group) == false && connectionsPerHost == null) { connectionsPerHost = setting.getInt("connectionsPerHost"); } - ConnectionPoolSettings.Builder connectionPoolSettingsBuilder = ConnectionPoolSettings.builder(); if (connectionsPerHost != null) { - connectionPoolSettingsBuilder.maxConnecting(connectionsPerHost); + builder.connectionsPerHost(connectionsPerHost); log.debug("MongoDB connectionsPerHost: {}", connectionsPerHost); } @@ -365,10 +359,9 @@ public class MongoDS implements Closeable { setting.getInt("connectTimeout"); } if (connectTimeout != null) { - connectionPoolSettingsBuilder.maxWaitTime(connectTimeout, TimeUnit.MILLISECONDS); + builder.connectTimeout(connectTimeout); log.debug("MongoDB connectTimeout: {}", connectTimeout); } - builder.applyToConnectionPoolSettings(b -> b.applySettings(connectionPoolSettingsBuilder.build())); // 套接字超时时间;该值会被传递给Socket.setSoTimeout(int)。默以为0(无穷) --int Integer socketTimeout = setting.getInt(group + "socketTimeout"); @@ -376,8 +369,7 @@ public class MongoDS implements Closeable { setting.getInt("socketTimeout"); } if (socketTimeout != null) { - SocketSettings socketSettings = SocketSettings.builder().connectTimeout(socketTimeout, TimeUnit.MILLISECONDS).build(); - builder.applyToSocketSettings(b -> b.applySettings(socketSettings)); + builder.socketTimeout(socketTimeout); log.debug("MongoDB socketTimeout: {}", socketTimeout); } diff --git a/hutool-db/src/main/java/cn/hutool/db/nosql/mongo/MongoDS4.java b/hutool-db/src/main/java/cn/hutool/db/nosql/mongo/MongoDS4.java new file mode 100644 index 0000000000..0b2ff0f620 --- /dev/null +++ b/hutool-db/src/main/java/cn/hutool/db/nosql/mongo/MongoDS4.java @@ -0,0 +1,404 @@ +package cn.hutool.db.nosql.mongo; + +import cn.hutool.core.exceptions.NotInitedException; +import cn.hutool.core.net.NetUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.db.DbRuntimeException; +import cn.hutool.log.Log; +import cn.hutool.setting.Setting; +import com.mongodb.MongoClientSettings; +import com.mongodb.MongoCredential; +import com.mongodb.MongoDriverInformation; +import com.mongodb.ServerAddress; +import com.mongodb.client.MongoClient; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoDatabase; +import com.mongodb.client.internal.MongoClientImpl; +import com.mongodb.connection.ConnectionPoolSettings; +import com.mongodb.connection.SocketSettings; +import org.bson.Document; + +import java.io.Closeable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.TimeUnit; + +/** + * MongoDB4工具类 + * + * @author VampireAchao + */ +public class MongoDS4 implements Closeable { + + private final static Log log = Log.get(); + + /** + * 默认配置文件 + */ + public final static String MONGO_CONFIG_PATH = "config/mongo.setting"; + + // MongoDB配置文件 + private Setting setting; + // MongoDB实例连接列表 + private String[] groups; + // MongoDB单点连接信息 + private ServerAddress serverAddress; + // MongoDB客户端对象 + private MongoClient mongo; + + // --------------------------------------------------------------------------- Constructor start + + /** + * 构造MongoDB数据源
    + * 调用者必须持有MongoDS实例,否则会被垃圾回收导致写入失败! + * + * @param host 主机(域名或者IP) + * @param port 端口 + */ + public MongoDS4(String host, int port) { + this.serverAddress = createServerAddress(host, port); + initSingle(); + } + + /** + * 构造MongoDB数据源
    + * 调用者必须持有MongoDS实例,否则会被垃圾回收导致写入失败! + * + * @param mongoSetting MongoDB的配置文件,如果是null则读取默认配置文件或者使用MongoDB默认客户端配置 + * @param host 主机(域名或者IP) + * @param port 端口 + */ + public MongoDS4(Setting mongoSetting, String host, int port) { + this.setting = mongoSetting; + this.serverAddress = createServerAddress(host, port); + initSingle(); + } + + /** + * 构造MongoDB数据源
    + * 当提供多个数据源时,这些数据源将为一个副本集或者多个mongos
    + * 调用者必须持有MongoDS实例,否则会被垃圾回收导致写入失败! 官方文档: http://docs.mongodb.org/manual/administration/replica-sets/ + * + * @param groups 分组列表,当为null或空时使用无分组配置,一个分组使用单一模式,否则使用副本集模式 + */ + public MongoDS4(String... groups) { + this.groups = groups; + init(); + } + + /** + * 构造MongoDB数据源
    + * 当提供多个数据源时,这些数据源将为一个副本集或者mongos
    + * 调用者必须持有MongoDS实例,否则会被垃圾回收导致写入失败!
    + * 官方文档: http://docs.mongodb.org/manual/administration/replica-sets/ + * + * @param mongoSetting MongoDB的配置文件,必须有 + * @param groups 分组列表,当为null或空时使用无分组配置,一个分组使用单一模式,否则使用副本集模式 + */ + public MongoDS4(Setting mongoSetting, String... groups) { + if (mongoSetting == null) { + throw new DbRuntimeException("Mongo setting is null!"); + } + this.setting = mongoSetting; + this.groups = groups; + init(); + } + // --------------------------------------------------------------------------- Constructor end + + /** + * 初始化,当给定分组数大于一个时使用 + */ + public void init() { + if (groups != null && groups.length > 1) { + initCloud(); + } else { + initSingle(); + } + } + + /** + * 初始化
    + * 设定文件中的host和端口有三种形式: + * + *
    +	 * host = host:port
    +	 * 
    + * + *
    +	 * host = host
    +	 * port = port
    +	 * 
    + * + *
    +	 * host = host
    +	 * 
    + */ + synchronized public void initSingle() { + if (setting == null) { + try { + setting = new Setting(MONGO_CONFIG_PATH, true); + } catch (Exception e) { + // 在single模式下,可以没有配置文件。 + } + } + + String group = StrUtil.EMPTY; + if (null == this.serverAddress) { + //存在唯一分组 + if (groups != null && groups.length == 1) { + group = groups[0]; + } + serverAddress = createServerAddress(group); + } + + final MongoCredential credentail = createCredentail(group); + try { + MongoClientSettings.Builder clusterSettingsBuilder = MongoClientSettings.builder().applyToClusterSettings(b -> b.hosts(Collections.singletonList(serverAddress))); + if (null != credentail) { + clusterSettingsBuilder.credential(credentail); + } + mongo = new MongoClientImpl(clusterSettingsBuilder.build(), MongoDriverInformation.builder().build()); + } catch (Exception e) { + throw new DbRuntimeException(StrUtil.format("Init MongoDB pool with connection to [{}] error!", serverAddress), e); + } + + log.info("Init MongoDB pool with connection to [{}]", serverAddress); + } + + /** + * 初始化集群
    + * 集群的其它客户端设定参数使用全局设定
    + * 集群中每一个实例成员用一个group表示,例如: + * + *
    +	 * user = test1
    +	 * pass = 123456
    +	 * database = test
    +	 * [db0]
    +	 * host = 192.168.1.1:27117
    +	 * [db1]
    +	 * host = 192.168.1.1:27118
    +	 * [db2]
    +	 * host = 192.168.1.1:27119
    +	 * 
    + */ + synchronized public void initCloud() { + if (groups == null || groups.length == 0) { + throw new DbRuntimeException("Please give replication set groups!"); + } + + if (setting == null) { + // 若未指定配置文件,则使用默认配置文件 + setting = new Setting(MONGO_CONFIG_PATH, true); + } + + final List addrList = new ArrayList<>(); + for (String group : groups) { + addrList.add(createServerAddress(group)); + } + + final MongoCredential credentail = createCredentail(StrUtil.EMPTY); + try { + MongoClientSettings.Builder clusterSettingsBuilder = MongoClientSettings.builder().applyToClusterSettings(b -> b.hosts(addrList)); + if (null != credentail) { + clusterSettingsBuilder.credential(credentail); + } + mongo = new MongoClientImpl(clusterSettingsBuilder.build(), MongoDriverInformation.builder().build()); + } catch (Exception e) { + log.error(e, "Init MongoDB connection error!"); + return; + } + + log.info("Init MongoDB cloud Set pool with connection to {}", addrList); + } + + /** + * 设定MongoDB配置文件 + * + * @param setting 配置文件 + */ + public void setSetting(Setting setting) { + this.setting = setting; + } + + /** + * @return 获得MongoDB客户端对象 + */ + public MongoClient getMongo() { + return mongo; + } + + /** + * 获得DB + * + * @param dbName DB + * @return DB + */ + public MongoDatabase getDb(String dbName) { + return mongo.getDatabase(dbName); + } + + /** + * 获得MongoDB中指定集合对象 + * + * @param dbName 库名 + * @param collectionName 集合名 + * @return DBCollection + */ + public MongoCollection getCollection(String dbName, String collectionName) { + return getDb(dbName).getCollection(collectionName); + } + + @Override + public void close() { + mongo.close(); + } + + // --------------------------------------------------------------------------- Private method start + + /** + * 创建ServerAddress对象,会读取配置文件中的相关信息 + * + * @param group 分组,如果为{@code null}或者""默认为无分组 + * @return ServerAddress + */ + private ServerAddress createServerAddress(String group) { + final Setting setting = checkSetting(); + + if (group == null) { + group = StrUtil.EMPTY; + } + + final String tmpHost = setting.getByGroup("host", group); + if (StrUtil.isBlank(tmpHost)) { + throw new NotInitedException("Host name is empy of group: {}", group); + } + + final int defaultPort = setting.getInt("port", group, 27017); + return new ServerAddress(NetUtil.buildInetSocketAddress(tmpHost, defaultPort)); + } + + /** + * 创建ServerAddress对象 + * + * @param host 主机域名或者IP(如果为空默认127.0.0.1) + * @param port 端口(如果为空默认为) + * @return ServerAddress + */ + private ServerAddress createServerAddress(String host, int port) { + return new ServerAddress(host, port); + } + + /** + * 创建{@link MongoCredential},用于服务端验证
    + * 此方法会首先读取指定分组下的属性,用户没有定义则读取空分组下的属性 + * + * @param group 分组 + * @return {@link MongoCredential},如果用户未指定用户名密码返回null + * @since 4.1.20 + */ + private MongoCredential createCredentail(String group) { + final Setting setting = this.setting; + if (null == setting) { + return null; + } + final String user = setting.getStr("user", group, setting.getStr("user")); + final String pass = setting.getStr("pass", group, setting.getStr("pass")); + final String database = setting.getStr("database", group, setting.getStr("database")); + return createCredentail(user, database, pass); + } + + /** + * 创建{@link MongoCredential},用于服务端验证 + * + * @param userName 用户名 + * @param database 数据库名 + * @param password 密码 + * @return {@link MongoCredential} + * @since 4.1.20 + */ + private MongoCredential createCredentail(String userName, String database, String password) { + if (StrUtil.hasEmpty(userName, database, database)) { + return null; + } + return MongoCredential.createCredential(userName, database, password.toCharArray()); + } + + /** + * 构件MongoDB连接选项
    + * + * @param group 分组,当分组对应的选项不存在时会读取根选项,如果也不存在使用默认值 + * @return MongoClientOptions + */ + private MongoClientSettings buildMongoClientOptions(String group) { + return buildMongoClientOptions(MongoClientSettings.builder(), group).build(); + } + + /** + * 构件MongoDB连接选项
    + * + * @param group 分组,当分组对应的选项不存在时会读取根选项,如果也不存在使用默认值 + * @return Builder + */ + private MongoClientSettings.Builder buildMongoClientOptions(MongoClientSettings.Builder builder, String group) { + if (setting == null) { + return builder; + } + + if (StrUtil.isEmpty(group)) { + group = StrUtil.EMPTY; + } else { + group = group + StrUtil.DOT; + } + + // 每个主机答应的连接数(每个主机的连接池大小),当连接池被用光时,会被阻塞住 + Integer connectionsPerHost = setting.getInt(group + "connectionsPerHost"); + if (StrUtil.isBlank(group) == false && connectionsPerHost == null) { + connectionsPerHost = setting.getInt("connectionsPerHost"); + } + ConnectionPoolSettings.Builder connectionPoolSettingsBuilder = ConnectionPoolSettings.builder(); + if (connectionsPerHost != null) { + connectionPoolSettingsBuilder.maxSize(connectionsPerHost); + log.debug("MongoDB connectionsPerHost: {}", connectionsPerHost); + } + + // 被阻塞线程从连接池获取连接的最长等待时间(ms) --int + Integer connectTimeout = setting.getInt(group + "connectTimeout"); + if (StrUtil.isBlank(group) == false && connectTimeout == null) { + setting.getInt("connectTimeout"); + } + if (connectTimeout != null) { + connectionPoolSettingsBuilder.maxWaitTime(connectTimeout, TimeUnit.MILLISECONDS); + log.debug("MongoDB connectTimeout: {}", connectTimeout); + } + builder.applyToConnectionPoolSettings(b -> b.applySettings(connectionPoolSettingsBuilder.build())); + + // 套接字超时时间;该值会被传递给Socket.setSoTimeout(int)。默以为0(无穷) --int + Integer socketTimeout = setting.getInt(group + "socketTimeout"); + if (StrUtil.isBlank(group) == false && socketTimeout == null) { + setting.getInt("socketTimeout"); + } + if (socketTimeout != null) { + SocketSettings socketSettings = SocketSettings.builder().connectTimeout(socketTimeout, TimeUnit.MILLISECONDS).build(); + builder.applyToSocketSettings(b -> b.applySettings(socketSettings)); + log.debug("MongoDB socketTimeout: {}", socketTimeout); + } + + return builder; + } + + /** + * 检查Setting配置文件 + * + * @return Setting配置文件 + */ + private Setting checkSetting() { + if (null == this.setting) { + throw new DbRuntimeException("Please indicate setting file or create default [{}]", MONGO_CONFIG_PATH); + } + return this.setting; + } + // --------------------------------------------------------------------------- Private method end + +} diff --git a/hutool-db/src/main/java/cn/hutool/db/nosql/mongo/MongoFactory4.java b/hutool-db/src/main/java/cn/hutool/db/nosql/mongo/MongoFactory4.java new file mode 100644 index 0000000000..a099118a4a --- /dev/null +++ b/hutool-db/src/main/java/cn/hutool/db/nosql/mongo/MongoFactory4.java @@ -0,0 +1,120 @@ +package cn.hutool.db.nosql.mongo; + +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.RuntimeUtil; +import cn.hutool.setting.Setting; + +import java.util.Collection; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * MongoDB4工厂类,用于创建 + * @author VampireAchao + */ +public class MongoFactory4 { + + /** 各分组做组合key的时候分隔符 */ + private final static String GROUP_SEPRATER = ","; + + /** 数据源池 */ + private static final Map DS_MAP = new ConcurrentHashMap<>(); + + // JVM关闭前关闭MongoDB连接 + static { + RuntimeUtil.addShutdownHook(MongoFactory4::closeAll); + } + + // ------------------------------------------------------------------------ Get DS start + /** + * 获取MongoDB数据源
    + * + * @param host 主机 + * @param port 端口 + * @return MongoDB连接 + */ + public static MongoDS4 getDS(String host, int port) { + final String key = host + ":" + port; + MongoDS4 ds = DS_MAP.get(key); + if (null == ds) { + // 没有在池中加入之 + ds = new MongoDS4(host, port); + DS_MAP.put(key, ds); + } + + return ds; + } + + /** + * 获取MongoDB数据源
    + * 多个分组名对应的连接组成集群 + * + * @param groups 分组列表 + * @return MongoDB连接 + */ + public static MongoDS4 getDS(String... groups) { + final String key = ArrayUtil.join(groups, GROUP_SEPRATER); + MongoDS4 ds = DS_MAP.get(key); + if (null == ds) { + // 没有在池中加入之 + ds = new MongoDS4(groups); + DS_MAP.put(key, ds); + } + + return ds; + } + + /** + * 获取MongoDB数据源
    + * + * @param groups 分组列表 + * @return MongoDB连接 + */ + public static MongoDS4 getDS(Collection groups) { + return getDS(groups.toArray(new String[0])); + } + + /** + * 获取MongoDB数据源
    + * + * @param setting 设定文件 + * @param groups 分组列表 + * @return MongoDB连接 + */ + public static MongoDS4 getDS(Setting setting, String... groups) { + final String key = setting.getSettingPath() + GROUP_SEPRATER + ArrayUtil.join(groups, GROUP_SEPRATER); + MongoDS4 ds = DS_MAP.get(key); + if (null == ds) { + // 没有在池中加入之 + ds = new MongoDS4(setting, groups); + DS_MAP.put(key, ds); + } + + return ds; + } + + /** + * 获取MongoDB数据源
    + * + * @param setting 配置文件 + * @param groups 分组列表 + * @return MongoDB连接 + */ + public static MongoDS4 getDS(Setting setting, Collection groups) { + return getDS(setting, groups.toArray(new String[0])); + } + // ------------------------------------------------------------------------ Get DS ends + + /** + * 关闭全部连接 + */ + public static void closeAll() { + if(MapUtil.isNotEmpty(DS_MAP)){ + for(MongoDS4 ds : DS_MAP.values()) { + ds.close(); + } + DS_MAP.clear(); + } + } +} diff --git a/hutool-db/src/test/java/cn/hutool/db/nosql/MongoDBTest.java b/hutool-db/src/test/java/cn/hutool/db/nosql/MongoDBTest.java index 278431b096..67ffadd9f3 100644 --- a/hutool-db/src/test/java/cn/hutool/db/nosql/MongoDBTest.java +++ b/hutool-db/src/test/java/cn/hutool/db/nosql/MongoDBTest.java @@ -1,6 +1,6 @@ package cn.hutool.db.nosql; -import cn.hutool.db.nosql.mongo.MongoFactory; +import cn.hutool.db.nosql.mongo.MongoFactory4; import com.mongodb.client.MongoDatabase; import org.junit.Assert; import org.junit.Ignore; @@ -14,7 +14,7 @@ public class MongoDBTest { @Test @Ignore public void redisDSTest() { - MongoDatabase db = MongoFactory.getDS("master").getDb("test"); + MongoDatabase db = MongoFactory4.getDS("master").getDb("test"); Assert.assertEquals("test", db.getName()); } } -- Gitee From c62d4d382acb0b25555e5f1d9a980b787edb2adf Mon Sep 17 00:00:00 2001 From: Husky <2466896229@qq.com> Date: Sun, 13 Mar 2022 23:07:41 +0800 Subject: [PATCH 42/57] =?UTF-8?q?https://gitee.com/dromara/hutool/issues/I?= =?UTF-8?q?4XG4L=E5=88=9D=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/cn/hutool/core/util/ArrayUtil.java | 57 ++++++++------- .../cn/hutool/core/util/ArrayUtilTest.java | 72 ++++++++++++++++--- 2 files changed, 93 insertions(+), 36 deletions(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ArrayUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ArrayUtil.java index 01e4ea721b..0c67d65222 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 @@ -369,8 +369,9 @@ public class ArrayUtil extends PrimitiveArrayUtil { } /** - * 将元素值设置为数组的某个位置,根据元素顺序添加
    - * 当给定的index大于数组长度,则追加 + * 将新元素插入到到已有数组中的某个位置
    + * 添加新元素会生成一个新数组或原有数组
    + * 如果插入位置为为负数,那么生成一个由插入元素顺序加已有数组顺序的新数组 * * @param 数组元素类型 * @param buffer 已有数组 @@ -378,31 +379,33 @@ public class ArrayUtil extends PrimitiveArrayUtil { * @param values 新值 * @return 新数组或原有数组 */ + @SuppressWarnings({"unchecked", "SuspiciousSystemArraycopy"}) public static T[] replace(T[] buffer, int index, T... values) { - return index == 0 ? values : replaceBy(buffer, index, values); - } - - /** - * 将元素值设置为数组的某个位置,根据元素顺序添加
    - * 当给定的index大于数组长度,则追加 - * - * @param 数组元素类型 - * @param buffer 已有数组 - * @param index 位置,大于长度追加,否则替换 - * @param values 新值 - * @return 新数组或原有数组 - */ - public static T[] replaceBy(T[] buffer, int index, T... values) { - if (index < buffer.length && buffer.length - index - 1 >= values.length) { - for (int i = index; i < values.length; i++) { - Array.set(buffer, index, values[i]); + if (index < 0) { + return insert(buffer, 0, values); + } + if (isEmpty(buffer) || index == 0 && isNotEmpty(values)) { + return values; + } + if (index >= buffer.length || isEmpty(values)) { + return append(buffer, values); + } + int replaceSpace = buffer.length - index - 1; + if (replaceSpace > values.length) { + for (int i = index - 1; i < values.length; i++) { + Array.set(buffer, i + 1, values[i]); } return buffer; - } else { - final T[] result = (T[]) Array.newInstance(buffer.getClass().getComponentType(), buffer.length - index - 1); - System.arraycopy(buffer, 0, result, 0, buffer.length - index - 1); - return append(result, values); } + int newArrayLength = index + values.length; + final T[] result = (T[]) Array.newInstance(buffer.getClass().getComponentType(), newArrayLength); + System.arraycopy(buffer, 0, result, 0, index); + int valueIndex = 0; + for (int i = index; i < newArrayLength; i++) { + Array.set(result, i, values[valueIndex]); + valueIndex = valueIndex + 1; + } + return result; } /** @@ -1577,7 +1580,7 @@ public class ArrayUtil extends PrimitiveArrayUtil { * @since 4.5.18 */ public static boolean isAllEmpty(Object... args) { - for (Object obj: args) { + for (Object obj : args) { if (false == ObjectUtil.isEmpty(obj)) { return false; } @@ -1795,10 +1798,10 @@ public class ArrayUtil extends PrimitiveArrayUtil { /** * 查找最后一个子数组的开始位置 * - * @param array 数组 + * @param array 数组 * @param endInclude 查找结束的位置(包含) - * @param subArray 子数组 - * @param 数组元素类型 + * @param subArray 子数组 + * @param 数组元素类型 * @return 最后一个子数组的开始位置,即子数字第一个元素在数组中的位置 * @since 5.4.8 */ 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 3ba18becf3..e2ce30b9e6 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 @@ -363,7 +363,7 @@ public class ArrayUtilTest { } @Test - public void indexOfSubTest2(){ + public void indexOfSubTest2() { Integer[] a = {0x12, 0x56, 0x34, 0x56, 0x78, 0x9A}; Integer[] b = {0x56, 0x78}; int i = ArrayUtil.indexOfSub(a, b); @@ -401,7 +401,7 @@ public class ArrayUtilTest { } @Test - public void lastIndexOfSubTest2(){ + public void lastIndexOfSubTest2() { Integer[] a = {0x12, 0x56, 0x78, 0x56, 0x21, 0x9A}; Integer[] b = {0x56, 0x78}; int i = ArrayUtil.indexOfSub(a, b); @@ -409,17 +409,17 @@ public class ArrayUtilTest { } @Test - public void reverseTest(){ - int[] a = {1,2,3,4}; + public void reverseTest() { + int[] a = {1, 2, 3, 4}; final int[] reverse = ArrayUtil.reverse(a); - Assert.assertArrayEquals(new int[]{4,3,2,1}, reverse); + Assert.assertArrayEquals(new int[]{4, 3, 2, 1}, reverse); } @Test - public void reverseTest2s(){ - Object[] a = {"1",'2',"3",4}; + public void reverseTest2s() { + Object[] a = {"1", '2', "3", 4}; final Object[] reverse = ArrayUtil.reverse(a); - Assert.assertArrayEquals(new Object[]{4,"3",'2',"1"}, reverse); + Assert.assertArrayEquals(new Object[]{4, "3", '2', "1"}, reverse); } @Test @@ -461,9 +461,63 @@ public class ArrayUtilTest { } @Test - public void getTest(){ + public void getTest() { String[] a = {"a", "b", "c"}; final Object o = ArrayUtil.get(a, -1); Assert.assertEquals("c", o); } + + @Test + public void replaceTest() { + String[] a = {"1", "2", "3", "4"}; + String[] b = {"a", "b", "c"}; + + // 在小于0的位置,-1位置插入,返回b+a + String[] result = ArrayUtil.replace(a, -1, b); + Assert.assertArrayEquals(new String[]{"a", "b", "c", "1", "2", "3", "4"}, result); + + // 在第0个位置插入,即覆盖a,直接返回b + result = ArrayUtil.replace(a, 0, b); + Assert.assertArrayEquals(new String[]{"a", "b", "c"}, result); + + // 在第1个位置插入,即"2"之前 + result = ArrayUtil.replace(a, 1, b); + Assert.assertArrayEquals(new String[]{"1", "a", "b", "c"}, result); + + //上一步测试修改了原数组结构 + String[] c = {"1", "2", "3", "4"}; + String[] d = {"a", "b", "c"}; + + // 在第2个位置插入,即"3"之后 + result = ArrayUtil.replace(c, 2, d); + Assert.assertArrayEquals(new String[]{"1", "2", "a", "b", "c"}, result); + + // 在第3个位置插入,即"4"之后 + result = ArrayUtil.replace(c, 3, d); + Assert.assertArrayEquals(new String[]{"1", "2", "3", "a", "b", "c"}, result); + + // 在第4个位置插入,数组长度为4,在索引4出替换即两个数组相加 + result = ArrayUtil.replace(c, 4, d); + Assert.assertArrayEquals(new String[]{"1", "2", "3", "4", "a", "b", "c"}, result); + + // 在大于3个位置插入,数组长度为4,即两个数组相加 + result = ArrayUtil.replace(c, 5, d); + Assert.assertArrayEquals(new String[]{"1", "2", "3", "4", "a", "b", "c"}, result); + + //上一步测试修改了原数组结构 + String[] e = null; + String[] f = {"a", "b", "c"}; + + // e为null 返回 f + result = ArrayUtil.replace(e, -1, f); + Assert.assertArrayEquals(new String[]{"a", "b", "c"}, result); + + //上一步测试修改了原数组结构 + String[] g = {"a", "b", "c"}; + String[] h = null; + + // h为null 返回 g + result = ArrayUtil.replace(g, 0, h); + Assert.assertArrayEquals(new String[]{"a", "b", "c"}, result); + } } -- Gitee From 7acac417e694287870ea6eda36339c0e4fa3f094 Mon Sep 17 00:00:00 2001 From: Husky <2466896229@qq.com> Date: Sun, 13 Mar 2022 23:08:40 +0800 Subject: [PATCH 43/57] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=97=A0=E7=94=A8?= =?UTF-8?q?=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/test/java/cn/hutool/core/util/ArrayUtilTest.java | 2 -- 1 file changed, 2 deletions(-) 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 e2ce30b9e6..442ce4b91b 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 @@ -504,7 +504,6 @@ public class ArrayUtilTest { result = ArrayUtil.replace(c, 5, d); Assert.assertArrayEquals(new String[]{"1", "2", "3", "4", "a", "b", "c"}, result); - //上一步测试修改了原数组结构 String[] e = null; String[] f = {"a", "b", "c"}; @@ -512,7 +511,6 @@ public class ArrayUtilTest { result = ArrayUtil.replace(e, -1, f); Assert.assertArrayEquals(new String[]{"a", "b", "c"}, result); - //上一步测试修改了原数组结构 String[] g = {"a", "b", "c"}; String[] h = null; -- Gitee From b541b70062cc71f704c177b16c5177a01a7e9f0a Mon Sep 17 00:00:00 2001 From: Looly Date: Sun, 13 Mar 2022 23:35:58 +0800 Subject: [PATCH 44/57] add method --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dfefa113bb..41f61d48a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ # 🚀Changelog ------------------------------------------------------------------------------------------------------------- -# 5.7.23 (2022-03-09) +# 5.7.23 (2022-03-13) ### 🐣新特性 * 【http 】 HttpRequest.form采用TableMap方式(issue#I4W427@Gitee) @@ -11,6 +11,7 @@ * 【crypto 】 增加XXTEA实现(issue#I4WH2X@Gitee) * 【core 】 增加Table实现(issue#2179@Github) * 【core 】 增加UniqueKeySet(issue#I4WUWR@Gitee) +* 【core 】 阿拉伯数字转换成中文对发票票面金额转换的扩展(pr#570@Gitee) * ### 🐞Bug修复 * 【core 】 修复ObjectUtil.hasNull传入null返回true的问题(pr#555@Gitee) -- Gitee From e38922796444015831319a04d01c85cbb4d87aa3 Mon Sep 17 00:00:00 2001 From: Looly Date: Mon, 14 Mar 2022 00:32:35 +0800 Subject: [PATCH 45/57] fix bug and add method --- CHANGELOG.md | 2 + .../core/convert/NumberChineseFormatter.java | 1 + .../java/cn/hutool/core/text/NamingCase.java | 2 +- .../java/cn/hutool/core/util/ArrayUtil.java | 36 +++++++++--------- .../cn/hutool/core/text/NamingCaseTest.java | 37 +++++++++++++++---- .../cn/hutool/core/util/ArrayUtilTest.java | 28 ++++++-------- .../java/cn/hutool/core/util/StrUtilTest.java | 35 ------------------ 7 files changed, 65 insertions(+), 76 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 41f61d48a3..aa4ee93a76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,11 +12,13 @@ * 【core 】 增加Table实现(issue#2179@Github) * 【core 】 增加UniqueKeySet(issue#I4WUWR@Gitee) * 【core 】 阿拉伯数字转换成中文对发票票面金额转换的扩展(pr#570@Gitee) +* 【core 】 ArrayUtil增加replace方法(pr#570@Gitee) * ### 🐞Bug修复 * 【core 】 修复ObjectUtil.hasNull传入null返回true的问题(pr#555@Gitee) * 【core 】 修复NumberConverter对数字转换的问题(issue#I4WPF4@Gitee) * 【core 】 修复ReflectUtil.getMethods获取接口方法问题(issue#I4WUWR@Gitee) +* 【core 】 修复NamingCase中大写转换问题(pr#572@Gitee) ------------------------------------------------------------------------------------------------------------- # 5.7.22 (2022-03-01) diff --git a/hutool-core/src/main/java/cn/hutool/core/convert/NumberChineseFormatter.java b/hutool-core/src/main/java/cn/hutool/core/convert/NumberChineseFormatter.java index 5f8a7b52fc..6ec3f0e160 100644 --- a/hutool-core/src/main/java/cn/hutool/core/convert/NumberChineseFormatter.java +++ b/hutool-core/src/main/java/cn/hutool/core/convert/NumberChineseFormatter.java @@ -68,6 +68,7 @@ public class NumberChineseFormatter { * @param unitName 单位名称 如:元、圆 * @return java.lang.String * @author machuanpeng + * @since 5.7.23 */ public static String format(double amount, boolean isUseTraditional, boolean isMoneyMode, String negativeName, String unitName) { if (0 == amount) { diff --git a/hutool-core/src/main/java/cn/hutool/core/text/NamingCase.java b/hutool-core/src/main/java/cn/hutool/core/text/NamingCase.java index a5c7a5d521..56eb04c464 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/NamingCase.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/NamingCase.java @@ -157,7 +157,7 @@ public class NamingCase { * 将连接符方式命名的字符串转换为驼峰式。如果转换前的下划线大写方式命名的字符串为空,则返回空字符串。 * * @param name 转换前的自定义方式命名的字符串 - * @param symbol 连接符 + * @param symbol 原字符串中的连接符连接符 * @return 转换后的驼峰式命名的字符串 * @since 5.7.17 */ 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 0c67d65222..399fe571dd 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 @@ -375,36 +375,38 @@ public class ArrayUtil extends PrimitiveArrayUtil { * * @param 数组元素类型 * @param buffer 已有数组 - * @param index 位置,大于长度追加,否则替换 + * @param index 位置,大于长度追加,否则替换,<0表示从头部追加 * @param values 新值 * @return 新数组或原有数组 + * @since 5.7.23 */ - @SuppressWarnings({"unchecked", "SuspiciousSystemArraycopy"}) + @SuppressWarnings({"unchecked"}) public static T[] replace(T[] buffer, int index, T... values) { - if (index < 0) { - return insert(buffer, 0, values); + if(isEmpty(values)){ + return buffer; } - if (isEmpty(buffer) || index == 0 && isNotEmpty(values)) { + if(isEmpty(buffer)){ return values; } - if (index >= buffer.length || isEmpty(values)) { + if (index < 0) { + // 从头部追加 + return insert(buffer, 0, values); + } + if (index >= buffer.length) { + // 超出长度,尾部追加 return append(buffer, values); } - int replaceSpace = buffer.length - index - 1; - if (replaceSpace > values.length) { - for (int i = index - 1; i < values.length; i++) { - Array.set(buffer, i + 1, values[i]); - } + + if (buffer.length >= values.length + index) { + System.arraycopy(values, 0, buffer, index, values.length); return buffer; } + + // 替换长度大于原数组长度,新建数组 int newArrayLength = index + values.length; - final T[] result = (T[]) Array.newInstance(buffer.getClass().getComponentType(), newArrayLength); + final T[] result = newArray(buffer.getClass().getComponentType(), newArrayLength); System.arraycopy(buffer, 0, result, 0, index); - int valueIndex = 0; - for (int i = index; i < newArrayLength; i++) { - Array.set(result, i, values[valueIndex]); - valueIndex = valueIndex + 1; - } + System.arraycopy(values, 0, result, index, values.length); return result; } diff --git a/hutool-core/src/test/java/cn/hutool/core/text/NamingCaseTest.java b/hutool-core/src/test/java/cn/hutool/core/text/NamingCaseTest.java index 62545d934c..2eec1a2485 100644 --- a/hutool-core/src/test/java/cn/hutool/core/text/NamingCaseTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/text/NamingCaseTest.java @@ -1,21 +1,44 @@ package cn.hutool.core.text; import cn.hutool.core.lang.Dict; +import cn.hutool.core.util.CharUtil; import org.junit.Assert; import org.junit.Test; public class NamingCaseTest { + @Test - public void toUnderlineCaseTest(){ - // https://github.com/dromara/hutool/issues/2070 + public void toCamelCaseTest() { Dict.create() - .set("customerNickV2", "customer_nick_v2") - .forEach((key, value) -> Assert.assertEquals(value, NamingCase.toUnderlineCase(key))); + .set("Table_Test_Of_day","tableTestOfDay") + .set("TableTestOfDay","TableTestOfDay") + .set("abc_1d","abc1d") + .forEach((key, value) -> Assert.assertEquals(value, NamingCase.toCamelCase(key))); + } + + @Test + public void toCamelCaseFromDashedTest() { + Dict.create() + .set("Table-Test-Of-day","tableTestOfDay") + .forEach((key, value) -> Assert.assertEquals(value, NamingCase.toCamelCase(key, CharUtil.DASHED))); } @Test - public void toUnderLineCaseTest2(){ - final String wPRunOZTime = NamingCase.toUnderlineCase("wPRunOZTime"); - Assert.assertEquals("w_P_run_OZ_time", wPRunOZTime); + public void toUnderLineCaseTest() { + Dict.create() + .set("Table_Test_Of_day", "table_test_of_day") + .set("_Table_Test_Of_day_", "_table_test_of_day_") + .set("_Table_Test_Of_DAY_", "_table_test_of_DAY_") + .set("_TableTestOfDAYToday", "_table_test_of_DAY_today") + .set("HelloWorld_test", "hello_world_test") + .set("H2", "H2") + .set("H#case", "H#case") + .set("PNLabel", "PN_label") + .set("wPRunOZTime", "w_P_run_OZ_time") + // https://github.com/dromara/hutool/issues/2070 + .set("customerNickV2", "customer_nick_v2") + // https://gitee.com/dromara/hutool/issues/I4X9TT + .set("DEPT_NAME","DEPT_NAME") + .forEach((key, value) -> Assert.assertEquals(value, NamingCase.toUnderlineCase(key))); } } 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 442ce4b91b..92f3603a80 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 @@ -472,36 +472,32 @@ public class ArrayUtilTest { String[] a = {"1", "2", "3", "4"}; String[] b = {"a", "b", "c"}; - // 在小于0的位置,-1位置插入,返回b+a + // 在小于0的位置,-1位置插入,返回b+a,新数组 String[] result = ArrayUtil.replace(a, -1, b); Assert.assertArrayEquals(new String[]{"a", "b", "c", "1", "2", "3", "4"}, result); - // 在第0个位置插入,即覆盖a,直接返回b - result = ArrayUtil.replace(a, 0, b); - Assert.assertArrayEquals(new String[]{"a", "b", "c"}, result); + // 在第0个位置开始替换,返回a + result = ArrayUtil.replace(ArrayUtil.clone(a), 0, b); + Assert.assertArrayEquals(new String[]{"a", "b", "c", "4"}, result); - // 在第1个位置插入,即"2"之前 - result = ArrayUtil.replace(a, 1, b); + // 在第1个位置替换,即"2"开始 + result = ArrayUtil.replace(ArrayUtil.clone(a), 1, b); Assert.assertArrayEquals(new String[]{"1", "a", "b", "c"}, result); - //上一步测试修改了原数组结构 - String[] c = {"1", "2", "3", "4"}; - String[] d = {"a", "b", "c"}; - // 在第2个位置插入,即"3"之后 - result = ArrayUtil.replace(c, 2, d); + result = ArrayUtil.replace(ArrayUtil.clone(a), 2, b); Assert.assertArrayEquals(new String[]{"1", "2", "a", "b", "c"}, result); // 在第3个位置插入,即"4"之后 - result = ArrayUtil.replace(c, 3, d); + result = ArrayUtil.replace(ArrayUtil.clone(a), 3, b); Assert.assertArrayEquals(new String[]{"1", "2", "3", "a", "b", "c"}, result); // 在第4个位置插入,数组长度为4,在索引4出替换即两个数组相加 - result = ArrayUtil.replace(c, 4, d); + result = ArrayUtil.replace(ArrayUtil.clone(a), 4, b); Assert.assertArrayEquals(new String[]{"1", "2", "3", "4", "a", "b", "c"}, result); // 在大于3个位置插入,数组长度为4,即两个数组相加 - result = ArrayUtil.replace(c, 5, d); + result = ArrayUtil.replace(ArrayUtil.clone(a), 5, b); Assert.assertArrayEquals(new String[]{"1", "2", "3", "4", "a", "b", "c"}, result); String[] e = null; @@ -509,13 +505,13 @@ public class ArrayUtilTest { // e为null 返回 f result = ArrayUtil.replace(e, -1, f); - Assert.assertArrayEquals(new String[]{"a", "b", "c"}, result); + Assert.assertArrayEquals(f, result); String[] g = {"a", "b", "c"}; String[] h = null; // h为null 返回 g result = ArrayUtil.replace(g, 0, h); - Assert.assertArrayEquals(new String[]{"a", "b", "c"}, result); + Assert.assertArrayEquals(g, result); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/StrUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/StrUtilTest.java index 2dcef2850a..514efd0891 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/StrUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/StrUtilTest.java @@ -391,41 +391,6 @@ public class StrUtilTest { Assert.assertEquals(text, str); } - @Test - public void toCamelCaseTest() { - String str = "Table_Test_Of_day"; - String result = StrUtil.toCamelCase(str); - Assert.assertEquals("tableTestOfDay", result); - - String str1 = "TableTestOfDay"; - String result1 = StrUtil.toCamelCase(str1); - Assert.assertEquals("TableTestOfDay", result1); - - String abc1d = StrUtil.toCamelCase("abc_1d"); - Assert.assertEquals("abc1d", abc1d); - - - String str2 = "Table-Test-Of-day"; - String result2 = StrUtil.toCamelCase(str2, CharUtil.DASHED); - System.out.println(result2); - Assert.assertEquals("tableTestOfDay", result2); - } - - @Test - public void toUnderLineCaseTest() { - Dict.create() - .set("Table_Test_Of_day", "table_test_of_day") - .set("_Table_Test_Of_day_", "_table_test_of_day_") - .set("_Table_Test_Of_DAY_", "_table_test_of_DAY_") - .set("_TableTestOfDAYToday", "_table_test_of_DAY_today") - .set("HelloWorld_test", "hello_world_test") - .set("H2", "H2") - .set("H#case", "H#case") - .set("PNLabel", "PN_label") - .set("DEPT_NAME","DEPT_NAME") - .forEach((key, value) -> Assert.assertEquals(value, StrUtil.toUnderlineCase(key))); - } - @Test public void containsAnyTest() { //字符 -- Gitee From 911f75e6bdf21df717026b26e6864e0dd1a77bc7 Mon Sep 17 00:00:00 2001 From: Looly Date: Mon, 14 Mar 2022 22:49:32 +0800 Subject: [PATCH 46/57] fix bug --- CHANGELOG.md | 3 ++- hutool-http/src/main/java/cn/hutool/http/HttpRequest.java | 7 ++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aa4ee93a76..03a5564817 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ # 🚀Changelog ------------------------------------------------------------------------------------------------------------- -# 5.7.23 (2022-03-13) +# 5.7.23 (2022-03-14) ### 🐣新特性 * 【http 】 HttpRequest.form采用TableMap方式(issue#I4W427@Gitee) @@ -19,6 +19,7 @@ * 【core 】 修复NumberConverter对数字转换的问题(issue#I4WPF4@Gitee) * 【core 】 修复ReflectUtil.getMethods获取接口方法问题(issue#I4WUWR@Gitee) * 【core 】 修复NamingCase中大写转换问题(pr#572@Gitee) +* 【http 】 修复GET重定向时,携带参数问题(issue#2189@Github) ------------------------------------------------------------------------------------------------------------- # 5.7.22 (2022-03-01) 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 927c8bd4d9..73883f0c78 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java @@ -1161,10 +1161,11 @@ public class HttpRequest extends HttpBase { /** * 对于GET请求将参数加到URL中
    - * 此处不对URL中的特殊字符做单独编码 + * 此处不对URL中的特殊字符做单独编码
    + * 对于非rest的GET请求,且处于重定向时,参数丢弃 */ private void urlWithParamIfGet() { - if (Method.GET.equals(method) && false == this.isRest) { + if (Method.GET.equals(method) && false == this.isRest && this.redirectCount > 0) { // 优先使用body形式的参数,不存在使用form if (ArrayUtil.isNotEmpty(this.bodyBytes)) { this.url.getQuery().parse(StrUtil.str(this.bodyBytes, this.charset), this.charset); @@ -1194,7 +1195,7 @@ public class HttpRequest extends HttpBase { if (responseCode != HttpURLConnection.HTTP_OK) { if (HttpStatus.isRedirected(responseCode)) { - setUrl(httpConnection.header(Header.LOCATION)); + setUrl(UrlBuilder.ofHttpWithoutEncode(httpConnection.header(Header.LOCATION))); if (redirectCount < this.maxRedirectCount) { redirectCount++; // 重定向不再走过滤器 -- Gitee From 989f93652fd322e41d67034f40d889dc4d70363c Mon Sep 17 00:00:00 2001 From: Looly Date: Mon, 14 Mar 2022 23:16:25 +0800 Subject: [PATCH 47/57] add config --- CHANGELOG.md | 1 + .../hutool/core/text/csv/CsvBaseReader.java | 2 +- .../cn/hutool/core/text/csv/CsvParser.java | 6 +++--- .../hutool/core/text/csv/CsvReadConfig.java | 21 +++++++++++++++---- 4 files changed, 22 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 03a5564817..6ddda765ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ * 【core 】 增加UniqueKeySet(issue#I4WUWR@Gitee) * 【core 】 阿拉伯数字转换成中文对发票票面金额转换的扩展(pr#570@Gitee) * 【core 】 ArrayUtil增加replace方法(pr#570@Gitee) +* 【core 】 CsvReadConfig增加自定义标题行行号(issue#2180@Github) * ### 🐞Bug修复 * 【core 】 修复ObjectUtil.hasNull传入null返回true的问题(pr#555@Gitee) diff --git a/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvBaseReader.java b/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvBaseReader.java index 84a244e375..2ac2f91868 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvBaseReader.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvBaseReader.java @@ -177,7 +177,7 @@ public class CsvBaseReader implements Serializable { final CsvParser csvParser = parse(reader); final List rows = new ArrayList<>(); read(csvParser, rows::add); - final List header = config.containsHeader ? csvParser.getHeader() : null; + final List header = config.headerLineNo > -1 ? csvParser.getHeader() : null; return new CsvData(header, rows); } diff --git a/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvParser.java b/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvParser.java index 21d996039c..690127f4fd 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvParser.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvParser.java @@ -84,13 +84,13 @@ public final class CsvParser extends ComputeIter implements Closeable, S } /** - * 获取头部字段列表,如果containsHeader设置为false则抛出异常 + * 获取头部字段列表,如果headerLineNo < 0,抛出异常 * * @return 头部列表 * @throws IllegalStateException 如果不解析头部或者没有调用nextRow()方法 */ public List getHeader() { - if (false == config.containsHeader) { + if (config.headerLineNo < 0) { throw new IllegalStateException("No header available - header parsing is disabled"); } if (lineNo < config.beginLineNo) { @@ -152,7 +152,7 @@ public final class CsvParser extends ComputeIter implements Closeable, S } //初始化标题 - if (config.containsHeader && null == header) { + if (lineNo == config.headerLineNo && null == header) { initHeader(currentFields); // 作为标题行后,此行跳过,下一行做为第一行 continue; diff --git a/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvReadConfig.java b/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvReadConfig.java index f9373f8fc0..5ad7c7d854 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvReadConfig.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvReadConfig.java @@ -11,8 +11,8 @@ import java.io.Serializable; public class CsvReadConfig extends CsvConfig implements Serializable { private static final long serialVersionUID = 5396453565371560052L; - /** 是否首行做为标题行,默认false */ - protected boolean containsHeader; + /** 指定标题行号,-1表示无标题行 */ + protected long headerLineNo = -1; /** 是否跳过空白行,默认true */ protected boolean skipEmptyRows = true; /** 每行字段个数不同时是否抛出异常,默认false */ @@ -34,13 +34,26 @@ public class CsvReadConfig extends CsvConfig implements Serializa } /** - * 设置是否首行做为标题行,默认false + * 设置是否首行做为标题行,默认false
    + * 当设置为{@code true}时,默认标题行号是{@link #beginLineNo},{@code false}为-1,表示无行号 * * @param containsHeader 是否首行做为标题行,默认false * @return this + * @see #setHeaderLineNo(long) */ public CsvReadConfig setContainsHeader(boolean containsHeader) { - this.containsHeader = containsHeader; + return setHeaderLineNo(containsHeader ? beginLineNo : -1); + } + + /** + * 设置标题行行号,默认-1,表示无标题行
    + * + * @param headerLineNo 标题行行号,-1表示无标题行 + * @return this + * @since 5.7.23 + */ + public CsvReadConfig setHeaderLineNo(long headerLineNo) { + this.headerLineNo = headerLineNo; return this; } -- Gitee From 1c5e9787339ffb141f6fbe1a626c37ba55c61ddc Mon Sep 17 00:00:00 2001 From: Looly Date: Mon, 14 Mar 2022 23:23:18 +0800 Subject: [PATCH 48/57] fix test --- .../test/java/cn/hutool/core/io/FileCopierTest.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/hutool-core/src/test/java/cn/hutool/core/io/FileCopierTest.java b/hutool-core/src/test/java/cn/hutool/core/io/FileCopierTest.java index 80c50288de..4d8403d7eb 100644 --- a/hutool-core/src/test/java/cn/hutool/core/io/FileCopierTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/io/FileCopierTest.java @@ -7,35 +7,35 @@ import cn.hutool.core.io.file.FileCopier; /** * 文件拷贝单元测试 - * @author Looly * + * @author Looly */ public class FileCopierTest { - + @Test @Ignore public void dirCopyTest() { FileCopier copier = FileCopier.create("D:\\Java", "e:/eclipse/eclipse2.zip"); copier.copy(); } - + @Test @Ignore public void dirCopyTest2() { //测试带.的文件夹复制 FileCopier copier = FileCopier.create("D:\\workspace\\java\\.metadata", "D:\\workspace\\java\\.metadata\\temp"); copier.copy(); - + FileUtil.copy("D:\\workspace\\java\\looly\\hutool\\.git", "D:\\workspace\\java\\temp", true); } - + @Test(expected = IORuntimeException.class) public void dirCopySubTest() { //测试父目录复制到子目录报错 FileCopier copier = FileCopier.create("D:\\workspace\\java\\.metadata", "D:\\workspace\\java\\.metadata\\temp"); copier.copy(); } - + @Test @Ignore public void copyFileToDirTest() { -- Gitee From e5c253042d75afcd704bf289f104fc9081a1f798 Mon Sep 17 00:00:00 2001 From: Looly Date: Mon, 14 Mar 2022 23:30:28 +0800 Subject: [PATCH 49/57] fix file bug --- CHANGELOG.md | 1 + hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java | 2 +- .../src/main/java/cn/hutool/core/io/file/FileCopier.java | 3 +-- .../src/test/java/cn/hutool/core/io/FileCopierTest.java | 4 ++++ 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ddda765ff..3439b0de68 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ * 【core 】 修复ReflectUtil.getMethods获取接口方法问题(issue#I4WUWR@Gitee) * 【core 】 修复NamingCase中大写转换问题(pr#572@Gitee) * 【http 】 修复GET重定向时,携带参数问题(issue#2189@Github) +* 【core 】 修复FileUtil、FileCopier相对路径获取父路径错误问题(pr#2188@Github) ------------------------------------------------------------------------------------------------------------- # 5.7.22 (2022-03-01) 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 e0622f7fc1..8cd3356f49 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 @@ -700,7 +700,7 @@ public class FileUtil extends PathUtil { if (null == file) { return null; } - return mkdir(file.getParentFile()); + return mkdir(getParent(file, 1)); } /** diff --git a/hutool-core/src/main/java/cn/hutool/core/io/file/FileCopier.java b/hutool-core/src/main/java/cn/hutool/core/io/file/FileCopier.java index 6f133f2e37..3e7df82206 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/file/FileCopier.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/file/FileCopier.java @@ -267,8 +267,7 @@ public class FileCopier extends SrcToDestCopier{ } }else { //路径不存在则创建父目录 - //noinspection ResultOfMethodCallIgnored - dest.getAbsoluteFile().getParentFile().mkdirs(); + FileUtil.mkParentDirs(dest); } final ArrayList optionList = new ArrayList<>(2); diff --git a/hutool-core/src/test/java/cn/hutool/core/io/FileCopierTest.java b/hutool-core/src/test/java/cn/hutool/core/io/FileCopierTest.java index 6cfaf37632..8419ca29d8 100644 --- a/hutool-core/src/test/java/cn/hutool/core/io/FileCopierTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/io/FileCopierTest.java @@ -1,5 +1,6 @@ package cn.hutool.core.io; +import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; @@ -48,8 +49,11 @@ public class FileCopierTest { @Test @Ignore public void copyFileByRelativePath(){ + // https://github.com/dromara/hutool/pull/2188 // 当复制的目标文件位置是相对路径的时候可以通过 FileCopier copier = FileCopier.create(new File("pom.xml"),new File("aaa.txt")); copier.copy(); + final boolean delete = new File("aaa.txt").delete(); + Assert.assertTrue(delete); } } -- Gitee From 652f43efdd9070d08f90a5f580f62463b77c911b Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 15 Mar 2022 00:07:59 +0800 Subject: [PATCH 50/57] add test --- .../java/cn/hutool/json/IssueI4XFMWTest.java | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 hutool-json/src/test/java/cn/hutool/json/IssueI4XFMWTest.java diff --git a/hutool-json/src/test/java/cn/hutool/json/IssueI4XFMWTest.java b/hutool-json/src/test/java/cn/hutool/json/IssueI4XFMWTest.java new file mode 100644 index 0000000000..51cffe12c4 --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/IssueI4XFMWTest.java @@ -0,0 +1,42 @@ +package cn.hutool.json; + +import cn.hutool.core.annotation.Alias; +import lombok.Data; +import org.junit.Assert; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +/** + * https://gitee.com/dromara/hutool/issues/I4XFMW + */ +public class IssueI4XFMWTest { + + @Test + public void test() { + List entityList = new ArrayList<>(); + TestEntity entityA = new TestEntity(); + entityA.setId("123"); + entityA.setPassword("456"); + entityList.add(entityA); + + TestEntity entityB = new TestEntity(); + entityB.setId("789"); + entityB.setPassword("098"); + entityList.add(entityB); + + String jsonStr = JSONUtil.toJsonStr(entityList); + Assert.assertEquals("[{\"uid\":\"123\",\"password\":\"456\"},{\"uid\":\"789\",\"password\":\"098\"}]", jsonStr); + List testEntities = JSONUtil.toList(jsonStr, TestEntity.class); + Assert.assertEquals("123", testEntities.get(0).getId()); + Assert.assertEquals("789", testEntities.get(1).getId()); + } + + @Data + static class TestEntity { + @Alias("uid") + private String id; + private String password; + } +} -- Gitee From a2c5115831a5ec5f57aac9b22e595968bb154c5b Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 15 Mar 2022 00:25:27 +0800 Subject: [PATCH 51/57] backuo --- .../hutool/extra/spring/SpringCronUtil.java | 149 ------------------ .../extra/spring/config/SpringCronConfig.java | 31 ---- .../main/resources/META-INF/spring.factories | 4 +- .../extra/spring/SpringCronUtilTest.java | 94 ----------- 4 files changed, 1 insertion(+), 277 deletions(-) delete mode 100644 hutool-extra/src/main/java/cn/hutool/extra/spring/SpringCronUtil.java delete mode 100644 hutool-extra/src/main/java/cn/hutool/extra/spring/config/SpringCronConfig.java delete mode 100644 hutool-extra/src/test/java/cn/hutool/extra/spring/SpringCronUtilTest.java diff --git a/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringCronUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringCronUtil.java deleted file mode 100644 index 29287bdd8a..0000000000 --- a/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringCronUtil.java +++ /dev/null @@ -1,149 +0,0 @@ -package cn.hutool.extra.spring; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.map.MapUtil; -import cn.hutool.core.util.IdUtil; -import org.springframework.scheduling.TaskScheduler; -import org.springframework.scheduling.support.CronTrigger; -import org.springframework.stereotype.Component; - -import javax.annotation.Resource; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ScheduledFuture; - -/** - * Spring 动态定时任务封装 - *
      - *
    1. 创建定时任务
    2. - *
    3. 修改定时任务
    4. - *
    5. 取消定时任务
    6. - *
    7. 高级操作
    8. - *
    - * 参考:Spring doc - * - * @author JC - * @date 03/13 - */ -@Component -public class SpringCronUtil { - /** - * 任务调度器 - */ - private static TaskScheduler taskScheduler; - - /** - * ID 与 Future 绑定 - */ - private static final Map> TASK_FUTURE = MapUtil.newConcurrentHashMap(); - - /** - * ID 与 Runnable 绑定 - */ - private static final Map TASK_RUNNABLE = MapUtil.newConcurrentHashMap(); - - /** - * 加入定时任务 - * - * @param task 任务 - * @param expression 定时任务执行时间的cron表达式 - * @return 定时任务ID - */ - public static String schedule(Runnable task, String expression) { - String id = IdUtil.fastUUID(); - return schedule(id, task, expression); - } - - /** - * 加入定时任务 - * - * @param id 定时任务ID - * @param expression 定时任务执行时间的cron表达式 - * @param task 任务 - * @return 定时任务ID - */ - public static String schedule(Serializable id, Runnable task, String expression) { - ScheduledFuture schedule = taskScheduler.schedule(task, new CronTrigger(expression)); - TASK_FUTURE.put(id, schedule); - TASK_RUNNABLE.put(id, task); - return id.toString(); - } - - /** - * 修改定时任务 - * - * @param id 定时任务ID - * @param expression 定时任务执行时间的cron表达式 - * @return 是否修改成功,{@code false}表示未找到对应ID的任务 - */ - public static boolean update(Serializable id, String expression) { - if (!TASK_FUTURE.containsKey(id)) { - return false; - } - ScheduledFuture future = TASK_FUTURE.get(id); - if (future == null) { - return false; - } - future.cancel(true); - schedule(id, TASK_RUNNABLE.get(id), expression); - return true; - } - - /** - * 移除任务 - * - * @param schedulerId 任务ID - * @return 是否移除成功,{@code false}表示未找到对应ID的任务 - */ - public static boolean cancel(Serializable schedulerId) { - if (!TASK_FUTURE.containsKey(schedulerId)) { - return false; - } - ScheduledFuture future = TASK_FUTURE.get(schedulerId); - boolean cancel = future.cancel(false); - if (cancel) { - TASK_FUTURE.remove(schedulerId); - TASK_RUNNABLE.remove(schedulerId); - } - return cancel; - } - - @Resource - public void setTaskScheduler(TaskScheduler taskScheduler) { - SpringCronUtil.taskScheduler = taskScheduler; - } - - /** - * @return 获得Scheduler对象 - */ - public static TaskScheduler getScheduler() { - return taskScheduler; - } - - /** - * 获得当前运行的所有任务 - * - * @return 所有任务 - */ - public static List getAllTask() { - if (CollUtil.isNotEmpty(TASK_FUTURE.keySet())) { - return new ArrayList<>(TASK_FUTURE.keySet()); - } - return new ArrayList<>(); - } - - /** - * 取消所有的任务 - */ - public static void destroy() { - for (ScheduledFuture future : TASK_FUTURE.values()) { - if (future != null) { - future.cancel(true); - } - } - TASK_FUTURE.clear(); - TASK_RUNNABLE.clear(); - } -} diff --git a/hutool-extra/src/main/java/cn/hutool/extra/spring/config/SpringCronConfig.java b/hutool-extra/src/main/java/cn/hutool/extra/spring/config/SpringCronConfig.java deleted file mode 100644 index 1f31d08320..0000000000 --- a/hutool-extra/src/main/java/cn/hutool/extra/spring/config/SpringCronConfig.java +++ /dev/null @@ -1,31 +0,0 @@ -package cn.hutool.extra.spring.config; - -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.scheduling.TaskScheduler; -import org.springframework.scheduling.annotation.EnableScheduling; -import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; - -/** - * 可自行配置任务线程池, 修改默认参数 - * - * @author JC - * @date 03/13 - */ -@Configuration -@EnableScheduling -public class SpringCronConfig { - @Bean - @ConditionalOnMissingBean(value = TaskScheduler.class) - public TaskScheduler taskScheduler() { - ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); - // 任务线程池初始化 - scheduler.setThreadNamePrefix("TaskScheduler-"); - scheduler.setPoolSize(Runtime.getRuntime().availableProcessors() / 3 + 1); - - // 保证能立刻丢弃运行中的任务 - scheduler.setRemoveOnCancelPolicy(true); - return scheduler; - } -} diff --git a/hutool-extra/src/main/resources/META-INF/spring.factories b/hutool-extra/src/main/resources/META-INF/spring.factories index 0f0032ef8a..09fa11dde2 100644 --- a/hutool-extra/src/main/resources/META-INF/spring.factories +++ b/hutool-extra/src/main/resources/META-INF/spring.factories @@ -1,5 +1,3 @@ # Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ -cn.hutool.extra.spring.SpringUtil,\ -cn.hutool.extra.spring.config.SpringCronConfig,\ -cn.hutool.extra.spring.SpringCronUtil +cn.hutool.extra.spring.SpringUtil diff --git a/hutool-extra/src/test/java/cn/hutool/extra/spring/SpringCronUtilTest.java b/hutool-extra/src/test/java/cn/hutool/extra/spring/SpringCronUtilTest.java deleted file mode 100644 index e4959da270..0000000000 --- a/hutool-extra/src/test/java/cn/hutool/extra/spring/SpringCronUtilTest.java +++ /dev/null @@ -1,94 +0,0 @@ -package cn.hutool.extra.spring; - -import cn.hutool.core.date.DateUtil; -import cn.hutool.extra.spring.config.SpringCronConfig; -import lombok.SneakyThrows; -import lombok.extern.slf4j.Slf4j; -import org.junit.After; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.scheduling.TaskScheduler; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; - -import java.io.Serializable; -import java.time.Duration; -import java.time.Instant; -import java.util.List; - -/** - * @author JC - * @date 03/13 - */ -@Slf4j -@RunWith(SpringJUnit4ClassRunner.class) -@SpringBootTest(classes = {SpringCronConfig.class, SpringCronUtil.class}) -public class SpringCronUtilTest { - /** - * 创建一个定时任务 - * 观察日志可进行验证 - */ - @Test - public void registerTask() { - String ID1 = SpringCronUtil.schedule(this::task, "0/1 * * * * ?"); - String ID2 = SpringCronUtil.schedule(888, this::task, "0/1 * * * * ?"); - log.info("taskId: {},{}", ID1, ID2); - } - - /** - * 修改一个定时任务 - */ - @Test - @SneakyThrows - public void updateTask() { - SpringCronUtil.schedule(888, this::task, "0/1 * * * * ?"); - Thread.sleep(5000); - boolean update = SpringCronUtil.update(888, "0/5 * * * * ?"); - log.info("update task result: {}", update); - } - - /** - * 取消一个定时任务 - */ - @Test - @SneakyThrows - public void cancelTask() { - SpringCronUtil.schedule(888, this::task, "0/1 * * * * ?"); - Thread.sleep(5000); - boolean cancel = SpringCronUtil.cancel(888); - log.info("cancel task result: {}", cancel); - } - - /** - * 高级用法 - * 参考:Spring doc - */ - @Test - public void senior() { - TaskScheduler scheduler = SpringCronUtil.getScheduler(); - // 给定时间 开始, 间隔时间.. - scheduler.scheduleAtFixedRate(this::task, Instant.now(), Duration.ofMinutes(10)); - // ... - } - - /** - * 取消全部定时任务 - * 查看当前所有的任务 - */ - @After - @SneakyThrows - public void cancelAll() { - Thread.sleep(10000); - List allTask = SpringCronUtil.getAllTask(); - log.info("allTask: {}", allTask); - - SpringCronUtil.destroy(); - - allTask = SpringCronUtil.getAllTask(); - log.info("allTask: {}", allTask); - } - - private void task() { - log.info("information only.. (date:{})", DateUtil.now()); - } -} -- Gitee From 3f4079bcede9927b803c47f5b6fcf43597e9788f Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 15 Mar 2022 01:09:08 +0800 Subject: [PATCH 52/57] update Mongo4.x support --- CHANGELOG.md | 3 +- hutool-db/pom.xml | 10 +- .../cn/hutool/db/nosql/mongo/MongoDS.java | 74 ++-- .../cn/hutool/db/nosql/mongo/MongoDS4.java | 404 ------------------ .../hutool/db/nosql/mongo/MongoFactory.java | 21 +- .../hutool/db/nosql/mongo/MongoFactory4.java | 120 ------ .../java/cn/hutool/db/nosql/MongoDBTest.java | 6 +- 7 files changed, 59 insertions(+), 579 deletions(-) delete mode 100644 hutool-db/src/main/java/cn/hutool/db/nosql/mongo/MongoDS4.java delete mode 100644 hutool-db/src/main/java/cn/hutool/db/nosql/mongo/MongoFactory4.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 3439b0de68..e7434e9d6b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ # 🚀Changelog ------------------------------------------------------------------------------------------------------------- -# 5.7.23 (2022-03-14) +# 5.7.23 (2022-03-15) ### 🐣新特性 * 【http 】 HttpRequest.form采用TableMap方式(issue#I4W427@Gitee) @@ -14,6 +14,7 @@ * 【core 】 阿拉伯数字转换成中文对发票票面金额转换的扩展(pr#570@Gitee) * 【core 】 ArrayUtil增加replace方法(pr#570@Gitee) * 【core 】 CsvReadConfig增加自定义标题行行号(issue#2180@Github) +* 【db 】 增加MongoDB4.x支持(pr#568@Gitee) * ### 🐞Bug修复 * 【core 】 修复ObjectUtil.hasNull传入null返回true的问题(pr#555@Gitee) diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml index f14a0cc1b4..ed7e1aed54 100644 --- a/hutool-db/pom.xml +++ b/hutool-db/pom.xml @@ -20,10 +20,9 @@ 0.9.5.5 2.9.0 - 10.0.14 + 10.0.16 1.2.8 2.4.13 - 3.12.10 4.5.0 3.36.0.3 @@ -97,13 +96,6 @@ ${dbcp2.version} true
    - - - org.mongodb - mongo-java-driver - ${mongo.version} - true - org.mongodb mongodb-driver-sync diff --git a/hutool-db/src/main/java/cn/hutool/db/nosql/mongo/MongoDS.java b/hutool-db/src/main/java/cn/hutool/db/nosql/mongo/MongoDS.java index 8158f0d33a..65ce4fa7aa 100644 --- a/hutool-db/src/main/java/cn/hutool/db/nosql/mongo/MongoDS.java +++ b/hutool-db/src/main/java/cn/hutool/db/nosql/mongo/MongoDS.java @@ -6,29 +6,35 @@ import cn.hutool.core.util.StrUtil; import cn.hutool.db.DbRuntimeException; import cn.hutool.log.Log; import cn.hutool.setting.Setting; -import com.mongodb.MongoClient; -import com.mongodb.MongoClientOptions; -import com.mongodb.MongoClientOptions.Builder; +import com.mongodb.MongoClientSettings; import com.mongodb.MongoCredential; import com.mongodb.ServerAddress; +import com.mongodb.client.MongoClient; +import com.mongodb.client.MongoClients; import com.mongodb.client.MongoCollection; import com.mongodb.client.MongoDatabase; +import com.mongodb.connection.ConnectionPoolSettings; +import com.mongodb.connection.SocketSettings; import org.bson.Document; import java.io.Closeable; import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import java.util.concurrent.TimeUnit; /** - * MongoDB工具类 - * - * @author xiaoleilu + * MongoDB4工具类 * + * @author VampireAchao */ public class MongoDS implements Closeable { + private final static Log log = Log.get(); - /** 默认配置文件 */ + /** + * 默认配置文件 + */ public final static String MONGO_CONFIG_PATH = "config/mongo.setting"; // MongoDB配置文件 @@ -41,6 +47,7 @@ public class MongoDS implements Closeable { private MongoClient mongo; // --------------------------------------------------------------------------- Constructor start + /** * 构造MongoDB数据源
    * 调用者必须持有MongoDS实例,否则会被垃圾回收导致写入失败! @@ -58,8 +65,8 @@ public class MongoDS implements Closeable { * 调用者必须持有MongoDS实例,否则会被垃圾回收导致写入失败! * * @param mongoSetting MongoDB的配置文件,如果是null则读取默认配置文件或者使用MongoDB默认客户端配置 - * @param host 主机(域名或者IP) - * @param port 端口 + * @param host 主机(域名或者IP) + * @param port 端口 */ public MongoDS(Setting mongoSetting, String host, int port) { this.setting = mongoSetting; @@ -86,7 +93,7 @@ public class MongoDS implements Closeable { * 官方文档: http://docs.mongodb.org/manual/administration/replica-sets/ * * @param mongoSetting MongoDB的配置文件,必须有 - * @param groups 分组列表,当为null或空时使用无分组配置,一个分组使用单一模式,否则使用副本集模式 + * @param groups 分组列表,当为null或空时使用无分组配置,一个分组使用单一模式,否则使用副本集模式 */ public MongoDS(Setting mongoSetting, String... groups) { if (mongoSetting == null) { @@ -146,11 +153,13 @@ public class MongoDS implements Closeable { final MongoCredential credentail = createCredentail(group); try { - if (null == credentail) { - mongo = new MongoClient(serverAddress, buildMongoClientOptions(group)); - } else { - mongo = new MongoClient(serverAddress, credentail, buildMongoClientOptions(group)); + MongoClientSettings.Builder clusterSettingsBuilder = MongoClientSettings.builder() + .applyToClusterSettings(b -> b.hosts(Collections.singletonList(serverAddress))); + buildMongoClientSettings(clusterSettingsBuilder, group); + if (null != credentail) { + clusterSettingsBuilder.credential(credentail); } + mongo = MongoClients.create(clusterSettingsBuilder.build()); } catch (Exception e) { throw new DbRuntimeException(StrUtil.format("Init MongoDB pool with connection to [{}] error!", serverAddress), e); } @@ -192,11 +201,13 @@ public class MongoDS implements Closeable { final MongoCredential credentail = createCredentail(StrUtil.EMPTY); try { - if (null == credentail) { - mongo = new MongoClient(addrList, buildMongoClientOptions(StrUtil.EMPTY)); - } else { - mongo = new MongoClient(addrList, credentail, buildMongoClientOptions(StrUtil.EMPTY)); + MongoClientSettings.Builder clusterSettingsBuilder = MongoClientSettings.builder() + .applyToClusterSettings(b -> b.hosts(addrList)); + buildMongoClientSettings(clusterSettingsBuilder, StrUtil.EMPTY); + if (null != credentail) { + clusterSettingsBuilder.credential(credentail); } + mongo = MongoClients.create(clusterSettingsBuilder.build()); } catch (Exception e) { log.error(e, "Init MongoDB connection error!"); return; @@ -234,7 +245,7 @@ public class MongoDS implements Closeable { /** * 获得MongoDB中指定集合对象 * - * @param dbName 库名 + * @param dbName 库名 * @param collectionName 集合名 * @return DBCollection */ @@ -248,6 +259,7 @@ public class MongoDS implements Closeable { } // --------------------------------------------------------------------------- Private method start + /** * 创建ServerAddress对象,会读取配置文件中的相关信息 * @@ -291,7 +303,7 @@ public class MongoDS implements Closeable { */ private MongoCredential createCredentail(String group) { final Setting setting = this.setting; - if(null == setting) { + if (null == setting) { return null; } final String user = setting.getStr("user", group, setting.getStr("user")); @@ -316,23 +328,13 @@ public class MongoDS implements Closeable { return MongoCredential.createCredential(userName, database, password.toCharArray()); } - /** - * 构件MongoDB连接选项
    - * - * @param group 分组,当分组对应的选项不存在时会读取根选项,如果也不存在使用默认值 - * @return MongoClientOptions - */ - private MongoClientOptions buildMongoClientOptions(String group) { - return buildMongoClientOptions(MongoClientOptions.builder(), group).build(); - } - /** * 构件MongoDB连接选项
    * * @param group 分组,当分组对应的选项不存在时会读取根选项,如果也不存在使用默认值 * @return Builder */ - private Builder buildMongoClientOptions(Builder builder, String group) { + private MongoClientSettings.Builder buildMongoClientSettings(MongoClientSettings.Builder builder, String group) { if (setting == null) { return builder; } @@ -348,8 +350,9 @@ public class MongoDS implements Closeable { if (StrUtil.isBlank(group) == false && connectionsPerHost == null) { connectionsPerHost = setting.getInt("connectionsPerHost"); } + ConnectionPoolSettings.Builder connectionPoolSettingsBuilder = ConnectionPoolSettings.builder(); if (connectionsPerHost != null) { - builder.connectionsPerHost(connectionsPerHost); + connectionPoolSettingsBuilder.maxSize(connectionsPerHost); log.debug("MongoDB connectionsPerHost: {}", connectionsPerHost); } @@ -359,9 +362,10 @@ public class MongoDS implements Closeable { setting.getInt("connectTimeout"); } if (connectTimeout != null) { - builder.connectTimeout(connectTimeout); + connectionPoolSettingsBuilder.maxWaitTime(connectTimeout, TimeUnit.MILLISECONDS); log.debug("MongoDB connectTimeout: {}", connectTimeout); } + builder.applyToConnectionPoolSettings(b -> b.applySettings(connectionPoolSettingsBuilder.build())); // 套接字超时时间;该值会被传递给Socket.setSoTimeout(int)。默以为0(无穷) --int Integer socketTimeout = setting.getInt(group + "socketTimeout"); @@ -369,7 +373,8 @@ public class MongoDS implements Closeable { setting.getInt("socketTimeout"); } if (socketTimeout != null) { - builder.socketTimeout(socketTimeout); + SocketSettings socketSettings = SocketSettings.builder().connectTimeout(socketTimeout, TimeUnit.MILLISECONDS).build(); + builder.applyToSocketSettings(b -> b.applySettings(socketSettings)); log.debug("MongoDB socketTimeout: {}", socketTimeout); } @@ -388,4 +393,5 @@ public class MongoDS implements Closeable { return this.setting; } // --------------------------------------------------------------------------- Private method end + } diff --git a/hutool-db/src/main/java/cn/hutool/db/nosql/mongo/MongoDS4.java b/hutool-db/src/main/java/cn/hutool/db/nosql/mongo/MongoDS4.java deleted file mode 100644 index 0b2ff0f620..0000000000 --- a/hutool-db/src/main/java/cn/hutool/db/nosql/mongo/MongoDS4.java +++ /dev/null @@ -1,404 +0,0 @@ -package cn.hutool.db.nosql.mongo; - -import cn.hutool.core.exceptions.NotInitedException; -import cn.hutool.core.net.NetUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.db.DbRuntimeException; -import cn.hutool.log.Log; -import cn.hutool.setting.Setting; -import com.mongodb.MongoClientSettings; -import com.mongodb.MongoCredential; -import com.mongodb.MongoDriverInformation; -import com.mongodb.ServerAddress; -import com.mongodb.client.MongoClient; -import com.mongodb.client.MongoCollection; -import com.mongodb.client.MongoDatabase; -import com.mongodb.client.internal.MongoClientImpl; -import com.mongodb.connection.ConnectionPoolSettings; -import com.mongodb.connection.SocketSettings; -import org.bson.Document; - -import java.io.Closeable; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.TimeUnit; - -/** - * MongoDB4工具类 - * - * @author VampireAchao - */ -public class MongoDS4 implements Closeable { - - private final static Log log = Log.get(); - - /** - * 默认配置文件 - */ - public final static String MONGO_CONFIG_PATH = "config/mongo.setting"; - - // MongoDB配置文件 - private Setting setting; - // MongoDB实例连接列表 - private String[] groups; - // MongoDB单点连接信息 - private ServerAddress serverAddress; - // MongoDB客户端对象 - private MongoClient mongo; - - // --------------------------------------------------------------------------- Constructor start - - /** - * 构造MongoDB数据源
    - * 调用者必须持有MongoDS实例,否则会被垃圾回收导致写入失败! - * - * @param host 主机(域名或者IP) - * @param port 端口 - */ - public MongoDS4(String host, int port) { - this.serverAddress = createServerAddress(host, port); - initSingle(); - } - - /** - * 构造MongoDB数据源
    - * 调用者必须持有MongoDS实例,否则会被垃圾回收导致写入失败! - * - * @param mongoSetting MongoDB的配置文件,如果是null则读取默认配置文件或者使用MongoDB默认客户端配置 - * @param host 主机(域名或者IP) - * @param port 端口 - */ - public MongoDS4(Setting mongoSetting, String host, int port) { - this.setting = mongoSetting; - this.serverAddress = createServerAddress(host, port); - initSingle(); - } - - /** - * 构造MongoDB数据源
    - * 当提供多个数据源时,这些数据源将为一个副本集或者多个mongos
    - * 调用者必须持有MongoDS实例,否则会被垃圾回收导致写入失败! 官方文档: http://docs.mongodb.org/manual/administration/replica-sets/ - * - * @param groups 分组列表,当为null或空时使用无分组配置,一个分组使用单一模式,否则使用副本集模式 - */ - public MongoDS4(String... groups) { - this.groups = groups; - init(); - } - - /** - * 构造MongoDB数据源
    - * 当提供多个数据源时,这些数据源将为一个副本集或者mongos
    - * 调用者必须持有MongoDS实例,否则会被垃圾回收导致写入失败!
    - * 官方文档: http://docs.mongodb.org/manual/administration/replica-sets/ - * - * @param mongoSetting MongoDB的配置文件,必须有 - * @param groups 分组列表,当为null或空时使用无分组配置,一个分组使用单一模式,否则使用副本集模式 - */ - public MongoDS4(Setting mongoSetting, String... groups) { - if (mongoSetting == null) { - throw new DbRuntimeException("Mongo setting is null!"); - } - this.setting = mongoSetting; - this.groups = groups; - init(); - } - // --------------------------------------------------------------------------- Constructor end - - /** - * 初始化,当给定分组数大于一个时使用 - */ - public void init() { - if (groups != null && groups.length > 1) { - initCloud(); - } else { - initSingle(); - } - } - - /** - * 初始化
    - * 设定文件中的host和端口有三种形式: - * - *
    -	 * host = host:port
    -	 * 
    - * - *
    -	 * host = host
    -	 * port = port
    -	 * 
    - * - *
    -	 * host = host
    -	 * 
    - */ - synchronized public void initSingle() { - if (setting == null) { - try { - setting = new Setting(MONGO_CONFIG_PATH, true); - } catch (Exception e) { - // 在single模式下,可以没有配置文件。 - } - } - - String group = StrUtil.EMPTY; - if (null == this.serverAddress) { - //存在唯一分组 - if (groups != null && groups.length == 1) { - group = groups[0]; - } - serverAddress = createServerAddress(group); - } - - final MongoCredential credentail = createCredentail(group); - try { - MongoClientSettings.Builder clusterSettingsBuilder = MongoClientSettings.builder().applyToClusterSettings(b -> b.hosts(Collections.singletonList(serverAddress))); - if (null != credentail) { - clusterSettingsBuilder.credential(credentail); - } - mongo = new MongoClientImpl(clusterSettingsBuilder.build(), MongoDriverInformation.builder().build()); - } catch (Exception e) { - throw new DbRuntimeException(StrUtil.format("Init MongoDB pool with connection to [{}] error!", serverAddress), e); - } - - log.info("Init MongoDB pool with connection to [{}]", serverAddress); - } - - /** - * 初始化集群
    - * 集群的其它客户端设定参数使用全局设定
    - * 集群中每一个实例成员用一个group表示,例如: - * - *
    -	 * user = test1
    -	 * pass = 123456
    -	 * database = test
    -	 * [db0]
    -	 * host = 192.168.1.1:27117
    -	 * [db1]
    -	 * host = 192.168.1.1:27118
    -	 * [db2]
    -	 * host = 192.168.1.1:27119
    -	 * 
    - */ - synchronized public void initCloud() { - if (groups == null || groups.length == 0) { - throw new DbRuntimeException("Please give replication set groups!"); - } - - if (setting == null) { - // 若未指定配置文件,则使用默认配置文件 - setting = new Setting(MONGO_CONFIG_PATH, true); - } - - final List addrList = new ArrayList<>(); - for (String group : groups) { - addrList.add(createServerAddress(group)); - } - - final MongoCredential credentail = createCredentail(StrUtil.EMPTY); - try { - MongoClientSettings.Builder clusterSettingsBuilder = MongoClientSettings.builder().applyToClusterSettings(b -> b.hosts(addrList)); - if (null != credentail) { - clusterSettingsBuilder.credential(credentail); - } - mongo = new MongoClientImpl(clusterSettingsBuilder.build(), MongoDriverInformation.builder().build()); - } catch (Exception e) { - log.error(e, "Init MongoDB connection error!"); - return; - } - - log.info("Init MongoDB cloud Set pool with connection to {}", addrList); - } - - /** - * 设定MongoDB配置文件 - * - * @param setting 配置文件 - */ - public void setSetting(Setting setting) { - this.setting = setting; - } - - /** - * @return 获得MongoDB客户端对象 - */ - public MongoClient getMongo() { - return mongo; - } - - /** - * 获得DB - * - * @param dbName DB - * @return DB - */ - public MongoDatabase getDb(String dbName) { - return mongo.getDatabase(dbName); - } - - /** - * 获得MongoDB中指定集合对象 - * - * @param dbName 库名 - * @param collectionName 集合名 - * @return DBCollection - */ - public MongoCollection getCollection(String dbName, String collectionName) { - return getDb(dbName).getCollection(collectionName); - } - - @Override - public void close() { - mongo.close(); - } - - // --------------------------------------------------------------------------- Private method start - - /** - * 创建ServerAddress对象,会读取配置文件中的相关信息 - * - * @param group 分组,如果为{@code null}或者""默认为无分组 - * @return ServerAddress - */ - private ServerAddress createServerAddress(String group) { - final Setting setting = checkSetting(); - - if (group == null) { - group = StrUtil.EMPTY; - } - - final String tmpHost = setting.getByGroup("host", group); - if (StrUtil.isBlank(tmpHost)) { - throw new NotInitedException("Host name is empy of group: {}", group); - } - - final int defaultPort = setting.getInt("port", group, 27017); - return new ServerAddress(NetUtil.buildInetSocketAddress(tmpHost, defaultPort)); - } - - /** - * 创建ServerAddress对象 - * - * @param host 主机域名或者IP(如果为空默认127.0.0.1) - * @param port 端口(如果为空默认为) - * @return ServerAddress - */ - private ServerAddress createServerAddress(String host, int port) { - return new ServerAddress(host, port); - } - - /** - * 创建{@link MongoCredential},用于服务端验证
    - * 此方法会首先读取指定分组下的属性,用户没有定义则读取空分组下的属性 - * - * @param group 分组 - * @return {@link MongoCredential},如果用户未指定用户名密码返回null - * @since 4.1.20 - */ - private MongoCredential createCredentail(String group) { - final Setting setting = this.setting; - if (null == setting) { - return null; - } - final String user = setting.getStr("user", group, setting.getStr("user")); - final String pass = setting.getStr("pass", group, setting.getStr("pass")); - final String database = setting.getStr("database", group, setting.getStr("database")); - return createCredentail(user, database, pass); - } - - /** - * 创建{@link MongoCredential},用于服务端验证 - * - * @param userName 用户名 - * @param database 数据库名 - * @param password 密码 - * @return {@link MongoCredential} - * @since 4.1.20 - */ - private MongoCredential createCredentail(String userName, String database, String password) { - if (StrUtil.hasEmpty(userName, database, database)) { - return null; - } - return MongoCredential.createCredential(userName, database, password.toCharArray()); - } - - /** - * 构件MongoDB连接选项
    - * - * @param group 分组,当分组对应的选项不存在时会读取根选项,如果也不存在使用默认值 - * @return MongoClientOptions - */ - private MongoClientSettings buildMongoClientOptions(String group) { - return buildMongoClientOptions(MongoClientSettings.builder(), group).build(); - } - - /** - * 构件MongoDB连接选项
    - * - * @param group 分组,当分组对应的选项不存在时会读取根选项,如果也不存在使用默认值 - * @return Builder - */ - private MongoClientSettings.Builder buildMongoClientOptions(MongoClientSettings.Builder builder, String group) { - if (setting == null) { - return builder; - } - - if (StrUtil.isEmpty(group)) { - group = StrUtil.EMPTY; - } else { - group = group + StrUtil.DOT; - } - - // 每个主机答应的连接数(每个主机的连接池大小),当连接池被用光时,会被阻塞住 - Integer connectionsPerHost = setting.getInt(group + "connectionsPerHost"); - if (StrUtil.isBlank(group) == false && connectionsPerHost == null) { - connectionsPerHost = setting.getInt("connectionsPerHost"); - } - ConnectionPoolSettings.Builder connectionPoolSettingsBuilder = ConnectionPoolSettings.builder(); - if (connectionsPerHost != null) { - connectionPoolSettingsBuilder.maxSize(connectionsPerHost); - log.debug("MongoDB connectionsPerHost: {}", connectionsPerHost); - } - - // 被阻塞线程从连接池获取连接的最长等待时间(ms) --int - Integer connectTimeout = setting.getInt(group + "connectTimeout"); - if (StrUtil.isBlank(group) == false && connectTimeout == null) { - setting.getInt("connectTimeout"); - } - if (connectTimeout != null) { - connectionPoolSettingsBuilder.maxWaitTime(connectTimeout, TimeUnit.MILLISECONDS); - log.debug("MongoDB connectTimeout: {}", connectTimeout); - } - builder.applyToConnectionPoolSettings(b -> b.applySettings(connectionPoolSettingsBuilder.build())); - - // 套接字超时时间;该值会被传递给Socket.setSoTimeout(int)。默以为0(无穷) --int - Integer socketTimeout = setting.getInt(group + "socketTimeout"); - if (StrUtil.isBlank(group) == false && socketTimeout == null) { - setting.getInt("socketTimeout"); - } - if (socketTimeout != null) { - SocketSettings socketSettings = SocketSettings.builder().connectTimeout(socketTimeout, TimeUnit.MILLISECONDS).build(); - builder.applyToSocketSettings(b -> b.applySettings(socketSettings)); - log.debug("MongoDB socketTimeout: {}", socketTimeout); - } - - return builder; - } - - /** - * 检查Setting配置文件 - * - * @return Setting配置文件 - */ - private Setting checkSetting() { - if (null == this.setting) { - throw new DbRuntimeException("Please indicate setting file or create default [{}]", MONGO_CONFIG_PATH); - } - return this.setting; - } - // --------------------------------------------------------------------------- Private method end - -} diff --git a/hutool-db/src/main/java/cn/hutool/db/nosql/mongo/MongoFactory.java b/hutool-db/src/main/java/cn/hutool/db/nosql/mongo/MongoFactory.java index 23fdf8aad3..25717bcb77 100644 --- a/hutool-db/src/main/java/cn/hutool/db/nosql/mongo/MongoFactory.java +++ b/hutool-db/src/main/java/cn/hutool/db/nosql/mongo/MongoFactory.java @@ -10,16 +10,20 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** - * MongoDB工厂类,用于创建 - * @author looly + * {@link MongoDS}工厂类,用于创建 * + * @author Looly, VampireAchao */ public class MongoFactory { - /** 各分组做组合key的时候分隔符 */ + /** + * 各分组做组合key的时候分隔符 + */ private final static String GROUP_SEPRATER = ","; - /** 数据源池 */ + /** + * 数据源池 + */ private static final Map DS_MAP = new ConcurrentHashMap<>(); // JVM关闭前关闭MongoDB连接 @@ -28,6 +32,7 @@ public class MongoFactory { } // ------------------------------------------------------------------------ Get DS start + /** * 获取MongoDB数据源
    * @@ -80,7 +85,7 @@ public class MongoFactory { * 获取MongoDB数据源
    * * @param setting 设定文件 - * @param groups 分组列表 + * @param groups 分组列表 * @return MongoDB连接 */ public static MongoDS getDS(Setting setting, String... groups) { @@ -99,7 +104,7 @@ public class MongoFactory { * 获取MongoDB数据源
    * * @param setting 配置文件 - * @param groups 分组列表 + * @param groups 分组列表 * @return MongoDB连接 */ public static MongoDS getDS(Setting setting, Collection groups) { @@ -111,8 +116,8 @@ public class MongoFactory { * 关闭全部连接 */ public static void closeAll() { - if(MapUtil.isNotEmpty(DS_MAP)){ - for(MongoDS ds : DS_MAP.values()) { + if (MapUtil.isNotEmpty(DS_MAP)) { + for (MongoDS ds : DS_MAP.values()) { ds.close(); } DS_MAP.clear(); diff --git a/hutool-db/src/main/java/cn/hutool/db/nosql/mongo/MongoFactory4.java b/hutool-db/src/main/java/cn/hutool/db/nosql/mongo/MongoFactory4.java deleted file mode 100644 index a099118a4a..0000000000 --- a/hutool-db/src/main/java/cn/hutool/db/nosql/mongo/MongoFactory4.java +++ /dev/null @@ -1,120 +0,0 @@ -package cn.hutool.db.nosql.mongo; - -import cn.hutool.core.map.MapUtil; -import cn.hutool.core.util.ArrayUtil; -import cn.hutool.core.util.RuntimeUtil; -import cn.hutool.setting.Setting; - -import java.util.Collection; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -/** - * MongoDB4工厂类,用于创建 - * @author VampireAchao - */ -public class MongoFactory4 { - - /** 各分组做组合key的时候分隔符 */ - private final static String GROUP_SEPRATER = ","; - - /** 数据源池 */ - private static final Map DS_MAP = new ConcurrentHashMap<>(); - - // JVM关闭前关闭MongoDB连接 - static { - RuntimeUtil.addShutdownHook(MongoFactory4::closeAll); - } - - // ------------------------------------------------------------------------ Get DS start - /** - * 获取MongoDB数据源
    - * - * @param host 主机 - * @param port 端口 - * @return MongoDB连接 - */ - public static MongoDS4 getDS(String host, int port) { - final String key = host + ":" + port; - MongoDS4 ds = DS_MAP.get(key); - if (null == ds) { - // 没有在池中加入之 - ds = new MongoDS4(host, port); - DS_MAP.put(key, ds); - } - - return ds; - } - - /** - * 获取MongoDB数据源
    - * 多个分组名对应的连接组成集群 - * - * @param groups 分组列表 - * @return MongoDB连接 - */ - public static MongoDS4 getDS(String... groups) { - final String key = ArrayUtil.join(groups, GROUP_SEPRATER); - MongoDS4 ds = DS_MAP.get(key); - if (null == ds) { - // 没有在池中加入之 - ds = new MongoDS4(groups); - DS_MAP.put(key, ds); - } - - return ds; - } - - /** - * 获取MongoDB数据源
    - * - * @param groups 分组列表 - * @return MongoDB连接 - */ - public static MongoDS4 getDS(Collection groups) { - return getDS(groups.toArray(new String[0])); - } - - /** - * 获取MongoDB数据源
    - * - * @param setting 设定文件 - * @param groups 分组列表 - * @return MongoDB连接 - */ - public static MongoDS4 getDS(Setting setting, String... groups) { - final String key = setting.getSettingPath() + GROUP_SEPRATER + ArrayUtil.join(groups, GROUP_SEPRATER); - MongoDS4 ds = DS_MAP.get(key); - if (null == ds) { - // 没有在池中加入之 - ds = new MongoDS4(setting, groups); - DS_MAP.put(key, ds); - } - - return ds; - } - - /** - * 获取MongoDB数据源
    - * - * @param setting 配置文件 - * @param groups 分组列表 - * @return MongoDB连接 - */ - public static MongoDS4 getDS(Setting setting, Collection groups) { - return getDS(setting, groups.toArray(new String[0])); - } - // ------------------------------------------------------------------------ Get DS ends - - /** - * 关闭全部连接 - */ - public static void closeAll() { - if(MapUtil.isNotEmpty(DS_MAP)){ - for(MongoDS4 ds : DS_MAP.values()) { - ds.close(); - } - DS_MAP.clear(); - } - } -} diff --git a/hutool-db/src/test/java/cn/hutool/db/nosql/MongoDBTest.java b/hutool-db/src/test/java/cn/hutool/db/nosql/MongoDBTest.java index 67ffadd9f3..866ba0b4cc 100644 --- a/hutool-db/src/test/java/cn/hutool/db/nosql/MongoDBTest.java +++ b/hutool-db/src/test/java/cn/hutool/db/nosql/MongoDBTest.java @@ -1,6 +1,6 @@ package cn.hutool.db.nosql; -import cn.hutool.db.nosql.mongo.MongoFactory4; +import cn.hutool.db.nosql.mongo.MongoFactory; import com.mongodb.client.MongoDatabase; import org.junit.Assert; import org.junit.Ignore; @@ -13,8 +13,8 @@ public class MongoDBTest { @Test @Ignore - public void redisDSTest() { - MongoDatabase db = MongoFactory4.getDS("master").getDb("test"); + public void mongoDSTest() { + MongoDatabase db = MongoFactory.getDS("master").getDb("test"); Assert.assertEquals("test", db.getName()); } } -- Gitee From 23e21f8f9f50f6a10e6177f1c97f7ddf7e14ffcc Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 15 Mar 2022 01:14:05 +0800 Subject: [PATCH 53/57] fix test --- hutool-db/src/test/resources/config/mongo.setting | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hutool-db/src/test/resources/config/mongo.setting b/hutool-db/src/test/resources/config/mongo.setting index dc5ae2b337..b50b4d0264 100644 --- a/hutool-db/src/test/resources/config/mongo.setting +++ b/hutool-db/src/test/resources/config/mongo.setting @@ -13,8 +13,8 @@ socketKeepAlive=false #---------------------------------- MongoDB实例连接 [master] -host = 127.0.0.1:27017 +host = localhost:27017 [slave] -host = 127.0.0.1:27018 +host = localhost:27018 #----------------------------------------------------- -- Gitee From 4671ad1d90e9fc17885db1cb105113e6088db518 Mon Sep 17 00:00:00 2001 From: jcsun <80867809@qq.com> Date: Tue, 15 Mar 2022 14:29:13 +0800 Subject: [PATCH 54/57] =?UTF-8?q?*=20=E6=B7=BB=E5=8A=A0=E8=87=AA=E5=AE=9A?= =?UTF-8?q?=E4=B9=89=E6=B3=A8=E8=A7=A3=20*=20junit=20=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E9=80=9A=E8=BF=87=20-ConditionalOnAnnotation=20-ConditionalOnM?= =?UTF-8?q?issingAnnotation=20=E4=B8=BB=E8=A6=81=E7=94=A8=E4=BA=8E?= =?UTF-8?q?=E5=88=A4=E6=96=AD=20=E9=A1=B9=E7=9B=AE=E4=B8=AD=E6=98=AF?= =?UTF-8?q?=E5=90=A6=E5=AD=98=E5=9C=A8(=E4=BD=BF=E7=94=A8)=E6=9F=90?= =?UTF-8?q?=E6=B3=A8=E8=A7=A3=20-=E5=BF=85=E9=A1=BB=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=E6=9F=90=E4=BA=9B=E6=B3=A8=E8=A7=A3=20=E6=89=8D=E4=BC=9A?= =?UTF-8?q?=E5=B0=86=E8=AF=A5=E7=BB=84=E4=BB=B6=E6=B3=A8=E5=85=A5=E5=88=B0?= =?UTF-8?q?=E5=AE=B9=E5=99=A8=20-=E4=BD=BF=E7=94=A8=E4=BA=86=E6=9F=90?= =?UTF-8?q?=E4=BA=9B=E6=B3=A8=E8=A7=A3=20=E5=88=99=E4=B8=8D=E4=BC=9A?= =?UTF-8?q?=E5=B0=86=E8=AF=A5=E7=BB=84=E4=BB=B6=E6=B3=A8=E5=85=A5=E5=88=B0?= =?UTF-8?q?=E5=AE=B9=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../condition/ConditionalOnAnnotation.java | 23 +++++ .../ConditionalOnMissingAnnotation.java | 17 ++++ .../condition/OnAnnotationCondition.java | 87 +++++++++++++++++++ .../extra/spring/SpringCronUtilTest.java | 34 ++++++++ 4 files changed, 161 insertions(+) create mode 100644 hutool-extra/src/main/java/cn/hutool/extra/spring/condition/ConditionalOnAnnotation.java create mode 100644 hutool-extra/src/main/java/cn/hutool/extra/spring/condition/ConditionalOnMissingAnnotation.java create mode 100644 hutool-extra/src/main/java/cn/hutool/extra/spring/condition/OnAnnotationCondition.java create mode 100644 hutool-extra/src/test/java/cn/hutool/extra/spring/SpringCronUtilTest.java diff --git a/hutool-extra/src/main/java/cn/hutool/extra/spring/condition/ConditionalOnAnnotation.java b/hutool-extra/src/main/java/cn/hutool/extra/spring/condition/ConditionalOnAnnotation.java new file mode 100644 index 0000000000..8255ecb746 --- /dev/null +++ b/hutool-extra/src/main/java/cn/hutool/extra/spring/condition/ConditionalOnAnnotation.java @@ -0,0 +1,23 @@ +package cn.hutool.extra.spring.condition; + +import org.springframework.context.annotation.Conditional; + +import java.lang.annotation.*; + +/** + * @author JC + * @date 03/15 + */ +@Documented +@Conditional(OnAnnotationCondition.class) +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD}) +public @interface ConditionalOnAnnotation { + /** + *

    需要检查的注解类型 当指定的注解都存在项目时, 则注入该组件

    + *

    使用: @ConditionalOnAnnotation({Test.class})

    + * + * @return 检查结果 + */ + Class[] value() default {}; +} diff --git a/hutool-extra/src/main/java/cn/hutool/extra/spring/condition/ConditionalOnMissingAnnotation.java b/hutool-extra/src/main/java/cn/hutool/extra/spring/condition/ConditionalOnMissingAnnotation.java new file mode 100644 index 0000000000..94d4e50a45 --- /dev/null +++ b/hutool-extra/src/main/java/cn/hutool/extra/spring/condition/ConditionalOnMissingAnnotation.java @@ -0,0 +1,17 @@ +package cn.hutool.extra.spring.condition; + +import org.springframework.context.annotation.Conditional; + +import java.lang.annotation.*; + +/** + * @author JC + * @date 03/15 + */ +@Documented +@Conditional(OnAnnotationCondition.class) +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD}) +public @interface ConditionalOnMissingAnnotation { + +} diff --git a/hutool-extra/src/main/java/cn/hutool/extra/spring/condition/OnAnnotationCondition.java b/hutool-extra/src/main/java/cn/hutool/extra/spring/condition/OnAnnotationCondition.java new file mode 100644 index 0000000000..08293d6f9c --- /dev/null +++ b/hutool-extra/src/main/java/cn/hutool/extra/spring/condition/OnAnnotationCondition.java @@ -0,0 +1,87 @@ +package cn.hutool.extra.spring.condition; + + +import org.jetbrains.annotations.NotNull; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.boot.autoconfigure.AutoConfigurationImportFilter; +import org.springframework.context.annotation.Condition; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.context.annotation.ConfigurationCondition; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.MergedAnnotation; +import org.springframework.core.annotation.MergedAnnotations; +import org.springframework.core.annotation.Order; +import org.springframework.core.type.AnnotatedTypeMetadata; + +import java.lang.annotation.Annotation; + +/** + * {@link Condition} and {@link AutoConfigurationImportFilter} + * 检查是否存在特定注解 + * + * @author JC + * @date 03/15 + * @see ConditionalOnAnnotation + * @see ConditionalOnMissingAnnotation + */ +@Order(Ordered.HIGHEST_PRECEDENCE) +public class OnAnnotationCondition implements ConfigurationCondition { + private static final String VALUE = "value"; + + @NotNull + @Override + public ConfigurationPhase getConfigurationPhase() { + // 条件不匹配 @Configuration 也不会注入 + return ConfigurationPhase.REGISTER_BEAN; + } + + @Override + public boolean matches(@NotNull ConditionContext context, AnnotatedTypeMetadata metadata) { + MergedAnnotations annotations = metadata.getAnnotations(); + ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); + // 不存在 beanFactory 不进行注入 + if (beanFactory == null) { + return false; + } + + if (annotations.isPresent(ConditionalOnAnnotation.class)) { + return onAnnotation(beanFactory, annotations); + } + + if (annotations.isPresent(ConditionalOnMissingAnnotation.class)) { + return onMissingAnnotation(beanFactory, annotations); + } + return false; + } + + private boolean onAnnotation(ConfigurableListableBeanFactory beanFactory, MergedAnnotations annotations) { + // 注解参数中, 所有的值 + MergedAnnotation annotation = annotations.get(ConditionalOnAnnotation.class); + Class[] values = annotation.getClassArray(VALUE); + for (Class value : values) { + @SuppressWarnings("unchecked") // 该注解限定了 参数必须是注解, 故忽略警告 + String[] beanNames = beanFactory.getBeanNamesForAnnotation((Class) value); + // 验证 当前注解是否被其他类进行标注 + if (beanNames.length == 0) { + return false; + } + } + return true; + } + + private boolean onMissingAnnotation(ConfigurableListableBeanFactory beanFactory, MergedAnnotations annotations) { + // 注解参数中, 所有的值 + MergedAnnotation annotation = annotations.get(ConditionalOnMissingAnnotation.class); + Class[] values = annotation.getClassArray(VALUE); + for (Class value : values) { + @SuppressWarnings("unchecked") // 该注解限定了 参数必须是注解, 故忽略警告 + String[] beanNames = beanFactory.getBeanNamesForAnnotation((Class) value); + + // 验证 当前注解不能被使用(标注) + if (beanNames.length != 0) { + return false; + } + } + return true; + } +} diff --git a/hutool-extra/src/test/java/cn/hutool/extra/spring/SpringCronUtilTest.java b/hutool-extra/src/test/java/cn/hutool/extra/spring/SpringCronUtilTest.java new file mode 100644 index 0000000000..2c8172767c --- /dev/null +++ b/hutool-extra/src/test/java/cn/hutool/extra/spring/SpringCronUtilTest.java @@ -0,0 +1,34 @@ +package cn.hutool.extra.spring; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * @author JC + * @date 03/15 + */ +@SpringBootTest(classes = {SpringUtil.class, SpringCronUtil.class}) +@RunWith(SpringJUnit4ClassRunner.class) +public class SpringCronUtilTest { + /** + * 测试自定义注解是否生效 + * 需要手动添加注解或取消注解进行测试 + */ + @Test + public void existAnnotation() { + SpringCronUtil bean; + try { + bean = SpringUtil.getBean(SpringCronUtil.class); + } catch (NoSuchBeanDefinitionException e) { + // 没加注解 不会注入, 找不到时会报异常 + Assert.assertNull(null); + return; + } + // 例如: 在SpringUtil上加了@EnableScheduling时, 才会注入组件 + Assert.assertNotNull(bean); + } +} -- Gitee From 80757ccb25c383ddca1d6d960760e3942eedbee6 Mon Sep 17 00:00:00 2001 From: jcsun <80867809@qq.com> Date: Tue, 15 Mar 2022 14:33:01 +0800 Subject: [PATCH 55/57] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=20=E5=8C=85=E4=BF=A1?= =?UTF-8?q?=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cn/hutool/extra/spring/condition/package-info.java | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 hutool-extra/src/main/java/cn/hutool/extra/spring/condition/package-info.java diff --git a/hutool-extra/src/main/java/cn/hutool/extra/spring/condition/package-info.java b/hutool-extra/src/main/java/cn/hutool/extra/spring/condition/package-info.java new file mode 100644 index 0000000000..d9dea5a286 --- /dev/null +++ b/hutool-extra/src/main/java/cn/hutool/extra/spring/condition/package-info.java @@ -0,0 +1,7 @@ +/** + * Spring相关工具封装 + * + * @author looly + * + */ +package cn.hutool.extra.spring.condition; -- Gitee From eca159c4febdaabe0decf12362f03a69f11a9381 Mon Sep 17 00:00:00 2001 From: jcsun <80867809@qq.com> Date: Tue, 15 Mar 2022 15:22:05 +0800 Subject: [PATCH 56/57] =?UTF-8?q?*=20=E6=B7=BB=E5=8A=A0=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E4=BF=A1=E6=81=AF=20=E5=A6=82=E6=9E=9C=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E6=9C=AA=E5=BC=80=E5=90=AF=20=E5=AE=9A=E6=97=B6=E4=BB=BB?= =?UTF-8?q?=E5=8A=A1=20(@EnableScheduling)=20=E5=88=99=E4=B8=8D=E8=BF=9B?= =?UTF-8?q?=E8=A1=8C=20=E5=AE=B9=E5=99=A8=E7=9A=84=E6=B3=A8=E5=85=A5=20?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E7=94=A8=E6=88=B7=20=E8=87=AA=E5=AE=9A?= =?UTF-8?q?=E4=B9=89=20=E4=BB=BB=E5=8A=A1=E7=BA=BF=E7=A8=8B=E6=B1=A0,=20?= =?UTF-8?q?=E5=A6=82=E8=8B=A5=E6=B2=A1=E6=9C=89=E8=87=AA=E5=AE=9A=E4=B9=89?= =?UTF-8?q?=20=E5=88=99=E4=BD=BF=E7=94=A8=E9=BB=98=E8=AE=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../extra/spring/config/SpringCronConfig.java | 30 +++++++++++++++++++ .../extra/spring/config/package-info.java | 6 ++++ 2 files changed, 36 insertions(+) create mode 100644 hutool-extra/src/main/java/cn/hutool/extra/spring/config/SpringCronConfig.java create mode 100644 hutool-extra/src/main/java/cn/hutool/extra/spring/config/package-info.java diff --git a/hutool-extra/src/main/java/cn/hutool/extra/spring/config/SpringCronConfig.java b/hutool-extra/src/main/java/cn/hutool/extra/spring/config/SpringCronConfig.java new file mode 100644 index 0000000000..5182e78482 --- /dev/null +++ b/hutool-extra/src/main/java/cn/hutool/extra/spring/config/SpringCronConfig.java @@ -0,0 +1,30 @@ +package cn.hutool.extra.spring.config; + +import cn.hutool.extra.spring.condition.ConditionalOnAnnotation; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.TaskScheduler; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; + +/** + * @author JC、TCL + * @date 03/15 + */ +@Configuration +public class SpringCronConfig { + @Bean + @ConditionalOnAnnotation(EnableScheduling.class) + @ConditionalOnMissingBean(value = {TaskScheduler.class}) + public TaskScheduler taskScheduler() { + ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); + // 任务线程池初始化 + scheduler.setThreadNamePrefix("TaskScheduler-"); + scheduler.setPoolSize(Runtime.getRuntime().availableProcessors() / 3 + 1); + + // 保证能立刻丢弃运行中的任务 + scheduler.setRemoveOnCancelPolicy(true); + return scheduler; + } +} diff --git a/hutool-extra/src/main/java/cn/hutool/extra/spring/config/package-info.java b/hutool-extra/src/main/java/cn/hutool/extra/spring/config/package-info.java new file mode 100644 index 0000000000..551a6e08e7 --- /dev/null +++ b/hutool-extra/src/main/java/cn/hutool/extra/spring/config/package-info.java @@ -0,0 +1,6 @@ +/** + * Spring config package + * @author JC、TCL + * + */ +package cn.hutool.extra.spring.config; -- Gitee From 8f88bdca09cbfad55d00329825cb5b9a9a3c56ea Mon Sep 17 00:00:00 2001 From: jcsun <80867809@qq.com> Date: Tue, 15 Mar 2022 15:25:22 +0800 Subject: [PATCH 57/57] =?UTF-8?q?*=20=E6=B7=BB=E5=8A=A0=E5=8A=A8=E6=80=81?= =?UTF-8?q?=E5=AE=9A=E6=97=B6=E4=BB=BB=E5=8A=A1=E5=B7=A5=E5=85=B7=E7=B1=BB?= =?UTF-8?q?=20=E8=BF=9B=E8=A1=8C=E4=BA=86Junit=E6=B5=8B=E8=AF=95,=20?= =?UTF-8?q?=E9=9C=80=E8=A6=81=E4=BA=BA=E5=B7=A5=E3=80=82=E5=A4=AA=E9=AB=98?= =?UTF-8?q?=E7=BA=A7=E7=9A=84Junit=20=E4=B8=8D=E6=98=AF=E5=BE=88=E4=BC=9A?= =?UTF-8?q?=E5=86=99=20^=5F^?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hutool/extra/spring/SpringCronUtil.java | 155 ++++++++++++++++++ .../main/resources/META-INF/spring.factories | 4 +- .../extra/spring/SpringCronUtilTest.java | 98 ++++++++++- 3 files changed, 253 insertions(+), 4 deletions(-) create mode 100644 hutool-extra/src/main/java/cn/hutool/extra/spring/SpringCronUtil.java diff --git a/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringCronUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringCronUtil.java new file mode 100644 index 0000000000..99ad38b085 --- /dev/null +++ b/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringCronUtil.java @@ -0,0 +1,155 @@ +package cn.hutool.extra.spring; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.IdUtil; +import cn.hutool.extra.spring.condition.ConditionalOnAnnotation; +import org.springframework.context.annotation.Lazy; +import org.springframework.scheduling.TaskScheduler; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.support.CronTrigger; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ScheduledFuture; + +/** + * Spring 动态定时任务封装 + *
      + *
    1. 创建定时任务
    2. + *
    3. 修改定时任务
    4. + *
    5. 取消定时任务
    6. + *
    7. 高级操作
    8. + *
    + * + * @author JC、TCL + * @date 03/15 + * @see TaskScheduler + * @see TaskScheduler + */ +@Component +@ConditionalOnAnnotation(EnableScheduling.class) +public class SpringCronUtil { + /** + * 任务调度器 + */ + private static TaskScheduler taskScheduler; + + /** + * ID 与 Future 绑定 + */ + private static final Map> TASK_FUTURE = MapUtil.newConcurrentHashMap(); + + /** + * ID 与 Runnable 绑定 + */ + private static final Map TASK_RUNNABLE = MapUtil.newConcurrentHashMap(); + + /** + * 加入定时任务 + * + * @param task 任务 + * @param expression 定时任务执行时间的cron表达式 + * @return 定时任务ID + */ + public static String schedule(Runnable task, String expression) { + String id = IdUtil.fastUUID(); + return schedule(id, task, expression); + } + + /** + * 加入定时任务 + * + * @param id 定时任务ID + * @param expression 定时任务执行时间的cron表达式 + * @param task 任务 + * @return 定时任务ID + */ + public static String schedule(Serializable id, Runnable task, String expression) { + ScheduledFuture schedule = taskScheduler.schedule(task, new CronTrigger(expression)); + TASK_FUTURE.put(id, schedule); + TASK_RUNNABLE.put(id, task); + return id.toString(); + } + + /** + * 修改定时任务 + * + * @param id 定时任务ID + * @param expression 定时任务执行时间的cron表达式 + * @return 是否修改成功,{@code false}表示未找到对应ID的任务 + */ + public static boolean update(Serializable id, String expression) { + if (!TASK_FUTURE.containsKey(id)) { + return false; + } + ScheduledFuture future = TASK_FUTURE.get(id); + if (future == null) { + return false; + } + future.cancel(true); + schedule(id, TASK_RUNNABLE.get(id), expression); + return true; + } + + /** + * 移除任务 + * + * @param schedulerId 任务ID + * @return 是否移除成功,{@code false}表示未找到对应ID的任务 + */ + public static boolean cancel(Serializable schedulerId) { + if (!TASK_FUTURE.containsKey(schedulerId)) { + return false; + } + ScheduledFuture future = TASK_FUTURE.get(schedulerId); + boolean cancel = future.cancel(false); + if (cancel) { + TASK_FUTURE.remove(schedulerId); + TASK_RUNNABLE.remove(schedulerId); + } + return cancel; + } + + @Lazy + @Resource + public void setTaskScheduler(TaskScheduler taskScheduler) { + SpringCronUtil.taskScheduler = taskScheduler; + } + + /** + * @return 获得Scheduler对象 + */ + public static TaskScheduler getScheduler() { + return taskScheduler; + } + + /** + * 获得当前运行的所有任务 + * + * @return 所有任务 + */ + public static List getAllTask() { + if (CollUtil.isNotEmpty(TASK_FUTURE.keySet())) { + return new ArrayList<>(TASK_FUTURE.keySet()); + } + return new ArrayList<>(); + } + + /** + * 取消所有的任务 + */ + public static void destroy() { + for (ScheduledFuture future : TASK_FUTURE.values()) { + if (future != null) { + future.cancel(true); + } + } + TASK_FUTURE.clear(); + TASK_RUNNABLE.clear(); + } +} diff --git a/hutool-extra/src/main/resources/META-INF/spring.factories b/hutool-extra/src/main/resources/META-INF/spring.factories index 09fa11dde2..0f0032ef8a 100644 --- a/hutool-extra/src/main/resources/META-INF/spring.factories +++ b/hutool-extra/src/main/resources/META-INF/spring.factories @@ -1,3 +1,5 @@ # Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ -cn.hutool.extra.spring.SpringUtil +cn.hutool.extra.spring.SpringUtil,\ +cn.hutool.extra.spring.config.SpringCronConfig,\ +cn.hutool.extra.spring.SpringCronUtil diff --git a/hutool-extra/src/test/java/cn/hutool/extra/spring/SpringCronUtilTest.java b/hutool-extra/src/test/java/cn/hutool/extra/spring/SpringCronUtilTest.java index 2c8172767c..a580332c6a 100644 --- a/hutool-extra/src/test/java/cn/hutool/extra/spring/SpringCronUtilTest.java +++ b/hutool-extra/src/test/java/cn/hutool/extra/spring/SpringCronUtilTest.java @@ -1,22 +1,37 @@ package cn.hutool.extra.spring; +import cn.hutool.core.date.DateUtil; +import cn.hutool.extra.spring.config.SpringCronConfig; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.junit.After; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.scheduling.TaskScheduler; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import java.io.Serializable; +import java.time.Duration; +import java.time.Instant; +import java.util.Arrays; +import java.util.List; + /** + * 测试动态定时任务时, 必须用户手动指定 @EnableScheduling + * 否则无法进行测试 默认不开启 (跟Spring一致) * @author JC * @date 03/15 */ -@SpringBootTest(classes = {SpringUtil.class, SpringCronUtil.class}) +@Slf4j @RunWith(SpringJUnit4ClassRunner.class) +@SpringBootTest(classes = {SpringUtil.class, SpringCronUtil.class, SpringCronConfig.class}) public class SpringCronUtilTest { /** - * 测试自定义注解是否生效 - * 需要手动添加注解或取消注解进行测试 + *

    测试自定义注解是否生效

    + *

    需要手动添加注解或取消注解进行测试

    */ @Test public void existAnnotation() { @@ -31,4 +46,81 @@ public class SpringCronUtilTest { // 例如: 在SpringUtil上加了@EnableScheduling时, 才会注入组件 Assert.assertNotNull(bean); } + + /** + *

    创建一个定时任务

    + *

    观察日志可进行验证

    + */ + @Test + public void registerTask() { + Long taskId = 666L; + String expression = "0/1 * * * * ?"; + String[] beanDefinitionNames = SpringUtil.getBeanFactory().getBeanDefinitionNames(); + log.info(Arrays.toString(beanDefinitionNames)); + String ID1 = SpringCronUtil.schedule(this::task, expression); + String ID2 = SpringCronUtil.schedule(taskId, this::task, expression); + log.info("taskId: {},{}", ID1, ID2); + } + + /** + * 修改一个定时任务 + */ + @Test + @SneakyThrows + public void updateTask() { + Long taskId = 888L; + String oidExpression = "0/1 * * * * ?"; + String newExpression = "0/5 * * * * ?"; + SpringCronUtil.schedule(taskId, this::task, oidExpression); + Thread.sleep(5000); + boolean update = SpringCronUtil.update(taskId, newExpression); + log.info("update task result: {}", update); + } + + /** + * 取消一个定时任务 + */ + @Test + @SneakyThrows + public void cancelTask() { + Long taskId = 999L; + String expression = "0/1 * * * * ?"; + SpringCronUtil.schedule(taskId, this::task, expression); + Thread.sleep(5000); + boolean cancel = SpringCronUtil.cancel(taskId); + log.info("cancel task result: {}", cancel); + } + + /** + * 高级用法 + * 参考:Spring 官网 + */ + @Test + public void senior() { + TaskScheduler scheduler = SpringCronUtil.getScheduler(); + // 给定时间 开始, 间隔时间.. + scheduler.scheduleAtFixedRate(this::task, Instant.now(), Duration.ofMinutes(10)); + // ... + } + + /** + * 取消全部定时任务 + * 查看当前所有的任务 + */ + @After + @SneakyThrows + public void cancelAll() { + Thread.sleep(10000); + List allTask = SpringCronUtil.getAllTask(); + log.info("allTask: {}", allTask); + + SpringCronUtil.destroy(); + + allTask = SpringCronUtil.getAllTask(); + log.info("allTask: {}", allTask); + } + + private void task() { + log.info("information only.. (date:{})", DateUtil.now()); + } } -- Gitee