diff --git a/.github/codeql-analysis.yml b/.github/codeql-analysis.yml
new file mode 100644
index 000000000..1f58c7a8f
--- /dev/null
+++ b/.github/codeql-analysis.yml
@@ -0,0 +1,71 @@
+# For most projects, this workflow file will not need changing; you simply need
+# to commit it to your repository.
+#
+# You may wish to alter this file to override the set of languages analyzed,
+# or to provide custom queries or build logic.
+#
+# ******** NOTE ********
+# We have attempted to detect the languages in your repository. Please check
+# the `language` matrix defined below to confirm you have the correct set of
+# supported CodeQL languages.
+#
+name: "CodeQL"
+
+on:
+ push:
+ branches: [ v5-dev ]
+ pull_request:
+ # The branches below must be a subset of the branches above
+ branches: [ v5-dev ]
+ schedule:
+ - cron: '45 6 * * 1'
+
+jobs:
+ analyze:
+ name: Analyze
+ runs-on: ubuntu-latest
+ permissions:
+ actions: read
+ contents: read
+ security-events: write
+
+ strategy:
+ fail-fast: false
+ matrix:
+ language: [ 'java', 'javascript' ]
+ # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
+ # Learn more:
+ # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v2
+
+ # Initializes the CodeQL tools for scanning.
+ - name: Initialize CodeQL
+ uses: github/codeql-action/init@v1
+ with:
+ languages: ${{ matrix.language }}
+ # If you wish to specify custom queries, you can do so here or in a config file.
+ # By default, queries listed here will override any specified in a config file.
+ # Prefix the list here with "+" to use these queries and those in the config file.
+ # queries: ./path/to/local/query, your-org/your-repo/queries@main
+
+ # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
+ # If this step fails, then you should remove it and run the build manually (see below)
+ - name: Autobuild
+ uses: github/codeql-action/autobuild@v1
+
+ # ℹ️ Command-line programs to run using the OS shell.
+ # 📚 https://git.io/JvXDl
+
+ # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
+ # and modify them (or add more) to build your code if your project
+ # uses a compiled language
+
+ #- run: |
+ # make bootstrap
+ # make release
+
+ - name: Perform CodeQL Analysis
+ uses: github/codeql-action/analyze@v1
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b776605de..a21c83482 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,7 +3,62 @@
-------------------------------------------------------------------------------------------------------------
-# 5.7.14 (2021-10-08)
+# 5.7.16 (2021-10-30)
+
+### 🐣新特性
+* 【core 】 增加DateTime.toLocalDateTime
+* 【core 】 CharSequenceUtil增加normalize方法(pr#444@Gitee)
+* 【core 】 MailAccount增加setEncodefilename()方法,可选是否编码附件的文件名(issue#I4F160@Gitee)
+* 【core 】 MailAccount中charset增加null时的默认规则
+* 【core 】 NumberUtil.compare修正注释说明(issue#I4FAJ1@Gitee)
+* 【core 】 增加RFC3986类
+* 【extra 】 Sftp增加put和upload重载(issue#I4FGDH@Gitee)
+* 【core 】 TemporalUtil增加toChronoUnit、toTimeUnit方法(issue#I4FGDH@Gitee)
+* 【core 】 StopWatch增加prettyPrint重载(issue#1910@Github)
+* 【core 】 修改RegexPool中Ipv4正则
+* 【json 】 Filter改为MutablePair,以便编辑键值对(issue#1921@Github)
+
+### 🐞Bug修复
+* 【core 】 修复UrlBuilder.addPath歧义问题(issue#1912@Github)
+* 【core 】 修复StrBuilder中总长度计算问题(issue#I4F9L7@Gitee)
+* 【core 】 修复CharSequenceUtil.wrapIfMissing预定义长度计算问题(issue#I4FDZ2@Gitee)
+* 【poi 】 修复合并单元格为日期时,导出单元格数据为数字问题(issue#1911@Github)
+* 【core 】 修复CompilerUtil.getFileManager参数没有使用的问题(issue#I4FIO6@Gitee)
+* 【core 】 修复NetUtil.isInRange的cidr判断问题(pr#1917@Github)
+
+-------------------------------------------------------------------------------------------------------------
+
+# 5.7.15 (2021-10-21)
+
+### 🐣新特性
+* 【db 】 Db.quietSetAutoCommit增加判空(issue#I4D75B@Gitee)
+* 【core 】 增加RingIndexUtil(pr#438@Gitee)
+* 【core 】 Assert增加checkBetween重载(pr#436@Gitee)
+* 【core 】 ReUtil增加命名分组重载(pr#439@Gitee)
+* 【json 】 toString和writer增加Filter(issue#I4DQNQ@Gitee)
+* 【core 】 ContentType增加build重载(pr#1898@Github)
+* 【bom 】 支持scope=import方式引入(issue#1561@Github)
+* 【core 】 新增Hash接口,HashXXX继承此接口
+* 【core 】 ZipUtil增加append方法(pr#441@Gitee)
+* 【core 】 CollUtil增加重载(issue#I4E9FS@Gitee)
+* 【core 】 CopyOptions新增setFieldValueEditor(issue#I4E08T@Gitee)
+* 【core 】 增加SystemPropsUtil(issue#1918@Gitee)
+* 【core 】 增加`hutool.date.lenient`系统属性(issue#1918@Gitee)
+
+### 🐞Bug修复
+* 【core 】 修复CollUtil.isEqualList两个null返回错误问题(issue#1885@Github)
+* 【poi 】 修复ExcelWriter多余调试信息导致的问题(issue#1884@Github)
+* 【poi 】 修复TemporalAccessorUtil.toInstant使用DateTimeFormatter导致问题(issue#1891@Github)
+* 【poi 】 修复sheet.getRow(y)为null导致的问题(issue#1893@Github)
+* 【cache 】 修复LRUCache线程安全问题(issue#1895@Github)
+* 【crypto 】 修复KeyUtil异常信息参数丢失问题(issue#1902@Github)
+* 【core 】 修复StrUtil.split和splittoArray不一致问题(issue#I4ELU5@Github)
+* 【core 】 修复SymmetricCrypto未关闭CipherOutputStream导致的问题(issue#I4EMST@Gitee)
+* 【core 】 修复QueryBuilder对/转义问题(issue#1904@Github)
+
+-------------------------------------------------------------------------------------------------------------
+
+# 5.7.14 (2021-10-09)
### 🐣新特性
* 【extra 】 修复HttpCookie设置cookies的方法,不符合RFC6265规范问题(issue#I4B70D@Gitee)
@@ -16,12 +71,13 @@
* 【core 】 DateTime构造和DateUtil.parse可选是否宽松模式(issue#1849@Github)
* 【core 】 TreeBuilder增加部分根节点set方法(issue#1848@Github)
* 【core 】 优化Base64.isBase64方法:减少一次多余的判断(pr#1860@Github)
-* 【core 】 优化Base64.isBase64方法:减少一次多余的判断(pr#1860@Github)
* 【cache 】 优化FIFOCache未设置过期策略时,无需遍历判断过期对象(pr#425@Gitee)
* 【core 】 增加Opt类(pr#426@Gitee)
* 【core 】 Week增加of重载,支持DayOfWek(pr#1872@Github)
* 【poi 】 优化read,避免多次创建CopyOptions(issue#1875@Github)
* 【core 】 优化CsvReader,实现可控遍历(pr#1873@Github)
+* 【core 】 优化Base64.isBase64判断(pr#1879@Github)
+* 【core 】 新增StrFormatter.formatWith(pr#430@Gitee)
### 🐞Bug修复
* 【http 】 修复HttpCookie设置cookies的方法,不符合RFC6265规范问题(pr#418@Gitee)
@@ -30,6 +86,7 @@
* 【db 】 修复Condition没占位符的情况下sql没引号问题(issue#1846@Github)
* 【cache 】 修复FIFOCache中remove回调无效问题(pr#1856@Github)
* 【json 】 修复JSONArray.set中,index为0报错问题(issue#1858@Github)
+* 【core 】 修复FileUtil.checkSlip中getCanonicalPath异常引起的问题(issue#1858@Github)
-------------------------------------------------------------------------------------------------------------
diff --git a/README-EN.md b/README-EN.md
index 1a91bf2e4..f73d4c304 100644
--- a/README-EN.md
+++ b/README-EN.md
@@ -117,6 +117,24 @@ Each module can be introduced individually, or all modules can be introduced by
-------------------------------------------------------------------------------
+## 🪙Support Hutool
+
+### 💳Donate
+
+If you think Hutool is good, you can donate to buy the author a pack of chili~, thanks in advance ^_^.
+
+[Gitee donate](https://gitee.com/dromara/hutool)
+
+[Dromara donate](https://dromara.gitee.io/donate.html)
+
+### 👕Shop about Hutool
+
+We provide the T-Shirt and Sweater with Hutool Logo, please visit the shop:
+
+👉 [Hutool Shop](https://m.tb.cn/h.fVxoBOm?sm=2756b2) 👈
+
+-------------------------------------------------------------------------------
+
## 📦Install
### 🍊Maven
@@ -124,18 +142,18 @@ Each module can be introduced individually, or all modules can be introduced by
cn.hutool
hutool-all
- 5.7.14
+ 5.7.16
```
### 🍐Gradle
```
-implementation 'cn.hutool:hutool-all:5.7.14'
+implementation 'cn.hutool:hutool-all:5.7.16'
```
## 📥Download
-- [Maven Repo](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.7.14/)
+- [Maven Repo](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.7.16/)
> 🔔️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.
@@ -191,20 +209,9 @@ Hutool welcomes anyone to contribute code to Hutool, but the author suffers from
[](https://starchart.cc/dromara/hutool)
-## 💳Donate
-
-If you think Hutool is good, you can donate to buy tshe author a pack of chili~, thanks in advance ^_^.
-
-[Gitee donate](https://gitee.com/dromara/hutool)
-
-[Dromara donate](https://dromara.gitee.io/donate.html)
-
## 📌WeChat Official Account
-#### 🐧Welcome to the official account of Hutool cooperation.
-
-
-
-#### 🐧Welcome to organization Dromara
-
-
\ No newline at end of file
+
+

+

+
\ No newline at end of file
diff --git a/README.md b/README.md
index 27523f6ac..ae1aea6e8 100644
--- a/README.md
+++ b/README.md
@@ -113,6 +113,26 @@ Hutool的存在就是为了减少代码搜索成本,避免网络上参差不
-------------------------------------------------------------------------------
+## 🪙支持Hutool
+
+### 💳捐赠
+
+如果你觉得Hutool不错,可以捐赠请维护者吃包辣条~,在此表示感谢^_^。
+
+[Gitee上捐赠](https://gitee.com/dromara/hutool)
+
+[捐赠给Dromara组织](https://dromara.gitee.io/donate.html)
+
+### 👕周边商店
+
+你也可以通过购买Hutool的周边商品来支持Hutool维护哦!
+
+我们提供了印有Hutool Logo的周边商品,欢迎点击购买支持:
+
+👉 [Hutool 周边商店](https://m.tb.cn/h.fVxoBOm?sm=2756b2) 👈
+
+-------------------------------------------------------------------------------
+
## 📦安装
### 🍊Maven
@@ -122,24 +142,24 @@ Hutool的存在就是为了减少代码搜索成本,避免网络上参差不
cn.hutool
hutool-all
- 5.7.14
+ 5.7.16
```
### 🍐Gradle
```
-implementation 'cn.hutool:hutool-all:5.7.14'
+implementation 'cn.hutool:hutool-all:5.7.16'
```
### 📥下载jar
点击以下链接,下载`hutool-all-X.X.X.jar`即可:
-- [Maven中央库](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.7.14/)
+- [Maven中央库](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.7.16/)
> 🔔️注意
> Hutool 5.x支持JDK8+,对Android平台没有测试,不能保证所有工具类或工具方法可用。
-> 如果你的项目使用JDK7,请使用Hutool 4.x版本
+> 如果你的项目使用JDK7,请使用Hutool 4.x版本(不再更新)
### 🚽编译安装
@@ -198,22 +218,9 @@ Hutool欢迎任何人为Hutool添砖加瓦,贡献代码,不过维护者是
[](https://starchart.cc/dromara/hutool)
-## 💳捐赠
-
-如果你觉得Hutool不错,可以捐赠请维护者吃包辣条~,在此表示感谢^_^。
-
-点击以下链接,将页面拉到最下方点击“捐赠”即可。
-
-[Gitee上捐赠](https://gitee.com/dromara/hutool)
-
-[捐赠给Dromara组织](https://dromara.gitee.io/donate.html)
-
## 📌公众号
-#### 🧡欢迎关注Hutool合作的公众号
-
-
-
-#### 🧡Dromara开源组织公众号
-
-
\ No newline at end of file
+
+

+

+
\ No newline at end of file
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 000000000..d0c2c5589
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,15 @@
+# Security Policy
+
+## Supported Versions(支持的版本)
+
+| Version | Supported |
+| ------- | ------------------ |
+| 5.x.x | :white_check_mark: |
+| 4.x.x | :x: |
+| 3.x.x | :x: |
+
+## Reporting a Vulnerability(报告漏洞)
+
+如果你发现有安全问题或漏洞,请发送邮件到`loolly@aliyun.com`。
+
+To report any found security issues or vulnerabilities, please send a mail to `loolly@aliyun.com`.
\ No newline at end of file
diff --git a/bin/version.txt b/bin/version.txt
index 2ad2244f6..e8406ce30 100755
--- a/bin/version.txt
+++ b/bin/version.txt
@@ -1 +1 @@
-5.7.14
+5.7.16
diff --git a/docs/js/version.js b/docs/js/version.js
index 71f2db4e9..3e6f04e99 100644
--- a/docs/js/version.js
+++ b/docs/js/version.js
@@ -1 +1 @@
-var version = '5.7.14'
\ No newline at end of file
+var version = '5.7.16'
\ No newline at end of file
diff --git a/hutool-all/pom.xml b/hutool-all/pom.xml
index 15601f7d5..b97625af4 100644
--- a/hutool-all/pom.xml
+++ b/hutool-all/pom.xml
@@ -9,7 +9,7 @@
cn.hutool
hutool-parent
- 5.7.14-SNAPSHOT
+ 5.7.16-SNAPSHOT
hutool-all
diff --git a/hutool-aop/pom.xml b/hutool-aop/pom.xml
index fe27b1a20..5590a0b53 100644
--- a/hutool-aop/pom.xml
+++ b/hutool-aop/pom.xml
@@ -9,7 +9,7 @@
cn.hutool
hutool-parent
- 5.7.14-SNAPSHOT
+ 5.7.16-SNAPSHOT
hutool-aop
diff --git a/hutool-bloomFilter/pom.xml b/hutool-bloomFilter/pom.xml
index 4caef1e5c..96f02bf66 100644
--- a/hutool-bloomFilter/pom.xml
+++ b/hutool-bloomFilter/pom.xml
@@ -9,7 +9,7 @@
cn.hutool
hutool-parent
- 5.7.14-SNAPSHOT
+ 5.7.16-SNAPSHOT
hutool-bloomFilter
diff --git a/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/BloomFilter.java b/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/BloomFilter.java
index e7c9cc9fc..9ab8278b3 100644
--- a/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/BloomFilter.java
+++ b/hutool-bloomFilter/src/main/java/cn/hutool/bloomfilter/BloomFilter.java
@@ -20,10 +20,10 @@ public interface BloomFilter extends Serializable{
/**
* 在boolean的bitMap中增加一个字符串
- * 如果存在就返回false
.如果不存在.先增加这个字符串.再返回true
+ * 如果存在就返回{@code false} .如果不存在.先增加这个字符串.再返回{@code true}
*
* @param str 字符串
- * @return 是否加入成功,如果存在就返回false
.如果不存在返回true
+ * @return 是否加入成功,如果存在就返回{@code false} .如果不存在返回{@code true}
*/
boolean add(String str);
-}
\ No newline at end of file
+}
diff --git a/hutool-bom/pom.xml b/hutool-bom/pom.xml
index f2d8e0a81..a484343e3 100644
--- a/hutool-bom/pom.xml
+++ b/hutool-bom/pom.xml
@@ -9,7 +9,7 @@
cn.hutool
hutool-parent
- 5.7.14-SNAPSHOT
+ 5.7.16-SNAPSHOT
hutool-bom
@@ -17,101 +17,182 @@
提供丰富的Java工具方法,此模块为Hutool所有模块汇总,最终形式为拆分开的多个jar包,可以通过exclude方式排除不需要的模块
https://github.com/looly/hutool
+
+
+
+ cn.hutool
+ hutool-core
+ ${project.parent.version}
+
+
+ cn.hutool
+ hutool-aop
+ ${project.parent.version}
+
+
+ cn.hutool
+ hutool-bloomFilter
+ ${project.parent.version}
+
+
+ cn.hutool
+ hutool-cache
+ ${project.parent.version}
+
+
+ cn.hutool
+ hutool-crypto
+ ${project.parent.version}
+
+
+ cn.hutool
+ hutool-db
+ ${project.parent.version}
+
+
+ cn.hutool
+ hutool-dfa
+ ${project.parent.version}
+
+
+ cn.hutool
+ hutool-extra
+ ${project.parent.version}
+
+
+ cn.hutool
+ hutool-http
+ ${project.parent.version}
+
+
+ cn.hutool
+ hutool-log
+ ${project.parent.version}
+
+
+ cn.hutool
+ hutool-script
+ ${project.parent.version}
+
+
+ cn.hutool
+ hutool-setting
+ ${project.parent.version}
+
+
+ cn.hutool
+ hutool-system
+ ${project.parent.version}
+
+
+ cn.hutool
+ hutool-cron
+ ${project.parent.version}
+
+
+ cn.hutool
+ hutool-json
+ ${project.parent.version}
+
+
+ cn.hutool
+ hutool-poi
+ ${project.parent.version}
+
+
+ cn.hutool
+ hutool-captcha
+ ${project.parent.version}
+
+
+ cn.hutool
+ hutool-socket
+ ${project.parent.version}
+
+
+ cn.hutool
+ hutool-jwt
+ ${project.parent.version}
+
+
+
+
cn.hutool
hutool-core
- ${project.parent.version}
cn.hutool
hutool-aop
- ${project.parent.version}
cn.hutool
hutool-bloomFilter
- ${project.parent.version}
cn.hutool
hutool-cache
- ${project.parent.version}
cn.hutool
hutool-crypto
- ${project.parent.version}
cn.hutool
hutool-db
- ${project.parent.version}
cn.hutool
hutool-dfa
- ${project.parent.version}
cn.hutool
hutool-extra
- ${project.parent.version}
cn.hutool
hutool-http
- ${project.parent.version}
cn.hutool
hutool-log
- ${project.parent.version}
cn.hutool
hutool-script
- ${project.parent.version}
cn.hutool
hutool-setting
- ${project.parent.version}
cn.hutool
hutool-system
- ${project.parent.version}
cn.hutool
hutool-cron
- ${project.parent.version}
cn.hutool
hutool-json
- ${project.parent.version}
cn.hutool
hutool-poi
- ${project.parent.version}
cn.hutool
hutool-captcha
- ${project.parent.version}
cn.hutool
hutool-socket
- ${project.parent.version}
cn.hutool
hutool-jwt
- ${project.parent.version}
diff --git a/hutool-cache/pom.xml b/hutool-cache/pom.xml
index 7f50b660e..7e84b9cea 100644
--- a/hutool-cache/pom.xml
+++ b/hutool-cache/pom.xml
@@ -9,7 +9,7 @@
cn.hutool
hutool-parent
- 5.7.14-SNAPSHOT
+ 5.7.16-SNAPSHOT
hutool-cache
diff --git a/hutool-cache/src/main/java/cn/hutool/cache/impl/AbstractCache.java b/hutool-cache/src/main/java/cn/hutool/cache/impl/AbstractCache.java
index 6999922ee..6bb549689 100644
--- a/hutool-cache/src/main/java/cn/hutool/cache/impl/AbstractCache.java
+++ b/hutool-cache/src/main/java/cn/hutool/cache/impl/AbstractCache.java
@@ -2,7 +2,6 @@ package cn.hutool.cache.impl;
import cn.hutool.cache.Cache;
import cn.hutool.cache.CacheListener;
-import cn.hutool.core.collection.CopiedIter;
import cn.hutool.core.lang.func.Func0;
import java.util.Iterator;
@@ -12,7 +11,6 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.LongAdder;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
-import java.util.concurrent.locks.StampedLock;
/**
* 超时和限制大小的缓存的默认实现
@@ -31,11 +29,6 @@ public abstract class AbstractCache implements Cache {
protected Map> cacheMap;
- // 乐观锁,此处使用乐观锁解决读多写少的场景
- // get时乐观读,再检查是否修改,修改则转入悲观读重新读一遍,可以有效解决在写时阻塞大量读操作的情况。
- // see: https://www.cnblogs.com/jiagoushijuzi/p/13721319.html
- protected final StampedLock lock = new StampedLock();
-
/**
* 写的时候每个key一把锁,降低锁的粒度
*/
@@ -75,16 +68,6 @@ public abstract class AbstractCache implements Cache {
put(key, object, timeout);
}
- @Override
- public void put(K key, V object, long timeout) {
- final long stamp = lock.writeLock();
- try {
- putWithoutLock(key, object, timeout);
- } finally {
- lock.unlockWrite(stamp);
- }
- }
-
/**
* 加入元素,无锁
*
@@ -93,7 +76,7 @@ public abstract class AbstractCache implements Cache {
* @param timeout 超时时长
* @since 4.5.16
*/
- private void putWithoutLock(K key, V object, long timeout) {
+ protected void putWithoutLock(K key, V object, long timeout) {
CacheObj co = new CacheObj<>(key, object, timeout);
if (timeout != 0) {
existCustomTimeout = true;
@@ -106,29 +89,6 @@ public abstract class AbstractCache implements Cache {
// ---------------------------------------------------------------- put end
// ---------------------------------------------------------------- get start
- @Override
- public boolean containsKey(K key) {
- final long stamp = lock.readLock();
- try {
- // 不存在或已移除
- final CacheObj co = cacheMap.get(key);
- if (co == null) {
- return false;
- }
-
- if (false == co.isExpired()) {
- // 命中
- return true;
- }
- } finally {
- lock.unlockRead(stamp);
- }
-
- // 过期
- remove(key, true);
- return false;
- }
-
/**
* @return 命中数
*/
@@ -170,36 +130,6 @@ public abstract class AbstractCache implements Cache {
}
return v;
}
-
- @Override
- public V get(K key, boolean isUpdateLastAccess) {
- // 尝试读取缓存,使用乐观读锁
- long stamp = lock.tryOptimisticRead();
- CacheObj co = cacheMap.get(key);
- if(false == lock.validate(stamp)){
- // 有写线程修改了此对象,悲观读
- stamp = lock.readLock();
- try {
- co = cacheMap.get(key);
- } finally {
- lock.unlockRead(stamp);
- }
- }
-
- // 未命中
- if (null == co) {
- missCount.increment();
- return null;
- } else if (false == co.isExpired()) {
- hitCount.increment();
- return co.get(isUpdateLastAccess);
- }
-
- // 过期,既不算命中也不算非命中
- remove(key, true);
- return null;
- }
-
// ---------------------------------------------------------------- get end
@Override
@@ -207,21 +137,7 @@ public abstract class AbstractCache implements Cache {
CacheObjIterator copiedIterator = (CacheObjIterator) this.cacheObjIterator();
return new CacheValuesIterator<>(copiedIterator);
}
-
- @Override
- public Iterator> cacheObjIterator() {
- CopiedIter> copiedIterator;
- final long stamp = lock.readLock();
- try {
- copiedIterator = CopiedIter.copyOf(this.cacheMap.values().iterator());
- } finally {
- lock.unlockRead(stamp);
- }
- return new CacheObjIterator<>(copiedIterator);
- }
-
// ---------------------------------------------------------------- prune start
-
/**
* 清理实现
* 子类实现此方法时无需加锁
@@ -229,16 +145,6 @@ public abstract class AbstractCache implements Cache {
* @return 清理数
*/
protected abstract int pruneCache();
-
- @Override
- public final int prune() {
- final long stamp = lock.writeLock();
- try {
- return pruneCache();
- } finally {
- lock.unlockWrite(stamp);
- }
- }
// ---------------------------------------------------------------- prune end
// ---------------------------------------------------------------- common start
@@ -270,21 +176,6 @@ public abstract class AbstractCache implements Cache {
return (capacity > 0) && (cacheMap.size() >= capacity);
}
- @Override
- public void remove(K key) {
- remove(key, false);
- }
-
- @Override
- public void clear() {
- final long stamp = lock.writeLock();
- try {
- cacheMap.clear();
- } finally {
- lock.unlockWrite(stamp);
- }
- }
-
@Override
public int size() {
return cacheMap.size();
@@ -338,25 +229,6 @@ public abstract class AbstractCache implements Cache {
}
}
- /**
- * 移除key对应的对象
- *
- * @param key 键
- * @param withMissCount 是否计数丢失数
- */
- private void remove(K key, boolean withMissCount) {
- final long stamp = lock.writeLock();
- CacheObj co;
- try {
- co = removeWithoutLock(key, withMissCount);
- } finally {
- lock.unlockWrite(stamp);
- }
- if (null != co) {
- onRemove(co.key, co.obj);
- }
- }
-
/**
* 移除key对应的对象,不加锁
*
@@ -364,7 +236,7 @@ public abstract class AbstractCache implements Cache {
* @param withMissCount 是否计数丢失数
* @return 移除的对象,无返回null
*/
- private CacheObj removeWithoutLock(K key, boolean withMissCount) {
+ protected CacheObj removeWithoutLock(K key, boolean withMissCount) {
final CacheObj co = cacheMap.remove(key);
if (withMissCount) {
// 在丢失计数有效的情况下,移除一般为get时的超时操作,此处应该丢失数+1
diff --git a/hutool-cache/src/main/java/cn/hutool/cache/impl/FIFOCache.java b/hutool-cache/src/main/java/cn/hutool/cache/impl/FIFOCache.java
index c403ae10c..558fed75f 100644
--- a/hutool-cache/src/main/java/cn/hutool/cache/impl/FIFOCache.java
+++ b/hutool-cache/src/main/java/cn/hutool/cache/impl/FIFOCache.java
@@ -16,7 +16,7 @@ import java.util.LinkedHashMap;
* @param 值类型
* @author Looly
*/
-public class FIFOCache extends AbstractCache {
+public class FIFOCache extends StampedCache {
private static final long serialVersionUID = 1L;
/**
diff --git a/hutool-cache/src/main/java/cn/hutool/cache/impl/LFUCache.java b/hutool-cache/src/main/java/cn/hutool/cache/impl/LFUCache.java
index 32709ed6b..4b1e2608a 100644
--- a/hutool-cache/src/main/java/cn/hutool/cache/impl/LFUCache.java
+++ b/hutool-cache/src/main/java/cn/hutool/cache/impl/LFUCache.java
@@ -15,7 +15,7 @@ import java.util.Iterator;
* @param 键类型
* @param 值类型
*/
-public class LFUCache extends AbstractCache {
+public class LFUCache extends StampedCache {
private static final long serialVersionUID = 1L;
/**
diff --git a/hutool-cache/src/main/java/cn/hutool/cache/impl/LRUCache.java b/hutool-cache/src/main/java/cn/hutool/cache/impl/LRUCache.java
index b385b57fe..29f83f02f 100644
--- a/hutool-cache/src/main/java/cn/hutool/cache/impl/LRUCache.java
+++ b/hutool-cache/src/main/java/cn/hutool/cache/impl/LRUCache.java
@@ -16,7 +16,7 @@ import java.util.Iterator;
* @param 键类型
* @param 值类型
*/
-public class LRUCache extends AbstractCache {
+public class LRUCache extends ReentrantCache {
private static final long serialVersionUID = 1L;
/**
diff --git a/hutool-cache/src/main/java/cn/hutool/cache/impl/ReentrantCache.java b/hutool-cache/src/main/java/cn/hutool/cache/impl/ReentrantCache.java
new file mode 100644
index 000000000..180bc7785
--- /dev/null
+++ b/hutool-cache/src/main/java/cn/hutool/cache/impl/ReentrantCache.java
@@ -0,0 +1,136 @@
+package cn.hutool.cache.impl;
+
+import cn.hutool.core.collection.CopiedIter;
+
+import java.util.Iterator;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * 使用{@link ReentrantLock}保护的缓存,读写都使用悲观锁完成,主要避免某些Map无法使用读写锁的问题
+ * 例如使用了LinkedHashMap的缓存,由于get方法也会改变Map的结构,因此读写必须加互斥锁
+ *
+ * @param 键类型
+ * @param 值类型
+ * @author looly
+ * @since 5.7.15
+ */
+public abstract class ReentrantCache extends AbstractCache {
+ private static final long serialVersionUID = 1L;
+
+ // 一些特殊缓存,例如使用了LinkedHashMap的缓存,由于get方法也会改变Map的结构,导致无法使用读写锁
+ // 最优的解决方案是使用Guava的ConcurrentLinkedHashMap,此处使用简化的互斥锁
+ protected final ReentrantLock lock = new ReentrantLock();
+
+ @Override
+ public void put(K key, V object, long timeout) {
+ lock.lock();
+ try {
+ putWithoutLock(key, object, timeout);
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ @Override
+ public boolean containsKey(K key) {
+ lock.lock();
+ try {
+ // 不存在或已移除
+ final CacheObj co = cacheMap.get(key);
+ if (co == null) {
+ return false;
+ }
+
+ if (false == co.isExpired()) {
+ // 命中
+ return true;
+ }
+ } finally {
+ lock.unlock();
+ }
+
+ // 过期
+ remove(key, true);
+ return false;
+ }
+
+ @Override
+ public V get(K key, boolean isUpdateLastAccess) {
+ CacheObj co;
+ lock.lock();
+ try {
+ co = cacheMap.get(key);
+ } finally {
+ lock.unlock();
+ }
+
+ // 未命中
+ if (null == co) {
+ missCount.increment();
+ return null;
+ } else if (false == co.isExpired()) {
+ hitCount.increment();
+ return co.get(isUpdateLastAccess);
+ }
+
+ // 过期,既不算命中也不算非命中
+ remove(key, true);
+ return null;
+ }
+
+ @Override
+ public Iterator> cacheObjIterator() {
+ CopiedIter> copiedIterator;
+ lock.lock();
+ try {
+ copiedIterator = CopiedIter.copyOf(this.cacheMap.values().iterator());
+ } finally {
+ lock.unlock();
+ }
+ return new CacheObjIterator<>(copiedIterator);
+ }
+
+ @Override
+ public final int prune() {
+ lock.lock();
+ try {
+ return pruneCache();
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ @Override
+ public void remove(K key) {
+ remove(key, false);
+ }
+
+ @Override
+ public void clear() {
+ lock.lock();
+ try {
+ cacheMap.clear();
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * 移除key对应的对象
+ *
+ * @param key 键
+ * @param withMissCount 是否计数丢失数
+ */
+ private void remove(K key, boolean withMissCount) {
+ lock.lock();
+ CacheObj co;
+ try {
+ co = removeWithoutLock(key, withMissCount);
+ } finally {
+ lock.unlock();
+ }
+ if (null != co) {
+ onRemove(co.key, co.obj);
+ }
+ }
+}
diff --git a/hutool-cache/src/main/java/cn/hutool/cache/impl/StampedCache.java b/hutool-cache/src/main/java/cn/hutool/cache/impl/StampedCache.java
new file mode 100644
index 000000000..79534f2bf
--- /dev/null
+++ b/hutool-cache/src/main/java/cn/hutool/cache/impl/StampedCache.java
@@ -0,0 +1,141 @@
+package cn.hutool.cache.impl;
+
+import cn.hutool.core.collection.CopiedIter;
+
+import java.util.Iterator;
+import java.util.concurrent.locks.StampedLock;
+
+/**
+ * 使用{@link StampedLock}保护的缓存,使用读写乐观锁
+ *
+ * @param 键类型
+ * @param 值类型
+ * @author looly
+ * @since 5.7.15
+ */
+public abstract class StampedCache extends AbstractCache{
+ private static final long serialVersionUID = 1L;
+
+ // 乐观锁,此处使用乐观锁解决读多写少的场景
+ // get时乐观读,再检查是否修改,修改则转入悲观读重新读一遍,可以有效解决在写时阻塞大量读操作的情况。
+ // see: https://www.cnblogs.com/jiagoushijuzi/p/13721319.html
+ protected final StampedLock lock = new StampedLock();
+
+ @Override
+ public void put(K key, V object, long timeout) {
+ final long stamp = lock.writeLock();
+ try {
+ putWithoutLock(key, object, timeout);
+ } finally {
+ lock.unlockWrite(stamp);
+ }
+ }
+
+ @Override
+ public boolean containsKey(K key) {
+ final long stamp = lock.readLock();
+ try {
+ // 不存在或已移除
+ final CacheObj co = cacheMap.get(key);
+ if (co == null) {
+ return false;
+ }
+
+ if (false == co.isExpired()) {
+ // 命中
+ return true;
+ }
+ } finally {
+ lock.unlockRead(stamp);
+ }
+
+ // 过期
+ remove(key, true);
+ return false;
+ }
+
+ @Override
+ public V get(K key, boolean isUpdateLastAccess) {
+ // 尝试读取缓存,使用乐观读锁
+ long stamp = lock.tryOptimisticRead();
+ CacheObj co = cacheMap.get(key);
+ if(false == lock.validate(stamp)){
+ // 有写线程修改了此对象,悲观读
+ stamp = lock.readLock();
+ try {
+ co = cacheMap.get(key);
+ } finally {
+ lock.unlockRead(stamp);
+ }
+ }
+
+ // 未命中
+ if (null == co) {
+ missCount.increment();
+ return null;
+ } else if (false == co.isExpired()) {
+ hitCount.increment();
+ return co.get(isUpdateLastAccess);
+ }
+
+ // 过期,既不算命中也不算非命中
+ remove(key, true);
+ return null;
+ }
+
+ @Override
+ public Iterator> cacheObjIterator() {
+ CopiedIter> copiedIterator;
+ final long stamp = lock.readLock();
+ try {
+ copiedIterator = CopiedIter.copyOf(this.cacheMap.values().iterator());
+ } finally {
+ lock.unlockRead(stamp);
+ }
+ return new CacheObjIterator<>(copiedIterator);
+ }
+
+ @Override
+ public final int prune() {
+ final long stamp = lock.writeLock();
+ try {
+ return pruneCache();
+ } finally {
+ lock.unlockWrite(stamp);
+ }
+ }
+
+ @Override
+ public void remove(K key) {
+ remove(key, false);
+ }
+
+ @Override
+ public void clear() {
+ final long stamp = lock.writeLock();
+ try {
+ cacheMap.clear();
+ } finally {
+ lock.unlockWrite(stamp);
+ }
+ }
+
+ /**
+ * 移除key对应的对象
+ *
+ * @param key 键
+ * @param withMissCount 是否计数丢失数
+ */
+ private void remove(K key, boolean withMissCount) {
+ final long stamp = lock.writeLock();
+ CacheObj co;
+ try {
+ co = removeWithoutLock(key, withMissCount);
+ } finally {
+ lock.unlockWrite(stamp);
+ }
+ if (null != co) {
+ onRemove(co.key, co.obj);
+ }
+ }
+}
diff --git a/hutool-cache/src/main/java/cn/hutool/cache/impl/TimedCache.java b/hutool-cache/src/main/java/cn/hutool/cache/impl/TimedCache.java
index 5e094875d..e03ede728 100644
--- a/hutool-cache/src/main/java/cn/hutool/cache/impl/TimedCache.java
+++ b/hutool-cache/src/main/java/cn/hutool/cache/impl/TimedCache.java
@@ -16,7 +16,7 @@ import java.util.concurrent.ScheduledFuture;
* @param 键类型
* @param 值类型
*/
-public class TimedCache extends AbstractCache {
+public class TimedCache extends StampedCache {
private static final long serialVersionUID = 1L;
/** 正在执行的定时任务 */
diff --git a/hutool-cache/src/test/java/cn/hutool/cache/LRUCacheTest.java b/hutool-cache/src/test/java/cn/hutool/cache/LRUCacheTest.java
new file mode 100644
index 000000000..6ccc2db2f
--- /dev/null
+++ b/hutool-cache/src/test/java/cn/hutool/cache/LRUCacheTest.java
@@ -0,0 +1,52 @@
+package cn.hutool.cache;
+
+import cn.hutool.cache.impl.LRUCache;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * 见:https://github.com/dromara/hutool/issues/1895
+ * 并发问题测试,在5.7.15前,LRUCache存在并发问题,多线程get后,map结构变更,导致null的位置不确定,
+ * 并可能引起死锁。
+ */
+public class LRUCacheTest {
+
+ @Test
+ public void readWriteTest() throws InterruptedException {
+ LRUCache cache = CacheUtil.newLRUCache(10);
+ for (int i = 0; i < 10; i++) {
+ cache.put(i, i);
+ }
+
+ CountDownLatch countDownLatch = new CountDownLatch(10);
+ // 10个线程分别读0-9 10000次
+ for (int i = 0; i < 10; i++) {
+ int finalI = i;
+ new Thread(() -> {
+ for (int j = 0; j < 10000; j++) {
+ cache.get(finalI);
+ }
+ countDownLatch.countDown();
+ }).start();
+ }
+ // 等待读线程结束
+ countDownLatch.await();
+ // 按顺序读0-9
+ StringBuilder sb1 = new StringBuilder();
+ for (int i = 0; i < 10; i++) {
+ sb1.append(cache.get(i));
+ }
+ Assert.assertEquals("0123456789", sb1.toString());
+
+ // 新加11,此时0最久未使用,应该淘汰0
+ cache.put(11, 11);
+
+ StringBuilder sb2 = new StringBuilder();
+ for (int i = 0; i < 10; i++) {
+ sb2.append(cache.get(i));
+ }
+ Assert.assertEquals("null123456789", sb2.toString());
+ }
+}
diff --git a/hutool-captcha/pom.xml b/hutool-captcha/pom.xml
index 2f8960c18..b22ac7ab2 100644
--- a/hutool-captcha/pom.xml
+++ b/hutool-captcha/pom.xml
@@ -9,7 +9,7 @@
cn.hutool
hutool-parent
- 5.7.14-SNAPSHOT
+ 5.7.16-SNAPSHOT
hutool-captcha
diff --git a/hutool-core/pom.xml b/hutool-core/pom.xml
index 3ccf717a2..ed9ac8a21 100644
--- a/hutool-core/pom.xml
+++ b/hutool-core/pom.xml
@@ -9,7 +9,7 @@
cn.hutool
hutool-parent
- 5.7.14-SNAPSHOT
+ 5.7.16-SNAPSHOT
hutool-core
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 84b99c239..796124648 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
@@ -664,6 +664,32 @@ public class BeanUtil {
).copy();
}
+ /**
+ * 对象转Map
+ * 通过自定义{@link CopyOptions} 完成抓换选项,以便实现:
+ *
+ *
+ * 1. 字段筛选,可以去除不需要的字段
+ * 2. 字段变换,例如实现驼峰转下划线
+ * 3. 自定义字段前缀或后缀等等
+ * 4. 字段值处理
+ * ...
+ *
+ *
+ * @param bean bean对象
+ * @param targetMap 目标的Map
+ * @param copyOptions 拷贝选项
+ * @return Map
+ * @since 5.7.15
+ */
+ public static Map beanToMap(Object bean, Map targetMap, CopyOptions copyOptions) {
+ if (null == bean) {
+ return null;
+ }
+
+ return BeanCopier.create(bean, targetMap, copyOptions).copy();
+ }
+
// --------------------------------------------------------------------------------------------- copyProperties
/**
diff --git a/hutool-core/src/main/java/cn/hutool/core/bean/copier/BeanCopier.java b/hutool-core/src/main/java/cn/hutool/core/bean/copier/BeanCopier.java
index fc6c3947d..bcfae58a1 100644
--- a/hutool-core/src/main/java/cn/hutool/core/bean/copier/BeanCopier.java
+++ b/hutool-core/src/main/java/cn/hutool/core/bean/copier/BeanCopier.java
@@ -191,6 +191,10 @@ public class BeanCopier implements Copier, Serializable {
if(null != copyOptions.propertiesFilter && false == copyOptions.propertiesFilter.test(prop.getField(), value)) {
return;
}
+
+ // since 5.7.15
+ value = copyOptions.editFieldValue(key, value);
+
if ((null == value && copyOptions.ignoreNullValue) || bean == value) {
// 当允许跳过空时,跳过
//值不能为bean本身,防止循环引用,此类也跳过
@@ -257,6 +261,9 @@ public class BeanCopier implements Copier, Serializable {
return;
}
+ // since 5.7.15
+ value = copyOptions.editFieldValue(providerKey, value);
+
if ((null == value && copyOptions.ignoreNullValue) || bean == value) {
// 当允许跳过空时,跳过
// 值不能为bean本身,防止循环引用
diff --git a/hutool-core/src/main/java/cn/hutool/core/bean/copier/CopyOptions.java b/hutool-core/src/main/java/cn/hutool/core/bean/copier/CopyOptions.java
index 502ea4846..b0e35315f 100644
--- a/hutool-core/src/main/java/cn/hutool/core/bean/copier/CopyOptions.java
+++ b/hutool-core/src/main/java/cn/hutool/core/bean/copier/CopyOptions.java
@@ -7,6 +7,7 @@ import cn.hutool.core.util.ObjectUtil;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.Map;
+import java.util.function.BiFunction;
import java.util.function.BiPredicate;
/**
@@ -57,6 +58,10 @@ public class CopyOptions implements Serializable {
* 字段属性编辑器,用于自定义属性转换规则,例如驼峰转下划线等
*/
protected Editor fieldNameEditor;
+ /**
+ * 字段属性值编辑器,用于自定义属性值转换规则,例如null转""等
+ */
+ protected BiFunction fieldValueEditor;
/**
* 是否支持transient关键字修饰和@Transient注解,如果支持,被修饰的字段或方法对应的字段将被忽略。
*/
@@ -224,6 +229,31 @@ public class CopyOptions implements Serializable {
return this;
}
+ /**
+ * 设置字段属性值编辑器,用于自定义属性值转换规则,例如null转""等
+ *
+ * @param fieldValueEditor 字段属性值编辑器,用于自定义属性值转换规则,例如null转""等
+ * @return CopyOptions
+ * @since 5.7.15
+ */
+ public CopyOptions setFieldValueEditor(BiFunction fieldValueEditor) {
+ this.fieldValueEditor = fieldValueEditor;
+ return this;
+ }
+
+ /**
+ * 编辑字段值
+ *
+ * @param fieldName 字段名
+ * @param fieldValue 字段值
+ * @return 编辑后的字段值
+ * @since 5.7.15
+ */
+ protected Object editFieldValue(String fieldName, Object fieldValue) {
+ return (null != this.fieldValueEditor) ?
+ this.fieldValueEditor.apply(fieldName, fieldValue) : fieldValue;
+ }
+
/**
* 是否支持transient关键字修饰和@Transient注解,如果支持,被修饰的字段或方法对应的字段将被忽略。
*
@@ -251,12 +281,12 @@ public class CopyOptions implements Serializable {
* 当非反向,则根据源字段名获取目标字段名,反之根据目标字段名获取源字段名。
*
* @param fieldName 字段名
- * @param reversed 是否反向映射
+ * @param reversed 是否反向映射
* @return 映射后的字段名
*/
- protected String getMappedFieldName(String fieldName, boolean reversed){
+ protected String getMappedFieldName(String fieldName, boolean reversed) {
Map mapping = reversed ? getReversedMapping() : this.fieldMapping;
- if(MapUtil.isEmpty(mapping)){
+ if (MapUtil.isEmpty(mapping)) {
return fieldName;
}
return ObjectUtil.defaultIfNull(mapping.get(fieldName), fieldName);
@@ -264,11 +294,12 @@ public class CopyOptions implements Serializable {
/**
* 转换字段名为编辑后的字段名
+ *
* @param fieldName 字段名
* @return 编辑后的字段名
* @since 5.4.2
*/
- protected String editFieldName(String fieldName){
+ protected String editFieldName(String fieldName) {
return (null != this.fieldNameEditor) ? this.fieldNameEditor.edit(fieldName) : fieldName;
}
@@ -279,10 +310,10 @@ public class CopyOptions implements Serializable {
* @since 4.1.10
*/
private Map getReversedMapping() {
- if(null == this.fieldMapping){
+ if (null == this.fieldMapping) {
return null;
}
- if(null == this.reversedFieldMapping){
+ if (null == this.reversedFieldMapping) {
reversedFieldMapping = MapUtil.reverse(this.fieldMapping);
}
return reversedFieldMapping;
diff --git a/hutool-core/src/main/java/cn/hutool/core/codec/Base64.java b/hutool-core/src/main/java/cn/hutool/core/codec/Base64.java
index 976124c01..ec5b018f6 100644
--- a/hutool-core/src/main/java/cn/hutool/core/codec/Base64.java
+++ b/hutool-core/src/main/java/cn/hutool/core/codec/Base64.java
@@ -299,7 +299,7 @@ public class Base64 {
* base64解码
*
* @param base64 被解码的base64字符串
- * @return 被加密后的字符串
+ * @return 解码后的bytes
*/
public static byte[] decode(CharSequence base64) {
return Base64Decoder.decode(base64);
@@ -322,8 +322,19 @@ public class Base64 {
* @return 是否为Base64
* @since 5.7.5
*/
- public static boolean isBase64(CharSequence base64){
- return isBase64(StrUtil.utf8Bytes(base64));
+ public static boolean isBase64(CharSequence base64) {
+ if (base64 == null || base64.length() < 2) {
+ return false;
+ }
+
+ final byte[] bytes = StrUtil.utf8Bytes(base64);
+
+ if (bytes.length != base64.length()) {
+ // 如果长度不相等,说明存在双字节字符,肯定不是Base64,直接返回false
+ return false;
+ }
+
+ return isBase64(bytes);
}
/**
@@ -333,15 +344,15 @@ public class Base64 {
* @return 是否为Base64
* @since 5.7.5
*/
- public static boolean isBase64(byte[] base64Bytes){
+ public static boolean isBase64(byte[] base64Bytes) {
boolean hasPadding = false;
for (byte base64Byte : base64Bytes) {
- if(hasPadding){
- if('=' != base64Byte){
+ if (hasPadding) {
+ if ('=' != base64Byte) {
// 前一个字符是'=',则后边的字符都必须是'=',即'='只能都位于结尾
return false;
}
- } else if('=' == base64Byte){
+ } else if ('=' == base64Byte) {
// 发现'=' 标记之
hasPadding = true;
} else if (false == (Base64Decoder.isBase64Code(base64Byte) || isWhiteSpace(base64Byte))) {
@@ -353,12 +364,12 @@ public class Base64 {
private static boolean isWhiteSpace(byte byteToCheck) {
switch (byteToCheck) {
- case ' ' :
- case '\n' :
- case '\r' :
- case '\t' :
+ case ' ':
+ case '\n':
+ case '\r':
+ case '\t':
return true;
- default :
+ default:
return false;
}
}
diff --git a/hutool-core/src/main/java/cn/hutool/core/codec/PercentCodec.java b/hutool-core/src/main/java/cn/hutool/core/codec/PercentCodec.java
new file mode 100644
index 000000000..91faa74f6
--- /dev/null
+++ b/hutool-core/src/main/java/cn/hutool/core/codec/PercentCodec.java
@@ -0,0 +1,188 @@
+package cn.hutool.core.codec;
+
+import cn.hutool.core.util.CharUtil;
+import cn.hutool.core.util.HexUtil;
+import cn.hutool.core.util.StrUtil;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.Serializable;
+import java.nio.charset.Charset;
+import java.util.BitSet;
+
+/**
+ * 百分号编码(Percent-encoding), 也称作URL编码(URL encoding)。
+ * 百分号编码可用于URI的编码,也可以用于"application/x-www-form-urlencoded"的MIME准备数据。
+ *
+ *
+ * 百分号编码会对 URI 中不允许出现的字符或者其他特殊情况的允许的字符进行编码,对于被编码的字符,最终会转为以百分号"%“开头,后面跟着两位16进制数值的形式。
+ * 举个例子,空格符(SP)是不允许的字符,在 ASCII 码对应的二进制值是"00100000”,最终转为"%20"。
+ *
+ *
+ * 对于不同场景应遵循不同规范:
+ *
+ *
+ * - URI:遵循RFC 3986保留字规范
+ * - application/x-www-form-urlencoded,遵循W3C HTML Form content types规范,如空格须转+
+ *
+ *
+ * @author looly
+ * @since 5.7.16
+ */
+public class PercentCodec implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 从已知PercentCodec创建PercentCodec,会复制给定PercentCodec的安全字符
+ *
+ * @param codec PercentCodec
+ * @return PercentCodec
+ */
+ public static PercentCodec of(PercentCodec codec) {
+ return new PercentCodec((BitSet) codec.safeCharacters.clone());
+ }
+
+ /**
+ * 创建PercentCodec,使用指定字符串中的字符作为安全字符
+ *
+ * @param chars 安全字符合集
+ * @return PercentCodec
+ */
+ public static PercentCodec of(CharSequence chars) {
+ final PercentCodec codec = new PercentCodec();
+ final int length = chars.length();
+ for (int i = 0; i < length; i++) {
+ codec.addSafe(chars.charAt(i));
+ }
+ return codec;
+ }
+
+ /**
+ * 存放安全编码
+ */
+ private final BitSet safeCharacters;
+ /**
+ * 是否编码空格为+
+ */
+ private boolean encodeSpaceAsPlus = false;
+
+ /**
+ * 构造
+ * [a-zA-Z0-9]默认不被编码
+ */
+ public PercentCodec() {
+ this(new BitSet(256));
+ }
+
+ /**
+ * 构造
+ *
+ * @param safeCharacters 安全字符,安全字符不被编码
+ */
+ public PercentCodec(BitSet safeCharacters) {
+ this.safeCharacters = safeCharacters;
+ }
+
+ /**
+ * 增加安全字符
+ * 安全字符不被编码
+ *
+ * @param c 字符
+ * @return this
+ */
+ public PercentCodec addSafe(char c) {
+ safeCharacters.set(c);
+ return this;
+ }
+
+ /**
+ * 移除安全字符
+ * 安全字符不被编码
+ *
+ * @param c 字符
+ * @return this
+ */
+ public PercentCodec removeSafe(char c) {
+ safeCharacters.clear(c);
+ return this;
+ }
+
+ /**
+ * 增加安全字符到挡墙的PercentCodec
+ *
+ * @param codec PercentCodec
+ * @return this
+ */
+ public PercentCodec or(PercentCodec codec) {
+ this.safeCharacters.or(codec.safeCharacters);
+ return this;
+ }
+
+ /**
+ * 组合当前PercentCodec和指定PercentCodec为一个新的PercentCodec,安全字符为并集
+ *
+ * @param codec PercentCodec
+ * @return 新的PercentCodec
+ */
+ public PercentCodec orNew(PercentCodec codec) {
+ return of(this).or(codec);
+ }
+
+ /**
+ * 是否将空格编码为+
+ *
+ * @param encodeSpaceAsPlus 是否将空格编码为+
+ * @return this
+ */
+ public PercentCodec setEncodeSpaceAsPlus(boolean encodeSpaceAsPlus) {
+ this.encodeSpaceAsPlus = encodeSpaceAsPlus;
+ return this;
+ }
+
+ /**
+ * 将URL中的字符串编码为%形式
+ *
+ * @param path 需要编码的字符串
+ * @param charset 编码, {@code null}返回原字符串,表示不编码
+ * @return 编码后的字符串
+ */
+ public String encode(CharSequence path, Charset charset) {
+ if (null == charset || StrUtil.isEmpty(path)) {
+ return StrUtil.str(path);
+ }
+
+ final StringBuilder rewrittenPath = new StringBuilder(path.length());
+ final ByteArrayOutputStream buf = new ByteArrayOutputStream();
+ final OutputStreamWriter writer = new OutputStreamWriter(buf, charset);
+
+ int c;
+ for (int i = 0; i < path.length(); i++) {
+ c = path.charAt(i);
+ if (safeCharacters.get(c)) {
+ rewrittenPath.append((char) c);
+ } else if (encodeSpaceAsPlus && c == CharUtil.SPACE) {
+ // 对于空格单独处理
+ rewrittenPath.append('+');
+ } else {
+ // convert to external encoding before hex conversion
+ try {
+ writer.write((char) c);
+ writer.flush();
+ } catch (IOException e) {
+ buf.reset();
+ continue;
+ }
+
+ byte[] ba = buf.toByteArray();
+ for (byte toEncode : ba) {
+ // Converting each byte in the buffer
+ rewrittenPath.append('%');
+ HexUtil.appendHex(rewrittenPath, toEncode, false);
+ }
+ buf.reset();
+ }
+ }
+ return rewrittenPath.toString();
+ }
+}
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 2083ced3d..94e992aa1 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
@@ -53,6 +53,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.function.Function;
import java.util.function.Predicate;
+import java.util.function.Supplier;
/**
* 集合相关工具类
@@ -1050,8 +1051,8 @@ public class CollUtil {
* @param end 结束位置(不包含)
* @param step 步进
* @return 截取后的数组,当开始位置超过最大时,返回空的List
- * @since 4.0.6
* @see ListUtil#sub(List, int, int, int)
+ * @since 4.0.6
*/
public static List sub(List list, int start, int end, int step) {
return ListUtil.sub(list, start, end, step);
@@ -1073,11 +1074,11 @@ public class CollUtil {
/**
* 截取集合的部分
*
- * @param 集合元素类型
- * @param collection 被截取的数组
- * @param start 开始位置(包含)
- * @param end 结束位置(不包含)
- * @param step 步进
+ * @param 集合元素类型
+ * @param collection 被截取的数组
+ * @param start 开始位置(包含)
+ * @param end 结束位置(不包含)
+ * @param step 步进
* @return 截取后的数组,当开始位置超过最大时,返回空集合
* @since 4.0.6
*/
@@ -1086,7 +1087,7 @@ public class CollUtil {
return ListUtil.empty();
}
- final List list = collection instanceof List ? (List)collection : ListUtil.toList(collection);
+ final List list = collection instanceof List ? (List) collection : ListUtil.toList(collection);
return sub(list, start, end, step);
}
@@ -1575,6 +1576,20 @@ public class CollUtil {
return isEmpty(collection) ? defaultCollection : collection;
}
+ /**
+ * 如果给定集合为空,返回默认集合
+ *
+ * @param 集合类型
+ * @param 集合元素类型
+ * @param collection 集合
+ * @param supplier 默认值懒加载函数
+ * @return 非空(empty)的原集合或默认集合
+ * @since 5.7.15
+ */
+ public static , E> T defaultIfEmpty(T collection, Supplier extends T> supplier) {
+ return isEmpty(collection) ? supplier.get() : collection;
+ }
+
/**
* Iterable是否为空
*
@@ -2948,6 +2963,9 @@ public class CollUtil {
* @since 5.6.0
*/
public static boolean isEqualList(final Collection> list1, final Collection> list2) {
+ if (list1 == list2) {
+ return true;
+ }
if (list1 == null || list2 == null || list1.size() != list2.size()) {
return false;
}
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 ef0991fc8..c538e317c 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
@@ -819,7 +819,7 @@ public class IterUtil {
* @return Iterable对象的元素数量
* @since 5.5.0
*/
- public static int size(final Iterable> iterable) {
+ public static int size(Iterable> iterable) {
if (null == iterable) {
return 0;
}
@@ -838,7 +838,7 @@ public class IterUtil {
* @return Iterator对象的元素数量
* @since 5.5.0
*/
- public static int size(final Iterator> iterator) {
+ public static int size(Iterator> iterator) {
int size = 0;
if (iterator != null) {
while (iterator.hasNext()) {
@@ -862,7 +862,7 @@ public class IterUtil {
* @return 是否相同
* @since 5.6.0
*/
- public static boolean isEqualList(final Iterable> list1, final Iterable> list2) {
+ public static boolean isEqualList(Iterable> list1, Iterable> list2) {
if (list1 == list2) {
return true;
}
diff --git a/hutool-core/src/main/java/cn/hutool/core/collection/RingIndexUtil.java b/hutool-core/src/main/java/cn/hutool/core/collection/RingIndexUtil.java
new file mode 100644
index 000000000..b997b1428
--- /dev/null
+++ b/hutool-core/src/main/java/cn/hutool/core/collection/RingIndexUtil.java
@@ -0,0 +1,79 @@
+package cn.hutool.core.collection;
+
+import cn.hutool.core.lang.Assert;
+
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * 集合索引环形获取工具类
+ *
+ * @author ZhouChuGang
+ * @since 5.7.15
+ */
+public class RingIndexUtil {
+
+ /**
+ * 通过cas操作 实现对指定值内的回环累加
+ *
+ * @param object 集合
+ *
+ * - Collection - the collection size
+ *
- Map - the map size
+ *
- Array - the array size
+ *
- Iterator - the number of elements remaining in the iterator
+ *
- Enumeration - the number of elements remaining in the enumeration
+ *
+ * @param atomicInteger 原子操作类
+ * @return 索引位置
+ */
+ public static int ringNextIntByObj(Object object, AtomicInteger atomicInteger) {
+ Assert.notNull(object);
+ int modulo = CollUtil.size(object);
+ return ringNextInt(modulo, atomicInteger);
+ }
+
+ /**
+ * 通过cas操作 实现对指定值内的回环累加
+ *
+ * @param modulo 回环周期值
+ * @param atomicInteger 原子操作类
+ * @return 索引位置
+ */
+ public static int ringNextInt(int modulo, AtomicInteger atomicInteger) {
+ Assert.notNull(atomicInteger);
+ Assert.isTrue(modulo > 0);
+ if (modulo <= 1) {
+ return 0;
+ }
+ for (; ; ) {
+ int current = atomicInteger.get();
+ int next = (current + 1) % modulo;
+ if (atomicInteger.compareAndSet(current, next)) {
+ return next;
+ }
+ }
+ }
+
+ /**
+ * 通过cas操作 实现对指定值内的回环累加
+ *
+ * @param modulo 回环周期值
+ * @param atomicLong 原子操作类
+ * @return 索引位置
+ */
+ public static long ringNextLong(long modulo, AtomicLong atomicLong) {
+ Assert.notNull(atomicLong);
+ Assert.isTrue(modulo > 0);
+ if (modulo <= 1) {
+ return 0;
+ }
+ for (; ; ) {
+ long current = atomicLong.get();
+ long next = (current + 1) % modulo;
+ if (atomicLong.compareAndSet(current, next)) {
+ return next;
+ }
+ }
+ }
+}
diff --git a/hutool-core/src/main/java/cn/hutool/core/compiler/CompilerUtil.java b/hutool-core/src/main/java/cn/hutool/core/compiler/CompilerUtil.java
index 1af24f204..fb5cbc635 100644
--- a/hutool-core/src/main/java/cn/hutool/core/compiler/CompilerUtil.java
+++ b/hutool-core/src/main/java/cn/hutool/core/compiler/CompilerUtil.java
@@ -36,7 +36,7 @@ public class CompilerUtil {
* @return {@link StandardJavaFileManager}
*/
public static StandardJavaFileManager getFileManager() {
- return SYSTEM_COMPILER.getStandardFileManager(null, null, null);
+ return getFileManager(null);
}
/**
@@ -47,7 +47,7 @@ public class CompilerUtil {
* @since 5.5.8
*/
public static StandardJavaFileManager getFileManager(DiagnosticListener super JavaFileObject> diagnosticListener) {
- return SYSTEM_COMPILER.getStandardFileManager(null, null, null);
+ return SYSTEM_COMPILER.getStandardFileManager(diagnosticListener, null, null);
}
/**
diff --git a/hutool-core/src/main/java/cn/hutool/core/compress/ZipCopyVisitor.java b/hutool-core/src/main/java/cn/hutool/core/compress/ZipCopyVisitor.java
new file mode 100644
index 000000000..3423097be
--- /dev/null
+++ b/hutool-core/src/main/java/cn/hutool/core/compress/ZipCopyVisitor.java
@@ -0,0 +1,84 @@
+package cn.hutool.core.compress;
+
+import cn.hutool.core.util.StrUtil;
+
+import java.io.IOException;
+import java.nio.file.CopyOption;
+import java.nio.file.FileAlreadyExistsException;
+import java.nio.file.FileSystem;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+
+/**
+ * Zip文件拷贝的FileVisitor实现,zip中追加文件,此类非线程安全
+ * 此类在遍历源目录并复制过程中会自动创建目标目录中不存在的上级目录。
+ *
+ * @author looly
+ * @since 5.7.15
+ */
+public class ZipCopyVisitor extends SimpleFileVisitor {
+
+ /**
+ * 源Path,或基准路径,用于计算被拷贝文件的相对路径
+ */
+ private final Path source;
+ private final FileSystem fileSystem;
+ private final CopyOption[] copyOptions;
+
+ /**
+ * 构造
+ *
+ * @param source 源Path,或基准路径,用于计算被拷贝文件的相对路径
+ * @param fileSystem 目标Zip文件
+ * @param copyOptions 拷贝选项,如跳过已存在等
+ */
+ public ZipCopyVisitor(Path source, FileSystem fileSystem, CopyOption... copyOptions) {
+ this.source = source;
+ this.fileSystem = fileSystem;
+ this.copyOptions = copyOptions;
+ }
+
+ @Override
+ public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
+ final Path targetDir = resolveTarget(dir);
+ if(StrUtil.isNotEmpty(targetDir.toString())){
+ // 在目标的Zip文件中的相对位置创建目录
+ try {
+ Files.copy(dir, targetDir, copyOptions);
+ } catch (FileAlreadyExistsException e) {
+ if (false == Files.isDirectory(targetDir)) {
+ throw e;
+ }
+ }
+ }
+
+ return FileVisitResult.CONTINUE;
+ }
+
+ @Override
+ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
+ // 如果目标存在,无论目录还是文件都抛出FileAlreadyExistsException异常,此处不做特别处理
+ Files.copy(file, resolveTarget(file), copyOptions);
+
+ return FileVisitResult.CONTINUE;
+ }
+
+ /**
+ * 根据源文件或目录路径,拼接生成目标的文件或目录路径
+ * 原理是首先截取源路径,得到相对路径,再和目标路径拼接
+ *
+ *
+ * 如:源路径是 /opt/test/,需要拷贝的文件是 /opt/test/a/a.txt,得到相对路径 a/a.txt
+ * 目标路径是/home/,则得到最终目标路径是 /home/a/a.txt
+ *
+ *
+ * @param file 需要拷贝的文件或目录Path
+ * @return 目标Path
+ */
+ private Path resolveTarget(Path file) {
+ return fileSystem.getPath(source.relativize(file).toString());
+ }
+}
diff --git a/hutool-core/src/main/java/cn/hutool/core/compress/ZipWriter.java b/hutool-core/src/main/java/cn/hutool/core/compress/ZipWriter.java
index 48962fe5f..b27d86338 100755
--- a/hutool-core/src/main/java/cn/hutool/core/compress/ZipWriter.java
+++ b/hutool-core/src/main/java/cn/hutool/core/compress/ZipWriter.java
@@ -26,22 +26,22 @@ import java.util.zip.ZipOutputStream;
public class ZipWriter implements Closeable {
/**
- * 创建{@link ZipWriter}
+ * 创建ZipWriter
*
* @param zipFile 生成的Zip文件
* @param charset 编码
- * @return {@link ZipWriter}
+ * @return ZipWriter
*/
public static ZipWriter of(File zipFile, Charset charset) {
return new ZipWriter(zipFile, charset);
}
/**
- * 创建{@link ZipWriter}
+ * 创建ZipWriter
*
* @param out Zip输出的流,一般为输出文件流
* @param charset 编码
- * @return {@link ZipWriter}
+ * @return ZipWriter
*/
public static ZipWriter of(OutputStream out, Charset charset) {
return new ZipWriter(out, charset);
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 2475cb022..5cac6c295 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
@@ -20,6 +20,7 @@ import cn.hutool.core.convert.impl.EnumConverter;
import cn.hutool.core.convert.impl.LocaleConverter;
import cn.hutool.core.convert.impl.MapConverter;
import cn.hutool.core.convert.impl.NumberConverter;
+import cn.hutool.core.convert.impl.OptConverter;
import cn.hutool.core.convert.impl.OptionalConverter;
import cn.hutool.core.convert.impl.PathConverter;
import cn.hutool.core.convert.impl.PeriodConverter;
@@ -33,6 +34,7 @@ import cn.hutool.core.convert.impl.URIConverter;
import cn.hutool.core.convert.impl.URLConverter;
import cn.hutool.core.convert.impl.UUIDConverter;
import cn.hutool.core.date.DateTime;
+import cn.hutool.core.lang.Opt;
import cn.hutool.core.lang.TypeReference;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.ObjectUtil;
@@ -444,6 +446,7 @@ public class ConverterRegistry implements Serializable {
defaultConverterMap.put(UUID.class, new UUIDConverter());// since 4.0.10
defaultConverterMap.put(StackTraceElement.class, new StackTraceElementConverter());// since 4.5.2
defaultConverterMap.put(Optional.class, new OptionalConverter());// since 5.0.0
+ defaultConverterMap.put(Opt.class, new OptConverter());// since 5.7.16
return this;
}
diff --git a/hutool-core/src/main/java/cn/hutool/core/convert/impl/OptConverter.java b/hutool-core/src/main/java/cn/hutool/core/convert/impl/OptConverter.java
new file mode 100644
index 000000000..cf79c179e
--- /dev/null
+++ b/hutool-core/src/main/java/cn/hutool/core/convert/impl/OptConverter.java
@@ -0,0 +1,21 @@
+package cn.hutool.core.convert.impl;
+
+import cn.hutool.core.convert.AbstractConverter;
+import cn.hutool.core.lang.Opt;
+
+/**
+ *
+ * {@link Opt}对象转换器
+ *
+ * @author Looly
+ * @since 5.7.16
+ */
+public class OptConverter extends AbstractConverter> {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ protected Opt> convertInternal(Object value) {
+ return Opt.ofNullable(value);
+ }
+
+}
diff --git a/hutool-core/src/main/java/cn/hutool/core/date/DateTime.java b/hutool-core/src/main/java/cn/hutool/core/date/DateTime.java
index f9b37927f..5727c460f 100644
--- a/hutool-core/src/main/java/cn/hutool/core/date/DateTime.java
+++ b/hutool-core/src/main/java/cn/hutool/core/date/DateTime.java
@@ -7,11 +7,13 @@ import cn.hutool.core.date.format.GlobalCustomFormat;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
+import cn.hutool.core.util.SystemPropsUtil;
import java.sql.Timestamp;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.time.Instant;
+import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
@@ -167,7 +169,7 @@ public class DateTime extends Date {
* @since 5.0.5
*/
public DateTime(Instant instant, ZoneId zoneId) {
- this(instant.toEpochMilli(), TimeZone.getTimeZone(ObjectUtil.defaultIfNull(zoneId, ZoneId.systemDefault())));
+ this(instant.toEpochMilli(), ZoneUtil.toTimeZone(zoneId));
}
/**
@@ -177,7 +179,7 @@ public class DateTime extends Date {
* @since 5.0.0
*/
public DateTime(TemporalAccessor temporalAccessor) {
- this(DateUtil.toInstant(temporalAccessor));
+ this(TemporalAccessorUtil.toInstant(temporalAccessor));
}
/**
@@ -276,7 +278,7 @@ public class DateTime extends Date {
* @since 5.0.0
*/
public DateTime(CharSequence dateStr, DateTimeFormatter formatter) {
- this(Instant.from(formatter.parse(dateStr)), formatter.getZone());
+ this(TemporalAccessorUtil.toInstant(formatter.parse(dateStr)), formatter.getZone());
}
/**
@@ -287,7 +289,7 @@ public class DateTime extends Date {
* @see DatePattern
*/
public DateTime(CharSequence dateStr, DateParser dateParser) {
- this(dateStr, dateParser, true);
+ this(dateStr, dateParser, SystemPropsUtil.getBoolean(SystemPropsUtil.HUTOOL_DATE_LENIENT, true));
}
/**
@@ -701,6 +703,16 @@ public class DateTime extends Date {
return new java.sql.Date(getTime());
}
+ /**
+ * 转换为 {@link LocalDateTime}
+ *
+ * @return {@link LocalDateTime}
+ * @since 5.7.16
+ */
+ public LocalDateTime toLocalDateTime() {
+ return LocalDateTimeUtil.of(this);
+ }
+
/**
* 计算相差时长
*
diff --git a/hutool-core/src/main/java/cn/hutool/core/date/DateUtil.java b/hutool-core/src/main/java/cn/hutool/core/date/DateUtil.java
index 4ba6d7a9b..37c935d04 100644
--- a/hutool-core/src/main/java/cn/hutool/core/date/DateUtil.java
+++ b/hutool-core/src/main/java/cn/hutool/core/date/DateUtil.java
@@ -2059,6 +2059,32 @@ public class DateUtil extends CalendarUtil {
return format;
}
+ /**
+ * 获取时长单位简写
+ *
+ * @param unit 单位
+ * @return 单位简写名称
+ * @since 5.7.16
+ */
+ public static String getShotName(TimeUnit unit) {
+ switch (unit) {
+ case NANOSECONDS:
+ return "ns";
+ case MICROSECONDS:
+ return "μs";
+ case MILLISECONDS:
+ return "ms";
+ case SECONDS:
+ return "s";
+ case MINUTES:
+ return "min";
+ case HOURS:
+ return "h";
+ default:
+ return unit.name().toLowerCase();
+ }
+ }
+
// ------------------------------------------------------------------------ Private method start
/**
diff --git a/hutool-core/src/main/java/cn/hutool/core/date/StopWatch.java b/hutool-core/src/main/java/cn/hutool/core/date/StopWatch.java
index 7bf6b3401..e83bd25de 100755
--- a/hutool-core/src/main/java/cn/hutool/core/date/StopWatch.java
+++ b/hutool-core/src/main/java/cn/hutool/core/date/StopWatch.java
@@ -6,6 +6,7 @@ import cn.hutool.core.util.StrUtil;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.TimeUnit;
/**
* 秒表封装
@@ -48,7 +49,7 @@ public class StopWatch {
* @return StopWatch
* @since 5.5.2
*/
- public static StopWatch create(String id){
+ public static StopWatch create(String id) {
return new StopWatch(id);
}
@@ -251,6 +252,17 @@ public class StopWatch {
return this.lastTaskInfo;
}
+ /**
+ * 获取所有任务的总花费时间
+ *
+ * @param unit 时间单位,{@code null}表示默认{@link TimeUnit#NANOSECONDS}
+ * @return 花费时间
+ * @since 5.7.16
+ */
+ public long getTotal(TimeUnit unit){
+ return unit.convert(this.totalTimeNanos, TimeUnit.NANOSECONDS);
+ }
+
/**
* 获取所有任务的总花费时间(纳秒)
*
@@ -270,7 +282,7 @@ public class StopWatch {
* @see #getTotalTimeSeconds()
*/
public long getTotalTimeMillis() {
- return DateUtil.nanosToMillis(this.totalTimeNanos);
+ return getTotal(TimeUnit.MILLISECONDS);
}
/**
@@ -306,27 +318,62 @@ public class StopWatch {
}
/**
- * 获取任务信息
+ * 获取任务信息,类似于:
+ *
+ * StopWatch '[id]': running time = [total] ns
+ *
*
* @return 任务信息
*/
public String shortSummary() {
- return StrUtil.format("StopWatch '{}': running time = {} ns", this.id, this.totalTimeNanos);
+ return shortSummary(null);
+ }
+
+ /**
+ * 获取任务信息,类似于:
+ *
+ * StopWatch '[id]': running time = [total] [unit]
+ *
+ *
+ * @param unit 时间单位,{@code null}则默认为{@link TimeUnit#NANOSECONDS}
+ * @return 任务信息
+ */
+ public String shortSummary(TimeUnit unit) {
+ if(null == unit){
+ unit = TimeUnit.NANOSECONDS;
+ }
+ return StrUtil.format("StopWatch '{}': running time = {} {}",
+ this.id, getTotal(unit), DateUtil.getShotName(unit));
+ }
+
+ /**
+ * 生成所有任务的一个任务花费时间表,单位纳秒
+ *
+ * @return 任务时间表
+ */
+ public String prettyPrint() {
+ return prettyPrint(null);
}
/**
* 生成所有任务的一个任务花费时间表
*
+ * @param unit 时间单位,{@code null}则默认{@link TimeUnit#NANOSECONDS} 纳秒
* @return 任务时间表
+ * @since 5.7.16
*/
- public String prettyPrint() {
- StringBuilder sb = new StringBuilder(shortSummary());
+ public String prettyPrint(TimeUnit unit) {
+ if (null == unit) {
+ unit = TimeUnit.NANOSECONDS;
+ }
+
+ final StringBuilder sb = new StringBuilder(shortSummary(unit));
sb.append(FileUtil.getLineSeparator());
if (null == this.taskList) {
sb.append("No task info kept");
} else {
sb.append("---------------------------------------------").append(FileUtil.getLineSeparator());
- sb.append("ns % Task name").append(FileUtil.getLineSeparator());
+ sb.append(DateUtil.getShotName(unit)).append(" % Task name").append(FileUtil.getLineSeparator());
sb.append("---------------------------------------------").append(FileUtil.getLineSeparator());
final NumberFormat nf = NumberFormat.getNumberInstance();
@@ -334,11 +381,12 @@ public class StopWatch {
nf.setGroupingUsed(false);
final NumberFormat pf = NumberFormat.getPercentInstance();
- pf.setMinimumIntegerDigits(3);
+ pf.setMinimumIntegerDigits(2);
pf.setGroupingUsed(false);
+
for (TaskInfo task : getTaskInfo()) {
- sb.append(nf.format(task.getTimeNanos())).append(" ");
- sb.append(pf.format((double) task.getTimeNanos() / getTotalTimeNanos())).append(" ");
+ sb.append(nf.format(task.getTime(unit))).append(" ");
+ sb.append(pf.format((double) task.getTimeNanos() / getTotalTimeNanos())).append(" ");
sb.append(task.getTaskName()).append(FileUtil.getLineSeparator());
}
}
@@ -370,6 +418,12 @@ public class StopWatch {
private final String taskName;
private final long timeNanos;
+ /**
+ * 构造
+ *
+ * @param taskName 任务名称
+ * @param timeNanos 花费时间(纳秒)
+ */
TaskInfo(String taskName, long timeNanos) {
this.taskName = taskName;
this.timeNanos = timeNanos;
@@ -384,6 +438,17 @@ public class StopWatch {
return this.taskName;
}
+ /**
+ * 获取指定单位的任务花费时间
+ *
+ * @param unit 单位
+ * @return 任务花费时间
+ * @since 5.7.16
+ */
+ public long getTime(TimeUnit unit) {
+ return unit.convert(this.timeNanos, TimeUnit.NANOSECONDS);
+ }
+
/**
* 获取任务花费时间(单位:纳秒)
*
@@ -403,7 +468,7 @@ public class StopWatch {
* @see #getTimeSeconds()
*/
public long getTimeMillis() {
- return DateUtil.nanosToMillis(this.timeNanos);
+ return getTime(TimeUnit.MILLISECONDS);
}
/**
diff --git a/hutool-core/src/main/java/cn/hutool/core/date/TemporalAccessorUtil.java b/hutool-core/src/main/java/cn/hutool/core/date/TemporalAccessorUtil.java
index f2918c55e..1d9baef32 100644
--- a/hutool-core/src/main/java/cn/hutool/core/date/TemporalAccessorUtil.java
+++ b/hutool-core/src/main/java/cn/hutool/core/date/TemporalAccessorUtil.java
@@ -138,7 +138,10 @@ public class TemporalAccessorUtil extends TemporalUtil{
// 指定本地时间转换 为Instant,取当天日期
result = ((OffsetTime) temporalAccessor).atDate(LocalDate.now()).toInstant();
} else {
- result = Instant.from(temporalAccessor);
+ // issue#1891@Github
+ // Instant.from不能完成日期转换
+ //result = Instant.from(temporalAccessor);
+ result = toInstant(LocalDateTimeUtil.of(temporalAccessor));
}
return result;
diff --git a/hutool-core/src/main/java/cn/hutool/core/date/TemporalUtil.java b/hutool-core/src/main/java/cn/hutool/core/date/TemporalUtil.java
index 28878b69a..d003f4971 100644
--- a/hutool-core/src/main/java/cn/hutool/core/date/TemporalUtil.java
+++ b/hutool-core/src/main/java/cn/hutool/core/date/TemporalUtil.java
@@ -3,6 +3,7 @@ package cn.hutool.core.date;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.time.temporal.Temporal;
+import java.util.concurrent.TimeUnit;
/**
* {@link Temporal} 工具类封装
@@ -38,4 +39,67 @@ public class TemporalUtil {
public static long between(Temporal startTimeInclude, Temporal endTimeExclude, ChronoUnit unit) {
return unit.between(startTimeInclude, endTimeExclude);
}
+
+ /**
+ * 将 {@link TimeUnit} 转换为 {@link ChronoUnit}.
+ *
+ * @param unit 被转换的{@link TimeUnit}单位,如果为{@code null}返回{@code null}
+ * @return {@link ChronoUnit}
+ * @since 5.7.16
+ */
+ public static ChronoUnit toChronoUnit(TimeUnit unit) throws IllegalArgumentException {
+ if (null == unit) {
+ return null;
+ }
+ switch (unit) {
+ case NANOSECONDS:
+ return ChronoUnit.NANOS;
+ case MICROSECONDS:
+ return ChronoUnit.MICROS;
+ case MILLISECONDS:
+ return ChronoUnit.MILLIS;
+ case SECONDS:
+ return ChronoUnit.SECONDS;
+ case MINUTES:
+ return ChronoUnit.MINUTES;
+ case HOURS:
+ return ChronoUnit.HOURS;
+ case DAYS:
+ return ChronoUnit.DAYS;
+ default:
+ throw new IllegalArgumentException("Unknown TimeUnit constant");
+ }
+ }
+
+ /**
+ * 转换 {@link ChronoUnit} 到 {@link TimeUnit}.
+ *
+ * @param unit {@link ChronoUnit},如果为{@code null}返回{@code null}
+ * @return {@link TimeUnit}
+ * @throws IllegalArgumentException 如果{@link TimeUnit}没有对应单位抛出
+ * @since 5.7.16
+ */
+ public static TimeUnit toTimeUnit(ChronoUnit unit) throws IllegalArgumentException {
+ if (null == unit) {
+ return null;
+ }
+ switch (unit) {
+ case NANOS:
+ return TimeUnit.NANOSECONDS;
+ case MICROS:
+ return TimeUnit.MICROSECONDS;
+ case MILLIS:
+ return TimeUnit.MILLISECONDS;
+ case SECONDS:
+ return TimeUnit.SECONDS;
+ case MINUTES:
+ return TimeUnit.MINUTES;
+ case HOURS:
+ return TimeUnit.HOURS;
+ case DAYS:
+ return TimeUnit.DAYS;
+ default:
+ throw new IllegalArgumentException("ChronoUnit cannot be converted to TimeUnit: " + unit);
+ }
+ }
}
diff --git a/hutool-core/src/main/java/cn/hutool/core/date/ZoneUtil.java b/hutool-core/src/main/java/cn/hutool/core/date/ZoneUtil.java
new file mode 100644
index 000000000..1b12788da
--- /dev/null
+++ b/hutool-core/src/main/java/cn/hutool/core/date/ZoneUtil.java
@@ -0,0 +1,41 @@
+package cn.hutool.core.date;
+
+import java.time.ZoneId;
+import java.util.TimeZone;
+
+/**
+ * {@link ZoneId}和{@link TimeZone}相关封装
+ *
+ * @author looly
+ * @since 5.7.15
+ */
+public class ZoneUtil {
+
+ /**
+ * {@link ZoneId}转换为{@link TimeZone},{@code null}则返回系统默认值
+ *
+ * @param zoneId {@link ZoneId},{@code null}则返回系统默认值
+ * @return {@link TimeZone}
+ */
+ public static TimeZone toTimeZone(ZoneId zoneId) {
+ if (null == zoneId) {
+ return TimeZone.getDefault();
+ }
+
+ return TimeZone.getTimeZone(zoneId);
+ }
+
+ /**
+ * {@link TimeZone}转换为{@link ZoneId},{@code null}则返回系统默认值
+ *
+ * @param timeZone {@link TimeZone},{@code null}则返回系统默认值
+ * @return {@link ZoneId}
+ */
+ public static ZoneId toZoneId(TimeZone timeZone) {
+ if (null == timeZone) {
+ return ZoneId.systemDefault();
+ }
+
+ return timeZone.toZoneId();
+ }
+}
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 7f5b4c28f..4c838288d 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
@@ -3278,7 +3278,10 @@ public class FileUtil extends PathUtil {
parentCanonicalPath = parentFile.getCanonicalPath();
canonicalPath = file.getCanonicalPath();
} catch (IOException e) {
- throw new IORuntimeException(e);
+ // issue#I4CWMO@Gitee
+ // getCanonicalPath有时会抛出奇怪的IO异常,此时忽略异常,使用AbsolutePath判断。
+ parentCanonicalPath = parentFile.getAbsolutePath();
+ canonicalPath = file.getAbsolutePath();
}
if (false == canonicalPath.startsWith(parentCanonicalPath)) {
throw new IllegalArgumentException("New file is outside of the parent dir: " + file.getName());
diff --git a/hutool-core/src/main/java/cn/hutool/core/io/IoUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/IoUtil.java
index 3479c17b8..02699d4a4 100644
--- a/hutool-core/src/main/java/cn/hutool/core/io/IoUtil.java
+++ b/hutool-core/src/main/java/cn/hutool/core/io/IoUtil.java
@@ -684,7 +684,7 @@ public class IoUtil extends NioUtil {
* @return 内容
* @throws IORuntimeException IO异常
*/
- public static > T readLines(Reader reader, final T collection) throws IORuntimeException {
+ public static > T readLines(Reader reader, T collection) throws IORuntimeException {
readLines(reader, (LineHandler) collection::add);
return collection;
}
diff --git a/hutool-core/src/main/java/cn/hutool/core/io/file/FileSystemUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/file/FileSystemUtil.java
new file mode 100644
index 000000000..502bf09be
--- /dev/null
+++ b/hutool-core/src/main/java/cn/hutool/core/io/file/FileSystemUtil.java
@@ -0,0 +1,84 @@
+package cn.hutool.core.io.file;
+
+import cn.hutool.core.io.IORuntimeException;
+import cn.hutool.core.map.MapUtil;
+import cn.hutool.core.util.CharsetUtil;
+import cn.hutool.core.util.StrUtil;
+
+import java.io.IOException;
+import java.net.URI;
+import java.nio.charset.Charset;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystems;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.HashMap;
+
+/**
+ * {@link FileSystem}相关工具类封装
+ * 参考:https://blog.csdn.net/j16421881/article/details/78858690
+ *
+ * @author looly
+ * @since 5.7.15
+ */
+public class FileSystemUtil {
+
+ /**
+ * 创建 {@link FileSystem}
+ *
+ * @param path 文件路径,可以是目录或Zip文件等
+ * @return {@link FileSystem}
+ */
+ public static FileSystem create(String path) {
+ try {
+ return FileSystems.newFileSystem(
+ Paths.get(path).toUri(),
+ MapUtil.of("create", "true"));
+ } catch (IOException e) {
+ throw new IORuntimeException(e);
+ }
+ }
+
+ /**
+ * 创建 Zip的{@link FileSystem},默认UTF-8编码
+ *
+ * @param path 文件路径,可以是目录或Zip文件等
+ * @return {@link FileSystem}
+ */
+ public static FileSystem createZip(String path) {
+ return createZip(path, null);
+ }
+
+ /**
+ * 创建 Zip的{@link FileSystem}
+ *
+ * @param path 文件路径,可以是目录或Zip文件等
+ * @param charset 编码
+ * @return {@link FileSystem}
+ */
+ public static FileSystem createZip(String path, Charset charset) {
+ if(null == charset){
+ charset = CharsetUtil.CHARSET_UTF_8;
+ }
+ final HashMap env = new HashMap<>();
+ env.put("create", "true");
+ env.put("encoding", charset.name());
+
+ try {
+ return FileSystems.newFileSystem(
+ URI.create("jar:" + Paths.get(path).toUri()), env);
+ } catch (IOException e) {
+ throw new IORuntimeException(e);
+ }
+ }
+
+ /**
+ * 获取目录的根路径,或Zip文件中的根路径
+ *
+ * @param fileSystem {@link FileSystem}
+ * @return 根 {@link Path}
+ */
+ public static Path getRoot(FileSystem fileSystem) {
+ return fileSystem.getPath(StrUtil.SLASH);
+ }
+}
diff --git a/hutool-core/src/main/java/cn/hutool/core/io/file/PathUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/file/PathUtil.java
index d9423d126..1c3ed2d92 100644
--- a/hutool-core/src/main/java/cn/hutool/core/io/file/PathUtil.java
+++ b/hutool-core/src/main/java/cn/hutool/core/io/file/PathUtil.java
@@ -55,6 +55,19 @@ public class PathUtil {
}
}
+ /**
+ * 递归遍历目录以及子目录中的所有文件
+ * 如果提供path为文件,直接返回过滤结果
+ *
+ * @param path 当前遍历文件或目录
+ * @param fileFilter 文件过滤规则对象,选择要保留的文件,只对文件有效,不过滤目录,null表示接收全部文件
+ * @return 文件列表
+ * @since 5.4.1
+ */
+ public static List loopFiles(Path path, FileFilter fileFilter) {
+ return loopFiles(path, -1, fileFilter);
+ }
+
/**
* 递归遍历目录以及子目录中的所有文件
* 如果提供path为文件,直接返回过滤结果
@@ -643,6 +656,20 @@ public class PathUtil {
return mkdir(path.getParent());
}
+ /**
+ * 获取{@link Path}文件名
+ *
+ * @param path {@link Path}
+ * @return 文件名
+ * @since 5.7.15
+ */
+ public static String getName(Path path) {
+ if (null == path) {
+ return null;
+ }
+ return path.getFileName().toString();
+ }
+
/**
* 删除文件或空目录,不追踪软链
*
diff --git a/hutool-core/src/main/java/cn/hutool/core/io/file/visitor/CopyVisitor.java b/hutool-core/src/main/java/cn/hutool/core/io/file/visitor/CopyVisitor.java
index 842dbff1e..b36e8c193 100644
--- a/hutool-core/src/main/java/cn/hutool/core/io/file/visitor/CopyVisitor.java
+++ b/hutool-core/src/main/java/cn/hutool/core/io/file/visitor/CopyVisitor.java
@@ -20,20 +20,27 @@ import java.nio.file.attribute.BasicFileAttributes;
*/
public class CopyVisitor extends SimpleFileVisitor {
+ /**
+ * 源Path,或基准路径,用于计算被拷贝文件的相对路径
+ */
private final Path source;
private final Path target;
- private boolean isTargetCreated;
private final CopyOption[] copyOptions;
+ /**
+ * 标记目标目录是否创建,省略每次判断目标是否存在
+ */
+ private boolean isTargetCreated;
+
/**
* 构造
*
- * @param source 源Path
- * @param target 目标Path
+ * @param source 源Path,或基准路径,用于计算被拷贝文件的相对路径
+ * @param target 目标Path
* @param copyOptions 拷贝选项,如跳过已存在等
*/
public CopyVisitor(Path source, Path target, CopyOption... copyOptions) {
- if(PathUtil.exists(target, false) && false == PathUtil.isDirectory(target)){
+ if (PathUtil.exists(target, false) && false == PathUtil.isDirectory(target)) {
throw new IllegalArgumentException("Target must be a directory");
}
this.source = source;
@@ -42,16 +49,19 @@ public class CopyVisitor extends SimpleFileVisitor {
}
@Override
- public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
- throws IOException {
- initTarget();
+ public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
+ initTargetDir();
// 将当前目录相对于源路径转换为相对于目标路径
- final Path targetDir = target.resolve(source.relativize(dir));
+ final Path targetDir = resolveTarget(dir);
+
+ // 在目录不存在的情况下,copy方法会创建新目录
try {
Files.copy(dir, targetDir, copyOptions);
} catch (FileAlreadyExistsException e) {
- if (false == Files.isDirectory(targetDir))
+ if (false == Files.isDirectory(targetDir)) {
+ // 目标文件存在抛出异常,目录忽略
throw e;
+ }
}
return FileVisitResult.CONTINUE;
}
@@ -59,16 +69,33 @@ public class CopyVisitor extends SimpleFileVisitor {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
throws IOException {
- initTarget();
- Files.copy(file, target.resolve(source.relativize(file)), copyOptions);
+ initTargetDir();
+ // 如果目标存在,无论目录还是文件都抛出FileAlreadyExistsException异常,此处不做特别处理
+ Files.copy(file, resolveTarget(file), copyOptions);
return FileVisitResult.CONTINUE;
}
+ /**
+ * 根据源文件或目录路径,拼接生成目标的文件或目录路径
+ * 原理是首先截取源路径,得到相对路径,再和目标路径拼接
+ *
+ *
+ * 如:源路径是 /opt/test/,需要拷贝的文件是 /opt/test/a/a.txt,得到相对路径 a/a.txt
+ * 目标路径是/home/,则得到最终目标路径是 /home/a/a.txt
+ *
+ *
+ * @param file 需要拷贝的文件或目录Path
+ * @return 目标Path
+ */
+ private Path resolveTarget(Path file) {
+ return target.resolve(source.relativize(file));
+ }
+
/**
* 初始化目标文件或目录
*/
- private void initTarget(){
- if(false == this.isTargetCreated){
+ private void initTargetDir() {
+ if (false == this.isTargetCreated) {
PathUtil.mkdir(this.target);
this.isTargetCreated = true;
}
diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/Assert.java b/hutool-core/src/main/java/cn/hutool/core/lang/Assert.java
index 1efdd5aef..a33b2a4ed 100644
--- a/hutool-core/src/main/java/cn/hutool/core/lang/Assert.java
+++ b/hutool-core/src/main/java/cn/hutool/core/lang/Assert.java
@@ -16,6 +16,9 @@ import java.util.function.Supplier;
*/
public class Assert {
+ private static final String TEMPLATE_VALUE_MUST_BE_BETWEEN_AND = "The value must be between {} and {}.";
+
+
/**
* 断言是否为真,如果为 {@code false} 抛出给定的异常
*
@@ -834,6 +837,41 @@ public class Assert {
return index;
}
+ /**
+ * 检查值是否在指定范围内
+ *
+ * @param 异常类型
+ * @param value 值
+ * @param min 最小值(包含)
+ * @param max 最大值(包含)
+ * @param errorSupplier 错误抛出异常附带的消息生产接口
+ * @return 经过检查后的值
+ * @throws X if value is out of bound
+ * @since 5.7.15
+ */
+ public static int checkBetween(int value, int min, int max, Supplier extends X> errorSupplier) throws X {
+ if (value < min || value > max) {
+ throw errorSupplier.get();
+ }
+
+ return value;
+ }
+
+ /**
+ * 检查值是否在指定范围内
+ *
+ * @param value 值
+ * @param min 最小值(包含)
+ * @param max 最大值(包含)
+ * @param errorMsgTemplate 异常信息模板,类似于"aa{}bb{}cc"
+ * @param params 异常信息参数,用于替换"{}"占位符
+ * @return 经过检查后的值
+ * @since 5.7.15
+ */
+ public static int checkBetween(int value, int min, int max, String errorMsgTemplate, Object... params) {
+ return checkBetween(value, min, max, () -> new IllegalArgumentException(StrUtil.format(errorMsgTemplate, params)));
+ }
+
/**
* 检查值是否在指定范围内
*
@@ -844,12 +882,44 @@ public class Assert {
* @since 4.1.10
*/
public static int checkBetween(int value, int min, int max) {
+ return checkBetween(value, min, max, TEMPLATE_VALUE_MUST_BE_BETWEEN_AND, min, max);
+ }
+
+ /**
+ * 检查值是否在指定范围内
+ *
+ * @param 异常类型
+ * @param value 值
+ * @param min 最小值(包含)
+ * @param max 最大值(包含)
+ * @param errorSupplier 错误抛出异常附带的消息生产接口
+ * @return 经过检查后的值
+ * @throws X if value is out of bound
+ * @since 5.7.15
+ */
+ public static long checkBetween(long value, long min, long max, Supplier extends X> errorSupplier) throws X {
if (value < min || value > max) {
- throw new IllegalArgumentException(StrUtil.format("Length must be between {} and {}.", min, max));
+ throw errorSupplier.get();
}
+
return value;
}
+ /**
+ * 检查值是否在指定范围内
+ *
+ * @param value 值
+ * @param min 最小值(包含)
+ * @param max 最大值(包含)
+ * @param errorMsgTemplate 异常信息模板,类似于"aa{}bb{}cc"
+ * @param params 异常信息参数,用于替换"{}"占位符
+ * @return 经过检查后的值
+ * @since 5.7.15
+ */
+ public static long checkBetween(long value, long min, long max, String errorMsgTemplate, Object... params) {
+ return checkBetween(value, min, max, () -> new IllegalArgumentException(StrUtil.format(errorMsgTemplate, params)));
+ }
+
/**
* 检查值是否在指定范围内
*
@@ -860,12 +930,44 @@ public class Assert {
* @since 4.1.10
*/
public static long checkBetween(long value, long min, long max) {
+ return checkBetween(value, min, max, TEMPLATE_VALUE_MUST_BE_BETWEEN_AND, min, max);
+ }
+
+ /**
+ * 检查值是否在指定范围内
+ *
+ * @param 异常类型
+ * @param value 值
+ * @param min 最小值(包含)
+ * @param max 最大值(包含)
+ * @param errorSupplier 错误抛出异常附带的消息生产接口
+ * @return 经过检查后的值
+ * @throws X if value is out of bound
+ * @since 5.7.15
+ */
+ public static double checkBetween(double value, double min, double max, Supplier extends X> errorSupplier) throws X {
if (value < min || value > max) {
- throw new IllegalArgumentException(StrUtil.format("Length must be between {} and {}.", min, max));
+ throw errorSupplier.get();
}
+
return value;
}
+ /**
+ * 检查值是否在指定范围内
+ *
+ * @param value 值
+ * @param min 最小值(包含)
+ * @param max 最大值(包含)
+ * @param errorMsgTemplate 异常信息模板,类似于"aa{}bb{}cc"
+ * @param params 异常信息参数,用于替换"{}"占位符
+ * @return 经过检查后的值
+ * @since 5.7.15
+ */
+ public static double checkBetween(double value, double min, double max, String errorMsgTemplate, Object... params) {
+ return checkBetween(value, min, max, () -> new IllegalArgumentException(StrUtil.format(errorMsgTemplate, params)));
+ }
+
/**
* 检查值是否在指定范围内
*
@@ -876,10 +978,7 @@ public class Assert {
* @since 4.1.10
*/
public static double checkBetween(double value, double min, double max) {
- if (value < min || value > max) {
- throw new IllegalArgumentException(StrUtil.format("Length must be between {} and {}.", min, max));
- }
- return value;
+ return checkBetween(value, min, max, TEMPLATE_VALUE_MUST_BE_BETWEEN_AND, min, max);
}
/**
@@ -899,7 +998,7 @@ public class Assert {
double minDouble = min.doubleValue();
double maxDouble = max.doubleValue();
if (valueDouble < minDouble || valueDouble > maxDouble) {
- throw new IllegalArgumentException(StrUtil.format("Length must be between {} and {}.", min, max));
+ throw new IllegalArgumentException(StrUtil.format(TEMPLATE_VALUE_MUST_BE_BETWEEN_AND, min, max));
}
return value;
}
diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/Pair.java b/hutool-core/src/main/java/cn/hutool/core/lang/Pair.java
index b6cfa3ca8..a7cb9cd69 100644
--- a/hutool-core/src/main/java/cn/hutool/core/lang/Pair.java
+++ b/hutool-core/src/main/java/cn/hutool/core/lang/Pair.java
@@ -16,17 +16,17 @@ import java.util.Objects;
public class Pair extends CloneSupport> implements Serializable {
private static final long serialVersionUID = 1L;
- private final K key;
- private final V value;
+ protected K key;
+ protected V value;
/**
- * 构建{@link Pair}对象
+ * 构建{@code Pair}对象
*
* @param 键类型
* @param 值类型
* @param key 键
* @param value 值
- * @return {@link Pair}
+ * @return {@code Pair}
* @since 5.4.3
*/
public static Pair of(K key, V value) {
diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/RegexPool.java b/hutool-core/src/main/java/cn/hutool/core/lang/RegexPool.java
index d21b8523b..ed7f8cad6 100755
--- a/hutool-core/src/main/java/cn/hutool/core/lang/RegexPool.java
+++ b/hutool-core/src/main/java/cn/hutool/core/lang/RegexPool.java
@@ -32,9 +32,11 @@ public interface RegexPool {
*/
String GROUP_VAR = "\\$(\\d+)";
/**
- * IP v4
+ * IP v4
+ * 采用分组方式便于解析地址的每一个段
*/
- String IPV4 = "\\b((?!\\d\\d\\d)\\d+|1\\d\\d|2[0-4]\\d|25[0-5])\\.((?!\\d\\d\\d)\\d+|1\\d\\d|2[0-4]\\d|25[0-5])\\.((?!\\d\\d\\d)\\d+|1\\d\\d|2[0-4]\\d|25[0-5])\\.((?!\\d\\d\\d)\\d+|1\\d\\d|2[0-4]\\d|25[0-5])\\b";
+ //String IPV4 = "\\b((?!\\d\\d\\d)\\d+|1\\d\\d|2[0-4]\\d|25[0-5])\\.((?!\\d\\d\\d)\\d+|1\\d\\d|2[0-4]\\d|25[0-5])\\.((?!\\d\\d\\d)\\d+|1\\d\\d|2[0-4]\\d|25[0-5])\\.((?!\\d\\d\\d)\\d+|1\\d\\d|2[0-4]\\d|25[0-5])\\b";
+ String IPV4 = "^(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)$";
/**
* IP v6
*/
diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/hash/CityHash.java b/hutool-core/src/main/java/cn/hutool/core/lang/hash/CityHash.java
index 61c9f9ba6..aad2da056 100644
--- a/hutool-core/src/main/java/cn/hutool/core/lang/hash/CityHash.java
+++ b/hutool-core/src/main/java/cn/hutool/core/lang/hash/CityHash.java
@@ -318,7 +318,7 @@ public class CityHash {
private static long hashLen0to16(byte[] byteArray) {
int len = byteArray.length;
if (len >= 8) {
- long mul = k2 + len * 2;
+ long mul = k2 + len * 2L;
long a = fetch64(byteArray, 0) + k2;
long b = fetch64(byteArray, len - 8);
long c = rotate(b, 37) * mul + a;
@@ -344,7 +344,7 @@ public class CityHash {
// This probably works well for 16-byte strings as well, but it may be overkill in that case.
private static long hashLen17to32(byte[] byteArray) {
int len = byteArray.length;
- long mul = k2 + len * 2;
+ long mul = k2 + len * 2L;
long a = fetch64(byteArray, 0) * k1;
long b = fetch64(byteArray, 8);
long c = fetch64(byteArray, len - 8) * mul;
@@ -355,7 +355,7 @@ public class CityHash {
private static long hashLen33to64(byte[] byteArray) {
int len = byteArray.length;
- long mul = k2 + len * 2;
+ long mul = k2 + len * 2L;
long a = fetch64(byteArray, 0) * k2;
long b = fetch64(byteArray, 8);
long c = fetch64(byteArray, len - 24);
diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/hash/Hash.java b/hutool-core/src/main/java/cn/hutool/core/lang/hash/Hash.java
new file mode 100644
index 000000000..8ad0a9e35
--- /dev/null
+++ b/hutool-core/src/main/java/cn/hutool/core/lang/hash/Hash.java
@@ -0,0 +1,19 @@
+package cn.hutool.core.lang.hash;
+
+/**
+ * Hash计算接口
+ *
+ * @param 被计算hash的对象类型
+ * @author looly
+ * @since 5.7.15
+ */
+@FunctionalInterface
+public interface Hash {
+ /**
+ * 计算Hash值
+ *
+ * @param t 对象
+ * @return hash
+ */
+ Number hash(T t);
+}
diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/hash/Hash128.java b/hutool-core/src/main/java/cn/hutool/core/lang/hash/Hash128.java
index af5dfc496..b16b4c9ce 100644
--- a/hutool-core/src/main/java/cn/hutool/core/lang/hash/Hash128.java
+++ b/hutool-core/src/main/java/cn/hutool/core/lang/hash/Hash128.java
@@ -8,7 +8,8 @@ package cn.hutool.core.lang.hash;
* @since 5.2.5
*/
@FunctionalInterface
-public interface Hash128 {
+public interface Hash128 extends Hash{
+
/**
* 计算Hash值
*
@@ -16,4 +17,9 @@ public interface Hash128 {
* @return hash
*/
Number128 hash128(T t);
-}
\ No newline at end of file
+
+ @Override
+ default Number hash(T t){
+ return hash128(t);
+ }
+}
diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/hash/Hash32.java b/hutool-core/src/main/java/cn/hutool/core/lang/hash/Hash32.java
index b0aaa8ced..7a9016697 100644
--- a/hutool-core/src/main/java/cn/hutool/core/lang/hash/Hash32.java
+++ b/hutool-core/src/main/java/cn/hutool/core/lang/hash/Hash32.java
@@ -8,7 +8,7 @@ package cn.hutool.core.lang.hash;
* @since 5.2.5
*/
@FunctionalInterface
-public interface Hash32 {
+public interface Hash32 extends Hash{
/**
* 计算Hash值
*
@@ -16,4 +16,9 @@ public interface Hash32 {
* @return hash
*/
int hash32(T t);
-}
\ No newline at end of file
+
+ @Override
+ default Number hash(T t){
+ return hash32(t);
+ }
+}
diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/hash/Hash64.java b/hutool-core/src/main/java/cn/hutool/core/lang/hash/Hash64.java
index 9feac2969..61a50e4bd 100644
--- a/hutool-core/src/main/java/cn/hutool/core/lang/hash/Hash64.java
+++ b/hutool-core/src/main/java/cn/hutool/core/lang/hash/Hash64.java
@@ -8,7 +8,7 @@ package cn.hutool.core.lang.hash;
* @since 5.2.5
*/
@FunctionalInterface
-public interface Hash64 {
+public interface Hash64 extends Hash{
/**
* 计算Hash值
*
@@ -16,4 +16,9 @@ public interface Hash64 {
* @return hash
*/
long hash64(T t);
-}
\ No newline at end of file
+
+ @Override
+ default Number hash(T t){
+ return hash64(t);
+ }
+}
diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/hash/Number128.java b/hutool-core/src/main/java/cn/hutool/core/lang/hash/Number128.java
index 9af5bad4d..4f6301095 100644
--- a/hutool-core/src/main/java/cn/hutool/core/lang/hash/Number128.java
+++ b/hutool-core/src/main/java/cn/hutool/core/lang/hash/Number128.java
@@ -6,7 +6,8 @@ package cn.hutool.core.lang.hash;
* @author hexiufeng
* @since 5.2.5
*/
-public class Number128 {
+public class Number128 extends Number{
+ private static final long serialVersionUID = 1L;
private long lowValue;
private long highValue;
@@ -41,4 +42,24 @@ public class Number128 {
public long[] getLongArray() {
return new long[]{lowValue, highValue};
}
+
+ @Override
+ public int intValue() {
+ return (int) longValue();
+ }
+
+ @Override
+ public long longValue() {
+ return this.lowValue;
+ }
+
+ @Override
+ public float floatValue() {
+ return longValue();
+ }
+
+ @Override
+ public double doubleValue() {
+ return longValue();
+ }
}
diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/mutable/MutableBool.java b/hutool-core/src/main/java/cn/hutool/core/lang/mutable/MutableBool.java
index 2edf65727..1afbfbd18 100644
--- a/hutool-core/src/main/java/cn/hutool/core/lang/mutable/MutableBool.java
+++ b/hutool-core/src/main/java/cn/hutool/core/lang/mutable/MutableBool.java
@@ -3,7 +3,7 @@ package cn.hutool.core.lang.mutable;
import java.io.Serializable;
/**
- * 可变 boolean
类型
+ * 可变 {@code boolean} 类型
*
* @see Boolean
* @since 3.0.1
@@ -59,12 +59,12 @@ public class MutableBool implements Comparable, Mutable, S
* 相等需同时满足如下条件:
*
* - 非空
- * - 类型为 {@link MutableBool}
+ * - 类型为 MutableBool
* - 值相等
*
*
* @param obj 比对的对象
- * @return 相同返回true
,否则 false
+ * @return 相同返回true
,否则 {@code false}
*/
@Override
public boolean equals(final Object obj) {
@@ -83,7 +83,7 @@ public class MutableBool implements Comparable, Mutable, S
/**
* 比较
*
- * @param other 其它 {@link MutableBool} 对象
+ * @param other 其它 MutableBool 对象
* @return x==y返回0,x<y返回-1,x>y返回1
*/
@Override
diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/mutable/MutableByte.java b/hutool-core/src/main/java/cn/hutool/core/lang/mutable/MutableByte.java
index 0d0755bf8..55d539fc4 100644
--- a/hutool-core/src/main/java/cn/hutool/core/lang/mutable/MutableByte.java
+++ b/hutool-core/src/main/java/cn/hutool/core/lang/mutable/MutableByte.java
@@ -3,7 +3,7 @@ package cn.hutool.core.lang.mutable;
import cn.hutool.core.util.NumberUtil;
/**
- * 可变 byte
类型
+ * 可变 {@code byte} 类型
*
* @see Byte
* @since 3.0.1
@@ -157,12 +157,12 @@ public class MutableByte extends Number implements Comparable, Muta
* 相等需同时满足如下条件:
*
* - 非空
- * - 类型为 {@link MutableByte}
+ * - 类型为 MutableByte
* - 值相等
*
*
* @param obj 比对的对象
- * @return 相同返回true
,否则 false
+ * @return 相同返回true
,否则 {@code false}
*/
@Override
public boolean equals(final Object obj) {
@@ -181,7 +181,7 @@ public class MutableByte extends Number implements Comparable, Muta
/**
* 比较
*
- * @param other 其它 {@link MutableByte} 对象
+ * @param other 其它 MutableByte 对象
* @return x==y返回0,x<y返回-1,x>y返回1
*/
@Override
diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/mutable/MutableDouble.java b/hutool-core/src/main/java/cn/hutool/core/lang/mutable/MutableDouble.java
index 31a72d8bd..17702fbe7 100644
--- a/hutool-core/src/main/java/cn/hutool/core/lang/mutable/MutableDouble.java
+++ b/hutool-core/src/main/java/cn/hutool/core/lang/mutable/MutableDouble.java
@@ -3,7 +3,7 @@ package cn.hutool.core.lang.mutable;
import cn.hutool.core.util.NumberUtil;
/**
- * 可变 double
类型
+ * 可变 {@code double} 类型
*
* @see Double
* @since 3.0.1
@@ -150,12 +150,12 @@ public class MutableDouble extends Number implements Comparable,
* 相等需同时满足如下条件:
*
* - 非空
- * - 类型为 {@link MutableDouble}
+ * - 类型为 {@code MutableDouble}
* - 值相等
*
*
* @param obj 比对的对象
- * @return 相同返回true
,否则 false
+ * @return 相同返回true
,否则 {@code false}
*/
@Override
public boolean equals(final Object obj) {
@@ -175,7 +175,7 @@ public class MutableDouble extends Number implements Comparable,
/**
* 比较
*
- * @param other 其它 {@link MutableDouble} 对象
+ * @param other 其它 {@code MutableDouble} 对象
* @return x==y返回0,x<y返回-1,x>y返回1
*/
@Override
diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/mutable/MutableObj.java b/hutool-core/src/main/java/cn/hutool/core/lang/mutable/MutableObj.java
index b516c454e..72a490366 100644
--- a/hutool-core/src/main/java/cn/hutool/core/lang/mutable/MutableObj.java
+++ b/hutool-core/src/main/java/cn/hutool/core/lang/mutable/MutableObj.java
@@ -3,7 +3,7 @@ package cn.hutool.core.lang.mutable;
import java.io.Serializable;
/**
- * 可变Object
+ * 可变{@code Object}
*
* @param 可变的类型
* @since 3.0.1
diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/mutable/MutablePair.java b/hutool-core/src/main/java/cn/hutool/core/lang/mutable/MutablePair.java
new file mode 100644
index 000000000..7e5538515
--- /dev/null
+++ b/hutool-core/src/main/java/cn/hutool/core/lang/mutable/MutablePair.java
@@ -0,0 +1,57 @@
+package cn.hutool.core.lang.mutable;
+
+import cn.hutool.core.lang.Pair;
+
+/**
+ * 可变{@link Pair}实现,可以修改键和值
+ *
+ * @param 键类型
+ * @param 值类型
+ * @since 5.7.16
+ */
+public class MutablePair extends Pair implements Mutable>{
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 构造
+ *
+ * @param key 键
+ * @param value 值
+ */
+ public MutablePair(K key, V value) {
+ super(key, value);
+ }
+
+ /**
+ * 设置键
+ *
+ * @param key 新键
+ * @return this
+ */
+ public MutablePair setKey(K key) {
+ this.key = key;
+ return this;
+ }
+
+ /**
+ * 设置值
+ *
+ * @param value 新值
+ * @return this
+ */
+ public MutablePair setValue(V value) {
+ this.value = value;
+ return this;
+ }
+
+ @Override
+ public Pair get() {
+ return this;
+ }
+
+ @Override
+ public void set(Pair pair) {
+ this.key = pair.getKey();
+ this.value = pair.getValue();
+ }
+}
diff --git a/hutool-core/src/main/java/cn/hutool/core/net/Ipv4Util.java b/hutool-core/src/main/java/cn/hutool/core/net/Ipv4Util.java
index 3b6ab8140..9b45795e6 100644
--- a/hutool-core/src/main/java/cn/hutool/core/net/Ipv4Util.java
+++ b/hutool-core/src/main/java/cn/hutool/core/net/Ipv4Util.java
@@ -3,13 +3,14 @@ package cn.hutool.core.net;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.Assert;
-import cn.hutool.core.lang.Validator;
+import cn.hutool.core.lang.PatternPool;
import cn.hutool.core.util.CharUtil;
import cn.hutool.core.util.StrUtil;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
+import java.util.regex.Matcher;
/**
* IPV4地址工具类
@@ -20,6 +21,7 @@ import java.util.Objects;
* @since 5.4.1
*/
public class Ipv4Util {
+
/**
* IP段的分割符
*/
@@ -149,18 +151,25 @@ public class Ipv4Util {
/**
* 根据ip地址(xxx.xxx.xxx.xxx)计算出long型的数据
* 方法别名:inet_aton
+ *
* @param strIP IP V4 地址
* @return long值
*/
public static long ipv4ToLong(String strIP) {
- Validator.validateIpv4(strIP, "Invalid IPv4 address!");
- final long[] ip = Convert.convert(long[].class, StrUtil.split(strIP, CharUtil.DOT));
- return (ip[0] << 24) + (ip[1] << 16) + (ip[2] << 8) + ip[3];
+ final Matcher matcher = PatternPool.IPV4.matcher(strIP);
+ if (matcher.matches()) {
+ return matchAddress(matcher);
+ }
+// Validator.validateIpv4(strIP, "Invalid IPv4 address!");
+// final long[] ip = Convert.convert(long[].class, StrUtil.split(strIP, CharUtil.DOT));
+// return (ip[0] << 24) + (ip[1] << 16) + (ip[2] << 8) + ip[3];
+ throw new IllegalArgumentException("Invalid IPv4 address!");
}
/**
* 根据 ip/掩码位 计算IP段的起始IP(字符串型)
* 方法别名:inet_ntoa
+ *
* @param ip 给定的IP,如218.240.38.69
* @param maskBit 给定的掩码位,如30
* @return 起始IP的字符串表示
@@ -195,9 +204,7 @@ public class Ipv4Util {
* 根据子网掩码转换为掩码位
*
* @param mask 掩码的点分十进制表示,例如 255.255.255.0
- *
* @return 掩码位,例如 24
- *
* @throws IllegalArgumentException 子网掩码非法
*/
public static int getMaskBitByMask(String mask) {
@@ -282,7 +289,6 @@ public class Ipv4Util {
* 判断掩码是否合法
*
* @param mask 掩码的点分十进制表示,例如 255.255.255.0
- *
* @return true:掩码合法;false:掩码不合法
*/
public static boolean isMaskValid(String mask) {
@@ -293,7 +299,6 @@ public class Ipv4Util {
* 判断掩码位是否合法
*
* @param maskBit 掩码位,例如 24
- *
* @return true:掩码位合法;false:掩码位不合法
*/
public static boolean isMaskBitValid(int maskBit) {
@@ -315,5 +320,19 @@ public class Ipv4Util {
return getBeginIpLong(ip, maskBit)
+ ~ipv4ToLong(getMaskByMaskBit(maskBit));
}
+
+ /**
+ * 将匹配到的Ipv4地址的4个分组分别处理
+ *
+ * @param matcher 匹配到的Ipv4正则
+ * @return ipv4对应long
+ */
+ private static long matchAddress(Matcher matcher) {
+ long addr = 0;
+ for (int i = 1; i <= 4; ++i) {
+ addr |= Long.parseLong(matcher.group(i)) << 8 * (4 - i);
+ }
+ return addr;
+ }
//-------------------------------------------------------------------------------- Private method end
}
diff --git a/hutool-core/src/main/java/cn/hutool/core/net/NetUtil.java b/hutool-core/src/main/java/cn/hutool/core/net/NetUtil.java
index ecebe74b6..7758998e5 100644
--- a/hutool-core/src/main/java/cn/hutool/core/net/NetUtil.java
+++ b/hutool-core/src/main/java/cn/hutool/core/net/NetUtil.java
@@ -699,14 +699,15 @@ public class NetUtil {
* @since 4.0.6
*/
public static boolean isInRange(String ip, String cidr) {
- String[] ips = StrUtil.splitToArray(ip, '.');
- int ipAddr = (Integer.parseInt(ips[0]) << 24) | (Integer.parseInt(ips[1]) << 16) | (Integer.parseInt(ips[2]) << 8) | Integer.parseInt(ips[3]);
- int type = Integer.parseInt(cidr.replaceAll(".*/", ""));
- int mask = 0xFFFFFFFF << (32 - type);
- String cidrIp = cidr.replaceAll("/.*", "");
- String[] cidrIps = cidrIp.split("\\.");
- int cidrIpAddr = (Integer.parseInt(cidrIps[0]) << 24) | (Integer.parseInt(cidrIps[1]) << 16) | (Integer.parseInt(cidrIps[2]) << 8) | Integer.parseInt(cidrIps[3]);
- return (ipAddr & mask) == (cidrIpAddr & mask);
+ final int maskSplitMarkIndex = cidr.lastIndexOf(Ipv4Util.IP_MASK_SPLIT_MARK);
+ if(maskSplitMarkIndex < 0){
+ throw new IllegalArgumentException("Invalid cidr: " + cidr);
+ }
+
+ final long mask = (-1L << 32 - Integer.parseInt(cidr.substring(maskSplitMarkIndex + 1)));
+ long cidrIpAddr = ipv4ToLong(cidr.substring(0, maskSplitMarkIndex));
+
+ return (ipv4ToLong(ip) & mask) == (cidrIpAddr & mask);
}
/**
diff --git a/hutool-core/src/main/java/cn/hutool/core/net/RFC3986.java b/hutool-core/src/main/java/cn/hutool/core/net/RFC3986.java
new file mode 100644
index 000000000..713b082c2
--- /dev/null
+++ b/hutool-core/src/main/java/cn/hutool/core/net/RFC3986.java
@@ -0,0 +1,98 @@
+package cn.hutool.core.net;
+
+import cn.hutool.core.codec.PercentCodec;
+
+/**
+ * rfc3986 : https://www.ietf.org/rfc/rfc3986.html 编码实现
+ *
+ * @author looly
+ * @since 5.7.16
+ */
+public class RFC3986 {
+
+ /**
+ * gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@"
+ */
+ public static final PercentCodec GEN_DELIMS = PercentCodec.of(":/?#[]&");
+
+ /**
+ * sub-delims = "!" / "$" / "{@code &}" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "="
+ */
+ public static final PercentCodec SUB_DELIMS = PercentCodec.of("!$&'()*+,;=");
+
+ /**
+ * reserved = gen-delims / sub-delims
+ */
+ public static final PercentCodec RESERVED = GEN_DELIMS.orNew(SUB_DELIMS);
+
+ /**
+ * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
+ */
+ public static final PercentCodec UNRESERVED = PercentCodec.of(unreservedChars());
+
+ /**
+ * pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
+ */
+ public static final PercentCodec PCHAR = UNRESERVED.orNew(SUB_DELIMS).or(PercentCodec.of(":@"));
+
+ /**
+ * segment = pchar
+ */
+ public static final PercentCodec SEGMENT = PCHAR;
+ /**
+ * segment-nz-nc = SEGMENT ; non-zero-length segment without any colon ":"
+ */
+ public static final PercentCodec SEGMENT_NZ_NC = PercentCodec.of(SEGMENT).removeSafe(':');
+
+ /**
+ * path = segment / "/"
+ */
+ public static final PercentCodec PATH = SEGMENT.orNew(PercentCodec.of("/"));
+
+ /**
+ * query = pchar / "/" / "?"
+ */
+ public static final PercentCodec QUERY = PCHAR.orNew(PercentCodec.of("/?"));
+
+ /**
+ * fragment = pchar / "/" / "?"
+ */
+ public static final PercentCodec FRAGMENT = QUERY;
+
+ /**
+ * query中的key
+ */
+ public static final PercentCodec QUERY_PARAM_NAME = PercentCodec.of(QUERY).removeSafe('&').removeSafe('=');
+
+ /**
+ * query中的value
+ */
+ public static final PercentCodec QUERY_PARAM_VALUE = PercentCodec.of(QUERY).removeSafe('&');
+
+ /**
+ * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
+ *
+ * @return unreserved字符
+ */
+ private static StringBuilder unreservedChars() {
+ StringBuilder sb = new StringBuilder();
+
+ // ALPHA
+ for (char c = 'A'; c <= 'Z'; c++) {
+ sb.append(c);
+ }
+ for (char c = 'a'; c <= 'z'; c++) {
+ sb.append(c);
+ }
+
+ // DIGIT
+ for (char c = '0'; c <= '9'; c++) {
+ sb.append(c);
+ }
+
+ // "-" / "." / "_" / "~"
+ sb.append("_.-~");
+
+ return sb;
+ }
+}
diff --git a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java
index 357a215b8..708e9721a 100644
--- a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java
+++ b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlBuilder.java
@@ -1,6 +1,7 @@
package cn.hutool.core.net.url;
import cn.hutool.core.lang.Assert;
+import cn.hutool.core.net.RFC3986;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
@@ -129,7 +130,7 @@ public final class UrlBuilder implements Serializable {
*/
public static UrlBuilder of(String url, Charset charset) {
Assert.notBlank(url, "Url must be not blank!");
- return of(URLUtil.url(url.trim()), charset);
+ return of(URLUtil.url(StrUtil.trim(url)), charset);
}
/**
@@ -322,13 +323,25 @@ public final class UrlBuilder implements Serializable {
}
/**
- * 增加路径节点
+ * 增加路径,在现有路径基础上追加路径
+ *
+ * @param path 路径,例如aaa/bbb/ccc
+ * @return this
+ */
+ public UrlBuilder addPath(CharSequence path) {
+ UrlPath.of(path, this.charset).getSegments().forEach(this::addPathSegment);
+ return this;
+ }
+
+ /**
+ * 增加路径节点,路径节点中的"/"会被转义为"%2F"
*
* @param segment 路径节点
* @return this
+ * @since 5.7.16
*/
- public UrlBuilder addPath(String segment) {
- if (StrUtil.isBlank(segment)) {
+ public UrlBuilder addPathSegment(CharSequence segment) {
+ if (StrUtil.isEmpty(segment)) {
return this;
}
if (null == this.path) {
@@ -341,19 +354,13 @@ public final class UrlBuilder implements Serializable {
/**
* 追加path节点
*
- * @param segment path节点
+ * @param path path节点
* @return this
+ * @deprecated 方法重复,请使用{@link #addPath(CharSequence)}
*/
- public UrlBuilder appendPath(CharSequence segment) {
- if (StrUtil.isEmpty(segment)) {
- return this;
- }
-
- if (this.path == null) {
- this.path = new UrlPath();
- }
- this.path.add(segment);
- return this;
+ @Deprecated
+ public UrlBuilder appendPath(CharSequence path) {
+ return addPath(path);
}
/**
@@ -419,7 +426,7 @@ public final class UrlBuilder implements Serializable {
* @return 标识符,例如#后边的部分
*/
public String getFragmentEncoded() {
- return URLUtil.encodeFragment(this.fragment, this.charset);
+ return RFC3986.FRAGMENT.encode(this.fragment, this.charset);
}
/**
diff --git a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlPath.java b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlPath.java
index 66fc70109..bc91edf9b 100644
--- a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlPath.java
+++ b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlPath.java
@@ -2,10 +2,10 @@ package cn.hutool.core.net.url;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
+import cn.hutool.core.net.RFC3986;
import cn.hutool.core.net.URLDecoder;
import cn.hutool.core.util.CharUtil;
import cn.hutool.core.util.StrUtil;
-import cn.hutool.core.util.URLUtil;
import java.nio.charset.Charset;
import java.util.LinkedList;
@@ -29,7 +29,7 @@ public class UrlPath {
* @param charset decode用的编码,null表示不做decode
* @return UrlPath
*/
- public static UrlPath of(String pathStr, Charset charset) {
+ public static UrlPath of(CharSequence pathStr, Charset charset) {
final UrlPath urlPath = new UrlPath();
urlPath.parse(pathStr, charset);
return urlPath;
@@ -97,7 +97,7 @@ public class UrlPath {
* @param charset decode编码,null表示不解码
* @return this
*/
- public UrlPath parse(String path, Charset charset) {
+ public UrlPath parse(CharSequence path, Charset charset) {
if (StrUtil.isNotEmpty(path)) {
// 原URL中以/结尾,则这个规则需保留,issue#I1G44J@Gitee
if(StrUtil.endWith(path, CharUtil.SLASH)){
@@ -127,7 +127,7 @@ public class UrlPath {
final StringBuilder builder = new StringBuilder();
for (String segment : segments) {
- builder.append(CharUtil.SLASH).append(URLUtil.encodePathSegment(segment, charset));
+ builder.append(CharUtil.SLASH).append(RFC3986.SEGMENT_NZ_NC.encode(segment, charset));
}
if (withEngTag || StrUtil.isEmpty(builder)) {
builder.append(CharUtil.SLASH);
diff --git a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java
index 3c297b9de..69d5ec641 100644
--- a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java
+++ b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlQuery.java
@@ -5,6 +5,7 @@ import cn.hutool.core.collection.IterUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.map.TableMap;
+import cn.hutool.core.net.RFC3986;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
@@ -220,10 +221,15 @@ public class UrlQuery {
}
/**
- * 构建URL查询字符串,即将key-value键值对转换为key1=v1&key2=&key3=v3形式
+ * 构建URL查询字符串,即将key-value键值对转换为{@code key1=v1&key2=v2&key3=v3}形式。
+ * 对于{@code null}处理规则如下:
+ *
+ * - 如果key为{@code null},则这个键值对忽略
+ * - 如果value为{@code null},只保留key,如key1对应value为{@code null}生成类似于{@code key1&key2=v2}形式
+ *
*
* @param charset encode编码,null表示不做encode编码
- * @param isEncode 是否转义键和值
+ * @param isEncode 是否转义键和值,转义遵循rfc3986规范
* @return URL查询字符串
* @since 5.7.13
*/
@@ -233,21 +239,18 @@ public class UrlQuery {
}
final StringBuilder sb = new StringBuilder();
- boolean isFirst = true;
- CharSequence key;
+ CharSequence name;
CharSequence value;
for (Map.Entry entry : this.query) {
- if (isFirst) {
- isFirst = false;
- } else {
- sb.append("&");
- }
- key = entry.getKey();
- if (null != key) {
- sb.append(toStr(key, charset, isEncode));
+ name = entry.getKey();
+ if (null != name) {
+ if(sb.length() >0){
+ sb.append("&");
+ }
+ sb.append(isEncode ? RFC3986.QUERY_PARAM_NAME.encode(name, charset) : name);
value = entry.getValue();
if (null != value) {
- sb.append("=").append(toStr(value, charset, isEncode));
+ sb.append("=").append(isEncode ? RFC3986.QUERY_PARAM_VALUE.encode(value, charset) : value);
}
}
}
@@ -301,18 +304,18 @@ public class UrlQuery {
}
/**
- * 键值对的{@link CharSequence}转换为String,可选是否转义
+ * 键值对的name转换为
*
* @param str 原字符串
* @param charset 编码,只用于encode中
- * @param isEncode 是否转义
+ * @param isEncode 是否转义,转义遵循rfc3986规范
* @return 转换后的String
* @since 5.7.13
*/
- private static String toStr(CharSequence str, Charset charset, boolean isEncode) {
+ private static String nameToStr(CharSequence str, Charset charset, boolean isEncode) {
String result = StrUtil.str(str);
if (isEncode) {
- result = URLUtil.encodeAll(result, charset);
+ result = RFC3986.QUERY_PARAM_NAME.encode(result, charset);
}
return result;
}
diff --git a/hutool-core/src/main/java/cn/hutool/core/text/CharSequenceUtil.java b/hutool-core/src/main/java/cn/hutool/core/text/CharSequenceUtil.java
index eef69acec..511fb3c6e 100644
--- a/hutool-core/src/main/java/cn/hutool/core/text/CharSequenceUtil.java
+++ b/hutool-core/src/main/java/cn/hutool/core/text/CharSequenceUtil.java
@@ -7,12 +7,23 @@ import cn.hutool.core.lang.Assert;
import cn.hutool.core.lang.Filter;
import cn.hutool.core.lang.Matcher;
import cn.hutool.core.lang.func.Func1;
-import cn.hutool.core.util.*;
+import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.CharUtil;
+import cn.hutool.core.util.CharsetUtil;
+import cn.hutool.core.util.DesensitizedUtil;
+import cn.hutool.core.util.NumberUtil;
+import cn.hutool.core.util.ReUtil;
+import cn.hutool.core.util.StrUtil;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.text.MessageFormat;
-import java.util.*;
+import java.text.Normalizer;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;
@@ -32,11 +43,6 @@ public class CharSequenceUtil {
*/
public static final String NULL = "null";
- /**
- * 字符串常量:{@code "undefined"}
- */
- public static final String UNDEFINED = "undefined";
-
/**
* 字符串常量:空字符串 {@code ""}
*/
@@ -489,17 +495,6 @@ public class CharSequenceUtil {
return isNullOrUndefinedStr(str);
}
- /**
- * 检查字符串是否不为null、空白串、“null”、“undefined”
- *
- * @param str 被检查的字符串
- * @return 是否不为null、空白串、“null”、“undefined”
- * 不为null、空白串、“null”、“undefined”返回true,否则返回false
- */
- public static boolean isNotBlankOrUndefined(CharSequence str) {
- return !isBlankOrUndefined(str);
- }
-
/**
* 是否为“null”、“undefined”,不做空指针检查
*
@@ -508,18 +503,7 @@ public class CharSequenceUtil {
*/
private static boolean isNullOrUndefinedStr(CharSequence str) {
String strString = str.toString().trim();
- return NULL.equals(strString) || UNDEFINED.equals(strString);
- }
-
- /**
- * 是否不为“null”、“undefined”,不做空指针检查
- *
- * @param str 字符串
- * @return 是否不为“null”、“undefined”,不为“null”、“undefined”返回true,否则false
- */
- private static boolean isNotNullAndNotUndefinedStr(CharSequence str) {
- String strString = str.toString().trim();
- return !NULL.equals(strString) && !UNDEFINED.equals(strString);
+ return NULL.equals(strString) || "undefined".equals(strString);
}
// ------------------------------------------------------------------------ Trim
@@ -1746,16 +1730,14 @@ public class CharSequenceUtil {
/**
* 切分字符串
*
- * @param str 被切分的字符串
+ * @param text 被切分的字符串
* @param separator 分隔符字符
* @param limit 限制分片数
* @return 切分后的数组
*/
- public static String[] splitToArray(CharSequence str, char separator, int limit) {
- if (null == str) {
- return new String[]{};
- }
- return StrSplitter.splitToArray(str.toString(), separator, limit, false, false);
+ public static String[] splitToArray(CharSequence text, char separator, int limit) {
+ Assert.notNull(text, "Text must be not null!");
+ return StrSplitter.splitToArray(text.toString(), separator, limit, false, false);
}
/**
@@ -2875,10 +2857,10 @@ public class CharSequenceUtil {
len += str.length();
}
if (isNotEmpty(prefix)) {
- len += str.length();
+ len += prefix.length();
}
if (isNotEmpty(suffix)) {
- len += str.length();
+ len += suffix.length();
}
StringBuilder sb = new StringBuilder(len);
if (isNotEmpty(prefix) && false == startWith(str, prefix)) {
@@ -4357,7 +4339,21 @@ public class CharSequenceUtil {
* @return 给定字符串的所有字符是否都一样
* @since 5.7.3
*/
- public static boolean isCharEquals(String str) {
- return isBlank(str.replace(str.charAt(0), CharUtil.SPACE));
+ public static boolean isCharEquals(CharSequence str) {
+ Assert.notEmpty(str, "Str to check must be not empty!");
+ return count(str, str.charAt(0)) == str.length();
+ }
+
+ /**
+ * 对字符串归一化处理,如 "Á" 可以使用 "u00C1"或 "u0041u0301"表示,实际测试中两个字符串并不equals
+ * 因此使用此方法归一为一种表示形式,默认按照W3C通常建议的,在NFC中交换文本。
+ *
+ * @param str 归一化的字符串
+ * @return 归一化后的字符串
+ * @see Normalizer#normalize(CharSequence, Normalizer.Form)
+ * @since 5.7.16
+ */
+ public static String normalize(CharSequence str) {
+ return Normalizer.normalize(str, Normalizer.Form.NFC);
}
}
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 0de657e51..69bc75f04 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
@@ -579,7 +579,7 @@ public class StrBuilder implements CharSequence, Appendable, Serializable {
private static int totalLength(CharSequence... strs) {
int totalLength = 0;
for (CharSequence str : strs) {
- totalLength += (null == str ? 4 : str.length());
+ totalLength += (null == str ? 0 : str.length());
}
return totalLength;
}
diff --git a/hutool-core/src/main/java/cn/hutool/core/text/StrFormatter.java b/hutool-core/src/main/java/cn/hutool/core/text/StrFormatter.java
index cc5ec7cc5..a70d5542f 100644
--- a/hutool-core/src/main/java/cn/hutool/core/text/StrFormatter.java
+++ b/hutool-core/src/main/java/cn/hutool/core/text/StrFormatter.java
@@ -9,7 +9,6 @@ import java.util.Map;
* 字符串格式化工具
*
* @author Looly
- *
*/
public class StrFormatter {
@@ -23,22 +22,42 @@ public class StrFormatter {
* 转义\: format("this is \\\\{} for {}", "a", "b") =》 this is \a for b
*
* @param strPattern 字符串模板
- * @param argArray 参数列表
+ * @param argArray 参数列表
* @return 结果
*/
- public static String format(final String strPattern, final Object... argArray) {
- if (StrUtil.isBlank(strPattern) || ArrayUtil.isEmpty(argArray)) {
+ public static String format(String strPattern, Object... argArray) {
+ return formatWith(strPattern, StrUtil.EMPTY_JSON, argArray);
+ }
+
+ /**
+ * 格式化字符串
+ * 此方法只是简单将指定占位符 按照顺序替换为参数
+ * 如果想输出占位符使用 \\转义即可,如果想输出占位符之前的 \ 使用双转义符 \\\\ 即可
+ * 例:
+ * 通常使用:format("this is {} for {}", "{}", "a", "b") =》 this is a for b
+ * 转义{}: format("this is \\{} for {}", "{}", "a", "b") =》 this is \{} for a
+ * 转义\: format("this is \\\\{} for {}", "{}", "a", "b") =》 this is \a for b
+ *
+ * @param strPattern 字符串模板
+ * @param placeHolder 占位符,例如{}
+ * @param argArray 参数列表
+ * @return 结果
+ * @since 5.7.14
+ */
+ public static String formatWith(String strPattern, String placeHolder, Object... argArray) {
+ if (StrUtil.isBlank(strPattern) || StrUtil.isBlank(placeHolder) || ArrayUtil.isEmpty(argArray)) {
return strPattern;
}
final int strPatternLength = strPattern.length();
+ final int placeHolderLength = placeHolder.length();
// 初始化定义好的长度以获得更好的性能
- StringBuilder sbuf = new StringBuilder(strPatternLength + 50);
+ final StringBuilder sbuf = new StringBuilder(strPatternLength + 50);
int handledPosition = 0;// 记录已经处理到的位置
int delimIndex;// 占位符所在位置
for (int argIndex = 0; argIndex < argArray.length; argIndex++) {
- delimIndex = strPattern.indexOf(StrUtil.EMPTY_JSON, handledPosition);
+ delimIndex = strPattern.indexOf(placeHolder, handledPosition);
if (delimIndex == -1) {// 剩余部分无占位符
if (handledPosition == 0) { // 不带占位符的模板直接返回
return strPattern;
@@ -54,24 +73,23 @@ public class StrFormatter {
// 转义符之前还有一个转义符,占位符依旧有效
sbuf.append(strPattern, handledPosition, delimIndex - 1);
sbuf.append(StrUtil.utf8Str(argArray[argIndex]));
- handledPosition = delimIndex + 2;
+ handledPosition = delimIndex + placeHolderLength;
} else {
// 占位符被转义
argIndex--;
sbuf.append(strPattern, handledPosition, delimIndex - 1);
- sbuf.append(StrUtil.C_DELIM_START);
+ sbuf.append(placeHolder.charAt(0));
handledPosition = delimIndex + 1;
}
} else {// 正常占位符
sbuf.append(strPattern, handledPosition, delimIndex);
sbuf.append(StrUtil.utf8Str(argArray[argIndex]));
- handledPosition = delimIndex + 2;
+ handledPosition = delimIndex + placeHolderLength;
}
}
- // append the characters following the last {} pair.
// 加入最后一个占位符后所有的字符
- sbuf.append(strPattern, handledPosition, strPattern.length());
+ sbuf.append(strPattern, handledPosition, strPatternLength);
return sbuf.toString();
}
diff --git a/hutool-core/src/main/java/cn/hutool/core/text/StrSplitter.java b/hutool-core/src/main/java/cn/hutool/core/text/StrSplitter.java
index ec3d4aa45..d9333e61c 100644
--- a/hutool-core/src/main/java/cn/hutool/core/text/StrSplitter.java
+++ b/hutool-core/src/main/java/cn/hutool/core/text/StrSplitter.java
@@ -1,5 +1,6 @@
package cn.hutool.core.text;
+import cn.hutool.core.lang.Assert;
import cn.hutool.core.lang.PatternPool;
import cn.hutool.core.text.finder.CharFinder;
import cn.hutool.core.text.finder.CharMatcherFinder;
@@ -146,7 +147,7 @@ public class StrSplitter {
/**
* 切分字符串,忽略大小写
*
- * @param str 被切分的字符串
+ * @param text 被切分的字符串
* @param separator 分隔符字符
* @param limit 限制分片数,-1不限制
* @param isTrim 是否去除切分字符串后每个元素两边的空格
@@ -154,8 +155,8 @@ public class StrSplitter {
* @return 切分后的集合
* @since 3.2.1
*/
- public static List splitIgnoreCase(CharSequence str, char separator, int limit, boolean isTrim, boolean ignoreEmpty) {
- return split(str, separator, limit, isTrim, ignoreEmpty, true);
+ public static List splitIgnoreCase(CharSequence text, char separator, int limit, boolean isTrim, boolean ignoreEmpty) {
+ return split(text, separator, limit, isTrim, ignoreEmpty, true);
}
/**
@@ -177,7 +178,7 @@ public class StrSplitter {
/**
* 切分字符串
*
- * @param 切分后的元素类型
+ * @param 切分后的元素类型
* @param text 被切分的字符串
* @param separator 分隔符字符
* @param limit 限制分片数,-1不限制
@@ -204,7 +205,7 @@ public class StrSplitter {
* @return 切分后的集合
* @since 3.0.8
*/
- public static String[] splitToArray(String str, char separator, int limit, boolean isTrim, boolean ignoreEmpty) {
+ public static String[] splitToArray(CharSequence str, char separator, int limit, boolean isTrim, boolean ignoreEmpty) {
return toArray(split(str, separator, limit, isTrim, ignoreEmpty));
}
@@ -220,7 +221,7 @@ public class StrSplitter {
* @return 切分后的集合
* @since 3.0.8
*/
- public static List split(String str, String separator, boolean isTrim, boolean ignoreEmpty) {
+ public static List split(CharSequence str, String separator, boolean isTrim, boolean ignoreEmpty) {
return split(str, separator, -1, isTrim, ignoreEmpty, false);
}
@@ -233,7 +234,7 @@ public class StrSplitter {
* @return 切分后的集合
* @since 3.2.1
*/
- public static List splitTrim(String str, String separator, boolean ignoreEmpty) {
+ public static List splitTrim(CharSequence str, String separator, boolean ignoreEmpty) {
return split(str, separator, true, ignoreEmpty);
}
@@ -248,7 +249,7 @@ public class StrSplitter {
* @return 切分后的集合
* @since 3.0.8
*/
- public static List split(String str, String separator, int limit, boolean isTrim, boolean ignoreEmpty) {
+ public static List split(CharSequence str, String separator, int limit, boolean isTrim, boolean ignoreEmpty) {
return split(str, separator, limit, isTrim, ignoreEmpty, false);
}
@@ -262,7 +263,7 @@ public class StrSplitter {
* @return 切分后的集合
* @since 3.2.1
*/
- public static List splitTrim(String str, String separator, int limit, boolean ignoreEmpty) {
+ public static List splitTrim(CharSequence str, String separator, int limit, boolean ignoreEmpty) {
return split(str, separator, limit, true, ignoreEmpty);
}
@@ -277,7 +278,7 @@ public class StrSplitter {
* @return 切分后的集合
* @since 3.2.1
*/
- public static List splitIgnoreCase(String str, String separator, int limit, boolean isTrim, boolean ignoreEmpty) {
+ public static List splitIgnoreCase(CharSequence str, String separator, int limit, boolean isTrim, boolean ignoreEmpty) {
return split(str, separator, limit, isTrim, ignoreEmpty, true);
}
@@ -291,7 +292,7 @@ public class StrSplitter {
* @return 切分后的集合
* @since 3.2.1
*/
- public static List splitTrimIgnoreCase(String str, String separator, int limit, boolean ignoreEmpty) {
+ public static List splitTrimIgnoreCase(CharSequence str, String separator, int limit, boolean ignoreEmpty) {
return split(str, separator, limit, true, ignoreEmpty, true);
}
@@ -307,7 +308,7 @@ public class StrSplitter {
* @return 切分后的集合
* @since 3.2.1
*/
- public static List split(String text, String separator, int limit, boolean isTrim, boolean ignoreEmpty, boolean ignoreCase) {
+ public static List split(CharSequence text, String separator, int limit, boolean isTrim, boolean ignoreEmpty, boolean ignoreCase) {
final SplitIter splitIter = new SplitIter(text, new StrFinder(separator, ignoreCase), limit, ignoreEmpty);
return splitIter.toList(isTrim);
}
@@ -323,7 +324,7 @@ public class StrSplitter {
* @return 切分后的集合
* @since 3.0.8
*/
- public static String[] splitToArray(String str, String separator, int limit, boolean isTrim, boolean ignoreEmpty) {
+ public static String[] splitToArray(CharSequence str, String separator, int limit, boolean isTrim, boolean ignoreEmpty) {
return toArray(split(str, separator, limit, isTrim, ignoreEmpty));
}
@@ -338,7 +339,8 @@ public class StrSplitter {
* @return 切分后的集合
* @since 3.0.8
*/
- public static List split(String text, int limit) {
+ public static List split(CharSequence text, int limit) {
+
if (StrUtil.isEmpty(text)) {
return new ArrayList<>(0);
}
@@ -362,7 +364,7 @@ public class StrSplitter {
/**
* 通过正则切分字符串
*
- * @param str 字符串
+ * @param text 字符串
* @param separatorRegex 分隔符正则
* @param limit 限制分片数
* @param isTrim 是否去除切分字符串后每个元素两边的空格
@@ -370,9 +372,9 @@ public class StrSplitter {
* @return 切分后的集合
* @since 3.0.8
*/
- public static List splitByRegex(String str, String separatorRegex, int limit, boolean isTrim, boolean ignoreEmpty) {
+ public static List splitByRegex(String text, String separatorRegex, int limit, boolean isTrim, boolean ignoreEmpty) {
final Pattern pattern = PatternPool.get(separatorRegex);
- return split(str, pattern, limit, isTrim, ignoreEmpty);
+ return split(text, pattern, limit, isTrim, ignoreEmpty);
}
/**
@@ -387,7 +389,8 @@ public class StrSplitter {
* @since 3.0.8
*/
public static List split(String text, Pattern separatorPattern, int limit, boolean isTrim, boolean ignoreEmpty) {
- if (StrUtil.isEmpty(text)) {
+ Assert.notNull(text, "Text must be not null!");
+ if (text.length() < 1) {
return new ArrayList<>(0);
}
final SplitIter splitIter = new SplitIter(text, new PatternFinder(separatorPattern), limit, ignoreEmpty);
diff --git a/hutool-core/src/main/java/cn/hutool/core/text/split/SplitIter.java b/hutool-core/src/main/java/cn/hutool/core/text/split/SplitIter.java
index c6d977696..847a23ce3 100644
--- a/hutool-core/src/main/java/cn/hutool/core/text/split/SplitIter.java
+++ b/hutool-core/src/main/java/cn/hutool/core/text/split/SplitIter.java
@@ -53,7 +53,6 @@ public class SplitIter extends ComputeIter implements Serializable {
@Override
protected String computeNext() {
- Assert.notNull(this.text, "Text to find must be not null!");
// 达到数量上限或末尾,结束
if (count >= limit || offset > text.length()) {
return null;
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 5912fd56b..adc11b2f8 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
@@ -22,7 +22,6 @@ import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
-import java.util.Objects;
import java.util.Random;
import java.util.Set;
import java.util.function.Function;
@@ -118,7 +117,7 @@ public class ArrayUtil extends PrimitiveArrayUtil {
public static boolean hasNull(T... array) {
if (isNotEmpty(array)) {
for (T element : array) {
- if (null == element) {
+ if (ObjectUtil.isNull(element)) {
return true;
}
}
@@ -150,7 +149,7 @@ public class ArrayUtil extends PrimitiveArrayUtil {
*/
@SuppressWarnings("unchecked")
public static T firstNonNull(T... array) {
- return firstMatch(Objects::nonNull, array);
+ return firstMatch(ObjectUtil::isNotNull, array);
}
/**
diff --git a/hutool-core/src/main/java/cn/hutool/core/util/NumberUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/NumberUtil.java
index c5bf25e57..08f30132a 100644
--- a/hutool-core/src/main/java/cn/hutool/core/util/NumberUtil.java
+++ b/hutool-core/src/main/java/cn/hutool/core/util/NumberUtil.java
@@ -1675,12 +1675,12 @@ public class NumberUtil {
*
* @param x 第一个值
* @param y 第二个值
- * @return x==y返回0,x<y返回-1,x>y返回1
+ * @return x==y返回0,x<y返回小于0的数,x>y返回大于0的数
* @see Character#compare(char, char)
* @since 3.0.1
*/
public static int compare(char x, char y) {
- return x - y;
+ return Character.compare(x, y);
}
/**
@@ -1688,7 +1688,7 @@ public class NumberUtil {
*
* @param x 第一个值
* @param y 第二个值
- * @return x==y返回0,x<y返回-1,x>y返回1
+ * @return x==y返回0,x<y返回小于0的数,x>y返回大于0的数
* @see Double#compare(double, double)
* @since 3.0.1
*/
@@ -1701,7 +1701,7 @@ public class NumberUtil {
*
* @param x 第一个值
* @param y 第二个值
- * @return x==y返回0,x<y返回-1,x>y返回1
+ * @return x==y返回0,x<y返回小于0的数,x>y返回大于0的数
* @see Integer#compare(int, int)
* @since 3.0.1
*/
@@ -1714,7 +1714,7 @@ public class NumberUtil {
*
* @param x 第一个值
* @param y 第二个值
- * @return x==y返回0,x<y返回-1,x>y返回1
+ * @return x==y返回0,x<y返回小于0的数,x>y返回大于0的数
* @see Long#compare(long, long)
* @since 3.0.1
*/
@@ -1727,7 +1727,7 @@ public class NumberUtil {
*
* @param x 第一个值
* @param y 第二个值
- * @return x==y返回0,x<y返回-1,x>y返回1
+ * @return x==y返回0,x<y返回小于0的数,x>y返回大于0的数
* @see Short#compare(short, short)
* @since 3.0.1
*/
@@ -1754,7 +1754,7 @@ public class NumberUtil {
* @param bigNum1 数字1
* @param bigNum2 数字2
* @return 是否大于
- * @since 3, 0.9
+ * @since 3.0.9
*/
public static boolean isGreater(BigDecimal bigNum1, BigDecimal bigNum2) {
Assert.notNull(bigNum1);
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 649e18e19..eeab6d5fc 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
@@ -292,7 +292,7 @@ public class ObjectUtil {
* @since 3.0.7
*/
public static T defaultIfNull(final T object, final T defaultValue) {
- return (null != object) ? object : defaultValue;
+ return isNull(object) ? defaultValue : object;
}
@@ -300,14 +300,14 @@ public class ObjectUtil {
* 如果给定对象为{@code null} 返回默认值, 如果不为null 返回自定义handle处理后的返回值
*
* @param source Object 类型对象
- * @param handle 自定义的处理方法
+ * @param handle 非空时自定义的处理方法
* @param defaultValue 默认为空的返回值
* @param 被检查对象为{@code null}返回默认值,否则返回自定义handle处理后的返回值
* @return 处理后的返回值
* @since 5.4.6
*/
public static T defaultIfNull(Object source, Supplier extends T> handle, final T defaultValue) {
- if (Objects.nonNull(source)) {
+ if (isNotNull(source)) {
return handle.get();
}
return defaultValue;
@@ -455,11 +455,14 @@ public class ObjectUtil {
/**
* 是否为基本类型,包括包装类型和非包装类型
*
- * @param object 被检查对象
+ * @param object 被检查对象,{@code null}返回{@code false}
* @return 是否为基本类型
* @see ClassUtil#isBasicType(Class)
*/
public static boolean isBasicType(Object object) {
+ if (null == object) {
+ return false;
+ }
return ClassUtil.isBasicType(object.getClass());
}
diff --git a/hutool-core/src/main/java/cn/hutool/core/util/RandomUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/RandomUtil.java
index aae07f8be..8d77384e7 100644
--- a/hutool-core/src/main/java/cn/hutool/core/util/RandomUtil.java
+++ b/hutool-core/src/main/java/cn/hutool/core/util/RandomUtil.java
@@ -163,6 +163,16 @@ public class RandomUtil {
return 0 == randomInt(2);
}
+ /**
+ * 随机汉字('\u4E00'-'\u9FFF')
+ *
+ * @return 随机的汉字字符
+ * @since 5.7.15
+ */
+ public static char randomChinese() {
+ return (char) randomInt('\u4E00', '\u9FFF');
+ }
+
/**
* 获得指定范围内的随机数
*
diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java
index fcad035a5..e9212367f 100644
--- a/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java
+++ b/hutool-core/src/main/java/cn/hutool/core/util/ReUtil.java
@@ -3,18 +3,23 @@ package cn.hutool.core.util;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.exceptions.UtilException;
+import cn.hutool.core.lang.Assert;
import cn.hutool.core.lang.Holder;
import cn.hutool.core.lang.PatternPool;
import cn.hutool.core.lang.RegexPool;
import cn.hutool.core.lang.Validator;
import cn.hutool.core.lang.func.Func1;
+import cn.hutool.core.lang.mutable.MutableObj;
+import cn.hutool.core.map.MapUtil;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
+import java.util.function.Consumer;
import java.util.regex.MatchResult;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -78,11 +83,27 @@ public class ReUtil {
return null;
}
- // Pattern pattern = Pattern.compile(regex, Pattern.DOTALL);
final Pattern pattern = PatternPool.get(regex, Pattern.DOTALL);
return get(pattern, content, groupIndex);
}
+ /**
+ * 获得匹配的字符串
+ *
+ * @param regex 匹配的正则
+ * @param content 被匹配的内容
+ * @param groupName 匹配正则的分组名称
+ * @return 匹配后得到的字符串,未匹配返回null
+ */
+ public static String get(String regex, CharSequence content, String groupName) {
+ if (null == content || null == regex) {
+ return null;
+ }
+
+ final Pattern pattern = PatternPool.get(regex, Pattern.DOTALL);
+ return get(pattern, content, groupName);
+ }
+
/**
* 获得匹配的字符串,,获得正则中分组0的内容
*
@@ -120,11 +141,47 @@ public class ReUtil {
return null;
}
- final Matcher matcher = pattern.matcher(content);
- if (matcher.find()) {
- return matcher.group(groupIndex);
+ final MutableObj result = new MutableObj<>();
+ get(pattern, content, matcher -> result.set(matcher.group(groupIndex)));
+ return result.get();
+ }
+
+ /**
+ * 获得匹配的字符串
+ *
+ * @param pattern 匹配的正则
+ * @param content 被匹配的内容
+ * @param groupName 匹配正则的分组名称
+ * @return 匹配后得到的字符串,未匹配返回null
+ * @since 5.7.15
+ */
+ public static String get(Pattern pattern, CharSequence content, String groupName) {
+ if (null == content || null == pattern || null == groupName) {
+ return null;
+ }
+
+ final MutableObj result = new MutableObj<>();
+ get(pattern, content, matcher -> result.set(matcher.group(groupName)));
+ return result.get();
+ }
+
+ /**
+ * 在给定字符串中查找给定规则的字符,如果找到则使用{@link Consumer}处理之
+ * 如果内容中有多个匹配项,则只处理找到的第一个结果。
+ *
+ * @param pattern 匹配的正则
+ * @param content 被匹配的内容
+ * @param consumer 匹配到的内容处理器
+ * @since 5.7.15
+ */
+ public static void get(Pattern pattern, CharSequence content, Consumer consumer) {
+ if (null == content || null == pattern || null == consumer) {
+ return;
+ }
+ final Matcher m = pattern.matcher(content);
+ if (m.find()) {
+ consumer.accept(m);
}
- return null;
}
/**
@@ -165,6 +222,33 @@ public class ReUtil {
return result;
}
+ /**
+ * 根据给定正则查找字符串中的匹配项,返回所有匹配的分组名对应分组值
+ *
+ * pattern: (?<year>\\d+)-(?<month>\\d+)-(?<day>\\d+)
+ * content: 2021-10-11
+ * result : year: 2021, month: 10, day: 11
+ *
+ *
+ * @param pattern 匹配的正则
+ * @param content 被匹配的内容
+ * @return 命名捕获组,key为分组名,value为对应值
+ * @since 5.7.15
+ */
+ public static Map getAllGroupNames(Pattern pattern, CharSequence content) {
+ if (null == content || null == pattern) {
+ return null;
+ }
+ final Matcher m = pattern.matcher(content);
+ final Map result = MapUtil.newHashMap(m.groupCount());
+ if (m.find()) {
+ // 通过反射获取 namedGroups 方法
+ final Map map = ReflectUtil.invoke(pattern, "namedGroups");
+ map.forEach((key, value) -> result.put(key, m.group(value)));
+ }
+ return result;
+ }
+
/**
* 从content中匹配出多个值并根据template生成新的字符串
* 例如:
@@ -213,7 +297,6 @@ public class ReUtil {
return null;
}
- // Pattern pattern = Pattern.compile(regex, Pattern.DOTALL);
final Pattern pattern = PatternPool.get(regex, Pattern.DOTALL);
return extractMulti(pattern, content, template);
}
@@ -264,7 +347,6 @@ public class ReUtil {
return null;
}
- // Pattern pattern = Pattern.compile(regex, Pattern.DOTALL);
final Pattern pattern = PatternPool.get(regex, Pattern.DOTALL);
return extractMultiAndDelPre(pattern, contentHolder, template);
}
@@ -281,7 +363,6 @@ public class ReUtil {
return StrUtil.str(content);
}
- // Pattern pattern = Pattern.compile(regex, Pattern.DOTALL);
final Pattern pattern = PatternPool.get(regex, Pattern.DOTALL);
return delFirst(pattern, content);
}
@@ -300,8 +381,8 @@ public class ReUtil {
/**
* 替换匹配的第一个内容
*
- * @param pattern 正则
- * @param content 被匹配的内容
+ * @param pattern 正则
+ * @param content 被匹配的内容
* @param replacement 替换的内容
* @return 替换后剩余的内容
* @since 5.6.5
@@ -342,7 +423,7 @@ public class ReUtil {
public static String delLast(Pattern pattern, CharSequence str) {
if (null != pattern && StrUtil.isNotEmpty(str)) {
final MatchResult matchResult = lastIndexOf(pattern, str);
- if(null != matchResult){
+ if (null != matchResult) {
return StrUtil.subPre(str, matchResult.start()) + StrUtil.subSuf(str, matchResult.end());
}
}
@@ -362,7 +443,6 @@ public class ReUtil {
return StrUtil.str(content);
}
- // Pattern pattern = Pattern.compile(regex, Pattern.DOTALL);
final Pattern pattern = PatternPool.get(regex, Pattern.DOTALL);
return delAll(pattern, content);
}
@@ -394,9 +474,23 @@ public class ReUtil {
return StrUtil.str(content);
}
- // Pattern pattern = Pattern.compile(regex, Pattern.DOTALL);
final Pattern pattern = PatternPool.get(regex, Pattern.DOTALL);
- Matcher matcher = pattern.matcher(content);
+ return delPre(pattern, content);
+ }
+
+ /**
+ * 删除正则匹配到的内容之前的字符 如果没有找到,则返回原文
+ *
+ * @param pattern 定位正则模式
+ * @param content 被查找的内容
+ * @return 删除前缀后的新内容
+ */
+ public static String delPre(Pattern pattern, CharSequence content) {
+ if (null == content || null == pattern) {
+ return StrUtil.str(content);
+ }
+
+ final Matcher matcher = pattern.matcher(content);
if (matcher.find()) {
return StrUtil.sub(content, matcher.end(), content.length());
}
@@ -455,7 +549,7 @@ public class ReUtil {
return collection;
}
- return findAll(Pattern.compile(regex, Pattern.DOTALL), content, group, collection);
+ return findAll(PatternPool.get(regex, Pattern.DOTALL), content, group, collection);
}
/**
@@ -509,16 +603,29 @@ public class ReUtil {
if (null == pattern || null == content) {
return null;
}
+ Assert.notNull(collection, "Collection must be not null !");
- if (null == collection) {
- throw new NullPointerException("Null collection param provided!");
+ findAll(pattern, content, (matcher) -> collection.add(matcher.group(group)));
+ return collection;
+ }
+
+ /**
+ * 取得内容中匹配的所有结果,使用{@link Consumer}完成匹配结果处理
+ *
+ * @param pattern 编译后的正则模式
+ * @param content 被查找的内容
+ * @param consumer 匹配结果处理函数
+ * @since 5.7.15
+ */
+ public static void findAll(Pattern pattern, CharSequence content, Consumer consumer) {
+ if (null == pattern || null == content) {
+ return;
}
final Matcher matcher = pattern.matcher(content);
while (matcher.find()) {
- collection.add(matcher.group(group));
+ consumer.accept(matcher);
}
- return collection;
}
/**
@@ -533,7 +640,6 @@ public class ReUtil {
return 0;
}
- // Pattern pattern = Pattern.compile(regex, Pattern.DOTALL);
final Pattern pattern = PatternPool.get(regex, Pattern.DOTALL);
return count(pattern, content);
}
@@ -594,12 +700,12 @@ public class ReUtil {
/**
* 找到指定正则匹配到字符串的开始位置
*
- * @param regex 正则
+ * @param regex 正则
* @param content 字符串
* @return 位置,{@code null}表示未找到
* @since 5.6.5
*/
- public static MatchResult indexOf(String regex, CharSequence content){
+ public static MatchResult indexOf(String regex, CharSequence content) {
if (null == regex || null == content) {
return null;
}
@@ -616,10 +722,10 @@ public class ReUtil {
* @return 位置,{@code null}表示未找到
* @since 5.6.5
*/
- public static MatchResult indexOf(Pattern pattern, CharSequence content){
- if(null != pattern && null != content){
+ public static MatchResult indexOf(Pattern pattern, CharSequence content) {
+ if (null != pattern && null != content) {
final Matcher matcher = pattern.matcher(content);
- if(matcher.find()){
+ if (matcher.find()) {
return matcher.toMatchResult();
}
}
@@ -630,12 +736,12 @@ public class ReUtil {
/**
* 找到指定正则匹配到第一个字符串的位置
*
- * @param regex 正则
+ * @param regex 正则
* @param content 字符串
* @return 位置,{@code null}表示未找到
* @since 5.6.5
*/
- public static MatchResult lastIndexOf(String regex, CharSequence content){
+ public static MatchResult lastIndexOf(String regex, CharSequence content) {
if (null == regex || null == content) {
return null;
}
@@ -652,11 +758,11 @@ public class ReUtil {
* @return 位置,{@code null}表示未找到
* @since 5.6.5
*/
- public static MatchResult lastIndexOf(Pattern pattern, CharSequence content){
+ public static MatchResult lastIndexOf(Pattern pattern, CharSequence content) {
MatchResult result = null;
- if(null != pattern && null != content){
+ if (null != pattern && null != content) {
final Matcher matcher = pattern.matcher(content);
- while(matcher.find()){
+ while (matcher.find()) {
result = matcher.toMatchResult();
}
}
diff --git a/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java
new file mode 100644
index 000000000..d22679567
--- /dev/null
+++ b/hutool-core/src/main/java/cn/hutool/core/util/SystemPropsUtil.java
@@ -0,0 +1,146 @@
+package cn.hutool.core.util;
+
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.lang.Console;
+
+import java.util.Properties;
+
+/**
+ * 系统属性工具
+ * 此工具用于读取系统属性或环境变量信息,封装包括:
+ *
+ * - {@link System#getProperty(String)}
+ * - {@link System#getenv(String)}
+ *
+ *
+ * @author looly
+ * @since 5.7.16
+ */
+public class SystemPropsUtil {
+
+ /** Hutool自定义系统属性:是否解析日期字符串采用严格模式 */
+ public static String HUTOOL_DATE_LENIENT = "hutool.date.lenient";
+
+ /**
+ * 取得系统属性,如果因为Java安全的限制而失败,则将错误打在Log中,然后返回 defaultValue
+ *
+ * @param name 属性名
+ * @param defaultValue 默认值
+ * @return 属性值或defaultValue
+ * @see System#getProperty(String)
+ * @see System#getenv(String)
+ */
+ public static String get(String name, String defaultValue) {
+ return StrUtil.nullToDefault(get(name, false), defaultValue);
+ }
+
+ /**
+ * 取得系统属性,如果因为Java安全的限制而失败,则将错误打在Log中,然后返回 {@code null}
+ *
+ * @param name 属性名
+ * @param quiet 安静模式,不将出错信息打在{@code System.err}中
+ * @return 属性值或{@code null}
+ * @see System#getProperty(String)
+ * @see System#getenv(String)
+ */
+ public static String get(String name, boolean quiet) {
+ String value = null;
+ try {
+ value = System.getProperty(name);
+ } catch (SecurityException e) {
+ if (false == quiet) {
+ Console.error("Caught a SecurityException reading the system property '{}'; " +
+ "the SystemUtil property value will default to null.", name);
+ }
+ }
+
+ if (null == value) {
+ try {
+ value = System.getenv(name);
+ } catch (SecurityException e) {
+ if (false == quiet) {
+ Console.error("Caught a SecurityException reading the system env '{}'; " +
+ "the SystemUtil env value will default to null.", name);
+ }
+ }
+ }
+
+ return value;
+ }
+
+ /**
+ * 获得System属性
+ *
+ * @param key 键
+ * @return 属性值
+ * @see System#getProperty(String)
+ * @see System#getenv(String)
+ */
+ public static String get(String key) {
+ return get(key, null);
+ }
+
+ /**
+ * 获得boolean类型值
+ *
+ * @param key 键
+ * @param defaultValue 默认值
+ * @return 值
+ */
+ public static boolean getBoolean(String key, boolean defaultValue) {
+ String value = get(key);
+ if (value == null) {
+ return defaultValue;
+ }
+
+ value = value.trim().toLowerCase();
+ if (value.isEmpty()) {
+ return true;
+ }
+
+ return Convert.toBool(value, defaultValue);
+ }
+
+ /**
+ * 获得int类型值
+ *
+ * @param key 键
+ * @param defaultValue 默认值
+ * @return 值
+ */
+ public static long getInt(String key, int defaultValue) {
+ return Convert.toInt(get(key), defaultValue);
+ }
+
+ /**
+ * 获得long类型值
+ *
+ * @param key 键
+ * @param defaultValue 默认值
+ * @return 值
+ */
+ public static long getLong(String key, long defaultValue) {
+ return Convert.toLong(get(key), defaultValue);
+ }
+
+ /**
+ * @return 属性列表
+ */
+ public static Properties getProps() {
+ return System.getProperties();
+ }
+
+ /**
+ * 设置系统属性,value为{@code null}表示移除此属性
+ *
+ * @param key 属性名
+ * @param value 属性值,{@code null}表示移除此属性
+ */
+ public static void set(String key, String value) {
+ if (null == value) {
+ System.clearProperty(key);
+ } else {
+ System.setProperty(key, value);
+ }
+ }
+}
diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java
index c40ded1cd..37e77b5d5 100644
--- a/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java
+++ b/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java
@@ -2,6 +2,7 @@ package cn.hutool.core.util;
import cn.hutool.core.compress.Deflate;
import cn.hutool.core.compress.Gzip;
+import cn.hutool.core.compress.ZipCopyVisitor;
import cn.hutool.core.compress.ZipReader;
import cn.hutool.core.compress.ZipWriter;
import cn.hutool.core.exceptions.UtilException;
@@ -9,6 +10,8 @@ import cn.hutool.core.io.FastByteArrayOutputStream;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.IoUtil;
+import cn.hutool.core.io.file.FileSystemUtil;
+import cn.hutool.core.io.file.PathUtil;
import cn.hutool.core.io.resource.Resource;
import java.io.BufferedInputStream;
@@ -20,6 +23,11 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
+import java.nio.file.CopyOption;
+import java.nio.file.FileAlreadyExistsException;
+import java.nio.file.FileSystem;
+import java.nio.file.Files;
+import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -32,8 +40,8 @@ import java.util.zip.ZipOutputStream;
/**
* 压缩工具类
*
- * @see cn.hutool.core.compress.ZipWriter
* @author Looly
+ * @see cn.hutool.core.compress.ZipWriter
*/
public class ZipUtil {
@@ -75,6 +83,36 @@ public class ZipUtil {
}
}
+ /**
+ * 在zip文件中添加新文件或目录
+ * 新文件添加在zip根目录,文件夹包括其本身和内容
+ * 如果待添加文件夹是系统根路径(如/或c:/),则只复制文件夹下的内容
+ *
+ * @param zipPath zip文件的Path
+ * @param appendFilePath 待添加文件Path(可以是文件夹)
+ * @param options 拷贝选项,可选是否覆盖等
+ * @throws IORuntimeException IO异常
+ * @since 5.7.15
+ */
+ public static void append(Path zipPath, Path appendFilePath, CopyOption... options) throws IORuntimeException {
+ try (FileSystem zipFileSystem = FileSystemUtil.createZip(zipPath.toString())) {
+ if (Files.isDirectory(appendFilePath)) {
+ Path source = appendFilePath.getParent();
+ if (null == source) {
+ // 如果用户提供的是根路径,则不复制目录,直接复制目录下的内容
+ source = appendFilePath;
+ }
+ Files.walkFileTree(appendFilePath, new ZipCopyVisitor(source, zipFileSystem, options));
+ } else {
+ Files.copy(appendFilePath, zipFileSystem.getPath(PathUtil.getName(appendFilePath)), options);
+ }
+ } catch (FileAlreadyExistsException ignored) {
+ // 不覆盖情况下,文件已存在, 跳过
+ } catch (IOException e){
+ throw new IORuntimeException(e);
+ }
+ }
+
/**
* 打包到当前目录,使用默认编码UTF-8
*
@@ -240,7 +278,7 @@ public class ZipUtil {
*/
@Deprecated
public static void zip(ZipOutputStream zipOutputStream, boolean withSrcDir, FileFilter filter, File... srcFiles) throws IORuntimeException {
- try(final ZipWriter zipWriter = new ZipWriter(zipOutputStream)){
+ try (final ZipWriter zipWriter = new ZipWriter(zipOutputStream)) {
zipWriter.add(withSrcDir, filter, srcFiles);
}
}
@@ -339,7 +377,7 @@ public class ZipUtil {
throw new IllegalArgumentException("Paths length is not equals to ins length !");
}
- try(final ZipWriter zipWriter = ZipWriter.of(zipFile, charset)){
+ try (final ZipWriter zipWriter = ZipWriter.of(zipFile, charset)) {
for (int i = 0; i < paths.length; i++) {
zipWriter.add(paths[i], ins[i]);
}
@@ -364,7 +402,7 @@ public class ZipUtil {
throw new IllegalArgumentException("Paths length is not equals to ins length !");
}
- try(final ZipWriter zipWriter = ZipWriter.of(out, DEFAULT_CHARSET)){
+ try (final ZipWriter zipWriter = ZipWriter.of(out, DEFAULT_CHARSET)) {
for (int i = 0; i < paths.length; i++) {
zipWriter.add(paths[i], ins[i]);
}
@@ -388,7 +426,7 @@ public class ZipUtil {
throw new IllegalArgumentException("Paths length is not equals to ins length !");
}
- try(final ZipWriter zipWriter = new ZipWriter(zipOutputStream)){
+ try (final ZipWriter zipWriter = new ZipWriter(zipOutputStream)) {
for (int i = 0; i < paths.length; i++) {
zipWriter.add(paths[i], ins[i]);
}
@@ -528,7 +566,7 @@ public class ZipUtil {
StrUtil.format("Target path [{}] exist!", outFile.getAbsolutePath()));
}
- try(final ZipReader reader = new ZipReader(zipFile)){
+ try (final ZipReader reader = new ZipReader(zipFile)) {
reader.readTo(outFile);
}
return outFile;
@@ -571,7 +609,7 @@ public class ZipUtil {
* @since 5.5.2
*/
public static void read(ZipFile zipFile, Consumer consumer) {
- try(final ZipReader reader = new ZipReader(zipFile)){
+ try (final ZipReader reader = new ZipReader(zipFile)) {
reader.read(consumer);
}
}
@@ -605,7 +643,7 @@ public class ZipUtil {
* @since 4.5.8
*/
public static File unzip(ZipInputStream zipStream, File outFile) throws UtilException {
- try(final ZipReader reader = new ZipReader(zipStream)){
+ try (final ZipReader reader = new ZipReader(zipStream)) {
reader.readTo(outFile);
}
return outFile;
@@ -619,7 +657,7 @@ public class ZipUtil {
* @since 5.5.2
*/
public static void read(ZipInputStream zipStream, Consumer consumer) {
- try(final ZipReader reader = new ZipReader(zipStream)){
+ try (final ZipReader reader = new ZipReader(zipStream)) {
reader.read(consumer);
}
}
@@ -671,7 +709,7 @@ public class ZipUtil {
* @since 4.1.8
*/
public static byte[] unzipFileBytes(File zipFile, Charset charset, String name) {
- try(final ZipReader reader = ZipReader.of(zipFile, charset)){
+ try (final ZipReader reader = ZipReader.of(zipFile, charset)) {
return IoUtil.readBytes(reader.get(name));
}
}
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 526e8bbca..d05bc13b0 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
@@ -197,6 +197,19 @@ public class BeanUtilTest {
Assert.assertEquals("sub名字", map.get("sub_name"));
}
+ @Test
+ public void beanToMapWithValueEditTest() {
+ SubPerson person = new SubPerson();
+ person.setAge(14);
+ person.setOpenid("11213232");
+ person.setName("测试A11");
+ person.setSubName("sub名字");
+
+ Map map = BeanUtil.beanToMap(person, new LinkedHashMap<>(),
+ CopyOptions.create().setFieldValueEditor((key, value) -> key + "_" + value));
+ Assert.assertEquals("subName_sub名字", map.get("subName"));
+ }
+
@Test
public void beanToMapWithAliasTest() {
SubPersonWithAlias person = new SubPersonWithAlias();
diff --git a/hutool-core/src/test/java/cn/hutool/core/collection/CollUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/collection/CollUtilTest.java
index 80c66af8f..c3be3c277 100644
--- a/hutool-core/src/test/java/cn/hutool/core/collection/CollUtilTest.java
+++ b/hutool-core/src/test/java/cn/hutool/core/collection/CollUtilTest.java
@@ -712,6 +712,23 @@ public class CollUtilTest {
Assert.assertEquals("d", map.get("keyd"));
}
+ @Test
+ public void mapToMapTest(){
+ final HashMap oldMap = new HashMap<>();
+ oldMap.put("a", "1");
+ oldMap.put("b", "12");
+ oldMap.put("c", "134");
+
+ final Map map = CollUtil.toMap(oldMap.entrySet(),
+ new HashMap<>(),
+ Map.Entry::getKey,
+ entry -> Long.parseLong(entry.getValue()));
+
+ Assert.assertEquals(1L, (long)map.get("a"));
+ Assert.assertEquals(12L, (long)map.get("b"));
+ Assert.assertEquals(134L, (long)map.get("c"));
+ }
+
@Test
public void countMapTest() {
ArrayList list = CollUtil.newArrayList("a", "b", "c", "c", "a", "b", "d");
diff --git a/hutool-core/src/test/java/cn/hutool/core/collection/RingIndexUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/collection/RingIndexUtilTest.java
new file mode 100644
index 000000000..4ff70cdd1
--- /dev/null
+++ b/hutool-core/src/test/java/cn/hutool/core/collection/RingIndexUtilTest.java
@@ -0,0 +1,34 @@
+package cn.hutool.core.collection;
+
+import cn.hutool.core.lang.Assert;
+import cn.hutool.core.thread.ThreadUtil;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * 集合索引环形获取工具类测试类
+ *
+ * @author ZhouChuGang
+ */
+public class RingIndexUtilTest {
+
+ private final List strList = Arrays.asList("0", "1", "2", "3", "4", "5", "6", "7", "8", "9");
+
+ /**
+ * 观察输出的打印为不重复的
+ */
+ @Test
+ public void ringNextIntByObjTest() {
+ final AtomicInteger atomicInteger = new AtomicInteger();
+ // 开启并发测试,每个线程获取到的元素都是唯一的
+ ThreadUtil.concurrencyTest(strList.size(), () -> {
+ final int index = RingIndexUtil.ringNextIntByObj(strList, atomicInteger);
+ final String s = strList.get(index);
+ Assert.notNull(s);
+ });
+ }
+
+}
diff --git a/hutool-core/src/test/java/cn/hutool/core/compress/ZipWriterTest.java b/hutool-core/src/test/java/cn/hutool/core/compress/ZipWriterTest.java
index 97bb6af52..c2a2f5bd4 100755
--- a/hutool-core/src/test/java/cn/hutool/core/compress/ZipWriterTest.java
+++ b/hutool-core/src/test/java/cn/hutool/core/compress/ZipWriterTest.java
@@ -1,5 +1,8 @@
package cn.hutool.core.compress;
+import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.io.resource.FileResource;
+import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.ZipUtil;
import org.junit.Ignore;
import org.junit.Test;
@@ -13,4 +16,12 @@ public class ZipWriterTest {
public void zipDirTest() {
ZipUtil.zip(new File("d:/test"));
}
+
+ @Test
+ @Ignore
+ public void addTest(){
+ final ZipWriter writer = ZipWriter.of(FileUtil.file("d:/test/test.zip"), CharsetUtil.CHARSET_UTF_8);
+ writer.add(new FileResource("d:/test/qr_c.png"));
+ writer.close();
+ }
}
diff --git a/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java
index 05c674cf5..738995533 100644
--- a/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java
+++ b/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java
@@ -989,4 +989,10 @@ public class DateUtilTest {
Assert.assertNotNull(parse);
Assert.assertEquals("2021-01-01 00:00:00", parse.toString());
}
+
+ @Test
+ public void parseByDateTimeFormatterTest(){
+ final DateTime parse = DateUtil.parse("2021-12-01", DatePattern.NORM_DATE_FORMATTER);
+ Assert.assertEquals("2021-12-01 00:00:00", parse.toString());
+ }
}
diff --git a/hutool-core/src/test/java/cn/hutool/core/io/file/FileSystemUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/io/file/FileSystemUtilTest.java
new file mode 100644
index 000000000..130b400f8
--- /dev/null
+++ b/hutool-core/src/test/java/cn/hutool/core/io/file/FileSystemUtilTest.java
@@ -0,0 +1,31 @@
+package cn.hutool.core.io.file;
+
+import cn.hutool.core.lang.Console;
+import cn.hutool.core.util.CharsetUtil;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import java.nio.file.FileSystem;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+
+public class FileSystemUtilTest {
+
+ @Test
+ @Ignore
+ public void listTest(){
+ final FileSystem fileSystem = FileSystemUtil.createZip("d:/test/test.zip",
+ CharsetUtil.CHARSET_GBK);
+ final Path root = FileSystemUtil.getRoot(fileSystem);
+ PathUtil.walkFiles(root, new SimpleFileVisitor() {
+
+ @Override
+ public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) {
+ Console.log(path);
+ return FileVisitResult.CONTINUE;
+ }
+ });
+ }
+}
diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/StrFormatterTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/StrFormatterTest.java
index 5803a6287..a2ad790c9 100644
--- a/hutool-core/src/test/java/cn/hutool/core/lang/StrFormatterTest.java
+++ b/hutool-core/src/test/java/cn/hutool/core/lang/StrFormatterTest.java
@@ -6,19 +6,49 @@ import org.junit.Test;
import cn.hutool.core.text.StrFormatter;
public class StrFormatterTest {
-
+
@Test
- public void formatTest(){
+ public void formatTest() {
//通常使用
String result1 = StrFormatter.format("this is {} for {}", "a", "b");
Assert.assertEquals("this is a for b", result1);
-
+
//转义{}
String result2 = StrFormatter.format("this is \\{} for {}", "a", "b");
Assert.assertEquals("this is {} for a", result2);
-
+
//转义\
String result3 = StrFormatter.format("this is \\\\{} for {}", "a", "b");
Assert.assertEquals("this is \\a for b", result3);
}
+
+ @Test
+ public void formatWithTest() {
+ //通常使用
+ String result1 = StrFormatter.formatWith("this is ? for ?", "?", "a", "b");
+ Assert.assertEquals("this is a for b", result1);
+
+ //转义?
+ String result2 = StrFormatter.formatWith("this is \\? for ?", "?", "a", "b");
+ Assert.assertEquals("this is ? for a", result2);
+
+ //转义\
+ String result3 = StrFormatter.formatWith("this is \\\\? for ?", "?", "a", "b");
+ Assert.assertEquals("this is \\a for b", result3);
+ }
+
+ @Test
+ public void formatWithTest2() {
+ //通常使用
+ String result1 = StrFormatter.formatWith("this is $$$ for $$$", "$$$", "a", "b");
+ Assert.assertEquals("this is a for b", result1);
+
+ //转义?
+ String result2 = StrFormatter.formatWith("this is \\$$$ for $$$", "$$$", "a", "b");
+ Assert.assertEquals("this is $$$ for a", result2);
+
+ //转义\
+ String result3 = StrFormatter.formatWith("this is \\\\$$$ for $$$", "$$$", "a", "b");
+ Assert.assertEquals("this is \\a for b", result3);
+ }
}
diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/ValidatorTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/ValidatorTest.java
index a14a3d8dd..cfcd7866f 100644
--- a/hutool-core/src/test/java/cn/hutool/core/lang/ValidatorTest.java
+++ b/hutool-core/src/test/java/cn/hutool/core/lang/ValidatorTest.java
@@ -21,7 +21,7 @@ public class ValidatorTest {
}
@Test
- public void hasNumberTest() throws Exception {
+ public void hasNumberTest() {
String var1 = "";
String var2 = "str";
String var3 = "180";
@@ -218,4 +218,13 @@ public class ValidatorTest {
public void isCarDrivingLicenceTest(){
Assert.assertTrue(Validator.isCarDrivingLicence("430101758218"));
}
+
+ @Test
+ public void validateIpv4Test(){
+ Validator.validateIpv4("192.168.1.1", "Error ip");
+ Validator.validateIpv4("8.8.8.8", "Error ip");
+ Validator.validateIpv4("0.0.0.0", "Error ip");
+ Validator.validateIpv4("255.255.255.255", "Error ip");
+ Validator.validateIpv4("127.0.0.0", "Error ip");
+ }
}
diff --git a/hutool-core/src/test/java/cn/hutool/core/net/Ipv4UtilTest.java b/hutool-core/src/test/java/cn/hutool/core/net/Ipv4UtilTest.java
index 5a472963c..13d5ddbd1 100644
--- a/hutool-core/src/test/java/cn/hutool/core/net/Ipv4UtilTest.java
+++ b/hutool-core/src/test/java/cn/hutool/core/net/Ipv4UtilTest.java
@@ -1,11 +1,10 @@
package cn.hutool.core.net;
-import cn.hutool.core.lang.Console;
import org.junit.Assert;
import org.junit.Test;
+import org.junit.function.ThrowingRunnable;
import java.util.List;
-import org.junit.function.ThrowingRunnable;
public class Ipv4UtilTest {
@@ -40,7 +39,7 @@ public class Ipv4UtilTest {
String ip = "192.168.1.1";
final int maskBitByMask = Ipv4Util.getMaskBitByMask("255.255.255.0");
final String endIpStr = Ipv4Util.getEndIpStr(ip, maskBitByMask);
- Console.log(endIpStr);
+ Assert.assertEquals("192.168.1.255", endIpStr);
}
@Test
@@ -75,4 +74,16 @@ public class Ipv4UtilTest {
boolean maskBitValid = Ipv4Util.isMaskBitValid(33);
Assert.assertFalse("掩码位非法检验", maskBitValid);
}
+
+ @Test
+ public void ipv4ToLongTest(){
+ long l = Ipv4Util.ipv4ToLong("127.0.0.1");
+ Assert.assertEquals(2130706433L, l);
+ l = Ipv4Util.ipv4ToLong("114.114.114.114");
+ Assert.assertEquals(1920103026L, l);
+ l = Ipv4Util.ipv4ToLong("0.0.0.0");
+ Assert.assertEquals(0L, l);
+ l = Ipv4Util.ipv4ToLong("255.255.255.255");
+ Assert.assertEquals(4294967295L, l);
+ }
}
diff --git a/hutool-core/src/test/java/cn/hutool/core/net/NetUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/net/NetUtilTest.java
index ff467456a..0bed82723 100644
--- a/hutool-core/src/test/java/cn/hutool/core/net/NetUtilTest.java
+++ b/hutool-core/src/test/java/cn/hutool/core/net/NetUtilTest.java
@@ -101,4 +101,15 @@ public class NetUtilTest {
Console.log(txt);
}
+ @Test
+ public void isInRangeTest(){
+ Assert.assertTrue(NetUtil.isInRange("114.114.114.114","0.0.0.0/0"));
+ Assert.assertTrue(NetUtil.isInRange("192.168.3.4","192.0.0.0/8"));
+ Assert.assertTrue(NetUtil.isInRange("192.168.3.4","192.168.0.0/16"));
+ Assert.assertTrue(NetUtil.isInRange("192.168.3.4","192.168.3.0/24"));
+ Assert.assertTrue(NetUtil.isInRange("192.168.3.4","192.168.3.4/32"));
+ Assert.assertFalse(NetUtil.isInRange("8.8.8.8","192.0.0.0/8"));
+ Assert.assertFalse(NetUtil.isInRange("114.114.114.114","192.168.3.4/32"));
+ }
+
}
diff --git a/hutool-core/src/test/java/cn/hutool/core/net/UrlBuilderTest.java b/hutool-core/src/test/java/cn/hutool/core/net/UrlBuilderTest.java
index f53085fc6..ea7604161 100644
--- a/hutool-core/src/test/java/cn/hutool/core/net/UrlBuilderTest.java
+++ b/hutool-core/src/test/java/cn/hutool/core/net/UrlBuilderTest.java
@@ -192,9 +192,9 @@ public class UrlBuilderTest {
"&sn=1044c0d19723f74f04f4c1da34eefa35" +
"&chksm=6cbda3a25bca2ab4516410db6ce6e125badaac2f8c5548ea6e18eab6dc3c5422cb8cbe1095f7";
final UrlBuilder builder = UrlBuilder.ofHttp(urlStr, CharsetUtil.CHARSET_UTF_8);
- // 原URL中的&替换为&,value中的=被编码为%3D
+ // 原URL中的&替换为&
Assert.assertEquals("https://mp.weixin.qq.com/s?" +
- "__biz=MzI5NjkyNTIxMg%3D%3D" +
+ "__biz=MzI5NjkyNTIxMg==" +
"&mid=100000465&idx=1" +
"&sn=1044c0d19723f74f04f4c1da34eefa35" +
"&chksm=6cbda3a25bca2ab4516410db6ce6e125badaac2f8c5548ea6e18eab6dc3c5422cb8cbe1095f7",
@@ -240,7 +240,7 @@ public class UrlBuilderTest {
public void testEncodeInQuery() {
String webUrl = "http://exmple.com/patha/pathb?a=123&b=4?6&c=789"; // b=4?6 参数中有未编码的?
final UrlBuilder urlBuilder = UrlBuilder.of(webUrl, StandardCharsets.UTF_8);
- Assert.assertEquals("a=123&b=4%3F6&c=789", urlBuilder.getQueryStr());
+ Assert.assertEquals("a=123&b=4?6&c=789", urlBuilder.getQueryStr());
}
@Test
@@ -271,4 +271,39 @@ public class UrlBuilderTest {
urlBuilder = UrlBuilder.ofHttp(urlBuilder.toString());
Assert.assertEquals(urlBuilder.toString(), urlBuilder.toString());
}
+
+ @Test
+ public void slashEncodeTest(){
+ // https://github.com/dromara/hutool/issues/1904
+ // 在query中,"/"是不可转义字符
+ // 见:https://www.rfc-editor.org/rfc/rfc3986.html#section-3.4
+ String url = "https://invoice.maycur.com/2b27a802-8423-4d41-86f5-63a6b259f61e.xlsx?download/2b27a802-8423-4d41-86f5-63a6b259f61e.xlsx&e=1630491088";
+ final UrlBuilder urlBuilder = UrlBuilder.ofHttp(url);
+ Assert.assertEquals(url, urlBuilder.toString());
+ }
+
+ @Test
+ public void addPathEncodeTest(){
+ String url = UrlBuilder.create()
+ .setScheme("https")
+ .setHost("domain.cn")
+ .addPath("api")
+ .addPath("xxx")
+ .addPath("bbb")
+ .build();
+
+ Assert.assertEquals("https://domain.cn/api/xxx/bbb", url);
+ }
+
+ @Test
+ public void addPathEncodeTest2(){
+ // https://github.com/dromara/hutool/issues/1912
+ String url = UrlBuilder.create()
+ .setScheme("https")
+ .setHost("domain.cn")
+ .addPath("/api/xxx/bbb")
+ .build();
+
+ Assert.assertEquals("https://domain.cn/api/xxx/bbb", url);
+ }
}
diff --git a/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java b/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java
index d4ab80682..85d11ec00 100644
--- a/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java
+++ b/hutool-core/src/test/java/cn/hutool/core/net/UrlQueryTest.java
@@ -63,4 +63,40 @@ public class UrlQueryTest {
query = URLUtil.buildQuery(map, StandardCharsets.UTF_8);
Assert.assertEquals("password=123456&username=SSM", query);
}
+
+ @Test
+ public void buildHasNullTest() {
+ Map map = new LinkedHashMap<>();
+ map.put(null, "SSM");
+ map.put("password", "123456");
+ String query = URLUtil.buildQuery(map, StandardCharsets.UTF_8);
+ Assert.assertEquals("password=123456", query);
+
+ map = new TreeMap<>();
+ map.put("username", "SSM");
+ map.put("password", "");
+ query = URLUtil.buildQuery(map, StandardCharsets.UTF_8);
+ Assert.assertEquals("password=&username=SSM", query);
+
+ map = new TreeMap<>();
+ map.put("username", "SSM");
+ map.put("password", null);
+ query = URLUtil.buildQuery(map, StandardCharsets.UTF_8);
+ Assert.assertEquals("password&username=SSM", query);
+ }
+
+ @Test
+ public void buildSpecialTest() {
+ Map map = new LinkedHashMap<>();
+ map.put("key1&", "SSM");
+ map.put("key2", "123456&");
+ String query = URLUtil.buildQuery(map, StandardCharsets.UTF_8);
+ Assert.assertEquals("key1%26=SSM&key2=123456%26", query);
+
+ map = new TreeMap<>();
+ map.put("username=", "SSM");
+ map.put("password", "=");
+ query = URLUtil.buildQuery(map, StandardCharsets.UTF_8);
+ Assert.assertEquals("password==&username%3D=SSM", query);
+ }
}
diff --git a/hutool-core/src/test/java/cn/hutool/core/text/CharSequenceUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/text/CharSequenceUtilTest.java
index bec53ad4d..85a35a18c 100644
--- a/hutool-core/src/test/java/cn/hutool/core/text/CharSequenceUtilTest.java
+++ b/hutool-core/src/test/java/cn/hutool/core/text/CharSequenceUtilTest.java
@@ -33,5 +33,19 @@ public class CharSequenceUtilTest {
Assert.assertEquals( str + " is Good", result);
}
+ @Test
+ public void normalizeTest(){
+ // https://blog.csdn.net/oscar999/article/details/105326270
+
+ String str1 = "\u00C1";
+ String str2 = "\u0041\u0301";
+
+ Assert.assertNotEquals(str1, str2);
+
+ str1 = CharSequenceUtil.normalize(str1);
+ str2 = CharSequenceUtil.normalize(str2);
+ Assert.assertEquals(str1, str2);
+ }
+
// ------------------------------------------------------------------------ remove
}
diff --git a/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java b/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java
index 1a5e2df3e..2ce2eee8c 100644
--- a/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java
+++ b/hutool-core/src/test/java/cn/hutool/core/text/StrJoinerTest.java
@@ -34,7 +34,7 @@ public class StrJoinerTest {
public void joinMultiArrayTest(){
final StrJoiner append = StrJoiner.of(",");
append.append(new Object[]{ListUtil.of("1", "2"),
- CollUtil.newHashSet("3", "4")
+ CollUtil.newLinkedHashSet("3", "4")
});
Assert.assertEquals("1,2,3,4", append.toString());
}
diff --git a/hutool-core/src/test/java/cn/hutool/core/thread/ThreadUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/thread/ThreadUtilTest.java
index 78aed5e29..e0e951baf 100644
--- a/hutool-core/src/test/java/cn/hutool/core/thread/ThreadUtilTest.java
+++ b/hutool-core/src/test/java/cn/hutool/core/thread/ThreadUtilTest.java
@@ -4,11 +4,11 @@ import org.junit.Assert;
import org.junit.Test;
public class ThreadUtilTest {
-
+
@Test
public void executeTest() {
final boolean isValid = true;
-
+
ThreadUtil.execute(() -> Assert.assertTrue(isValid));
}
}
diff --git a/hutool-core/src/test/java/cn/hutool/core/util/RandomUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/RandomUtilTest.java
index 42ba46299..978fcd2ed 100644
--- a/hutool-core/src/test/java/cn/hutool/core/util/RandomUtilTest.java
+++ b/hutool-core/src/test/java/cn/hutool/core/util/RandomUtilTest.java
@@ -53,4 +53,10 @@ public class RandomUtilTest {
final byte[] c = RandomUtil.randomBytes(10);
Assert.assertNotNull(c);
}
+
+ @Test
+ public void randomChineseTest(){
+ char c = RandomUtil.randomChinese();
+ Assert.assertTrue(c > 0);
+ }
}
diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ReUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ReUtilTest.java
index 4c392edc0..0b26e6241 100644
--- a/hutool-core/src/test/java/cn/hutool/core/util/ReUtilTest.java
+++ b/hutool-core/src/test/java/cn/hutool/core/util/ReUtilTest.java
@@ -8,6 +8,7 @@ import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
import java.util.regex.Pattern;
public class ReUtilTest {
@@ -163,4 +164,26 @@ public class ReUtilTest {
"(.+?)省(.+?)市(.+?)区", "广东省深圳市南山区");
Console.log(match);
}
+
+ @Test
+ public void getByGroupNameTest() {
+ String content = "2021-10-11";
+ String regex = "(?\\d+)-(?\\d+)-(?\\d+)";
+ String year = ReUtil.get(regex, content, "year");
+ Assert.assertEquals("2021", year);
+ String month = ReUtil.get(regex, content, "month");
+ Assert.assertEquals("10", month);
+ String day = ReUtil.get(regex, content, "day");
+ Assert.assertEquals("11", day);
+ }
+
+ @Test
+ public void getAllGroupNamesTest() {
+ String content = "2021-10-11";
+ String regex = "(?\\d+)-(?\\d+)-(?\\d+)";
+ Map map = ReUtil.getAllGroupNames(PatternPool.get(regex, Pattern.DOTALL), content);
+ Assert.assertEquals(map.get("year"), "2021");
+ Assert.assertEquals(map.get("month"), "10");
+ Assert.assertEquals(map.get("day"), "11");
+ }
}
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 73d8e6e7f..b0aca478c 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
@@ -81,6 +81,16 @@ public class StrUtilTest {
Assert.assertEquals("", split.get(2));
}
+ @Test(expected = IllegalArgumentException.class)
+ public void splitNullTest() {
+ StrUtil.split(null, '.');
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void splitToArrayNullTest() {
+ StrUtil.splitToArray(null, '.');
+ }
+
@Test
public void splitToLongTest() {
String str = "1,2,3,4, 5";
diff --git a/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java
index 0246f6688..a98972e67 100644
--- a/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java
+++ b/hutool-core/src/test/java/cn/hutool/core/util/ZipUtilTest.java
@@ -1,5 +1,6 @@
package cn.hutool.core.util;
+import cn.hutool.core.compress.ZipReader;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.lang.Console;
@@ -12,6 +13,8 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.List;
/**
* {@link ZipUtil}单元测试
@@ -20,6 +23,54 @@ import java.nio.charset.Charset;
*/
public class ZipUtilTest {
+ @Test
+ public void appendTest() throws IOException {
+ File appendFile = FileUtil.file("test-zip/addFile.txt");
+ File zipFile = FileUtil.file("test-zip/test.zip");
+
+ // 用于测试完成后将被测试文件恢复
+ File tempZipFile = FileUtil.createTempFile(FileUtil.file("test-zip"));
+ tempZipFile.deleteOnExit();
+ FileUtil.copy(zipFile, tempZipFile, true);
+
+ // test file add
+ List beforeNames = zipEntryNames(tempZipFile);
+ ZipUtil.append(tempZipFile.toPath(), appendFile.toPath());
+ List afterNames = zipEntryNames(tempZipFile);
+
+ // 确认增加了文件
+ Assert.assertEquals(beforeNames.size() + 1, afterNames.size());
+ Assert.assertTrue(afterNames.containsAll(beforeNames));
+ Assert.assertTrue(afterNames.contains(appendFile.getName()));
+
+ // test dir add
+ beforeNames = zipEntryNames(tempZipFile);
+ File addDirFile = FileUtil.file("test-zip/test-add");
+ ZipUtil.append(tempZipFile.toPath(), addDirFile.toPath());
+ afterNames = zipEntryNames(tempZipFile);
+
+ // 确认增加了文件和目录,增加目录和目录下一个文件,故此处+2
+ Assert.assertEquals(beforeNames.size() + 2, afterNames.size());
+ Assert.assertTrue(afterNames.containsAll(beforeNames));
+ Assert.assertTrue(afterNames.contains(appendFile.getName()));
+
+ // rollback
+ Assert.assertTrue(String.format("delete temp file %s failed", tempZipFile.getCanonicalPath()), tempZipFile.delete());
+ }
+
+ /**
+ * 获取zip文件中所有一级文件/文件夹的name
+ *
+ * @param zipFile 待测试的zip文件
+ * @return zip文件中一级目录下的所有文件/文件夹名
+ */
+ private List zipEntryNames(File zipFile) {
+ List fileNames = new ArrayList<>();
+ ZipReader reader = ZipReader.of(zipFile, CharsetUtil.CHARSET_UTF_8);
+ reader.read(zipEntry -> fileNames.add(zipEntry.getName()));
+ reader.close();
+ return fileNames;
+ }
@Test
@Ignore
@@ -109,4 +160,24 @@ public class ZipUtilTest {
}
}
+ @Test
+ @Ignore
+ public void zipStreamTest2(){
+ // https://github.com/dromara/hutool/issues/944
+ String file1 = "d:/test/a.txt";
+ String file2 = "d:/test/a.txt";
+ String file3 = "d:/test/asn1.key";
+
+ String zip = "d:/test/test2.zip";
+ try (OutputStream out = new FileOutputStream(zip)){
+ //实际应用中, out 为 HttpServletResponse.getOutputStream
+ ZipUtil.zip(out, Charset.defaultCharset(), false, null,
+ new File(file1),
+ new File(file2),
+ new File(file3)
+ );
+ } catch (IOException e) {
+ throw new IORuntimeException(e);
+ }
+ }
}
diff --git a/hutool-core/src/test/resources/test-zip/addFile.txt b/hutool-core/src/test/resources/test-zip/addFile.txt
new file mode 100644
index 000000000..8d1c2fee6
--- /dev/null
+++ b/hutool-core/src/test/resources/test-zip/addFile.txt
@@ -0,0 +1,2 @@
+this file will be used to add into the test.zip
+before the add action, the test.zip won't have this file.
diff --git a/hutool-core/src/test/resources/test-zip/test-add/test.txt b/hutool-core/src/test/resources/test-zip/test-add/test.txt
new file mode 100644
index 000000000..56a6051ca
--- /dev/null
+++ b/hutool-core/src/test/resources/test-zip/test-add/test.txt
@@ -0,0 +1 @@
+1
\ No newline at end of file
diff --git a/hutool-core/src/test/resources/test-zip/test.zip b/hutool-core/src/test/resources/test-zip/test.zip
new file mode 100644
index 000000000..86126d555
Binary files /dev/null and b/hutool-core/src/test/resources/test-zip/test.zip differ
diff --git a/hutool-cron/pom.xml b/hutool-cron/pom.xml
index 76a5a1fcc..4d3a77ef2 100644
--- a/hutool-cron/pom.xml
+++ b/hutool-cron/pom.xml
@@ -9,7 +9,7 @@
cn.hutool
hutool-parent
- 5.7.14-SNAPSHOT
+ 5.7.16-SNAPSHOT
hutool-cron
diff --git a/hutool-crypto/pom.xml b/hutool-crypto/pom.xml
index 37cc747eb..b58ed2229 100644
--- a/hutool-crypto/pom.xml
+++ b/hutool-crypto/pom.xml
@@ -9,7 +9,7 @@
cn.hutool
hutool-parent
- 5.7.14-SNAPSHOT
+ 5.7.16-SNAPSHOT
hutool-crypto
diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java b/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java
index b9df3e891..90ef70540 100644
--- a/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java
+++ b/hutool-crypto/src/main/java/cn/hutool/crypto/KeyUtil.java
@@ -180,7 +180,7 @@ public class KeyUtil {
*/
public static SecretKey generateDESKey(String algorithm, byte[] key) {
if (StrUtil.isBlank(algorithm) || false == algorithm.startsWith("DES")) {
- throw new CryptoException("Algorithm [{}] is not a DES algorithm!");
+ throw new CryptoException("Algorithm [{}] is not a DES algorithm!", algorithm);
}
SecretKey secretKey;
@@ -212,7 +212,7 @@ public class KeyUtil {
*/
public static SecretKey generatePBEKey(String algorithm, char[] key) {
if (StrUtil.isBlank(algorithm) || false == algorithm.startsWith("PBE")) {
- throw new CryptoException("Algorithm [{}] is not a PBE algorithm!");
+ throw new CryptoException("Algorithm [{}] is not a PBE algorithm!", algorithm);
}
if (null == key) {
diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/DigestUtil.java b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/DigestUtil.java
index 775d6183c..fc6fd61ce 100644
--- a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/DigestUtil.java
+++ b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/DigestUtil.java
@@ -1,13 +1,12 @@
package cn.hutool.crypto.digest;
+import cn.hutool.core.util.CharsetUtil;
+
+import javax.crypto.SecretKey;
import java.io.File;
import java.io.InputStream;
import java.nio.charset.Charset;
-import javax.crypto.SecretKey;
-
-import cn.hutool.core.util.CharsetUtil;
-
/**
* 摘要算法工具类
*
@@ -424,7 +423,7 @@ public class DigestUtil {
* 创建HMac对象,调用digest方法可获得hmac值
*
* @param algorithm {@link HmacAlgorithm}
- * @param key 密钥,如果为null
生成随机密钥
+ * @param key 密钥,如果为{@code null}生成随机密钥
* @return {@link HMac}
* @since 3.0.3
*/
@@ -436,7 +435,7 @@ public class DigestUtil {
* 创建HMac对象,调用digest方法可获得hmac值
*
* @param algorithm {@link HmacAlgorithm}
- * @param key 密钥{@link SecretKey},如果为null
生成随机密钥
+ * @param key 密钥{@link SecretKey},如果为{@code null}生成随机密钥
* @return {@link HMac}
* @since 3.0.3
*/
diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/symmetric/SymmetricCrypto.java b/hutool-crypto/src/main/java/cn/hutool/crypto/symmetric/SymmetricCrypto.java
index abc31d182..ba92ab553 100644
--- a/hutool-crypto/src/main/java/cn/hutool/crypto/symmetric/SymmetricCrypto.java
+++ b/hutool-crypto/src/main/java/cn/hutool/crypto/symmetric/SymmetricCrypto.java
@@ -300,9 +300,11 @@ public class SymmetricCrypto implements SymmetricEncryptor, SymmetricDecryptor,
throw new CryptoException(e);
} finally {
lock.unlock();
+ // issue#I4EMST@Gitee
+ // CipherOutputStream必须关闭,才能完全写出
+ IoUtil.close(cipherOutputStream);
if (isClose) {
IoUtil.close(data);
- IoUtil.close(cipherOutputStream);
}
}
}
@@ -351,9 +353,11 @@ public class SymmetricCrypto implements SymmetricEncryptor, SymmetricDecryptor,
throw new CryptoException(e);
} finally {
lock.unlock();
+ // issue#I4EMST@Gitee
+ // CipherOutputStream必须关闭,才能完全写出
+ IoUtil.close(cipherInputStream);
if (isClose) {
IoUtil.close(data);
- IoUtil.close(cipherInputStream);
}
}
}
diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/SmTest.java b/hutool-crypto/src/test/java/cn/hutool/crypto/test/SmTest.java
index 80c7995fe..c98dd21ee 100644
--- a/hutool-crypto/src/test/java/cn/hutool/crypto/test/SmTest.java
+++ b/hutool-crypto/src/test/java/cn/hutool/crypto/test/SmTest.java
@@ -81,4 +81,6 @@ public class SmTest {
String digest = hMac.digestHex(content);
Assert.assertEquals("493e3f9a1896b43075fbe54658076727960d69632ac6b6ed932195857a6840c6", digest);
}
+
+
}
diff --git a/hutool-crypto/src/test/java/cn/hutool/crypto/test/symmetric/Sm4StreamTest.java b/hutool-crypto/src/test/java/cn/hutool/crypto/test/symmetric/Sm4StreamTest.java
new file mode 100644
index 000000000..87ec2675c
--- /dev/null
+++ b/hutool-crypto/src/test/java/cn/hutool/crypto/test/symmetric/Sm4StreamTest.java
@@ -0,0 +1,51 @@
+package cn.hutool.crypto.test.symmetric;
+
+import cn.hutool.crypto.symmetric.SM4;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * https://gitee.com/dromara/hutool/issues/I4EMST
+ */
+public class Sm4StreamTest {
+
+ private static final SM4 sm4 = new SM4();
+
+ private static final boolean IS_CLOSE = false;
+
+ @Test
+ @Ignore
+ public void sm4Test(){
+ String source = "d:/test/sm4_1.txt";
+ String target = "d:/test/sm4_2.data";
+ String target2 = "d:/test/sm4_3.txt";
+ encrypt(source, target);
+ decrypt(target, target2);
+ }
+
+ public static void encrypt(String source, String target) {
+ try (InputStream input = new FileInputStream(source);
+ OutputStream out = new FileOutputStream(target)) {
+ sm4.encrypt(input, out, IS_CLOSE);
+ System.out.println("============encrypt end");
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public static void decrypt(String source, String target) {
+ try (InputStream input = new FileInputStream(source);
+ OutputStream out = new FileOutputStream(target)) {
+ sm4.decrypt(input, out, IS_CLOSE);
+ System.out.println("============decrypt end");
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml
index e5f1f2414..99574b86d 100644
--- a/hutool-db/pom.xml
+++ b/hutool-db/pom.xml
@@ -9,7 +9,7 @@
cn.hutool
hutool-parent
- 5.7.14-SNAPSHOT
+ 5.7.16-SNAPSHOT
hutool-db
@@ -143,13 +143,13 @@
mysql
mysql-connector-java
- 8.0.26
+ 8.0.27
test
org.postgresql
postgresql
- 42.2.23.jre7
+ 42.3.0
test
diff --git a/hutool-db/src/main/java/cn/hutool/db/Db.java b/hutool-db/src/main/java/cn/hutool/db/Db.java
index 4af1b1dcb..f1b3f23aa 100644
--- a/hutool-db/src/main/java/cn/hutool/db/Db.java
+++ b/hutool-db/src/main/java/cn/hutool/db/Db.java
@@ -1,10 +1,5 @@
package cn.hutool.db;
-import java.sql.Connection;
-import java.sql.SQLException;
-
-import javax.sql.DataSource;
-
import cn.hutool.core.lang.func.VoidFunc1;
import cn.hutool.db.dialect.Dialect;
import cn.hutool.db.dialect.DialectFactory;
@@ -13,10 +8,14 @@ import cn.hutool.db.sql.Wrapper;
import cn.hutool.db.transaction.TransactionLevel;
import cn.hutool.log.StaticLog;
+import javax.sql.DataSource;
+import java.sql.Connection;
+import java.sql.SQLException;
+
/**
* 数据库操作类
* 通过给定的数据源执行给定SQL或者给定数据源和方言,执行相应的CRUD操作
- *
+ *
* @author Looly
* @since 4.1.2
*/
@@ -26,7 +25,7 @@ public class Db extends AbstractDb {
/**
* 创建Db
* 使用默认数据源,自动探测数据库连接池
- *
+ *
* @return Db
*/
public static Db use() {
@@ -36,7 +35,7 @@ public class Db extends AbstractDb {
/**
* 创建Db
* 使用默认数据源,自动探测数据库连接池
- *
+ *
* @param group 数据源分组
* @return Db
*/
@@ -47,7 +46,7 @@ public class Db extends AbstractDb {
/**
* 创建Db
* 会根据数据源连接的元信息识别目标数据库类型,进而使用合适的数据源
- *
+ *
* @param ds 数据源
* @return Db
*/
@@ -57,7 +56,7 @@ public class Db extends AbstractDb {
/**
* 创建Db
- *
+ *
* @param ds 数据源
* @param dialect 方言
* @return Db
@@ -68,7 +67,7 @@ public class Db extends AbstractDb {
/**
* 创建Db
- *
+ *
* @param ds 数据源
* @param driverClassName 数据库连接驱动类名
* @return Db
@@ -80,7 +79,7 @@ public class Db extends AbstractDb {
// ---------------------------------------------------------------------------- Constructor start
/**
* 构造,从DataSource中识别方言
- *
+ *
* @param ds 数据源
*/
public Db(DataSource ds) {
@@ -89,7 +88,7 @@ public class Db extends AbstractDb {
/**
* 构造
- *
+ *
* @param ds 数据源
* @param driverClassName 数据库连接驱动类名,用于识别方言
*/
@@ -99,7 +98,7 @@ public class Db extends AbstractDb {
/**
* 构造
- *
+ *
* @param ds 数据源
* @param dialect 方言
*/
@@ -118,7 +117,7 @@ public class Db extends AbstractDb {
public Db setWrapper(Wrapper wrapper) {
return (Db) super.setWrapper(wrapper);
}
-
+
@Override
public Db disableWrapper() {
return (Db)super.disableWrapper();
@@ -147,7 +146,7 @@ public class Db extends AbstractDb {
/**
* 执行事务,使用默认的事务级别
* 在同一事务中,所有对数据库操作都是原子的,同时提交或者同时回滚
- *
+ *
* @param func 事务函数,所有操作应在同一函数下执行,确保在同一事务中
* @return this
* @throws SQLException SQL异常
@@ -159,7 +158,7 @@ public class Db extends AbstractDb {
/**
* 执行事务
* 在同一事务中,所有对数据库操作都是原子的,同时提交或者同时回滚
- *
+ *
* @param transactionLevel 事务级别枚举,null表示使用JDBC默认事务
* @param func 事务函数,所有操作应在同一函数下执行,确保在同一事务中
* @return this
@@ -208,7 +207,7 @@ public class Db extends AbstractDb {
// ---------------------------------------------------------------------------- Private method start
/**
* 静默回滚事务
- *
+ *
* @param conn Connection
*/
private void quietRollback(Connection conn) {
@@ -223,12 +222,12 @@ public class Db extends AbstractDb {
/**
* 静默设置自动提交
- *
+ *
* @param conn Connection
* @param autoCommit 是否自动提交
*/
private void quietSetAutoCommit(Connection conn, Boolean autoCommit) {
- if (null != autoCommit) {
+ if (null != conn && null != autoCommit) {
try {
conn.setAutoCommit(autoCommit);
} catch (Exception e) {
@@ -237,4 +236,4 @@ public class Db extends AbstractDb {
}
}
// ---------------------------------------------------------------------------- Private method end
-}
\ No newline at end of file
+}
diff --git a/hutool-dfa/pom.xml b/hutool-dfa/pom.xml
index 87ea5c8e1..2e73f8080 100644
--- a/hutool-dfa/pom.xml
+++ b/hutool-dfa/pom.xml
@@ -9,7 +9,7 @@
cn.hutool
hutool-parent
- 5.7.14-SNAPSHOT
+ 5.7.16-SNAPSHOT
hutool-dfa
diff --git a/hutool-extra/pom.xml b/hutool-extra/pom.xml
index 5e0c1edfe..fd49b4779 100644
--- a/hutool-extra/pom.xml
+++ b/hutool-extra/pom.xml
@@ -9,7 +9,7 @@
cn.hutool
hutool-parent
- 5.7.14-SNAPSHOT
+ 5.7.16-SNAPSHOT
hutool-extra
@@ -19,7 +19,7 @@
2.3
- 3.6.1.RELEASE
+ 3.7.0.RELEASE
1.4.1
2.3.31
4.9.16
@@ -27,10 +27,10 @@
1.6.2
0.1.55
3.4.1
- 3.7.2
+ 3.8.0
5.1.1
4.0.1
- 2.5.4
+ 2.5.6
3.3.0
@@ -241,7 +241,7 @@
org.apache.lucene
lucene-analyzers-smartcn
- 8.9.0
+ 8.10.1
true
@@ -326,7 +326,7 @@
com.github.houbb
pinyin
- 0.2.1
+ 0.2.2
true
@@ -418,7 +418,7 @@
org.mvel
mvel2
- 2.4.12.Final
+ 2.4.13.Final
compile
true
@@ -432,7 +432,7 @@
org.springframework
spring-expression
- 5.3.10
+ 5.3.12
compile
true
diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java b/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java
index a74af7073..f94abd94c 100644
--- a/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java
+++ b/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java
@@ -470,9 +470,9 @@ public class Ftp extends AbstractFtp {
* 上传文件到指定目录,可选:
*
*
- * 1. path为null或""上传到当前路径
- * 2. path为相对路径则相对于当前路径的子路径
- * 3. path为绝对路径则上传到此路径
+ * 1. destPath为null或""上传到当前路径
+ * 2. destPath为相对路径则相对于当前路径的子路径
+ * 3. destPath为绝对路径则上传到此路径
*
*
* @param destPath 服务端路径,可以为{@code null} 或者相对路径或绝对路径
@@ -489,20 +489,20 @@ public class Ftp extends AbstractFtp {
* 上传文件到指定目录,可选:
*
*
- * 1. path为null或""上传到当前路径
- * 2. path为相对路径则相对于当前路径的子路径
- * 3. path为绝对路径则上传到此路径
+ * 1. destPath为null或""上传到当前路径
+ * 2. destPath为相对路径则相对于当前路径的子路径
+ * 3. destPath为绝对路径则上传到此路径
*
*
* @param file 文件
- * @param path 服务端路径,可以为{@code null} 或者相对路径或绝对路径
+ * @param destPath 服务端路径,可以为{@code null} 或者相对路径或绝对路径
* @param fileName 自定义在服务端保存的文件名
* @return 是否上传成功
* @throws IORuntimeException IO异常
*/
- public boolean upload(String path, String fileName, File file) throws IORuntimeException {
+ public boolean upload(String destPath, String fileName, File file) throws IORuntimeException {
try (InputStream in = FileUtil.getInputStream(file)) {
- return upload(path, fileName, in);
+ return upload(destPath, fileName, in);
} catch (IOException e) {
throw new IORuntimeException(e);
}
@@ -512,18 +512,18 @@ public class Ftp extends AbstractFtp {
* 上传文件到指定目录,可选:
*
*
- * 1. path为null或""上传到当前路径
- * 2. path为相对路径则相对于当前路径的子路径
- * 3. path为绝对路径则上传到此路径
+ * 1. destPath为null或""上传到当前路径
+ * 2. destPath为相对路径则相对于当前路径的子路径
+ * 3. destPath为绝对路径则上传到此路径
*
*
- * @param path 服务端路径,可以为{@code null} 或者相对路径或绝对路径
+ * @param destPath 服务端路径,可以为{@code null} 或者相对路径或绝对路径
* @param fileName 文件名
* @param fileStream 文件流
* @return 是否上传成功
* @throws IORuntimeException IO异常
*/
- public boolean upload(String path, String fileName, InputStream fileStream) throws IORuntimeException {
+ public boolean upload(String destPath, String fileName, InputStream fileStream) throws IORuntimeException {
try {
client.setFileType(FTPClient.BINARY_FILE_TYPE);
} catch (IOException e) {
@@ -535,10 +535,10 @@ public class Ftp extends AbstractFtp {
pwd = pwd();
}
- if (StrUtil.isNotBlank(path)) {
- mkDirs(path);
- if (false == isDir(path)) {
- throw new FtpException("Change dir to [{}] error, maybe dir not exist!", path);
+ if (StrUtil.isNotBlank(destPath)) {
+ mkDirs(destPath);
+ if (false == isDir(destPath)) {
+ throw new FtpException("Change dir to [{}] error, maybe dir not exist!", destPath);
}
}
diff --git a/hutool-extra/src/main/java/cn/hutool/extra/mail/InternalMailUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/mail/InternalMailUtil.java
index a4e015cf3..64eddb20a 100644
--- a/hutool-extra/src/main/java/cn/hutool/extra/mail/InternalMailUtil.java
+++ b/hutool-extra/src/main/java/cn/hutool/extra/mail/InternalMailUtil.java
@@ -17,11 +17,11 @@ import java.util.List;
* @since 3.2.3
*/
public class InternalMailUtil {
-
+
/**
* 将多个字符串邮件地址转为{@link InternetAddress}列表
* 单个字符串地址可以是多个地址合并的字符串
- *
+ *
* @param addrStrs 地址数组
* @param charset 编码(主要用于中文用户名的编码)
* @return 地址数组
@@ -38,12 +38,12 @@ public class InternalMailUtil {
}
return resultList.toArray(new InternetAddress[0]);
}
-
+
/**
* 解析第一个地址
- *
+ *
* @param address 地址字符串
- * @param charset 编码
+ * @param charset 编码,{@code null}表示使用系统属性定义的编码或系统编码
* @return 地址列表
*/
public static InternetAddress parseFirstAddress(String address, Charset charset) {
@@ -61,9 +61,9 @@ public class InternalMailUtil {
/**
* 将一个地址字符串解析为多个地址
* 地址间使用" "、","、";"分隔
- *
+ *
* @param address 地址字符串
- * @param charset 编码
+ * @param charset 编码,{@code null}表示使用系统属性定义的编码或系统编码
* @return 地址列表
*/
public static InternetAddress[] parseAddress(String address, Charset charset) {
@@ -75,9 +75,10 @@ public class InternalMailUtil {
}
//编码用户名
if (ArrayUtil.isNotEmpty(addresses)) {
+ final String charsetStr = null == charset ? null : charset.name();
for (InternetAddress internetAddress : addresses) {
try {
- internetAddress.setPersonal(internetAddress.getPersonal(), charset.name());
+ internetAddress.setPersonal(internetAddress.getPersonal(), charsetStr);
} catch (UnsupportedEncodingException e) {
throw new MailException(e);
}
@@ -90,7 +91,7 @@ public class InternalMailUtil {
/**
* 编码中文字符
* 编码失败返回原字符串
- *
+ *
* @param text 被编码的文本
* @param charset 编码
* @return 编码后的结果
diff --git a/hutool-extra/src/main/java/cn/hutool/extra/mail/Mail.java b/hutool-extra/src/main/java/cn/hutool/extra/mail/Mail.java
index 1f28d16a9..2d71b2dcc 100644
--- a/hutool-extra/src/main/java/cn/hutool/extra/mail/Mail.java
+++ b/hutool-extra/src/main/java/cn/hutool/extra/mail/Mail.java
@@ -21,6 +21,7 @@ import javax.mail.Transport;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
+import javax.mail.internet.MimeUtility;
import javax.mail.util.ByteArrayDataSource;
import java.io.File;
import java.io.IOException;
@@ -36,6 +37,7 @@ import java.util.Date;
* @since 3.2.0
*/
public class Mail implements Builder {
+ private static final long serialVersionUID = 1L;
/**
* 邮箱帐户信息以及一些客户端配置信息
@@ -262,7 +264,10 @@ public class Mail implements Builder {
for (DataSource attachment : attachments) {
bodyPart = new MimeBodyPart();
bodyPart.setDataHandler(new DataHandler(attachment));
- nameEncoded = InternalMailUtil.encodeText(attachment.getName(), charset);
+ nameEncoded = attachment.getName();
+ if (this.mailAccount.isEncodefilename()) {
+ nameEncoded = InternalMailUtil.encodeText(nameEncoded, charset);
+ }
// 普通附件文件名
bodyPart.setFileName(nameEncoded);
if (StrUtil.startWith(attachment.getContentType(), "image/")) {
@@ -384,7 +389,7 @@ public class Mail implements Builder {
try {
return doSend();
} catch (MessagingException e) {
- if(e instanceof SendFailedException){
+ if (e instanceof SendFailedException) {
// 当地址无效时,显示更加详细的无效地址信息
final Address[] invalidAddresses = ((SendFailedException) e).getInvalidAddresses();
final String msg = StrUtil.format("Invalid Addresses: {}", ArrayUtil.toString(invalidAddresses));
@@ -426,7 +431,7 @@ public class Mail implements Builder {
msg.setFrom(InternalMailUtil.parseFirstAddress(from, charset));
}
// 标题
- msg.setSubject(this.title, charset.name());
+ msg.setSubject(this.title, (null == charset) ? null : charset.name());
// 发送时间
msg.setSentDate(new Date());
// 内容和附件
@@ -452,14 +457,15 @@ public class Mail implements Builder {
/**
* 构建邮件信息主体
*
- * @param charset 编码
+ * @param charset 编码,{@code null}则使用{@link MimeUtility#getDefaultJavaCharset()}
* @return 邮件信息主体
* @throws MessagingException 消息异常
*/
private Multipart buildContent(Charset charset) throws MessagingException {
+ final String charsetStr = null != charset ? charset.name() : MimeUtility.getDefaultJavaCharset();
// 正文
final MimeBodyPart body = new MimeBodyPart();
- body.setContent(content, StrUtil.format("text/{}; charset={}", isHtml ? "html" : "plain", charset));
+ body.setContent(content, StrUtil.format("text/{}; charset={}", isHtml ? "html" : "plain", charsetStr));
this.multipart.addBodyPart(body);
return this.multipart;
@@ -474,7 +480,7 @@ public class Mail implements Builder {
private Session getSession() {
final Session session = MailUtil.getSession(this.mailAccount, this.useGlobalSession);
- if(null != this.debugOutput){
+ if (null != this.debugOutput) {
session.setDebugOut(debugOutput);
}
diff --git a/hutool-extra/src/main/java/cn/hutool/extra/mail/MailAccount.java b/hutool-extra/src/main/java/cn/hutool/extra/mail/MailAccount.java
index 925550241..6cff1777d 100644
--- a/hutool-extra/src/main/java/cn/hutool/extra/mail/MailAccount.java
+++ b/hutool-extra/src/main/java/cn/hutool/extra/mail/MailAccount.java
@@ -34,8 +34,13 @@ public class MailAccount implements Serializable {
private static final String SOCKET_FACTORY_FALLBACK = "mail.smtp.socketFactory.fallback";
private static final String SOCKET_FACTORY_PORT = "smtp.socketFactory.port";
- private static final String MAIL_DEBUG = "mail.debug";
+ // System Properties
private static final String SPLIT_LONG_PARAMS = "mail.mime.splitlongparameters";
+ //private static final String ENCODE_FILE_NAME = "mail.mime.encodefilename";
+ //private static final String CHARSET = "mail.mime.charset";
+
+ // 其他
+ private static final String MAIL_DEBUG = "mail.debug";
public static final String[] MAIL_SETTING_PATHS = new String[]{"config/mail.setting", "config/mailAccount.setting", "mail.setting"};
@@ -75,7 +80,11 @@ public class MailAccount implements Serializable {
/**
* 对于超长参数是否切分为多份,默认为false(国内邮箱附件不支持切分的附件名)
*/
- private boolean splitlongparameters;
+ private boolean splitlongparameters = false;
+ /**
+ * 对于文件名是否使用{@link #charset}编码,默认为 {@code true}
+ */
+ private boolean encodefilename = true;
/**
* 使用 STARTTLS安全连接,STARTTLS是对纯文本通信协议的扩展。它将纯文本连接升级为加密连接(TLS或SSL), 而不是使用一个单独的加密通信端口。
@@ -297,16 +306,19 @@ public class MailAccount implements Serializable {
/**
* 获取字符集编码
*
- * @return 编码
+ * @return 编码,可能为{@code null}
*/
public Charset getCharset() {
return charset;
}
/**
- * 设置字符集编码
+ * 设置字符集编码,此选项不会修改全局配置,若修改全局配置,请设置此项为{@code null}并设置:
+ *
+ * System.setProperty("mail.mime.charset", charset);
+ *
*
- * @param charset 字符集编码
+ * @param charset 字符集编码,{@code null} 则表示使用全局设置的默认编码,全局编码为mail.mime.charset系统属性
* @return this
*/
public MailAccount setCharset(Charset charset) {
@@ -324,7 +336,11 @@ public class MailAccount implements Serializable {
}
/**
- * 设置对于超长参数是否切分为多份,默认为false(国内邮箱附件不支持切分的附件名)
+ * 设置对于超长参数是否切分为多份,默认为false(国内邮箱附件不支持切分的附件名)
+ * 注意此项为全局设置,此项会调用
+ *
+ * System.setProperty("mail.mime.splitlongparameters", true)
+ *
*
* @param splitlongparameters 对于超长参数是否切分为多份
*/
@@ -332,6 +348,32 @@ public class MailAccount implements Serializable {
this.splitlongparameters = splitlongparameters;
}
+ /**
+ * 对于文件名是否使用{@link #charset}编码,默认为 {@code true}
+ *
+ * @return 对于文件名是否使用{@link #charset}编码,默认为 {@code true}
+ * @since 5.7.16
+ */
+ public boolean isEncodefilename() {
+
+ return encodefilename;
+ }
+
+ /**
+ * 设置对于文件名是否使用{@link #charset}编码,此选项不会修改全局配置
+ * 如果此选项设置为{@code false},则是否编码取决于两个系统属性:
+ *
+ * - mail.mime.encodefilename 是否编码附件文件名
+ * - mail.mime.charset 编码文件名的编码
+ *
+ *
+ * @param encodefilename 对于文件名是否使用{@link #charset}编码
+ * @since 5.7.16
+ */
+ public void setEncodefilename(boolean encodefilename) {
+ this.encodefilename = encodefilename;
+ }
+
/**
* 是否使用 STARTTLS安全连接,STARTTLS是对纯文本通信协议的扩展。它将纯文本连接升级为加密连接(TLS或SSL), 而不是使用一个单独的加密通信端口。
*
@@ -374,6 +416,7 @@ public class MailAccount implements Serializable {
/**
* 获取SSL协议,多个协议用空格分隔
+ *
* @return SSL协议,多个协议用空格分隔
* @since 5.5.7
*/
diff --git a/hutool-extra/src/main/java/cn/hutool/extra/qrcode/QrCodeUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/qrcode/QrCodeUtil.java
index f9b800282..36e048d2b 100644
--- a/hutool-extra/src/main/java/cn/hutool/extra/qrcode/QrCodeUtil.java
+++ b/hutool-extra/src/main/java/cn/hutool/extra/qrcode/QrCodeUtil.java
@@ -313,9 +313,10 @@ public class QrCodeUtil {
// 默认配置
config = new QrConfig();
}
+
BitMatrix bitMatrix;
try {
- bitMatrix = multiFormatWriter.encode(content, format, config.width, config.height, config.toHints());
+ bitMatrix = multiFormatWriter.encode(content, format, config.width, config.height, config.toHints(format));
} catch (WriterException e) {
throw new QrCodeException(e);
}
diff --git a/hutool-extra/src/main/java/cn/hutool/extra/qrcode/QrConfig.java b/hutool-extra/src/main/java/cn/hutool/extra/qrcode/QrConfig.java
index f9aae03e7..0c2789c87 100644
--- a/hutool-extra/src/main/java/cn/hutool/extra/qrcode/QrConfig.java
+++ b/hutool-extra/src/main/java/cn/hutool/extra/qrcode/QrConfig.java
@@ -3,6 +3,7 @@ package cn.hutool.extra.qrcode;
import cn.hutool.core.img.ImgUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.CharsetUtil;
+import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
@@ -331,13 +332,31 @@ public class QrConfig {
* @return 配置
*/
public HashMap toHints() {
+ return toHints(BarcodeFormat.QR_CODE);
+ }
+
+ /**
+ * 转换为Zxing的二维码配置
+ *
+ * @param format 格式,根据格式不同,{@link #errorCorrection}的值类型有所不同
+ * @return 配置
+ */
+ public HashMap toHints(BarcodeFormat format) {
// 配置
final HashMap hints = new HashMap<>();
if (null != this.charset) {
hints.put(EncodeHintType.CHARACTER_SET, charset.toString().toLowerCase());
}
if (null != this.errorCorrection) {
- hints.put(EncodeHintType.ERROR_CORRECTION, this.errorCorrection);
+ Object value;
+ if(BarcodeFormat.AZTEC == format || BarcodeFormat.PDF_417 == format){
+ // issue#I4FE3U@Gitee
+ value = this.errorCorrection.getBits();
+ } else {
+ value = this.errorCorrection;
+ }
+
+ hints.put(EncodeHintType.ERROR_CORRECTION, value);
}
if (null != this.margin) {
hints.put(EncodeHintType.MARGIN, this.margin);
diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ssh/Sftp.java b/hutool-extra/src/main/java/cn/hutool/extra/ssh/Sftp.java
index a38b0c897..c553b33c8 100644
--- a/hutool-extra/src/main/java/cn/hutool/extra/ssh/Sftp.java
+++ b/hutool-extra/src/main/java/cn/hutool/extra/ssh/Sftp.java
@@ -17,6 +17,7 @@ import com.jcraft.jsch.SftpException;
import com.jcraft.jsch.SftpProgressMonitor;
import java.io.File;
+import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.ArrayList;
@@ -337,7 +338,7 @@ public class Sftp extends AbstractFtp {
try {
sftpATTRS = this.channel.stat(dir);
} catch (SftpException e) {
- if(e.getMessage().contains("No such file")){
+ if (e.getMessage().contains("No such file")) {
// 文件不存在直接返回false
// pr#378@Gitee
return false;
@@ -464,6 +465,27 @@ public class Sftp extends AbstractFtp {
return true;
}
+ /**
+ * 上传文件到指定目录,可选:
+ *
+ *
+ * 1. path为null或""上传到当前路径
+ * 2. path为相对路径则相对于当前路径的子路径
+ * 3. path为绝对路径则上传到此路径
+ *
+ *
+ * @param destPath 服务端路径,可以为{@code null} 或者相对路径或绝对路径
+ * @param fileName 文件名
+ * @param fileStream 文件流
+ * @return 是否上传成功
+ * @since 5.7.16
+ */
+ public boolean upload(String destPath, String fileName, InputStream fileStream) {
+ destPath = StrUtil.addSuffixIfNot(destPath, StrUtil.SLASH) + StrUtil.removePrefix(fileName, StrUtil.SLASH);
+ put(fileStream, destPath, null, Mode.OVERWRITE);
+ return true;
+ }
+
/**
* 将本地文件上传到目标服务器,目标文件名为destPath,若destPath为目录,则目标文件名将与srcFilePath文件名相同。覆盖模式
*
@@ -506,6 +528,25 @@ public class Sftp extends AbstractFtp {
return this;
}
+ /**
+ * 将本地数据流上传到目标服务器,目标文件名为destPath,目标必须为文件
+ *
+ * @param srcStream 本地的数据流
+ * @param destPath 目标路径,
+ * @param monitor 上传进度监控,通过实现此接口完成进度显示
+ * @param mode {@link Mode} 模式
+ * @return this
+ * @since 5.7.16
+ */
+ public Sftp put(InputStream srcStream, String destPath, SftpProgressMonitor monitor, Mode mode) {
+ try {
+ channel.put(srcStream, destPath, monitor, mode.ordinal());
+ } catch (SftpException e) {
+ throw new JschRuntimeException(e);
+ }
+ return this;
+ }
+
@Override
public void download(String src, File destFile) {
get(src, FileUtil.getAbsolutePath(destFile));
diff --git a/hutool-extra/src/test/java/cn/hutool/extra/ftp/FtpTest.java b/hutool-extra/src/test/java/cn/hutool/extra/ftp/FtpTest.java
index 6265b6ca0..1cba5c565 100644
--- a/hutool-extra/src/test/java/cn/hutool/extra/ftp/FtpTest.java
+++ b/hutool-extra/src/test/java/cn/hutool/extra/ftp/FtpTest.java
@@ -7,8 +7,6 @@ import cn.hutool.extra.ssh.Sftp;
import org.junit.Ignore;
import org.junit.Test;
-import java.util.List;
-
public class FtpTest {
@Test
@@ -25,12 +23,9 @@ public class FtpTest {
@Test
@Ignore
public void uploadTest() {
- Ftp ftp = new Ftp("looly.centos");
+ Ftp ftp = new Ftp("localhost");
- List ls = ftp.ls("/file");
- Console.log(ls);
-
- boolean upload = ftp.upload("/file/aaa", FileUtil.file("E:/qrcodeWithLogo.jpg"));
+ boolean upload = ftp.upload("/temp", FileUtil.file("d:/test/test.zip"));
Console.log(upload);
IoUtil.close(ftp);
diff --git a/hutool-extra/src/test/java/cn/hutool/extra/qrcode/QrCodeUtilTest.java b/hutool-extra/src/test/java/cn/hutool/extra/qrcode/QrCodeUtilTest.java
index cfb5dd251..7f31d796f 100644
--- a/hutool-extra/src/test/java/cn/hutool/extra/qrcode/QrCodeUtilTest.java
+++ b/hutool-extra/src/test/java/cn/hutool/extra/qrcode/QrCodeUtilTest.java
@@ -4,6 +4,7 @@ import cn.hutool.core.codec.Base64;
import cn.hutool.core.img.ImgUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.Console;
+import com.google.zxing.BarcodeFormat;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import org.junit.Assert;
import org.junit.Ignore;
@@ -87,4 +88,10 @@ public class QrCodeUtilTest {
final String decode = QrCodeUtil.decode(ImgUtil.read("d:/test/qr_a.png"), false, true);
Console.log(decode);
}
+
+ @Test
+ public void pdf417Test(){
+ final BufferedImage image = QrCodeUtil.generate("content111", BarcodeFormat.PDF_417, QrConfig.create());
+ Assert.assertNotNull(image);
+ }
}
diff --git a/hutool-extra/src/test/resources/config/mail.setting b/hutool-extra/src/test/resources/config/mail.setting
index a7aa08342..f9a9cf720 100644
--- a/hutool-extra/src/test/resources/config/mail.setting
+++ b/hutool-extra/src/test/resources/config/mail.setting
@@ -20,3 +20,7 @@ starttlsEnable = true
sslEnable = true
# 调试模式
debug = true
+# 对于超长参数是否切分为多份,默认为false(国内邮箱附件不支持切分的附件名)
+splitlongparameters = false
+# 是否编码附件文件名(默认true)
+encodefilename = true
diff --git a/hutool-http/pom.xml b/hutool-http/pom.xml
index a6906caff..b86a35819 100644
--- a/hutool-http/pom.xml
+++ b/hutool-http/pom.xml
@@ -9,7 +9,7 @@
cn.hutool
hutool-parent
- 5.7.14-SNAPSHOT
+ 5.7.16-SNAPSHOT
hutool-http
diff --git a/hutool-http/src/main/java/cn/hutool/http/ContentType.java b/hutool-http/src/main/java/cn/hutool/http/ContentType.java
index d6d998267..29064428c 100644
--- a/hutool-http/src/main/java/cn/hutool/http/ContentType.java
+++ b/hutool-http/src/main/java/cn/hutool/http/ContentType.java
@@ -45,6 +45,7 @@ public enum ContentType {
/**
* 构造
+ *
* @param value ContentType值
*/
ContentType(String value) {
@@ -141,4 +142,16 @@ public enum ContentType {
public static String build(String contentType, Charset charset) {
return StrUtil.format("{};charset={}", contentType, charset.name());
}
+
+ /**
+ * 输出Content-Type字符串,附带编码信息
+ *
+ * @param contentType Content-Type 枚举类型
+ * @param charset 编码
+ * @return Content-Type字符串
+ * @since 5.7.15
+ */
+ public static String build(ContentType contentType, Charset charset) {
+ return build(contentType.getValue(), charset);
+ }
}
diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpResponse.java b/hutool-http/src/main/java/cn/hutool/http/HttpResponse.java
index eda670ac5..f64f1ecd7 100644
--- a/hutool-http/src/main/java/cn/hutool/http/HttpResponse.java
+++ b/hutool-http/src/main/java/cn/hutool/http/HttpResponse.java
@@ -148,9 +148,9 @@ public class HttpResponse extends HttpBase implements Closeable {
}
/**
- * 是否为zlib(Defalte)压缩过的内容
+ * 是否为zlib(Deflate)压缩过的内容
*
- * @return 是否为zlib(Defalte)压缩过的内容
+ * @return 是否为zlib(Deflate)压缩过的内容
* @since 4.5.7
*/
public boolean isDeflate() {
diff --git a/hutool-http/src/main/java/cn/hutool/http/useragent/Engine.java b/hutool-http/src/main/java/cn/hutool/http/useragent/Engine.java
index ed6e57b2d..358a8e272 100644
--- a/hutool-http/src/main/java/cn/hutool/http/useragent/Engine.java
+++ b/hutool-http/src/main/java/cn/hutool/http/useragent/Engine.java
@@ -21,16 +21,16 @@ public class Engine extends UserAgentInfo {
/**
* 支持的引擎类型
*/
- public static final List engines = CollUtil.newArrayList(//
- new Engine("Trident", "trident"), //
- new Engine("Webkit", "webkit"), //
- new Engine("Chrome", "chrome"), //
- new Engine("Opera", "opera"), //
- new Engine("Presto", "presto"), //
- new Engine("Gecko", "gecko"), //
- new Engine("KHTML", "khtml"), //
- new Engine("Konqeror", "konqueror"), //
- new Engine("MIDP", "MIDP")//
+ public static final List engines = CollUtil.newArrayList(
+ new Engine("Trident", "trident"),
+ new Engine("Webkit", "webkit"),
+ new Engine("Chrome", "chrome"),
+ new Engine("Opera", "opera"),
+ new Engine("Presto", "presto"),
+ new Engine("Gecko", "gecko"),
+ new Engine("KHTML", "khtml"),
+ new Engine("Konqueror", "konqueror"),
+ new Engine("MIDP", "MIDP")
);
private final Pattern versionPattern;
diff --git a/hutool-http/src/test/java/cn/hutool/http/ContentTypeTest.java b/hutool-http/src/test/java/cn/hutool/http/ContentTypeTest.java
new file mode 100644
index 000000000..1d03c44eb
--- /dev/null
+++ b/hutool-http/src/test/java/cn/hutool/http/ContentTypeTest.java
@@ -0,0 +1,19 @@
+package cn.hutool.http;
+
+import cn.hutool.core.util.CharsetUtil;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * ContentType 单元测试
+ *
+ *
+ */
+public class ContentTypeTest {
+
+ @Test
+ public void testBuild() {
+ String result = ContentType.build(ContentType.JSON, CharsetUtil.CHARSET_UTF_8);
+ Assert.assertEquals("application/json;charset=UTF-8", result);
+ }
+}
diff --git a/hutool-http/src/test/java/cn/hutool/http/webservice/SoapClientTest.java b/hutool-http/src/test/java/cn/hutool/http/webservice/SoapClientTest.java
index ffd137bc9..52b46791b 100644
--- a/hutool-http/src/test/java/cn/hutool/http/webservice/SoapClientTest.java
+++ b/hutool-http/src/test/java/cn/hutool/http/webservice/SoapClientTest.java
@@ -10,7 +10,7 @@ import javax.xml.soap.SOAPMessage;
/**
* SOAP相关单元测试
- *
+ *
* @author looly
*
*/
@@ -23,19 +23,19 @@ public class SoapClientTest {
.setMethod("web:getCountryCityByIp", "http://WebXml.com.cn/")
.setCharset(CharsetUtil.CHARSET_GBK)
.setParam("theIpAddress", "218.21.240.106");
-
+
Console.log(client.getMsgStr(true));
-
+
Console.log(client.send(true));
}
-
+
@Test
@Ignore
public void requestForMessageTest() throws SOAPException {
SoapClient client = SoapClient.create("http://www.webxml.com.cn/WebServices/IpAddressSearchWebService.asmx")
.setMethod("web:getCountryCityByIp", "http://WebXml.com.cn/")
.setParam("theIpAddress", "218.21.240.106");
-
+
SOAPMessage message = client.sendForMessage();
Console.log(message.getSOAPBody().getTextContent());
}
diff --git a/hutool-json/pom.xml b/hutool-json/pom.xml
index 8ea5bd302..7a94609b1 100644
--- a/hutool-json/pom.xml
+++ b/hutool-json/pom.xml
@@ -9,7 +9,7 @@
cn.hutool
hutool-parent
- 5.7.14-SNAPSHOT
+ 5.7.16-SNAPSHOT
hutool-json
diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONArray.java b/hutool-json/src/main/java/cn/hutool/json/JSONArray.java
index fdfafabc7..df5f34f13 100644
--- a/hutool-json/src/main/java/cn/hutool/json/JSONArray.java
+++ b/hutool-json/src/main/java/cn/hutool/json/JSONArray.java
@@ -3,6 +3,8 @@ package cn.hutool.json;
import cn.hutool.core.bean.BeanPath;
import cn.hutool.core.collection.ArrayIter;
import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.lang.Filter;
+import cn.hutool.core.lang.mutable.MutablePair;
import cn.hutool.core.text.StrJoiner;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
@@ -12,6 +14,7 @@ import cn.hutool.json.serialize.GlobalSerializeMapping;
import cn.hutool.json.serialize.JSONSerializer;
import cn.hutool.json.serialize.JSONWriter;
+import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collection;
@@ -443,13 +446,13 @@ public class JSONArray implements JSON, JSONGetter, List