diff --git a/CHANGELOG.md b/CHANGELOG.md index e384f82ffe..63d0f298f6 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,22 @@ # 🚀Changelog +------------------------------------------------------------------------------------------------------------- +# 5.8.36(2025-02-18) + +### 🐣新特性 +* 【crypto 】 增加BCUtil.decodeECPrivateKey方法(issue#3829@Github) +* 【core 】 增加HtmlUtil.cleanEmptyTag方法(pr#3838@Github) +* 【db 】 GlobalDbSetting优化默认配置读取规则,优先读取文件而非jar中的文件(issue#900@Github) +* 【dfa 】 删除StopChar类中存在重复字符(pr#3841@Github) +* 【http 】 支持鸿蒙设备 UA 解析(pr#1301@Gitee) + +### 🐞Bug修复 +* 【aop 】 修复ProxyUtil可能的空指针问题(issue#IBF20Z@Gitee) +* 【core 】 修复XmlUtil转义调用方法错误问题,修复XmlEscape未转义单引号问题(pr#3837@Github) +* 【core 】 修复FileUtil.isAbsolutePath没有判断smb路径问题(pr#1299@Gitee) +* 【core 】 修复AbstractFilter没有检查参数长度问题(issue#3854@Github) + ------------------------------------------------------------------------------------------------------------- # 5.8.35(2024-12-25) diff --git a/README-EN.md b/README-EN.md index 80a055aef1..b22814afe5 100755 --- a/README-EN.md +++ b/README-EN.md @@ -153,18 +153,18 @@ We provide the T-Shirt and Sweater with Hutool Logo, please visit the shop: cn.hutool hutool-all - 5.8.35 + 5.8.36 ``` ### 🍐Gradle ``` -implementation 'cn.hutool:hutool-all:5.8.35' +implementation 'cn.hutool:hutool-all:5.8.36' ``` ## 📥Download -- [Maven Repo](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.8.35/) +- [Maven Repo](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.8.36/) > 🔔️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 9034505b0c..02d05218e6 100755 --- a/README.md +++ b/README.md @@ -146,20 +146,20 @@ Hutool = Hu + tool,是原公司项目底层代码剥离后的开源库,“Hu cn.hutool hutool-all - 5.8.35 + 5.8.36 ``` ### 🍐Gradle ``` -implementation 'cn.hutool:hutool-all:5.8.35' +implementation 'cn.hutool:hutool-all:5.8.36' ``` ### 📥下载jar 点击以下链接,下载`hutool-all-X.X.X.jar`即可: -- [Maven中央库](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.8.35/) +- [Maven中央库](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.8.36/) > 🔔️注意 > Hutool 5.x支持JDK8+,对Android平台没有测试,不能保证所有工具类或工具方法可用。 diff --git a/bin/javadoc.sh b/bin/javadoc.sh index 38eb2b683c..0341e1d221 100755 --- a/bin/javadoc.sh +++ b/bin/javadoc.sh @@ -4,3 +4,8 @@ # 多模块聚合文档,生成在target/site/apidocs exec mvn javadoc:aggregate + +bin_home="$(dirname ${BASH_SOURCE[0]})" + +# 拷贝自定义的index.html到聚合文档目录 +cp -vf $bin_home/../docs/apidocs/index.html $bin_home/../target/reports/apidocs/ diff --git a/bin/version.txt b/bin/version.txt index 8ce621ecff..e37cbd6e42 100755 --- a/bin/version.txt +++ b/bin/version.txt @@ -1 +1 @@ -5.8.35 +5.8.36 diff --git a/docs/js/version.js b/docs/js/version.js index d677c5f769..e09850ebaf 100755 --- a/docs/js/version.js +++ b/docs/js/version.js @@ -1 +1 @@ -var version = '5.8.35' \ No newline at end of file +var version = '5.8.36' \ No newline at end of file diff --git a/hutool-all/pom.xml b/hutool-all/pom.xml index 95a1e8296b..461bbb281d 100755 --- a/hutool-all/pom.xml +++ b/hutool-all/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.35 + 5.8.36 hutool-all diff --git a/hutool-aop/pom.xml b/hutool-aop/pom.xml index 3b96ca4cdb..98d35c75e1 100755 --- a/hutool-aop/pom.xml +++ b/hutool-aop/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.35 + 5.8.36 hutool-aop diff --git a/hutool-aop/src/main/java/cn/hutool/aop/proxy/JdkProxyFactory.java b/hutool-aop/src/main/java/cn/hutool/aop/proxy/JdkProxyFactory.java index 91a5a71298..166b45e2ae 100755 --- a/hutool-aop/src/main/java/cn/hutool/aop/proxy/JdkProxyFactory.java +++ b/hutool-aop/src/main/java/cn/hutool/aop/proxy/JdkProxyFactory.java @@ -12,6 +12,11 @@ public class JdkProxyFactory extends ProxyFactory { private static final long serialVersionUID = 1L; + /** + * 获取单例 + */ + public static JdkProxyFactory INSTANCE = new JdkProxyFactory(); + @Override public T proxy(T target, Aspect aspect) { return ProxyUtil.newProxyInstance(// diff --git a/hutool-aop/src/main/java/cn/hutool/aop/proxy/ProxyFactory.java b/hutool-aop/src/main/java/cn/hutool/aop/proxy/ProxyFactory.java index 96d988971c..db45b9d45d 100755 --- a/hutool-aop/src/main/java/cn/hutool/aop/proxy/ProxyFactory.java +++ b/hutool-aop/src/main/java/cn/hutool/aop/proxy/ProxyFactory.java @@ -59,7 +59,13 @@ public static T createProxy(T target, Class aspectClass) { * @return 代理对象 */ public static T createProxy(T target, Aspect aspect) { - return create().proxy(target, aspect); + ProxyFactory factory = create(); + if(null == factory){ + // issue#IBF20Z + // 可能的空指针问题 + factory = JdkProxyFactory.INSTANCE; + } + return factory.proxy(target, aspect); } /** diff --git a/hutool-aop/src/test/java/cn/hutool/aop/test/IssueIBF20ZTest.java b/hutool-aop/src/test/java/cn/hutool/aop/test/IssueIBF20ZTest.java new file mode 100644 index 0000000000..22a2468094 --- /dev/null +++ b/hutool-aop/src/test/java/cn/hutool/aop/test/IssueIBF20ZTest.java @@ -0,0 +1,49 @@ +package cn.hutool.aop.test; + +import cn.hutool.aop.proxy.ProxyFactory; +import cn.hutool.core.lang.Console; +import cn.hutool.core.thread.ThreadUtil; +import org.junit.jupiter.api.Test; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class IssueIBF20ZTest { + + @Test + public void testLoadFirstAvailableConcurrent() throws InterruptedException { + // 创建一个固定大小的线程池 + int threadCount = 1000; + ExecutorService executorService = ThreadUtil.newExecutor(threadCount); + + // 创建一个 CountDownLatch,用于等待所有任务完成 + CountDownLatch latch = new CountDownLatch(threadCount); + + // 计数器用于统计成功加载服务提供者的次数 + AtomicInteger successCount = new AtomicInteger(0); + + // 提交多个任务到线程池 + for (int i = 0; i < threadCount; i++) { + executorService.submit(() -> { + ProxyFactory factory = ProxyFactory.create(); + if (factory != null) { + Console.log(factory.getClass()); + successCount.incrementAndGet(); + } + latch.countDown(); // 每个任务完成时,计数减一 + }); + } + + // 等待所有任务完成 + latch.await(); + + // 关闭线程池并等待所有任务完成 + executorService.shutdown(); + + // 验证所有线程都成功加载了服务提供者 + assertEquals(threadCount, successCount.get()); + } +} diff --git a/hutool-bloomFilter/pom.xml b/hutool-bloomFilter/pom.xml index ff6d81e10c..4f168a9a3e 100755 --- a/hutool-bloomFilter/pom.xml +++ b/hutool-bloomFilter/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.35 + 5.8.36 hutool-bloomFilter diff --git a/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/filter/AbstractFilter.java b/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/filter/AbstractFilter.java index bc37e729d5..327fad055e 100755 --- a/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/filter/AbstractFilter.java +++ b/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/filter/AbstractFilter.java @@ -4,6 +4,7 @@ import cn.hutool.bloomfilter.bitMap.BitMap; import cn.hutool.bloomfilter.bitMap.IntMap; import cn.hutool.bloomfilter.bitMap.LongMap; +import cn.hutool.core.lang.Assert; /** * 抽象Bloom过滤器 @@ -46,7 +47,7 @@ public AbstractFilter(long maxValue) { * @param machineNum 机器位数 */ public void init(long maxValue, int machineNum) { - this.size = maxValue; + this.size = Assert.checkBetween(maxValue, 1, Integer.MAX_VALUE); switch (machineNum) { case BitMap.MACHINE32: bm = new IntMap((int) (size / machineNum)); diff --git a/hutool-bom/pom.xml b/hutool-bom/pom.xml index 1b8b92bd4b..98a3f7e1b2 100755 --- a/hutool-bom/pom.xml +++ b/hutool-bom/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.35 + 5.8.36 hutool-bom diff --git a/hutool-cache/pom.xml b/hutool-cache/pom.xml index 6e302bf304..66a711b3e6 100755 --- a/hutool-cache/pom.xml +++ b/hutool-cache/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.35 + 5.8.36 hutool-cache diff --git a/hutool-captcha/pom.xml b/hutool-captcha/pom.xml index 6ab907469c..a196bd42cc 100755 --- a/hutool-captcha/pom.xml +++ b/hutool-captcha/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.35 + 5.8.36 hutool-captcha diff --git a/hutool-core/pom.xml b/hutool-core/pom.xml index bc1ab47d9f..30520742ca 100755 --- a/hutool-core/pom.xml +++ b/hutool-core/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.35 + 5.8.36 hutool-core 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 b81ac8ad2d..11f8f29bd2 100755 --- a/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java @@ -64,6 +64,10 @@ public class FileUtil extends PathUtil { */ private static final Pattern PATTERN_PATH_ABSOLUTE = Pattern.compile("^[a-zA-Z]:([/\\\\].*)?", Pattern.DOTALL); + /** + * windows的共享文件夹开头 + */ + private static final String SMB_PATH_PREFIX = "\\\\"; /** * 是否为Windows环境 @@ -1370,6 +1374,7 @@ public static String getAbsolutePath(File file) { *
  • 以/开头的路径
  • *
  • 满足类似于 c:/xxxxx,其中祖母随意,不区分大小写
  • *
  • 满足类似于 d:\xxxxx,其中祖母随意,不区分大小写
  • + *
  • 满足windows SMB协议格式,如: \\192.168.254.1\Share
  • * * * @param path 需要检查的Path @@ -1381,7 +1386,7 @@ public static boolean isAbsolutePath(String path) { } // 给定的路径已经是绝对路径了 - return StrUtil.C_SLASH == path.charAt(0) || ReUtil.isMatch(PATTERN_PATH_ABSOLUTE, path); + return StrUtil.C_SLASH == path.charAt(0) || path.startsWith(SMB_PATH_PREFIX) || ReUtil.isMatch(PATTERN_PATH_ABSOLUTE, path); } /** @@ -1667,7 +1672,7 @@ public static String normalize(String path) { } //兼容Windows下的共享目录路径(原始路径如果以\\开头,则保留这种路径) - if (path.startsWith("\\\\")) { + if (path.startsWith(SMB_PATH_PREFIX)) { return path; } diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/FileResource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/FileResource.java index 3fcdea20af..06b661e112 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/FileResource.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/FileResource.java @@ -79,6 +79,9 @@ public URL getUrl(){ @Override public InputStream getStream() throws NoResourceException { + if (!this.file.exists()) { + throw new NoResourceException("File [{}] not exist!", this.file.getAbsolutePath()); + } return FileUtil.getInputStream(this.file); } diff --git a/hutool-core/src/main/java/cn/hutool/core/text/escape/Html4Escape.java b/hutool-core/src/main/java/cn/hutool/core/text/escape/Html4Escape.java index 023ab98bc6..8473e417d2 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/escape/Html4Escape.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/escape/Html4Escape.java @@ -1,6 +1,7 @@ package cn.hutool.core.text.escape; import cn.hutool.core.text.replacer.LookupReplacer; +import cn.hutool.core.text.replacer.ReplacerChain; /** * HTML4的ESCAPE @@ -9,9 +10,21 @@ * @author looly * */ -public class Html4Escape extends XmlEscape { +public class Html4Escape extends ReplacerChain { private static final long serialVersionUID = 1L; + /** + * HTML转义字符
    + * HTML转义相比XML,并不转义单引号
    + * 见:https://stackoverflow.com/questions/1091945/what-characters-do-i-need-to-escape-in-xml-documents + */ + protected static final String[][] BASIC_ESCAPE = { // + {"\"", """}, // " - double-quote + {"&", "&"}, // & - ampersand + {"<", "<"}, // < - less-than + {">", ">"}, // > - greater-than + }; + protected static final String[][] ISO8859_1_ESCAPE = { // { "\u00A0", " " }, // non-breaking space { "\u00A1", "¡" }, // inverted exclamation mark @@ -310,6 +323,7 @@ public class Html4Escape extends XmlEscape { public Html4Escape() { super(); + addChain(new LookupReplacer(BASIC_ESCAPE)); addChain(new LookupReplacer(ISO8859_1_ESCAPE)); addChain(new LookupReplacer(HTML40_EXTENDED_ESCAPE)); } diff --git a/hutool-core/src/main/java/cn/hutool/core/text/escape/XmlEscape.java b/hutool-core/src/main/java/cn/hutool/core/text/escape/XmlEscape.java index 8aba9e2a79..461b1c90a6 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/escape/XmlEscape.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/escape/XmlEscape.java @@ -22,7 +22,7 @@ public class XmlEscape extends ReplacerChain { private static final long serialVersionUID = 1L; protected static final String[][] BASIC_ESCAPE = { // -// {"'", "'"}, // " - single-quote + {"'", "'"}, // " - single-quote {"\"", """}, // " - double-quote {"&", "&"}, // & - ampersand {"<", "<"}, // < - less-than diff --git a/hutool-core/src/main/java/cn/hutool/core/text/escape/XmlUnescape.java b/hutool-core/src/main/java/cn/hutool/core/text/escape/XmlUnescape.java index 80073a9c17..dee7988512 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/escape/XmlUnescape.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/escape/XmlUnescape.java @@ -13,8 +13,6 @@ public class XmlUnescape extends ReplacerChain { private static final long serialVersionUID = 1L; protected static final String[][] BASIC_UNESCAPE = InternalEscapeUtil.invert(XmlEscape.BASIC_ESCAPE); - // issue#1118 - protected static final String[][] OTHER_UNESCAPE = new String[][]{new String[]{"'", "'"}}; /** * 构造 @@ -22,6 +20,5 @@ public class XmlUnescape extends ReplacerChain { public XmlUnescape() { addChain(new LookupReplacer(BASIC_UNESCAPE)); addChain(new NumericEntityUnescaper()); - addChain(new LookupReplacer(OTHER_UNESCAPE)); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/XmlUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/XmlUtil.java index 1b42ff9a3c..5a4b17ce17 100755 --- a/hutool-core/src/main/java/cn/hutool/core/util/XmlUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/XmlUtil.java @@ -956,6 +956,7 @@ public static Object getByXPath(String expression, Object source, QName returnTy * < (小于) 替换为 &lt; * > (大于) 替换为 &gt; * " (双引号) 替换为 &quot; + * ' (单引号) 替换为 &apos; * * * @param string 被替换的字符串 @@ -963,7 +964,7 @@ public static Object getByXPath(String expression, Object source, QName returnTy * @since 4.0.8 */ public static String escape(String string) { - return EscapeUtil.escapeHtml4(string); + return EscapeUtil.escapeXml(string); } /** @@ -975,7 +976,7 @@ public static String escape(String string) { * @since 5.0.6 */ public static String unescape(String string) { - return EscapeUtil.unescapeHtml4(string); + return EscapeUtil.unescapeXml(string); } /** diff --git a/hutool-core/src/test/java/cn/hutool/core/bean/IssueIBLTZWTest.java b/hutool-core/src/test/java/cn/hutool/core/bean/IssueIBLTZWTest.java new file mode 100644 index 0000000000..892f840dce --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/bean/IssueIBLTZWTest.java @@ -0,0 +1,61 @@ +package cn.hutool.core.bean; + +import cn.hutool.core.bean.copier.CopyOptions; +import cn.hutool.core.convert.TypeConverter; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.ReflectUtil; +import lombok.Data; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.lang.reflect.Field; +import java.util.Date; + +/** + * 自定义某个字段的转换 + */ +public class IssueIBLTZWTest { + @Test + public void copyTest() { + TestBean bean = new TestBean(); + bean.setName("test"); + bean.setDate(DateUtil.parse("2025-02-17")); + + final TestBean2 testBean2 = new TestBean2(); + BeanUtil.copyProperties(bean, testBean2, createCopyOptions(TestBean2.class)); + Assertions.assertEquals("2025", testBean2.getDate()); + } + + @Data + static class TestBean { + private String name; + private Date date; + } + + @Data + static class TestBean2 { + private String name; + private String date; + } + + static CopyOptions createCopyOptions(Class targetClass) { + CopyOptions copyOptions = CopyOptions.create(); + TypeConverter converter = (TypeConverter) ReflectUtil.getFieldValue(copyOptions, "converter"); + copyOptions + .setIgnoreError(true) // 忽略类型错误,避免自动转换 + .setConverter(null) + .setFieldValueEditor((fieldName, value) -> { + try { + Field targetField = targetClass.getDeclaredField(fieldName); + // Date类型的 value instanceof 结果是String + if (targetField.getType() == String.class && value instanceof Date) { + return DateUtil.format((Date)value, "yyyy"); + } + return converter.convert(targetField.getType(), value); + } catch (NoSuchFieldException e) { + return value; + } + }); + return copyOptions; + } +} 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 5f1fb6b7e7..bce768e790 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 @@ -53,6 +53,16 @@ public void getAbsolutePathTest() { path = FileUtil.getAbsolutePath("d:"); assertEquals("d:", path); + + } + + @Test + public void smbPathTest() { + final String smbPath = "\\\\192.168.1.1\\share\\rc-source"; + final String parseSmbPath = FileUtil.getAbsolutePath(smbPath); + assertEquals(smbPath, parseSmbPath); + assertTrue(FileUtil.isAbsolutePath(smbPath)); + assertTrue(Paths.get(smbPath).isAbsolute()); } @Test diff --git a/hutool-core/src/test/java/cn/hutool/core/io/Issue3846Test.java b/hutool-core/src/test/java/cn/hutool/core/io/Issue3846Test.java new file mode 100644 index 0000000000..98a493dc28 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/io/Issue3846Test.java @@ -0,0 +1,23 @@ +package cn.hutool.core.io; + +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.date.StopWatch; +import cn.hutool.core.io.resource.ResourceUtil; +import cn.hutool.core.lang.Console; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import java.util.concurrent.TimeUnit; + +public class Issue3846Test { + @Test + @Disabled + void readBytesTest() { + final StopWatch stopWatch = DateUtil.createStopWatch(); + stopWatch.start(); + final String filePath = "d:/test/issue3846.data"; + final byte[] bytes = IoUtil.readBytes(ResourceUtil.getStream(filePath), false); + stopWatch.stop(); + Console.log(stopWatch.prettyPrint(TimeUnit.MILLISECONDS)); + } +} 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 0a9500135e..3082e09796 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 @@ -3,12 +3,15 @@ import cn.hutool.core.convert.Convert; import cn.hutool.core.lang.Dict; import cn.hutool.core.lang.Opt; +import cn.hutool.core.lang.TypeReference; import cn.hutool.core.util.StrUtil; import lombok.Builder; import lombok.Data; import org.junit.jupiter.api.Test; import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -61,10 +64,10 @@ public void filterTest() { public void mapTest() { // Add test like a foreigner final Map adjectivesMap = MapUtil.builder() - .put(0, "lovely") - .put(1, "friendly") - .put(2, "happily") - .build(); + .put(0, "lovely") + .put(1, "friendly") + .put(2, "happily") + .build(); final Map resultMap = MapUtil.map(adjectivesMap, (k, v) -> v + " " + PeopleEnum.values()[k].name().toLowerCase()); @@ -80,7 +83,7 @@ public void mapTest() { final Map idUserMap = Stream.iterate(0L, i -> ++i).limit(4).map(i -> User.builder().id(i).name(customers.poll()).build()).collect(Collectors.toMap(User::getId, Function.identity())); // 如你所见,它是一个map,key由分组id,value由用户ids组成,典型的多对多关系 final Map> groupIdUserIdsMap = groups.stream().flatMap(group -> idUserMap.keySet().stream().map(userId -> UserGroup.builder().groupId(group.getId()).userId(userId).build())) - .collect(Collectors.groupingBy(UserGroup::getGroupId, Collectors.mapping(UserGroup::getUserId, Collectors.toList()))); + .collect(Collectors.groupingBy(UserGroup::getGroupId, Collectors.mapping(UserGroup::getUserId, Collectors.toList()))); // 神奇的魔法发生了, 分组id和用户ids组成的map,竟然变成了订单编号和用户实体集合组成的map final Map> groupIdUserMap = MapUtil.map(groupIdUserIdsMap, (groupId, userIds) -> userIds.stream().map(idUserMap::get).collect(Collectors.toList())); @@ -190,11 +193,11 @@ public void toObjectArrayTest() { } @Test - public void sortJoinTest(){ + public void sortJoinTest() { final Map build = MapUtil.builder(new HashMap()) - .put("key1", "value1") - .put("key3", "value3") - .put("key2", "value2").build(); + .put("key1", "value1") + .put("key3", "value3") + .put("key2", "value2").build(); final String join1 = MapUtil.sortJoin(build, StrUtil.EMPTY, StrUtil.EMPTY, false); assertEquals("key1value1key2value2key3value3", join1); @@ -207,7 +210,7 @@ public void sortJoinTest(){ } @Test - public void ofEntriesTest(){ + public void ofEntriesTest() { final Map map = MapUtil.ofEntries(MapUtil.entry("a", 1), MapUtil.entry("b", 2)); assertEquals(2, map.size()); @@ -216,7 +219,19 @@ public void ofEntriesTest(){ } @Test - public void getIntTest(){ + public void ofEntriesSimpleEntryTest() { + final Map map = MapUtil.ofEntries( + MapUtil.entry("a", 1, false), + MapUtil.entry("b", 2, false) + ); + assertEquals(2, map.size()); + + assertEquals(Integer.valueOf(1), map.get("a")); + assertEquals(Integer.valueOf(2), map.get("b")); + } + + @Test + public void getIntTest() { assertThrows(NumberFormatException.class, () -> { final HashMap map = MapUtil.of("age", "d"); final Integer age = MapUtil.getInt(map, "age"); @@ -238,18 +253,613 @@ public void renameKeyTest() { assertEquals("张三", map.get("newName")); } + @Test + public void renameKeyMapEmptyNoChange() { + Map map = new HashMap<>(); + Map result = MapUtil.renameKey(map, "oldKey", "newKey"); + assertTrue(result.isEmpty()); + } + + @Test + public void renameKeyOldKeyNotPresentNoChange() { + Map map = new HashMap<>(); + map.put("anotherKey", "value"); + Map result = MapUtil.renameKey(map, "oldKey", "newKey"); + assertEquals(1, result.size()); + assertEquals("value", result.get("anotherKey")); + } + + @Test + public void renameKeyOldKeyPresentNewKeyNotPresentKeyRenamed() { + Map map = new HashMap<>(); + map.put("oldKey", "value"); + Map result = MapUtil.renameKey(map, "oldKey", "newKey"); + assertEquals(1, result.size()); + assertEquals("value", result.get("newKey")); + } + + @Test + public void renameKeyNewKeyPresentThrowsException() { + Map map = new HashMap<>(); + map.put("oldKey", "value"); + map.put("newKey", "existingValue"); + assertThrows(IllegalArgumentException.class, () -> { + MapUtil.renameKey(map, "oldKey", "newKey"); + }); + } + @Test public void issue3162Test() { final Map map = new HashMap() { private static final long serialVersionUID = 1L; + { - put("a", "1"); - put("b", "2"); - put("c", "3"); - }}; + put("a", "1"); + put("b", "2"); + put("c", "3"); + } + }; final Map filtered = MapUtil.filter(map, "a", "b"); assertEquals(2, filtered.size()); assertEquals("1", filtered.get("a")); assertEquals("2", filtered.get("b")); } + + + @Test + public void partitionNullMapThrowsException() { + assertThrows(IllegalArgumentException.class, () -> MapUtil.partition(null, 2)); + } + + @Test + public void partitionSizeZeroThrowsException() { + Map map = new HashMap<>(); + map.put("a", "1"); + assertThrows(IllegalArgumentException.class, () -> MapUtil.partition(map, 0)); + } + + @Test + public void partitionSizeNegativeThrowsException() { + Map map = new HashMap<>(); + map.put("a", "1"); + assertThrows(IllegalArgumentException.class, () -> MapUtil.partition(map, -1)); + } + + @Test + public void partitionEmptyMapReturnsEmptyList() { + Map map = new HashMap<>(); + List> result = MapUtil.partition(map, 2); + assertTrue(result.isEmpty()); + } + + @Test + public void partitionMapSizeMultipleOfSizePartitionsCorrectly() { + Map map = new HashMap<>(); + map.put("a", "1"); + map.put("b", "2"); + map.put("c", "3"); + map.put("d", "4"); + + List> result = MapUtil.partition(map, 2); + + assertEquals(2, result.size()); + assertEquals(2, result.get(0).size()); + assertEquals(2, result.get(1).size()); + } + + @Test + public void partitionMapSizeNotMultipleOfSizePartitionsCorrectly() { + Map map = new HashMap<>(); + map.put("a", "1"); + map.put("b", "2"); + map.put("c", "3"); + map.put("d", "4"); + map.put("e", "5"); + + List> result = MapUtil.partition(map, 2); + + assertEquals(3, result.size()); + assertEquals(2, result.get(0).size()); + assertEquals(2, result.get(1).size()); + assertEquals(1, result.get(2).size()); + } + + @Test + public void partitionGeneralCasePartitionsCorrectly() { + Map map = new HashMap<>(); + map.put("a", "1"); + map.put("b", "2"); + map.put("c", "3"); + map.put("d", "4"); + map.put("e", "5"); + map.put("f", "6"); + + List> result = MapUtil.partition(map, 3); + + assertEquals(2, result.size()); + assertEquals(3, result.get(0).size()); + assertEquals(3, result.get(1).size()); + } + + + // ---------MapUtil.computeIfAbsentForJdk8 + @Test + public void computeIfAbsentForJdk8KeyExistsReturnsExistingValue() { + Map map = new HashMap<>(); + map.put("key", 10); + Integer result = MapUtil.computeIfAbsentForJdk8(map, "key", k -> 20); + assertEquals(10, result); + } + + @Test + public void computeIfAbsentForJdk8KeyDoesNotExistComputesAndInsertsValue() { + Map map = new HashMap<>(); + Integer result = MapUtil.computeIfAbsentForJdk8(map, "key", k -> 20); + assertEquals(20, result); + assertEquals(20, map.get("key")); + } + + @Test + public void computeIfAbsentForJdk8ConcurrentInsertReturnsOldValue() { + ConcurrentHashMap concurrentMap = new ConcurrentHashMap<>(); + concurrentMap.put("key", 30); + AtomicInteger counter = new AtomicInteger(0); + + // 模拟并发插入 + concurrentMap.computeIfAbsent("key", k -> { + counter.incrementAndGet(); + return 40; + }); + + Integer result = MapUtil.computeIfAbsentForJdk8(concurrentMap, "key", k -> 50); + assertEquals(30, result); + assertEquals(30, concurrentMap.get("key")); + assertEquals(0, counter.get()); + } + + @Test + public void computeIfAbsentForJdk8NullValueComputesAndInsertsValue() { + Map map = new HashMap<>(); + map.put("key", null); + Integer result = MapUtil.computeIfAbsentForJdk8(map, "key", k -> 20); + assertEquals(20, result); + assertEquals(20, map.get("key")); + } + + //--------MapUtil.computeIfAbsent + @Test + public void computeIfAbsentKeyExistsReturnsExistingValue() { + Map map = new HashMap<>(); + map.put("key", 10); + Integer result = MapUtil.computeIfAbsent(map, "key", k -> 20); + assertEquals(10, result); + } + + @Test + public void computeIfAbsentKeyDoesNotExistComputesAndInsertsValue() { + Map map = new HashMap<>(); + Integer result = MapUtil.computeIfAbsent(map, "key", k -> 20); + assertEquals(20, result); + assertEquals(20, map.get("key")); + } + + @Test + public void computeIfAbsentConcurrentInsertReturnsOldValue() { + ConcurrentHashMap concurrentMap = new ConcurrentHashMap<>(); + concurrentMap.put("key", 30); + AtomicInteger counter = new AtomicInteger(0); + + // 模拟并发插入 + concurrentMap.computeIfAbsent("key", k -> { + counter.incrementAndGet(); + return 40; + }); + + Integer result = MapUtil.computeIfAbsent(concurrentMap, "key", k -> 50); + assertEquals(30, result); + assertEquals(30, concurrentMap.get("key")); + assertEquals(0, counter.get()); + } + + @Test + public void computeIfAbsentNullValueComputesAndInsertsValue() { + Map map = new HashMap<>(); + map.put("key", null); + Integer result = MapUtil.computeIfAbsent(map, "key", k -> 20); + assertEquals(20, result); + assertEquals(20, map.get("key")); + } + + @Test + public void computeIfAbsentEmptyMapInsertsValue() { + Map map = new HashMap<>(); + Integer result = MapUtil.computeIfAbsent(map, "newKey", k -> 100); + assertEquals(100, result); + assertEquals(100, map.get("newKey")); + } + + @Test + public void computeIfAbsentJdk8KeyExistsReturnsExistingValue() { + Map map = new HashMap<>(); + // 假设JdkUtil.ISJDK8为true + map.put("key", 10); + Integer result = MapUtil.computeIfAbsent(map, "key", k -> 20); + assertEquals(10, result); + } + + @Test + public void computeIfAbsentJdk8KeyDoesNotExistComputesAndInsertsValue() { + Map map = new HashMap<>(); + // 假设JdkUtil.ISJDK8为true + Integer result = MapUtil.computeIfAbsent(map, "key", k -> 20); + assertEquals(20, result); + assertEquals(20, map.get("key")); + } + + + //----------valuesOfKeys + @Test + public void valuesOfKeysEmptyIteratorReturnsEmptyList() { + Map map = new HashMap<>(); + map.put("a", "1"); + map.put("b", "2"); + map.put("c", "3"); + Iterator emptyIterator = Collections.emptyIterator(); + ArrayList result = MapUtil.valuesOfKeys(map, emptyIterator); + assertEquals(new ArrayList(), result); + } + + @Test + public void valuesOfKeysNonEmptyIteratorReturnsValuesList() { + Map map = new HashMap<>(); + map.put("a", "1"); + map.put("b", "2"); + map.put("c", "3"); + Iterator iterator = new ArrayList() { + private static final long serialVersionUID = -4593258366224032110L; + + { + add("a"); + add("b"); + } + }.iterator(); + ArrayList result = MapUtil.valuesOfKeys(map, iterator); + assertEquals(new ArrayList() { + private static final long serialVersionUID = 7218152799308667271L; + + { + add("1"); + add("2"); + } + }, result); + } + + @Test + public void valuesOfKeysKeysNotInMapReturnsNulls() { + Map map = new HashMap<>(); + map.put("a", "1"); + map.put("b", "2"); + map.put("c", "3"); + Iterator iterator = new ArrayList() { + private static final long serialVersionUID = -5479427021989481058L; + + { + add("d"); + add("e"); + } + }.iterator(); + ArrayList result = MapUtil.valuesOfKeys(map, iterator); + assertEquals(new ArrayList() { + private static final long serialVersionUID = 4390715387901549136L; + + { + add(null); + add(null); + } + }, result); + } + + @Test + public void valuesOfKeysMixedKeysReturnsMixedValues() { + Map map = new HashMap<>(); + map.put("a", "1"); + map.put("b", "2"); + map.put("c", "3"); + Iterator iterator = new ArrayList() { + private static final long serialVersionUID = 8510595063492828968L; + + { + add("a"); + add("d"); + add("b"); + } + }.iterator(); + ArrayList result = MapUtil.valuesOfKeys(map, iterator); + assertEquals(new ArrayList() { + private static final long serialVersionUID = 6383576410597048337L; + { + add("1"); + add(null); + add("2"); + } + }, result); + } + + //--------clear + @Test + public void clearNoMapsProvidedNoAction() { + MapUtil.clear(); + // 预期没有异常发生,且没有Map被处理 + } + + @Test + public void clearEmptyMapNoChange() { + Map map = new HashMap<>(); + MapUtil.clear(map); + assertTrue(map.isEmpty()); + } + + @Test + public void clearNonEmptyMapClearsMap() { + Map map = new HashMap<>(); + map.put("key", "value"); + MapUtil.clear(map); + assertTrue(map.isEmpty()); + } + + @Test + public void clearMultipleMapsClearsNonEmptyMaps() { + Map map1 = new HashMap<>(); + map1.put("key1", "value1"); + + Map map2 = new HashMap<>(); + map2.put("key2", "value2"); + + Map map3 = new HashMap<>(); + + MapUtil.clear(map1, map2, map3); + + assertTrue(map1.isEmpty()); + assertTrue(map2.isEmpty()); + assertTrue(map3.isEmpty()); + } + + @Test + public void clearMixedMapsClearsNonEmptyMaps() { + Map map = new HashMap<>(); + map.put("key", "value"); + + Map emptyMap = new HashMap<>(); + + MapUtil.clear(map, emptyMap); + + assertTrue(map.isEmpty()); + assertTrue(emptyMap.isEmpty()); + } + + //-----empty + + @Test + public void emptyNoParametersReturnsEmptyMap() { + Map emptyMap = MapUtil.empty(); + assertTrue(emptyMap.isEmpty(), "The map should be empty."); + assertSame(Collections.emptyMap(), emptyMap, "The map should be the same instance as Collections.emptyMap()."); + } + + @Test + public void emptyNullMapClassReturnsEmptyMap() { + Map emptyMap = MapUtil.empty(null); + assertTrue(emptyMap.isEmpty(), "The map should be empty."); + assertSame(Collections.emptyMap(), emptyMap, "The map should be the same instance as Collections.emptyMap()."); + } + + @Test + public void emptyNavigableMapClassReturnsEmptyNavigableMap() { + Map map = MapUtil.empty(NavigableMap.class); + assertTrue(map.isEmpty()); + assertInstanceOf(NavigableMap.class, map); + } + + @Test + public void emptySortedMapClassReturnsEmptySortedMap() { + Map map = MapUtil.empty(SortedMap.class); + assertTrue(map.isEmpty()); + assertInstanceOf(SortedMap.class, map); + } + + @Test + public void emptyMapClassReturnsEmptyMap() { + Map map = MapUtil.empty(Map.class); + assertTrue(map.isEmpty()); + } + + @Test + public void emptyUnsupportedMapClassThrowsIllegalArgumentException() { + assertThrows(IllegalArgumentException.class, () -> { + MapUtil.empty(TreeMap.class); + }); + } + + //--------removeNullValue + @Test + public void removeNullValueNullMapReturnsNull() { + Map result = MapUtil.removeNullValue(null); + assertNull(result); + } + + @Test + public void removeNullValueEmptyMapReturnsEmptyMap() { + Map map = new HashMap<>(); + Map result = MapUtil.removeNullValue(map); + assertEquals(0, result.size()); + } + + @Test + public void removeNullValueNoNullValuesReturnsSameMap() { + Map map = new HashMap<>(); + map.put("key1", "value1"); + map.put("key2", "value2"); + + Map result = MapUtil.removeNullValue(map); + + assertEquals(2, result.size()); + assertEquals("value1", result.get("key1")); + assertEquals("value2", result.get("key2")); + } + + @Test + public void removeNullValueWithNullValuesRemovesNullEntries() { + Map map = new HashMap<>(); + map.put("key1", "value1"); + map.put("key2", null); + map.put("key3", "value3"); + + Map result = MapUtil.removeNullValue(map); + + assertEquals(2, result.size()); + assertEquals("value1", result.get("key1")); + assertEquals("value3", result.get("key3")); + assertNull(result.get("key2")); + } + + @Test + public void removeNullValueAllNullValuesReturnsEmptyMap() { + Map map = new HashMap<>(); + map.put("key1", null); + map.put("key2", null); + + Map result = MapUtil.removeNullValue(map); + + assertEquals(0, result.size()); + } + + + //------getQuietly + @Test + public void getQuietlyMapIsNullReturnsDefaultValue() { + String result = MapUtil.getQuietly(null, "key1", new TypeReference() { + }, "default"); + assertEquals("default", result); + result = MapUtil.getQuietly(null, "key1", String.class, "default"); + assertEquals("default", result); + } + + @Test + public void getQuietlyKeyExistsReturnsConvertedValue() { + Map map = new HashMap<>(); + map.put("key1", "value1"); + map.put("key2", 123); + String result = MapUtil.getQuietly(map, "key1", new TypeReference() { + }, "default"); + assertEquals("value1", result); + } + + @Test + public void getQuietlyKeyDoesNotExistReturnsDefaultValue() { + Map map = new HashMap<>(); + map.put("key1", "value1"); + map.put("key2", 123); + String result = MapUtil.getQuietly(map, "key3", new TypeReference() { + }, "default"); + assertEquals("default", result); + } + + @Test + public void getQuietlyConversionFailsReturnsDefaultValue() { + Map map = new HashMap<>(); + map.put("key1", "value1"); + map.put("key2", 123); + Integer result = MapUtil.getQuietly(map, "key1", new TypeReference() { + }, 0); + assertEquals(0, result); + } + + @Test + public void getQuietlyKeyExistsWithCorrectTypeReturnsValue() { + Map map = new HashMap<>(); + map.put("key1", "value1"); + map.put("key2", 123); + Integer result = MapUtil.getQuietly(map, "key2", new TypeReference() { + }, 0); + assertEquals(123, result); + } + + @Test + public void getQuietlyKeyExistsWithNullValueReturnsDefaultValue() { + Map map = new HashMap<>(); + map.put("key1", "value1"); + map.put("key2", 123); + map.put("key3", null); + String result = MapUtil.getQuietly(map, "key3", new TypeReference() { + }, "default"); + assertEquals("default", result); + } + + @Test + public void getMapIsNullReturnsDefaultValue() { + assertNull(MapUtil.get(null, "age", String.class)); + } + + @Test + public void getKeyExistsReturnsConvertedValue() { + Map map = new HashMap<>(); + map.put("age", "18"); + map.put("name", "Hutool"); + assertEquals("18", MapUtil.get(map, "age", String.class)); + } + + @Test + public void getKeyDoesNotExistReturnsDefaultValue() { + Map map = new HashMap<>(); + map.put("age", "18"); + map.put("name", "Hutool"); + assertEquals("default", MapUtil.get(map, "nonexistent", String.class, "default")); + } + + @Test + public void getTypeConversionFailsReturnsDefaultValue() { + Map map = new HashMap<>(); + map.put("age", "18"); + map.put("name", "Hutool"); + assertEquals(18, MapUtil.get(map, "age", Integer.class, 0)); + } + + @Test + public void getQuietlyTypeConversionFailsReturnsDefaultValue() { + Map map = new HashMap<>(); + map.put("age", "18"); + map.put("name", "Hutool"); + assertEquals(0, MapUtil.getQuietly(map, "name", Integer.class, 0)); + } + + @Test + public void getTypeReferenceReturnsConvertedValue() { + Map map = new HashMap<>(); + map.put("age", "18"); + map.put("name", "Hutool"); + assertEquals("18", MapUtil.get(map, "age", new TypeReference() { + })); + } + + @Test + public void getTypeReferenceWithDefaultValueReturnsConvertedValue() { + Map map = new HashMap<>(); + map.put("age", "18"); + map.put("name", "Hutool"); + assertEquals("18", MapUtil.get(map, "age", new TypeReference() { + }, "default")); + } + + @Test + public void getTypeReferenceWithDefaultValueTypeConversionFailsReturnsDefaultValue() { + Map map = new HashMap<>(); + map.put("age", "18"); + map.put("name", "Hutool"); + assertEquals(18, MapUtil.get(map, "age", new TypeReference() { + }, 0)); + + map = null; + assertEquals(0, MapUtil.get(map, "age", new TypeReference() { + }, 0)); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/XmlUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/XmlUtilTest.java index 6f0932082e..373aa323a5 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/XmlUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/XmlUtilTest.java @@ -7,7 +7,7 @@ import cn.hutool.core.map.MapBuilder; import cn.hutool.core.map.MapUtil; import lombok.Data; -import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.w3c.dom.Document; @@ -22,6 +22,8 @@ import java.util.Map; import java.util.Set; +import static org.junit.jupiter.api.Assertions.*; + /** * {@link XmlUtil} 工具类 * @@ -319,7 +321,8 @@ public void formatTest(){ public void escapeTest(){ final String a = "<>"; final String escape = XmlUtil.escape(a); - Console.log(escape); + Assertions.assertEquals("<>", escape); + Assertions.assertEquals("中文“双引号”", XmlUtil.escape("中文“双引号”")); } @Test diff --git a/hutool-cron/pom.xml b/hutool-cron/pom.xml index 78263c9826..2ace039964 100755 --- a/hutool-cron/pom.xml +++ b/hutool-cron/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.35 + 5.8.36 hutool-cron diff --git a/hutool-cron/src/test/java/cn/hutool/cron/demo/CronTest.java b/hutool-cron/src/test/java/cn/hutool/cron/demo/CronTest.java index 2735eae873..f4ca55db0a 100644 --- a/hutool-cron/src/test/java/cn/hutool/cron/demo/CronTest.java +++ b/hutool-cron/src/test/java/cn/hutool/cron/demo/CronTest.java @@ -17,7 +17,7 @@ public class CronTest { @Test @Disabled public void customCronTest() { - CronUtil.schedule("*/2 * * * * *", (Task) () -> Console.log("Task excuted.")); + CronUtil.schedule("*/2 * * * * *", (Task) () -> Console.log("Task executed.")); // 支持秒级别定时任务 CronUtil.setMatchSecond(true); diff --git a/hutool-crypto/pom.xml b/hutool-crypto/pom.xml index 557797fb6a..fee560ee40 100755 --- a/hutool-crypto/pom.xml +++ b/hutool-crypto/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.35 + 5.8.36 hutool-crypto diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/BCUtil.java b/hutool-crypto/src/main/java/cn/hutool/crypto/BCUtil.java index 1b603cc092..f1fb2a1ae3 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/BCUtil.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/BCUtil.java @@ -14,7 +14,9 @@ import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil; import org.bouncycastle.jce.spec.ECNamedCurveSpec; import org.bouncycastle.jce.spec.ECParameterSpec; +import org.bouncycastle.jce.spec.ECPrivateKeySpec; import org.bouncycastle.math.ec.ECCurve; +import org.bouncycastle.util.BigIntegers; import java.io.IOException; import java.io.InputStream; @@ -44,6 +46,27 @@ public static byte[] encodeECPrivateKey(PrivateKey privateKey) { return ((BCECPrivateKey) privateKey).getD().toByteArray(); } + /** + * 解码恢复EC私钥,支持Base64和Hex编码,(基于BouncyCastle) + * + * @param d 私钥d值 + * @param curveName EC曲线名 + * @return 私钥 + * @since 5.8.36 + */ + public static PrivateKey decodeECPrivateKey(final byte[] d, final String curveName) { + final X9ECParameters x9ECParameters = ECUtil.getNamedCurveByName(curveName); + final ECParameterSpec ecSpec = new ECParameterSpec( + x9ECParameters.getCurve(), + x9ECParameters.getG(), + x9ECParameters.getN(), + x9ECParameters.getH() + ); + + final ECPrivateKeySpec privateKeySpec = new ECPrivateKeySpec(BigIntegers.fromUnsignedByteArray(d), ecSpec); + return KeyUtil.generatePrivateKey("EC", privateKeySpec); + } + /** * 编码压缩EC公钥(基于BouncyCastle),即Q值
    * 见:https://www.cnblogs.com/xinzhao/p/8963724.html diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml index ee443457a5..12b9268a09 100755 --- a/hutool-db/pom.xml +++ b/hutool-db/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.35 + 5.8.36 hutool-db diff --git a/hutool-db/src/main/java/cn/hutool/db/GlobalDbConfig.java b/hutool-db/src/main/java/cn/hutool/db/GlobalDbConfig.java index fc720fe72e..0c3ba4ba2c 100644 --- a/hutool-db/src/main/java/cn/hutool/db/GlobalDbConfig.java +++ b/hutool-db/src/main/java/cn/hutool/db/GlobalDbConfig.java @@ -1,6 +1,7 @@ package cn.hutool.db; import cn.hutool.core.io.resource.NoResourceException; +import cn.hutool.core.util.ArrayUtil; import cn.hutool.db.sql.SqlLog; import cn.hutool.log.level.Level; import cn.hutool.setting.Setting; @@ -83,18 +84,28 @@ public static Setting createDbSetting() { throw new NoResourceException("Customize db setting file [{}] not found !", dbSettingPath); } } else { + setting = tryDefaultDbSetting(); + } + return setting; + } + + /** + * 获取自定义或默认位置数据库配置{@link Setting} + * + * @return 数据库配置 + * @since 5.8.36 + */ + private static Setting tryDefaultDbSetting() { + final String[] defaultDbSettingPaths = {"file:" + DEFAULT_DB_SETTING_PATH, "file:" + DEFAULT_DB_SETTING_PATH2, DEFAULT_DB_SETTING_PATH, DEFAULT_DB_SETTING_PATH2}; + for (final String settingPath : defaultDbSettingPaths) { try { - setting = new Setting(DEFAULT_DB_SETTING_PATH, true); - } catch (NoResourceException e) { - // 尝试ClassPath下直接读取配置文件 - try { - setting = new Setting(DEFAULT_DB_SETTING_PATH2, true); - } catch (NoResourceException e2) { - throw new NoResourceException("Default db setting [{}] or [{}] in classpath not found !", DEFAULT_DB_SETTING_PATH, DEFAULT_DB_SETTING_PATH2); - } + return new Setting(settingPath, true); + } catch (final NoResourceException e) { + // ignore } } - return setting; + + throw new NoResourceException("Default db settings [{}] in classpath not found !", ArrayUtil.join(defaultDbSettingPaths, ",")); } /** diff --git a/hutool-dfa/pom.xml b/hutool-dfa/pom.xml index 5927c802fb..44e9726337 100755 --- a/hutool-dfa/pom.xml +++ b/hutool-dfa/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.35 + 5.8.36 hutool-dfa diff --git a/hutool-dfa/src/main/java/cn/hutool/dfa/StopChar.java b/hutool-dfa/src/main/java/cn/hutool/dfa/StopChar.java index 9aaa7b6a9c..d4ffb136d1 100755 --- a/hutool-dfa/src/main/java/cn/hutool/dfa/StopChar.java +++ b/hutool-dfa/src/main/java/cn/hutool/dfa/StopChar.java @@ -1,37 +1,37 @@ package cn.hutool.dfa; -import java.util.Set; - import cn.hutool.core.collection.CollUtil; +import java.util.Set; + /** * 过滤词及一些简单处理 - * + * * @author Looly */ public class StopChar { /** 不需要处理的词,如标点符号、空格等 */ public static final Set STOP_WORD = CollUtil.newHashSet(' ', '\'', '、', '。', // - '·', 'ˉ', 'ˇ', '々', '—', '~', '‖', '…', '‘', '’', '“', '”', '〔', '〕', '〈', '〉', '《', '》', '「', '」', '『', // - '』', '〖', '〗', '【', '】', '±', '+', '-', '×', '÷', '∧', '∨', '∑', '∏', '∪', '∩', '∈', '√', '⊥', '⊙', '∫', // - '∮', '≡', '≌', '≈', '∽', '∝', '≠', '≮', '≯', '≤', '≥', '∞', '∶', '∵', '∴', '∷', '♂', '♀', '°', '′', '〃', // - '℃', '$', '¤', '¢', '£', '‰', '§', '☆', '★', '〇', '○', '●', '◎', '◇', '◆', '□', '■', '△', '▽', '⊿', '▲', // - '▼', '◣', '◤', '◢', '◥', '▁', '▂', '▃', '▄', '▅', '▆', '▇', '█', '▉', '▊', '▋', '▌', '▍', '▎', '▏', '▓', // - '※', '→', '←', '↑', '↓', '↖', '↗', '↘', '↙', '〓', 'ⅰ', 'ⅱ', 'ⅲ', 'ⅳ', 'ⅴ', 'ⅵ', 'ⅶ', 'ⅷ', 'ⅸ', 'ⅹ', '①', // - '②', '③', '④', '⑤', '⑥', '⑦', '⑧', '⑨', '⑩', '⒈', '⒉', '⒊', '⒋', '⒌', '⒍', '⒎', '⒏', '⒐', '⒑', '⒒', '⒓', // - '⒔', '⒕', '⒖', '⒗', '⒘', '⒙', '⒚', '⒛', '⑴', '⑵', '⑶', '⑷', '⑸', '⑹', '⑺', '⑻', '⑼', '⑽', '⑾', '⑿', '⒀', // - '⒁', '⒂', '⒃', '⒄', '⒅', '⒆', '⒇', 'Ⅰ', 'Ⅱ', 'Ⅲ', 'Ⅳ', 'Ⅴ', 'Ⅵ', 'Ⅶ', 'Ⅷ', 'Ⅸ', 'Ⅹ', 'Ⅺ', 'Ⅻ', '!', '”', // - '#', '¥', '%', '&', '’', '(', ')', '*', '+', ',', '-', '.', '/', '0', '1', '2', '3', '4', '5', '6', '7', // - '8', '9', ':', ';', '<', '=', '>', '?', '@', '〔', '\', '〕', '^', '_', '‘', '{', '|', '}', '∏', 'Ρ', '∑', // - 'Υ', 'Φ', 'Χ', 'Ψ', 'Ω', 'α', 'β', 'γ', 'δ', 'ε', 'ζ', 'η', 'θ', 'ι', 'κ', 'λ', 'μ', 'ν', 'ξ', 'ο', 'π', // - 'ρ', 'σ', 'τ', 'υ', 'φ', 'χ', 'ψ', 'ω', '(', ')', '〔', '〕', '^', '﹊', '﹍', '╭', '╮', '╰', '╯', '', '_', // - '', '^', '(', '^', ':', '!', '/', '\\', '\"', '<', '>', '`', '·', '。', '{', '}', '~', '~', '(', ')', '-', // - '√', '$', '@', '*', '&', '#', '卐', '㎎', '㎏', '㎜', '㎝', '㎞', '㎡', '㏄', '㏎', '㏑', '㏒', '㏕', '+', '=', '?', - ':', '.', '!', ';', ']','|','%'); + '·', 'ˉ', 'ˇ', '々', '—', '~', '‖', '…', '‘', '’', '“', '”', '〔', '〕', '〈', '〉', '《', '》', '「', '」', '『', // + '』', '〖', '〗', '【', '】', '±', '+', '-', '×', '÷', '∧', '∨', '∑', '∏', '∪', '∩', '∈', '√', '⊥', '⊙', '∫', // + '∮', '≡', '≌', '≈', '∽', '∝', '≠', '≮', '≯', '≤', '≥', '∞', '∶', '∵', '∴', '∷', '♂', '♀', '°', '′', '〃', // + '℃', '$', '¤', '¢', '£', '‰', '§', '☆', '★', '〇', '○', '●', '◎', '◇', '◆', '□', '■', '△', '▽', '⊿', '▲', // + '▼', '◣', '◤', '◢', '◥', '▁', '▂', '▃', '▄', '▅', '▆', '▇', '█', '▉', '▊', '▋', '▌', '▍', '▎', '▏', '▓', // + '※', '→', '←', '↑', '↓', '↖', '↗', '↘', '↙', '〓', 'ⅰ', 'ⅱ', 'ⅲ', 'ⅳ', 'ⅴ', 'ⅵ', 'ⅶ', 'ⅷ', 'ⅸ', 'ⅹ', '①', // + '②', '③', '④', '⑤', '⑥', '⑦', '⑧', '⑨', '⑩', '⒈', '⒉', '⒊', '⒋', '⒌', '⒍', '⒎', '⒏', '⒐', '⒑', '⒒', '⒓', // + '⒔', '⒕', '⒖', '⒗', '⒘', '⒙', '⒚', '⒛', '⑴', '⑵', '⑶', '⑷', '⑸', '⑹', '⑺', '⑻', '⑼', '⑽', '⑾', '⑿', '⒀', // + '⒁', '⒂', '⒃', '⒄', '⒅', '⒆', '⒇', 'Ⅰ', 'Ⅱ', 'Ⅲ', 'Ⅳ', 'Ⅴ', 'Ⅵ', 'Ⅶ', 'Ⅷ', 'Ⅸ', 'Ⅹ', 'Ⅺ', 'Ⅻ', '!', // + '#', '¥', '%', '&', '(', ')', '*', ',', '.', '/', '0', '1', '2', '3', '4', '5', '6', '7', // + '8', '9', ':', ';', '<', '=', '>', '?', '@', '〔', '\', '〕', '^', '_', '‘', '{', '|', '}', 'Ρ', // + 'Υ', 'Φ', 'Χ', 'Ψ', 'Ω', 'α', 'β', 'γ', 'δ', 'ε', 'ζ', 'η', 'θ', 'ι', 'κ', 'λ', 'μ', 'ν', 'ξ', 'ο', 'π', // + 'ρ', 'σ', 'τ', 'υ', 'φ', 'χ', 'ψ', 'ω', '(', ')', '^', '﹊', '﹍', '╭', '╮', '╰', '╯', '', '_', // + '', '^', '(', ':', '!', '/', '\\', '\"', '<', '>', '`', '{', '}', '~', '(', ')', '-', // + '$', '@', '*', '&', '#', '卐', '㎎', '㎏', '㎜', '㎝', '㎞', '㎡', '㏄', '㏎', '㏑', '㏒', '㏕', '+', '=', '?', + ':', '.', '!', ';', ']','|','%'); /** * 判断指定的词是否是不处理的词。 如果参数为空,则返回true,因为空也属于不处理的字符。 - * + * * @param ch 指定的词 * @return 是否是不处理的词 */ @@ -41,7 +41,7 @@ public static boolean isStopChar(char ch) { /** * 是否为合法字符(待处理字符) - * + * * @param ch 指定的词 * @return 是否为合法字符(待处理字符) */ diff --git a/hutool-dfa/src/test/java/cn/hutool/dfa/DfaTest.java b/hutool-dfa/src/test/java/cn/hutool/dfa/DfaTest.java index 515237e250..97b068be5a 100755 --- a/hutool-dfa/src/test/java/cn/hutool/dfa/DfaTest.java +++ b/hutool-dfa/src/test/java/cn/hutool/dfa/DfaTest.java @@ -1,11 +1,12 @@ package cn.hutool.dfa; import cn.hutool.core.collection.CollUtil; -import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test; import java.util.List; +import static org.junit.jupiter.api.Assertions.*; + /** * DFA单元测试 * @@ -27,7 +28,7 @@ public void matchAllTest() { // 匹配到【大】,就不再继续匹配了,因此【大土豆】不匹配 // 匹配到【刚出锅】,就跳过这三个字了,因此【出锅】不匹配(由于刚首先被匹配,因此长的被匹配,最短匹配只针对第一个字相同选最短) List matchAll = tree.matchAll(text, -1, false, false); - assertEquals(matchAll, CollUtil.newArrayList("大", "土^豆", "刚出锅")); + assertEquals(CollUtil.newArrayList("大", "土^豆", "刚出锅"), matchAll); } /** diff --git a/hutool-extra/pom.xml b/hutool-extra/pom.xml index 564ebc22d8..e9705ee0f8 100755 --- a/hutool-extra/pom.xml +++ b/hutool-extra/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.35 + 5.8.36 hutool-extra diff --git a/hutool-http/pom.xml b/hutool-http/pom.xml index 1662ffb7f1..7cade24012 100755 --- a/hutool-http/pom.xml +++ b/hutool-http/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.35 + 5.8.36 hutool-http diff --git a/hutool-http/src/main/java/cn/hutool/http/HtmlUtil.java b/hutool-http/src/main/java/cn/hutool/http/HtmlUtil.java index 94d429a186..9e08e360ad 100755 --- a/hutool-http/src/main/java/cn/hutool/http/HtmlUtil.java +++ b/hutool-http/src/main/java/cn/hutool/http/HtmlUtil.java @@ -24,6 +24,7 @@ public class HtmlUtil { public static final String GT = StrUtil.HTML_GT; public static final String RE_HTML_MARK = "(<[^<]*?>)|(<[\\s]*?/[^<]*?>)|(<[^<]*?/[\\s]*?>)"; + public static final String RE_HTML_EMPTY_MARK = "<(\\w+)([^>]*)>\\s*"; public static final String RE_SCRIPT = "<[\\s]*?script[^>]*?>.*?<[\\s]*?\\/[\\s]*?script[\\s]*?>"; private static final char[][] TEXT = new char[256][]; @@ -86,6 +87,17 @@ public static String cleanHtmlTag(String content) { return content.replaceAll(RE_HTML_MARK, ""); } + /** + * 清除所有HTML空标签
    + * 例如:<p></p> + * + * @param content 文本 + * @return 清除空标签后的文本 + */ + public static String cleanEmptyTag(String content) { + return content.replaceAll(RE_HTML_EMPTY_MARK, ""); + } + /** * 清除指定HTML标签和被标签包围的内容
    * 不区分大小写 diff --git a/hutool-http/src/main/java/cn/hutool/http/useragent/OS.java b/hutool-http/src/main/java/cn/hutool/http/useragent/OS.java index 38a956c580..d6f9644215 100755 --- a/hutool-http/src/main/java/cn/hutool/http/useragent/OS.java +++ b/hutool-http/src/main/java/cn/hutool/http/useragent/OS.java @@ -36,6 +36,7 @@ public class OS extends UserAgentInfo { new OS("Windows", "windows"), // new OS("OSX", "os x (\\d+)[._](\\d+)", "os x (\\d+([._]\\d+)*)"), // new OS("Android", "Android", "Android (\\d+([._]\\d+)*)"),// + new OS("Harmony", "OpenHarmony", "OpenHarmony (\\d+([._]\\d+)*)"), // new OS("Android", "XiaoMi|MI\\s+", "\\(X(\\d+([._]\\d+)*)"),// new OS("Linux", "linux"), // new OS("Wii", "wii", "wii libnup/(\\d+([._]\\d+)*)"), // diff --git a/hutool-http/src/main/java/cn/hutool/http/useragent/Platform.java b/hutool-http/src/main/java/cn/hutool/http/useragent/Platform.java index 960ee18f99..17a4380b46 100644 --- a/hutool-http/src/main/java/cn/hutool/http/useragent/Platform.java +++ b/hutool-http/src/main/java/cn/hutool/http/useragent/Platform.java @@ -36,6 +36,11 @@ public class Platform extends UserAgentInfo { * android */ public static final Platform ANDROID = new Platform("Android", "android"); + + /** + * harmony + */ + public static final Platform HARMONY = new Platform("Harmony", "OpenHarmony"); /** * android */ @@ -59,7 +64,8 @@ public class Platform extends UserAgentInfo { GOOGLE_TV, // new Platform("htcFlyer", "htc_flyer"), // new Platform("Symbian", "symbian(os)?"), // - new Platform("Blackberry", "blackberry") // + new Platform("Blackberry", "blackberry"), // + HARMONY ); /** @@ -144,4 +150,13 @@ public boolean isAndroid() { return this.equals(ANDROID) || this.equals(GOOGLE_TV); } + /** + * 是否为Harmony平台 + * + * @return 是否为Harmony平台 + */ + public boolean isHarmony() { + return this.equals(HARMONY); + } + } diff --git a/hutool-http/src/test/java/cn/hutool/http/HtmlUtilTest.java b/hutool-http/src/test/java/cn/hutool/http/HtmlUtilTest.java index 6e0d56a59b..585c612664 100644 --- a/hutool-http/src/test/java/cn/hutool/http/HtmlUtilTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/HtmlUtilTest.java @@ -1,8 +1,9 @@ package cn.hutool.http; -import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; + /** * Html单元测试 * @@ -77,6 +78,29 @@ public void cleanHtmlTagTest() { assertEquals("pre\r\n\t\tdfdsfdsfdsf\r\nBBBB", result); } + @Test + public void cleanEmptyTagTest() { + String str = "

    "; + String result = HtmlUtil.cleanEmptyTag(str); + assertEquals("", result); + + str = "

    TEXT

    "; + result = HtmlUtil.cleanEmptyTag(str); + assertEquals("

    TEXT

    ", result); + + str = "

    TEXT
    "; + result = HtmlUtil.cleanEmptyTag(str); + assertEquals("
    TEXT
    ", result); + + str = "

    TEXT

    TEXT
    "; + result = HtmlUtil.cleanEmptyTag(str); + assertEquals("

    TEXT

    TEXT
    ", result); + + str = "TEXT

    TEXT"; + result = HtmlUtil.cleanEmptyTag(str); + assertEquals("TEXTTEXT", result); + } + @Test public void unwrapHtmlTagTest() { //非闭合标签 diff --git a/hutool-http/src/test/java/cn/hutool/http/useragent/UserAgentUtilTest.java b/hutool-http/src/test/java/cn/hutool/http/useragent/UserAgentUtilTest.java index 3239483088..700177e64d 100644 --- a/hutool-http/src/test/java/cn/hutool/http/useragent/UserAgentUtilTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/useragent/UserAgentUtilTest.java @@ -413,6 +413,16 @@ public void parseFromDeepinTest(){ assertEquals("Linux", ua.getOs().toString()); } + @Test + public void parseHarmonyUATest() { + final String uaStr = "Mozilla/5.0 (Phone; OpenHarmony 4.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36 ArkWeb/4.1.6.1 Mobile "; + final UserAgent ua = UserAgentUtil.parse(uaStr); + assertEquals("Harmony", ua.getPlatform().toString()); + assertTrue(ua.getPlatform().isHarmony()); + assertEquals("Harmony", ua.getOs().toString()); + assertEquals("4.1", ua.getOsVersion()); + } + @Test public void issueI60UOPTest() { final String uaStr = "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.164 Safari/537.36 dingtalk-win/1.0.0 nw(0.14.7) DingTalk(6.5.40-Release.9059101) Mojo/1.0.0 Native AppType(release) Channel/201200"; diff --git a/hutool-json/pom.xml b/hutool-json/pom.xml index 281a1fc5f0..4729bd20f0 100755 --- a/hutool-json/pom.xml +++ b/hutool-json/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.35 + 5.8.36 hutool-json diff --git a/hutool-json/src/main/java/cn/hutool/json/JSON.java b/hutool-json/src/main/java/cn/hutool/json/JSON.java index 9a5972cc99..3034902d8e 100755 --- a/hutool-json/src/main/java/cn/hutool/json/JSON.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSON.java @@ -117,6 +117,7 @@ public interface JSON extends Cloneable, Serializable, IJSONTypeConverter { * * @param expression 表达式 * @param targetType 返回值类型 + * @param 获取对象类型 * @return 对象 * @see BeanPath#get(Object) * @since 5.8.34 diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java index 1d5e0d5579..f6a36847a9 100755 --- a/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java @@ -729,4 +729,15 @@ public void parseFilterEditTest() { }); assertEquals("value2_edit", jsonObject.get("b")); } + + @Test + void issue3844Test(){ + String camelCaseStr = "{\"userAge\":\"123\"}"; + final JSONObject entries = new JSONObject(camelCaseStr, null, (pair) -> { + pair.setKey(StrUtil.toUnderlineCase(pair.getKey())); + return true; + }); + + Console.log(entries); + } } diff --git a/hutool-jwt/pom.xml b/hutool-jwt/pom.xml index 2f1fc0078d..03624d7ed5 100755 --- a/hutool-jwt/pom.xml +++ b/hutool-jwt/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.35 + 5.8.36 hutool-jwt diff --git a/hutool-log/pom.xml b/hutool-log/pom.xml index f42c075b54..b264dcd714 100755 --- a/hutool-log/pom.xml +++ b/hutool-log/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.35 + 5.8.36 hutool-log diff --git a/hutool-poi/pom.xml b/hutool-poi/pom.xml index 3166aafcb4..b6bf3b2ab7 100755 --- a/hutool-poi/pom.xml +++ b/hutool-poi/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.35 + 5.8.36 hutool-poi diff --git a/hutool-script/pom.xml b/hutool-script/pom.xml index e7147b9fca..e6d9756020 100755 --- a/hutool-script/pom.xml +++ b/hutool-script/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.35 + 5.8.36 hutool-script diff --git a/hutool-setting/pom.xml b/hutool-setting/pom.xml index 8529bb9999..6104366abe 100755 --- a/hutool-setting/pom.xml +++ b/hutool-setting/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.35 + 5.8.36 hutool-setting diff --git a/hutool-socket/pom.xml b/hutool-socket/pom.xml index 18a5e972eb..32807733c1 100755 --- a/hutool-socket/pom.xml +++ b/hutool-socket/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.35 + 5.8.36 hutool-socket diff --git a/hutool-system/pom.xml b/hutool-system/pom.xml index 09b69acbce..79817e3a1e 100755 --- a/hutool-system/pom.xml +++ b/hutool-system/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.35 + 5.8.36 hutool-system diff --git a/pom.xml b/pom.xml index 582dc12b46..2cc77cd718 100755 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.8.35 + 5.8.36 hutool Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。 https://github.com/dromara/hutool @@ -126,7 +126,7 @@ org.apache.maven.plugins maven-javadoc-plugin - 3.8.0 + 3.11.2 package