mirror of
https://gitee.com/chinabugotech/hutool.git
synced 2025-05-09 23:51:34 +08:00
Merge remote-tracking branch 'origin/v5-dev' into v5-dev
# Conflicts: # hutool-core/src/main/java/cn/hutool/core/text/CharSequenceUtil.java
This commit is contained in:
commit
27b69908c8
71
.github/codeql-analysis.yml
vendored
Normal file
71
.github/codeql-analysis.yml
vendored
Normal file
@ -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
|
61
CHANGELOG.md
61
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)
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
43
README-EN.md
43
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
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
<version>5.7.14</version>
|
||||
<version>5.7.16</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
### 🍐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
|
||||
|
||||

|
||||
<div align="center">
|
||||
<img src="https://cdn.jsdelivr.net/gh/looly/hutool-site/images/qr_tuling.jpg" height="150">
|
||||
<img src="https://dromara.org/img/qrcode/qrcode_1.png" height="150">
|
||||
</div>
|
49
README.md
49
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的存在就是为了减少代码搜索成本,避免网络上参差不
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
<version>5.7.14</version>
|
||||
<version>5.7.16</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
### 🍐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开源组织公众号
|
||||
|
||||

|
||||
<div align="center">
|
||||
<img src="https://cdn.jsdelivr.net/gh/looly/hutool-site/images/qr_tuling.jpg" height="150">
|
||||
<img src="https://dromara.org/img/qrcode/qrcode_1.png" height="150">
|
||||
</div>
|
15
SECURITY.md
Normal file
15
SECURITY.md
Normal file
@ -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`.
|
@ -1 +1 @@
|
||||
5.7.14
|
||||
5.7.16
|
||||
|
@ -1 +1 @@
|
||||
var version = '5.7.14'
|
||||
var version = '5.7.16'
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.7.14-SNAPSHOT</version>
|
||||
<version>5.7.16-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-all</artifactId>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.7.14-SNAPSHOT</version>
|
||||
<version>5.7.16-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-aop</artifactId>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.7.14-SNAPSHOT</version>
|
||||
<version>5.7.16-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-bloomFilter</artifactId>
|
||||
|
@ -20,10 +20,10 @@ public interface BloomFilter extends Serializable{
|
||||
|
||||
/**
|
||||
* 在boolean的bitMap中增加一个字符串<br>
|
||||
* 如果存在就返回<code>false</code> .如果不存在.先增加这个字符串.再返回<code>true</code>
|
||||
* 如果存在就返回{@code false} .如果不存在.先增加这个字符串.再返回{@code true}
|
||||
*
|
||||
* @param str 字符串
|
||||
* @return 是否加入成功,如果存在就返回<code>false</code> .如果不存在返回<code>true</code>
|
||||
* @return 是否加入成功,如果存在就返回{@code false} .如果不存在返回{@code true}
|
||||
*/
|
||||
boolean add(String str);
|
||||
}
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.7.14-SNAPSHOT</version>
|
||||
<version>5.7.16-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-bom</artifactId>
|
||||
@ -17,101 +17,182 @@
|
||||
<description>提供丰富的Java工具方法,此模块为Hutool所有模块汇总,最终形式为拆分开的多个jar包,可以通过exclude方式排除不需要的模块</description>
|
||||
<url>https://github.com/looly/hutool</url>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-core</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-aop</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-bloomFilter</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-cache</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-crypto</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-db</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-dfa</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-extra</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-http</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-log</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-script</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-setting</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-system</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-cron</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-json</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-poi</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-captcha</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-socket</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-jwt</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-core</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-aop</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-bloomFilter</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-cache</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-crypto</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-db</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-dfa</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-extra</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-http</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-log</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-script</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-setting</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-system</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-cron</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-json</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-poi</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-captcha</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-socket</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-jwt</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.7.14-SNAPSHOT</version>
|
||||
<version>5.7.16-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-cache</artifactId>
|
||||
|
@ -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;
|
||||
|
||||
/**
|
||||
* 超时和限制大小的缓存的默认实现<br>
|
||||
@ -31,11 +29,6 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
|
||||
|
||||
protected Map<K, CacheObj<K, V>> 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<K, V> implements Cache<K, V> {
|
||||
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<K, V> implements Cache<K, V> {
|
||||
* @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<K, V> co = new CacheObj<>(key, object, timeout);
|
||||
if (timeout != 0) {
|
||||
existCustomTimeout = true;
|
||||
@ -106,29 +89,6 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
|
||||
// ---------------------------------------------------------------- put end
|
||||
|
||||
// ---------------------------------------------------------------- get start
|
||||
@Override
|
||||
public boolean containsKey(K key) {
|
||||
final long stamp = lock.readLock();
|
||||
try {
|
||||
// 不存在或已移除
|
||||
final CacheObj<K, V> 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<K, V> implements Cache<K, V> {
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public V get(K key, boolean isUpdateLastAccess) {
|
||||
// 尝试读取缓存,使用乐观读锁
|
||||
long stamp = lock.tryOptimisticRead();
|
||||
CacheObj<K, V> 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<K, V> implements Cache<K, V> {
|
||||
CacheObjIterator<K, V> copiedIterator = (CacheObjIterator<K, V>) this.cacheObjIterator();
|
||||
return new CacheValuesIterator<>(copiedIterator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<CacheObj<K, V>> cacheObjIterator() {
|
||||
CopiedIter<CacheObj<K, V>> copiedIterator;
|
||||
final long stamp = lock.readLock();
|
||||
try {
|
||||
copiedIterator = CopiedIter.copyOf(this.cacheMap.values().iterator());
|
||||
} finally {
|
||||
lock.unlockRead(stamp);
|
||||
}
|
||||
return new CacheObjIterator<>(copiedIterator);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- prune start
|
||||
|
||||
/**
|
||||
* 清理实现<br>
|
||||
* 子类实现此方法时无需加锁
|
||||
@ -229,16 +145,6 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
|
||||
* @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<K, V> implements Cache<K, V> {
|
||||
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<K, V> implements Cache<K, V> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除key对应的对象
|
||||
*
|
||||
* @param key 键
|
||||
* @param withMissCount 是否计数丢失数
|
||||
*/
|
||||
private void remove(K key, boolean withMissCount) {
|
||||
final long stamp = lock.writeLock();
|
||||
CacheObj<K, V> 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<K, V> implements Cache<K, V> {
|
||||
* @param withMissCount 是否计数丢失数
|
||||
* @return 移除的对象,无返回null
|
||||
*/
|
||||
private CacheObj<K, V> removeWithoutLock(K key, boolean withMissCount) {
|
||||
protected CacheObj<K, V> removeWithoutLock(K key, boolean withMissCount) {
|
||||
final CacheObj<K, V> co = cacheMap.remove(key);
|
||||
if (withMissCount) {
|
||||
// 在丢失计数有效的情况下,移除一般为get时的超时操作,此处应该丢失数+1
|
||||
|
@ -16,7 +16,7 @@ import java.util.LinkedHashMap;
|
||||
* @param <V> 值类型
|
||||
* @author Looly
|
||||
*/
|
||||
public class FIFOCache<K, V> extends AbstractCache<K, V> {
|
||||
public class FIFOCache<K, V> extends StampedCache<K, V> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
|
@ -15,7 +15,7 @@ import java.util.Iterator;
|
||||
* @param <K> 键类型
|
||||
* @param <V> 值类型
|
||||
*/
|
||||
public class LFUCache<K, V> extends AbstractCache<K, V> {
|
||||
public class LFUCache<K, V> extends StampedCache<K, V> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
|
@ -16,7 +16,7 @@ import java.util.Iterator;
|
||||
* @param <K> 键类型
|
||||
* @param <V> 值类型
|
||||
*/
|
||||
public class LRUCache<K, V> extends AbstractCache<K, V> {
|
||||
public class LRUCache<K, V> extends ReentrantCache<K, V> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
|
136
hutool-cache/src/main/java/cn/hutool/cache/impl/ReentrantCache.java
vendored
Normal file
136
hutool-cache/src/main/java/cn/hutool/cache/impl/ReentrantCache.java
vendored
Normal file
@ -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无法使用读写锁的问题<br>
|
||||
* 例如使用了LinkedHashMap的缓存,由于get方法也会改变Map的结构,因此读写必须加互斥锁
|
||||
*
|
||||
* @param <K> 键类型
|
||||
* @param <V> 值类型
|
||||
* @author looly
|
||||
* @since 5.7.15
|
||||
*/
|
||||
public abstract class ReentrantCache<K, V> extends AbstractCache<K, V> {
|
||||
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<K, V> 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<K, V> 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<CacheObj<K, V>> cacheObjIterator() {
|
||||
CopiedIter<CacheObj<K, V>> 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<K, V> co;
|
||||
try {
|
||||
co = removeWithoutLock(key, withMissCount);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
if (null != co) {
|
||||
onRemove(co.key, co.obj);
|
||||
}
|
||||
}
|
||||
}
|
141
hutool-cache/src/main/java/cn/hutool/cache/impl/StampedCache.java
vendored
Normal file
141
hutool-cache/src/main/java/cn/hutool/cache/impl/StampedCache.java
vendored
Normal file
@ -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 <K> 键类型
|
||||
* @param <V> 值类型
|
||||
* @author looly
|
||||
* @since 5.7.15
|
||||
*/
|
||||
public abstract class StampedCache<K, V> extends AbstractCache<K, V>{
|
||||
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<K, V> 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<K, V> 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<CacheObj<K, V>> cacheObjIterator() {
|
||||
CopiedIter<CacheObj<K, V>> 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<K, V> co;
|
||||
try {
|
||||
co = removeWithoutLock(key, withMissCount);
|
||||
} finally {
|
||||
lock.unlockWrite(stamp);
|
||||
}
|
||||
if (null != co) {
|
||||
onRemove(co.key, co.obj);
|
||||
}
|
||||
}
|
||||
}
|
@ -16,7 +16,7 @@ import java.util.concurrent.ScheduledFuture;
|
||||
* @param <K> 键类型
|
||||
* @param <V> 值类型
|
||||
*/
|
||||
public class TimedCache<K, V> extends AbstractCache<K, V> {
|
||||
public class TimedCache<K, V> extends StampedCache<K, V> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 正在执行的定时任务 */
|
||||
|
52
hutool-cache/src/test/java/cn/hutool/cache/LRUCacheTest.java
vendored
Normal file
52
hutool-cache/src/test/java/cn/hutool/cache/LRUCacheTest.java
vendored
Normal file
@ -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<br>
|
||||
* 并发问题测试,在5.7.15前,LRUCache存在并发问题,多线程get后,map结构变更,导致null的位置不确定,
|
||||
* 并可能引起死锁。
|
||||
*/
|
||||
public class LRUCacheTest {
|
||||
|
||||
@Test
|
||||
public void readWriteTest() throws InterruptedException {
|
||||
LRUCache<Integer, Integer> 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());
|
||||
}
|
||||
}
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.7.14-SNAPSHOT</version>
|
||||
<version>5.7.16-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-captcha</artifactId>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.7.14-SNAPSHOT</version>
|
||||
<version>5.7.16-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-core</artifactId>
|
||||
|
@ -664,6 +664,32 @@ public class BeanUtil {
|
||||
).copy();
|
||||
}
|
||||
|
||||
/**
|
||||
* 对象转Map<br>
|
||||
* 通过自定义{@link CopyOptions} 完成抓换选项,以便实现:
|
||||
*
|
||||
* <pre>
|
||||
* 1. 字段筛选,可以去除不需要的字段
|
||||
* 2. 字段变换,例如实现驼峰转下划线
|
||||
* 3. 自定义字段前缀或后缀等等
|
||||
* 4. 字段值处理
|
||||
* ...
|
||||
* </pre>
|
||||
*
|
||||
* @param bean bean对象
|
||||
* @param targetMap 目标的Map
|
||||
* @param copyOptions 拷贝选项
|
||||
* @return Map
|
||||
* @since 5.7.15
|
||||
*/
|
||||
public static Map<String, Object> beanToMap(Object bean, Map<String, Object> targetMap, CopyOptions copyOptions) {
|
||||
if (null == bean) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return BeanCopier.create(bean, targetMap, copyOptions).copy();
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------------------------- copyProperties
|
||||
|
||||
/**
|
||||
|
@ -191,6 +191,10 @@ public class BeanCopier<T> implements Copier<T>, 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<T> implements Copier<T>, Serializable {
|
||||
return;
|
||||
}
|
||||
|
||||
// since 5.7.15
|
||||
value = copyOptions.editFieldValue(providerKey, value);
|
||||
|
||||
if ((null == value && copyOptions.ignoreNullValue) || bean == value) {
|
||||
// 当允许跳过空时,跳过
|
||||
// 值不能为bean本身,防止循环引用
|
||||
|
@ -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<String> fieldNameEditor;
|
||||
/**
|
||||
* 字段属性值编辑器,用于自定义属性值转换规则,例如null转""等
|
||||
*/
|
||||
protected BiFunction<String, Object, Object> fieldValueEditor;
|
||||
/**
|
||||
* 是否支持transient关键字修饰和@Transient注解,如果支持,被修饰的字段或方法对应的字段将被忽略。
|
||||
*/
|
||||
@ -224,6 +229,31 @@ public class CopyOptions implements Serializable {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置字段属性值编辑器,用于自定义属性值转换规则,例如null转""等<br>
|
||||
*
|
||||
* @param fieldValueEditor 字段属性值编辑器,用于自定义属性值转换规则,例如null转""等
|
||||
* @return CopyOptions
|
||||
* @since 5.7.15
|
||||
*/
|
||||
public CopyOptions setFieldValueEditor(BiFunction<String, Object, Object> 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<String, String> 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<String, String> 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;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
188
hutool-core/src/main/java/cn/hutool/core/codec/PercentCodec.java
Normal file
188
hutool-core/src/main/java/cn/hutool/core/codec/PercentCodec.java
Normal file
@ -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)。<br>
|
||||
* 百分号编码可用于URI的编码,也可以用于"application/x-www-form-urlencoded"的MIME准备数据。
|
||||
*
|
||||
* <p>
|
||||
* 百分号编码会对 URI 中不允许出现的字符或者其他特殊情况的允许的字符进行编码,对于被编码的字符,最终会转为以百分号"%“开头,后面跟着两位16进制数值的形式。
|
||||
* 举个例子,空格符(SP)是不允许的字符,在 ASCII 码对应的二进制值是"00100000”,最终转为"%20"。
|
||||
* </p>
|
||||
* <p>
|
||||
* 对于不同场景应遵循不同规范:
|
||||
*
|
||||
* <ul>
|
||||
* <li>URI:遵循RFC 3986保留字规范</li>
|
||||
* <li>application/x-www-form-urlencoded,遵循W3C HTML Form content types规范,如空格须转+</li>
|
||||
* </ul>
|
||||
*
|
||||
* @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;
|
||||
|
||||
/**
|
||||
* 构造<br>
|
||||
* [a-zA-Z0-9]默认不被编码
|
||||
*/
|
||||
public PercentCodec() {
|
||||
this(new BitSet(256));
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param safeCharacters 安全字符,安全字符不被编码
|
||||
*/
|
||||
public PercentCodec(BitSet safeCharacters) {
|
||||
this.safeCharacters = safeCharacters;
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加安全字符<br>
|
||||
* 安全字符不被编码
|
||||
*
|
||||
* @param c 字符
|
||||
* @return this
|
||||
*/
|
||||
public PercentCodec addSafe(char c) {
|
||||
safeCharacters.set(c);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除安全字符<br>
|
||||
* 安全字符不被编码
|
||||
*
|
||||
* @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();
|
||||
}
|
||||
}
|
@ -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 <T> List<T> sub(List<T> list, int start, int end, int step) {
|
||||
return ListUtil.sub(list, start, end, step);
|
||||
@ -1073,11 +1074,11 @@ public class CollUtil {
|
||||
/**
|
||||
* 截取集合的部分
|
||||
*
|
||||
* @param <T> 集合元素类型
|
||||
* @param collection 被截取的数组
|
||||
* @param start 开始位置(包含)
|
||||
* @param end 结束位置(不包含)
|
||||
* @param step 步进
|
||||
* @param <T> 集合元素类型
|
||||
* @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<T> list = collection instanceof List ? (List<T>)collection : ListUtil.toList(collection);
|
||||
final List<T> list = collection instanceof List ? (List<T>) collection : ListUtil.toList(collection);
|
||||
return sub(list, start, end, step);
|
||||
}
|
||||
|
||||
@ -1575,6 +1576,20 @@ public class CollUtil {
|
||||
return isEmpty(collection) ? defaultCollection : collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果给定集合为空,返回默认集合
|
||||
*
|
||||
* @param <T> 集合类型
|
||||
* @param <E> 集合元素类型
|
||||
* @param collection 集合
|
||||
* @param supplier 默认值懒加载函数
|
||||
* @return 非空(empty)的原集合或默认集合
|
||||
* @since 5.7.15
|
||||
*/
|
||||
public static <T extends Collection<E>, 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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 集合
|
||||
* <ul>
|
||||
* <li>Collection - the collection size
|
||||
* <li>Map - the map size
|
||||
* <li>Array - the array size
|
||||
* <li>Iterator - the number of elements remaining in the iterator
|
||||
* <li>Enumeration - the number of elements remaining in the enumeration
|
||||
* </ul>
|
||||
* @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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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中追加文件,此类非线程安全<br>
|
||||
* 此类在遍历源目录并复制过程中会自动创建目标目录中不存在的上级目录。
|
||||
*
|
||||
* @author looly
|
||||
* @since 5.7.15
|
||||
*/
|
||||
public class ZipCopyVisitor extends SimpleFileVisitor<Path> {
|
||||
|
||||
/**
|
||||
* 源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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据源文件或目录路径,拼接生成目标的文件或目录路径<br>
|
||||
* 原理是首先截取源路径,得到相对路径,再和目标路径拼接
|
||||
*
|
||||
* <p>
|
||||
* 如:源路径是 /opt/test/,需要拷贝的文件是 /opt/test/a/a.txt,得到相对路径 a/a.txt<br>
|
||||
* 目标路径是/home/,则得到最终目标路径是 /home/a/a.txt
|
||||
* </p>
|
||||
*
|
||||
* @param file 需要拷贝的文件或目录Path
|
||||
* @return 目标Path
|
||||
*/
|
||||
private Path resolveTarget(Path file) {
|
||||
return fileSystem.getPath(source.relativize(file).toString());
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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<Opt<?>> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
protected Opt<?> convertInternal(Object value) {
|
||||
return Opt.ofNullable(value);
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算相差时长
|
||||
*
|
||||
|
@ -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
|
||||
|
||||
/**
|
||||
|
@ -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;
|
||||
|
||||
/**
|
||||
* 秒表封装<br>
|
||||
@ -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 {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取任务信息
|
||||
* 获取任务信息,类似于:
|
||||
* <pre>
|
||||
* StopWatch '[id]': running time = [total] ns
|
||||
* </pre>
|
||||
*
|
||||
* @return 任务信息
|
||||
*/
|
||||
public String shortSummary() {
|
||||
return StrUtil.format("StopWatch '{}': running time = {} ns", this.id, this.totalTimeNanos);
|
||||
return shortSummary(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取任务信息,类似于:
|
||||
* <pre>
|
||||
* StopWatch '[id]': running time = [total] [unit]
|
||||
* </pre>
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
41
hutool-core/src/main/java/cn/hutool/core/date/ZoneUtil.java
Normal file
41
hutool-core/src/main/java/cn/hutool/core/date/ZoneUtil.java
Normal file
@ -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();
|
||||
}
|
||||
}
|
@ -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());
|
||||
|
@ -684,7 +684,7 @@ public class IoUtil extends NioUtil {
|
||||
* @return 内容
|
||||
* @throws IORuntimeException IO异常
|
||||
*/
|
||||
public static <T extends Collection<String>> T readLines(Reader reader, final T collection) throws IORuntimeException {
|
||||
public static <T extends Collection<String>> T readLines(Reader reader, T collection) throws IORuntimeException {
|
||||
readLines(reader, (LineHandler) collection::add);
|
||||
return collection;
|
||||
}
|
||||
|
@ -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}相关工具类封装<br>
|
||||
* 参考: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<String, String> 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);
|
||||
}
|
||||
}
|
@ -55,6 +55,19 @@ public class PathUtil {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归遍历目录以及子目录中的所有文件<br>
|
||||
* 如果提供path为文件,直接返回过滤结果
|
||||
*
|
||||
* @param path 当前遍历文件或目录
|
||||
* @param fileFilter 文件过滤规则对象,选择要保留的文件,只对文件有效,不过滤目录,null表示接收全部文件
|
||||
* @return 文件列表
|
||||
* @since 5.4.1
|
||||
*/
|
||||
public static List<File> loopFiles(Path path, FileFilter fileFilter) {
|
||||
return loopFiles(path, -1, fileFilter);
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归遍历目录以及子目录中的所有文件<br>
|
||||
* 如果提供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();
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除文件或空目录,不追踪软链
|
||||
*
|
||||
|
@ -20,20 +20,27 @@ import java.nio.file.attribute.BasicFileAttributes;
|
||||
*/
|
||||
public class CopyVisitor extends SimpleFileVisitor<Path> {
|
||||
|
||||
/**
|
||||
* 源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<Path> {
|
||||
}
|
||||
|
||||
@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<Path> {
|
||||
@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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据源文件或目录路径,拼接生成目标的文件或目录路径<br>
|
||||
* 原理是首先截取源路径,得到相对路径,再和目标路径拼接
|
||||
*
|
||||
* <p>
|
||||
* 如:源路径是 /opt/test/,需要拷贝的文件是 /opt/test/a/a.txt,得到相对路径 a/a.txt<br>
|
||||
* 目标路径是/home/,则得到最终目标路径是 /home/a/a.txt
|
||||
* </p>
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
|
@ -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} 抛出给定的异常<br>
|
||||
*
|
||||
@ -834,6 +837,41 @@ public class Assert {
|
||||
return index;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查值是否在指定范围内
|
||||
*
|
||||
* @param <X> 异常类型
|
||||
* @param value 值
|
||||
* @param min 最小值(包含)
|
||||
* @param max 最大值(包含)
|
||||
* @param errorSupplier 错误抛出异常附带的消息生产接口
|
||||
* @return 经过检查后的值
|
||||
* @throws X if value is out of bound
|
||||
* @since 5.7.15
|
||||
*/
|
||||
public static <X extends Throwable> 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 <X> 异常类型
|
||||
* @param value 值
|
||||
* @param min 最小值(包含)
|
||||
* @param max 最大值(包含)
|
||||
* @param errorSupplier 错误抛出异常附带的消息生产接口
|
||||
* @return 经过检查后的值
|
||||
* @throws X if value is out of bound
|
||||
* @since 5.7.15
|
||||
*/
|
||||
public static <X extends Throwable> 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 <X> 异常类型
|
||||
* @param value 值
|
||||
* @param min 最小值(包含)
|
||||
* @param max 最大值(包含)
|
||||
* @param errorSupplier 错误抛出异常附带的消息生产接口
|
||||
* @return 经过检查后的值
|
||||
* @throws X if value is out of bound
|
||||
* @since 5.7.15
|
||||
*/
|
||||
public static <X extends Throwable> 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;
|
||||
}
|
||||
|
@ -16,17 +16,17 @@ import java.util.Objects;
|
||||
public class Pair<K, V> extends CloneSupport<Pair<K, V>> 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 <K> 键类型
|
||||
* @param <V> 值类型
|
||||
* @param key 键
|
||||
* @param value 值
|
||||
* @return {@link Pair}
|
||||
* @return {@code Pair}
|
||||
* @since 5.4.3
|
||||
*/
|
||||
public static <K, V> Pair<K, V> of(K key, V value) {
|
||||
|
@ -32,9 +32,11 @@ public interface RegexPool {
|
||||
*/
|
||||
String GROUP_VAR = "\\$(\\d+)";
|
||||
/**
|
||||
* IP v4
|
||||
* IP v4<br>
|
||||
* 采用分组方式便于解析地址的每一个段
|
||||
*/
|
||||
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
|
||||
*/
|
||||
|
@ -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);
|
||||
|
19
hutool-core/src/main/java/cn/hutool/core/lang/hash/Hash.java
Normal file
19
hutool-core/src/main/java/cn/hutool/core/lang/hash/Hash.java
Normal file
@ -0,0 +1,19 @@
|
||||
package cn.hutool.core.lang.hash;
|
||||
|
||||
/**
|
||||
* Hash计算接口
|
||||
*
|
||||
* @param <T> 被计算hash的对象类型
|
||||
* @author looly
|
||||
* @since 5.7.15
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface Hash<T> {
|
||||
/**
|
||||
* 计算Hash值
|
||||
*
|
||||
* @param t 对象
|
||||
* @return hash
|
||||
*/
|
||||
Number hash(T t);
|
||||
}
|
@ -8,7 +8,8 @@ package cn.hutool.core.lang.hash;
|
||||
* @since 5.2.5
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface Hash128<T> {
|
||||
public interface Hash128<T> extends Hash<T>{
|
||||
|
||||
/**
|
||||
* 计算Hash值
|
||||
*
|
||||
@ -16,4 +17,9 @@ public interface Hash128<T> {
|
||||
* @return hash
|
||||
*/
|
||||
Number128 hash128(T t);
|
||||
|
||||
@Override
|
||||
default Number hash(T t){
|
||||
return hash128(t);
|
||||
}
|
||||
}
|
@ -8,7 +8,7 @@ package cn.hutool.core.lang.hash;
|
||||
* @since 5.2.5
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface Hash32<T> {
|
||||
public interface Hash32<T> extends Hash<T>{
|
||||
/**
|
||||
* 计算Hash值
|
||||
*
|
||||
@ -16,4 +16,9 @@ public interface Hash32<T> {
|
||||
* @return hash
|
||||
*/
|
||||
int hash32(T t);
|
||||
|
||||
@Override
|
||||
default Number hash(T t){
|
||||
return hash32(t);
|
||||
}
|
||||
}
|
@ -8,7 +8,7 @@ package cn.hutool.core.lang.hash;
|
||||
* @since 5.2.5
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface Hash64<T> {
|
||||
public interface Hash64<T> extends Hash<T>{
|
||||
/**
|
||||
* 计算Hash值
|
||||
*
|
||||
@ -16,4 +16,9 @@ public interface Hash64<T> {
|
||||
* @return hash
|
||||
*/
|
||||
long hash64(T t);
|
||||
|
||||
@Override
|
||||
default Number hash(T t){
|
||||
return hash64(t);
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ package cn.hutool.core.lang.mutable;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 可变 <code>boolean</code> 类型
|
||||
* 可变 {@code boolean} 类型
|
||||
*
|
||||
* @see Boolean
|
||||
* @since 3.0.1
|
||||
@ -59,12 +59,12 @@ public class MutableBool implements Comparable<MutableBool>, Mutable<Boolean>, S
|
||||
* 相等需同时满足如下条件:
|
||||
* <ol>
|
||||
* <li>非空</li>
|
||||
* <li>类型为 {@link MutableBool}</li>
|
||||
* <li>类型为 MutableBool</li>
|
||||
* <li>值相等</li>
|
||||
* </ol>
|
||||
*
|
||||
* @param obj 比对的对象
|
||||
* @return 相同返回<code>true</code>,否则 <code>false</code>
|
||||
* @return 相同返回<code>true</code>,否则 {@code false}
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
@ -83,7 +83,7 @@ public class MutableBool implements Comparable<MutableBool>, Mutable<Boolean>, S
|
||||
/**
|
||||
* 比较
|
||||
*
|
||||
* @param other 其它 {@link MutableBool} 对象
|
||||
* @param other 其它 MutableBool 对象
|
||||
* @return x==y返回0,x<y返回-1,x>y返回1
|
||||
*/
|
||||
@Override
|
||||
|
@ -3,7 +3,7 @@ package cn.hutool.core.lang.mutable;
|
||||
import cn.hutool.core.util.NumberUtil;
|
||||
|
||||
/**
|
||||
* 可变 <code>byte</code> 类型
|
||||
* 可变 {@code byte} 类型
|
||||
*
|
||||
* @see Byte
|
||||
* @since 3.0.1
|
||||
@ -157,12 +157,12 @@ public class MutableByte extends Number implements Comparable<MutableByte>, Muta
|
||||
* 相等需同时满足如下条件:
|
||||
* <ol>
|
||||
* <li>非空</li>
|
||||
* <li>类型为 {@link MutableByte}</li>
|
||||
* <li>类型为 MutableByte</li>
|
||||
* <li>值相等</li>
|
||||
* </ol>
|
||||
*
|
||||
* @param obj 比对的对象
|
||||
* @return 相同返回<code>true</code>,否则 <code>false</code>
|
||||
* @return 相同返回<code>true</code>,否则 {@code false}
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
@ -181,7 +181,7 @@ public class MutableByte extends Number implements Comparable<MutableByte>, Muta
|
||||
/**
|
||||
* 比较
|
||||
*
|
||||
* @param other 其它 {@link MutableByte} 对象
|
||||
* @param other 其它 MutableByte 对象
|
||||
* @return x==y返回0,x<y返回-1,x>y返回1
|
||||
*/
|
||||
@Override
|
||||
|
@ -3,7 +3,7 @@ package cn.hutool.core.lang.mutable;
|
||||
import cn.hutool.core.util.NumberUtil;
|
||||
|
||||
/**
|
||||
* 可变 <code>double</code> 类型
|
||||
* 可变 {@code double} 类型
|
||||
*
|
||||
* @see Double
|
||||
* @since 3.0.1
|
||||
@ -150,12 +150,12 @@ public class MutableDouble extends Number implements Comparable<MutableDouble>,
|
||||
* 相等需同时满足如下条件:
|
||||
* <ol>
|
||||
* <li>非空</li>
|
||||
* <li>类型为 {@link MutableDouble}</li>
|
||||
* <li>类型为 {@code MutableDouble}</li>
|
||||
* <li>值相等</li>
|
||||
* </ol>
|
||||
*
|
||||
* @param obj 比对的对象
|
||||
* @return 相同返回<code>true</code>,否则 <code>false</code>
|
||||
* @return 相同返回<code>true</code>,否则 {@code false}
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
@ -175,7 +175,7 @@ public class MutableDouble extends Number implements Comparable<MutableDouble>,
|
||||
/**
|
||||
* 比较
|
||||
*
|
||||
* @param other 其它 {@link MutableDouble} 对象
|
||||
* @param other 其它 {@code MutableDouble} 对象
|
||||
* @return x==y返回0,x<y返回-1,x>y返回1
|
||||
*/
|
||||
@Override
|
||||
|
@ -3,7 +3,7 @@ package cn.hutool.core.lang.mutable;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 可变<code>Object</code>
|
||||
* 可变{@code Object}
|
||||
*
|
||||
* @param <T> 可变的类型
|
||||
* @since 3.0.1
|
||||
|
@ -0,0 +1,57 @@
|
||||
package cn.hutool.core.lang.mutable;
|
||||
|
||||
import cn.hutool.core.lang.Pair;
|
||||
|
||||
/**
|
||||
* 可变{@link Pair}实现,可以修改键和值
|
||||
*
|
||||
* @param <K> 键类型
|
||||
* @param <V> 值类型
|
||||
* @since 5.7.16
|
||||
*/
|
||||
public class MutablePair<K, V> extends Pair<K, V> implements Mutable<Pair<K, V>>{
|
||||
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<K, V> setKey(K key) {
|
||||
this.key = key;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置值
|
||||
*
|
||||
* @param value 新值
|
||||
* @return this
|
||||
*/
|
||||
public MutablePair<K, V> setValue(V value) {
|
||||
this.value = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pair<K, V> get() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(Pair<K, V> pair) {
|
||||
this.key = pair.getKey();
|
||||
this.value = pair.getValue();
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
98
hutool-core/src/main/java/cn/hutool/core/net/RFC3986.java
Normal file
98
hutool-core/src/main/java/cn/hutool/core/net/RFC3986.java
Normal file
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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);
|
||||
|
@ -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}形式。<br>
|
||||
* 对于{@code null}处理规则如下:
|
||||
* <ul>
|
||||
* <li>如果key为{@code null},则这个键值对忽略</li>
|
||||
* <li>如果value为{@code null},只保留key,如key1对应value为{@code null}生成类似于{@code key1&key2=v2}形式</li>
|
||||
* </ul>
|
||||
*
|
||||
* @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<CharSequence, CharSequence> 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;
|
||||
}
|
||||
|
@ -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<br>
|
||||
* 因此使用此方法归一为一种表示形式,默认按照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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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<br>
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化字符串<br>
|
||||
* 此方法只是简单将指定占位符 按照顺序替换为参数<br>
|
||||
* 如果想输出占位符使用 \\转义即可,如果想输出占位符之前的 \ 使用双转义符 \\\\ 即可<br>
|
||||
* 例:<br>
|
||||
* 通常使用:format("this is {} for {}", "{}", "a", "b") =》 this is a for b<br>
|
||||
* 转义{}: format("this is \\{} for {}", "{}", "a", "b") =》 this is \{} for a<br>
|
||||
* 转义\: format("this is \\\\{} for {}", "{}", "a", "b") =》 this is \a for b<br>
|
||||
*
|
||||
* @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();
|
||||
}
|
||||
|
@ -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<String> splitIgnoreCase(CharSequence str, char separator, int limit, boolean isTrim, boolean ignoreEmpty) {
|
||||
return split(str, separator, limit, isTrim, ignoreEmpty, true);
|
||||
public static List<String> 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 <R> 切分后的元素类型
|
||||
* @param <R> 切分后的元素类型
|
||||
* @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<String> split(String str, String separator, boolean isTrim, boolean ignoreEmpty) {
|
||||
public static List<String> 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<String> splitTrim(String str, String separator, boolean ignoreEmpty) {
|
||||
public static List<String> 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<String> split(String str, String separator, int limit, boolean isTrim, boolean ignoreEmpty) {
|
||||
public static List<String> 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<String> splitTrim(String str, String separator, int limit, boolean ignoreEmpty) {
|
||||
public static List<String> 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<String> splitIgnoreCase(String str, String separator, int limit, boolean isTrim, boolean ignoreEmpty) {
|
||||
public static List<String> 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<String> splitTrimIgnoreCase(String str, String separator, int limit, boolean ignoreEmpty) {
|
||||
public static List<String> 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<String> split(String text, String separator, int limit, boolean isTrim, boolean ignoreEmpty, boolean ignoreCase) {
|
||||
public static List<String> 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<String> split(String text, int limit) {
|
||||
public static List<String> 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<String> splitByRegex(String str, String separatorRegex, int limit, boolean isTrim, boolean ignoreEmpty) {
|
||||
public static List<String> 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<String> 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);
|
||||
|
@ -53,7 +53,6 @@ public class SplitIter extends ComputeIter<String> 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;
|
||||
|
@ -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 <T> 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> T firstNonNull(T... array) {
|
||||
return firstMatch(Objects::nonNull, array);
|
||||
return firstMatch(ObjectUtil::isNotNull, array);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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);
|
||||
|
@ -292,7 +292,7 @@ public class ObjectUtil {
|
||||
* @since 3.0.7
|
||||
*/
|
||||
public static <T> 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 <T> 被检查对象为{@code null}返回默认值,否则返回自定义handle处理后的返回值
|
||||
* @return 处理后的返回值
|
||||
* @since 5.4.6
|
||||
*/
|
||||
public static <T> 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());
|
||||
}
|
||||
|
||||
|
@ -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');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得指定范围内的随机数
|
||||
*
|
||||
|
@ -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<String> 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<String> result = new MutableObj<>();
|
||||
get(pattern, content, matcher -> result.set(matcher.group(groupName)));
|
||||
return result.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* 在给定字符串中查找给定规则的字符,如果找到则使用{@link Consumer}处理之<br>
|
||||
* 如果内容中有多个匹配项,则只处理找到的第一个结果。
|
||||
*
|
||||
* @param pattern 匹配的正则
|
||||
* @param content 被匹配的内容
|
||||
* @param consumer 匹配到的内容处理器
|
||||
* @since 5.7.15
|
||||
*/
|
||||
public static void get(Pattern pattern, CharSequence content, Consumer<Matcher> 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据给定正则查找字符串中的匹配项,返回所有匹配的分组名对应分组值<br>
|
||||
* <pre>
|
||||
* pattern: (?<year>\\d+)-(?<month>\\d+)-(?<day>\\d+)
|
||||
* content: 2021-10-11
|
||||
* result : year: 2021, month: 10, day: 11
|
||||
* </pre>
|
||||
*
|
||||
* @param pattern 匹配的正则
|
||||
* @param content 被匹配的内容
|
||||
* @return 命名捕获组,key为分组名,value为对应值
|
||||
* @since 5.7.15
|
||||
*/
|
||||
public static Map<String, String> getAllGroupNames(Pattern pattern, CharSequence content) {
|
||||
if (null == content || null == pattern) {
|
||||
return null;
|
||||
}
|
||||
final Matcher m = pattern.matcher(content);
|
||||
final Map<String, String> result = MapUtil.newHashMap(m.groupCount());
|
||||
if (m.find()) {
|
||||
// 通过反射获取 namedGroups 方法
|
||||
final Map<String, Integer> map = ReflectUtil.invoke(pattern, "namedGroups");
|
||||
map.forEach((key, value) -> result.put(key, m.group(value)));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从content中匹配出多个值并根据template生成新的字符串<br>
|
||||
* 例如:<br>
|
||||
@ -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<Matcher> 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();
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
/**
|
||||
* 系统属性工具<br>
|
||||
* 此工具用于读取系统属性或环境变量信息,封装包括:
|
||||
* <ul>
|
||||
* <li>{@link System#getProperty(String)}</li>
|
||||
* <li>{@link System#getenv(String)}</li>
|
||||
* </ul>
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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文件中添加新文件或目录<br>
|
||||
* 新文件添加在zip根目录,文件夹包括其本身和内容<br>
|
||||
* 如果待添加文件夹是系统根路径(如/或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<ZipEntry> 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<ZipEntry> 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));
|
||||
}
|
||||
}
|
||||
|
@ -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<String, Object> 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();
|
||||
|
@ -712,6 +712,23 @@ public class CollUtilTest {
|
||||
Assert.assertEquals("d", map.get("keyd"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mapToMapTest(){
|
||||
final HashMap<String, String> oldMap = new HashMap<>();
|
||||
oldMap.put("a", "1");
|
||||
oldMap.put("b", "12");
|
||||
oldMap.put("c", "134");
|
||||
|
||||
final Map<String, Long> 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<String> list = CollUtil.newArrayList("a", "b", "c", "c", "a", "b", "d");
|
||||
|
@ -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<String> 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);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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<Path>() {
|
||||
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) {
|
||||
Console.log(path);
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -8,7 +8,7 @@ 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);
|
||||
@ -21,4 +21,34 @@ public class StrFormatterTest {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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"));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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<String, String> 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<String, String> 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);
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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 = "(?<year>\\d+)-(?<month>\\d+)-(?<day>\\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 = "(?<year>\\d+)-(?<month>\\d+)-(?<day>\\d+)";
|
||||
Map<String, String> 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");
|
||||
}
|
||||
}
|
||||
|
@ -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";
|
||||
|
@ -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<String> beforeNames = zipEntryNames(tempZipFile);
|
||||
ZipUtil.append(tempZipFile.toPath(), appendFile.toPath());
|
||||
List<String> 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<String> zipEntryNames(File zipFile) {
|
||||
List<String> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
2
hutool-core/src/test/resources/test-zip/addFile.txt
Normal file
2
hutool-core/src/test/resources/test-zip/addFile.txt
Normal file
@ -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.
|
@ -0,0 +1 @@
|
||||
1
|
BIN
hutool-core/src/test/resources/test-zip/test.zip
Normal file
BIN
hutool-core/src/test/resources/test-zip/test.zip
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user