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 extends Aspect> 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
* < (小于) 替换为 <
* > (大于) 替换为 >
* " (双引号) 替换为 "
+ * ' (单引号) 替换为 '
*
*
* @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