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:
achao 2021-10-30 13:52:23 +08:00
commit 27b69908c8
154 changed files with 3534 additions and 833 deletions

71
.github/codeql-analysis.yml vendored Normal file
View 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

View File

@ -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 】 增加RingIndexUtilpr#438@Gitee
* 【core 】 Assert增加checkBetween重载pr#436@Gitee
* 【core 】 ReUtil增加命名分组重载pr#439@Gitee
* 【json 】 toString和writer增加Filterissue#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新增setFieldValueEditorissue#I4E08T@Gitee
* 【core 】 增加SystemPropsUtilissue#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重载支持DayOfWekpr#1872@Github
* 【poi 】 优化read避免多次创建CopyOptionsissue#1875@Github
* 【core 】 优化CsvReader实现可控遍历pr#1873@Github
* 【core 】 优化Base64.isBase64判断pr#1879@Github
* 【core 】 新增StrFormatter.formatWithpr#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
-------------------------------------------------------------------------------------------------------------

View File

@ -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
[![Stargazers over time](https://starchart.cc/dromara/hutool.svg)](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.
![Tuling](https://cdn.jsdelivr.net/gh/looly/hutool-site/images/qr_tuling.jpg)
#### 🐧Welcome to organization Dromara
![Dromara](https://dromara.org/img/qrcode/qrcode_1.png)
<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>

View File

@ -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添砖加瓦贡献代码不过维护者是
[![Stargazers over time](https://starchart.cc/dromara/hutool.svg)](https://starchart.cc/dromara/hutool)
## 💳捐赠
如果你觉得Hutool不错可以捐赠请维护者吃包辣条~,在此表示感谢^_^。
点击以下链接,将页面拉到最下方点击“捐赠”即可。
[Gitee上捐赠](https://gitee.com/dromara/hutool)
[捐赠给Dromara组织](https://dromara.gitee.io/donate.html)
## 📌公众号
#### 🧡欢迎关注Hutool合作的公众号
![图灵学院](https://cdn.jsdelivr.net/gh/looly/hutool-site/images/qr_tuling.jpg)
#### 🧡Dromara开源组织公众号
![Dromara](https://dromara.org/img/qrcode/qrcode_1.png)
<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
View 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`.

View File

@ -1 +1 @@
5.7.14
5.7.16

View File

@ -1 +1 @@
var version = '5.7.14'
var version = '5.7.16'

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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);
}
}

View File

@ -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>

View File

@ -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>

View File

@ -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

View File

@ -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;
/**

View File

@ -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;
/**

View File

@ -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;
/**

View 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);
}
}
}

View 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);
}
}
}

View File

@ -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;
/** 正在执行的定时任务 */

View 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());
}
}

View File

@ -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>

View File

@ -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>

View File

@ -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
/**

View File

@ -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本身防止循环引用

View File

@ -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;

View File

@ -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;
}
}

View 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();
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}
}
}
}

View File

@ -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);
}
/**

View File

@ -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());
}
}

View File

@ -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);

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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);
}
/**
* 计算相差时长
*

View File

@ -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
/**

View File

@ -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);
}
/**

View File

@ -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;

View File

@ -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);
}
}
}

View 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();
}
}

View File

@ -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());

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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();
}
/**
* 删除文件或空目录不追踪软链
*

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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) {

View File

@ -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
*/

View File

@ -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);

View 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);
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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返回0x&lt;y返回-1x&gt;y返回1
*/
@Override

View File

@ -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返回0x&lt;y返回-1x&gt;y返回1
*/
@Override

View File

@ -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返回0x&lt;y返回-1x&gt;y返回1
*/
@Override

View File

@ -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

View File

@ -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();
}
}

View File

@ -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
}

View File

@ -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);
}
/**

View 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;
}
}

View File

@ -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);
}
/**

View File

@ -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);

View File

@ -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&amp;key2=&amp;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;
}

View File

@ -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空白串nullundefined
*
* @param str 被检查的字符串
* @return 是否不为null空白串nullundefined
* 不为null空白串nullundefined返回true否则返回false
*/
public static boolean isNotBlankOrUndefined(CharSequence str) {
return !isBlankOrUndefined(str);
}
/**
* 是否为nullundefined不做空指针检查
*
@ -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);
}
/**
* 是否不为nullundefined不做空指针检查
*
* @param str 字符串
* @return 是否不为nullundefined不为nullundefined返回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);
}
}

View File

@ -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;
}

View File

@ -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();
}

View File

@ -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);

View File

@ -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;

View File

@ -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);
}
/**

View File

@ -1675,12 +1675,12 @@ public class NumberUtil {
*
* @param x 第一个值
* @param y 第二个值
* @return x==y返回0x&lt;y返回-1x&gt;y返回1
* @return x==y返回0x&lt;y返回小于0的数x&gt;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返回0x&lt;y返回-1x&gt;y返回1
* @return x==y返回0x&lt;y返回小于0的数x&gt;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返回0x&lt;y返回-1x&gt;y返回1
* @return x==y返回0x&lt;y返回小于0的数x&gt;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返回0x&lt;y返回-1x&gt;y返回1
* @return x==y返回0x&lt;y返回小于0的数x&gt;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返回0x&lt;y返回-1x&gt;y返回1
* @return x==y返回0x&lt;y返回小于0的数x&gt;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);

View File

@ -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());
}

View File

@ -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');
}
/**
* 获得指定范围内的随机数
*

View File

@ -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: (?&lt;year&gt;\\d+)-(?&lt;month&gt;\\d+)-(?&lt;day&gt;\\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();
}
}

View File

@ -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);
}
}
}

View File

@ -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));
}
}

View File

@ -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();

View File

@ -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");

View File

@ -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);
});
}
}

View File

@ -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();
}
}

View File

@ -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());
}
}

View File

@ -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;
}
});
}
}

View File

@ -6,19 +6,49 @@ import org.junit.Test;
import cn.hutool.core.text.StrFormatter;
public class StrFormatterTest {
@Test
public void formatTest(){
public void formatTest() {
//通常使用
String result1 = StrFormatter.format("this is {} for {}", "a", "b");
Assert.assertEquals("this is a for b", result1);
//转义{}
String result2 = StrFormatter.format("this is \\{} for {}", "a", "b");
Assert.assertEquals("this is {} for a", result2);
//转义\
String result3 = StrFormatter.format("this is \\\\{} for {}", "a", "b");
Assert.assertEquals("this is \\a for b", result3);
}
@Test
public void formatWithTest() {
//通常使用
String result1 = StrFormatter.formatWith("this is ? for ?", "?", "a", "b");
Assert.assertEquals("this is a for b", result1);
//转义?
String result2 = StrFormatter.formatWith("this is \\? for ?", "?", "a", "b");
Assert.assertEquals("this is ? for a", result2);
//转义\
String result3 = StrFormatter.formatWith("this is \\\\? for ?", "?", "a", "b");
Assert.assertEquals("this is \\a for b", result3);
}
@Test
public void formatWithTest2() {
//通常使用
String result1 = StrFormatter.formatWith("this is $$$ for $$$", "$$$", "a", "b");
Assert.assertEquals("this is a for b", result1);
//转义?
String result2 = StrFormatter.formatWith("this is \\$$$ for $$$", "$$$", "a", "b");
Assert.assertEquals("this is $$$ for a", result2);
//转义\
String result3 = StrFormatter.formatWith("this is \\\\$$$ for $$$", "$$$", "a", "b");
Assert.assertEquals("this is \\a for b", result3);
}
}

View File

@ -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");
}
}

View File

@ -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);
}
}

View File

@ -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"));
}
}

View File

@ -192,9 +192,9 @@ public class UrlBuilderTest {
"&amp;sn=1044c0d19723f74f04f4c1da34eefa35" +
"&amp;chksm=6cbda3a25bca2ab4516410db6ce6e125badaac2f8c5548ea6e18eab6dc3c5422cb8cbe1095f7";
final UrlBuilder builder = UrlBuilder.ofHttp(urlStr, CharsetUtil.CHARSET_UTF_8);
// 原URL中的&amp;替换为&value中的=被编码为%3D
// 原URL中的&amp;替换为&
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);
}
}

View File

@ -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);
}
}

View File

@ -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
}

View File

@ -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());
}

View File

@ -4,11 +4,11 @@ import org.junit.Assert;
import org.junit.Test;
public class ThreadUtilTest {
@Test
public void executeTest() {
final boolean isValid = true;
ThreadUtil.execute(() -> Assert.assertTrue(isValid));
}
}

View File

@ -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);
}
}

View File

@ -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");
}
}

View File

@ -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";

View File

@ -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);
}
}
}

View 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.

View File

@ -0,0 +1 @@
1

Some files were not shown because too many files have changed in this diff Show More