mirror of
https://gitee.com/chinabugotech/hutool.git
synced 2025-08-18 20:38:02 +08:00
Merge branch 'v5-dev' into v5-dev
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -26,6 +26,7 @@ build/
|
||||
*.diff
|
||||
*.patch
|
||||
*.tmp
|
||||
.jython_cache/
|
||||
|
||||
# system ignore
|
||||
.DS_Store
|
||||
|
||||
1461
CHANGELOG-v4.md
1461
CHANGELOG-v4.md
File diff suppressed because it is too large
Load Diff
164
CHANGELOG.md
164
CHANGELOG.md
@@ -3,7 +3,168 @@
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------
|
||||
|
||||
## 5.2.5
|
||||
## 5.3.6 (2020-05-25)
|
||||
|
||||
### 新特性
|
||||
* 【core 】 NumberConverter Long类型增加日期转换(pr#872@Github)
|
||||
* 【all 】 StrUtil and SymmetricCrypto注释修正(pr#873@Github)
|
||||
* 【core 】 CsvReader支持返回Bean(issue#869@Github)
|
||||
* 【core 】 Snowflake循环等待下一个时间时避免长时间循环,加入对时钟倒退的判断(pr#874@Github)
|
||||
* 【extra 】 新增 QRCode base64 编码形式返回(pr#878@Github)
|
||||
* 【core 】 ImgUtil增加toBase64DateUri,URLUtil增加getDataUri方法
|
||||
* 【core 】 IterUtil添加List转Map的工具方法(pr#123@Gitee)
|
||||
* 【core 】 BeanValuePovider转换失败时,返回原数据,而非null
|
||||
* 【core 】 支持BeanUtil.toBean(object, Map.class)转换(issue#I1I4HC@Gitee)
|
||||
* 【core 】 MapUtil和CollUtil增加clear方法(issue#I1I4HC@Gitee)
|
||||
* 【core 】 增加FontUtil,可定义pressText是否从中间(issue#I1HSWU@Gitee)
|
||||
* 【http 】 SoapClient支持自定义请求头(issue#I1I0AO@Gitee)
|
||||
* 【script 】 ScriptUtil增加evalInvocable和invoke方法(issue#I1HHCP@Gitee)
|
||||
* 【core 】 ImgUtil增加去除背景色的方法(pr#124@Gitee)
|
||||
* 【system 】 OshiUtil增加获取CPU使用率的方法(pr#124@Gitee)
|
||||
* 【crypto 】 AsymmetricAlgorithm去除EC(issue#887@Github)
|
||||
* 【cache 】 超时缓存使用的线程池大小默认为1(issue#890@Github)
|
||||
* 【poi 】 ExcelSaxReader支持handleCell方法
|
||||
* 【core 】 Snowflake容忍2秒内的时间回拨(issue#I1IGDX@Gitee)
|
||||
|
||||
### Bug修复
|
||||
* 【core 】 修复SimpleCache死锁问题(issue#I1HOKB@Gitee)
|
||||
* 【core 】 修复SemaphoreRunnable释放问题(issue#I1HLQQ@Gitee)
|
||||
* 【poi 】 修复Sax方式读取Excel行号错误问题(issue#882@Gitee)
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------
|
||||
|
||||
## 5.3.5 (2020-05-13)
|
||||
|
||||
### 新特性
|
||||
* 【core 】 增加CollUtil.map方法
|
||||
* 【extra 】 增加Sftp.lsEntries方法,Ftp和Sftp增加recursiveDownloadFolder(pr#121@Gitee)
|
||||
* 【system 】 OshiUtil增加getNetworkIFs方法
|
||||
* 【core 】 CollUtil增加unionDistinct、unionAll方法(pr#122@Gitee)
|
||||
* 【core 】 增加IoUtil.readObj重载,通过ValidateObjectInputStream由用户自定义安全检查。
|
||||
* 【http 】 改造HttpRequest中文件上传部分,增加MultipartBody类
|
||||
|
||||
### Bug修复
|
||||
* 【core 】 修复IoUtil.readObj中反序列化安全检查导致的一些问题,去掉安全检查。
|
||||
* 【http 】 修复SimpleServer文件访问404问题(issue#I1GZI3@Gitee)
|
||||
* 【core 】 修复BeanCopier中循环引用逻辑问题(issue#I1H2VN@Gitee)
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------
|
||||
|
||||
## 5.3.4 (2020-05-10)
|
||||
|
||||
### 新特性
|
||||
* 【core 】 增加URLUtil.getContentLength方法(issue#I1GB1Z@Gitee)
|
||||
* 【extra 】 增加PinyinUtil(issue#I1GMIV@Gitee)
|
||||
|
||||
### Bug修复
|
||||
* 【extra 】 修复Ftp设置超时问题(issue#I1GMTQ@Gitee)
|
||||
* 【core 】 修复TreeUtil根据id查找子节点时的NPE问题(pr#120@Gitee)
|
||||
* 【core 】 修复BeanUtil.copyProperties中Alias注解无效问题(issue#I1GK3M@Gitee)
|
||||
* 【core 】 修复CollUtil.containsAll空集合判断问题(issue#I1G9DE@Gitee)
|
||||
* 【core 】 修复XmlUtil.xmlToBean失败问题(issue#865@Github)
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------
|
||||
|
||||
## 5.3.3 (2020-05-05)
|
||||
|
||||
### 新特性
|
||||
* 【core 】 ImgUtil.createImage支持背景透明(issue#851@Github)
|
||||
* 【json 】 更改JSON转字符串时"</"被转义的规则为不转义(issue#852@Github)
|
||||
* 【cron 】 表达式的所有段支持L关键字(issue#849@Github)
|
||||
* 【extra 】 增加PinyinUtil,封装TinyPinyin
|
||||
* 【extra 】 Ftp和Sftp增加FtpConfig,提供超时等更多可选参数
|
||||
* 【extra 】 SpringUtil增加getActiveProfiles、getBeansOfType、getBeanNamesForType方法(issue#I1FXF3@Gitee)
|
||||
* 【bloomFilter】 避免布隆过滤器数字溢出(pr#119@Gitee)
|
||||
* 【core 】 增加IoUtil.writeObj(issue#I1FZIE)
|
||||
* 【core 】 增加FastStringWriter
|
||||
* 【core 】 增加NumberUtil.ceilDiv方法(pr#858@Github)
|
||||
* 【core 】 IdcardUtil增加省份校验(issue#859@Github)
|
||||
* 【extra 】 TemplateFactory和TokenizerFactory增加单例的get方法
|
||||
|
||||
### Bug修复
|
||||
* 【core 】 修复URLBuilder中请求参数有`&`导致的问题(issue#850@Github)
|
||||
* 【core 】 修复URLBuilder中路径以`/`结尾导致的问题(issue#I1G44J@Gitee)
|
||||
* 【db 】 修复SqlBuilder中orderBy无效问题(issue#856@Github)
|
||||
* 【core 】 修复StrUtil.subBetweenAll错误问题(issue#861@Github)
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------
|
||||
|
||||
## 5.3.2 (2020-04-23)
|
||||
|
||||
### 新特性
|
||||
* 【core 】 增加NetUtil.isOpen方法
|
||||
* 【core 】 增加ThreadUtil.sleep和safeSleep的重载
|
||||
* 【core 】 Sftp类增加toString方法(issue#I1F2T4@Gitee)
|
||||
* 【core 】 修改FileUtil.size逻辑,不存在的文件返回0
|
||||
* 【extra 】 Sftp.ls遇到文件不存在返回空集合,而非抛异常(issue#844@Github)
|
||||
* 【http 】 改进HttpRequest.toString()格式,添加url
|
||||
|
||||
### Bug修复
|
||||
* 【db 】 修复PageResult.isLast计算问题
|
||||
* 【cron 】 修复更改系统时间后CronTimer被阻塞的问题(issue#838@Github)
|
||||
* 【db 】 修复Page.addOrder无效问题(issue#I1F9MZ@Gitee)
|
||||
* 【json 】 修复JSONConvert转换日期空指针问题(issue#I1F8M2@Gitee)
|
||||
* 【core 】 修复XML中带注释Xpath解析导致空指针问题(issue#I1F2WI@Gitee)
|
||||
* 【core 】 修复FileUtil.rename原文件无扩展名多点的问题(issue#839@Github)
|
||||
* 【db 】 修复DbUtil.close可能存在的空指针问题(issue#847@Github)
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------
|
||||
## 5.3.1 (2020-04-17)
|
||||
|
||||
### 新特性
|
||||
* 【core 】 ListUtil、MapUtil、CollUtil增加empty方法
|
||||
* 【poi 】 调整别名策略,clearHeaderAlias和addHeaderAlias同时清除aliasComparator(issue#828@Github)
|
||||
* 【core 】 修改StrUtil.equals逻辑,改为contentEquals
|
||||
* 【core 】 增加URLUtil.UrlDecoder
|
||||
* 【core 】 增加XmlUtil.setNamespaceAware,getByPath支持UniversalNamespaceCache
|
||||
* 【aop 】 增加Spring-cglib支持,改为SPI实现
|
||||
* 【json 】 增加JSONUtil.parseXXX增加JSONConfig参数
|
||||
* 【core 】 RandomUtil.randomNumber改为返回char
|
||||
* 【crypto 】 SM2支持设置Digest和DSAEncoding(issue#829@Github)
|
||||
|
||||
### Bug修复
|
||||
* 【json 】 修复解析JSON字符串时配置无法传递问题(issue#I1EIDN@Gitee)
|
||||
* 【core 】 修复ServletUtil.readCookieMap空指针问题(issue#827@Github)
|
||||
* 【crypto 】 修复SM2中检查密钥导致的问题(issue#I1EC47@Gitee)
|
||||
* 【core 】 修复TableMap.isEmpty判断问题
|
||||
* 【http 】 修复编码后的URL传入导致二次编码的问题(issue#I1EIMN@Gitee)
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------
|
||||
|
||||
## 5.3.0 (2020-04-07)
|
||||
|
||||
### 新特性
|
||||
* 【extra 】 JschUtil增加execByShell方法(issue#I1CYES@Gitee)
|
||||
* 【core 】 StrUtil增加subBetweenAll方法,Console增加where和lineNumber方法(issue#812@Github)
|
||||
* 【core 】 TableMap增加getKeys和getValues方法
|
||||
* 【json 】 JSONObject和JSONArray增加set方法,标识put弃用
|
||||
* 【http 】 增加SimpleHttpServer
|
||||
* 【script 】 增加createXXXScript,区别单例
|
||||
* 【core 】 修改FileUtil.writeFileToStream等方法返回值为long
|
||||
* 【core 】 CollUtil.split增加空集合判定(issue#814@Github)
|
||||
* 【core 】 NetUtil增加parseCookies方法
|
||||
* 【core 】 CollUtil增加toMap方法
|
||||
* 【core 】 CollUtil和IterUtil废弃一些方法
|
||||
* 【core 】 添加ValidateObjectInputStream避免对象反序列化漏洞风险
|
||||
* 【core 】 添加BiMap
|
||||
* 【all 】 cn.hutool.extra.servlet.multipart包迁移到cn.hutool.core.net下
|
||||
* 【core 】 XmlUtil.mapToXml方法支持集合解析(issue#820@Github)
|
||||
* 【json 】 解析Object中对是否为bean单独判断,而不是直接解析
|
||||
* 【core 】 SimHash锁改为StampedLock
|
||||
* 【core 】 Singleton改为SimpleCache实现
|
||||
* 【core 】 增加CalendarUtil,DateUtil相关方法全部迁移到此
|
||||
|
||||
### Bug修复
|
||||
* 【extra 】 修复SpringUtil使用devtools重启报错问题
|
||||
* 【http 】 修复HttpUtil.encodeParams针对无参数URL问题(issue#817@Github)
|
||||
* 【extra 】 修复模板中无效引用的问题
|
||||
* 【extra 】 修复读取JSON文本配置未应用到子对象的问题(issue#818@Github)
|
||||
* 【extra 】 修复XmlUtil.createXml中namespace反向问题
|
||||
* 【core 】 修复WatchMonitor默认无event问题
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------
|
||||
|
||||
## 5.2.5 (2020-03-26)
|
||||
|
||||
### 新特性
|
||||
* 【core 】 增加逻辑,对于原始类型注入,使用默认值(issue#797@Github)
|
||||
@@ -16,6 +177,7 @@
|
||||
* 【core 】 CollUtil.newHashSet重载歧义,更换为set方法
|
||||
* 【core 】 增加ListUtil,增加Hash32、Hash64、Hash128接口
|
||||
* 【crypto 】 BCUtil增加readPemPrivateKey和readPemPublicKey方法
|
||||
* 【cache 】 替换读写锁为StampedLock,增加LockUtil
|
||||
|
||||
### Bug修复
|
||||
* 【core 】 修复NumberWordFormatter拼写错误(issue#799@Github)
|
||||
|
||||
22
README.md
22
README.md
@@ -8,8 +8,8 @@
|
||||
<a target="_blank" href="https://search.maven.org/search?q=g:%22cn.hutool%22%20AND%20a:%22hutool-all%22">
|
||||
<img src="https://img.shields.io/maven-central/v/cn.hutool/hutool-all.svg?label=Maven%20Central" />
|
||||
</a>
|
||||
<a target="_blank" href="http://license.coscl.org.cn/MulanPSL/">
|
||||
<img src="https://img.shields.io/:license-MulanPSL-blue.svg" />
|
||||
<a target="_blank" href="https://license.coscl.org.cn/MulanPSL2/">
|
||||
<img src="https://img.shields.io/:license-MulanPSL2-blue.svg" />
|
||||
</a>
|
||||
<a target="_blank" href="https://www.oracle.com/technetwork/java/javase/downloads/index.html">
|
||||
<img src="https://img.shields.io/badge/JDK-8+-green.svg" />
|
||||
@@ -37,10 +37,9 @@
|
||||
</a>
|
||||
</p>
|
||||
<p align="center">
|
||||
-- 主页:<a href="https://hutool.cn">https://hutool.cn/</a> | <a href="https://www.hutool.club/">https://www.hutool.club/</a> --
|
||||
-- 主页:<a href="https://hutool.cn">https://hutool.cn/</a> --
|
||||
</p>
|
||||
<p align="center">
|
||||
-- QQ群③:<a href="https://shang.qq.com/wpa/qunwpa?idkey=35764b2247c46ffebe28e45.2.5b2af8f5dee5efcf47ceec69d21e4521aa8c75">555368316</a> --
|
||||
-- QQ群④:<a href="https://shang.qq.com/wpa/qunwpa?idkey=309056e409a304a454c7ba250a10d38dd82b9b49cd0e1f180fedbde78b02ae0d">718802356</a> --
|
||||
</p>
|
||||
|
||||
@@ -91,7 +90,7 @@ Hutool的存在就是为了减少代码搜索成本,避免网络上参差不
|
||||
| hutool-system | 系统参数调用封装(JVM信息等) |
|
||||
| hutool-json | JSON实现 |
|
||||
| hutool-captcha | 图片验证码实现 |
|
||||
| hutool-poi | 针对POI中Excel的封装 |
|
||||
| hutool-poi | 针对POI中Excel和Word的封装 |
|
||||
| hutool-socket | 基于Java的NIO和AIO的Socket封装 |
|
||||
|
||||
可以根据需求对每个模块单独引入,也可以通过引入`hutool-all`方式引入所有模块。
|
||||
@@ -101,10 +100,11 @@ Hutool的存在就是为了减少代码搜索成本,避免网络上参差不
|
||||
## 文档
|
||||
|
||||
[中文文档](https://www.hutool.cn/docs/)
|
||||
[中文文档(备用)](https://www.hutool.club/docs/)
|
||||
|
||||
[参考API](https://apidoc.gitee.com/loolly/hutool/)
|
||||
|
||||
[视频介绍](https://www.bilibili.com/video/BV1bQ4y1M7d9?p=2)
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
## 安装
|
||||
@@ -116,24 +116,24 @@ Hutool的存在就是为了减少代码搜索成本,避免网络上参差不
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
<version>5.2.5</version>
|
||||
<version>5.3.6</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
### Gradle
|
||||
```
|
||||
compile 'cn.hutool:hutool-all:5.2.5'
|
||||
compile 'cn.hutool:hutool-all:5.3.6'
|
||||
```
|
||||
|
||||
### 非Maven项目
|
||||
|
||||
点击以下任一链接,下载`hutool-all-X.X.X.jar`即可:
|
||||
|
||||
- [Maven中央库1](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.2.5/)
|
||||
- [Maven中央库2](http://repo2.maven.org/maven2/cn/hutool/hutool-all/5.2.5/)
|
||||
- [Maven中央库1](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.3.6/)
|
||||
- [Maven中央库2](http://repo2.maven.org/maven2/cn/hutool/hutool-all/5.3.6/)
|
||||
|
||||
> 注意
|
||||
> Hutool 5.x支持JDK8+,对Android平台没有测试,不能保证所有工具类获工具方法可用。
|
||||
> Hutool 5.x支持JDK8+,对Android平台没有测试,不能保证所有工具类或工具方法可用。
|
||||
> 如果你的项目使用JDK7,请使用Hutool 4.x版本
|
||||
|
||||
### 编译安装
|
||||
|
||||
3
bin/cobertura.sh
Normal file
3
bin/cobertura.sh
Normal file
@@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
|
||||
exec mvn -T 1 cobertura:cobertura
|
||||
3
bin/simple_install.sh
Normal file
3
bin/simple_install.sh
Normal file
@@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
|
||||
exec mvn -T 1C clean install -Dmaven.test.skip=true -Dmaven.javadoc.skip=true
|
||||
@@ -1 +1 @@
|
||||
5.2.5
|
||||
5.3.6
|
||||
|
||||
@@ -1 +1 @@
|
||||
var version = '5.2.5'
|
||||
var version = '5.3.6'
|
||||
@@ -9,12 +9,12 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.2.5-SNAPSHOT</version>
|
||||
<version>5.3.6-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-all</artifactId>
|
||||
<name>${project.artifactId}</name>
|
||||
<description>提供丰富的Java工具方法,此模块为Hutool所有模块的打包汇总,最终形式为一个jar包</description>
|
||||
<description>Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。</description>
|
||||
<url>https://github.com/looly/hutool</url>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -18,14 +18,15 @@ package cn.hutool;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Hutool是Hu + tool的自造词,前者致敬我的“前任公司”,后者为工具之意,谐音“糊涂”,寓意追求“万事都作糊涂观,无所谓失,无所谓得”的境界。
|
||||
* Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Hutool是一个Java工具包,也只是一个工具包,它帮助我们简化每一行代码,减少每一个方法,让Java语言也可以“甜甜的”。<br>
|
||||
* Hutool最初是我项目中“util”包的一个整理,后来慢慢积累并加入更多非业务相关功能,并广泛学习其它开源项目精髓,经过自己整理修改,最终形成丰富的开源工具集。
|
||||
* Hutool中的工具方法来自于每个用户的精雕细琢,它涵盖了Java开发底层代码中的方方面面,它既是大型项目开发中解决小问题的利器,也是小型项目中的效率担当;<br>
|
||||
* </p>
|
||||
*
|
||||
* <p>Hutool是项目中“util”包友好的替代,它节省了开发人员对项目中公用类和公用工具方法的封装时间,使开发专注于业务,同时可以最大限度的避免封装不完善带来的bug。</p>
|
||||
*
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.2.5-SNAPSHOT</version>
|
||||
<version>5.3.6-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-aop</artifactId>
|
||||
@@ -19,6 +19,7 @@
|
||||
<properties>
|
||||
<!-- versions -->
|
||||
<cglib.version>3.3.0</cglib.version>
|
||||
<spring.version>5.2.5.RELEASE</spring.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
@@ -34,5 +35,12 @@
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-core</artifactId>
|
||||
<version>${spring.version}</version>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
||||
@@ -13,7 +13,7 @@ import java.lang.reflect.Method;
|
||||
public class TimeIntervalAspect extends SimpleAspect {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private TimeInterval interval = new TimeInterval();
|
||||
private final TimeInterval interval = new TimeInterval();
|
||||
|
||||
@Override
|
||||
public boolean before(Object target, Method method, Object[] args) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package cn.hutool.aop.interceptor;
|
||||
|
||||
import cn.hutool.aop.aspects.Aspect;
|
||||
import cn.hutool.core.lang.Console;
|
||||
import net.sf.cglib.proxy.MethodInterceptor;
|
||||
import net.sf.cglib.proxy.MethodProxy;
|
||||
|
||||
@@ -42,6 +43,7 @@ public class CglibInterceptor implements MethodInterceptor, Serializable {
|
||||
if (aspect.before(target, method, args)) {
|
||||
try {
|
||||
// result = proxy.invokeSuper(obj, args);
|
||||
Console.log(target);
|
||||
result = proxy.invoke(target, args);
|
||||
} catch (InvocationTargetException e) {
|
||||
// 异常回调(只捕获业务代码导致的异常,而非反射导致的异常)
|
||||
|
||||
@@ -18,8 +18,8 @@ import java.lang.reflect.Method;
|
||||
public class JdkInterceptor implements InvocationHandler, Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Object target;
|
||||
private Aspect aspect;
|
||||
private final Object target;
|
||||
private final Aspect aspect;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
package cn.hutool.aop.interceptor;
|
||||
|
||||
import cn.hutool.aop.aspects.Aspect;
|
||||
import org.springframework.cglib.proxy.MethodInterceptor;
|
||||
import org.springframework.cglib.proxy.MethodProxy;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* Spring-cglib实现的动态代理切面
|
||||
*
|
||||
* @author looly
|
||||
*/
|
||||
public class SpringCglibInterceptor implements MethodInterceptor, Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final Object target;
|
||||
private final Aspect aspect;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param target 被代理对象
|
||||
* @param aspect 切面实现
|
||||
*/
|
||||
public SpringCglibInterceptor(Object target, Aspect aspect) {
|
||||
this.target = target;
|
||||
this.aspect = aspect;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得目标对象
|
||||
*
|
||||
* @return 目标对象
|
||||
*/
|
||||
public Object getTarget() {
|
||||
return this.target;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
|
||||
final Object target = this.target;
|
||||
Object result = null;
|
||||
// 开始前回调
|
||||
if (aspect.before(target, method, args)) {
|
||||
try {
|
||||
// result = proxy.invokeSuper(obj, args);
|
||||
result = proxy.invoke(target, args);
|
||||
} catch (InvocationTargetException e) {
|
||||
// 异常回调(只捕获业务代码导致的异常,而非反射导致的异常)
|
||||
if (aspect.afterException(target, method, args, e.getTargetException())) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 结束执行回调
|
||||
if (aspect.after(target, method, args, result)) {
|
||||
return result;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,33 @@
|
||||
package cn.hutool.aop.proxy;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import cn.hutool.aop.aspects.Aspect;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import cn.hutool.core.util.ServiceLoaderUtil;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 代理工厂<br>
|
||||
* 根据用户引入代理库的不同,产生不同的代理对象
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
public abstract class ProxyFactory implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 创建代理
|
||||
*
|
||||
* @param <T> 代理对象类型
|
||||
* @param target 被代理对象
|
||||
* @param aspectClass 切面实现类,自动实例化
|
||||
* @return 代理对象
|
||||
* @since 5.3.1
|
||||
*/
|
||||
public <T> T proxy(T target, Class<? extends Aspect> aspectClass) {
|
||||
return proxy(target, ReflectUtil.newInstanceIfPossible(aspectClass));
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建代理
|
||||
*
|
||||
@@ -55,11 +68,6 @@ public abstract class ProxyFactory implements Serializable{
|
||||
* @return 代理工厂
|
||||
*/
|
||||
public static ProxyFactory create() {
|
||||
try {
|
||||
return new CglibProxyFactory();
|
||||
} catch (NoClassDefFoundError e) {
|
||||
// ignore
|
||||
}
|
||||
return new JdkProxyFactory();
|
||||
return ServiceLoaderUtil.loadFirstAvailable(ProxyFactory.class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package cn.hutool.aop.proxy;
|
||||
|
||||
import cn.hutool.aop.aspects.Aspect;
|
||||
import cn.hutool.aop.interceptor.SpringCglibInterceptor;
|
||||
import org.springframework.cglib.proxy.Enhancer;
|
||||
|
||||
/**
|
||||
* 基于Spring-cglib的切面代理工厂
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
public class SpringCglibProxyFactory extends ProxyFactory{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T proxy(T target, Aspect aspect) {
|
||||
final Enhancer enhancer = new Enhancer();
|
||||
enhancer.setSuperclass(target.getClass());
|
||||
enhancer.setCallback(new SpringCglibInterceptor(target, aspect));
|
||||
return (T) enhancer.create();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
cn.hutool.aop.proxy.CglibProxyFactory
|
||||
cn.hutool.aop.proxy.SpringCglibProxyFactory
|
||||
cn.hutool.aop.proxy.JdkProxyFactory
|
||||
@@ -23,7 +23,7 @@ public class AopTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void aopByCglibTest() {
|
||||
public void aopByAutoCglibTest() {
|
||||
Dog dog = ProxyUtil.proxy(new Dog(), TimeIntervalAspect.class);
|
||||
String result = dog.eat();
|
||||
Assert.assertEquals("狗吃肉", result);
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.2.5-SNAPSHOT</version>
|
||||
<version>5.3.6-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-bloomFilter</artifactId>
|
||||
|
||||
@@ -23,10 +23,11 @@ public class BitMapBloomFilter implements BloomFilter{
|
||||
|
||||
/**
|
||||
* 构造,使用默认的5个过滤器
|
||||
*
|
||||
* @param m M值决定BitMap的大小
|
||||
*/
|
||||
public BitMapBloomFilter(int m) {
|
||||
int mNum =NumberUtil.div(String.valueOf(m), String.valueOf(5)).intValue();
|
||||
long mNum = NumberUtil.div(String.valueOf(m), String.valueOf(5)).longValue();
|
||||
long size = mNum * 1024 * 1024 * 8;
|
||||
|
||||
filters = new BloomFilter[]{
|
||||
@@ -51,11 +52,12 @@ public class BitMapBloomFilter implements BloomFilter{
|
||||
|
||||
/**
|
||||
* 增加字符串到Filter映射中
|
||||
*
|
||||
* @param str 字符串
|
||||
*/
|
||||
@Override
|
||||
public boolean add(String str) {
|
||||
boolean flag = true;
|
||||
boolean flag = false;
|
||||
for (BloomFilter filter : filters) {
|
||||
flag |= filter.add(str);
|
||||
}
|
||||
@@ -64,6 +66,7 @@ public class BitMapBloomFilter implements BloomFilter{
|
||||
|
||||
/**
|
||||
* 是否可能包含此字符串,此处存在误判
|
||||
*
|
||||
* @param str 字符串
|
||||
* @return 是否存在
|
||||
*/
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package cn.hutool.bloomfilter;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.util.BitSet;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.util.HashUtil;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.util.BitSet;
|
||||
|
||||
/**
|
||||
* BloomFilter实现方式2,此方式使用BitSet存储。<br>
|
||||
* Hash算法的使用使用固定顺序,只需指定个数即可
|
||||
@@ -17,10 +17,10 @@ import cn.hutool.core.util.HashUtil;
|
||||
public class BitSetBloomFilter implements BloomFilter{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private BitSet bitSet;
|
||||
private int bitSetSize;
|
||||
private int addedElements;
|
||||
private int hashFunctionNumber;
|
||||
private final BitSet bitSet;
|
||||
private final int bitSetSize;
|
||||
private final int addedElements;
|
||||
private final int hashFunctionNumber;
|
||||
|
||||
/**
|
||||
* 构造一个布隆过滤器,过滤器的容量为c * n 个bit.
|
||||
|
||||
@@ -11,7 +11,7 @@ import java.io.Serializable;
|
||||
public class IntMap implements BitMap, Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private int[] ints;
|
||||
private final int[] ints;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
|
||||
@@ -11,7 +11,7 @@ import java.io.Serializable;
|
||||
public class LongMap implements BitMap, Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private long[] longs;
|
||||
private final long[] longs;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.2.5-SNAPSHOT</version>
|
||||
<version>5.3.6-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-bom</artifactId>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.2.5-SNAPSHOT</version>
|
||||
<version>5.3.6-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-cache</artifactId>
|
||||
|
||||
@@ -24,7 +24,7 @@ public enum GlobalPruneTimer {
|
||||
/**
|
||||
* 缓存任务计数
|
||||
*/
|
||||
private AtomicInteger cacheTaskNumber = new AtomicInteger(1);
|
||||
private final AtomicInteger cacheTaskNumber = new AtomicInteger(1);
|
||||
|
||||
/**
|
||||
* 定时器
|
||||
@@ -56,7 +56,7 @@ public enum GlobalPruneTimer {
|
||||
if (null != pruneTimer) {
|
||||
shutdownNow();
|
||||
}
|
||||
this.pruneTimer = new ScheduledThreadPoolExecutor(16, r -> ThreadUtil.newThread(r, StrUtil.format("Pure-Timer-{}", cacheTaskNumber.getAndIncrement())));
|
||||
this.pruneTimer = new ScheduledThreadPoolExecutor(1, r -> ThreadUtil.newThread(r, StrUtil.format("Pure-Timer-{}", cacheTaskNumber.getAndIncrement())));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -6,9 +6,7 @@ import cn.hutool.core.lang.func.Func0;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
|
||||
import java.util.concurrent.locks.StampedLock;
|
||||
|
||||
/**
|
||||
* 超时和限制大小的缓存的默认实现<br>
|
||||
@@ -18,31 +16,38 @@ import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
|
||||
* <li>实现 <code>prune</code> 策略</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author Looly,jodd
|
||||
*
|
||||
* @param <K> 键类型
|
||||
* @param <V> 值类型
|
||||
* @author Looly, jodd
|
||||
*/
|
||||
public abstract class AbstractCache<K, V> implements Cache<K, V> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
protected Map<K, CacheObj<K, V>> cacheMap;
|
||||
|
||||
private final ReentrantReadWriteLock cacheLock = new ReentrantReadWriteLock();
|
||||
private final ReadLock readLock = cacheLock.readLock();
|
||||
private final WriteLock writeLock = cacheLock.writeLock();
|
||||
private final StampedLock lock = new StampedLock();
|
||||
|
||||
/** 返回缓存容量,<code>0</code>表示无大小限制 */
|
||||
/**
|
||||
* 返回缓存容量,<code>0</code>表示无大小限制
|
||||
*/
|
||||
protected int capacity;
|
||||
/** 缓存失效时长, <code>0</code> 表示无限制,单位毫秒 */
|
||||
/**
|
||||
* 缓存失效时长, <code>0</code> 表示无限制,单位毫秒
|
||||
*/
|
||||
protected long timeout;
|
||||
|
||||
/** 每个对象是否有单独的失效时长,用于决定清理过期对象是否有必要。 */
|
||||
/**
|
||||
* 每个对象是否有单独的失效时长,用于决定清理过期对象是否有必要。
|
||||
*/
|
||||
protected boolean existCustomTimeout;
|
||||
|
||||
/** 命中数 */
|
||||
/**
|
||||
* 命中数
|
||||
*/
|
||||
protected int hitCount;
|
||||
/** 丢失数 */
|
||||
/**
|
||||
* 丢失数
|
||||
*/
|
||||
protected int missCount;
|
||||
|
||||
// ---------------------------------------------------------------- put start
|
||||
@@ -53,12 +58,11 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
|
||||
|
||||
@Override
|
||||
public void put(K key, V object, long timeout) {
|
||||
writeLock.lock();
|
||||
|
||||
final long stamp = lock.writeLock();
|
||||
try {
|
||||
putWithoutLock(key, object, timeout);
|
||||
} finally {
|
||||
writeLock.unlock();
|
||||
lock.unlockWrite(stamp);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,8 +89,7 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
|
||||
// ---------------------------------------------------------------- get start
|
||||
@Override
|
||||
public boolean containsKey(K key) {
|
||||
readLock.lock();
|
||||
|
||||
final long stamp = lock.readLock();
|
||||
try {
|
||||
// 不存在或已移除
|
||||
final CacheObj<K, V> co = cacheMap.get(key);
|
||||
@@ -99,7 +102,7 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
|
||||
return true;
|
||||
}
|
||||
} finally {
|
||||
readLock.unlock();
|
||||
lock.unlockRead(stamp);
|
||||
}
|
||||
|
||||
// 过期
|
||||
@@ -111,24 +114,14 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
|
||||
* @return 命中数
|
||||
*/
|
||||
public int getHitCount() {
|
||||
this.readLock.lock();
|
||||
try {
|
||||
return hitCount;
|
||||
} finally {
|
||||
this.readLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 丢失数
|
||||
*/
|
||||
public int getMissCount() {
|
||||
this.readLock.lock();
|
||||
try {
|
||||
return missCount;
|
||||
} finally {
|
||||
this.readLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -140,11 +133,11 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
|
||||
public V get(K key, Func0<V> supplier) {
|
||||
V v = get(key);
|
||||
if (null == v && null != supplier) {
|
||||
writeLock.lock();
|
||||
final long stamp = lock.writeLock();
|
||||
try {
|
||||
// 双重检查锁
|
||||
final CacheObj<K, V> co = cacheMap.get(key);
|
||||
if(null == co || co.isExpired() || null == co.getValue()) {
|
||||
if (null == co || co.isExpired()) {
|
||||
try {
|
||||
v = supplier.call();
|
||||
} catch (Exception e) {
|
||||
@@ -155,7 +148,7 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
|
||||
v = co.get(true);
|
||||
}
|
||||
} finally {
|
||||
writeLock.unlock();
|
||||
lock.unlockWrite(stamp);
|
||||
}
|
||||
}
|
||||
return v;
|
||||
@@ -163,23 +156,25 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
|
||||
|
||||
@Override
|
||||
public V get(K key, boolean isUpdateLastAccess) {
|
||||
readLock.lock();
|
||||
|
||||
// 尝试读取缓存,使用乐观读锁
|
||||
long stamp = lock.readLock();
|
||||
try {
|
||||
// 不存在或已移除
|
||||
final CacheObj<K, V> co = cacheMap.get(key);
|
||||
if (co == null) {
|
||||
if (null == co) {
|
||||
missCount++;
|
||||
return null;
|
||||
}
|
||||
|
||||
if (false == co.isExpired()) {
|
||||
if (co.isExpired()) {
|
||||
missCount++;
|
||||
} else{
|
||||
// 命中
|
||||
hitCount++;
|
||||
return co.get(isUpdateLastAccess);
|
||||
}
|
||||
} finally {
|
||||
readLock.unlock();
|
||||
lock.unlock(stamp);
|
||||
}
|
||||
|
||||
// 过期
|
||||
@@ -189,7 +184,6 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
|
||||
|
||||
// ---------------------------------------------------------------- get end
|
||||
|
||||
@SuppressWarnings("NullableProblems")
|
||||
@Override
|
||||
public Iterator<V> iterator() {
|
||||
CacheObjIterator<K, V> copiedIterator = (CacheObjIterator<K, V>) this.cacheObjIterator();
|
||||
@@ -199,18 +193,20 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
|
||||
@Override
|
||||
public Iterator<CacheObj<K, V>> cacheObjIterator() {
|
||||
CopiedIter<CacheObj<K, V>> copiedIterator;
|
||||
readLock.lock();
|
||||
final long stamp = lock.readLock();
|
||||
try {
|
||||
copiedIterator = CopiedIter.copyOf(this.cacheMap.values().iterator());
|
||||
} finally {
|
||||
readLock.unlock();
|
||||
lock.unlockRead(stamp);
|
||||
}
|
||||
return new CacheObjIterator<>(copiedIterator);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- prune start
|
||||
|
||||
/**
|
||||
* 清理实现
|
||||
* 清理实现<br>
|
||||
* 子类实现此方法时无需加锁
|
||||
*
|
||||
* @return 清理数
|
||||
*/
|
||||
@@ -218,11 +214,11 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
|
||||
|
||||
@Override
|
||||
public final int prune() {
|
||||
writeLock.lock();
|
||||
final long stamp = lock.writeLock();
|
||||
try {
|
||||
return pruneCache();
|
||||
} finally {
|
||||
writeLock.unlock();
|
||||
lock.unlockWrite(stamp);
|
||||
}
|
||||
}
|
||||
// ---------------------------------------------------------------- prune end
|
||||
@@ -248,22 +244,12 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
|
||||
* @return 过期对象清理是否可用,内部使用
|
||||
*/
|
||||
protected boolean isPruneExpiredActive() {
|
||||
this.readLock.lock();
|
||||
try {
|
||||
return (timeout != 0) || existCustomTimeout;
|
||||
} finally {
|
||||
this.readLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFull() {
|
||||
this.readLock.lock();
|
||||
try {
|
||||
return (capacity > 0) && (cacheMap.size() >= capacity);
|
||||
} finally {
|
||||
this.readLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -273,42 +259,27 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
writeLock.lock();
|
||||
final long stamp = lock.writeLock();
|
||||
try {
|
||||
cacheMap.clear();
|
||||
} finally {
|
||||
writeLock.unlock();
|
||||
lock.unlockWrite(stamp);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
this.readLock.lock();
|
||||
try {
|
||||
return cacheMap.size();
|
||||
} finally {
|
||||
this.readLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
this.readLock.lock();
|
||||
try {
|
||||
return cacheMap.isEmpty();
|
||||
} finally {
|
||||
this.readLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
this.readLock.lock();
|
||||
try {
|
||||
return this.cacheMap.toString();
|
||||
} finally {
|
||||
this.readLock.unlock();
|
||||
}
|
||||
}
|
||||
// ---------------------------------------------------------------- common end
|
||||
|
||||
@@ -329,12 +300,12 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
|
||||
* @param withMissCount 是否计数丢失数
|
||||
*/
|
||||
private void remove(K key, boolean withMissCount) {
|
||||
writeLock.lock();
|
||||
final long stamp = lock.writeLock();
|
||||
CacheObj<K, V> co;
|
||||
try {
|
||||
co = removeWithoutLock(key, withMissCount);
|
||||
} finally {
|
||||
writeLock.unlock();
|
||||
lock.unlockWrite(stamp);
|
||||
}
|
||||
if (null != co) {
|
||||
onRemove(co.key, co.obj);
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package cn.hutool.cache.impl;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
import cn.hutool.cache.Cache;
|
||||
import cn.hutool.core.lang.func.Func0;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* 无缓存实现,用于快速关闭缓存
|
||||
*
|
||||
@@ -61,8 +61,18 @@ public class NoCache<K, V> implements Cache<K, V> {
|
||||
|
||||
@Override
|
||||
public Iterator<V> iterator() {
|
||||
return new Iterator<V>() {
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public V next() {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<CacheObj<K, V>> cacheObjIterator() {
|
||||
|
||||
@@ -28,7 +28,7 @@ public class TimedCache<K, V> extends AbstractCache<K, V> {
|
||||
* @param timeout 超时(过期)时长,单位毫秒
|
||||
*/
|
||||
public TimedCache(long timeout) {
|
||||
this(timeout, new HashMap<K, CacheObj<K, V>>());
|
||||
this(timeout, new HashMap<>());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
package cn.hutool.cache.test;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import cn.hutool.cache.Cache;
|
||||
import cn.hutool.cache.impl.FIFOCache;
|
||||
import cn.hutool.cache.impl.LRUCache;
|
||||
import cn.hutool.core.lang.Console;
|
||||
import cn.hutool.core.thread.ThreadUtil;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* 缓存单元测试
|
||||
@@ -28,9 +25,7 @@ public class CacheConcurrentTest {
|
||||
// 由于缓存容量只有3,当加入第四个元素的时候,根据FIFO规则,最先放入的对象将被移除
|
||||
|
||||
for (int i = 0; i < threadCount; i++) {
|
||||
ThreadUtil.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
ThreadUtil.execute(() -> {
|
||||
cache.put("key1", "value1", System.currentTimeMillis() * 3);
|
||||
cache.put("key2", "value2", System.currentTimeMillis() * 3);
|
||||
cache.put("key3", "value3", System.currentTimeMillis() * 3);
|
||||
@@ -41,17 +36,11 @@ public class CacheConcurrentTest {
|
||||
cache.put("key7", "value7", System.currentTimeMillis() * 3);
|
||||
cache.put("key8", "value8", System.currentTimeMillis() * 3);
|
||||
Console.log("put all");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for (int i = 0; i < threadCount; i++) {
|
||||
ThreadUtil.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
show(cache);
|
||||
}
|
||||
});
|
||||
ThreadUtil.execute(() -> show(cache));
|
||||
}
|
||||
|
||||
System.out.println("==============================");
|
||||
@@ -66,9 +55,7 @@ public class CacheConcurrentTest {
|
||||
|
||||
for (int i = 0; i < threadCount; i++) {
|
||||
final int index = i;
|
||||
ThreadUtil.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
ThreadUtil.execute(() -> {
|
||||
cache.put("key1"+ index, "value1");
|
||||
cache.put("key2"+ index, "value2", System.currentTimeMillis() * 3);
|
||||
|
||||
@@ -83,7 +70,6 @@ public class CacheConcurrentTest {
|
||||
if(size > capacity) {
|
||||
Console.log("## {} {}", size, capacity);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -91,10 +77,8 @@ public class CacheConcurrentTest {
|
||||
}
|
||||
|
||||
private void show(Cache<String, String> cache) {
|
||||
Iterator<?> its = cache.iterator();
|
||||
|
||||
while (its.hasNext()) {
|
||||
Object tt = its.next();
|
||||
for (Object tt : cache) {
|
||||
Console.log(tt);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.2.5-SNAPSHOT</version>
|
||||
<version>5.3.6-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-captcha</artifactId>
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
package cn.hutool.captcha;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Image;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
import cn.hutool.core.img.GraphicsUtil;
|
||||
import cn.hutool.core.img.ImgUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Image;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
/**
|
||||
* 圆圈干扰验证码
|
||||
*
|
||||
@@ -73,7 +72,7 @@ public class CircleCaptcha extends AbstractCaptcha {
|
||||
/**
|
||||
* 绘制字符串
|
||||
*
|
||||
* @param g {@link Graphics}画笔
|
||||
* @param g {@link Graphics2D}画笔
|
||||
* @param code 验证码
|
||||
*/
|
||||
private void drawString(Graphics2D g, String code) {
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
package cn.hutool.captcha;
|
||||
|
||||
import cn.hutool.core.img.GraphicsUtil;
|
||||
import cn.hutool.core.img.ImgUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Image;
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
import cn.hutool.core.img.GraphicsUtil;
|
||||
import cn.hutool.core.img.ImgUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
|
||||
/**
|
||||
* 扭曲干扰验证码
|
||||
*
|
||||
@@ -110,19 +110,16 @@ public class ShearCaptcha extends AbstractCaptcha {
|
||||
|
||||
int period = RandomUtil.randomInt(this.width);
|
||||
|
||||
boolean borderGap = true;
|
||||
int frames = 1;
|
||||
int phase = RandomUtil.randomInt(2);
|
||||
|
||||
for (int i = 0; i < h1; i++) {
|
||||
double d = (double) (period >> 1) * Math.sin((double) i / (double) period + (6.2831853071795862D * (double) phase) / (double) frames);
|
||||
g.copyArea(0, i, w1, 1, (int) d, 0);
|
||||
if (borderGap) {
|
||||
g.setColor(color);
|
||||
g.drawLine((int) d, i, 0, i);
|
||||
g.drawLine((int) d + w1, i, w1, i);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -162,6 +159,7 @@ public class ShearCaptcha extends AbstractCaptcha {
|
||||
* @param thickness 粗细
|
||||
* @param c 颜色
|
||||
*/
|
||||
@SuppressWarnings("SameParameterValue")
|
||||
private void drawInterfere(Graphics g, int x1, int y1, int x2, int y2, int thickness, Color c) {
|
||||
|
||||
// The thick line is in fact a filled polygon
|
||||
|
||||
@@ -16,7 +16,7 @@ public class MathGenerator implements CodeGenerator {
|
||||
private static final String operators = "+-*";
|
||||
|
||||
/** 参与计算数字最大长度 */
|
||||
private int numberLength;
|
||||
private final int numberLength;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.2.5-SNAPSHOT</version>
|
||||
<version>5.3.6-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-core</artifactId>
|
||||
|
||||
@@ -1,14 +1,5 @@
|
||||
package cn.hutool.core.bean;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import cn.hutool.core.annotation.Alias;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.map.CaseInsensitiveMap;
|
||||
import cn.hutool.core.util.BooleanUtil;
|
||||
@@ -18,6 +9,14 @@ import cn.hutool.core.util.ReflectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.core.util.TypeUtil;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Bean信息描述做为BeanInfo替代方案,此对象持有JavaBean中的setters和getters等相关信息描述<br>
|
||||
* 查找Getter和Setter方法时会:
|
||||
@@ -142,7 +141,7 @@ public class BeanDesc implements Serializable{
|
||||
for (Field field : ReflectUtil.getFields(this.beanClass)) {
|
||||
if(false == ModifierUtil.isStatic(field)) {
|
||||
//只针对非static属性
|
||||
this.propMap.put(field.getName(), createProp(field));
|
||||
this.propMap.put(ReflectUtil.getFieldName(field), createProp(field));
|
||||
}
|
||||
}
|
||||
return this;
|
||||
@@ -322,7 +321,7 @@ public class BeanDesc implements Serializable{
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字段名,如果存在{@link Alias}注解,读取注解的值作为名称
|
||||
* 获取字段名,如果存在Alias注解,读取注解的值作为名称
|
||||
*
|
||||
* @return 字段名
|
||||
*/
|
||||
|
||||
@@ -1,13 +1,5 @@
|
||||
package cn.hutool.core.bean;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
@@ -16,6 +8,14 @@ import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.CharUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Bean路径表达式,用于获取多层嵌套Bean中的字段值或Bean对象<br>
|
||||
* 根据给定的表达式,查找Bean中对应的属性值对象。 表达式分为两种:
|
||||
@@ -242,10 +242,6 @@ public class BeanPath implements Serializable{
|
||||
}
|
||||
isNumStart = false;
|
||||
// 中括号结束加入下标
|
||||
if (builder.length() > 0) {
|
||||
localPatternParts.add(unWrapIfPossible(builder));
|
||||
}
|
||||
builder.reset();
|
||||
} else {
|
||||
if (isNumStart) {
|
||||
// 非结束中括号情况下发现起始中括号报错(中括号未关闭)
|
||||
@@ -255,11 +251,11 @@ public class BeanPath implements Serializable{
|
||||
isNumStart = true;
|
||||
}
|
||||
// 每一个边界符之前的表达式是一个完整的KEY,开始处理KEY
|
||||
}
|
||||
if (builder.length() > 0) {
|
||||
localPatternParts.add(unWrapIfPossible(builder));
|
||||
}
|
||||
builder.reset();
|
||||
}
|
||||
} else {
|
||||
// 非边界符号,追加字符
|
||||
builder.append(c);
|
||||
|
||||
@@ -42,6 +42,23 @@ import java.util.Map;
|
||||
*/
|
||||
public class BeanUtil {
|
||||
|
||||
/**
|
||||
* 判断是否为可读的Bean对象,判定方法是:
|
||||
*
|
||||
* <pre>
|
||||
* 1、是否存在只有无参数的getXXX方法或者isXXX方法
|
||||
* 2、是否存在public类型的字段
|
||||
* </pre>
|
||||
*
|
||||
* @param clazz 待测试类
|
||||
* @return 是否为可读的Bean对象
|
||||
* @see #hasGetter(Class)
|
||||
* @see #hasPublicField(Class)
|
||||
*/
|
||||
public static boolean isReadableBean(Class<?> clazz) {
|
||||
return hasGetter(clazz) || hasPublicField(clazz);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为Bean对象,判定方法是:
|
||||
*
|
||||
@@ -53,6 +70,7 @@ public class BeanUtil {
|
||||
* @param clazz 待测试类
|
||||
* @return 是否为Bean对象
|
||||
* @see #hasSetter(Class)
|
||||
* @see #hasPublicField(Class)
|
||||
*/
|
||||
public static boolean isBean(Class<?> clazz) {
|
||||
return hasSetter(clazz) || hasPublicField(clazz);
|
||||
@@ -458,7 +476,7 @@ public class BeanUtil {
|
||||
* @since 5.2.4
|
||||
*/
|
||||
public static <T> T toBean(Object source, Class<T> clazz, CopyOptions options) {
|
||||
final T target = ReflectUtil.newInstance(clazz);
|
||||
final T target = ReflectUtil.newInstanceIfPossible(clazz);
|
||||
copyProperties(source, target, options);
|
||||
return target;
|
||||
}
|
||||
@@ -596,7 +614,7 @@ public class BeanUtil {
|
||||
* @return 目标对象
|
||||
*/
|
||||
public static <T> T copyProperties(Object source, Class<T> tClass) {
|
||||
T target = ReflectUtil.newInstance(tClass);
|
||||
T target = ReflectUtil.newInstanceIfPossible(tClass);
|
||||
copyProperties(source, target, CopyOptions.create());
|
||||
return target;
|
||||
}
|
||||
@@ -676,7 +694,7 @@ public class BeanUtil {
|
||||
*/
|
||||
public static <T> T trimStrFields(T bean, String... ignoreFields) {
|
||||
if (bean == null) {
|
||||
return bean;
|
||||
return null;
|
||||
}
|
||||
|
||||
final Field[] fields = ReflectUtil.getFields(bean.getClass());
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
package cn.hutool.core.bean;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
|
||||
import cn.hutool.core.clone.CloneSupport;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.ClassUtil;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 动态Bean,通过反射对Bean的相关方法做操作<br>
|
||||
* 支持Map和普通Bean
|
||||
@@ -19,8 +19,8 @@ import cn.hutool.core.util.ReflectUtil;
|
||||
public class DynaBean extends CloneSupport<DynaBean> implements Serializable{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Class<?> beanClass;
|
||||
private Object bean;
|
||||
private final Class<?> beanClass;
|
||||
private final Object bean;
|
||||
|
||||
/**
|
||||
* 创建一个{@link DynaBean}
|
||||
|
||||
@@ -130,7 +130,10 @@ public class BeanCopier<T> implements Copier<T>, Serializable {
|
||||
* @param bean Bean
|
||||
*/
|
||||
private void mapToBean(Map<?, ?> map, Object bean) {
|
||||
valueProviderToBean(new MapValueProvider(map, this.copyOptions.ignoreCase), bean);
|
||||
valueProviderToBean(
|
||||
new MapValueProvider(map, this.copyOptions.ignoreCase, this.copyOptions.ignoreError),
|
||||
bean
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -264,7 +267,7 @@ public class BeanCopier<T> implements Copier<T>, Serializable {
|
||||
if (null == value && copyOptions.ignoreNullValue) {
|
||||
continue;// 当允许跳过空时,跳过
|
||||
}
|
||||
if (bean.equals(value)) {
|
||||
if (bean == value) {
|
||||
continue;// 值不能为bean本身,防止循环引用
|
||||
}
|
||||
|
||||
|
||||
@@ -2,8 +2,6 @@ package cn.hutool.core.bean.copier;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
import cn.hutool.core.convert.Convert;
|
||||
|
||||
/**
|
||||
* 值提供者,用于提供Bean注入时参数对应值得抽象接口<br>
|
||||
* 继承或匿名实例化此接口<br>
|
||||
@@ -17,10 +15,10 @@ public interface ValueProvider<T>{
|
||||
|
||||
/**
|
||||
* 获取值<br>
|
||||
* 返回值一般需要匹配被注入类型,如果不匹配会调用默认转换 {@link Convert#convert(Type, Object)}实现转换
|
||||
* 返回值一般需要匹配被注入类型,如果不匹配会调用默认转换 Convert#convert(Type, Object)实现转换
|
||||
*
|
||||
* @param key Bean对象中参数名
|
||||
* @param valueType 被注入的值得类型
|
||||
* @param valueType 被注入的值的类型
|
||||
* @return 对应参数名的值
|
||||
*/
|
||||
Object value(T key, Type valueType);
|
||||
|
||||
@@ -3,6 +3,7 @@ package cn.hutool.core.bean.copier.provider;
|
||||
import cn.hutool.core.bean.BeanDesc.PropDesc;
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.bean.copier.ValueProvider;
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.exceptions.UtilException;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
@@ -43,19 +44,26 @@ public class BeanValueProvider implements ValueProvider<String> {
|
||||
sourcePd = sourcePdMap.get(StrUtil.upperFirstAndAddPre(key, "is"));
|
||||
}
|
||||
|
||||
Object result = null;
|
||||
if (null != sourcePd) {
|
||||
final Method getter = sourcePd.getGetter();
|
||||
if (null != getter) {
|
||||
try {
|
||||
return getter.invoke(source);
|
||||
result = getter.invoke(source);
|
||||
} catch (Exception e) {
|
||||
if (false == ignoreError) {
|
||||
throw new UtilException(e, "Inject [{}] error!", key);
|
||||
}
|
||||
}
|
||||
|
||||
// 尝试将结果转换为目标类型,如果转换失败,返回原类型。
|
||||
final Object convertValue = Convert.convertWithCheck(valueType,result, null, ignoreError);
|
||||
if(null != convertValue){
|
||||
result = convertValue;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -16,7 +16,8 @@ import java.util.Map;
|
||||
*/
|
||||
public class MapValueProvider implements ValueProvider<String> {
|
||||
|
||||
private Map<?, ?> map;
|
||||
private final Map<?, ?> map;
|
||||
private final boolean ignoreError;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
@@ -25,6 +26,18 @@ public class MapValueProvider implements ValueProvider<String> {
|
||||
* @param ignoreCase 是否忽略key的大小写
|
||||
*/
|
||||
public MapValueProvider(Map<?, ?> map, boolean ignoreCase) {
|
||||
this(map, ignoreCase, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param map Map
|
||||
* @param ignoreCase 是否忽略key的大小写
|
||||
* @param ignoreError 是否忽略错误
|
||||
* @since 5.3.2
|
||||
*/
|
||||
public MapValueProvider(Map<?, ?> map, boolean ignoreCase, boolean ignoreError) {
|
||||
if(false == ignoreCase || map instanceof CaseInsensitiveMap) {
|
||||
//不忽略大小写或者提供的Map本身为CaseInsensitiveMap则无需转换
|
||||
this.map = map;
|
||||
@@ -32,6 +45,7 @@ public class MapValueProvider implements ValueProvider<String> {
|
||||
//转换为大小写不敏感的Map
|
||||
this.map = new CaseInsensitiveMap<>(map);
|
||||
}
|
||||
this.ignoreError = ignoreError;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -42,15 +56,15 @@ public class MapValueProvider implements ValueProvider<String> {
|
||||
value = map.get(StrUtil.toUnderlineCase(key));
|
||||
}
|
||||
|
||||
return Convert.convert(valueType, value);
|
||||
return Convert.convertWithCheck(valueType, value, null, this.ignoreError);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsKey(String key) {
|
||||
//检查下划线模式
|
||||
if(map.containsKey(key)) {
|
||||
return true;
|
||||
}else {
|
||||
//检查下划线模式
|
||||
return map.containsKey(StrUtil.toUnderlineCase(key));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package cn.hutool.core.builder;
|
||||
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
|
||||
import java.lang.reflect.AccessibleObject;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
|
||||
/**
|
||||
* 用于构建 {@link java.lang.Comparable#compareTo(Object)} 方法的辅助工具
|
||||
*
|
||||
@@ -418,7 +418,7 @@ public class CompareToBuilder implements Builder<Integer> {
|
||||
if (comparison != 0) {
|
||||
return this;
|
||||
}
|
||||
comparison = ((lhs < rhs) ? -1 : ((lhs > rhs) ? 1 : 0));
|
||||
comparison = (Long.compare(lhs, rhs));
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -434,7 +434,7 @@ public class CompareToBuilder implements Builder<Integer> {
|
||||
if (comparison != 0) {
|
||||
return this;
|
||||
}
|
||||
comparison = ((lhs < rhs) ? -1 : ((lhs > rhs) ? 1 : 0));
|
||||
comparison = (Integer.compare(lhs, rhs));
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -450,7 +450,7 @@ public class CompareToBuilder implements Builder<Integer> {
|
||||
if (comparison != 0) {
|
||||
return this;
|
||||
}
|
||||
comparison = ((lhs < rhs) ? -1 : ((lhs > rhs) ? 1 : 0));
|
||||
comparison = (Short.compare(lhs, rhs));
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -466,7 +466,7 @@ public class CompareToBuilder implements Builder<Integer> {
|
||||
if (comparison != 0) {
|
||||
return this;
|
||||
}
|
||||
comparison = ((lhs < rhs) ? -1 : ((lhs > rhs) ? 1 : 0));
|
||||
comparison = (Character.compare(lhs, rhs));
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -482,7 +482,7 @@ public class CompareToBuilder implements Builder<Integer> {
|
||||
if (comparison != 0) {
|
||||
return this;
|
||||
}
|
||||
comparison = ((lhs < rhs) ? -1 : ((lhs > rhs) ? 1 : 0));
|
||||
comparison = (Byte.compare(lhs, rhs));
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
package cn.hutool.core.builder;
|
||||
|
||||
import cn.hutool.core.lang.Pair;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
|
||||
import java.lang.reflect.AccessibleObject;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
@@ -7,9 +10,6 @@ import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import cn.hutool.core.lang.Pair;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
|
||||
/**
|
||||
* <p>{@link Object#equals(Object)} 方法的构建器</p>
|
||||
*
|
||||
@@ -39,7 +39,6 @@ import cn.hutool.core.util.ArrayUtil;
|
||||
* return EqualsBuilder.reflectionEquals(this, obj);
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
*/
|
||||
public class EqualsBuilder implements Builder<Boolean> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
@@ -51,7 +50,7 @@ public class EqualsBuilder implements Builder<Boolean> {
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
private static final ThreadLocal<Set<Pair<IDKey, IDKey>>> REGISTRY = new ThreadLocal<Set<Pair<IDKey, IDKey>>>();
|
||||
private static final ThreadLocal<Set<Pair<IDKey, IDKey>>> REGISTRY = new ThreadLocal<>();
|
||||
|
||||
/**
|
||||
* <p>
|
||||
@@ -73,13 +72,12 @@ public class EqualsBuilder implements Builder<Boolean> {
|
||||
*
|
||||
* @param lhs <code>this</code> object
|
||||
* @param rhs the other object
|
||||
*
|
||||
* @return the pair
|
||||
*/
|
||||
static Pair<IDKey, IDKey> getRegisterPair(final Object lhs, final Object rhs) {
|
||||
final IDKey left = new IDKey(lhs);
|
||||
final IDKey right = new IDKey(rhs);
|
||||
return new Pair<IDKey, IDKey>(left, right);
|
||||
return new Pair<>(left, right);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -98,7 +96,7 @@ public class EqualsBuilder implements Builder<Boolean> {
|
||||
static boolean isRegistered(final Object lhs, final Object rhs) {
|
||||
final Set<Pair<IDKey, IDKey>> registry = getRegistry();
|
||||
final Pair<IDKey, IDKey> pair = getRegisterPair(lhs, rhs);
|
||||
final Pair<IDKey, IDKey> swappedPair = new Pair<IDKey, IDKey>(pair.getKey(), pair.getValue());
|
||||
final Pair<IDKey, IDKey> swappedPair = new Pair<>(pair.getKey(), pair.getValue());
|
||||
|
||||
return registry != null
|
||||
&& (registry.contains(pair) || registry.contains(swappedPair));
|
||||
@@ -116,7 +114,7 @@ public class EqualsBuilder implements Builder<Boolean> {
|
||||
static void register(final Object lhs, final Object rhs) {
|
||||
synchronized (EqualsBuilder.class) {
|
||||
if (getRegistry() == null) {
|
||||
REGISTRY.set(new HashSet<Pair<IDKey, IDKey>>());
|
||||
REGISTRY.set(new HashSet<>());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,7 +150,9 @@ public class EqualsBuilder implements Builder<Boolean> {
|
||||
}
|
||||
}
|
||||
|
||||
/** 是否equals,此值随着构建会变更,默认true */
|
||||
/**
|
||||
* 是否equals,此值随着构建会变更,默认true
|
||||
*/
|
||||
private boolean isEquals = true;
|
||||
|
||||
/**
|
||||
@@ -416,10 +416,8 @@ public class EqualsBuilder implements Builder<Boolean> {
|
||||
* Test if two <code>long</code> s are equal.
|
||||
* </p>
|
||||
*
|
||||
* @param lhs
|
||||
* the left hand <code>long</code>
|
||||
* @param rhs
|
||||
* the right hand <code>long</code>
|
||||
* @param lhs the left hand <code>long</code>
|
||||
* @param rhs the right hand <code>long</code>
|
||||
* @return EqualsBuilder - used to chain calls.
|
||||
*/
|
||||
public EqualsBuilder append(final long lhs, final long rhs) {
|
||||
@@ -840,12 +838,11 @@ public class EqualsBuilder implements Builder<Boolean> {
|
||||
*
|
||||
* @return <code>true</code> if all of the fields that have been checked
|
||||
* are equal, <code>false</code> otherwise.
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
@Override
|
||||
public Boolean build() {
|
||||
return Boolean.valueOf(isEquals());
|
||||
return isEquals();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -854,12 +851,13 @@ public class EqualsBuilder implements Builder<Boolean> {
|
||||
* @param isEquals The value to set.
|
||||
* @since 2.1
|
||||
*/
|
||||
protected void setEquals(final boolean isEquals) {
|
||||
protected void setEquals(boolean isEquals) {
|
||||
this.isEquals = isEquals;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the EqualsBuilder so you can use the same object again
|
||||
*
|
||||
* @since 2.5
|
||||
*/
|
||||
public void reset() {
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
package cn.hutool.core.codec;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
|
||||
/**
|
||||
* Base64工具类,提供Base64的编码和解码方案<br>
|
||||
* base64编码是用64(2的6次方)个ASCII字符来表示256(2的8次方)个ASCII字符,<br>
|
||||
@@ -72,7 +72,7 @@ public class Base64 {
|
||||
* @return 被加密后的字符串
|
||||
*/
|
||||
public static String encode(CharSequence source, String charset) {
|
||||
return Base64Encoder.encode(source, CharsetUtil.charset(charset));
|
||||
return encode(source, CharsetUtil.charset(charset));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -84,7 +84,7 @@ public class Base64 {
|
||||
* @since 3.0.6
|
||||
*/
|
||||
public static String encodeUrlSafe(CharSequence source, String charset) {
|
||||
return Base64Encoder.encodeUrlSafe(source, CharsetUtil.charset(charset));
|
||||
return encodeUrlSafe(source, CharsetUtil.charset(charset));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -272,7 +272,7 @@ public class Base64 {
|
||||
* @return 被加密后的字符串
|
||||
*/
|
||||
public static String decodeStr(CharSequence source, String charset) {
|
||||
return Base64Decoder.decodeStr(source, CharsetUtil.charset(charset));
|
||||
return decodeStr(source, CharsetUtil.charset(charset));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,7 +2,6 @@ package cn.hutool.core.collection;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Iterator;
|
||||
import java.util.PriorityQueue;
|
||||
@@ -80,7 +79,7 @@ public class BoundedPriorityQueue<E> extends PriorityQueue<E>{
|
||||
*/
|
||||
public ArrayList<E> toList() {
|
||||
final ArrayList<E> list = new ArrayList<>(this);
|
||||
Collections.sort(list, comparator);
|
||||
list.sort(comparator);
|
||||
return list;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import cn.hutool.core.exceptions.UtilException;
|
||||
import cn.hutool.core.lang.Editor;
|
||||
import cn.hutool.core.lang.Filter;
|
||||
import cn.hutool.core.lang.Matcher;
|
||||
import cn.hutool.core.lang.func.Func1;
|
||||
import cn.hutool.core.lang.hash.Hash32;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
@@ -21,7 +22,6 @@ import cn.hutool.core.util.TypeUtil;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.AbstractCollection;
|
||||
import java.util.AbstractMap;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
@@ -38,8 +38,10 @@ import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.NavigableSet;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.SortedSet;
|
||||
import java.util.Stack;
|
||||
import java.util.TreeMap;
|
||||
import java.util.TreeSet;
|
||||
@@ -47,6 +49,7 @@ import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.LinkedBlockingDeque;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* 集合相关工具类
|
||||
@@ -141,6 +144,74 @@ public class CollUtil {
|
||||
return union;
|
||||
}
|
||||
|
||||
/**
|
||||
* 多个集合的非重复并集,类似于SQL中的“UNION DISTINCT”<br>
|
||||
* 针对一个集合中存在多个相同元素的情况,只保留一个<br>
|
||||
* 例如:集合1:[a, b, c, c, c],集合2:[a, b, c, c]<br>
|
||||
* 结果:[a, b, c],此结果中只保留了一个c
|
||||
*
|
||||
* @param <T> 集合元素类型
|
||||
* @param coll1 集合1
|
||||
* @param coll2 集合2
|
||||
* @param otherColls 其它集合
|
||||
* @return 并集的集合,返回 {@link LinkedHashSet}
|
||||
*/
|
||||
@SafeVarargs
|
||||
public static <T> Set<T> unionDistinct(Collection<T> coll1, Collection<T> coll2, Collection<T>... otherColls) {
|
||||
final Set<T> result;
|
||||
if (isEmpty(coll1)) {
|
||||
result = new LinkedHashSet<>();
|
||||
} else {
|
||||
result = new LinkedHashSet<>(coll1);
|
||||
}
|
||||
|
||||
if (isNotEmpty(coll2)) {
|
||||
result.addAll(coll2);
|
||||
}
|
||||
|
||||
if (ArrayUtil.isNotEmpty(otherColls)) {
|
||||
for (Collection<T> otherColl : otherColls) {
|
||||
result.addAll(otherColl);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 多个集合的完全并集,类似于SQL中的“UNION ALL”<br>
|
||||
* 针对一个集合中存在多个相同元素的情况,保留全部元素<br>
|
||||
* 例如:集合1:[a, b, c, c, c],集合2:[a, b, c, c]<br>
|
||||
* 结果:[a, b, c, c, c, a, b, c, c]
|
||||
*
|
||||
* @param <T> 集合元素类型
|
||||
* @param coll1 集合1
|
||||
* @param coll2 集合2
|
||||
* @param otherColls 其它集合
|
||||
* @return 并集的集合,返回 {@link ArrayList}
|
||||
*/
|
||||
@SafeVarargs
|
||||
public static <T> List<T> unionAll(Collection<T> coll1, Collection<T> coll2, Collection<T>... otherColls) {
|
||||
final List<T> result;
|
||||
if (isEmpty(coll1)) {
|
||||
result = new ArrayList<>();
|
||||
} else {
|
||||
result = new ArrayList<>(coll1);
|
||||
}
|
||||
|
||||
if (isNotEmpty(coll2)) {
|
||||
result.addAll(coll2);
|
||||
}
|
||||
|
||||
if (ArrayUtil.isNotEmpty(otherColls)) {
|
||||
for (Collection<T> otherColl : otherColls) {
|
||||
result.addAll(otherColl);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 两个集合的交集<br>
|
||||
* 针对一个集合中存在多个相同元素的情况,计算两个集合中此元素的个数,保留最少的个数<br>
|
||||
@@ -222,7 +293,7 @@ public class CollUtil {
|
||||
return coll1;
|
||||
}
|
||||
|
||||
final ArrayList<T> result = new ArrayList<>();
|
||||
final List<T> result = new ArrayList<>();
|
||||
final Map<T, Integer> map1 = countMap(coll1);
|
||||
final Map<T, Integer> map2 = countMap(coll2);
|
||||
final Set<T> elts = newHashSet(coll2);
|
||||
@@ -255,6 +326,39 @@ public class CollUtil {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算集合的单差集,即只返回【集合1】中有,但是【集合2】中没有的元素,例如:
|
||||
*
|
||||
* <pre>
|
||||
* subtractToList([1,2,3,4],[2,3,4,5]) -》 [1]
|
||||
* </pre>
|
||||
*
|
||||
* @param coll1 集合1
|
||||
* @param coll2 集合2
|
||||
* @param <T> 元素类型
|
||||
* @return 单差集
|
||||
* @since 5.3.5
|
||||
*/
|
||||
public static <T> List<T> subtractToList(Collection<T> coll1, Collection<T> coll2) {
|
||||
|
||||
if (isEmpty(coll1)) {
|
||||
return ListUtil.empty();
|
||||
}
|
||||
if (isEmpty(coll2)) {
|
||||
return ListUtil.list(true, coll2);
|
||||
}
|
||||
|
||||
//将被交数用链表储存,防止因为频繁扩容影响性能
|
||||
final List<T> result = new LinkedList<>();
|
||||
Set<T> set = new HashSet<>(coll2);
|
||||
for (T t : coll1) {
|
||||
if (false == set.contains(t)) {
|
||||
result.add(t);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断指定集合是否包含指定值,如果集合为空(null或者空),返回{@code false},否则找到元素返回{@code true}
|
||||
*
|
||||
@@ -305,7 +409,15 @@ public class CollUtil {
|
||||
* @since 4.5.12
|
||||
*/
|
||||
public static boolean containsAll(Collection<?> coll1, Collection<?> coll2) {
|
||||
if (isEmpty(coll1) || isEmpty(coll2) || coll1.size() < coll2.size()) {
|
||||
if (isEmpty(coll1)) {
|
||||
return isEmpty(coll2);
|
||||
}
|
||||
|
||||
if (isEmpty(coll2)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (coll1.size() < coll2.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -328,10 +440,10 @@ public class CollUtil {
|
||||
* @param <T> 集合元素类型
|
||||
* @param collection 集合
|
||||
* @return {@link Map}
|
||||
* @see IterUtil#countMap(Iterable)
|
||||
* @see IterUtil#countMap(Iterator)
|
||||
*/
|
||||
public static <T> Map<T, Integer> countMap(Iterable<T> collection) {
|
||||
return IterUtil.countMap(collection);
|
||||
return IterUtil.countMap(null == collection ? null : collection.iterator());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -342,10 +454,31 @@ public class CollUtil {
|
||||
* @param iterable {@link Iterable}
|
||||
* @param conjunction 分隔符
|
||||
* @return 连接后的字符串
|
||||
* @see IterUtil#join(Iterable, CharSequence)
|
||||
* @see IterUtil#join(Iterator, CharSequence)
|
||||
*/
|
||||
public static <T> String join(Iterable<T> iterable, CharSequence conjunction) {
|
||||
return IterUtil.join(iterable, conjunction);
|
||||
if (null == iterable) {
|
||||
return null;
|
||||
}
|
||||
return IterUtil.join(iterable.iterator(), conjunction);
|
||||
}
|
||||
|
||||
/**
|
||||
* 以 conjunction 为分隔符将集合转换为字符串
|
||||
*
|
||||
* @param <T> 集合元素类型
|
||||
* @param iterable {@link Iterable}
|
||||
* @param conjunction 分隔符
|
||||
* @param prefix 每个元素添加的前缀,null表示不添加
|
||||
* @param suffix 每个元素添加的后缀,null表示不添加
|
||||
* @return 连接后的字符串
|
||||
* @since 5.3.0
|
||||
*/
|
||||
public static <T> String join(Iterable<T> iterable, CharSequence conjunction, String prefix, String suffix) {
|
||||
if (null == iterable) {
|
||||
return null;
|
||||
}
|
||||
return IterUtil.join(iterable.iterator(), conjunction, prefix, suffix);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -356,8 +489,9 @@ public class CollUtil {
|
||||
* @param iterator 集合
|
||||
* @param conjunction 分隔符
|
||||
* @return 连接后的字符串
|
||||
* @see IterUtil#join(Iterator, CharSequence)
|
||||
* @deprecated 请使用IterUtil#join(Iterator, CharSequence)
|
||||
*/
|
||||
@Deprecated
|
||||
public static <T> String join(Iterator<T> iterator, CharSequence conjunction) {
|
||||
return IterUtil.join(iterator, conjunction);
|
||||
}
|
||||
@@ -486,7 +620,7 @@ public class CollUtil {
|
||||
*/
|
||||
@SafeVarargs
|
||||
public static <T> LinkedHashSet<T> newLinkedHashSet(T... ts) {
|
||||
return (LinkedHashSet<T>) newHashSet(true, ts);
|
||||
return (LinkedHashSet<T>) set(true, ts);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -824,7 +958,7 @@ public class CollUtil {
|
||||
|
||||
/**
|
||||
* 创建Map<br>
|
||||
* 传入抽象Map{@link AbstractMap}和{@link Map}类将默认创建{@link HashMap}
|
||||
* 传入AbstractMap和{@link Map}类将默认创建{@link HashMap}
|
||||
*
|
||||
* @param <K> map键类型
|
||||
* @param <V> map值类型
|
||||
@@ -923,6 +1057,9 @@ public class CollUtil {
|
||||
*/
|
||||
public static <T> List<List<T>> split(Collection<T> collection, int size) {
|
||||
final List<List<T>> result = new ArrayList<>();
|
||||
if (CollUtil.isEmpty(collection)) {
|
||||
return result;
|
||||
}
|
||||
|
||||
ArrayList<T> subList = new ArrayList<>(size);
|
||||
for (T t : collection) {
|
||||
@@ -1134,17 +1271,34 @@ public class CollUtil {
|
||||
* @param editor 编辑器
|
||||
* @param ignoreNull 是否忽略空值
|
||||
* @return 抽取后的新列表
|
||||
* @see #map(Iterable, Function, boolean)
|
||||
* @since 4.5.7
|
||||
*/
|
||||
public static List<Object> extract(Iterable<?> collection, Editor<Object> editor, boolean ignoreNull) {
|
||||
final List<Object> fieldValueList = new ArrayList<>();
|
||||
return map(collection, editor::edit, ignoreNull);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过func自定义一个规则,此规则将原集合中的元素转换成新的元素,生成新的列表返回<br>
|
||||
* 例如提供的是一个Bean列表,通过Function接口实现获取某个字段值,返回这个字段值组成的新列表
|
||||
*
|
||||
* @param <T> 集合元素类型
|
||||
* @param <R> 返回集合元素类型
|
||||
* @param collection 原集合
|
||||
* @param func 编辑函数
|
||||
* @param ignoreNull 是否忽略空值
|
||||
* @return 抽取后的新列表
|
||||
* @since 5.3.5
|
||||
*/
|
||||
public static <T, R> List<R> map(Iterable<T> collection, Function<T, R> func, boolean ignoreNull) {
|
||||
final List<R> fieldValueList = new ArrayList<>();
|
||||
if (null == collection) {
|
||||
return fieldValueList;
|
||||
}
|
||||
|
||||
Object value;
|
||||
for (Object bean : collection) {
|
||||
value = editor.edit(bean);
|
||||
R value;
|
||||
for (T bean : collection) {
|
||||
value = func.apply(bean);
|
||||
if (null == value && ignoreNull) {
|
||||
continue;
|
||||
}
|
||||
@@ -1177,7 +1331,7 @@ public class CollUtil {
|
||||
* @since 4.5.7
|
||||
*/
|
||||
public static List<Object> getFieldValues(Iterable<?> collection, final String fieldName, boolean ignoreNull) {
|
||||
return extract(collection, bean -> {
|
||||
return map(collection, bean -> {
|
||||
if (bean instanceof Map) {
|
||||
return ((Map<?, ?>) bean).get(fieldName);
|
||||
} else {
|
||||
@@ -1202,6 +1356,36 @@ public class CollUtil {
|
||||
return Convert.toList(elementType, fieldValues);
|
||||
}
|
||||
|
||||
/**
|
||||
* 字段值与列表值对应的Map,常用于元素对象中有唯一ID时需要按照这个ID查找对象的情况<br>
|
||||
* 例如:车牌号 =》车
|
||||
*
|
||||
* @param <K> 字段名对应值得类型,不确定请使用Object
|
||||
* @param <V> 对象类型
|
||||
* @param iterable 对象列表
|
||||
* @param fieldName 字段名(会通过反射获取其值)
|
||||
* @return 某个字段值与对象对应Map
|
||||
* @since 5.0.6
|
||||
*/
|
||||
public static <K, V> Map<K, V> fieldValueMap(Iterable<V> iterable, String fieldName) {
|
||||
return IterUtil.fieldValueMap(null == iterable ? null : iterable.iterator(), fieldName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 两个字段值组成新的Map
|
||||
*
|
||||
* @param <K> 字段名对应值得类型,不确定请使用Object
|
||||
* @param <V> 值类型,不确定使用Object
|
||||
* @param iterable 对象列表
|
||||
* @param fieldNameForKey 做为键的字段名(会通过反射获取其值)
|
||||
* @param fieldNameForValue 做为值的字段名(会通过反射获取其值)
|
||||
* @return 某个字段值与对象对应Map
|
||||
* @since 5.0.6
|
||||
*/
|
||||
public static <K, V> Map<K, V> fieldValueAsMap(Iterable<?> iterable, String fieldNameForKey, String fieldNameForValue) {
|
||||
return IterUtil.fieldValueAsMap(null == iterable ? null : iterable.iterator(), fieldNameForKey, fieldNameForValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找第一个匹配元素对象
|
||||
*
|
||||
@@ -1310,6 +1494,30 @@ public class CollUtil {
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取匹配规则定义中匹配到元素的所有位置<br>
|
||||
* 此方法对于某些无序集合的位置信息,以转换为数组后的位置为准。
|
||||
*
|
||||
* @param <T> 元素类型
|
||||
* @param collection 集合
|
||||
* @param matcher 匹配器,为空则全部匹配
|
||||
* @return 位置数组
|
||||
* @since 5.2.5
|
||||
*/
|
||||
public static <T> int[] indexOfAll(Collection<T> collection, Matcher<T> matcher) {
|
||||
final List<Integer> indexList = new ArrayList<>();
|
||||
if (null != collection) {
|
||||
int index = 0;
|
||||
for (T t : collection) {
|
||||
if (null == matcher || matcher.match(t)) {
|
||||
indexList.add(index);
|
||||
}
|
||||
index++;
|
||||
}
|
||||
}
|
||||
return Convert.convert(int[].class, indexList);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------- isEmpty
|
||||
|
||||
/**
|
||||
@@ -1692,6 +1900,40 @@ public class CollUtil {
|
||||
return MapUtil.toMapList(listMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* 集合转换为Map,转换规则为:<br>
|
||||
* 按照keyFunc函数规则根据元素对象生成Key,元素作为值
|
||||
*
|
||||
* @param <K> Map键类型
|
||||
* @param <V> Map值类型
|
||||
* @param values 数据列表
|
||||
* @param map Map对象,转换后的键值对加入此Map,通过传入此对象自定义Map类型
|
||||
* @param keyFunc 生成key的函数
|
||||
* @return 生成的map
|
||||
* @since 5.2.6
|
||||
*/
|
||||
public static <K, V> Map<K, V> toMap(Iterable<V> values, Map<K, V> map, Func1<V, K> keyFunc) {
|
||||
return IterUtil.toMap(null == values ? null : values.iterator(), map, keyFunc);
|
||||
}
|
||||
|
||||
/**
|
||||
* 集合转换为Map,转换规则为:<br>
|
||||
* 按照keyFunc函数规则根据元素对象生成Key,按照valueFunc函数规则根据元素对象生成value组成新的Map
|
||||
*
|
||||
* @param <K> Map键类型
|
||||
* @param <V> Map值类型
|
||||
* @param <E> 元素类型
|
||||
* @param values 数据列表
|
||||
* @param map Map对象,转换后的键值对加入此Map,通过传入此对象自定义Map类型
|
||||
* @param keyFunc 生成key的函数
|
||||
* @param valueFunc 生成值的策略函数
|
||||
* @return 生成的map
|
||||
* @since 5.2.6
|
||||
*/
|
||||
public static <K, V, E> Map<K, V> toMap(Iterable<E> values, Map<K, V> map, Func1<E, K> keyFunc, Func1<E, V> valueFunc) {
|
||||
return IterUtil.toMap(null == values ? null : values.iterator(), map, keyFunc, valueFunc);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将指定对象全部加入到集合中<br>
|
||||
* 提供的对象如果为集合类型,会自动转换为目标元素类型<br>
|
||||
@@ -2249,7 +2491,8 @@ public class CollUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 循环遍历Map,使用{@link KVConsumer} 接受遍历的每条数据,并针对每条数据做处理
|
||||
* 循环遍历Map,使用{@link KVConsumer} 接受遍历的每条数据,并针对每条数据做处理<br>
|
||||
* 和JDK8中的map.forEach不同的是,此方法支持index
|
||||
*
|
||||
* @param <K> Key类型
|
||||
* @param <V> Value类型
|
||||
@@ -2434,6 +2677,70 @@ public class CollUtil {
|
||||
return Collections.min(coll);
|
||||
}
|
||||
|
||||
/**
|
||||
* 转为只读集合
|
||||
*
|
||||
* @param <T> 元素类型
|
||||
* @param c 集合
|
||||
* @return 只读集合
|
||||
* @since 5.2.6
|
||||
*/
|
||||
public static <T> Collection<T> unmodifiable(Collection<? extends T> c) {
|
||||
return Collections.unmodifiableCollection(c);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据给定的集合类型,返回对应的空集合,支持类型包括:
|
||||
* *
|
||||
* <pre>
|
||||
* 1. NavigableSet
|
||||
* 2. SortedSet
|
||||
* 3. Set
|
||||
* 4. List
|
||||
* </pre>
|
||||
*
|
||||
* @param <E> 元素类型
|
||||
* @param <T> 集合类型
|
||||
* @param collectionClass 集合类型
|
||||
* @return 空集合
|
||||
* @since 5.3.1
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <E, T extends Collection<E>> T empty(Class<?> collectionClass) {
|
||||
if (null == collectionClass) {
|
||||
return (T) Collections.emptyList();
|
||||
}
|
||||
|
||||
if (Set.class.isAssignableFrom(collectionClass)) {
|
||||
if (NavigableSet.class == collectionClass) {
|
||||
return (T) Collections.emptyNavigableSet();
|
||||
} else if (SortedSet.class == collectionClass) {
|
||||
return (T) Collections.emptySortedSet();
|
||||
} else {
|
||||
return (T) Collections.emptySet();
|
||||
}
|
||||
} else if (List.class.isAssignableFrom(collectionClass)) {
|
||||
return (T) Collections.emptyList();
|
||||
}
|
||||
|
||||
// 不支持空集合的集合类型
|
||||
throw new IllegalArgumentException(StrUtil.format("[{}] is not support to get empty!", collectionClass));
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除一个或多个集合内的元素,每个集合调用clear()方法
|
||||
*
|
||||
* @param collections 一个或多个集合
|
||||
* @since 5.3.6
|
||||
*/
|
||||
public static void clear(Collection<?>... collections) {
|
||||
for (Collection<?> collection : collections) {
|
||||
if (isNotEmpty(collection)) {
|
||||
collection.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------- Interface start
|
||||
|
||||
/**
|
||||
|
||||
@@ -24,7 +24,7 @@ import java.util.List;
|
||||
public class CopiedIter<E> implements Iterator<E>, Iterable<E>, Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Iterator<E> listIterator;
|
||||
private final Iterator<E> listIterator;
|
||||
|
||||
public static <V> CopiedIter<V> copyOf(Iterator<V> iterator){
|
||||
return new CopiedIter<>(iterator);
|
||||
|
||||
@@ -1,13 +1,23 @@
|
||||
package cn.hutool.core.collection;
|
||||
|
||||
import cn.hutool.core.exceptions.UtilException;
|
||||
import cn.hutool.core.lang.Filter;
|
||||
import cn.hutool.core.lang.func.Func1;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* {@link Iterable} 和 {@link Iterator} 相关工具类
|
||||
@@ -128,7 +138,9 @@ public class IterUtil {
|
||||
* @param <T> 集合元素类型
|
||||
* @param iter {@link Iterable},如果为null返回一个空的Map
|
||||
* @return {@link Map}
|
||||
* @deprecated 如果对象同时实现Iterable和Iterator接口会产生歧义,请使用CollUtil.countMap
|
||||
*/
|
||||
@Deprecated
|
||||
public static <T> Map<T, Integer> countMap(Iterable<T> iter) {
|
||||
return countMap(null == iter ? null : iter.iterator());
|
||||
}
|
||||
@@ -173,7 +185,9 @@ public class IterUtil {
|
||||
* @param fieldName 字段名(会通过反射获取其值)
|
||||
* @return 某个字段值与对象对应Map
|
||||
* @since 4.0.4
|
||||
* @deprecated 如果对象同时实现Iterable和Iterator接口会产生歧义,请使用CollUtil.fieldValueMap
|
||||
*/
|
||||
@Deprecated
|
||||
public static <K, V> Map<K, V> fieldValueMap(Iterable<V> iter, String fieldName) {
|
||||
return fieldValueMap(null == iter ? null : iter.iterator(), fieldName);
|
||||
}
|
||||
@@ -191,15 +205,7 @@ public class IterUtil {
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <K, V> Map<K, V> fieldValueMap(Iterator<V> iter, String fieldName) {
|
||||
final Map<K, V> result = new HashMap<>();
|
||||
if (null != iter) {
|
||||
V value;
|
||||
while (iter.hasNext()) {
|
||||
value = iter.next();
|
||||
result.put((K) ReflectUtil.getFieldValue(value, fieldName), value);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
return toMap(iter, new HashMap<>(), (value) -> (K) ReflectUtil.getFieldValue(value, fieldName));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -212,7 +218,9 @@ public class IterUtil {
|
||||
* @param fieldNameForValue 做为值的字段名(会通过反射获取其值)
|
||||
* @return 某个字段值与对象对应Map
|
||||
* @since 4.6.2
|
||||
* @deprecated 如果对象同时实现Iterable和Iterator接口会产生歧义,请使用CollUtil.fieldValueMap
|
||||
*/
|
||||
@Deprecated
|
||||
public static <K, V> Map<K, V> fieldValueAsMap(Iterable<?> iterable, String fieldNameForKey, String fieldNameForValue) {
|
||||
return fieldValueAsMap(null == iterable ? null : iterable.iterator(), fieldNameForKey, fieldNameForValue);
|
||||
}
|
||||
@@ -230,15 +238,10 @@ public class IterUtil {
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <K, V> Map<K, V> fieldValueAsMap(Iterator<?> iter, String fieldNameForKey, String fieldNameForValue) {
|
||||
final Map<K, V> result = new HashMap<>();
|
||||
if (null != iter) {
|
||||
Object value;
|
||||
while (iter.hasNext()) {
|
||||
value = iter.next();
|
||||
result.put((K) ReflectUtil.getFieldValue(value, fieldNameForKey), (V) ReflectUtil.getFieldValue(value, fieldNameForValue));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
return toMap(iter, new HashMap<>(),
|
||||
(value) -> (K) ReflectUtil.getFieldValue(value, fieldNameForKey),
|
||||
(value) -> (V) ReflectUtil.getFieldValue(value, fieldNameForValue)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -282,7 +285,9 @@ public class IterUtil {
|
||||
* @param iterable {@link Iterable}
|
||||
* @param conjunction 分隔符
|
||||
* @return 连接后的字符串
|
||||
* @deprecated 如果对象同时实现Iterable和Iterator接口会产生歧义,请使用CollUtil.join
|
||||
*/
|
||||
@Deprecated
|
||||
public static <T> String join(Iterable<T> iterable, CharSequence conjunction) {
|
||||
if (null == iterable) {
|
||||
return null;
|
||||
@@ -300,7 +305,9 @@ public class IterUtil {
|
||||
* @param suffix 每个元素添加的后缀,null表示不添加
|
||||
* @return 连接后的字符串
|
||||
* @since 4.0.10
|
||||
* @deprecated 如果对象同时实现Iterable和Iterator接口会产生歧义,请使用CollUtil.join
|
||||
*/
|
||||
@Deprecated
|
||||
public static <T> String join(Iterable<T> iterable, CharSequence conjunction, String prefix, String suffix) {
|
||||
if (null == iterable) {
|
||||
return null;
|
||||
@@ -452,6 +459,122 @@ public class IterUtil {
|
||||
return resultMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将列表转成值为List的HashMap
|
||||
*
|
||||
* @param iterable 值列表
|
||||
* @param keyMapper Map的键映射
|
||||
* @param <K> 键类型
|
||||
* @param <V> 值类型
|
||||
* @return HashMap
|
||||
* @since 5.3.6
|
||||
*/
|
||||
public static <K, V> Map<K, List<V>> toListMap(Iterable<V> iterable, Function<V, K> keyMapper) {
|
||||
return toListMap(iterable, keyMapper, v -> v);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将列表转成值为List的HashMap
|
||||
*
|
||||
* @param iterable 值列表
|
||||
* @param keyMapper Map的键映射
|
||||
* @param valueMapper Map中List的值映射
|
||||
* @param <T> 列表值类型
|
||||
* @param <K> 键类型
|
||||
* @param <V> 值类型
|
||||
* @return HashMap
|
||||
* @since 5.3.6
|
||||
*/
|
||||
public static <T, K, V> Map<K, List<V>> toListMap(Iterable<T> iterable, Function<T, K> keyMapper, Function<T, V> valueMapper) {
|
||||
return toListMap(MapUtil.newHashMap(), iterable, keyMapper, valueMapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将列表转成值为List的HashMap
|
||||
*
|
||||
* @param resultMap 结果Map,可自定义结果Map类型
|
||||
* @param iterable 值列表
|
||||
* @param keyMapper Map的键映射
|
||||
* @param valueMapper Map中List的值映射
|
||||
* @param <T> 列表值类型
|
||||
* @param <K> 键类型
|
||||
* @param <V> 值类型
|
||||
* @return HashMap
|
||||
* @since 5.3.6
|
||||
*/
|
||||
public static <T, K, V> Map<K, List<V>> toListMap(Map<K, List<V>> resultMap, Iterable<T> iterable, Function<T, K> keyMapper, Function<T, V> valueMapper) {
|
||||
if (null == resultMap) {
|
||||
resultMap = MapUtil.newHashMap();
|
||||
}
|
||||
if (ObjectUtil.isNull(iterable)) {
|
||||
return resultMap;
|
||||
}
|
||||
|
||||
for (T value : iterable) {
|
||||
resultMap.computeIfAbsent(keyMapper.apply(value), k -> new ArrayList<>()).add(valueMapper.apply(value));
|
||||
}
|
||||
|
||||
return resultMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将列表转成HashMap
|
||||
*
|
||||
* @param iterable 值列表
|
||||
* @param keyMapper Map的键映射
|
||||
* @param <K> 键类型
|
||||
* @param <V> 值类型
|
||||
* @return HashMap
|
||||
* @since 5.3.6
|
||||
*/
|
||||
public static <K, V> Map<K, V> toMap(Iterable<V> iterable, Function<V, K> keyMapper) {
|
||||
return toMap(iterable, keyMapper, v -> v);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将列表转成HashMap
|
||||
*
|
||||
* @param iterable 值列表
|
||||
* @param keyMapper Map的键映射
|
||||
* @param valueMapper Map的值映射
|
||||
* @param <T> 列表值类型
|
||||
* @param <K> 键类型
|
||||
* @param <V> 值类型
|
||||
* @return HashMap
|
||||
* @since 5.3.6
|
||||
*/
|
||||
public static <T, K, V> Map<K, V> toMap(Iterable<T> iterable, Function<T, K> keyMapper, Function<T, V> valueMapper) {
|
||||
return toMap(MapUtil.newHashMap(), iterable, keyMapper, valueMapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将列表转成Map
|
||||
*
|
||||
* @param resultMap 结果Map,通过传入map对象决定结果的Map类型
|
||||
* @param iterable 值列表
|
||||
* @param keyMapper Map的键映射
|
||||
* @param valueMapper Map的值映射
|
||||
* @param <T> 列表值类型
|
||||
* @param <K> 键类型
|
||||
* @param <V> 值类型
|
||||
* @return HashMap
|
||||
* @since 5.3.6
|
||||
*/
|
||||
public static <T, K, V> Map<K, V> toMap(Map<K, V> resultMap, Iterable<T> iterable, Function<T, K> keyMapper, Function<T, V> valueMapper) {
|
||||
if (null == resultMap) {
|
||||
resultMap = MapUtil.newHashMap();
|
||||
}
|
||||
if (ObjectUtil.isNull(iterable)) {
|
||||
return resultMap;
|
||||
}
|
||||
|
||||
for (T value : iterable) {
|
||||
resultMap.put(keyMapper.apply(value), valueMapper.apply(value));
|
||||
}
|
||||
|
||||
return resultMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterator转List<br>
|
||||
* 不判断,直接生成新的List
|
||||
@@ -621,4 +744,67 @@ public class IterUtil {
|
||||
}
|
||||
return iter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterator转换为Map,转换规则为:<br>
|
||||
* 按照keyFunc函数规则根据元素对象生成Key,元素作为值
|
||||
*
|
||||
* @param <K> Map键类型
|
||||
* @param <V> Map值类型
|
||||
* @param iterator 数据列表
|
||||
* @param map Map对象,转换后的键值对加入此Map,通过传入此对象自定义Map类型
|
||||
* @param keyFunc 生成key的函数
|
||||
* @return 生成的map
|
||||
* @since 5.2.6
|
||||
*/
|
||||
public static <K, V> Map<K, V> toMap(Iterator<V> iterator, Map<K, V> map, Func1<V, K> keyFunc) {
|
||||
return toMap(iterator, map, keyFunc, (value) -> value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 集合转换为Map,转换规则为:<br>
|
||||
* 按照keyFunc函数规则根据元素对象生成Key,按照valueFunc函数规则根据元素对象生成value组成新的Map
|
||||
*
|
||||
* @param <K> Map键类型
|
||||
* @param <V> Map值类型
|
||||
* @param <E> 元素类型
|
||||
* @param iterator 数据列表
|
||||
* @param map Map对象,转换后的键值对加入此Map,通过传入此对象自定义Map类型
|
||||
* @param keyFunc 生成key的函数
|
||||
* @param valueFunc 生成值的策略函数
|
||||
* @return 生成的map
|
||||
* @since 5.2.6
|
||||
*/
|
||||
public static <K, V, E> Map<K, V> toMap(Iterator<E> iterator, Map<K, V> map, Func1<E, K> keyFunc, Func1<E, V> valueFunc) {
|
||||
if (null == iterator) {
|
||||
return map;
|
||||
}
|
||||
|
||||
if (null == map) {
|
||||
map = MapUtil.newHashMap(true);
|
||||
}
|
||||
|
||||
E element;
|
||||
while (iterator.hasNext()) {
|
||||
element = iterator.next();
|
||||
try {
|
||||
map.put(keyFunc.call(element), valueFunc.call(element));
|
||||
} catch (Exception e) {
|
||||
throw new UtilException(e);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回一个空Iterator
|
||||
*
|
||||
* @param <T> 元素类型
|
||||
* @return 空Iterator
|
||||
* @see Collections#emptyIterator()
|
||||
* @since 5.3.1
|
||||
*/
|
||||
public static <T> Iterator<T> empty() {
|
||||
return Collections.emptyIterator();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@ package cn.hutool.core.collection;
|
||||
|
||||
import cn.hutool.core.comparator.PinyinComparator;
|
||||
import cn.hutool.core.comparator.PropertyComparator;
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.lang.Editor;
|
||||
import cn.hutool.core.lang.Matcher;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.PageUtil;
|
||||
@@ -419,4 +421,50 @@ public class ListUtil {
|
||||
}
|
||||
return list2;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取匹配规则定义中匹配到元素的所有位置
|
||||
*
|
||||
* @param <T> 元素类型
|
||||
* @param list 列表
|
||||
* @param matcher 匹配器,为空则全部匹配
|
||||
* @return 位置数组
|
||||
* @since 5.2.5
|
||||
*/
|
||||
public static <T> int[] indexOfAll(List<T> list, Matcher<T> matcher) {
|
||||
final List<Integer> indexList = new ArrayList<>();
|
||||
if (null != list) {
|
||||
int index = 0;
|
||||
for (T t : list) {
|
||||
if (null == matcher || matcher.match(t)) {
|
||||
indexList.add(index);
|
||||
}
|
||||
index++;
|
||||
}
|
||||
}
|
||||
return Convert.convert(int[].class, indexList);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将对应List转换为不可修改的List
|
||||
*
|
||||
* @param list List
|
||||
* @param <T> 元素类型
|
||||
* @return 不可修改List
|
||||
* @since 5.2.6
|
||||
*/
|
||||
public static <T> List<T> unmodifiable(List<T> list) {
|
||||
return Collections.unmodifiableList(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取一个空List
|
||||
*
|
||||
* @param <T> 元素类型
|
||||
* @return 空的List
|
||||
* @since 5.2.6
|
||||
*/
|
||||
public static <T> List<T> empty() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package cn.hutool.core.comparator;
|
||||
|
||||
import cn.hutool.core.lang.Chain;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.BitSet;
|
||||
@@ -8,8 +10,6 @@ import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import cn.hutool.core.lang.Chain;
|
||||
|
||||
/**
|
||||
* 比较器链。此链包装了多个比较器,最终比较结果按照比较器顺序综合多个比较器结果。<br>
|
||||
* 按照比较器链的顺序分别比较,如果比较出相等则转向下一个比较器,否则直接返回<br>
|
||||
@@ -24,7 +24,7 @@ public class ComparatorChain<E> implements Chain<Comparator<E>, ComparatorChain<
|
||||
/** 比较器链. */
|
||||
private final List<Comparator<E>> chain;
|
||||
/** 对应比较器位置是否反序. */
|
||||
private BitSet orderingBits;
|
||||
private final BitSet orderingBits;
|
||||
/** 比较器是否被锁定。锁定的比较器链不能再添加新的比较器。比较器会在开始比较时开始加锁。 */
|
||||
private boolean lock = false;
|
||||
|
||||
@@ -249,8 +249,9 @@ public class ComparatorChain<E> implements Chain<Comparator<E>, ComparatorChain<
|
||||
}
|
||||
if (object.getClass().equals(this.getClass())) {
|
||||
final ComparatorChain<?> otherChain = (ComparatorChain<?>) object;
|
||||
return (Objects.equals(this.orderingBits, otherChain.orderingBits)) //
|
||||
&& (null == otherChain ? null == otherChain.chain : this.chain.equals(otherChain.chain));
|
||||
//
|
||||
return Objects.equals(this.orderingBits, otherChain.orderingBits)
|
||||
&& this.chain.equals(otherChain.chain);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,11 @@ package cn.hutool.core.comparator;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
/**
|
||||
* 比较工具类
|
||||
*
|
||||
* @author looly
|
||||
*/
|
||||
public class CompareUtil {
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package cn.hutool.core.comparator;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
/**
|
||||
* 按照数组的顺序正序排列,数组的元素位置决定了对象的排序先后<br>
|
||||
* 如果参与排序的元素并不在数组中,则排序在前
|
||||
@@ -15,7 +15,7 @@ import cn.hutool.core.util.ArrayUtil;
|
||||
*/
|
||||
public class IndexedComparator<T> implements Comparator<T> {
|
||||
|
||||
private T[] array;
|
||||
private final T[] array;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
|
||||
@@ -58,7 +58,8 @@ public abstract class AbstractConverter<T> implements Converter<T>, Serializable
|
||||
T result = convertInternal(value);
|
||||
return ((null == result) ? defaultValue : result);
|
||||
} else {
|
||||
throw new IllegalArgumentException(StrUtil.format("Default value [{}] is not the instance of [{}]", defaultValue, targetType));
|
||||
throw new IllegalArgumentException(
|
||||
StrUtil.format("Default value [{}]({}) is not the instance of [{}]", defaultValue, defaultValue.getClass(), targetType));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,5 @@
|
||||
package cn.hutool.core.convert;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.nio.charset.Charset;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import cn.hutool.core.convert.impl.CollectionConverter;
|
||||
import cn.hutool.core.convert.impl.EnumConverter;
|
||||
import cn.hutool.core.convert.impl.MapConverter;
|
||||
@@ -20,6 +11,21 @@ import cn.hutool.core.util.ClassUtil;
|
||||
import cn.hutool.core.util.HexUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.nio.charset.Charset;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 类型转换器
|
||||
*
|
||||
@@ -674,7 +680,7 @@ public class Convert {
|
||||
* @throws ConvertException 转换器不存在
|
||||
*/
|
||||
public static <T> T convert(Type type, Object value, T defaultValue) throws ConvertException {
|
||||
return ConverterRegistry.getInstance().convert(type, value, defaultValue);
|
||||
return convertWithCheck(type, value, defaultValue, false);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -703,11 +709,31 @@ public class Convert {
|
||||
* @since 4.5.10
|
||||
*/
|
||||
public static <T> T convertQuietly(Type type, Object value, T defaultValue) {
|
||||
return convertWithCheck(type, value, defaultValue, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换值为指定类型,可选是否不抛异常转换<br>
|
||||
* 当转换失败时返回默认值
|
||||
*
|
||||
* @param <T> 目标类型
|
||||
* @param type 目标类型
|
||||
* @param value 值
|
||||
* @param defaultValue 默认值
|
||||
* @param quietly 是否静默转换,true不抛异常
|
||||
* @return 转换后的值
|
||||
* @since 5.3.2
|
||||
*/
|
||||
public static <T> T convertWithCheck(Type type, Object value, T defaultValue, boolean quietly) {
|
||||
final ConverterRegistry registry = ConverterRegistry.getInstance();
|
||||
try {
|
||||
return convert(type, value, defaultValue);
|
||||
return registry.convert(type, value, defaultValue);
|
||||
} catch (Exception e) {
|
||||
if(quietly){
|
||||
return defaultValue;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------- 全角半角转换
|
||||
|
||||
@@ -93,7 +93,7 @@ public class ConverterRegistry implements Serializable{
|
||||
/** 类级的内部类,也就是静态的成员式内部类,该内部类的实例与外部类的实例 没有绑定关系,而且只有被调用到才会装载,从而实现了延迟加载 */
|
||||
private static class SingletonHolder {
|
||||
/** 静态初始化器,由JVM来保证线程安全 */
|
||||
private static ConverterRegistry instance = new ConverterRegistry();
|
||||
private static final ConverterRegistry INSTANCE = new ConverterRegistry();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -102,7 +102,7 @@ public class ConverterRegistry implements Serializable{
|
||||
* @return {@link ConverterRegistry}
|
||||
*/
|
||||
public static ConverterRegistry getInstance() {
|
||||
return SingletonHolder.instance;
|
||||
return SingletonHolder.INSTANCE;
|
||||
}
|
||||
|
||||
public ConverterRegistry() {
|
||||
|
||||
@@ -65,7 +65,9 @@ public class BeanConverter<T> extends AbstractConverter<T> {
|
||||
|
||||
@Override
|
||||
protected T convertInternal(Object value) {
|
||||
if(value instanceof Map || value instanceof ValueProvider || BeanUtil.isBean(value.getClass())) {
|
||||
if(value instanceof Map ||
|
||||
value instanceof ValueProvider ||
|
||||
BeanUtil.isBean(value.getClass())) {
|
||||
if(value instanceof Map && this.beanClass.isInterface()) {
|
||||
// 将Map动态代理为Bean
|
||||
return MapProxy.create((Map<?, ?>)value).toProxyBean(this.beanClass);
|
||||
|
||||
@@ -13,11 +13,8 @@ public class BooleanConverter extends AbstractConverter<Boolean>{
|
||||
|
||||
@Override
|
||||
protected Boolean convertInternal(Object value) {
|
||||
if(boolean.class == value.getClass()){
|
||||
return Boolean.valueOf((boolean)value);
|
||||
}
|
||||
String valueStr = convertToStr(value);
|
||||
return Boolean.valueOf(BooleanUtil.toBoolean(valueStr));
|
||||
//Object不可能出现Primitive类型,故忽略
|
||||
return BooleanUtil.toBoolean(convertToStr(value));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,16 +15,12 @@ public class CharacterConverter extends AbstractConverter<Character> {
|
||||
|
||||
@Override
|
||||
protected Character convertInternal(Object value) {
|
||||
if (char.class == value.getClass()) {
|
||||
return Character.valueOf((char) value);
|
||||
} else if (value instanceof Boolean) {
|
||||
if (value instanceof Boolean) {
|
||||
return BooleanUtil.toCharacter((Boolean) value);
|
||||
} else if (boolean.class == value.getClass()) {
|
||||
return BooleanUtil.toCharacter((boolean) value);
|
||||
} else {
|
||||
final String valueStr = convertToStr(value);
|
||||
if (StrUtil.isNotBlank(valueStr)) {
|
||||
return Character.valueOf(valueStr.charAt(0));
|
||||
return valueStr.charAt(0);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -18,7 +18,7 @@ import java.util.Date;
|
||||
public class DateConverter extends AbstractConverter<java.util.Date> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Class<? extends java.util.Date> targetType;
|
||||
private final Class<? extends java.util.Date> targetType;
|
||||
/** 日期格式化 */
|
||||
private String format;
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ public class EnumConverter extends AbstractConverter<Object> {
|
||||
|
||||
private static final Map<Class<?>, Map<Class<?>, Method>> VALUE_OF_METHOD_CACHE = new ConcurrentHashMap<>();
|
||||
|
||||
private Class enumClass;
|
||||
private final Class enumClass;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
|
||||
@@ -14,7 +14,7 @@ import cn.hutool.core.convert.AbstractConverter;
|
||||
public class GenericEnumConverter<E extends Enum<E>> extends AbstractConverter<E> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Class<E> enumClass;
|
||||
private final Class<E> enumClass;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
@@ -25,9 +25,9 @@ public class GenericEnumConverter<E extends Enum<E>> extends AbstractConverter<E
|
||||
this.enumClass = enumClass;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
protected E convertInternal(Object value) {
|
||||
//noinspection unchecked
|
||||
E enumValue = (E) EnumConverter.tryConvertEnum(value, this.enumClass);
|
||||
if(null == enumValue && false == value instanceof String){
|
||||
// 最后尝试valueOf转换
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
package cn.hutool.core.convert.impl;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import cn.hutool.core.convert.AbstractConverter;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.util.BooleanUtil;
|
||||
import cn.hutool.core.util.NumberUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.time.temporal.TemporalAccessor;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
/**
|
||||
* 数字转换器<br>
|
||||
* 支持类型为:<br>
|
||||
@@ -17,7 +21,9 @@ import cn.hutool.core.util.StrUtil;
|
||||
* <li><code>java.lang.Byte</code></li>
|
||||
* <li><code>java.lang.Short</code></li>
|
||||
* <li><code>java.lang.Integer</code></li>
|
||||
* <li><code>java.util.concurrent.atomic.AtomicInteger</code></li>
|
||||
* <li><code>java.lang.Long</code></li>
|
||||
* <li><code>java.util.concurrent.atomic.AtomicLong</code></li>
|
||||
* <li><code>java.lang.Float</code></li>
|
||||
* <li><code>java.lang.Double</code></li>
|
||||
* <li><code>java.math.BigDecimal</code></li>
|
||||
@@ -25,12 +31,11 @@ import cn.hutool.core.util.StrUtil;
|
||||
* </ul>
|
||||
*
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
public class NumberConverter extends AbstractConverter<Number> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Class<? extends Number> targetType;
|
||||
private final Class<? extends Number> targetType;
|
||||
|
||||
public NumberConverter() {
|
||||
this.targetType = Number.class;
|
||||
@@ -47,10 +52,13 @@ public class NumberConverter extends AbstractConverter<Number> {
|
||||
|
||||
@Override
|
||||
protected Number convertInternal(Object value) {
|
||||
final Class<?> targetType = this.targetType;
|
||||
return convertInternal(value, this.targetType);
|
||||
}
|
||||
|
||||
private Number convertInternal(Object value, Class<?> targetType) {
|
||||
if (Byte.class == targetType) {
|
||||
if (value instanceof Number) {
|
||||
return Byte.valueOf(((Number) value).byteValue());
|
||||
return ((Number) value).byteValue();
|
||||
} else if (value instanceof Boolean) {
|
||||
return BooleanUtil.toByteObj((Boolean) value);
|
||||
}
|
||||
@@ -59,7 +67,7 @@ public class NumberConverter extends AbstractConverter<Number> {
|
||||
|
||||
} else if (Short.class == targetType) {
|
||||
if (value instanceof Number) {
|
||||
return Short.valueOf(((Number) value).shortValue());
|
||||
return ((Number) value).shortValue();
|
||||
} else if (value instanceof Boolean) {
|
||||
return BooleanUtil.toShortObj((Boolean) value);
|
||||
}
|
||||
@@ -68,52 +76,53 @@ public class NumberConverter extends AbstractConverter<Number> {
|
||||
|
||||
} else if (Integer.class == targetType) {
|
||||
if (value instanceof Number) {
|
||||
return Integer.valueOf(((Number) value).intValue());
|
||||
return ((Number) value).intValue();
|
||||
} else if (value instanceof Boolean) {
|
||||
return BooleanUtil.toInteger((Boolean) value);
|
||||
} else if (value instanceof Date) {
|
||||
return (int)((Date) value).getTime();
|
||||
} else if (value instanceof Calendar) {
|
||||
return (int)((Calendar) value).getTimeInMillis();
|
||||
} else if (value instanceof TemporalAccessor) {
|
||||
return (int)DateUtil.toInstant((TemporalAccessor) value).toEpochMilli();
|
||||
}
|
||||
final String valueStr = convertToStr(value);
|
||||
return StrUtil.isBlank(valueStr) ? null : Integer.valueOf(NumberUtil.parseInt(valueStr));
|
||||
return StrUtil.isBlank(valueStr) ? null : NumberUtil.parseInt(valueStr);
|
||||
|
||||
} else if (AtomicInteger.class == targetType) {
|
||||
final Number number = convertInternal(value, Integer.class);
|
||||
if (null != number) {
|
||||
final AtomicInteger intValue = new AtomicInteger();
|
||||
if (value instanceof Number) {
|
||||
intValue.set(((Number) value).intValue());
|
||||
} else if(value instanceof Boolean) {
|
||||
intValue.set(BooleanUtil.toInt((Boolean) value));
|
||||
}
|
||||
final String valueStr = convertToStr(value);
|
||||
if (StrUtil.isBlank(valueStr)) {
|
||||
return null;
|
||||
}
|
||||
intValue.set(NumberUtil.parseInt(valueStr));
|
||||
intValue.set(number.intValue());
|
||||
return intValue;
|
||||
}
|
||||
return null;
|
||||
} else if (Long.class == targetType) {
|
||||
if (value instanceof Number) {
|
||||
return Long.valueOf(((Number) value).longValue());
|
||||
return ((Number) value).longValue();
|
||||
} else if (value instanceof Boolean) {
|
||||
return BooleanUtil.toLongObj((Boolean) value);
|
||||
} else if (value instanceof Date) {
|
||||
return ((Date) value).getTime();
|
||||
} else if (value instanceof Calendar) {
|
||||
return ((Calendar) value).getTimeInMillis();
|
||||
} else if (value instanceof TemporalAccessor) {
|
||||
return DateUtil.toInstant((TemporalAccessor) value).toEpochMilli();
|
||||
}
|
||||
final String valueStr = convertToStr(value);
|
||||
return StrUtil.isBlank(valueStr) ? null : Long.valueOf(NumberUtil.parseLong(valueStr));
|
||||
return StrUtil.isBlank(valueStr) ? null : NumberUtil.parseLong(valueStr);
|
||||
|
||||
} else if (AtomicLong.class == targetType) {
|
||||
final Number number = convertInternal(value, Long.class);
|
||||
if (null != number) {
|
||||
final AtomicLong longValue = new AtomicLong();
|
||||
if (value instanceof Number) {
|
||||
longValue.set(((Number) value).longValue());
|
||||
} else if(value instanceof Boolean) {
|
||||
longValue.set(BooleanUtil.toLong((Boolean) value));
|
||||
}
|
||||
final String valueStr = convertToStr(value);
|
||||
if (StrUtil.isBlank(valueStr)) {
|
||||
return null;
|
||||
}
|
||||
longValue.set(NumberUtil.parseLong(valueStr));
|
||||
longValue.set(number.longValue());
|
||||
return longValue;
|
||||
|
||||
}
|
||||
return null;
|
||||
} else if (Float.class == targetType) {
|
||||
if (value instanceof Number) {
|
||||
return Float.valueOf(((Number) value).floatValue());
|
||||
return ((Number) value).floatValue();
|
||||
} else if (value instanceof Boolean) {
|
||||
return BooleanUtil.toFloatObj((Boolean) value);
|
||||
}
|
||||
@@ -122,7 +131,7 @@ public class NumberConverter extends AbstractConverter<Number> {
|
||||
|
||||
} else if (Double.class == targetType) {
|
||||
if (value instanceof Number) {
|
||||
return Double.valueOf(((Number) value).doubleValue());
|
||||
return ((Number) value).doubleValue();
|
||||
} else if (value instanceof Boolean) {
|
||||
return BooleanUtil.toDoubleObj((Boolean) value);
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ import java.util.Date;
|
||||
public class PrimitiveConverter extends AbstractConverter<Object> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Class<?> targetType;
|
||||
private final Class<?> targetType;
|
||||
|
||||
/**
|
||||
* 构造<br>
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
package cn.hutool.core.convert.impl;
|
||||
|
||||
import java.lang.ref.Reference;
|
||||
import java.lang.ref.SoftReference;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
import cn.hutool.core.convert.AbstractConverter;
|
||||
import cn.hutool.core.convert.ConverterRegistry;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.core.util.TypeUtil;
|
||||
|
||||
import java.lang.ref.Reference;
|
||||
import java.lang.ref.SoftReference;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
/**
|
||||
* {@link Reference}转换器
|
||||
*
|
||||
@@ -20,7 +20,7 @@ import cn.hutool.core.util.TypeUtil;
|
||||
public class ReferenceConverter extends AbstractConverter<Reference> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Class<? extends Reference> targetType;
|
||||
private final Class<? extends Reference> targetType;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
|
||||
@@ -4,6 +4,7 @@ import cn.hutool.core.convert.AbstractConverter;
|
||||
import cn.hutool.core.date.DateTime;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
@@ -39,7 +40,7 @@ import java.util.Objects;
|
||||
public class TemporalAccessorConverter extends AbstractConverter<TemporalAccessor> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Class<?> targetType;
|
||||
private final Class<?> targetType;
|
||||
/**
|
||||
* 日期格式化
|
||||
*/
|
||||
@@ -107,6 +108,10 @@ public class TemporalAccessorConverter extends AbstractConverter<TemporalAccesso
|
||||
* @return 日期对象
|
||||
*/
|
||||
private TemporalAccessor parseFromCharSequence(CharSequence value) {
|
||||
if(StrUtil.isBlank(value)){
|
||||
return null;
|
||||
}
|
||||
|
||||
final Instant instant;
|
||||
ZoneId zoneId;
|
||||
if (null != this.format) {
|
||||
|
||||
431
hutool-core/src/main/java/cn/hutool/core/date/CalendarUtil.java
Normal file
431
hutool-core/src/main/java/cn/hutool/core/date/CalendarUtil.java
Normal file
@@ -0,0 +1,431 @@
|
||||
package cn.hutool.core.date;
|
||||
|
||||
import cn.hutool.core.comparator.CompareUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedHashSet;
|
||||
|
||||
/**
|
||||
* 针对{@link Calendar} 对象封装工具类
|
||||
*
|
||||
* @author looly
|
||||
* @since 5.3.0
|
||||
*/
|
||||
public class CalendarUtil {
|
||||
|
||||
/**
|
||||
* 创建Calendar对象,时间为默认时区的当前时间
|
||||
*
|
||||
* @return Calendar对象
|
||||
* @since 4.6.6
|
||||
*/
|
||||
public static Calendar calendar() {
|
||||
return Calendar.getInstance();
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为Calendar对象
|
||||
*
|
||||
* @param date 日期对象
|
||||
* @return Calendar对象
|
||||
*/
|
||||
public static Calendar calendar(Date date) {
|
||||
if (date instanceof DateTime) {
|
||||
return ((DateTime) date).toCalendar();
|
||||
} else {
|
||||
return calendar(date.getTime());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为Calendar对象
|
||||
*
|
||||
* @param millis 时间戳
|
||||
* @return Calendar对象
|
||||
*/
|
||||
public static Calendar calendar(long millis) {
|
||||
final Calendar cal = Calendar.getInstance();
|
||||
cal.setTimeInMillis(millis);
|
||||
return cal;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为上午
|
||||
*
|
||||
* @param calendar {@link Calendar}
|
||||
* @return 是否为上午
|
||||
*/
|
||||
public static boolean isAM(Calendar calendar) {
|
||||
return Calendar.AM == calendar.get(Calendar.AM_PM);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为下午
|
||||
*
|
||||
* @param calendar {@link Calendar}
|
||||
* @return 是否为下午
|
||||
*/
|
||||
public static boolean isPM(Calendar calendar) {
|
||||
return Calendar.PM == calendar.get(Calendar.AM_PM);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改日期为某个时间字段起始时间
|
||||
*
|
||||
* @param calendar {@link Calendar}
|
||||
* @param dateField 时间字段
|
||||
* @return 原{@link Calendar}
|
||||
*/
|
||||
public static Calendar truncate(Calendar calendar, DateField dateField) {
|
||||
return DateModifier.modify(calendar, dateField.getValue(), DateModifier.ModifyType.TRUNCATE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改日期为某个时间字段四舍五入时间
|
||||
*
|
||||
* @param calendar {@link Calendar}
|
||||
* @param dateField 时间字段
|
||||
* @return 原{@link Calendar}
|
||||
*/
|
||||
public static Calendar round(Calendar calendar, DateField dateField) {
|
||||
return DateModifier.modify(calendar, dateField.getValue(), DateModifier.ModifyType.ROUND);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改日期为某个时间字段结束时间
|
||||
*
|
||||
* @param calendar {@link Calendar}
|
||||
* @param dateField 时间字段
|
||||
* @return 原{@link Calendar}
|
||||
*/
|
||||
public static Calendar ceiling(Calendar calendar, DateField dateField) {
|
||||
return DateModifier.modify(calendar, dateField.getValue(), DateModifier.ModifyType.CEILING);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取秒级别的开始时间,即忽略毫秒部分
|
||||
*
|
||||
* @param calendar 日期 {@link Calendar}
|
||||
* @return {@link Calendar}
|
||||
* @since 4.6.2
|
||||
*/
|
||||
public static Calendar beginOfSecond(Calendar calendar) {
|
||||
return truncate(calendar, DateField.SECOND);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取秒级别的结束时间,即毫秒设置为999
|
||||
*
|
||||
* @param calendar 日期 {@link Calendar}
|
||||
* @return {@link Calendar}
|
||||
* @since 4.6.2
|
||||
*/
|
||||
public static Calendar endOfSecond(Calendar calendar) {
|
||||
return ceiling(calendar, DateField.SECOND);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取某天的开始时间
|
||||
*
|
||||
* @param calendar 日期 {@link Calendar}
|
||||
* @return {@link Calendar}
|
||||
*/
|
||||
public static Calendar beginOfDay(Calendar calendar) {
|
||||
return truncate(calendar, DateField.DAY_OF_MONTH);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取某天的结束时间
|
||||
*
|
||||
* @param calendar 日期 {@link Calendar}
|
||||
* @return {@link Calendar}
|
||||
*/
|
||||
public static Calendar endOfDay(Calendar calendar) {
|
||||
return ceiling(calendar, DateField.DAY_OF_MONTH);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取给定日期当前周的开始时间,周一定为一周的开始时间
|
||||
*
|
||||
* @param calendar 日期 {@link Calendar}
|
||||
* @return {@link Calendar}
|
||||
*/
|
||||
public static Calendar beginOfWeek(Calendar calendar) {
|
||||
return beginOfWeek(calendar, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取给定日期当前周的开始时间
|
||||
*
|
||||
* @param calendar 日期 {@link Calendar}
|
||||
* @param isMondayAsFirstDay 是否周一做为一周的第一天(false表示周日做为第一天)
|
||||
* @return {@link Calendar}
|
||||
* @since 3.1.2
|
||||
*/
|
||||
public static Calendar beginOfWeek(Calendar calendar, boolean isMondayAsFirstDay) {
|
||||
calendar.setFirstDayOfWeek(isMondayAsFirstDay ? Calendar.MONDAY : Calendar.SUNDAY);
|
||||
// WEEK_OF_MONTH为上限的字段(不包括),实际调整的为DAY_OF_MONTH
|
||||
return truncate(calendar, DateField.WEEK_OF_MONTH);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取某周的结束时间,周日定为一周的结束
|
||||
*
|
||||
* @param calendar 日期 {@link Calendar}
|
||||
* @return {@link Calendar}
|
||||
*/
|
||||
public static Calendar endOfWeek(Calendar calendar) {
|
||||
return endOfWeek(calendar, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取某周的结束时间
|
||||
*
|
||||
* @param calendar 日期 {@link Calendar}
|
||||
* @param isSundayAsLastDay 是否周日做为一周的最后一天(false表示周六做为最后一天)
|
||||
* @return {@link Calendar}
|
||||
*/
|
||||
public static Calendar endOfWeek(Calendar calendar, boolean isSundayAsLastDay) {
|
||||
calendar.setFirstDayOfWeek(isSundayAsLastDay ? Calendar.MONDAY : Calendar.SUNDAY);
|
||||
// WEEK_OF_MONTH为上限的字段(不包括),实际调整的为DAY_OF_MONTH
|
||||
return ceiling(calendar, DateField.WEEK_OF_MONTH);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取某月的开始时间
|
||||
*
|
||||
* @param calendar 日期 {@link Calendar}
|
||||
* @return {@link Calendar}
|
||||
*/
|
||||
public static Calendar beginOfMonth(Calendar calendar) {
|
||||
return truncate(calendar, DateField.MONTH);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取某月的结束时间
|
||||
*
|
||||
* @param calendar 日期 {@link Calendar}
|
||||
* @return {@link Calendar}
|
||||
*/
|
||||
public static Calendar endOfMonth(Calendar calendar) {
|
||||
return ceiling(calendar, DateField.MONTH);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取某季度的开始时间
|
||||
*
|
||||
* @param calendar 日期 {@link Calendar}
|
||||
* @return {@link Calendar}
|
||||
* @since 4.1.0
|
||||
*/
|
||||
public static Calendar beginOfQuarter(Calendar calendar) {
|
||||
//noinspection MagicConstant
|
||||
calendar.set(Calendar.MONTH, calendar.get(DateField.MONTH.getValue()) / 3 * 3);
|
||||
calendar.set(Calendar.DAY_OF_MONTH, 1);
|
||||
return beginOfDay(calendar);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取某季度的结束时间
|
||||
*
|
||||
* @param calendar 日期 {@link Calendar}
|
||||
* @return {@link Calendar}
|
||||
* @since 4.1.0
|
||||
*/
|
||||
public static Calendar endOfQuarter(Calendar calendar) {
|
||||
//noinspection MagicConstant
|
||||
calendar.set(Calendar.MONTH, calendar.get(DateField.MONTH.getValue()) / 3 * 3 + 2);
|
||||
calendar.set(Calendar.DAY_OF_MONTH, calendar.getActualMaximum(Calendar.DAY_OF_MONTH));
|
||||
return endOfDay(calendar);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取某年的开始时间
|
||||
*
|
||||
* @param calendar 日期 {@link Calendar}
|
||||
* @return {@link Calendar}
|
||||
*/
|
||||
public static Calendar beginOfYear(Calendar calendar) {
|
||||
return truncate(calendar, DateField.YEAR);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取某年的结束时间
|
||||
*
|
||||
* @param calendar 日期 {@link Calendar}
|
||||
* @return {@link Calendar}
|
||||
*/
|
||||
public static Calendar endOfYear(Calendar calendar) {
|
||||
return ceiling(calendar, DateField.YEAR);
|
||||
}
|
||||
|
||||
/**
|
||||
* 比较两个日期是否为同一天
|
||||
*
|
||||
* @param cal1 日期1
|
||||
* @param cal2 日期2
|
||||
* @return 是否为同一天
|
||||
*/
|
||||
public static boolean isSameDay(Calendar cal1, Calendar cal2) {
|
||||
if (cal1 == null || cal2 == null) {
|
||||
throw new IllegalArgumentException("The date must not be null");
|
||||
}
|
||||
return cal1.get(Calendar.DAY_OF_YEAR) == cal2.get(Calendar.DAY_OF_YEAR) && //
|
||||
cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR) && //
|
||||
cal1.get(Calendar.ERA) == cal2.get(Calendar.ERA);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得指定日期区间内的年份和季度<br>
|
||||
*
|
||||
* @param startDate 起始日期(包含)
|
||||
* @param endDate 结束日期(包含)
|
||||
* @return 季度列表 ,元素类似于 20132
|
||||
* @since 4.1.15
|
||||
*/
|
||||
public static LinkedHashSet<String> yearAndQuarter(long startDate, long endDate) {
|
||||
LinkedHashSet<String> quarters = new LinkedHashSet<>();
|
||||
final Calendar cal = calendar(startDate);
|
||||
while (startDate <= endDate) {
|
||||
// 如果开始时间超出结束时间,让结束时间为开始时间,处理完后结束循环
|
||||
quarters.add(yearAndQuarter(cal));
|
||||
|
||||
cal.add(Calendar.MONTH, 3);
|
||||
startDate = cal.getTimeInMillis();
|
||||
}
|
||||
|
||||
return quarters;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得指定日期年份和季度<br>
|
||||
* 格式:[20131]表示2013年第一季度
|
||||
*
|
||||
* @param cal 日期
|
||||
* @return 年和季度,格式类似于20131
|
||||
*/
|
||||
public static String yearAndQuarter(Calendar cal) {
|
||||
return StrUtil.builder().append(cal.get(Calendar.YEAR)).append(cal.get(Calendar.MONTH) / 3 + 1).toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定日期字段的最小值,例如分钟的最小值是0
|
||||
*
|
||||
* @param calendar {@link Calendar}
|
||||
* @param dateField {@link DateField}
|
||||
* @return 字段最小值
|
||||
* @see Calendar#getActualMinimum(int)
|
||||
* @since 4.5.7
|
||||
*/
|
||||
public static int getBeginValue(Calendar calendar, int dateField) {
|
||||
if (Calendar.DAY_OF_WEEK == dateField) {
|
||||
return calendar.getFirstDayOfWeek();
|
||||
}
|
||||
return calendar.getActualMinimum(dateField);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定日期字段的最大值,例如分钟的最大值是59
|
||||
*
|
||||
* @param calendar {@link Calendar}
|
||||
* @param dateField {@link DateField}
|
||||
* @return 字段最大值
|
||||
* @see Calendar#getActualMaximum(int)
|
||||
* @since 4.5.7
|
||||
*/
|
||||
public static int getEndValue(Calendar calendar, int dateField) {
|
||||
if (Calendar.DAY_OF_WEEK == dateField) {
|
||||
return (calendar.getFirstDayOfWeek() + 6) % 7;
|
||||
}
|
||||
return calendar.getActualMaximum(dateField);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calendar{@link Instant}对象
|
||||
*
|
||||
* @param calendar Date对象
|
||||
* @return {@link Instant}对象
|
||||
* @since 5.0.5
|
||||
*/
|
||||
public static Instant toInstant(Calendar calendar) {
|
||||
return null == calendar ? null : calendar.toInstant();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Calendar} 转换为 {@link LocalDateTime},使用系统默认时区
|
||||
*
|
||||
* @param calendar {@link Calendar}
|
||||
* @return {@link LocalDateTime}
|
||||
* @since 5.0.5
|
||||
*/
|
||||
public static LocalDateTime toLocalDateTime(Calendar calendar) {
|
||||
return LocalDateTime.ofInstant(calendar.toInstant(), calendar.getTimeZone().toZoneId());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code null}安全的{@link Calendar}比较,{@code null}小于任何日期
|
||||
*
|
||||
* @param calendar1 日期1
|
||||
* @param calendar2 日期2
|
||||
* @return 比较结果,如果calendar1 < calendar2,返回数小于0,calendar1==calendar2返回0,calendar1 > calendar2 大于0
|
||||
* @since 4.6.2
|
||||
*/
|
||||
public static int compare(Calendar calendar1, Calendar calendar2) {
|
||||
return CompareUtil.compare(calendar1, calendar2);
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算相对于dateToCompare的年龄,长用于计算指定生日在某年的年龄
|
||||
*
|
||||
* @param birthday 生日
|
||||
* @param dateToCompare 需要对比的日期
|
||||
* @return 年龄
|
||||
*/
|
||||
public static int age(Calendar birthday, Calendar dateToCompare) {
|
||||
return age(birthday.getTimeInMillis(), dateToCompare.getTimeInMillis());
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算相对于dateToCompare的年龄,长用于计算指定生日在某年的年龄
|
||||
*
|
||||
* @param birthday 生日
|
||||
* @param dateToCompare 需要对比的日期
|
||||
* @return 年龄
|
||||
*/
|
||||
protected static int age(long birthday, long dateToCompare) {
|
||||
if (birthday > dateToCompare) {
|
||||
throw new IllegalArgumentException("Birthday is after dateToCompare!");
|
||||
}
|
||||
|
||||
final Calendar cal = Calendar.getInstance();
|
||||
cal.setTimeInMillis(dateToCompare);
|
||||
|
||||
final int year = cal.get(Calendar.YEAR);
|
||||
final int month = cal.get(Calendar.MONTH);
|
||||
final int dayOfMonth = cal.get(Calendar.DAY_OF_MONTH);
|
||||
final boolean isLastDayOfMonth = dayOfMonth == cal.getActualMaximum(Calendar.DAY_OF_MONTH);
|
||||
|
||||
cal.setTimeInMillis(birthday);
|
||||
int age = year - cal.get(Calendar.YEAR);
|
||||
|
||||
final int monthBirth = cal.get(Calendar.MONTH);
|
||||
if (month == monthBirth) {
|
||||
|
||||
final int dayOfMonthBirth = cal.get(Calendar.DAY_OF_MONTH);
|
||||
final boolean isLastDayOfMonthBirth = dayOfMonthBirth == cal.getActualMaximum(Calendar.DAY_OF_MONTH);
|
||||
if ((false == isLastDayOfMonth || false == isLastDayOfMonthBirth) && dayOfMonth < dayOfMonthBirth) {
|
||||
// 如果生日在当月,但是未达到生日当天的日期,年龄减一
|
||||
age--;
|
||||
}
|
||||
} else if (month < monthBirth) {
|
||||
// 如果当前月份未达到生日的月份,年龄计算减一
|
||||
age--;
|
||||
}
|
||||
|
||||
return age;
|
||||
}
|
||||
}
|
||||
@@ -19,16 +19,16 @@ public class ChineseDate {
|
||||
private static final Date baseDate = DateUtil.parseDate("1900-01-31");
|
||||
|
||||
//农历年
|
||||
private int year;
|
||||
private final int year;
|
||||
//农历月
|
||||
private int month;
|
||||
private final int month;
|
||||
//农历日
|
||||
private int day;
|
||||
private final int day;
|
||||
//是否闰年
|
||||
private boolean leap;
|
||||
private String[] chineseNumber = {"一", "二", "三", "四", "五", "六", "七", "八", "九", "十", "十一", "十二"};
|
||||
private String[] chineseNumberName = {"正", "二", "三", "四", "五", "六", "七", "八", "九", "十", "十一", "腊"};
|
||||
private long[] lunarInfo = new long[]{0x04bd8, 0x04ae0, 0x0a570,
|
||||
private final String[] chineseNumber = {"一", "二", "三", "四", "五", "六", "七", "八", "九", "十", "十一", "十二"};
|
||||
private final String[] chineseNumberName = {"正", "二", "三", "四", "五", "六", "七", "八", "九", "十", "十一", "腊"};
|
||||
private final long[] lunarInfo = new long[]{0x04bd8, 0x04ae0, 0x0a570,
|
||||
0x054d5, 0x0d260, 0x0d950, 0x16554, 0x056a0, 0x09ad0, 0x055d2,
|
||||
0x04ae0, 0x0a5b6, 0x0a4d0, 0x0d250, 0x1d255, 0x0b540, 0x0d6a0,
|
||||
0x0ada2, 0x095b0, 0x14977, 0x04970, 0x0a4b0, 0x0b4b5, 0x06a50,
|
||||
@@ -51,7 +51,7 @@ public class ChineseDate {
|
||||
0x1d0b6, 0x0d250, 0x0d520, 0x0dd45, 0x0b5a0, 0x056d0, 0x055b2,
|
||||
0x049b0, 0x0a577, 0x0a4b0, 0x0aa50, 0x1b255, 0x06d20, 0x0ada0};
|
||||
//农历节日 *表示放假日
|
||||
private String[] lFtv = new String[]{
|
||||
private final String[] lFtv = new String[]{
|
||||
"0101 春节", "0102 大年初二", "0103 大年初三", "0104 大年初四",
|
||||
"0105 大年初五", "0106 大年初六", "0107 大年初七", "0105 路神生日",
|
||||
"0115 元宵节", "0202 龙抬头", "0219 观世音圣诞", "0404 寒食节",
|
||||
|
||||
@@ -16,9 +16,9 @@ public class DateBetween implements Serializable{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 开始日期 */
|
||||
private Date begin;
|
||||
private final Date begin;
|
||||
/** 结束日期 */
|
||||
private Date end;
|
||||
private final Date end;
|
||||
|
||||
/**
|
||||
* 创建<br>
|
||||
|
||||
@@ -103,7 +103,7 @@ public enum DateField {
|
||||
MILLISECOND(Calendar.MILLISECOND);
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
private int value;
|
||||
private final int value;
|
||||
|
||||
DateField(int value) {
|
||||
this.value = value;
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
package cn.hutool.core.date;
|
||||
|
||||
import cn.hutool.core.date.format.DateParser;
|
||||
import cn.hutool.core.date.format.DatePrinter;
|
||||
import cn.hutool.core.date.format.FastDateFormat;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import java.sql.Timestamp;
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
@@ -13,13 +20,6 @@ import java.util.Date;
|
||||
import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import cn.hutool.core.date.format.DateParser;
|
||||
import cn.hutool.core.date.format.DatePrinter;
|
||||
import cn.hutool.core.date.format.FastDateFormat;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
/**
|
||||
* 包装java.util.Date
|
||||
*
|
||||
@@ -833,10 +833,10 @@ public class DateTime extends Date {
|
||||
// -------------------------------------------------------------------- toString start
|
||||
|
||||
/**
|
||||
* 转为"yyyy-MM-dd yyyy-MM-dd HH:mm:ss " 格式字符串<br>
|
||||
* 转为"yyyy-MM-dd HH:mm:ss" 格式字符串<br>
|
||||
* 如果时区被设置,会转换为其时区对应的时间,否则转换为当前地点对应的时区
|
||||
*
|
||||
* @return "yyyy-MM-dd yyyy-MM-dd HH:mm:ss " 格式字符串
|
||||
* @return "yyyy-MM-dd HH:mm:ss" 格式字符串
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
@@ -844,10 +844,10 @@ public class DateTime extends Date {
|
||||
}
|
||||
|
||||
/**
|
||||
* 转为"yyyy-MM-dd yyyy-MM-dd HH:mm:ss " 格式字符串<br>
|
||||
* 转为"yyyy-MM-dd HH:mm:ss" 格式字符串<br>
|
||||
* 时区使用当前地区的默认时区
|
||||
*
|
||||
* @return "yyyy-MM-dd yyyy-MM-dd HH:mm:ss " 格式字符串
|
||||
* @return "yyyy-MM-dd HH:mm:ss" 格式字符串
|
||||
* @since 4.1.14
|
||||
*/
|
||||
public String toStringDefaultTimeZone() {
|
||||
@@ -855,11 +855,11 @@ public class DateTime extends Date {
|
||||
}
|
||||
|
||||
/**
|
||||
* 转为"yyyy-MM-dd yyyy-MM-dd HH:mm:ss " 格式字符串<br>
|
||||
* 转为"yyyy-MM-dd HH:mm:ss" 格式字符串<br>
|
||||
* 如果时区不为{@code null},会转换为其时区对应的时间,否则转换为当前时间对应的时区
|
||||
*
|
||||
* @param timeZone 时区
|
||||
* @return "yyyy-MM-dd yyyy-MM-dd HH:mm:ss " 格式字符串
|
||||
* @return "yyyy-MM-dd HH:mm:ss" 格式字符串
|
||||
* @since 4.1.14
|
||||
*/
|
||||
public String toString(TimeZone timeZone) {
|
||||
|
||||
@@ -19,7 +19,7 @@ public enum DateUnit {
|
||||
/**一周的毫秒数 */
|
||||
WEEK(DAY.getMillis() * 7);
|
||||
|
||||
private long millis;
|
||||
private final long millis;
|
||||
DateUnit(long millis){
|
||||
this.millis = millis;
|
||||
}
|
||||
|
||||
@@ -3,10 +3,10 @@ package cn.hutool.core.date;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.comparator.CompareUtil;
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.date.DateModifier.ModifyType;
|
||||
import cn.hutool.core.date.format.DateParser;
|
||||
import cn.hutool.core.date.format.DatePrinter;
|
||||
import cn.hutool.core.date.format.FastDateFormat;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.lang.PatternPool;
|
||||
import cn.hutool.core.util.NumberUtil;
|
||||
import cn.hutool.core.util.ReUtil;
|
||||
@@ -39,7 +39,7 @@ import java.util.concurrent.TimeUnit;
|
||||
*
|
||||
* @author xiaoleilu
|
||||
*/
|
||||
public class DateUtil {
|
||||
public class DateUtil extends CalendarUtil {
|
||||
|
||||
/**
|
||||
* java.util.Date EEE MMM zzz 缩写数组
|
||||
@@ -129,42 +129,6 @@ public class DateUtil {
|
||||
return new DateTime(temporalAccessor);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建Calendar对象,时间为默认时区的当前时间
|
||||
*
|
||||
* @return Calendar对象
|
||||
* @since 4.6.6
|
||||
*/
|
||||
public static Calendar calendar() {
|
||||
return Calendar.getInstance();
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为Calendar对象
|
||||
*
|
||||
* @param date 日期对象
|
||||
* @return Calendar对象
|
||||
*/
|
||||
public static Calendar calendar(Date date) {
|
||||
if (date instanceof DateTime) {
|
||||
return ((DateTime) date).toCalendar();
|
||||
} else {
|
||||
return calendar(date.getTime());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为Calendar对象
|
||||
*
|
||||
* @param millis 时间戳
|
||||
* @return Calendar对象
|
||||
*/
|
||||
public static Calendar calendar(long millis) {
|
||||
final Calendar cal = Calendar.getInstance();
|
||||
cal.setTimeInMillis(millis);
|
||||
return cal;
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前时间的时间戳
|
||||
*
|
||||
@@ -372,17 +336,6 @@ public class DateUtil {
|
||||
return DateTime.of(date).isAM();
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为上午
|
||||
*
|
||||
* @param calendar {@link Calendar}
|
||||
* @return 是否为上午
|
||||
* @since 4.5.7
|
||||
*/
|
||||
public static boolean isAM(Calendar calendar) {
|
||||
return Calendar.AM == calendar.get(Calendar.AM_PM);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为下午
|
||||
*
|
||||
@@ -503,29 +456,6 @@ public class DateUtil {
|
||||
}
|
||||
return yearAndQuarter(startDate.getTime(), endDate.getTime());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得指定日期区间内的年份和季节<br>
|
||||
*
|
||||
* @param startDate 起始日期(包含)
|
||||
* @param endDate 结束日期(包含)
|
||||
* @return 季度列表 ,元素类似于 20132
|
||||
* @since 4.1.15
|
||||
*/
|
||||
public static LinkedHashSet<String> yearAndQuarter(long startDate, long endDate) {
|
||||
LinkedHashSet<String> quarters = new LinkedHashSet<>();
|
||||
final Calendar cal = calendar(startDate);
|
||||
while (startDate <= endDate) {
|
||||
// 如果开始时间超出结束时间,让结束时间为开始时间,处理完后结束循环
|
||||
quarters.add(yearAndQuarter(cal));
|
||||
|
||||
cal.add(Calendar.MONTH, 3);
|
||||
startDate = cal.getTimeInMillis();
|
||||
}
|
||||
|
||||
return quarters;
|
||||
}
|
||||
|
||||
// ------------------------------------ Format start ----------------------------------------------
|
||||
|
||||
/**
|
||||
@@ -797,7 +727,14 @@ public class DateUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式yyyy-MM-dd HH:mm:ss
|
||||
* 解析日期时间字符串,格式支持:
|
||||
*
|
||||
* <pre>
|
||||
* yyyy-MM-dd HH:mm:ss
|
||||
* yyyy/MM/dd HH:mm:ss
|
||||
* yyyy.MM.dd HH:mm:ss
|
||||
* yyyy年MM月dd日 HH:mm:ss
|
||||
* </pre>
|
||||
*
|
||||
* @param dateString 标准形式的时间字符串
|
||||
* @return 日期对象
|
||||
@@ -808,7 +745,13 @@ public class DateUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析格式为yyyy-MM-dd的日期,忽略时分秒
|
||||
* 解析日期字符串,忽略时分秒,支持的格式包括:
|
||||
* <pre>
|
||||
* yyyy-MM-dd
|
||||
* yyyy/MM/dd
|
||||
* yyyy.MM.dd
|
||||
* yyyy年MM月dd日
|
||||
* </pre>
|
||||
*
|
||||
* @param dateString 标准形式的日期字符串
|
||||
* @return 日期对象
|
||||
@@ -999,18 +942,6 @@ public class DateUtil {
|
||||
return new DateTime(truncate(calendar(date), dateField));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改日期为某个时间字段起始时间
|
||||
*
|
||||
* @param calendar {@link Calendar}
|
||||
* @param dateField 时间字段
|
||||
* @return 原{@link Calendar}
|
||||
* @since 4.5.7
|
||||
*/
|
||||
public static Calendar truncate(Calendar calendar, DateField dateField) {
|
||||
return DateModifier.modify(calendar, dateField.getValue(), ModifyType.TRUNCATE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改日期为某个时间字段四舍五入时间
|
||||
*
|
||||
@@ -1023,18 +954,6 @@ public class DateUtil {
|
||||
return new DateTime(round(calendar(date), dateField));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改日期为某个时间字段四舍五入时间
|
||||
*
|
||||
* @param calendar {@link Calendar}
|
||||
* @param dateField 时间字段
|
||||
* @return 原{@link Calendar}
|
||||
* @since 4.5.7
|
||||
*/
|
||||
public static Calendar round(Calendar calendar, DateField dateField) {
|
||||
return DateModifier.modify(calendar, dateField.getValue(), ModifyType.ROUND);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改日期为某个时间字段结束时间
|
||||
*
|
||||
@@ -1047,18 +966,6 @@ public class DateUtil {
|
||||
return new DateTime(ceiling(calendar(date), dateField));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改日期为某个时间字段结束时间
|
||||
*
|
||||
* @param calendar {@link Calendar}
|
||||
* @param dateField 时间字段
|
||||
* @return 原{@link Calendar}
|
||||
* @since 4.5.7
|
||||
*/
|
||||
public static Calendar ceiling(Calendar calendar, DateField dateField) {
|
||||
return DateModifier.modify(calendar, dateField.getValue(), ModifyType.CEILING);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取秒级别的开始时间,即忽略毫秒部分
|
||||
*
|
||||
@@ -1081,28 +988,6 @@ public class DateUtil {
|
||||
return new DateTime(endOfSecond(calendar(date)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取秒级别的开始时间,即忽略毫秒部分
|
||||
*
|
||||
* @param calendar 日期 {@link Calendar}
|
||||
* @return {@link Calendar}
|
||||
* @since 4.6.2
|
||||
*/
|
||||
public static Calendar beginOfSecond(Calendar calendar) {
|
||||
return truncate(calendar, DateField.SECOND);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取秒级别的结束时间,即毫秒设置为999
|
||||
*
|
||||
* @param calendar 日期 {@link Calendar}
|
||||
* @return {@link Calendar}
|
||||
* @since 4.6.2
|
||||
*/
|
||||
public static Calendar endOfSecond(Calendar calendar) {
|
||||
return ceiling(calendar, DateField.SECOND);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取某天的开始时间
|
||||
*
|
||||
@@ -1123,26 +1008,6 @@ public class DateUtil {
|
||||
return new DateTime(endOfDay(calendar(date)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取某天的开始时间
|
||||
*
|
||||
* @param calendar 日期 {@link Calendar}
|
||||
* @return {@link Calendar}
|
||||
*/
|
||||
public static Calendar beginOfDay(Calendar calendar) {
|
||||
return truncate(calendar, DateField.DAY_OF_MONTH);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取某天的结束时间
|
||||
*
|
||||
* @param calendar 日期 {@link Calendar}
|
||||
* @return {@link Calendar}
|
||||
*/
|
||||
public static Calendar endOfDay(Calendar calendar) {
|
||||
return ceiling(calendar, DateField.DAY_OF_MONTH);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取某周的开始时间,周一定为一周的开始时间
|
||||
*
|
||||
@@ -1163,54 +1028,6 @@ public class DateUtil {
|
||||
return new DateTime(endOfWeek(calendar(date)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取给定日期当前周的开始时间,周一定为一周的开始时间
|
||||
*
|
||||
* @param calendar 日期 {@link Calendar}
|
||||
* @return {@link Calendar}
|
||||
*/
|
||||
public static Calendar beginOfWeek(Calendar calendar) {
|
||||
return beginOfWeek(calendar, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取给定日期当前周的开始时间
|
||||
*
|
||||
* @param calendar 日期 {@link Calendar}
|
||||
* @param isMondayAsFirstDay 是否周一做为一周的第一天(false表示周日做为第一天)
|
||||
* @return {@link Calendar}
|
||||
* @since 3.1.2
|
||||
*/
|
||||
public static Calendar beginOfWeek(Calendar calendar, boolean isMondayAsFirstDay) {
|
||||
calendar.setFirstDayOfWeek(isMondayAsFirstDay ? Calendar.MONDAY : Calendar.SUNDAY);
|
||||
// WEEK_OF_MONTH为上限的字段(不包括),实际调整的为DAY_OF_MONTH
|
||||
return truncate(calendar, DateField.WEEK_OF_MONTH);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取某周的结束时间,周日定为一周的结束
|
||||
*
|
||||
* @param calendar 日期 {@link Calendar}
|
||||
* @return {@link Calendar}
|
||||
*/
|
||||
public static Calendar endOfWeek(Calendar calendar) {
|
||||
return endOfWeek(calendar, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取某周的结束时间
|
||||
*
|
||||
* @param calendar 日期 {@link Calendar}
|
||||
* @param isSundayAsLastDay 是否周日做为一周的最后一天(false表示周六做为最后一天)
|
||||
* @return {@link Calendar}
|
||||
* @since 3.1.2
|
||||
*/
|
||||
public static Calendar endOfWeek(Calendar calendar, boolean isSundayAsLastDay) {
|
||||
calendar.setFirstDayOfWeek(isSundayAsLastDay ? Calendar.MONDAY : Calendar.SUNDAY);
|
||||
// WEEK_OF_MONTH为上限的字段(不包括),实际调整的为DAY_OF_MONTH
|
||||
return ceiling(calendar, DateField.WEEK_OF_MONTH);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取某月的开始时间
|
||||
*
|
||||
@@ -1231,26 +1048,6 @@ public class DateUtil {
|
||||
return new DateTime(endOfMonth(calendar(date)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取某月的开始时间
|
||||
*
|
||||
* @param calendar 日期 {@link Calendar}
|
||||
* @return {@link Calendar}
|
||||
*/
|
||||
public static Calendar beginOfMonth(Calendar calendar) {
|
||||
return truncate(calendar, DateField.MONTH);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取某月的结束时间
|
||||
*
|
||||
* @param calendar 日期 {@link Calendar}
|
||||
* @return {@link Calendar}
|
||||
*/
|
||||
public static Calendar endOfMonth(Calendar calendar) {
|
||||
return ceiling(calendar, DateField.MONTH);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取某季度的开始时间
|
||||
*
|
||||
@@ -1271,34 +1068,6 @@ public class DateUtil {
|
||||
return new DateTime(endOfQuarter(calendar(date)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取某季度的开始时间
|
||||
*
|
||||
* @param calendar 日期 {@link Calendar}
|
||||
* @return {@link Calendar}
|
||||
* @since 4.1.0
|
||||
*/
|
||||
public static Calendar beginOfQuarter(Calendar calendar) {
|
||||
//noinspection MagicConstant
|
||||
calendar.set(Calendar.MONTH, calendar.get(DateField.MONTH.getValue()) / 3 * 3);
|
||||
calendar.set(Calendar.DAY_OF_MONTH, 1);
|
||||
return beginOfDay(calendar);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取某季度的结束时间
|
||||
*
|
||||
* @param calendar 日期 {@link Calendar}
|
||||
* @return {@link Calendar}
|
||||
* @since 4.1.0
|
||||
*/
|
||||
public static Calendar endOfQuarter(Calendar calendar) {
|
||||
//noinspection MagicConstant
|
||||
calendar.set(Calendar.MONTH, calendar.get(DateField.MONTH.getValue()) / 3 * 3 + 2);
|
||||
calendar.set(Calendar.DAY_OF_MONTH, calendar.getActualMaximum(Calendar.DAY_OF_MONTH));
|
||||
return endOfDay(calendar);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取某年的开始时间
|
||||
*
|
||||
@@ -1318,27 +1087,6 @@ public class DateUtil {
|
||||
public static DateTime endOfYear(Date date) {
|
||||
return new DateTime(endOfYear(calendar(date)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取某年的开始时间
|
||||
*
|
||||
* @param calendar 日期 {@link Calendar}
|
||||
* @return {@link Calendar}
|
||||
*/
|
||||
public static Calendar beginOfYear(Calendar calendar) {
|
||||
return truncate(calendar, DateField.YEAR);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取某年的结束时间
|
||||
*
|
||||
* @param calendar 日期 {@link Calendar}
|
||||
* @return {@link Calendar}
|
||||
*/
|
||||
public static Calendar endOfYear(Calendar calendar) {
|
||||
return ceiling(calendar, DateField.YEAR);
|
||||
}
|
||||
|
||||
// --------------------------------------------------- Offset for now
|
||||
|
||||
/**
|
||||
@@ -1699,23 +1447,6 @@ public class DateUtil {
|
||||
return isSameDay(calendar(date1), calendar(date2));
|
||||
}
|
||||
|
||||
/**
|
||||
* 比较两个日期是否为同一天
|
||||
*
|
||||
* @param cal1 日期1
|
||||
* @param cal2 日期2
|
||||
* @return 是否为同一天
|
||||
* @since 4.1.13
|
||||
*/
|
||||
public static boolean isSameDay(Calendar cal1, Calendar cal2) {
|
||||
if (cal1 == null || cal2 == null) {
|
||||
throw new IllegalArgumentException("The date must not be null");
|
||||
}
|
||||
return cal1.get(Calendar.DAY_OF_YEAR) == cal2.get(Calendar.DAY_OF_YEAR) && //
|
||||
cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR) && //
|
||||
cal1.get(Calendar.ERA) == cal2.get(Calendar.ERA);
|
||||
}
|
||||
|
||||
/**
|
||||
* 计时,常用于记录某段代码的执行时间,单位:纳秒
|
||||
*
|
||||
@@ -1876,41 +1607,16 @@ public class DateUtil {
|
||||
/**
|
||||
* 计算相对于dateToCompare的年龄,长用于计算指定生日在某年的年龄
|
||||
*
|
||||
* @param birthDay 生日
|
||||
* @param birthday 生日
|
||||
* @param dateToCompare 需要对比的日期
|
||||
* @return 年龄
|
||||
*/
|
||||
public static int age(Date birthDay, Date dateToCompare) {
|
||||
Calendar cal = Calendar.getInstance();
|
||||
cal.setTime(dateToCompare);
|
||||
|
||||
if (cal.before(birthDay)) {
|
||||
throw new IllegalArgumentException(StrUtil.format("Birthday is after date {}!", formatDate(dateToCompare)));
|
||||
public static int age(Date birthday, Date dateToCompare) {
|
||||
Assert.notNull(birthday, "Birthday can not be null !");
|
||||
if (null == dateToCompare) {
|
||||
dateToCompare = date();
|
||||
}
|
||||
|
||||
final int year = cal.get(Calendar.YEAR);
|
||||
final int month = cal.get(Calendar.MONTH);
|
||||
final int dayOfMonth = cal.get(Calendar.DAY_OF_MONTH);
|
||||
final boolean isLastDayOfMonth = dayOfMonth == cal.getActualMaximum(Calendar.DAY_OF_MONTH);
|
||||
|
||||
cal.setTime(birthDay);
|
||||
int age = year - cal.get(Calendar.YEAR);
|
||||
|
||||
final int monthBirth = cal.get(Calendar.MONTH);
|
||||
if (month == monthBirth) {
|
||||
|
||||
final int dayOfMonthBirth = cal.get(Calendar.DAY_OF_MONTH);
|
||||
final boolean isLastDayOfMonthBirth = dayOfMonthBirth == cal.getActualMaximum(Calendar.DAY_OF_MONTH);
|
||||
if ((false == isLastDayOfMonth || false == isLastDayOfMonthBirth) && dayOfMonth < dayOfMonthBirth) {
|
||||
// 如果生日在当月,但是未达到生日当天的日期,年龄减一
|
||||
age--;
|
||||
}
|
||||
} else if (month < monthBirth) {
|
||||
// 如果当前月份未达到生日的月份,年龄计算减一
|
||||
age--;
|
||||
}
|
||||
|
||||
return age;
|
||||
return age(birthday.getTime(), dateToCompare.getTime());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2055,38 +1761,6 @@ public class DateUtil {
|
||||
return Zodiac.getChineseZodiac(year);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定日期字段的最小值,例如分钟的最小值是0
|
||||
*
|
||||
* @param calendar {@link Calendar}
|
||||
* @param dateField {@link DateField}
|
||||
* @return 字段最小值
|
||||
* @see Calendar#getActualMinimum(int)
|
||||
* @since 4.5.7
|
||||
*/
|
||||
public static int getBeginValue(Calendar calendar, int dateField) {
|
||||
if (Calendar.DAY_OF_WEEK == dateField) {
|
||||
return calendar.getFirstDayOfWeek();
|
||||
}
|
||||
return calendar.getActualMinimum(dateField);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定日期字段的最大值,例如分钟的最大值是59
|
||||
*
|
||||
* @param calendar {@link Calendar}
|
||||
* @param dateField {@link DateField}
|
||||
* @return 字段最大值
|
||||
* @see Calendar#getActualMaximum(int)
|
||||
* @since 4.5.7
|
||||
*/
|
||||
public static int getEndValue(Calendar calendar, int dateField) {
|
||||
if (Calendar.DAY_OF_WEEK == dateField) {
|
||||
return (calendar.getFirstDayOfWeek() + 6) % 7;
|
||||
}
|
||||
return calendar.getActualMaximum(dateField);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code null}安全的日期比较,{@code null}对象排在末尾
|
||||
*
|
||||
@@ -2099,18 +1773,6 @@ public class DateUtil {
|
||||
return CompareUtil.compare(date1, date2);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code null}安全的{@link Calendar}比较,{@code null}小于任何日期
|
||||
*
|
||||
* @param calendar1 日期1
|
||||
* @param calendar2 日期2
|
||||
* @return 比较结果,如果calendar1 < calendar2,返回数小于0,calendar1==calendar2返回0,calendar1 > calendar2 大于0
|
||||
* @since 4.6.2
|
||||
*/
|
||||
public static int compare(Calendar calendar1, Calendar calendar2) {
|
||||
return CompareUtil.compare(calendar1, calendar2);
|
||||
}
|
||||
|
||||
/**
|
||||
* 纳秒转毫秒
|
||||
*
|
||||
@@ -2144,17 +1806,6 @@ public class DateUtil {
|
||||
return null == date ? null : date.toInstant();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calendar{@link Instant}对象
|
||||
*
|
||||
* @param calendar Date对象
|
||||
* @return {@link Instant}对象
|
||||
* @since 5.0.5
|
||||
*/
|
||||
public static Instant toInstant(Calendar calendar) {
|
||||
return null == calendar ? null : calendar.toInstant();
|
||||
}
|
||||
|
||||
/**
|
||||
* Date对象转换为{@link Instant}对象
|
||||
*
|
||||
@@ -2202,21 +1853,10 @@ public class DateUtil {
|
||||
return LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Calendar} 转换为 {@link LocalDateTime},使用系统默认时区
|
||||
*
|
||||
* @param calendar {@link Calendar}
|
||||
* @return {@link LocalDateTime}
|
||||
* @since 5.0.5
|
||||
*/
|
||||
public static LocalDateTime toLocalDateTime(Calendar calendar) {
|
||||
return LocalDateTime.ofInstant(calendar.toInstant(), calendar.getTimeZone().toZoneId());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Date} 转换为 {@link LocalDateTime},使用系统默认时区
|
||||
*
|
||||
* @param date {@link Calendar}
|
||||
* @param date {@link Date}
|
||||
* @return {@link LocalDateTime}
|
||||
* @since 5.0.5
|
||||
*/
|
||||
@@ -2227,16 +1867,6 @@ public class DateUtil {
|
||||
|
||||
// ------------------------------------------------------------------------ Private method start
|
||||
|
||||
/**
|
||||
* 获得指定日期年份和季节<br>
|
||||
* 格式:[20131]表示2013年第一季度
|
||||
*
|
||||
* @param cal 日期
|
||||
*/
|
||||
private static String yearAndQuarter(Calendar cal) {
|
||||
return StrUtil.builder().append(cal.get(Calendar.YEAR)).append(cal.get(Calendar.MONTH) / 3 + 1).toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 标准化日期,默认处理以空格区分的日期时间格式,空格前为日期,空格后为时间:<br>
|
||||
* 将以下字符替换为"-"
|
||||
|
||||
@@ -53,7 +53,7 @@ public enum Month {
|
||||
UNDECIMBER(Calendar.UNDECIMBER);
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
private int value;
|
||||
private final int value;
|
||||
|
||||
Month(int value) {
|
||||
this.value = value;
|
||||
|
||||
@@ -23,7 +23,7 @@ public enum Quarter {
|
||||
Q4(4);
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
private int value;
|
||||
private final int value;
|
||||
|
||||
Quarter(int value) {
|
||||
this.value = value;
|
||||
|
||||
@@ -26,7 +26,7 @@ public class SystemClock {
|
||||
* 构造
|
||||
* @param period 时钟更新间隔,单位毫秒
|
||||
*/
|
||||
private SystemClock(long period) {
|
||||
public SystemClock(long period) {
|
||||
this.period = period;
|
||||
this.now = System.currentTimeMillis();
|
||||
scheduleClockUpdating();
|
||||
|
||||
@@ -12,7 +12,7 @@ public class TimeInterval implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private long time;
|
||||
private boolean isNano;
|
||||
private final boolean isNano;
|
||||
|
||||
/**
|
||||
* 构造,默认使用毫秒计数
|
||||
|
||||
@@ -36,7 +36,7 @@ public enum Week {
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
/** 星期对应{@link Calendar} 中的Week值 */
|
||||
private int value;
|
||||
private final int value;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package cn.hutool.core.date.format;
|
||||
|
||||
import cn.hutool.core.date.DateException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.text.DateFormatSymbols;
|
||||
@@ -12,8 +14,6 @@ import java.util.TimeZone;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
import cn.hutool.core.date.DateException;
|
||||
|
||||
/**
|
||||
* {@link java.text.SimpleDateFormat} 的线程安全版本,用于将 {@link Date} 格式化输出<br>
|
||||
* Thanks to Apache Commons Lang 3.5
|
||||
@@ -48,7 +48,7 @@ class FastDatePrinter extends AbstractDateBasic implements DatePrinter {
|
||||
*/
|
||||
private void init() {
|
||||
final List<Rule> rulesList = parsePattern();
|
||||
rules = rulesList.toArray(new Rule[rulesList.size()]);
|
||||
rules = rulesList.toArray(new Rule[0]);
|
||||
|
||||
int len = 0;
|
||||
for (int i = rules.length; --i >= 0;) {
|
||||
|
||||
@@ -0,0 +1,354 @@
|
||||
package cn.hutool.core.img;
|
||||
|
||||
import cn.hutool.core.io.FileTypeUtil;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.swing.ImageIcon;
|
||||
import java.awt.Color;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* <p>图片背景识别处理、背景替换、背景设置为矢量图</p>
|
||||
* <p>根据一定规则算出图片背景色的RGB值,进行替换</p>
|
||||
* <p>2020-05-21 16:36</p>
|
||||
*
|
||||
* @author Dai Yuanchuan
|
||||
**/
|
||||
public class BackgroundRemoval {
|
||||
|
||||
/**
|
||||
* 目前暂时支持的图片类型数组
|
||||
* 其他格式的不保证结果
|
||||
*/
|
||||
public static String[] IMAGES_TYPE = {"jpg", "png"};
|
||||
|
||||
/**
|
||||
* 背景移除
|
||||
* 图片去底工具
|
||||
* 将 "纯色背景的图片" 还原成 "透明背景的图片"
|
||||
* 将纯色背景的图片转成矢量图
|
||||
* 取图片边缘的像素点和获取到的图片主题色作为要替换的背景色
|
||||
* 再加入一定的容差值,然后将所有像素点与该颜色进行比较
|
||||
* 发现相同则将颜色不透明度设置为0,使颜色完全透明.
|
||||
*
|
||||
* @param inputPath 要处理图片的路径
|
||||
* @param outputPath 输出图片的路径
|
||||
* @param tolerance 容差值[根据图片的主题色,加入容差值,值的范围在0~255之间]
|
||||
* @return 返回处理结果 true:图片处理完成 false:图片处理失败
|
||||
*/
|
||||
public static boolean backgroundRemoval(String inputPath, String outputPath, int tolerance) {
|
||||
return backgroundRemoval(new File(inputPath), new File(outputPath), tolerance);
|
||||
}
|
||||
|
||||
/**
|
||||
* 背景移除
|
||||
* 图片去底工具
|
||||
* 将 "纯色背景的图片" 还原成 "透明背景的图片"
|
||||
* 将纯色背景的图片转成矢量图
|
||||
* 取图片边缘的像素点和获取到的图片主题色作为要替换的背景色
|
||||
* 再加入一定的容差值,然后将所有像素点与该颜色进行比较
|
||||
* 发现相同则将颜色不透明度设置为0,使颜色完全透明.
|
||||
*
|
||||
* @param input 需要进行操作的图片
|
||||
* @param output 最后输出的文件
|
||||
* @param tolerance 容差值[根据图片的主题色,加入容差值,值的取值范围在0~255之间]
|
||||
* @return 返回处理结果 true:图片处理完成 false:图片处理失败
|
||||
*/
|
||||
public static boolean backgroundRemoval(File input, File output, int tolerance) {
|
||||
return backgroundRemoval(input, output, null, tolerance);
|
||||
}
|
||||
|
||||
/**
|
||||
* 背景移除
|
||||
* 图片去底工具
|
||||
* 将 "纯色背景的图片" 还原成 "透明背景的图片"
|
||||
* 将纯色背景的图片转成矢量图
|
||||
* 取图片边缘的像素点和获取到的图片主题色作为要替换的背景色
|
||||
* 再加入一定的容差值,然后将所有像素点与该颜色进行比较
|
||||
* 发现相同则将颜色不透明度设置为0,使颜色完全透明.
|
||||
*
|
||||
* @param input 需要进行操作的图片
|
||||
* @param output 最后输出的文件
|
||||
* @param override 指定替换成的背景颜色 为null时背景为透明
|
||||
* @param tolerance 容差值[根据图片的主题色,加入容差值,值的取值范围在0~255之间]
|
||||
* @return 返回处理结果 true:图片处理完成 false:图片处理失败
|
||||
*/
|
||||
public static boolean backgroundRemoval(File input, File output, Color override, int tolerance) {
|
||||
if (fileTypeValidation(input, IMAGES_TYPE)) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
// 获取图片左上、中上、右上、右中、右下、下中、左下、左中、8个像素点rgb的16进制值
|
||||
BufferedImage bufferedImage = ImageIO.read(input);
|
||||
// 图片输出的格式为 png
|
||||
return ImageIO.write(backgroundRemoval(bufferedImage, override, tolerance), "png", output);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 背景移除
|
||||
* 图片去底工具
|
||||
* 将 "纯色背景的图片" 还原成 "透明背景的图片"
|
||||
* 将纯色背景的图片转成矢量图
|
||||
* 取图片边缘的像素点和获取到的图片主题色作为要替换的背景色
|
||||
* 再加入一定的容差值,然后将所有像素点与该颜色进行比较
|
||||
* 发现相同则将颜色不透明度设置为0,使颜色完全透明.
|
||||
*
|
||||
* @param bufferedImage 需要进行处理的图片流
|
||||
* @param override 指定替换成的背景颜色 为null时背景为透明
|
||||
* @param tolerance 容差值[根据图片的主题色,加入容差值,值的取值范围在0~255之间]
|
||||
* @return 返回处理好的图片流
|
||||
*/
|
||||
public static BufferedImage backgroundRemoval(BufferedImage bufferedImage, Color override, int tolerance) {
|
||||
// 容差值 最大255 最小0
|
||||
tolerance = Math.min(255, Math.max(tolerance, 0));
|
||||
// 绘制icon
|
||||
ImageIcon imageIcon = new ImageIcon(bufferedImage);
|
||||
BufferedImage image = new BufferedImage(imageIcon.getIconWidth(), imageIcon.getIconHeight(),
|
||||
BufferedImage.TYPE_4BYTE_ABGR);
|
||||
// 绘图工具
|
||||
Graphics graphics = image.getGraphics();
|
||||
graphics.drawImage(imageIcon.getImage(), 0, 0, imageIcon.getImageObserver());
|
||||
// 需要删除的RGB元素
|
||||
String[] removeRgb = getRemoveRgb(bufferedImage);
|
||||
// 获取图片的大概主色调
|
||||
String mainColor = getMainColor(bufferedImage);
|
||||
int alpha = 0;
|
||||
for (int y = image.getMinY(); y < image.getHeight(); y++) {
|
||||
for (int x = image.getMinX(); x < image.getWidth(); x++) {
|
||||
// 获取像素的16进制
|
||||
int rgb = image.getRGB(x, y);
|
||||
String hex = ImgUtil.toHex((rgb & 0xff0000) >> 16, (rgb & 0xff00) >> 8, (rgb & 0xff));
|
||||
boolean isTrue = ArrayUtil.contains(removeRgb, hex) ||
|
||||
areColorsWithinTolerance(hexToRgb(mainColor), new Color(Integer.parseInt(hex.substring(1), 16)), tolerance);
|
||||
if (isTrue) {
|
||||
rgb = override == null ? ((alpha + 1) << 24) | (rgb & 0x00ffffff) : override.getRGB();
|
||||
}
|
||||
image.setRGB(x, y, rgb);
|
||||
}
|
||||
}
|
||||
graphics.drawImage(image, 0, 0, imageIcon.getImageObserver());
|
||||
return image;
|
||||
}
|
||||
|
||||
/**
|
||||
* 背景移除
|
||||
* 图片去底工具
|
||||
* 将 "纯色背景的图片" 还原成 "透明背景的图片"
|
||||
* 将纯色背景的图片转成矢量图
|
||||
* 取图片边缘的像素点和获取到的图片主题色作为要替换的背景色
|
||||
* 再加入一定的容差值,然后将所有像素点与该颜色进行比较
|
||||
* 发现相同则将颜色不透明度设置为0,使颜色完全透明.
|
||||
*
|
||||
* @param outputStream 需要进行处理的图片字节数组流
|
||||
* @param override 指定替换成的背景颜色 为null时背景为透明
|
||||
* @param tolerance 容差值[根据图片的主题色,加入容差值,值的取值范围在0~255之间]
|
||||
* @return 返回处理好的图片流
|
||||
*/
|
||||
public static BufferedImage backgroundRemoval(ByteArrayOutputStream outputStream, Color override, int tolerance) {
|
||||
try {
|
||||
return backgroundRemoval(ImageIO.read(new ByteArrayInputStream(outputStream.toByteArray())), override, tolerance);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取要删除的 RGB 元素
|
||||
* 分别获取图片左上、中上、右上、右中、右下、下中、左下、左中、8个像素点rgb的16进制值
|
||||
*
|
||||
* @param image 图片流
|
||||
* @return String数组 包含 各个位置的rgb数值
|
||||
*/
|
||||
private static String[] getRemoveRgb(BufferedImage image) {
|
||||
// 获取图片流的宽和高
|
||||
int width = image.getWidth() - 1;
|
||||
int height = image.getHeight() - 1;
|
||||
// 左上
|
||||
int leftUpPixel = image.getRGB(1, 1);
|
||||
String leftUp = ImgUtil.toHex((leftUpPixel & 0xff0000) >> 16, (leftUpPixel & 0xff00) >> 8, (leftUpPixel & 0xff));
|
||||
// 上中
|
||||
int upMiddlePixel = image.getRGB(width / 2, 1);
|
||||
String upMiddle = ImgUtil.toHex((upMiddlePixel & 0xff0000) >> 16, (upMiddlePixel & 0xff00) >> 8, (upMiddlePixel & 0xff));
|
||||
// 右上
|
||||
int rightUpPixel = image.getRGB(width, 1);
|
||||
String rightUp = ImgUtil.toHex((rightUpPixel & 0xff0000) >> 16, (rightUpPixel & 0xff00) >> 8, (rightUpPixel & 0xff));
|
||||
// 右中
|
||||
int rightMiddlePixel = image.getRGB(width, height / 2);
|
||||
String rightMiddle = ImgUtil.toHex((rightMiddlePixel & 0xff0000) >> 16, (rightMiddlePixel & 0xff00) >> 8, (rightMiddlePixel & 0xff));
|
||||
// 右下
|
||||
int lowerRightPixel = image.getRGB(width, height);
|
||||
String lowerRight = ImgUtil.toHex((lowerRightPixel & 0xff0000) >> 16, (lowerRightPixel & 0xff00) >> 8, (lowerRightPixel & 0xff));
|
||||
// 下中
|
||||
int lowerMiddlePixel = image.getRGB(width / 2, height);
|
||||
String lowerMiddle = ImgUtil.toHex((lowerMiddlePixel & 0xff0000) >> 16, (lowerMiddlePixel & 0xff00) >> 8, (lowerMiddlePixel & 0xff));
|
||||
// 左下
|
||||
int leftLowerPixel = image.getRGB(1, height);
|
||||
String leftLower = ImgUtil.toHex((leftLowerPixel & 0xff0000) >> 16, (leftLowerPixel & 0xff00) >> 8, (leftLowerPixel & 0xff));
|
||||
// 左中
|
||||
int leftMiddlePixel = image.getRGB(1, height / 2);
|
||||
String leftMiddle = ImgUtil.toHex((leftMiddlePixel & 0xff0000) >> 16, (leftMiddlePixel & 0xff00) >> 8, (leftMiddlePixel & 0xff));
|
||||
// 需要删除的RGB元素
|
||||
return new String[]{leftUp, upMiddle, rightUp, rightMiddle, lowerRight, lowerMiddle, leftLower, leftMiddle};
|
||||
}
|
||||
|
||||
/**
|
||||
* 十六进制颜色码转RGB颜色值
|
||||
*
|
||||
* @param hex 十六进制颜色码
|
||||
* @return 返回 RGB颜色值
|
||||
*/
|
||||
public static Color hexToRgb(String hex) {
|
||||
return new Color(Integer.parseInt(hex.substring(1), 16));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 判断颜色是否在容差范围内
|
||||
* 对比两个颜色的相似度,判断这个相似度是否小于 tolerance 容差值
|
||||
*
|
||||
* @param color1 颜色1
|
||||
* @param color2 颜色2
|
||||
* @param tolerance 容差值
|
||||
* @return 返回true:两个颜色在容差值之内 false: 不在
|
||||
*/
|
||||
public static boolean areColorsWithinTolerance(Color color1, Color color2, int tolerance) {
|
||||
return areColorsWithinTolerance(color1, color2, new Color(tolerance, tolerance, tolerance));
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断颜色是否在容差范围内
|
||||
* 对比两个颜色的相似度,判断这个相似度是否小于 tolerance 容差值
|
||||
*
|
||||
* @param color1 颜色1
|
||||
* @param color2 颜色2
|
||||
* @param tolerance 容差色值
|
||||
* @return 返回true:两个颜色在容差值之内 false: 不在
|
||||
*/
|
||||
public static boolean areColorsWithinTolerance(Color color1, Color color2, Color tolerance) {
|
||||
return (color1.getRed() - color2.getRed() < tolerance.getRed() && color1
|
||||
.getRed() - color2.getRed() > -tolerance.getRed())
|
||||
&& (color1.getBlue() - color2.getBlue() < tolerance
|
||||
.getBlue() && color1.getBlue() - color2.getBlue() > -tolerance
|
||||
.getBlue())
|
||||
&& (color1.getGreen() - color2.getGreen() < tolerance
|
||||
.getGreen() && color1.getGreen()
|
||||
- color2.getGreen() > -tolerance.getGreen());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取图片大概的主题色
|
||||
* 循环所有的像素点,取出出现次数最多的一个像素点的RGB值
|
||||
*
|
||||
* @param input 图片文件路径
|
||||
* @return 返回一个图片的大概的色值 一个16进制的颜色码
|
||||
*/
|
||||
public static String getMainColor(String input) {
|
||||
return getMainColor(new File(input));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取图片大概的主题色
|
||||
* 循环所有的像素点,取出出现次数最多的一个像素点的RGB值
|
||||
*
|
||||
* @param input 图片文件
|
||||
* @return 返回一个图片的大概的色值 一个16进制的颜色码
|
||||
*/
|
||||
public static String getMainColor(File input) {
|
||||
try {
|
||||
return getMainColor(ImageIO.read(input));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取图片大概的主题色
|
||||
* 循环所有的像素点,取出出现次数最多的一个像素点的RGB值
|
||||
*
|
||||
* @param bufferedImage 图片流
|
||||
* @return 返回一个图片的大概的色值 一个16进制的颜色码
|
||||
*/
|
||||
public static String getMainColor(BufferedImage bufferedImage) {
|
||||
if (bufferedImage == null) {
|
||||
throw new IllegalArgumentException("图片流是空的");
|
||||
}
|
||||
|
||||
// 存储图片的所有RGB元素
|
||||
List<String> list = new ArrayList<>();
|
||||
for (int y = bufferedImage.getMinY(); y < bufferedImage.getHeight(); y++) {
|
||||
for (int x = bufferedImage.getMinX(); x < bufferedImage.getWidth(); x++) {
|
||||
int pixel = bufferedImage.getRGB(x, y);
|
||||
list.add(((pixel & 0xff0000) >> 16) + "-" + ((pixel & 0xff00) >> 8) + "-" + (pixel & 0xff));
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, Integer> map = new HashMap<>(list.size());
|
||||
for (String string : list) {
|
||||
Integer integer = map.get(string);
|
||||
if (integer == null) {
|
||||
integer = 1;
|
||||
} else {
|
||||
integer++;
|
||||
}
|
||||
map.put(string, integer);
|
||||
}
|
||||
String max = "";
|
||||
long num = 0;
|
||||
for (Map.Entry<String, Integer> entry : map.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
Integer temp = entry.getValue();
|
||||
if (StrUtil.isBlank(max) || temp > num) {
|
||||
max = key;
|
||||
num = temp;
|
||||
}
|
||||
}
|
||||
String[] strings = max.split("-");
|
||||
// rgb 的数量只有3个
|
||||
int rgbLength = 3;
|
||||
if (strings.length == rgbLength) {
|
||||
return ImgUtil.toHex(Integer.parseInt(strings[0]), Integer.parseInt(strings[1]),
|
||||
Integer.parseInt(strings[2]));
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------- private
|
||||
|
||||
/**
|
||||
* 文件类型验证
|
||||
* 根据给定文件类型数据,验证给定文件类型.
|
||||
*
|
||||
* @param input 需要进行验证的文件
|
||||
* @param imagesType 文件包含的类型数组
|
||||
* @return 返回布尔值 false:给定文件的文件类型在文件数组中 true:给定文件的文件类型 不在给定数组中。
|
||||
*/
|
||||
private static boolean fileTypeValidation(File input, String[] imagesType) {
|
||||
if (!input.exists()) {
|
||||
throw new IllegalArgumentException("给定文件为空");
|
||||
}
|
||||
// 获取图片类型
|
||||
String type = FileTypeUtil.getType(input);
|
||||
// 类型对比
|
||||
if (!ArrayUtil.contains(imagesType, type)) {
|
||||
throw new IllegalArgumentException(StrUtil.format("文件类型{}不支持", type));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
110
hutool-core/src/main/java/cn/hutool/core/img/FontUtil.java
Normal file
110
hutool-core/src/main/java/cn/hutool/core/img/FontUtil.java
Normal file
@@ -0,0 +1,110 @@
|
||||
package cn.hutool.core.img;
|
||||
|
||||
import cn.hutool.core.exceptions.UtilException;
|
||||
import cn.hutool.core.io.IORuntimeException;
|
||||
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Font;
|
||||
import java.awt.FontFormatException;
|
||||
import java.awt.FontMetrics;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* AWT中字体相关工具类
|
||||
*
|
||||
* @author looly
|
||||
* @since 5.3.6
|
||||
*/
|
||||
public class FontUtil {
|
||||
|
||||
/**
|
||||
* 创建默认字体
|
||||
*
|
||||
* @return 默认字体
|
||||
*/
|
||||
public static Font createFont() {
|
||||
return new Font(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建SansSerif字体
|
||||
*
|
||||
* @param size 字体大小
|
||||
* @return 字体
|
||||
*/
|
||||
public static Font createSansSerifFont(int size) {
|
||||
return createFont(Font.SANS_SERIF, size);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建指定名称的字体
|
||||
*
|
||||
* @param name 字体名称
|
||||
* @param size 字体大小
|
||||
* @return 字体
|
||||
*/
|
||||
public static Font createFont(String name, int size) {
|
||||
return new Font(name, Font.PLAIN, size);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据文件创建字体<br>
|
||||
* 首先尝试创建{@link Font#TRUETYPE_FONT}字体,此类字体无效则创建{@link Font#TYPE1_FONT}
|
||||
*
|
||||
* @param fontFile 字体文件
|
||||
* @return {@link Font}
|
||||
*/
|
||||
public static Font createFont(File fontFile) {
|
||||
try {
|
||||
return Font.createFont(Font.TRUETYPE_FONT, fontFile);
|
||||
} catch (FontFormatException e) {
|
||||
// True Type字体无效时使用Type1字体
|
||||
try {
|
||||
return Font.createFont(Font.TYPE1_FONT, fontFile);
|
||||
} catch (Exception e1) {
|
||||
throw new UtilException(e);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据文件创建字体<br>
|
||||
* 首先尝试创建{@link Font#TRUETYPE_FONT}字体,此类字体无效则创建{@link Font#TYPE1_FONT}
|
||||
*
|
||||
* @param fontStream 字体流
|
||||
* @return {@link Font}
|
||||
*/
|
||||
public static Font createFont(InputStream fontStream) {
|
||||
try {
|
||||
return Font.createFont(Font.TRUETYPE_FONT, fontStream);
|
||||
} catch (FontFormatException e) {
|
||||
// True Type字体无效时使用Type1字体
|
||||
try {
|
||||
return Font.createFont(Font.TYPE1_FONT, fontStream);
|
||||
} catch (Exception e1) {
|
||||
throw new UtilException(e1);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得字体对应字符串的长宽信息
|
||||
*
|
||||
* @param metrics {@link FontMetrics}
|
||||
* @param str 字符串
|
||||
* @return 长宽信息
|
||||
*/
|
||||
public static Dimension getDimension(FontMetrics metrics, String str) {
|
||||
final int width = metrics.stringWidth(str);
|
||||
final int height = metrics.getAscent() - metrics.getLeading() - metrics.getDescent();
|
||||
|
||||
return new Dimension(width, height);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,10 +1,17 @@
|
||||
package cn.hutool.core.img;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
|
||||
import java.awt.AlphaComposite;
|
||||
import java.awt.Color;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Font;
|
||||
import java.awt.FontMetrics;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Image;
|
||||
import java.awt.Point;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
@@ -98,7 +105,7 @@ public class GraphicsUtil {
|
||||
g.setFont(font);
|
||||
|
||||
// 文字高度(必须在设置字体后调用)
|
||||
int midY = GraphicsUtil.getCenterY(g, height);
|
||||
int midY = getCenterY(g, height);
|
||||
if (null != color) {
|
||||
g.setColor(color);
|
||||
}
|
||||
@@ -115,4 +122,97 @@ public class GraphicsUtil {
|
||||
return g;
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制字符串,默认抗锯齿。<br>
|
||||
* 此方法定义一个矩形区域和坐标,文字基于这个区域中间偏移x,y绘制。
|
||||
*
|
||||
* @param g {@link Graphics}画笔
|
||||
* @param str 字符串
|
||||
* @param font 字体,字体大小决定了在背景中绘制的大小
|
||||
* @param color 字体颜色,{@code null} 表示使用黑色
|
||||
* @param rectangle 字符串绘制坐标和大小,此对象定义了绘制字符串的区域大小和偏移位置
|
||||
* @return 画笔对象
|
||||
* @since 4.5.10
|
||||
*/
|
||||
public static Graphics drawString(Graphics g, String str, Font font, Color color, Rectangle rectangle) {
|
||||
// 背景长宽
|
||||
final int backgroundWidth = rectangle.width;
|
||||
final int backgroundHeight = rectangle.height;
|
||||
|
||||
//获取字符串本身的长宽
|
||||
Dimension dimension;
|
||||
try {
|
||||
dimension = FontUtil.getDimension(g.getFontMetrics(font), str);
|
||||
} catch (Exception e) {
|
||||
// 此处报告bug某些情况下会抛出IndexOutOfBoundsException,在此做容错处理
|
||||
dimension = new Dimension(backgroundWidth / 3, backgroundHeight / 3);
|
||||
}
|
||||
|
||||
rectangle.setSize(dimension.width, dimension.height);
|
||||
final Point point = ImgUtil.getPointBaseCentre(rectangle, backgroundWidth, backgroundHeight);
|
||||
|
||||
return drawString(g, str, font, color, point);
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制字符串,默认抗锯齿
|
||||
*
|
||||
* @param g {@link Graphics}画笔
|
||||
* @param str 字符串
|
||||
* @param font 字体,字体大小决定了在背景中绘制的大小
|
||||
* @param color 字体颜色,{@code null} 表示使用黑色
|
||||
* @param point 绘制字符串的位置坐标
|
||||
* @return 画笔对象
|
||||
* @since 5.3.6
|
||||
*/
|
||||
public static Graphics drawString(Graphics g, String str, Font font, Color color, Point point) {
|
||||
// 抗锯齿
|
||||
if (g instanceof Graphics2D) {
|
||||
((Graphics2D) g).setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
}
|
||||
|
||||
g.setFont(font);
|
||||
g.setColor(ObjectUtil.defaultIfNull(color, Color.BLACK));
|
||||
g.drawString(str, point.x, point.y);
|
||||
|
||||
return g;
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制图片
|
||||
*
|
||||
* @param g 画笔
|
||||
* @param img 要绘制的图片
|
||||
* @param point 绘制的位置,基于左上角
|
||||
* @return 画笔对象
|
||||
*/
|
||||
public static Graphics drawImg(Graphics g, Image img, Point point) {
|
||||
return drawImg(g, img,
|
||||
new Rectangle(point.x, point.y, img.getWidth(null), img.getHeight(null)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制图片
|
||||
*
|
||||
* @param g 画笔
|
||||
* @param img 要绘制的图片
|
||||
* @param rectangle 矩形对象,表示矩形区域的x,y,width,height,,基于左上角
|
||||
* @return 画笔对象
|
||||
*/
|
||||
public static Graphics drawImg(Graphics g, Image img, Rectangle rectangle) {
|
||||
g.drawImage(img, rectangle.x, rectangle.y, rectangle.width, rectangle.height, null); // 绘制切割后的图
|
||||
return g;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置画笔透明度
|
||||
*
|
||||
* @param g 画笔
|
||||
* @param alpha 透明度:alpha 必须是范围 [0.0, 1.0] 之内(包含边界值)的一个浮点数字
|
||||
* @return 画笔
|
||||
*/
|
||||
public static Graphics2D setAlpha(Graphics2D g, float alpha){
|
||||
g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, alpha));
|
||||
return g;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,9 +15,9 @@ import javax.imageio.stream.ImageOutputStream;
|
||||
import java.awt.AlphaComposite;
|
||||
import java.awt.Color;
|
||||
import java.awt.Font;
|
||||
import java.awt.FontMetrics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Image;
|
||||
import java.awt.Point;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.Toolkit;
|
||||
@@ -47,7 +47,7 @@ import java.nio.file.Path;
|
||||
public class Img implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private BufferedImage srcImage;
|
||||
private final BufferedImage srcImage;
|
||||
private Image targetImage;
|
||||
/**
|
||||
* 目标图片文件格式,用于写出
|
||||
@@ -450,20 +450,22 @@ public class Img implements Serializable {
|
||||
|
||||
if (null == font) {
|
||||
// 默认字体
|
||||
font = new Font("Courier", Font.PLAIN, (int) (targetImage.getHeight() * 0.75));
|
||||
font = FontUtil.createSansSerifFont((int) (targetImage.getHeight() * 0.75));
|
||||
}
|
||||
|
||||
// 抗锯齿
|
||||
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
g.setColor(color);
|
||||
g.setFont(font);
|
||||
// 透明度
|
||||
g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, alpha));
|
||||
// 在指定坐标绘制水印文字
|
||||
final FontMetrics metrics = g.getFontMetrics(font);
|
||||
final int textLength = metrics.stringWidth(pressText);
|
||||
final int textHeight = metrics.getAscent() - metrics.getLeading() - metrics.getDescent();
|
||||
g.drawString(pressText, Math.abs(targetImage.getWidth() - textLength) / 2 + x, Math.abs(targetImage.getHeight() + textHeight) / 2 + y);
|
||||
|
||||
// 绘制
|
||||
if (positionBaseCentre) {
|
||||
// 基于中心绘制
|
||||
GraphicsUtil.drawString(g, pressText, font, color,
|
||||
new Rectangle(x, y, targetImage.getWidth(), targetImage.getHeight()));
|
||||
} else {
|
||||
// 基于左上角绘制
|
||||
GraphicsUtil.drawString(g, pressText, font, color,
|
||||
new Point(x, y));
|
||||
}
|
||||
|
||||
g.dispose();
|
||||
this.targetImage = targetImage;
|
||||
|
||||
@@ -482,7 +484,6 @@ public class Img implements Serializable {
|
||||
public Img pressImage(Image pressImg, int x, int y, float alpha) {
|
||||
final int pressImgWidth = pressImg.getWidth(null);
|
||||
final int pressImgHeight = pressImg.getHeight(null);
|
||||
|
||||
return pressImage(pressImg, new Rectangle(x, y, pressImgWidth, pressImgHeight), alpha);
|
||||
}
|
||||
|
||||
@@ -498,7 +499,6 @@ public class Img implements Serializable {
|
||||
public Img pressImage(Image pressImg, Rectangle rectangle, float alpha) {
|
||||
final Image targetImg = getValidSrcImg();
|
||||
|
||||
fixRectangle(rectangle, targetImg.getWidth(null), targetImg.getHeight(null));
|
||||
this.targetImage = draw(ImgUtil.toBufferedImage(targetImg), pressImg, rectangle, alpha);
|
||||
return this;
|
||||
}
|
||||
@@ -554,7 +554,7 @@ public class Img implements Serializable {
|
||||
* @return 处理过的图片
|
||||
*/
|
||||
public Image getImg() {
|
||||
return this.targetImage;
|
||||
return null == this.targetImage ? this.srcImage : this.targetImage;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -620,12 +620,21 @@ public class Img implements Serializable {
|
||||
* @param backgroundImg 背景图片
|
||||
* @param img 要绘制的图片
|
||||
* @param rectangle 矩形对象,表示矩形区域的x,y,width,height,x,y从背景图片中心计算
|
||||
* @param alpha 透明度:alpha 必须是范围 [0.0, 1.0] 之内(包含边界值)的一个浮点数字
|
||||
* @return 绘制后的背景
|
||||
*/
|
||||
private static BufferedImage draw(BufferedImage backgroundImg, Image img, Rectangle rectangle, float alpha) {
|
||||
private BufferedImage draw(BufferedImage backgroundImg, Image img, Rectangle rectangle, float alpha) {
|
||||
final Graphics2D g = backgroundImg.createGraphics();
|
||||
g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, alpha));
|
||||
g.drawImage(img, rectangle.x, rectangle.y, rectangle.width, rectangle.height, null); // 绘制切割后的图
|
||||
GraphicsUtil.setAlpha(g, alpha);
|
||||
|
||||
Point point;
|
||||
if (positionBaseCentre) {
|
||||
point = ImgUtil.getPointBaseCentre(rectangle, backgroundImg.getWidth(), backgroundImg.getHeight());
|
||||
} else {
|
||||
point = new Point(rectangle.x, rectangle.y);
|
||||
}
|
||||
GraphicsUtil.drawImg(g, img, point);
|
||||
|
||||
g.dispose();
|
||||
return backgroundImg;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package cn.hutool.core.img;
|
||||
|
||||
import cn.hutool.core.codec.Base64;
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.exceptions.UtilException;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.io.IORuntimeException;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
@@ -10,8 +9,10 @@ import cn.hutool.core.io.resource.Resource;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.NumberUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.core.util.URLUtil;
|
||||
|
||||
import javax.imageio.IIOImage;
|
||||
import javax.imageio.ImageIO;
|
||||
@@ -23,10 +24,10 @@ import javax.imageio.stream.ImageInputStream;
|
||||
import javax.imageio.stream.ImageOutputStream;
|
||||
import java.awt.Color;
|
||||
import java.awt.Font;
|
||||
import java.awt.FontFormatException;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Image;
|
||||
import java.awt.Point;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.font.FontRenderContext;
|
||||
import java.awt.geom.AffineTransform;
|
||||
@@ -1260,6 +1261,20 @@ public class ImgUtil {
|
||||
return IoUtil.toStream(toBytes(image, imageType));
|
||||
}
|
||||
|
||||
/**
|
||||
* 将图片对象转换为Base64的Data URI形式,格式为:data:image/[imageType];base64,[data]
|
||||
*
|
||||
* @param image 图片对象
|
||||
* @param imageType 图片类型
|
||||
* @return Base64的字符串表现形式
|
||||
* @since 5.3.6
|
||||
*/
|
||||
public static String toBase64DateUri(Image image, String imageType) {
|
||||
return URLUtil.getDataUri(
|
||||
"image/" + imageType, "base64",
|
||||
toBase64(image, imageType));
|
||||
}
|
||||
|
||||
/**
|
||||
* 将图片对象转换为Base64形式
|
||||
*
|
||||
@@ -1291,28 +1306,65 @@ public class ImgUtil {
|
||||
*
|
||||
* @param str 文字
|
||||
* @param font 字体{@link Font}
|
||||
* @param backgroundColor 背景颜色
|
||||
* @param fontColor 字体颜色
|
||||
* @param backgroundColor 背景颜色,默认透明
|
||||
* @param fontColor 字体颜色,默认黑色
|
||||
* @param out 图片输出地
|
||||
* @throws IORuntimeException IO异常
|
||||
*/
|
||||
public static void createImage(String str, Font font, Color backgroundColor, Color fontColor, ImageOutputStream out) throws IORuntimeException {
|
||||
writePng(createImage(str, font, backgroundColor, fontColor, BufferedImage.TYPE_INT_ARGB), out);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据文字创建图片
|
||||
*
|
||||
* @param str 文字
|
||||
* @param font 字体{@link Font}
|
||||
* @param backgroundColor 背景颜色,默认透明
|
||||
* @param fontColor 字体颜色,默认黑色
|
||||
* @param imageType 图片类型,见:{@link BufferedImage}
|
||||
* @return 图片
|
||||
* @throws IORuntimeException IO异常
|
||||
*/
|
||||
public static BufferedImage createImage(String str, Font font, Color backgroundColor, Color fontColor, int imageType) throws IORuntimeException {
|
||||
// 获取font的样式应用在str上的整个矩形
|
||||
Rectangle2D r = font.getStringBounds(str, new FontRenderContext(AffineTransform.getScaleInstance(1, 1), false, false));
|
||||
int unitHeight = (int) Math.floor(r.getHeight());// 获取单个字符的高度
|
||||
final Rectangle2D r = getRectangle(str, font);
|
||||
// 获取单个字符的高度
|
||||
int unitHeight = (int) Math.floor(r.getHeight());
|
||||
// 获取整个str用了font样式的宽度这里用四舍五入后+1保证宽度绝对能容纳这个字符串作为图片的宽度
|
||||
int width = (int) Math.round(r.getWidth()) + 1;
|
||||
int height = unitHeight + 3;// 把单个字符的高度+3保证高度绝对能容纳字符串作为图片的高度
|
||||
// 把单个字符的高度+3保证高度绝对能容纳字符串作为图片的高度
|
||||
int height = unitHeight + 3;
|
||||
|
||||
// 创建图片
|
||||
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_BGR);
|
||||
Graphics g = image.getGraphics();
|
||||
final BufferedImage image = new BufferedImage(width, height, imageType);
|
||||
final Graphics g = image.getGraphics();
|
||||
if (null != backgroundColor) {
|
||||
// 先用背景色填充整张图片,也就是背景
|
||||
g.setColor(backgroundColor);
|
||||
g.fillRect(0, 0, width, height);// 先用背景色填充整张图片,也就是背景
|
||||
g.setColor(fontColor);
|
||||
g.fillRect(0, 0, width, height);
|
||||
}
|
||||
g.setColor(ObjectUtil.defaultIfNull(fontColor, Color.BLACK));
|
||||
g.setFont(font);// 设置画笔字体
|
||||
g.drawString(str, 0, font.getSize());// 画出字符串
|
||||
g.dispose();
|
||||
writePng(image, out);
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取font的样式应用在str上的整个矩形
|
||||
*
|
||||
* @param str 字符串,必须非空
|
||||
* @param font 字体,必须非空
|
||||
* @return {@link Rectangle2D}
|
||||
* @since 5.3.3
|
||||
*/
|
||||
public static Rectangle2D getRectangle(String str, Font font) {
|
||||
return font.getStringBounds(str,
|
||||
new FontRenderContext(AffineTransform.getScaleInstance(1, 1),
|
||||
false,
|
||||
false));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1324,18 +1376,7 @@ public class ImgUtil {
|
||||
* @since 3.0.9
|
||||
*/
|
||||
public static Font createFont(File fontFile) {
|
||||
try {
|
||||
return Font.createFont(Font.TRUETYPE_FONT, fontFile);
|
||||
} catch (FontFormatException e) {
|
||||
// True Type字体无效时使用Type1字体
|
||||
try {
|
||||
return Font.createFont(Font.TYPE1_FONT, fontFile);
|
||||
} catch (Exception e1) {
|
||||
throw new UtilException(e);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
return FontUtil.createFont(fontFile);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1347,18 +1388,7 @@ public class ImgUtil {
|
||||
* @since 3.0.9
|
||||
*/
|
||||
public static Font createFont(InputStream fontStream) {
|
||||
try {
|
||||
return Font.createFont(Font.TRUETYPE_FONT, fontStream);
|
||||
} catch (FontFormatException e) {
|
||||
// True Type字体无效时使用Type1字体
|
||||
try {
|
||||
return Font.createFont(Font.TYPE1_FONT, fontStream);
|
||||
} catch (Exception e1) {
|
||||
throw new UtilException(e1);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
return FontUtil.createFont(fontStream);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1735,13 +1765,23 @@ public class ImgUtil {
|
||||
* @since 4.1.14
|
||||
*/
|
||||
public static String toHex(Color color) {
|
||||
String R = Integer.toHexString(color.getRed());
|
||||
R = R.length() < 2 ? ('0' + R) : R;
|
||||
String G = Integer.toHexString(color.getGreen());
|
||||
G = G.length() < 2 ? ('0' + G) : G;
|
||||
String B = Integer.toHexString(color.getBlue());
|
||||
B = B.length() < 2 ? ('0' + B) : B;
|
||||
return '#' + R + G + B;
|
||||
return toHex(color.getRed(), color.getGreen(), color.getBlue());
|
||||
}
|
||||
|
||||
/**
|
||||
* RGB颜色值转换成十六进制颜色码
|
||||
*
|
||||
* @param r 红(R)
|
||||
* @param g 绿(G)
|
||||
* @param b 蓝(B)
|
||||
* @return 返回字符串形式的 十六进制颜色码 如
|
||||
*/
|
||||
public static String toHex(int r, int g, int b) {
|
||||
// rgb 小于 255
|
||||
if(r < 0 || r > 255 || g < 0 || g > 255 || b < 0 || b > 255){
|
||||
throw new IllegalArgumentException("RGB must be 0~255!");
|
||||
}
|
||||
return String.format("#%02X%02X%02X", r, g, b);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1864,4 +1904,113 @@ public class ImgUtil {
|
||||
}
|
||||
return new Color(random.nextInt(255), random.nextInt(255), random.nextInt(255));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得修正后的矩形坐标位置,变为以背景中心为基准坐标(即x,y == 0,0时,处于背景正中)
|
||||
*
|
||||
* @param rectangle 矩形
|
||||
* @param backgroundWidth 参考宽(背景宽)
|
||||
* @param backgroundHeight 参考高(背景高)
|
||||
* @return 修正后的{@link Point}
|
||||
* @since 5.3.6
|
||||
*/
|
||||
public static Point getPointBaseCentre(Rectangle rectangle, int backgroundWidth, int backgroundHeight) {
|
||||
return new Point(
|
||||
rectangle.x + (Math.abs(backgroundWidth - rectangle.width) / 2), //
|
||||
rectangle.y + (Math.abs(backgroundHeight - rectangle.height) / 2)//
|
||||
);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------------ 背景图换算
|
||||
|
||||
/**
|
||||
* 背景移除
|
||||
* 图片去底工具
|
||||
* 将 "纯色背景的图片" 还原成 "透明背景的图片"
|
||||
* 将纯色背景的图片转成矢量图
|
||||
* 取图片边缘的像素点和获取到的图片主题色作为要替换的背景色
|
||||
* 再加入一定的容差值,然后将所有像素点与该颜色进行比较
|
||||
* 发现相同则将颜色不透明度设置为0,使颜色完全透明.
|
||||
*
|
||||
* @param inputPath 要处理图片的路径
|
||||
* @param outputPath 输出图片的路径
|
||||
* @param tolerance 容差值[根据图片的主题色,加入容差值,值的范围在0~255之间]
|
||||
* @return 返回处理结果 true:图片处理完成 false:图片处理失败
|
||||
*/
|
||||
public static boolean backgroundRemoval(String inputPath, String outputPath, int tolerance) {
|
||||
return BackgroundRemoval.backgroundRemoval(inputPath, outputPath, tolerance);
|
||||
}
|
||||
|
||||
/**
|
||||
* 背景移除
|
||||
* 图片去底工具
|
||||
* 将 "纯色背景的图片" 还原成 "透明背景的图片"
|
||||
* 将纯色背景的图片转成矢量图
|
||||
* 取图片边缘的像素点和获取到的图片主题色作为要替换的背景色
|
||||
* 再加入一定的容差值,然后将所有像素点与该颜色进行比较
|
||||
* 发现相同则将颜色不透明度设置为0,使颜色完全透明.
|
||||
*
|
||||
* @param input 需要进行操作的图片
|
||||
* @param output 最后输出的文件
|
||||
* @param tolerance 容差值[根据图片的主题色,加入容差值,值的取值范围在0~255之间]
|
||||
* @return 返回处理结果 true:图片处理完成 false:图片处理失败
|
||||
*/
|
||||
public static boolean backgroundRemoval(File input, File output, int tolerance) {
|
||||
return BackgroundRemoval.backgroundRemoval(input, output, tolerance);
|
||||
}
|
||||
|
||||
/**
|
||||
* 背景移除
|
||||
* 图片去底工具
|
||||
* 将 "纯色背景的图片" 还原成 "透明背景的图片"
|
||||
* 将纯色背景的图片转成矢量图
|
||||
* 取图片边缘的像素点和获取到的图片主题色作为要替换的背景色
|
||||
* 再加入一定的容差值,然后将所有像素点与该颜色进行比较
|
||||
* 发现相同则将颜色不透明度设置为0,使颜色完全透明.
|
||||
*
|
||||
* @param input 需要进行操作的图片
|
||||
* @param output 最后输出的文件
|
||||
* @param override 指定替换成的背景颜色 为null时背景为透明
|
||||
* @param tolerance 容差值[根据图片的主题色,加入容差值,值的取值范围在0~255之间]
|
||||
* @return 返回处理结果 true:图片处理完成 false:图片处理失败
|
||||
*/
|
||||
public static boolean backgroundRemoval(File input, File output, Color override, int tolerance) {
|
||||
return BackgroundRemoval.backgroundRemoval(input, output, override, tolerance);
|
||||
}
|
||||
|
||||
/**
|
||||
* 背景移除
|
||||
* 图片去底工具
|
||||
* 将 "纯色背景的图片" 还原成 "透明背景的图片"
|
||||
* 将纯色背景的图片转成矢量图
|
||||
* 取图片边缘的像素点和获取到的图片主题色作为要替换的背景色
|
||||
* 再加入一定的容差值,然后将所有像素点与该颜色进行比较
|
||||
* 发现相同则将颜色不透明度设置为0,使颜色完全透明.
|
||||
*
|
||||
* @param bufferedImage 需要进行处理的图片流
|
||||
* @param override 指定替换成的背景颜色 为null时背景为透明
|
||||
* @param tolerance 容差值[根据图片的主题色,加入容差值,值的取值范围在0~255之间]
|
||||
* @return 返回处理好的图片流
|
||||
*/
|
||||
public static BufferedImage backgroundRemoval(BufferedImage bufferedImage, Color override, int tolerance) {
|
||||
return BackgroundRemoval.backgroundRemoval(bufferedImage, override, tolerance);
|
||||
}
|
||||
|
||||
/**
|
||||
* 背景移除
|
||||
* 图片去底工具
|
||||
* 将 "纯色背景的图片" 还原成 "透明背景的图片"
|
||||
* 将纯色背景的图片转成矢量图
|
||||
* 取图片边缘的像素点和获取到的图片主题色作为要替换的背景色
|
||||
* 再加入一定的容差值,然后将所有像素点与该颜色进行比较
|
||||
* 发现相同则将颜色不透明度设置为0,使颜色完全透明.
|
||||
*
|
||||
* @param outputStream 需要进行处理的图片字节数组流
|
||||
* @param override 指定替换成的背景颜色 为null时背景为透明
|
||||
* @param tolerance 容差值[根据图片的主题色,加入容差值,值的取值范围在0~255之间]
|
||||
* @return 返回处理好的图片流
|
||||
*/
|
||||
public static BufferedImage backgroundRemoval(ByteArrayOutputStream outputStream, Color override, int tolerance) {
|
||||
return BackgroundRemoval.backgroundRemoval(outputStream, override, tolerance);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ public enum ScaleType {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
private int value;
|
||||
private final int value;
|
||||
|
||||
public int getValue() {
|
||||
return this.value;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package cn.hutool.core.io;
|
||||
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
|
||||
/**
|
||||
* 基于快速缓冲FastByteBuffer的OutputStream,随着数据的增长自动扩充缓冲区
|
||||
* <p>
|
||||
@@ -34,7 +34,6 @@ public class FastByteArrayOutputStream extends OutputStream {
|
||||
buffer = new FastByteBuffer(size);
|
||||
}
|
||||
|
||||
@SuppressWarnings("NullableProblems")
|
||||
@Override
|
||||
public void write(byte[] b, int off, int len) {
|
||||
buffer.append(b, off, len);
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
package cn.hutool.core.io;
|
||||
|
||||
import cn.hutool.core.text.StrBuilder;
|
||||
|
||||
import java.io.Writer;
|
||||
|
||||
/**
|
||||
* 借助{@link StrBuilder} 提供快读的字符串写出,相比jdk的StringWriter非线程安全,速度更快。
|
||||
*
|
||||
* @author looly
|
||||
* @since 5.3.3
|
||||
*/
|
||||
public final class FastStringWriter extends Writer {
|
||||
|
||||
private final StrBuilder builder;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*/
|
||||
public FastStringWriter() {
|
||||
this(StrBuilder.DEFAULT_CAPACITY);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param initialSize 初始容量
|
||||
*/
|
||||
public FastStringWriter(int initialSize) {
|
||||
super();
|
||||
if (initialSize < 0) {
|
||||
initialSize = StrBuilder.DEFAULT_CAPACITY;
|
||||
}
|
||||
this.builder = new StrBuilder(initialSize);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void write(final int c) {
|
||||
this.builder.append((char) c);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void write(final String str) {
|
||||
this.builder.append(str);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void write(final String str, final int off, final int len) {
|
||||
this.builder.append(str, off, off + len);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void write(final char[] cbuf) {
|
||||
this.builder.append(cbuf, 0, cbuf.length);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void write(final char[] cbuf, final int off, final int len) {
|
||||
if ((off < 0) || (off > cbuf.length) || (len < 0) ||
|
||||
((off + len) > cbuf.length) || ((off + len) < 0)) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
} else if (len == 0) {
|
||||
return;
|
||||
}
|
||||
this.builder.append(cbuf, off, len);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void flush() {
|
||||
// Nothing to be flushed
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
// Nothing to be closed
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.builder.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -59,7 +59,6 @@ import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.zip.CRC32;
|
||||
@@ -83,7 +82,7 @@ public class FileUtil {
|
||||
/**
|
||||
* Windows下文件名中的无效字符
|
||||
*/
|
||||
private static Pattern FILE_NAME_INVALID_PATTERN_WIN = Pattern.compile("[\\\\/:*?\"<>|]");
|
||||
private static final Pattern FILE_NAME_INVALID_PATTERN_WIN = Pattern.compile("[\\\\/:*?\"<>|]");
|
||||
|
||||
/**
|
||||
* Class文件扩展名
|
||||
@@ -273,7 +272,7 @@ public class FileUtil {
|
||||
* @param start 起始路径,必须为目录
|
||||
* @param maxDepth 最大遍历深度,-1表示不限制深度
|
||||
* @param visitor {@link FileVisitor} 接口,用于自定义在访问文件时,访问目录前后等节点做的操作
|
||||
* @see Files#walkFileTree(Path, Set, int, FileVisitor)
|
||||
* @see Files#walkFileTree(Path, java.util.Set, int, FileVisitor)
|
||||
* @since 4.6.3
|
||||
*/
|
||||
public static void walkFiles(Path start, int maxDepth, FileVisitor<? super Path> visitor) {
|
||||
@@ -568,7 +567,7 @@ public class FileUtil {
|
||||
* @return 最后修改时间
|
||||
*/
|
||||
public static Date lastModifiedTime(File file) {
|
||||
if (!exist(file)) {
|
||||
if (false == exist(file)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -590,13 +589,12 @@ public class FileUtil {
|
||||
* 当给定对象为文件时,直接调用 {@link File#length()}<br>
|
||||
* 当给定对象为目录时,遍历目录下的所有文件和目录,递归计算其大小,求和返回
|
||||
*
|
||||
* @param file 目录或文件
|
||||
* @param file 目录或文件,null或者文件不存在返回0
|
||||
* @return 总大小,bytes长度
|
||||
*/
|
||||
public static long size(File file) {
|
||||
Assert.notNull(file, "file argument is null !");
|
||||
if (false == file.exists()) {
|
||||
throw new IllegalArgumentException(StrUtil.format("File [{}] not exist !", file.getAbsolutePath()));
|
||||
if (null == file || false == file.exists()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (file.isDirectory()) {
|
||||
@@ -1177,7 +1175,10 @@ public class FileUtil {
|
||||
*/
|
||||
public static File rename(File file, String newName, boolean isRetainExt, boolean isOverride) {
|
||||
if (isRetainExt) {
|
||||
newName = newName.concat(".").concat(FileUtil.extName(file));
|
||||
final String extName = FileUtil.extName(file);
|
||||
if(StrUtil.isNotBlank(extName)){
|
||||
newName = newName.concat(".").concat(extName);
|
||||
}
|
||||
}
|
||||
final Path path = file.toPath();
|
||||
final CopyOption[] options = isOverride ? new CopyOption[]{StandardCopyOption.REPLACE_EXISTING} : new CopyOption[]{};
|
||||
@@ -3254,10 +3255,10 @@ public class FileUtil {
|
||||
*
|
||||
* @param file 文件
|
||||
* @param out 流
|
||||
* @return 目标文件
|
||||
* @return 写出的流byte数
|
||||
* @throws IORuntimeException IO异常
|
||||
*/
|
||||
public static File writeToStream(File file, OutputStream out) throws IORuntimeException {
|
||||
public static long writeToStream(File file, OutputStream out) throws IORuntimeException {
|
||||
return FileReader.create(file).writeToStream(out);
|
||||
}
|
||||
|
||||
@@ -3266,10 +3267,11 @@ public class FileUtil {
|
||||
*
|
||||
* @param fullFilePath 文件绝对路径
|
||||
* @param out 输出流
|
||||
* @return 写出的流byte数
|
||||
* @throws IORuntimeException IO异常
|
||||
*/
|
||||
public static void writeToStream(String fullFilePath, OutputStream out) throws IORuntimeException {
|
||||
writeToStream(touch(fullFilePath), out);
|
||||
public static long writeToStream(String fullFilePath, OutputStream out) throws IORuntimeException {
|
||||
return writeToStream(touch(fullFilePath), out);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3576,6 +3578,6 @@ public class FileUtil {
|
||||
* @param charset 编码
|
||||
*/
|
||||
public static void tail(File file, Charset charset) {
|
||||
FileUtil.tail(file, charset, Tailer.CONSOLE_HANDLER);
|
||||
tail(file, charset, Tailer.CONSOLE_HANDLER);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
package cn.hutool.core.io;
|
||||
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.exceptions.UtilException;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
import cn.hutool.core.util.HexUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.BufferedReader;
|
||||
@@ -13,7 +20,6 @@ import java.io.Flushable;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
@@ -36,33 +42,34 @@ import java.util.zip.CRC32;
|
||||
import java.util.zip.CheckedInputStream;
|
||||
import java.util.zip.Checksum;
|
||||
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.exceptions.UtilException;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
import cn.hutool.core.util.HexUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
/**
|
||||
* IO工具类<br>
|
||||
* IO工具类只是辅助流的读写,并不负责关闭流。原因是流可能被多次读写,读写关闭后容易造成问题。
|
||||
*
|
||||
* @author xiaoleilu
|
||||
*
|
||||
*/
|
||||
public class IoUtil {
|
||||
|
||||
/** 默认缓存大小 8192*/
|
||||
/**
|
||||
* 默认缓存大小 8192
|
||||
*/
|
||||
public static final int DEFAULT_BUFFER_SIZE = 2 << 12;
|
||||
/** 默认中等缓存大小 16384*/
|
||||
/**
|
||||
* 默认中等缓存大小 16384
|
||||
*/
|
||||
public static final int DEFAULT_MIDDLE_BUFFER_SIZE = 2 << 13;
|
||||
/** 默认大缓存大小 32768*/
|
||||
/**
|
||||
* 默认大缓存大小 32768
|
||||
*/
|
||||
public static final int DEFAULT_LARGE_BUFFER_SIZE = 2 << 14;
|
||||
|
||||
/** 数据流末尾 */
|
||||
/**
|
||||
* 数据流末尾
|
||||
*/
|
||||
public static final int EOF = -1;
|
||||
|
||||
// -------------------------------------------------------------------------------------- Copy start
|
||||
|
||||
/**
|
||||
* 将Reader中的内容复制到Writer中 使用默认缓存大小,拷贝后不关闭Reader
|
||||
*
|
||||
@@ -296,6 +303,7 @@ public class IoUtil {
|
||||
// -------------------------------------------------------------------------------------- Copy end
|
||||
|
||||
// -------------------------------------------------------------------------------------- getReader and getWriter start
|
||||
|
||||
/**
|
||||
* 获得一个文件读取器,默认使用UTF-8编码
|
||||
*
|
||||
@@ -412,6 +420,7 @@ public class IoUtil {
|
||||
// -------------------------------------------------------------------------------------- getReader and getWriter end
|
||||
|
||||
// -------------------------------------------------------------------------------------- read start
|
||||
|
||||
/**
|
||||
* 从流中读取内容
|
||||
*
|
||||
@@ -453,7 +462,7 @@ public class IoUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 从流中读取内容,读到输出流中
|
||||
* 从流中读取内容,读到输出流中,读取完毕后并不关闭流
|
||||
*
|
||||
* @param in 输入流
|
||||
* @return 输出流
|
||||
@@ -636,7 +645,11 @@ public class IoUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 从流中读取内容,读到输出流中
|
||||
* 从流中读取对象,即对象的反序列化
|
||||
*
|
||||
* <p>
|
||||
* 注意!!! 此方法不会检查反序列化安全,可能存在反序列化漏洞风险!!!
|
||||
* </p>
|
||||
*
|
||||
* @param <T> 读取对象的类型
|
||||
* @param in 输入流
|
||||
@@ -645,15 +658,56 @@ public class IoUtil {
|
||||
* @throws UtilException ClassNotFoundException包装
|
||||
*/
|
||||
public static <T> T readObj(InputStream in) throws IORuntimeException, UtilException {
|
||||
return readObj(in, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从流中读取对象,即对象的反序列化,读取后不关闭流
|
||||
*
|
||||
* <p>
|
||||
* 注意!!! 此方法不会检查反序列化安全,可能存在反序列化漏洞风险!!!
|
||||
* </p>
|
||||
*
|
||||
* @param <T> 读取对象的类型
|
||||
* @param in 输入流
|
||||
* @param clazz 读取对象类型
|
||||
* @return 输出流
|
||||
* @throws IORuntimeException IO异常
|
||||
* @throws UtilException ClassNotFoundException包装
|
||||
*/
|
||||
public static <T> T readObj(InputStream in, Class<T> clazz) throws IORuntimeException, UtilException {
|
||||
try {
|
||||
return readObj((in instanceof ValidateObjectInputStream) ?
|
||||
(ValidateObjectInputStream) in : new ValidateObjectInputStream(in),
|
||||
clazz);
|
||||
} catch (IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从流中读取对象,即对象的反序列化,读取后不关闭流
|
||||
*
|
||||
* <p>
|
||||
* 此方法使用了{@link ValidateObjectInputStream}中的黑白名单方式过滤类,用于避免反序列化漏洞<br>
|
||||
* 通过构造{@link ValidateObjectInputStream},调用{@link ValidateObjectInputStream#accept(Class[])}
|
||||
* 或者{@link ValidateObjectInputStream#refuse(Class[])}方法添加可以被序列化的类或者禁止序列化的类。
|
||||
* </p>
|
||||
*
|
||||
* @param <T> 读取对象的类型
|
||||
* @param in 输入流,使用{@link ValidateObjectInputStream}中的黑白名单方式过滤类,用于避免反序列化漏洞
|
||||
* @param clazz 读取对象类型
|
||||
* @return 输出流
|
||||
* @throws IORuntimeException IO异常
|
||||
* @throws UtilException ClassNotFoundException包装
|
||||
*/
|
||||
public static <T> T readObj(ValidateObjectInputStream in, Class<T> clazz) throws IORuntimeException, UtilException {
|
||||
if (in == null) {
|
||||
throw new IllegalArgumentException("The InputStream must not be null");
|
||||
}
|
||||
ObjectInputStream ois;
|
||||
try {
|
||||
ois = new ObjectInputStream(in);
|
||||
@SuppressWarnings("unchecked") // may fail with CCE if serialised form is incorrect
|
||||
final T obj = (T) ois.readObject();
|
||||
return obj;
|
||||
//noinspection unchecked
|
||||
return (T) in.readObject();
|
||||
} catch (IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
} catch (ClassNotFoundException e) {
|
||||
@@ -947,9 +1001,9 @@ public class IoUtil {
|
||||
for (Object content : contents) {
|
||||
if (content != null) {
|
||||
osw.write(Convert.toStr(content, StrUtil.EMPTY));
|
||||
}
|
||||
}
|
||||
osw.flush();
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
} finally {
|
||||
@@ -959,6 +1013,19 @@ public class IoUtil {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将多部分内容写到流中
|
||||
*
|
||||
* @param out 输出流
|
||||
* @param isCloseOut 写入完毕是否关闭输出流
|
||||
* @param obj 写入的对象内容
|
||||
* @throws IORuntimeException IO异常
|
||||
* @since 5.3.3
|
||||
*/
|
||||
public static void writeObj(OutputStream out, boolean isCloseOut, Serializable obj) throws IORuntimeException {
|
||||
writeObjects(out, isCloseOut, obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将多部分内容写到流中
|
||||
*
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
package cn.hutool.core.io;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InvalidClassException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectStreamClass;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 带有类验证的对象流,用于避免反序列化漏洞<br>
|
||||
* 详细见:https://xz.aliyun.com/t/41/
|
||||
*
|
||||
* @author looly
|
||||
* @since 5.2.6
|
||||
*/
|
||||
public class ValidateObjectInputStream extends ObjectInputStream {
|
||||
|
||||
private Set<String> whiteClassSet;
|
||||
private Set<String> blackClassSet;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param inputStream 流
|
||||
* @param acceptClasses 白名单的类
|
||||
* @throws IOException IO异常
|
||||
*/
|
||||
public ValidateObjectInputStream(InputStream inputStream, Class<?>... acceptClasses) throws IOException {
|
||||
super(inputStream);
|
||||
accept(acceptClasses);
|
||||
}
|
||||
|
||||
/**
|
||||
* 禁止反序列化的类,用于反序列化验证
|
||||
*
|
||||
* @param refuseClasses 禁止反序列化的类
|
||||
* @since 5.3.5
|
||||
*/
|
||||
public void refuse(Class<?>... refuseClasses) {
|
||||
if(null == this.blackClassSet){
|
||||
this.blackClassSet = new HashSet<>();
|
||||
}
|
||||
for (Class<?> acceptClass : refuseClasses) {
|
||||
this.blackClassSet.add(acceptClass.getName());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 接受反序列化的类,用于反序列化验证
|
||||
*
|
||||
* @param acceptClasses 接受反序列化的类
|
||||
*/
|
||||
public void accept(Class<?>... acceptClasses) {
|
||||
if(null == this.whiteClassSet){
|
||||
this.whiteClassSet = new HashSet<>();
|
||||
}
|
||||
for (Class<?> acceptClass : acceptClasses) {
|
||||
this.whiteClassSet.add(acceptClass.getName());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 只允许反序列化SerialObject class
|
||||
*/
|
||||
@Override
|
||||
protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
|
||||
validateClassName(desc.getName());
|
||||
return super.resolveClass(desc);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证反序列化的类是否合法
|
||||
* @param className 类名
|
||||
* @throws InvalidClassException 非法类
|
||||
*/
|
||||
private void validateClassName(String className) throws InvalidClassException {
|
||||
// 黑名单
|
||||
if(CollUtil.isNotEmpty(this.blackClassSet)){
|
||||
if(this.blackClassSet.contains(className)){
|
||||
throw new InvalidClassException("Unauthorized deserialization attempt by black list", className);
|
||||
}
|
||||
}
|
||||
|
||||
if(CollUtil.isEmpty(this.whiteClassSet)){
|
||||
return;
|
||||
}
|
||||
if(className.startsWith("java.")){
|
||||
// java中的类默认在白名单中
|
||||
return;
|
||||
}
|
||||
if(this.whiteClassSet.contains(className)){
|
||||
return;
|
||||
}
|
||||
|
||||
throw new InvalidClassException("Unauthorized deserialization attempt", className);
|
||||
}
|
||||
}
|
||||
@@ -66,7 +66,7 @@ public class CRC16 implements Checksum, Serializable {
|
||||
@Override
|
||||
public void update(byte[] b, int off, int len) {
|
||||
for (int i = off; i < off + len; i++)
|
||||
update((int) b[i]);
|
||||
update(b[i]);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package cn.hutool.core.io.file;
|
||||
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.Serializable;
|
||||
@@ -7,8 +9,6 @@ import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
|
||||
/**
|
||||
* 文件追加器<br>
|
||||
* 持有一个文件,在内存中积累一定量的数据后统一追加到文件<br>
|
||||
@@ -21,12 +21,12 @@ import cn.hutool.core.util.CharsetUtil;
|
||||
public class FileAppender implements Serializable{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private FileWriter writer;
|
||||
private final FileWriter writer;
|
||||
/** 内存中持有的字符串数 */
|
||||
private int capacity;
|
||||
private final int capacity;
|
||||
/** 追加内容是否为新行 */
|
||||
private boolean isNewLineMode;
|
||||
private List<String> list = new ArrayList<>(100);
|
||||
private final boolean isNewLineMode;
|
||||
private final List<String> list = new ArrayList<>(100);
|
||||
|
||||
/**
|
||||
* 构造
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
package cn.hutool.core.io.file;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.io.IORuntimeException;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.lang.copier.SrcToDestCopier;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.CopyOption;
|
||||
@@ -7,12 +14,6 @@ import java.nio.file.Files;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.io.IORuntimeException;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.lang.copier.SrcToDestCopier;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
/**
|
||||
* 文件拷贝器<br>
|
||||
* 支持以下几种情况:
|
||||
@@ -212,12 +213,14 @@ public class FileCopier extends SrcToDestCopier<File, FileCopier>{
|
||||
|
||||
if (false == dest.exists()) {
|
||||
//目标为不存在路径,创建为目录
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
dest.mkdirs();
|
||||
} else if (false == dest.isDirectory()) {
|
||||
throw new IORuntimeException(StrUtil.format("Src [{}] is a directory but dest [{}] is a file!", src.getPath(), dest.getPath()));
|
||||
}
|
||||
|
||||
final String[] files = src.list();
|
||||
if(ArrayUtil.isNotEmpty(files)){
|
||||
File srcFile;
|
||||
File destFile;
|
||||
for (String file : files) {
|
||||
@@ -231,6 +234,7 @@ public class FileCopier extends SrcToDestCopier<File, FileCopier>{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 拷贝文件,只用于内部,不做任何安全检查<br>
|
||||
@@ -263,6 +267,7 @@ public class FileCopier extends SrcToDestCopier<File, FileCopier>{
|
||||
}
|
||||
}else {
|
||||
//路径不存在则创建父目录
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
dest.getParentFile().mkdirs();
|
||||
}
|
||||
|
||||
@@ -275,7 +280,7 @@ public class FileCopier extends SrcToDestCopier<File, FileCopier>{
|
||||
}
|
||||
|
||||
try {
|
||||
Files.copy(src.toPath(), dest.toPath(), optionList.toArray(new CopyOption[optionList.size()]));
|
||||
Files.copy(src.toPath(), dest.toPath(), optionList.toArray(new CopyOption[0]));
|
||||
} catch (IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
package cn.hutool.core.io.file;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.io.IORuntimeException;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.io.LineHandler;
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
@@ -11,13 +18,6 @@ import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.io.IORuntimeException;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.io.LineHandler;
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
/**
|
||||
* 文件读取器
|
||||
*
|
||||
@@ -249,20 +249,15 @@ public class FileReader extends FileWrapper {
|
||||
* 将文件写入流中
|
||||
*
|
||||
* @param out 流
|
||||
* @return File
|
||||
* @return 写出的流byte数
|
||||
* @throws IORuntimeException IO异常
|
||||
*/
|
||||
public File writeToStream(OutputStream out) throws IORuntimeException {
|
||||
FileInputStream in = null;
|
||||
try {
|
||||
in = new FileInputStream(file);
|
||||
IoUtil.copy(in, out);
|
||||
public long writeToStream(OutputStream out) throws IORuntimeException {
|
||||
try (FileInputStream in = new FileInputStream(this.file)){
|
||||
return IoUtil.copy(in, out);
|
||||
}catch (IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
} finally {
|
||||
IoUtil.close(in);
|
||||
}
|
||||
return this.file;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------- Interface start
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
package cn.hutool.core.io.file;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.io.IORuntimeException;
|
||||
import cn.hutool.core.io.LineHandler;
|
||||
import cn.hutool.core.io.watch.SimpleWatcher;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.WatchEvent;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.io.IORuntimeException;
|
||||
import cn.hutool.core.io.LineHandler;
|
||||
import cn.hutool.core.io.watch.SimpleWatcher;
|
||||
|
||||
/**
|
||||
* 行处理的Watcher实现
|
||||
*
|
||||
@@ -19,9 +19,9 @@ import cn.hutool.core.io.watch.SimpleWatcher;
|
||||
*/
|
||||
public class LineReadWatcher extends SimpleWatcher implements Runnable {
|
||||
|
||||
private RandomAccessFile randomAccessFile;
|
||||
private Charset charset;
|
||||
private LineHandler lineHandler;
|
||||
private final RandomAccessFile randomAccessFile;
|
||||
private final Charset charset;
|
||||
private final LineHandler lineHandler;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
|
||||
@@ -23,7 +23,7 @@ public enum LineSeparator {
|
||||
/** Windows系统换行符:"\r\n" */
|
||||
WINDOWS("\r\n");
|
||||
|
||||
private String value;
|
||||
private final String value;
|
||||
|
||||
LineSeparator(String lineSeparator) {
|
||||
this.value = lineSeparator;
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
package cn.hutool.core.io.file;
|
||||
|
||||
import cn.hutool.core.date.DateUnit;
|
||||
import cn.hutool.core.exceptions.UtilException;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.io.IORuntimeException;
|
||||
import cn.hutool.core.io.LineHandler;
|
||||
import cn.hutool.core.lang.Console;
|
||||
import cn.hutool.core.util.CharUtil;
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
@@ -12,15 +21,6 @@ import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import cn.hutool.core.date.DateUnit;
|
||||
import cn.hutool.core.exceptions.UtilException;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.io.IORuntimeException;
|
||||
import cn.hutool.core.io.LineHandler;
|
||||
import cn.hutool.core.lang.Console;
|
||||
import cn.hutool.core.util.CharUtil;
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
|
||||
/**
|
||||
* 文件内容跟随器,实现类似Linux下"tail -f"命令功能
|
||||
*
|
||||
@@ -33,16 +33,16 @@ public class Tailer implements Serializable {
|
||||
public static final LineHandler CONSOLE_HANDLER = new ConsoleLineHandler();
|
||||
|
||||
/** 编码 */
|
||||
private Charset charset;
|
||||
private final Charset charset;
|
||||
/** 行处理器 */
|
||||
private LineHandler lineHandler;
|
||||
private final LineHandler lineHandler;
|
||||
/** 初始读取的行数 */
|
||||
private int initReadLine;
|
||||
private final int initReadLine;
|
||||
/** 定时任务检查间隔时长 */
|
||||
private long period;
|
||||
private final long period;
|
||||
|
||||
private RandomAccessFile randomAccessFile;
|
||||
private ScheduledExecutorService executorService;
|
||||
private final RandomAccessFile randomAccessFile;
|
||||
private final ScheduledExecutorService executorService;
|
||||
|
||||
/**
|
||||
* 构造,默认UTF-8编码
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
package cn.hutool.core.io.resource;
|
||||
|
||||
import cn.hutool.core.io.IORuntimeException;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
@@ -8,10 +11,6 @@ import java.io.StringReader;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import cn.hutool.core.io.IORuntimeException;
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
/**
|
||||
* 基于byte[]的资源获取器<br>
|
||||
* 注意:此对象中getUrl方法始终返回null
|
||||
@@ -70,11 +69,6 @@ public class BytesResource implements Resource, Serializable {
|
||||
return StrUtil.str(this.bytes, charset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String readUtf8Str() throws IORuntimeException {
|
||||
return readStr(CharsetUtil.CHARSET_UTF_8);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] readBytes() throws IORuntimeException {
|
||||
return this.bytes;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user