Merge branch 'v5-dev' into v5-dev

This commit is contained in:
大火yzs
2020-05-29 14:26:53 +08:00
committed by GitHub
481 changed files with 15001 additions and 7851 deletions

1
.gitignore vendored
View File

@@ -26,6 +26,7 @@ build/
*.diff
*.patch
*.tmp
.jython_cache/
# system ignore
.DS_Store

File diff suppressed because it is too large Load Diff

View File

@@ -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支持返回Beanissue#869@Github
* 【core 】 Snowflake循环等待下一个时间时避免长时间循环加入对时钟倒退的判断pr#874@Github
* 【extra 】 新增 QRCode base64 编码形式返回pr#878@Github
* 【core 】 ImgUtil增加toBase64DateUriURLUtil增加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去除ECissue#887@Github
* 【cache 】 超时缓存使用的线程池大小默认为1issue#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增加recursiveDownloadFolderpr#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 】 增加PinyinUtilissue#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.writeObjissue#I1FZIE
* 【core 】 增加FastStringWriter
* 【core 】 增加NumberUtil.ceilDiv方法pr#858@Github
* 【core 】 IdcardUtil增加省份校验issue#859@Github
* 【extra 】 TemplateFactory和TokenizerFactory增加单例的get方法
### Bug修复
* 【core 】 修复URLBuilder中请求参数有`&amp;`导致的问题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同时清除aliasComparatorissue#828@Github
* 【core 】 修改StrUtil.equals逻辑改为contentEquals
* 【core 】 增加URLUtil.UrlDecoder
* 【core 】 增加XmlUtil.setNamespaceAwaregetByPath支持UniversalNamespaceCache
* 【aop 】 增加Spring-cglib支持改为SPI实现
* 【json 】 增加JSONUtil.parseXXX增加JSONConfig参数
* 【core 】 RandomUtil.randomNumber改为返回char
* 【crypto 】 SM2支持设置Digest和DSAEncodingissue#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 】 增加CalendarUtilDateUtil相关方法全部迁移到此
### 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

View File

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

@@ -0,0 +1,3 @@
#!/bin/bash
exec mvn -T 1 cobertura:cobertura

3
bin/simple_install.sh Normal file
View File

@@ -0,0 +1,3 @@
#!/bin/bash
exec mvn -T 1C clean install -Dmaven.test.skip=true -Dmaven.javadoc.skip=true

View File

@@ -1 +1 @@
5.2.5
5.3.6

View File

@@ -1 +1 @@
var version = '5.2.5'
var version = '5.3.6'

View File

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

View File

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

View File

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

View File

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

View File

@@ -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) {
// 异常回调(只捕获业务代码导致的异常,而非反射导致的异常)

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,3 @@
cn.hutool.aop.proxy.CglibProxyFactory
cn.hutool.aop.proxy.SpringCglibProxyFactory
cn.hutool.aop.proxy.JdkProxyFactory

View File

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

View File

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

View File

@@ -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 是否存在
*/

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -16,7 +16,7 @@ public class MathGenerator implements CodeGenerator {
private static final String operators = "+-*";
/** 参与计算数字最大长度 */
private int numberLength;
private final int numberLength;
/**
* 构造

View File

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

View File

@@ -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 字段名
*/

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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编码是用642的6次方个ASCII字符来表示2562的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));
}
/**

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,6 +2,11 @@ package cn.hutool.core.comparator;
import java.util.Comparator;
/**
* 比较工具类
*
* @author looly
*/
public class CompareUtil {
/**

View File

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

View File

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

View File

@@ -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;
}
}
// ----------------------------------------------------------------------- 全角半角转换

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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 &lt; calendar2返回数小于0calendar1==calendar2返回0calendar1 &gt; 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;
}
}

View File

@@ -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 寒食节",

View File

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

View File

@@ -103,7 +103,7 @@ public enum DateField {
MILLISECOND(Calendar.MILLISECOND);
// ---------------------------------------------------------------
private int value;
private final int value;
DateField(int value) {
this.value = value;

View File

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

View File

@@ -19,7 +19,7 @@ public enum DateUnit {
/**一周的毫秒数 */
WEEK(DAY.getMillis() * 7);
private long millis;
private final long millis;
DateUnit(long millis){
this.millis = millis;
}

View File

@@ -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 &lt; calendar2返回数小于0calendar1==calendar2返回0calendar1 &gt; 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>
* 将以下字符替换为"-"

View File

@@ -53,7 +53,7 @@ public enum Month {
UNDECIMBER(Calendar.UNDECIMBER);
// ---------------------------------------------------------------
private int value;
private final int value;
Month(int value) {
this.value = value;

View File

@@ -23,7 +23,7 @@ public enum Quarter {
Q4(4);
// ---------------------------------------------------------------
private int value;
private final int value;
Quarter(int value) {
this.value = value;

View File

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

View File

@@ -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;
/**
* 构造,默认使用毫秒计数

View File

@@ -36,7 +36,7 @@ public enum Week {
// ---------------------------------------------------------------
/** 星期对应{@link Calendar} 中的Week值 */
private int value;
private final int value;
/**
* 构造

View File

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

View File

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

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

View File

@@ -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 矩形对象表示矩形区域的xywidthheight,,基于左上角
* @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;
}
}

View File

@@ -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 矩形对象表示矩形区域的xywidthheightx,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;
}

View File

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

View File

@@ -35,7 +35,7 @@ public enum ScaleType {
this.value = value;
}
private int value;
private final int value;
public int getValue() {
return this.value;

View File

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

View File

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

View File

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

View File

@@ -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);
}
/**
* 将多部分内容写到流中
*

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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