mirror of
https://gitee.com/chinabugotech/hutool.git
synced 2025-05-09 23:51:34 +08:00
Merge branch 'v6-dev' into v6-dev
This commit is contained in:
commit
1f5700a57e
@ -11,8 +11,8 @@ hutool版本: 5.X.X(请确保最新尝试是否还有问题)
|
||||
Console.log("报错了");
|
||||
```
|
||||
|
||||
2. 堆栈信息
|
||||
1. 堆栈信息
|
||||
|
||||
3. 测试涉及到的文件(注意脱密)
|
||||
2. 测试涉及到的文件(注意脱密)
|
||||
|
||||
比如报错的Excel文件,有问题的图片等。
|
4
.github/ISSUE_TEMPLATE.md
vendored
4
.github/ISSUE_TEMPLATE.md
vendored
@ -11,8 +11,8 @@ hutool版本: 5.X.X(请确保最新尝试是否还有问题)
|
||||
Console.log("报错了");
|
||||
```
|
||||
|
||||
2. 堆栈信息
|
||||
1. 堆栈信息
|
||||
|
||||
3. 测试涉及到的文件(注意脱密)
|
||||
2. 测试涉及到的文件(注意脱密)
|
||||
|
||||
比如报错的Excel文件,有问题的图片等。
|
@ -3,14 +3,13 @@
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------
|
||||
|
||||
# 6.0.0.M1 (2022-10-09)
|
||||
# 6.0.0.M1 (2023-03-10)
|
||||
|
||||
### 计划实现
|
||||
* 【poi 】 PDF相关(基于PdfBox)
|
||||
* 【poi 】 HTML、DOCX转换相关
|
||||
* 【poi 】 Markdown相关(如HTML转换等),基于commonmark-java
|
||||
* 【db 】 增加DDL封装
|
||||
* 【poi 】 CellUtil.getCellIfMergedRegion考虑添加缓存支持,增加最大和最小范围判断,减少遍历
|
||||
* 【http 】 公共代理和SSL验证
|
||||
|
||||
### ❌不兼容特性
|
||||
|
||||
|
16
README-EN.md
16
README-EN.md
@ -1,5 +1,5 @@
|
||||
<p align="center">
|
||||
<a href="https://hutool.cn/"><img src="https://cdn.jsdelivr.net/gh/looly/hutool-site/images/logo.jpg" width="45%"></a>
|
||||
<a href="https://hutool.cn/"><img src="https://plus.hutool.cn/images/hutool.svg" width="45%"></a>
|
||||
</p>
|
||||
<p align="center">
|
||||
<strong>🍬A set of tools that keep Java sweet.</strong>
|
||||
@ -51,7 +51,7 @@
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
## 📚Introduction
|
||||
**Hutool** is a small but comprehensive library of Java tools, encapsulation by static methods, reduce the cost of learning related APIs, increase productivity, and make Java as elegant as a functional programming language,let the Java be "sweet" too.
|
||||
**Hutool** is a small but comprehensive library of Java tools, achieved by encapsulation through static methods, reduce the cost of learning related APIs, increase productivity, and make Java as elegant as a functional programming language,let the Java be "sweet" too.
|
||||
|
||||
**Hutool** tools and methods from each user's crafted, it covers all aspects of the underlying code of Java development, it is a powerful tool for large project development to solve small problems, but also the efficiency of small projects;
|
||||
|
||||
@ -138,18 +138,18 @@ We provide the T-Shirt and Sweater with Hutool Logo, please visit the shop:
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
<version>6.0.0.M1</version>
|
||||
<version>6.0.0.M2</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
### 🍐Gradle
|
||||
```
|
||||
implementation 'cn.hutool:hutool-all:6.0.0.M1'
|
||||
implementation 'cn.hutool:hutool-all:6.0.0.M2'
|
||||
```
|
||||
|
||||
## 📥Download
|
||||
|
||||
- [Maven Repo](https://repo1.maven.org/maven2/cn/hutool/hutool-all/6.0.0.M1/)
|
||||
- [Maven Repo](https://repo1.maven.org/maven2/cn/hutool/hutool-all/6.0.0.M2/)
|
||||
|
||||
> 🔔️note:
|
||||
> Hutool 5.x supports JDK8+ and is not tested on Android platforms, and cannot guarantee that all tool classes or tool methods are available.
|
||||
@ -176,10 +176,10 @@ cd ${hutool}
|
||||
|
||||
Hutool's source code is divided into two branches:
|
||||
|
||||
| branch | description |
|
||||
|-----------|---------------------------------------------------------------|
|
||||
| branch | description |
|
||||
|-----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| v6-master | The master branch, the branch used by the release version, is the same as the jar committed to the central repository and does not receive any pr or modifications. |
|
||||
| v6-dev | Development branch, which defaults to the next SNAPSHOT version, accepts modifications or pr |
|
||||
| v6-dev | Development branch, which defaults to the next SNAPSHOT version, accepts modifications or pr |
|
||||
|
||||
### 🐞Provide feedback or suggestions on bugs
|
||||
|
||||
|
14
README.md
14
README.md
@ -1,5 +1,5 @@
|
||||
<p align="center">
|
||||
<a href="https://hutool.cn/"><img src="https://cdn.jsdelivr.net/gh/looly/hutool-site/images/logo.jpg" width="45%"></a>
|
||||
<a href="https://hutool.cn/"><img src="https://plus.hutool.cn/images/hutool.svg" width="45%"></a>
|
||||
</p>
|
||||
<p align="center">
|
||||
<strong>🍬A set of tools that keep Java sweet.</strong>
|
||||
@ -141,21 +141,21 @@ Hutool的存在就是为了减少代码搜索成本,避免网络上参差不
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
<version>6.0.0.M1</version>
|
||||
<version>6.0.0.M2</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
### 🍐Gradle
|
||||
|
||||
```
|
||||
implementation 'cn.hutool:hutool-all:6.0.0.M1'
|
||||
implementation 'cn.hutool:hutool-all:6.0.0.M2'
|
||||
```
|
||||
|
||||
### 📥下载jar
|
||||
|
||||
点击以下链接,下载`hutool-all-X.X.X.jar`即可:
|
||||
|
||||
- [Maven中央库](https://repo1.maven.org/maven2/cn/hutool/hutool-all/6.0.0.M1/)
|
||||
- [Maven中央库](https://repo1.maven.org/maven2/cn/hutool/hutool-all/6.0.0.M2/)
|
||||
|
||||
> 🔔️注意
|
||||
> Hutool 5.x支持JDK8+,对Android平台没有测试,不能保证所有工具类或工具方法可用。
|
||||
@ -180,10 +180,10 @@ implementation 'cn.hutool:hutool-all:6.0.0.M1'
|
||||
|
||||
Hutool的源码分为两个分支,功能如下:
|
||||
|
||||
| 分支 | 作用 |
|
||||
|-----------|---------------------------------------------------------------|
|
||||
| 分支 | 作用 |
|
||||
|-----------|--------------------------------------------|
|
||||
| v6-master | 主分支,release版本使用的分支,与中央库提交的jar一致,不接收任何pr或修改 |
|
||||
| v6-dev | 开发分支,默认为下个版本的SNAPSHOT版本,接受修改或pr |
|
||||
| v6-dev | 开发分支,默认为下个版本的SNAPSHOT版本,接受修改或pr |
|
||||
|
||||
### 🐞提供bug反馈或建议
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
## Supported Versions(支持的版本)
|
||||
|
||||
| Version | Supported |
|
||||
|---------| ------------------ |
|
||||
|---------|--------------------|
|
||||
| 6.x.x | :white_check_mark: |
|
||||
|
||||
## Reporting a Vulnerability(报告漏洞)
|
||||
|
@ -12,7 +12,7 @@ echo "当前路径:${pwd}"
|
||||
|
||||
if [ -n "$1" ];then
|
||||
new_version="$1"
|
||||
old_version=`cat ${pwd}/bin/version.txt`
|
||||
old_version=$(cat "${pwd}"/bin/version.txt)
|
||||
echo "$old_version 替换为新版本 $new_version"
|
||||
else
|
||||
# 参数错误,退出
|
||||
@ -20,16 +20,16 @@ else
|
||||
exit
|
||||
fi
|
||||
|
||||
if [ ! -n "$old_version" ]; then
|
||||
if [ -z "$old_version" ]; then
|
||||
echo "ERROR: 旧版本不存在,请确认bin/version.txt中信息正确"
|
||||
exit
|
||||
fi
|
||||
|
||||
# 替换README.md中的版本
|
||||
sed -i "s/${old_version}/${new_version}/g" $pwd/README.md
|
||||
sed -i "s/${old_version}/${new_version}/g" $pwd/README-EN.md
|
||||
sed -i "s/${old_version}/${new_version}/g" "$pwd"/README.md
|
||||
sed -i "s/${old_version}/${new_version}/g" "$pwd"/README-EN.md
|
||||
# 替换docs/js/version.js中的版本
|
||||
sed -i "s/${old_version}/${new_version}/g" $pwd/docs/js/version.js
|
||||
sed -i "s/${old_version}/${new_version}/g" "$pwd"/docs/js/version.js
|
||||
|
||||
# 保留新版本号
|
||||
echo "$new_version" > $pwd/bin/version.txt
|
||||
echo "$new_version" > "$pwd"/bin/version.txt
|
||||
|
@ -1 +1 @@
|
||||
6.0.0.M1
|
||||
6.0.0.M2
|
||||
|
@ -1 +1 @@
|
||||
var version = '6.0.0.M1'
|
||||
var version = '6.0.0.M2'
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>6.0.0.M1</version>
|
||||
<version>6.0.0.M2</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-all</artifactId>
|
||||
|
@ -42,6 +42,9 @@ import java.util.Set;
|
||||
*/
|
||||
public class Hutool {
|
||||
|
||||
/**
|
||||
* 作者(贡献者)
|
||||
*/
|
||||
public static final String AUTHOR = "Looly";
|
||||
|
||||
private Hutool() {
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>6.0.0.M1</version>
|
||||
<version>6.0.0.M2</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-bom</artifactId>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<p align="center">
|
||||
<a href="https://hutool.cn/"><img src="https://cdn.jsdelivr.net/gh/looly/hutool-site/images/logo.jpg" width="45%"></a>
|
||||
<a href="https://hutool.cn/"><img src="https://plus.hutool.cn/images/hutool.svg" width="45%"></a>
|
||||
</p>
|
||||
<p align="center">
|
||||
<strong>🍬A set of tools that keep Java sweet.</strong>
|
||||
|
@ -9,23 +9,15 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>6.0.0.M1</version>
|
||||
<version>6.0.0.M2</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-core</artifactId>
|
||||
<name>${project.artifactId}</name>
|
||||
<description>Hutool核心,包括集合、字符串、Bean等工具</description>
|
||||
|
||||
<properties>
|
||||
<Automatic-Module-Name>cn.hutool.core</Automatic-Module-Name>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<redirectTestOutputToFile>true</redirectTestOutputToFile>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
</project>
|
@ -703,7 +703,7 @@ public class AnnotatedElementUtil {
|
||||
/**
|
||||
* 清空相关缓存,包括:
|
||||
* <ul>
|
||||
* <li>{@link AnnotatedElementUtil}中的{@link AnnotatedElement}及{@link AnnotationMapping}缓存;</li>
|
||||
* <li>{@code AnnotatedElementUtil}中的{@link AnnotatedElement}及{@link AnnotationMapping}缓存;</li>
|
||||
* <li>{@link AnnotationUtil}中的{@link AnnotatedElement}上直接声明的注解缓存;</li>
|
||||
* <li>{@link RepeatableAnnotationCollector}中单例的注解属性缓存;</li>
|
||||
* </ul>
|
||||
|
@ -28,7 +28,7 @@ public interface AnnotationMapping<T extends Annotation> extends Annotation {
|
||||
T getAnnotation();
|
||||
|
||||
/**
|
||||
* 根据当前映射对象,通过动态代理生成一个类型与被包装注解对象一致的合成注解,该注解相对原生注解:
|
||||
* 根据当前映射对象,通过动态代理生成一个类型与被包装注解对象一致地合成注解,该注解相对原生注解:
|
||||
* <ul>
|
||||
* <li>支持同注解内通过{@link Alias}构建的别名机制;</li>
|
||||
* <li>支持子注解对元注解的同名同类型属性覆盖机制;</li>
|
||||
|
@ -9,6 +9,7 @@ import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.map.WeakConcurrentMap;
|
||||
import cn.hutool.core.reflect.FieldUtil;
|
||||
import cn.hutool.core.reflect.MethodUtil;
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import cn.hutool.core.text.StrUtil;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.ObjUtil;
|
||||
@ -21,6 +22,7 @@ import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.lang.reflect.Proxy;
|
||||
@ -38,6 +40,11 @@ import java.util.stream.Stream;
|
||||
*/
|
||||
public class AnnotationUtil {
|
||||
|
||||
private static final String JDK_MEMBER_ATTRIBUTE = "memberValues";
|
||||
private static final String SPRING_MEMBER_ATTRIBUTE = "valueCache";
|
||||
private static final String HUTOOL_MEMBER_ATTRIBUTE = "valueCache";
|
||||
private static final String SPRING_INVOCATION_HANDLER = "SynthesizedMergedAnnotationInvocationHandler";
|
||||
|
||||
/**
|
||||
* 直接声明的注解缓存
|
||||
*/
|
||||
@ -323,9 +330,20 @@ public class AnnotationUtil {
|
||||
* @param value 要更新的属性值
|
||||
* @since 5.5.2
|
||||
*/
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
public static void setValue(final Annotation annotation, final String annotationField, final Object value) {
|
||||
final Map memberValues = (Map) FieldUtil.getFieldValue(Proxy.getInvocationHandler(annotation), "memberValues");
|
||||
@SuppressWarnings("unchecked")
|
||||
public static void setValue(
|
||||
final Annotation annotation, final String annotationField, final Object value) {
|
||||
InvocationHandler invocationHandler = Proxy.getInvocationHandler(annotation);
|
||||
String memberAttributeName = JDK_MEMBER_ATTRIBUTE;
|
||||
// Spring合成注解
|
||||
if (CharSequenceUtil.contains(invocationHandler.getClass().getName(), SPRING_INVOCATION_HANDLER)) {
|
||||
memberAttributeName = SPRING_MEMBER_ATTRIBUTE;
|
||||
}
|
||||
// Hutool合成注解
|
||||
else if (invocationHandler instanceof AnnotationMappingProxy) {
|
||||
memberAttributeName = HUTOOL_MEMBER_ATTRIBUTE;
|
||||
}
|
||||
Map<String, Object> memberValues = (Map<String, Object>) FieldUtil.getFieldValue(invocationHandler, memberAttributeName);
|
||||
memberValues.put(annotationField, value);
|
||||
}
|
||||
|
||||
|
@ -17,21 +17,21 @@ import java.util.stream.Stream;
|
||||
*/
|
||||
public class GenericAnnotationMapping implements AnnotationMapping<Annotation> {
|
||||
|
||||
private final Annotation annotation;
|
||||
private final boolean isRoot;
|
||||
private final Method[] attributes;
|
||||
|
||||
/**
|
||||
* 创建一个通用注解包装类
|
||||
*
|
||||
* @param annotation 注解对象
|
||||
* @param isRoot 是否根注解
|
||||
* @return {@link GenericAnnotationMapping}实例
|
||||
* @return {@code GenericAnnotationMapping}实例
|
||||
*/
|
||||
public static GenericAnnotationMapping create(final Annotation annotation, final boolean isRoot) {
|
||||
return new GenericAnnotationMapping(annotation, isRoot);
|
||||
}
|
||||
|
||||
private final Annotation annotation;
|
||||
private final boolean isRoot;
|
||||
private final Method[] attributes;
|
||||
|
||||
/**
|
||||
* 创建一个通用注解包装类
|
||||
*
|
||||
@ -133,14 +133,14 @@ public class GenericAnnotationMapping implements AnnotationMapping<Annotation> {
|
||||
* @return 是否
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
public boolean equals(final Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
GenericAnnotationMapping that = (GenericAnnotationMapping)o;
|
||||
final GenericAnnotationMapping that = (GenericAnnotationMapping)o;
|
||||
return isRoot == that.isRoot && annotation.equals(that.annotation);
|
||||
}
|
||||
|
||||
|
@ -25,7 +25,7 @@ public interface RepeatableAnnotationCollector {
|
||||
/**
|
||||
* 空实现
|
||||
*
|
||||
* @return {@link RepeatableAnnotationCollector}实例
|
||||
* @return {@code RepeatableAnnotationCollector}实例
|
||||
*/
|
||||
static RepeatableAnnotationCollector none() {
|
||||
return None.INSTANCE;
|
||||
@ -47,7 +47,7 @@ public interface RepeatableAnnotationCollector {
|
||||
* </code></pre>
|
||||
* 解析任意{@code Annotation}注解对象,则可以获得{@code value}属性中的{@code Item}注解对象
|
||||
*
|
||||
* @return {@link RepeatableAnnotationCollector}实例
|
||||
* @return {@code RepeatableAnnotationCollector}实例
|
||||
* @see Standard
|
||||
*/
|
||||
static RepeatableAnnotationCollector standard() {
|
||||
@ -59,7 +59,7 @@ public interface RepeatableAnnotationCollector {
|
||||
* 收集器将返回所有匹配的属性中的可重复注解。
|
||||
*
|
||||
* @param predicate 是否为容纳可重复注解的属性的判断条件
|
||||
* @return {@link RepeatableAnnotationCollector}实例
|
||||
* @return {@code RepeatableAnnotationCollector}实例
|
||||
*/
|
||||
static RepeatableAnnotationCollector condition(final BiPredicate<Annotation, Method> predicate) {
|
||||
return new Condition(predicate);
|
||||
@ -80,7 +80,7 @@ public interface RepeatableAnnotationCollector {
|
||||
* 则可以获得{@code items1}属性中的{@code Item1}注解对象,
|
||||
* 以及{@code items2}属性中的{@code Item2}注解对象,
|
||||
*
|
||||
* @return {@link RepeatableAnnotationCollector}实例
|
||||
* @return {@code RepeatableAnnotationCollector}实例
|
||||
*/
|
||||
static RepeatableAnnotationCollector full() {
|
||||
return Full.INSTANCE;
|
||||
@ -189,7 +189,7 @@ public interface RepeatableAnnotationCollector {
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link RepeatableAnnotationCollector}的基本实现
|
||||
* {@code RepeatableAnnotationCollector}的基本实现
|
||||
*/
|
||||
abstract class AbstractCollector implements RepeatableAnnotationCollector {
|
||||
|
||||
@ -218,7 +218,7 @@ public interface RepeatableAnnotationCollector {
|
||||
* @return 容器注解中的可重复注解,若{@code annotation}不为容器注解,则数组中仅有其本身一个对象
|
||||
*/
|
||||
@Override
|
||||
public List<Annotation> getAllRepeatableAnnotations(Annotation annotation) {
|
||||
public List<Annotation> getAllRepeatableAnnotations(final Annotation annotation) {
|
||||
return find(annotation, null, true);
|
||||
}
|
||||
|
||||
@ -261,7 +261,7 @@ public interface RepeatableAnnotationCollector {
|
||||
final boolean isTarget = hasCondition && condition.test(source);
|
||||
if (CollUtil.isEmpty(repeatableMethods) || isTarget) {
|
||||
// 不是累加的,则仅当正在处理的注解不为可重复注解时才记录
|
||||
boolean shouldProcess = !accumulate && (!hasCondition || isTarget);
|
||||
final boolean shouldProcess = !accumulate && (!hasCondition || isTarget);
|
||||
if (shouldProcess) {
|
||||
results.add(source);
|
||||
}
|
||||
|
@ -145,10 +145,10 @@ public class ResolvedAnnotationMapping implements AnnotationMapping<Annotation>
|
||||
* <ul>
|
||||
* <li>当{@code annotation}已经被代理过时抛出;</li>
|
||||
* <li>当{@code source}包装的注解对象与{@code annotation}相同时抛出;</li>
|
||||
* <li>当{@code annotation}包装的注解对象类型为{@link ResolvedAnnotationMapping}时抛出;</li>
|
||||
* <li>当{@code annotation}包装的注解对象类型为{@code ResolvedAnnotationMapping}时抛出;</li>
|
||||
* </ul>
|
||||
*/
|
||||
ResolvedAnnotationMapping(final ResolvedAnnotationMapping source, final Annotation annotation, boolean resolveAttribute) {
|
||||
ResolvedAnnotationMapping(final ResolvedAnnotationMapping source, final Annotation annotation, final boolean resolveAttribute) {
|
||||
Objects.requireNonNull(annotation);
|
||||
Assert.isFalse(AnnotationMappingProxy.isProxied(annotation), "annotation has been proxied");
|
||||
Assert.isFalse(annotation instanceof ResolvedAnnotationMapping, "annotation has been wrapped");
|
||||
@ -254,7 +254,6 @@ public class ResolvedAnnotationMapping implements AnnotationMapping<Annotation>
|
||||
*
|
||||
* @return 所需的注解,若{@link ResolvedAnnotationMapping#isResolved()}为{@code false}则返回的是原始的注解对象
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Annotation getResolvedAnnotation() {
|
||||
if (!isResolved()) {
|
||||
@ -399,7 +398,7 @@ public class ResolvedAnnotationMapping implements AnnotationMapping<Annotation>
|
||||
}
|
||||
// 获取除自己外的全部子注解
|
||||
final Deque<ResolvedAnnotationMapping> sources = new LinkedList<>();
|
||||
Set<Class<? extends Annotation>> accessed = new HashSet<>();
|
||||
final Set<Class<? extends Annotation>> accessed = new HashSet<>();
|
||||
accessed.add(this.annotationType());
|
||||
ResolvedAnnotationMapping sourceMapping = this.source;
|
||||
while (Objects.nonNull(sourceMapping)) {
|
||||
@ -444,7 +443,7 @@ public class ResolvedAnnotationMapping implements AnnotationMapping<Annotation>
|
||||
* 更新需要覆写的属性的相关映射关系,若该属性存在别名,则将别名的映射关系一并覆写
|
||||
*/
|
||||
private void overwriteAttribute(
|
||||
final ResolvedAnnotationMapping overwriteMapping, final int overwriteIndex, final int targetIndex, boolean overwriteAliases) {
|
||||
final ResolvedAnnotationMapping overwriteMapping, final int overwriteIndex, final int targetIndex, final boolean overwriteAliases) {
|
||||
// 若目标属性已被覆写,则不允许再次覆写
|
||||
if (isOverwrittenAttribute(targetIndex)) {
|
||||
return;
|
||||
@ -463,7 +462,7 @@ public class ResolvedAnnotationMapping implements AnnotationMapping<Annotation>
|
||||
/**
|
||||
* 判断该属性是否已被覆写
|
||||
*/
|
||||
private boolean isOverwrittenAttribute(int index) {
|
||||
private boolean isOverwrittenAttribute(final int index) {
|
||||
// 若属性未发生过解析,则必然未被覆写
|
||||
return NOT_FOUND_INDEX != resolvedAttributes[index]
|
||||
// 若属性发生过解析,且指向其他实例,则说明已被覆写
|
||||
@ -567,14 +566,14 @@ public class ResolvedAnnotationMapping implements AnnotationMapping<Annotation>
|
||||
* @return 是否
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
public boolean equals(final Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
ResolvedAnnotationMapping that = (ResolvedAnnotationMapping)o;
|
||||
final ResolvedAnnotationMapping that = (ResolvedAnnotationMapping)o;
|
||||
return resolved == that.resolved && annotation.equals(that.annotation);
|
||||
}
|
||||
|
||||
@ -671,8 +670,8 @@ public class ResolvedAnnotationMapping implements AnnotationMapping<Annotation>
|
||||
/**
|
||||
* 遍历下标
|
||||
*/
|
||||
void forEach(IntConsumer consumer) {
|
||||
for (int index : indexes) {
|
||||
void forEach(final IntConsumer consumer) {
|
||||
for (final int index : indexes) {
|
||||
consumer.accept(index);
|
||||
}
|
||||
}
|
||||
|
@ -116,8 +116,11 @@ public class BeanUtil {
|
||||
if (ClassUtil.isNormalClass(clazz)) {
|
||||
for (final Method method : clazz.getMethods()) {
|
||||
if (method.getParameterCount() == 0) {
|
||||
if (method.getName().startsWith("get") || method.getName().startsWith("is")) {
|
||||
return true;
|
||||
final String name = method.getName();
|
||||
if (name.startsWith("get") || name.startsWith("is")) {
|
||||
if(false == "getClass".equals(name)){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,8 +3,8 @@ package cn.hutool.core.bean;
|
||||
import cn.hutool.core.annotation.AnnotationUtil;
|
||||
import cn.hutool.core.annotation.PropIgnore;
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.lang.func.LambdaUtil;
|
||||
import cn.hutool.core.reflect.FieldUtil;
|
||||
import cn.hutool.core.reflect.MethodUtil;
|
||||
import cn.hutool.core.reflect.ModifierUtil;
|
||||
import cn.hutool.core.reflect.ReflectUtil;
|
||||
import cn.hutool.core.reflect.TypeUtil;
|
||||
@ -153,7 +153,8 @@ public class PropDesc {
|
||||
*/
|
||||
public Object getValue(final Object bean) {
|
||||
if (null != this.getter) {
|
||||
return MethodUtil.invoke(bean, this.getter);
|
||||
//return MethodUtil.invoke(bean, this.getter);
|
||||
return LambdaUtil.buildGetter(this.getter).apply(bean);
|
||||
} else if (ModifierUtil.isPublic(this.field)) {
|
||||
return FieldUtil.getFieldValue(bean, this.field);
|
||||
}
|
||||
@ -224,7 +225,8 @@ public class PropDesc {
|
||||
*/
|
||||
public PropDesc setValue(final Object bean, final Object value) {
|
||||
if (null != this.setter) {
|
||||
MethodUtil.invoke(bean, this.setter, value);
|
||||
//MethodUtil.invoke(bean, this.setter, value);
|
||||
LambdaUtil.buildSetter(this.setter).accept(bean, value);
|
||||
} else if (ModifierUtil.isPublic(this.field)) {
|
||||
FieldUtil.setFieldValue(bean, this.field, value);
|
||||
}
|
||||
|
@ -20,6 +20,13 @@ public abstract class AbsCopier<S, T> implements Copier<T> {
|
||||
*/
|
||||
protected final CopyOptions copyOptions;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param source 源对象
|
||||
* @param target 目标对象
|
||||
* @param copyOptions 拷贝选项
|
||||
*/
|
||||
public AbsCopier(final S source, final T target, final CopyOptions copyOptions) {
|
||||
this.source = source;
|
||||
this.target = target;
|
||||
|
@ -115,17 +115,6 @@ public class MapToBeanCopier<T> extends AbsCopier<Map<?, ?>, T> {
|
||||
// 转驼峰尝试查找
|
||||
sKeyStr = StrUtil.toCamelCase(sKeyStr);
|
||||
propDesc = targetPropDescMap.get(sKeyStr);
|
||||
if(null != propDesc){
|
||||
return propDesc;
|
||||
}
|
||||
|
||||
// boolean类型参数名转换尝试查找
|
||||
if(sKeyStr.startsWith("is")){
|
||||
sKeyStr = StrUtil.removePreAndLowerFirst(sKeyStr, 2);
|
||||
propDesc = targetPropDescMap.get(sKeyStr);
|
||||
return propDesc;
|
||||
}
|
||||
|
||||
return null;
|
||||
return propDesc;
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
package cn.hutool.core.cache.file;
|
||||
|
||||
import cn.hutool.core.cache.Cache;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.io.file.FileUtil;
|
||||
import cn.hutool.core.io.IORuntimeException;
|
||||
|
||||
import java.io.File;
|
||||
|
@ -1,7 +1,7 @@
|
||||
package cn.hutool.core.classloader;
|
||||
|
||||
import cn.hutool.core.exceptions.UtilException;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.io.file.FileUtil;
|
||||
import cn.hutool.core.net.url.URLUtil;
|
||||
import cn.hutool.core.reflect.MethodUtil;
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
package cn.hutool.core.codec;
|
||||
|
||||
import cn.hutool.core.codec.BaseN.Base16Codec;
|
||||
import cn.hutool.core.codec.binary.Base16Codec;
|
||||
import cn.hutool.core.exceptions.UtilException;
|
||||
import cn.hutool.core.util.CharUtil;
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
@ -31,7 +31,7 @@ public class HexUtil {
|
||||
* @return 是否为16进制
|
||||
*/
|
||||
public static boolean isHexNumber(final String value) {
|
||||
if(StrUtil.startWith(value, '-')){
|
||||
if (StrUtil.startWith(value, '-')) {
|
||||
// issue#2875
|
||||
return false;
|
||||
}
|
||||
@ -39,7 +39,7 @@ public class HexUtil {
|
||||
if (value.startsWith("0x", index) || value.startsWith("0X", index)) {
|
||||
index += 2;
|
||||
} else if (value.startsWith("#", index)) {
|
||||
index ++;
|
||||
index++;
|
||||
}
|
||||
try {
|
||||
new BigInteger(value.substring(index), 16);
|
||||
@ -253,7 +253,7 @@ public class HexUtil {
|
||||
* 转换的字符串如果u后不足4位,则前面用0填充,例如:
|
||||
*
|
||||
* <pre>
|
||||
* '你' =》\u4f60
|
||||
* 你 =》 \u4f60
|
||||
* </pre>
|
||||
*
|
||||
* @param value int值,也可以是char
|
||||
@ -278,7 +278,7 @@ public class HexUtil {
|
||||
* 转换的字符串如果u后不足4位,则前面用0填充,例如:
|
||||
*
|
||||
* <pre>
|
||||
* '你' =》'\u4f60'
|
||||
* 你 =》 \u4f60
|
||||
* </pre>
|
||||
*
|
||||
* @param ch char值
|
||||
|
@ -1,6 +1,6 @@
|
||||
package cn.hutool.core.codec;
|
||||
|
||||
import cn.hutool.core.codec.BaseN.Base16Codec;
|
||||
import cn.hutool.core.codec.binary.Base16Codec;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.text.CharPool;
|
||||
import cn.hutool.core.text.StrUtil;
|
||||
|
@ -1,4 +1,4 @@
|
||||
package cn.hutool.core.codec.BaseN;
|
||||
package cn.hutool.core.codec.binary;
|
||||
|
||||
import cn.hutool.core.codec.Decoder;
|
||||
import cn.hutool.core.codec.Encoder;
|
||||
@ -81,7 +81,7 @@ public class Base16Codec implements Encoder<byte[], char[]>, Decoder<CharSequenc
|
||||
* 转换的字符串如果u后不足4位,则前面用0填充,例如:
|
||||
*
|
||||
* <pre>
|
||||
* '你' =》'\u4f60'
|
||||
* 你 =》\u4f60
|
||||
* </pre>
|
||||
*
|
||||
* @param ch char值
|
@ -1,4 +1,4 @@
|
||||
package cn.hutool.core.codec.BaseN;
|
||||
package cn.hutool.core.codec.binary;
|
||||
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
import cn.hutool.core.text.StrUtil;
|
@ -1,4 +1,4 @@
|
||||
package cn.hutool.core.codec.BaseN;
|
||||
package cn.hutool.core.codec.binary;
|
||||
|
||||
import cn.hutool.core.codec.Decoder;
|
||||
import cn.hutool.core.codec.Encoder;
|
||||
@ -7,7 +7,7 @@ import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Base32 - encodes and decodes RFC4648 Base32<br>
|
||||
* (see <a href="https://datatracker.ietf.org/doc/html/rfc4648#section-6">https://datatracker.ietf.org/doc/html/rfc4648#section-6</a> )<br>
|
||||
* (see <a href="https://datatracker.ietf.org/doc/html/rfc4648#section-6">https://datatracker.ietf.org/doc/html/rfc4648#section-6</a> )<br>
|
||||
* base32就是用32(2的5次方)个特定ASCII码来表示256个ASCII码。<br>
|
||||
* 所以,5个ASCII字符经过base32编码后会变为8个字符(公约数为40),长度增加3/5.不足8n用“=”补足。<br>
|
||||
* 根据RFC4648 Base32规范,支持两种模式:
|
||||
@ -21,6 +21,9 @@ import java.util.Arrays;
|
||||
*/
|
||||
public class Base32Codec implements Encoder<byte[], String>, Decoder<CharSequence, byte[]> {
|
||||
|
||||
/**
|
||||
* 单例对象
|
||||
*/
|
||||
public static Base32Codec INSTANCE = new Base32Codec();
|
||||
|
||||
@Override
|
||||
@ -66,7 +69,13 @@ public class Base32Codec implements Encoder<byte[], String>, Decoder<CharSequenc
|
||||
private static final Character DEFAULT_PAD = '=';
|
||||
private static final int[] BASE32_FILL = {-1, 4, 1, 6, 3};
|
||||
|
||||
/**
|
||||
* 编码器
|
||||
*/
|
||||
public static final Base32Encoder ENCODER = new Base32Encoder(DEFAULT_ALPHABET, DEFAULT_PAD);
|
||||
/**
|
||||
* 16进制编码器
|
||||
*/
|
||||
public static final Base32Encoder HEX_ENCODER = new Base32Encoder(HEX_ALPHABET, DEFAULT_PAD);
|
||||
|
||||
private final char[] alphabet;
|
||||
@ -142,7 +151,13 @@ public class Base32Codec implements Encoder<byte[], String>, Decoder<CharSequenc
|
||||
public static class Base32Decoder implements Decoder<CharSequence, byte[]> {
|
||||
private static final char BASE_CHAR = '0';
|
||||
|
||||
/**
|
||||
* 解码器
|
||||
*/
|
||||
public static final Base32Decoder DECODER = new Base32Decoder(Base32Encoder.DEFAULT_ALPHABET);
|
||||
/**
|
||||
* 16进制解码器
|
||||
*/
|
||||
public static final Base32Decoder HEX_DECODER = new Base32Decoder(Base32Encoder.HEX_ALPHABET);
|
||||
|
||||
private final byte[] lookupTable;
|
||||
@ -163,7 +178,7 @@ public class Base32Codec implements Encoder<byte[], String>, Decoder<CharSequenc
|
||||
c = alphabet.charAt(i);
|
||||
lookupTable[c - BASE_CHAR] = (byte) i;
|
||||
// 支持小写字母解码
|
||||
if(c >= 'A' && c <= 'Z'){
|
||||
if (c >= 'A' && c <= 'Z') {
|
||||
lookupTable[Character.toLowerCase(c) - BASE_CHAR] = (byte) i;
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package cn.hutool.core.codec.BaseN;
|
||||
package cn.hutool.core.codec.binary;
|
||||
|
||||
import cn.hutool.core.exceptions.UtilException;
|
||||
import cn.hutool.core.exceptions.ValidateException;
|
@ -1,4 +1,4 @@
|
||||
package cn.hutool.core.codec.BaseN;
|
||||
package cn.hutool.core.codec.binary;
|
||||
|
||||
import cn.hutool.core.codec.Decoder;
|
||||
import cn.hutool.core.codec.Encoder;
|
||||
@ -15,6 +15,9 @@ import java.util.Arrays;
|
||||
*/
|
||||
public class Base58Codec implements Encoder<byte[], String>, Decoder<CharSequence, byte[]> {
|
||||
|
||||
/**
|
||||
* 单例
|
||||
*/
|
||||
public static Base58Codec INSTANCE = new Base58Codec();
|
||||
|
||||
/**
|
||||
@ -48,6 +51,9 @@ public class Base58Codec implements Encoder<byte[], String>, Decoder<CharSequenc
|
||||
public static class Base58Encoder implements Encoder<byte[], String> {
|
||||
private static final String DEFAULT_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
||||
|
||||
/**
|
||||
* 编码器
|
||||
*/
|
||||
public static final Base58Encoder ENCODER = new Base58Encoder(DEFAULT_ALPHABET.toCharArray());
|
||||
|
||||
private final char[] alphabet;
|
||||
@ -105,6 +111,9 @@ public class Base58Codec implements Encoder<byte[], String>, Decoder<CharSequenc
|
||||
*/
|
||||
public static class Base58Decoder implements Decoder<CharSequence, byte[]> {
|
||||
|
||||
/**
|
||||
* 解码器
|
||||
*/
|
||||
public static Base58Decoder DECODER = new Base58Decoder(Base58Encoder.DEFAULT_ALPHABET);
|
||||
|
||||
private final byte[] lookupTable;
|
@ -1,6 +1,6 @@
|
||||
package cn.hutool.core.codec.BaseN;
|
||||
package cn.hutool.core.codec.binary;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.io.file.FileUtil;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
import cn.hutool.core.text.StrUtil;
|
@ -1,4 +1,4 @@
|
||||
package cn.hutool.core.codec.BaseN;
|
||||
package cn.hutool.core.codec.binary;
|
||||
|
||||
import cn.hutool.core.codec.Decoder;
|
||||
import cn.hutool.core.codec.Encoder;
|
||||
@ -20,6 +20,9 @@ public class Base62Codec implements Encoder<byte[], byte[]>, Decoder<byte[], byt
|
||||
private static final int STANDARD_BASE = 256;
|
||||
private static final int TARGET_BASE = 62;
|
||||
|
||||
/**
|
||||
* 单例
|
||||
*/
|
||||
public static Base62Codec INSTANCE = new Base62Codec();
|
||||
|
||||
/**
|
||||
@ -102,7 +105,13 @@ public class Base62Codec implements Encoder<byte[], byte[]>, Decoder<byte[], byt
|
||||
'U', 'V', 'W', 'X', 'Y', 'Z' //
|
||||
};
|
||||
|
||||
/**
|
||||
* GMP风格编码器
|
||||
*/
|
||||
public static Base62Encoder GMP_ENCODER = new Base62Encoder(GMP);
|
||||
/**
|
||||
* 反转风格,即将GMP风格中的大小写做转换编码器
|
||||
*/
|
||||
public static Base62Encoder INVERTED_ENCODER = new Base62Encoder(INVERTED);
|
||||
|
||||
private final byte[] alphabet;
|
||||
@ -130,7 +139,13 @@ public class Base62Codec implements Encoder<byte[], byte[]>, Decoder<byte[], byt
|
||||
*/
|
||||
public static class Base62Decoder implements Decoder<byte[], byte[]> {
|
||||
|
||||
/**
|
||||
* GMP风格解码器
|
||||
*/
|
||||
public static Base62Decoder GMP_DECODER = new Base62Decoder(Base62Encoder.GMP);
|
||||
/**
|
||||
* 反转风格,即将GMP风格中的大小写做转换解码器
|
||||
*/
|
||||
public static Base62Decoder INVERTED_DECODER = new Base62Decoder(Base62Encoder.INVERTED);
|
||||
|
||||
private final byte[] lookupTable;
|
@ -1,6 +1,6 @@
|
||||
package cn.hutool.core.codec.BaseN;
|
||||
package cn.hutool.core.codec.binary;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.io.file.FileUtil;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
import cn.hutool.core.text.StrUtil;
|
@ -1,4 +1,4 @@
|
||||
package cn.hutool.core.codec.BaseN;
|
||||
package cn.hutool.core.codec.binary;
|
||||
|
||||
import cn.hutool.core.codec.Decoder;
|
||||
import cn.hutool.core.lang.mutable.MutableInt;
|
||||
@ -13,6 +13,9 @@ import cn.hutool.core.util.ArrayUtil;
|
||||
*/
|
||||
public class Base64Decoder implements Decoder<byte[], byte[]> {
|
||||
|
||||
/**
|
||||
* 单例对象
|
||||
*/
|
||||
public static Base64Decoder INSTANCE = new Base64Decoder();
|
||||
|
||||
private static final byte PADDING = -2;
|
@ -0,0 +1,11 @@
|
||||
/**
|
||||
* BaseN编码解码,提供将bytes和baseN的编码转换功能。
|
||||
* <ul>
|
||||
* <li>Base16</li>
|
||||
* <li>Base32</li>
|
||||
* <li>Base58</li>
|
||||
* <li>Base62</li>
|
||||
* <li>Base64</li>
|
||||
* </ul>
|
||||
*/
|
||||
package cn.hutool.core.codec.binary;
|
@ -17,6 +17,9 @@ import java.util.Arrays;
|
||||
* @since 5.2.5
|
||||
*/
|
||||
public class CityHash implements Hash32<byte[]>, Hash64<byte[]>, Hash128<byte[]>{
|
||||
/**
|
||||
* 单例
|
||||
*/
|
||||
public static CityHash INSTANCE = new CityHash();
|
||||
|
||||
// Some primes between 2^63 and 2^64 for various uses.
|
||||
@ -50,46 +53,46 @@ public class CityHash implements Hash32<byte[]>, Hash64<byte[]>, Hash128<byte[]>
|
||||
|
||||
// len > 24
|
||||
int h = len, g = c1 * len, f = g;
|
||||
int a0 = rotate32(fetch32(data, len - 4) * c1, 17) * c2;
|
||||
int a1 = rotate32(fetch32(data, len - 8) * c1, 17) * c2;
|
||||
int a2 = rotate32(fetch32(data, len - 16) * c1, 17) * c2;
|
||||
int a3 = rotate32(fetch32(data, len - 12) * c1, 17) * c2;
|
||||
int a4 = rotate32(fetch32(data, len - 20) * c1, 17) * c2;
|
||||
int a0 = Integer.rotateRight(fetch32(data, len - 4) * c1, 17) * c2;
|
||||
int a1 = Integer.rotateRight(fetch32(data, len - 8) * c1, 17) * c2;
|
||||
int a2 = Integer.rotateRight(fetch32(data, len - 16) * c1, 17) * c2;
|
||||
int a3 = Integer.rotateRight(fetch32(data, len - 12) * c1, 17) * c2;
|
||||
int a4 = Integer.rotateRight(fetch32(data, len - 20) * c1, 17) * c2;
|
||||
h ^= a0;
|
||||
h = rotate32(h, 19);
|
||||
h = Integer.rotateRight(h, 19);
|
||||
h = h * 5 + 0xe6546b64;
|
||||
h ^= a2;
|
||||
h = rotate32(h, 19);
|
||||
h = Integer.rotateRight(h, 19);
|
||||
h = h * 5 + 0xe6546b64;
|
||||
g ^= a1;
|
||||
g = rotate32(g, 19);
|
||||
g = Integer.rotateRight(g, 19);
|
||||
g = g * 5 + 0xe6546b64;
|
||||
g ^= a3;
|
||||
g = rotate32(g, 19);
|
||||
g = Integer.rotateRight(g, 19);
|
||||
g = g * 5 + 0xe6546b64;
|
||||
f += a4;
|
||||
f = rotate32(f, 19);
|
||||
f = Integer.rotateRight(f, 19);
|
||||
f = f * 5 + 0xe6546b64;
|
||||
int iters = (len - 1) / 20;
|
||||
|
||||
int pos = 0;
|
||||
do {
|
||||
a0 = rotate32(fetch32(data, pos) * c1, 17) * c2;
|
||||
a0 = Integer.rotateRight(fetch32(data, pos) * c1, 17) * c2;
|
||||
a1 = fetch32(data, pos + 4);
|
||||
a2 = rotate32(fetch32(data, pos + 8) * c1, 17) * c2;
|
||||
a3 = rotate32(fetch32(data, pos + 12) * c1, 17) * c2;
|
||||
a2 = Integer.rotateRight(fetch32(data, pos + 8) * c1, 17) * c2;
|
||||
a3 = Integer.rotateRight(fetch32(data, pos + 12) * c1, 17) * c2;
|
||||
a4 = fetch32(data, pos + 16);
|
||||
h ^= a0;
|
||||
h = rotate32(h, 18);
|
||||
h = Integer.rotateRight(h, 18);
|
||||
h = h * 5 + 0xe6546b64;
|
||||
f += a1;
|
||||
f = rotate32(f, 19);
|
||||
f = Integer.rotateRight(f, 19);
|
||||
f = f * c1;
|
||||
g += a2;
|
||||
g = rotate32(g, 18);
|
||||
g = Integer.rotateRight(g, 18);
|
||||
g = g * 5 + 0xe6546b64;
|
||||
h ^= a3 + a1;
|
||||
h = rotate32(h, 19);
|
||||
h = Integer.rotateRight(h, 19);
|
||||
h = h * 5 + 0xe6546b64;
|
||||
g ^= a4;
|
||||
g = Integer.reverseBytes(g) * 5;
|
||||
@ -104,16 +107,16 @@ public class CityHash implements Hash32<byte[]>, Hash64<byte[]>, Hash128<byte[]>
|
||||
pos += 20;
|
||||
} while (--iters != 0);
|
||||
|
||||
g = rotate32(g, 11) * c1;
|
||||
g = rotate32(g, 17) * c1;
|
||||
f = rotate32(f, 11) * c1;
|
||||
f = rotate32(f, 17) * c1;
|
||||
h = rotate32(h + g, 19);
|
||||
g = Integer.rotateRight(g, 11) * c1;
|
||||
g = Integer.rotateRight(g, 17) * c1;
|
||||
f = Integer.rotateRight(f, 11) * c1;
|
||||
f = Integer.rotateRight(f, 17) * c1;
|
||||
h = Integer.rotateRight(h + g, 19);
|
||||
h = h * 5 + 0xe6546b64;
|
||||
h = rotate32(h, 17) * c1;
|
||||
h = rotate32(h + f, 19);
|
||||
h = Integer.rotateRight(h, 17) * c1;
|
||||
h = Integer.rotateRight(h + f, 19);
|
||||
h = h * 5 + 0xe6546b64;
|
||||
h = rotate32(h, 17) * c1;
|
||||
h = Integer.rotateRight(h, 17) * c1;
|
||||
return h;
|
||||
}
|
||||
|
||||
@ -149,11 +152,11 @@ public class CityHash implements Hash32<byte[]>, Hash64<byte[]>, Hash128<byte[]>
|
||||
len = (len - 1) & ~63;
|
||||
int pos = 0;
|
||||
do {
|
||||
x = rotate64(x + y + v.getLowValue() + fetch64(data, pos + 8), 37) * k1;
|
||||
y = rotate64(y + v.getHighValue() + fetch64(data, pos + 48), 42) * k1;
|
||||
x = Long.rotateRight(x + y + v.getLowValue() + fetch64(data, pos + 8), 37) * k1;
|
||||
y = Long.rotateRight(y + v.getHighValue() + fetch64(data, pos + 48), 42) * k1;
|
||||
x ^= w.getHighValue();
|
||||
y += v.getLowValue() + fetch64(data, pos + 40);
|
||||
z = rotate64(z + w.getLowValue(), 33) * k1;
|
||||
z = Long.rotateRight(z + w.getLowValue(), 33) * k1;
|
||||
v = weakHashLen32WithSeeds(data, pos, v.getHighValue() * k1, x + w.getLowValue());
|
||||
w = weakHashLen32WithSeeds(data, pos + 32, z + w.getHighValue(), y + fetch64(data, pos + 16));
|
||||
// swap z,x value
|
||||
@ -231,19 +234,19 @@ public class CityHash implements Hash32<byte[]>, Hash64<byte[]>, Hash128<byte[]>
|
||||
long x = seed.getLowValue();
|
||||
long y = seed.getHighValue();
|
||||
long z = len * k1;
|
||||
v.setLowValue(rotate64(y ^ k1, 49) * k1 + fetch64(byteArray, start));
|
||||
v.setHighValue(rotate64(v.getLowValue(), 42) * k1 + fetch64(byteArray, start + 8));
|
||||
w.setLowValue(rotate64(y + z, 35) * k1 + x);
|
||||
w.setHighValue(rotate64(x + fetch64(byteArray, start + 88), 53) * k1);
|
||||
v.setLowValue(Long.rotateRight(y ^ k1, 49) * k1 + fetch64(byteArray, start));
|
||||
v.setHighValue(Long.rotateRight(v.getLowValue(), 42) * k1 + fetch64(byteArray, start + 8));
|
||||
w.setLowValue(Long.rotateRight(y + z, 35) * k1 + x);
|
||||
w.setHighValue(Long.rotateRight(x + fetch64(byteArray, start + 88), 53) * k1);
|
||||
|
||||
// This is the same inner loop as CityHash64(), manually unrolled.
|
||||
int pos = start;
|
||||
do {
|
||||
x = rotate64(x + y + v.getLowValue() + fetch64(byteArray, pos + 8), 37) * k1;
|
||||
y = rotate64(y + v.getHighValue() + fetch64(byteArray, pos + 48), 42) * k1;
|
||||
x = Long.rotateRight(x + y + v.getLowValue() + fetch64(byteArray, pos + 8), 37) * k1;
|
||||
y = Long.rotateRight(y + v.getHighValue() + fetch64(byteArray, pos + 48), 42) * k1;
|
||||
x ^= w.getHighValue();
|
||||
y += v.getLowValue() + fetch64(byteArray, pos + 40);
|
||||
z = rotate64(z + w.getLowValue(), 33) * k1;
|
||||
z = Long.rotateRight(z + w.getLowValue(), 33) * k1;
|
||||
v = weakHashLen32WithSeeds(byteArray, pos, v.getHighValue() * k1, x + w.getLowValue());
|
||||
w = weakHashLen32WithSeeds(byteArray, pos + 32, z + w.getHighValue(), y + fetch64(byteArray, pos + 16));
|
||||
|
||||
@ -251,11 +254,11 @@ public class CityHash implements Hash32<byte[]>, Hash64<byte[]>, Hash128<byte[]>
|
||||
x = z;
|
||||
z = swapValue;
|
||||
pos += 64;
|
||||
x = rotate64(x + y + v.getLowValue() + fetch64(byteArray, pos + 8), 37) * k1;
|
||||
y = rotate64(y + v.getHighValue() + fetch64(byteArray, pos + 48), 42) * k1;
|
||||
x = Long.rotateRight(x + y + v.getLowValue() + fetch64(byteArray, pos + 8), 37) * k1;
|
||||
y = Long.rotateRight(y + v.getHighValue() + fetch64(byteArray, pos + 48), 42) * k1;
|
||||
x ^= w.getHighValue();
|
||||
y += v.getLowValue() + fetch64(byteArray, pos + 40);
|
||||
z = rotate64(z + w.getLowValue(), 33) * k1;
|
||||
z = Long.rotateRight(z + w.getLowValue(), 33) * k1;
|
||||
v = weakHashLen32WithSeeds(byteArray, pos, v.getHighValue() * k1, x + w.getLowValue());
|
||||
w = weakHashLen32WithSeeds(byteArray, pos + 32, z + w.getHighValue(), y + fetch64(byteArray, pos + 16));
|
||||
swapValue = x;
|
||||
@ -264,16 +267,16 @@ public class CityHash implements Hash32<byte[]>, Hash64<byte[]>, Hash128<byte[]>
|
||||
pos += 64;
|
||||
len -= 128;
|
||||
} while (len >= 128);
|
||||
x += rotate64(v.getLowValue() + z, 49) * k0;
|
||||
y = y * k0 + rotate64(w.getHighValue(), 37);
|
||||
z = z * k0 + rotate64(w.getLowValue(), 27);
|
||||
x += Long.rotateRight(v.getLowValue() + z, 49) * k0;
|
||||
y = y * k0 + Long.rotateRight(w.getHighValue(), 37);
|
||||
z = z * k0 + Long.rotateRight(w.getLowValue(), 27);
|
||||
w.setLowValue(w.getLowValue() * 9);
|
||||
v.setLowValue(v.getLowValue() * k0);
|
||||
|
||||
// If 0 < len < 128, hash up to 4 chunks of 32 bytes each from the end of s.
|
||||
for (int tail_done = 0; tail_done < len; ) {
|
||||
tail_done += 32;
|
||||
y = rotate64(x + y, 42) * k0 + v.getHighValue();
|
||||
y = Long.rotateRight(x + y, 42) * k0 + v.getHighValue();
|
||||
w.setLowValue(w.getLowValue() + fetch64(byteArray, pos + len - tail_done + 16));
|
||||
x = x * k0 + w.getLowValue();
|
||||
z += w.getHighValue() + fetch64(byteArray, pos + len - tail_done);
|
||||
@ -333,8 +336,8 @@ public class CityHash implements Hash32<byte[]>, Hash64<byte[]>, Hash128<byte[]>
|
||||
final long mul = k2 + len * 2L;
|
||||
final long a = fetch64(byteArray, 0) + k2;
|
||||
final long b = fetch64(byteArray, len - 8);
|
||||
final long c = rotate64(b, 37) * mul + a;
|
||||
final long d = (rotate64(a, 25) + b) * mul;
|
||||
final long c = Long.rotateRight(b, 37) * mul + a;
|
||||
final long d = (Long.rotateRight(a, 25) + b) * mul;
|
||||
return hashLen16(c, d, mul);
|
||||
}
|
||||
if (len >= 4) {
|
||||
@ -361,8 +364,8 @@ public class CityHash implements Hash32<byte[]>, Hash64<byte[]>, Hash128<byte[]>
|
||||
final long b = fetch64(byteArray, 8);
|
||||
final long c = fetch64(byteArray, len - 8) * mul;
|
||||
final long d = fetch64(byteArray, len - 16) * k2;
|
||||
return hashLen16(rotate64(a + b, 43) + rotate64(c, 30) + d,
|
||||
a + rotate64(b + k2, 18) + c, mul);
|
||||
return hashLen16(Long.rotateRight(a + b, 43) + Long.rotateRight(c, 30) + d,
|
||||
a + Long.rotateRight(b + k2, 18) + c, mul);
|
||||
}
|
||||
|
||||
private long hashLen33to64(final byte[] byteArray) {
|
||||
@ -376,10 +379,10 @@ public class CityHash implements Hash32<byte[]>, Hash64<byte[]>, Hash128<byte[]>
|
||||
final long f = fetch64(byteArray, 24) * 9;
|
||||
final long g = fetch64(byteArray, len - 8);
|
||||
final long h = fetch64(byteArray, len - 16) * mul;
|
||||
final long u = rotate64(a + g, 43) + (rotate64(b, 30) + c) * 9;
|
||||
final long u = Long.rotateRight(a + g, 43) + (Long.rotateRight(b, 30) + c) * 9;
|
||||
final long v = ((a + g) ^ d) + f + 1;
|
||||
final long w = Long.reverseBytes((u + v) * mul) + h;
|
||||
final long x = rotate64(e + f, 42) + c;
|
||||
final long x = Long.rotateRight(e + f, 42) + c;
|
||||
final long y = (Long.reverseBytes((v + w) * mul) + g) * mul;
|
||||
final long z = e + f + c;
|
||||
a = Long.reverseBytes((x + z) * mul + y) + b;
|
||||
@ -395,16 +398,6 @@ public class CityHash implements Hash32<byte[]>, Hash64<byte[]>, Hash128<byte[]>
|
||||
return ByteUtil.bytesToInt(byteArray, start, ByteUtil.CPU_ENDIAN);
|
||||
}
|
||||
|
||||
private static long rotate64(final long val, final int shift) {
|
||||
// Avoid shifting by 64: doing so yields an undefined result.
|
||||
return shift == 0 ? val : ((val >>> shift) | (val << (64 - shift)));
|
||||
}
|
||||
|
||||
private static int rotate32(final int val, final int shift) {
|
||||
// Avoid shifting by 32: doing so yields an undefined result.
|
||||
return shift == 0 ? val : ((val >>> shift) | (val << (32 - shift)));
|
||||
}
|
||||
|
||||
private static long hashLen16(final long u, final long v, final long mul) {
|
||||
// Murmur-inspired hashing.
|
||||
long a = (u ^ v) * mul;
|
||||
@ -446,21 +439,21 @@ public class CityHash implements Hash32<byte[]>, Hash64<byte[]>, Hash128<byte[]>
|
||||
private int mur(int a, int h) {
|
||||
// Helper from Murmur3 for combining two 32-bit values.
|
||||
a *= c1;
|
||||
a = rotate32(a, 17);
|
||||
a = Integer.rotateRight(a, 17);
|
||||
a *= c2;
|
||||
h ^= a;
|
||||
h = rotate32(h, 19);
|
||||
h = Integer.rotateRight(h, 19);
|
||||
return h * 5 + 0xe6546b64;
|
||||
}
|
||||
|
||||
private static Number128 weakHashLen32WithSeeds(
|
||||
final long w, final long x, final long y, final long z, long a, long b) {
|
||||
a += w;
|
||||
b = rotate64(b + a + z, 21);
|
||||
b = Long.rotateRight(b + a + z, 21);
|
||||
final long c = a;
|
||||
a += x;
|
||||
a += y;
|
||||
b += rotate64(a, 44);
|
||||
b += Long.rotateRight(a, 44);
|
||||
return new Number128(a + z, b + c);
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,8 @@
|
||||
package cn.hutool.core.codec.hash;
|
||||
|
||||
import cn.hutool.core.codec.Number128;
|
||||
import cn.hutool.core.codec.hash.metro.MetroHash128;
|
||||
import cn.hutool.core.codec.hash.metro.MetroHash64;
|
||||
|
||||
/**
|
||||
* Hash算法大全<br>
|
||||
@ -554,17 +556,7 @@ public class HashUtil {
|
||||
* @return hash值
|
||||
*/
|
||||
public static long metroHash64(final byte[] data, final long seed) {
|
||||
return MetroHash.INSTANCE.hash64(data, seed);
|
||||
}
|
||||
|
||||
/**
|
||||
* MetroHash 算法64-bit实现
|
||||
*
|
||||
* @param data 数据
|
||||
* @return hash值
|
||||
*/
|
||||
public static long metroHash64(final byte[] data) {
|
||||
return MetroHash.INSTANCE.hash64(data);
|
||||
return MetroHash64.of(seed).hash64(data);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -575,17 +567,7 @@ public class HashUtil {
|
||||
* @return hash值,long[0]:低位,long[1]:高位
|
||||
*/
|
||||
public static long[] metroHash128(final byte[] data, final long seed) {
|
||||
return MetroHash.INSTANCE.hash128(data, seed).getLongArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* MetroHash 算法128-bit实现
|
||||
*
|
||||
* @param data 数据
|
||||
* @return hash值,long[0]:低位,long[1]:高位
|
||||
*/
|
||||
public static long[] metroHash128(final byte[] data) {
|
||||
return MetroHash.INSTANCE.hash128(data).getLongArray();
|
||||
return MetroHash128.of(seed).hash128(data).getLongArray();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,237 +0,0 @@
|
||||
package cn.hutool.core.codec.hash;
|
||||
|
||||
import cn.hutool.core.codec.Number128;
|
||||
import cn.hutool.core.util.ByteUtil;
|
||||
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Apache 发布的MetroHash算法,是一组用于非加密用例的最先进的哈希函数。
|
||||
* 除了卓越的性能外,他们还以算法生成而著称。
|
||||
*
|
||||
* <p>
|
||||
* 官方实现:https://github.com/jandrewrogers/MetroHash
|
||||
* 官方文档:http://www.jandrewrogers.com/2015/05/27/metrohash/
|
||||
* Go语言实现:https://github.com/linvon/cuckoo-filter/blob/main/vendor/github.com/dgryski/go-metro/
|
||||
*
|
||||
* @author li
|
||||
*/
|
||||
public class MetroHash implements Hash64<byte[]>, Hash128<byte[]> {
|
||||
public static MetroHash INSTANCE = new MetroHash();
|
||||
|
||||
@Override
|
||||
public Number encode(final byte[] bytes) {
|
||||
return hash64(bytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long hash64(final byte[] data) {
|
||||
return hash64(data, 1337);
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算64位Hash值
|
||||
*
|
||||
* @param data 数据
|
||||
* @param seed 种子
|
||||
* @return hash64
|
||||
*/
|
||||
public long hash64(final byte[] data, final long seed) {
|
||||
final long k0_64 = 0xD6D018F5;
|
||||
final long k1_64 = 0xA2AA033B;
|
||||
final long k2_64 = 0x62992FC1;
|
||||
final long k3_64 = 0x30BC5B29;
|
||||
|
||||
byte[] buffer = data;
|
||||
long hash = (seed + k2_64) * k0_64;
|
||||
|
||||
long v0, v1, v2, v3;
|
||||
v0 = hash;
|
||||
v1 = hash;
|
||||
v2 = hash;
|
||||
v3 = hash;
|
||||
|
||||
if (buffer.length >= 32) {
|
||||
|
||||
while (buffer.length >= 32) {
|
||||
v0 += littleEndian64(buffer, 0) * k0_64;
|
||||
v0 = rotateLeft64(v0, -29) + v2;
|
||||
v1 += littleEndian64(buffer, 8) * k1_64;
|
||||
v1 = rotateLeft64(v1, -29) + v3;
|
||||
v2 += littleEndian64(buffer, 24) * k2_64;
|
||||
v2 = rotateLeft64(v2, -29) + v0;
|
||||
v3 += littleEndian64(buffer, 32) * k3_64;
|
||||
v3 = rotateLeft64(v3, -29) + v1;
|
||||
buffer = Arrays.copyOfRange(buffer, 32, buffer.length);
|
||||
}
|
||||
|
||||
v2 ^= rotateLeft64(((v0 + v3) * k0_64) + v1, -37) * k1_64;
|
||||
v3 ^= rotateLeft64(((v1 + v2) * k1_64) + v0, -37) * k0_64;
|
||||
v0 ^= rotateLeft64(((v0 + v2) * k0_64) + v3, -37) * k1_64;
|
||||
v1 ^= rotateLeft64(((v1 + v3) * k1_64) + v2, -37) * k0_64;
|
||||
hash += v0 ^ v1;
|
||||
}
|
||||
|
||||
if (buffer.length >= 16) {
|
||||
v0 = hash + littleEndian64(buffer, 0) * k2_64;
|
||||
v0 = rotateLeft64(v0, -29) * k3_64;
|
||||
v1 = hash + littleEndian64(buffer, 8) * k2_64;
|
||||
v1 = rotateLeft64(v1, -29) * k3_64;
|
||||
v0 ^= rotateLeft64(v0 * k0_64, -21) + v1;
|
||||
v1 ^= rotateLeft64(v1 * k3_64, -21) + v0;
|
||||
hash += v1;
|
||||
buffer = Arrays.copyOfRange(buffer, 16, buffer.length);
|
||||
}
|
||||
|
||||
if (buffer.length >= 8) {
|
||||
hash += littleEndian64(buffer, 0) * k3_64;
|
||||
buffer = Arrays.copyOfRange(buffer, 8, buffer.length);
|
||||
hash ^= rotateLeft64(hash, -55) * k1_64;
|
||||
}
|
||||
|
||||
if (buffer.length >= 4) {
|
||||
hash += (long) littleEndian32(Arrays.copyOfRange(buffer, 0, 4)) * k3_64;
|
||||
hash ^= rotateLeft64(hash, -26) * k1_64;
|
||||
buffer = Arrays.copyOfRange(buffer, 4, buffer.length);
|
||||
}
|
||||
|
||||
if (buffer.length >= 2) {
|
||||
hash += (long) littleEndian16(Arrays.copyOfRange(buffer, 0, 2)) * k3_64;
|
||||
buffer = Arrays.copyOfRange(buffer, 2, buffer.length);
|
||||
hash ^= rotateLeft64(hash, -48) * k1_64;
|
||||
}
|
||||
|
||||
if (buffer.length >= 1) {
|
||||
hash += (long) buffer[0] * k3_64;
|
||||
hash ^= rotateLeft64(hash, -38) * k1_64;
|
||||
}
|
||||
|
||||
hash ^= rotateLeft64(hash, -28);
|
||||
hash *= k0_64;
|
||||
hash ^= rotateLeft64(hash, -29);
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Number128 hash128(final byte[] data) {
|
||||
return hash128(data, 1337);
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算128位hash值
|
||||
*
|
||||
* @param data 数据
|
||||
* @param seed 种子
|
||||
* @return hash128
|
||||
*/
|
||||
public Number128 hash128(final byte[] data, final long seed) {
|
||||
final long k0_128 = 0xC83A91E1;
|
||||
final long k1_128 = 0x8648DBDB;
|
||||
final long k2_128 = 0x7BDEC03B;
|
||||
final long k3_128 = 0x2F5870A5;
|
||||
|
||||
byte[] buffer = data;
|
||||
|
||||
long v0, v1, v2, v3;
|
||||
|
||||
v0 = (seed - k0_128) * k3_128;
|
||||
v1 = (seed + k1_128) * k2_128;
|
||||
|
||||
if (buffer.length >= 32) {
|
||||
v2 = (seed + k0_128) * k2_128;
|
||||
v3 = (seed - k1_128) * k3_128;
|
||||
|
||||
while (buffer.length >= 32) {
|
||||
v0 += littleEndian64(buffer, 0) * k0_128;
|
||||
buffer = Arrays.copyOfRange(buffer, 8, buffer.length);
|
||||
v0 = rotateRight(v0, 29) + v2;
|
||||
v1 += littleEndian64(buffer, 0) * k1_128;
|
||||
buffer = Arrays.copyOfRange(buffer, 8, buffer.length);
|
||||
v1 = rotateRight(v1, 29) + v3;
|
||||
v2 += littleEndian64(buffer, 0) * k2_128;
|
||||
buffer = Arrays.copyOfRange(buffer, 8, buffer.length);
|
||||
v2 = rotateRight(v2, 29) + v0;
|
||||
v3 = littleEndian64(buffer, 0) * k3_128;
|
||||
buffer = Arrays.copyOfRange(buffer, 8, buffer.length);
|
||||
v3 = rotateRight(v3, 29) + v1;
|
||||
}
|
||||
|
||||
v2 ^= rotateRight(((v0 + v3) * k0_128) + v1, 21) * k1_128;
|
||||
v3 ^= rotateRight(((v1 + v2) * k1_128) + v0, 21) * k0_128;
|
||||
v0 ^= rotateRight(((v0 + v2) * k0_128) + v3, 21) * k1_128;
|
||||
v1 ^= rotateRight(((v1 + v3) * k1_128) + v2, 21) * k0_128;
|
||||
}
|
||||
|
||||
if (buffer.length >= 16) {
|
||||
v0 += littleEndian64(buffer, 0) * k2_128;
|
||||
buffer = Arrays.copyOfRange(buffer, 8, buffer.length);
|
||||
v0 = rotateRight(v0, 33) * k3_128;
|
||||
v1 += littleEndian64(buffer, 0) * k2_128;
|
||||
buffer = Arrays.copyOfRange(buffer, 8, buffer.length);
|
||||
v1 = rotateRight(v1, 33) * k3_128;
|
||||
v0 ^= rotateRight((v0 * k2_128) + v1, 45) + k1_128;
|
||||
v1 ^= rotateRight((v1 * k3_128) + v0, 45) + k0_128;
|
||||
}
|
||||
|
||||
if (buffer.length >= 8) {
|
||||
v0 += littleEndian64(buffer, 0) * k2_128;
|
||||
buffer = Arrays.copyOfRange(buffer, 8, buffer.length);
|
||||
v0 = rotateRight(v0, 33) * k3_128;
|
||||
v0 ^= rotateRight((v0 * k2_128) + v1, 27) * k1_128;
|
||||
}
|
||||
|
||||
if (buffer.length >= 4) {
|
||||
v1 += (long) littleEndian32(buffer) * k2_128;
|
||||
buffer = Arrays.copyOfRange(buffer, 4, buffer.length);
|
||||
v1 = rotateRight(v1, 33) * k3_128;
|
||||
v1 ^= rotateRight((v1 * k3_128) + v0, 46) * k0_128;
|
||||
}
|
||||
|
||||
if (buffer.length >= 2) {
|
||||
v0 += (long) littleEndian16(buffer) * k2_128;
|
||||
buffer = Arrays.copyOfRange(buffer, 2, buffer.length);
|
||||
v0 = rotateRight(v0, 33) * k3_128;
|
||||
v0 ^= rotateRight((v0 * k2_128) * v1, 22) * k1_128;
|
||||
}
|
||||
|
||||
if (buffer.length >= 1) {
|
||||
v1 += (long) buffer[0] * k2_128;
|
||||
v1 = rotateRight(v1, 33) * k3_128;
|
||||
v1 ^= rotateRight((v1 * k3_128) + v0, 58) * k0_128;
|
||||
}
|
||||
|
||||
v0 += rotateRight((v0 * k0_128) + v1, 13);
|
||||
v1 += rotateRight((v1 * k1_128) + v0, 37);
|
||||
v0 += rotateRight((v0 * k2_128) + v1, 13);
|
||||
v1 += rotateRight((v1 * k3_128) + v0, 37);
|
||||
|
||||
return new Number128(v0, v1);
|
||||
}
|
||||
|
||||
|
||||
// region =========== Private methods
|
||||
private static long littleEndian64(final byte[] b, final int start) {
|
||||
return ByteUtil.bytesToLong(b, start, ByteOrder.LITTLE_ENDIAN);
|
||||
}
|
||||
|
||||
private static int littleEndian32(final byte[] b) {
|
||||
return (int) b[0] | (int) b[1] << 8 | (int) b[2] << 16 | (int) b[3] << 24;
|
||||
}
|
||||
|
||||
private static int littleEndian16(final byte[] b) {
|
||||
return ByteUtil.bytesToShort(b, ByteOrder.LITTLE_ENDIAN);
|
||||
}
|
||||
|
||||
private static long rotateLeft64(final long x, final int k) {
|
||||
final int n = 64;
|
||||
final int s = k & (n - 1);
|
||||
return x << s | x >> (n - s);
|
||||
}
|
||||
|
||||
private static long rotateRight(final long val, final int shift) {
|
||||
return (val >> shift) | (val << (64 - shift));
|
||||
}
|
||||
// endregion =========== Private methods
|
||||
}
|
@ -21,6 +21,9 @@ import java.nio.charset.Charset;
|
||||
* @since 4.3.3
|
||||
*/
|
||||
public class MurmurHash implements Hash32<byte[]>, Hash64<byte[]>, Hash128<byte[]>{
|
||||
/**
|
||||
* 单例
|
||||
*/
|
||||
public static final MurmurHash INSTANCE = new MurmurHash();
|
||||
|
||||
// Constants for 32 bit variant
|
||||
|
@ -0,0 +1,71 @@
|
||||
package cn.hutool.core.codec.hash.metro;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Apache 发布的MetroHash算法抽象实现,是一组用于非加密用例的最先进的哈希函数。
|
||||
* 除了卓越的性能外,他们还以算法生成而著称。
|
||||
*
|
||||
* <p>
|
||||
* 官方实现:https://github.com/jandrewrogers/MetroHash
|
||||
* 官方文档:http://www.jandrewrogers.com/2015/05/27/metrohash/
|
||||
* 来自:https://github.com/postamar/java-metrohash/
|
||||
*
|
||||
* @author Marius Posta
|
||||
* @param <R> 返回值类型,为this类型
|
||||
*/
|
||||
public abstract class AbstractMetroHash<R extends AbstractMetroHash<R>> implements MetroHash<R> {
|
||||
|
||||
final long seed;
|
||||
long v0, v1, v2, v3;
|
||||
long nChunks;
|
||||
|
||||
/**
|
||||
* 使用指定种子构造
|
||||
*
|
||||
* @param seed 种子
|
||||
*/
|
||||
public AbstractMetroHash(final long seed) {
|
||||
this.seed = seed;
|
||||
reset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public R apply(final ByteBuffer input) {
|
||||
reset();
|
||||
while (input.remaining() >= 32) {
|
||||
partialApply32ByteChunk(input);
|
||||
}
|
||||
return partialApplyRemaining(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从byteBuffer中计算32-byte块并更新hash状态
|
||||
*
|
||||
* @param partialInput byte buffer,至少有32byte的数据
|
||||
* @return this
|
||||
*/
|
||||
abstract R partialApply32ByteChunk(ByteBuffer partialInput);
|
||||
|
||||
/**
|
||||
* 从byteBuffer中计算剩余bytes并更新hash状态
|
||||
*
|
||||
* @param partialInput byte buffer,少于32byte的数据
|
||||
* @return this
|
||||
*/
|
||||
abstract R partialApplyRemaining(ByteBuffer partialInput);
|
||||
|
||||
static long grab(final ByteBuffer bb, final int length) {
|
||||
long result = bb.get() & 0xFFL;
|
||||
for (int i = 1; i < length; i++) {
|
||||
result |= (bb.get() & 0xFFL) << (i << 3);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static void writeLittleEndian(final long hash, final ByteBuffer output) {
|
||||
for (int i = 0; i < 8; i++) {
|
||||
output.put((byte) (hash >>> (i*8)));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
package cn.hutool.core.codec.hash.metro;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
/**
|
||||
* Apache 发布的MetroHash算法接口,是一组用于非加密用例的最先进的哈希函数。
|
||||
* 除了卓越的性能外,他们还以算法生成而著称。
|
||||
*
|
||||
* <p>
|
||||
* 官方实现:https://github.com/jandrewrogers/MetroHash
|
||||
* 官方文档:http://www.jandrewrogers.com/2015/05/27/metrohash/
|
||||
* 来自:https://github.com/postamar/java-metrohash/
|
||||
*
|
||||
* @param <R> 返回值类型,为this类型
|
||||
* @author Marius Posta
|
||||
*/
|
||||
public interface MetroHash<R extends MetroHash<R>> {
|
||||
|
||||
/**
|
||||
* 创建 {@code MetroHash}对象
|
||||
*
|
||||
* @param seed 种子
|
||||
* @param is128 是否128位
|
||||
* @return {@code MetroHash}对象
|
||||
*/
|
||||
static MetroHash<?> of(final long seed, final boolean is128) {
|
||||
return is128 ? new MetroHash128(seed) : new MetroHash64(seed);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将给定的{@link ByteBuffer}中的数据追加计算hash值<br>
|
||||
* 此方法会更新hash值状态
|
||||
*
|
||||
* @param input 内容
|
||||
* @return this
|
||||
*/
|
||||
R apply(final ByteBuffer input);
|
||||
|
||||
/**
|
||||
* 将结果hash值写出到{@link ByteBuffer}中,可选端序
|
||||
*
|
||||
* @param output 输出
|
||||
* @param byteOrder 端序
|
||||
* @return this
|
||||
*/
|
||||
R write(ByteBuffer output, final ByteOrder byteOrder);
|
||||
|
||||
/**
|
||||
* 重置,重置后可复用对象开启新的计算
|
||||
*
|
||||
* @return this
|
||||
*/
|
||||
R reset();
|
||||
}
|
@ -0,0 +1,164 @@
|
||||
package cn.hutool.core.codec.hash.metro;
|
||||
|
||||
import cn.hutool.core.codec.Number128;
|
||||
import cn.hutool.core.codec.hash.Hash128;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
/**
|
||||
* Apache 发布的MetroHash算法的128位实现,是一组用于非加密用例的最先进的哈希函数。
|
||||
* 除了卓越的性能外,他们还以算法生成而著称。
|
||||
*
|
||||
* <p>
|
||||
* 官方实现:https://github.com/jandrewrogers/MetroHash
|
||||
* 官方文档:http://www.jandrewrogers.com/2015/05/27/metrohash/
|
||||
* 来自:https://github.com/postamar/java-metrohash/
|
||||
* @author Marius Posta
|
||||
*/
|
||||
public class MetroHash128 extends AbstractMetroHash<MetroHash128> implements Hash128<byte[]> {
|
||||
|
||||
/**
|
||||
* 创建 {@code MetroHash128}对象
|
||||
*
|
||||
* @param seed 种子
|
||||
* @return {@code MetroHash128}对象
|
||||
*/
|
||||
public static MetroHash128 of(final long seed) {
|
||||
return new MetroHash128(seed);
|
||||
}
|
||||
|
||||
private static final long K0 = 0xC83A91E1L;
|
||||
private static final long K1 = 0x8648DBDBL;
|
||||
private static final long K2 = 0x7BDEC03BL;
|
||||
private static final long K3 = 0x2F5870A5L;
|
||||
|
||||
/**
|
||||
* 使用指定种子构造
|
||||
*
|
||||
* @param seed 种子
|
||||
*/
|
||||
public MetroHash128(final long seed) {
|
||||
super(seed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MetroHash128 reset() {
|
||||
v0 = (seed - K0) * K3;
|
||||
v1 = (seed + K1) * K2;
|
||||
v2 = (seed + K0) * K2;
|
||||
v3 = (seed - K1) * K3;
|
||||
nChunks = 0;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取结果hash值
|
||||
*
|
||||
* @return hash值
|
||||
*/
|
||||
public Number128 get() {
|
||||
return new Number128(v1, v0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Number128 hash128(final byte[] bytes) {
|
||||
return apply(ByteBuffer.wrap(bytes)).get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MetroHash128 write(final ByteBuffer output, final ByteOrder byteOrder) {
|
||||
if (ByteOrder.LITTLE_ENDIAN == byteOrder) {
|
||||
writeLittleEndian(v0, output);
|
||||
writeLittleEndian(v1, output);
|
||||
} else {
|
||||
output.asLongBuffer().put(v1).put(v0);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
MetroHash128 partialApply32ByteChunk(final ByteBuffer partialInput) {
|
||||
assert partialInput.remaining() >= 32;
|
||||
v0 += grab(partialInput, 8) * K0;
|
||||
v0 = Long.rotateRight(v0, 29) + v2;
|
||||
v1 += grab(partialInput, 8) * K1;
|
||||
v1 = Long.rotateRight(v1, 29) + v3;
|
||||
v2 += grab(partialInput, 8) * K2;
|
||||
v2 = Long.rotateRight(v2, 29) + v0;
|
||||
v3 += grab(partialInput, 8) * K3;
|
||||
v3 = Long.rotateRight(v3, 29) + v1;
|
||||
++nChunks;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
MetroHash128 partialApplyRemaining(final ByteBuffer partialInput) {
|
||||
assert partialInput.remaining() < 32;
|
||||
if (nChunks > 0) {
|
||||
metroHash128_32();
|
||||
}
|
||||
if (partialInput.remaining() >= 16) {
|
||||
metroHash128_16(partialInput);
|
||||
}
|
||||
if (partialInput.remaining() >= 8) {
|
||||
metroHash128_8(partialInput);
|
||||
}
|
||||
if (partialInput.remaining() >= 4) {
|
||||
metroHash128_4(partialInput);
|
||||
}
|
||||
if (partialInput.remaining() >= 2) {
|
||||
metroHash128_2(partialInput);
|
||||
}
|
||||
if (partialInput.remaining() >= 1) {
|
||||
metroHash128_1(partialInput);
|
||||
}
|
||||
v0 += Long.rotateRight(v0 * K0 + v1, 13);
|
||||
v1 += Long.rotateRight(v1 * K1 + v0, 37);
|
||||
v0 += Long.rotateRight(v0 * K2 + v1, 13);
|
||||
v1 += Long.rotateRight(v1 * K3 + v0, 37);
|
||||
return this;
|
||||
}
|
||||
|
||||
// region ----- private methods
|
||||
private void metroHash128_32() {
|
||||
v2 ^= Long.rotateRight((v0 + v3) * K0 + v1, 21) * K1;
|
||||
v3 ^= Long.rotateRight((v1 + v2) * K1 + v0, 21) * K0;
|
||||
v0 ^= Long.rotateRight((v0 + v2) * K0 + v3, 21) * K1;
|
||||
v1 ^= Long.rotateRight((v1 + v3) * K1 + v2, 21) * K0;
|
||||
}
|
||||
|
||||
private void metroHash128_16(final ByteBuffer bb) {
|
||||
v0 += grab(bb, 8) * K2;
|
||||
v0 = Long.rotateRight(v0, 33) * K3;
|
||||
v1 += grab(bb, 8) * K2;
|
||||
v1 = Long.rotateRight(v1, 33) * K3;
|
||||
v0 ^= Long.rotateRight(v0 * K2 + v1, 45) * K1;
|
||||
v1 ^= Long.rotateRight(v1 * K3 + v0, 45) * K0;
|
||||
}
|
||||
|
||||
private void metroHash128_8(final ByteBuffer bb) {
|
||||
v0 += grab(bb, 8) * K2;
|
||||
v0 = Long.rotateRight(v0, 33) * K3;
|
||||
v0 ^= Long.rotateRight(v0 * K2 + v1, 27) * K1;
|
||||
}
|
||||
|
||||
private void metroHash128_4(final ByteBuffer bb) {
|
||||
v1 += grab(bb, 4) * K2;
|
||||
v1 = Long.rotateRight(v1, 33) * K3;
|
||||
v1 ^= Long.rotateRight(v1 * K3 + v0, 46) * K0;
|
||||
}
|
||||
|
||||
private void metroHash128_2(final ByteBuffer bb) {
|
||||
v0 += grab(bb, 2) * K2;
|
||||
v0 = Long.rotateRight(v0, 33) * K3;
|
||||
v0 ^= Long.rotateRight(v0 * K2 + v1, 22) * K1;
|
||||
}
|
||||
|
||||
private void metroHash128_1(final ByteBuffer bb) {
|
||||
v1 += grab(bb, 1) * K2;
|
||||
v1 = Long.rotateRight(v1, 33) * K3;
|
||||
v1 ^= Long.rotateRight(v1 * K3 + v0, 58) * K0;
|
||||
}
|
||||
// endregion
|
||||
}
|
@ -0,0 +1,158 @@
|
||||
package cn.hutool.core.codec.hash.metro;
|
||||
|
||||
import cn.hutool.core.codec.hash.Hash64;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
/**
|
||||
* Apache 发布的MetroHash算法的64位实现,是一组用于非加密用例的最先进的哈希函数。
|
||||
* 除了卓越的性能外,他们还以算法生成而著称。
|
||||
*
|
||||
* <p>
|
||||
* 官方实现:https://github.com/jandrewrogers/MetroHash
|
||||
* 官方文档:http://www.jandrewrogers.com/2015/05/27/metrohash/
|
||||
* 来自:https://github.com/postamar/java-metrohash/
|
||||
* @author Marius Posta
|
||||
*/
|
||||
public class MetroHash64 extends AbstractMetroHash<MetroHash64> implements Hash64<byte[]> {
|
||||
|
||||
/**
|
||||
* 创建 {@code MetroHash64}对象
|
||||
*
|
||||
* @param seed 种子
|
||||
* @return {@code MetroHash64}对象
|
||||
*/
|
||||
public static MetroHash64 of(final long seed) {
|
||||
return new MetroHash64(seed);
|
||||
}
|
||||
|
||||
private static final long K0 = 0xD6D018F5L;
|
||||
private static final long K1 = 0xA2AA033BL;
|
||||
private static final long K2 = 0x62992FC1L;
|
||||
private static final long K3 = 0x30BC5B29L;
|
||||
|
||||
private long hash;
|
||||
|
||||
/**
|
||||
* 使用指定种子构造
|
||||
*
|
||||
* @param seed 种子
|
||||
*/
|
||||
public MetroHash64(final long seed) {
|
||||
super(seed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MetroHash64 reset() {
|
||||
v0 = v1 = v2 = v3 = hash = (seed + K2) * K0;
|
||||
nChunks = 0;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取计算结果hash值
|
||||
*
|
||||
* @return hash值
|
||||
*/
|
||||
public long get() {
|
||||
return hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long hash64(final byte[] bytes) {
|
||||
return apply(ByteBuffer.wrap(bytes)).get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MetroHash64 write(final ByteBuffer output, final ByteOrder byteOrder) {
|
||||
if(ByteOrder.LITTLE_ENDIAN == byteOrder){
|
||||
writeLittleEndian(hash, output);
|
||||
} else{
|
||||
output.asLongBuffer().put(hash);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
MetroHash64 partialApply32ByteChunk(final ByteBuffer partialInput) {
|
||||
assert partialInput.remaining() >= 32;
|
||||
v0 += grab(partialInput, 8) * K0;
|
||||
v0 = Long.rotateRight(v0, 29) + v2;
|
||||
v1 += grab(partialInput, 8) * K1;
|
||||
v1 = Long.rotateRight(v1, 29) + v3;
|
||||
v2 += grab(partialInput, 8) * K2;
|
||||
v2 = Long.rotateRight(v2, 29) + v0;
|
||||
v3 += grab(partialInput, 8) * K3;
|
||||
v3 = Long.rotateRight(v3, 29) + v1;
|
||||
++nChunks;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
MetroHash64 partialApplyRemaining(final ByteBuffer partialInput) {
|
||||
assert partialInput.remaining() < 32;
|
||||
if (nChunks > 0) {
|
||||
metroHash64_32();
|
||||
}
|
||||
if (partialInput.remaining() >= 16) {
|
||||
metroHash64_16(partialInput);
|
||||
}
|
||||
if (partialInput.remaining() >= 8) {
|
||||
metroHash64_8(partialInput);
|
||||
}
|
||||
if (partialInput.remaining() >= 4) {
|
||||
metroHash64_4(partialInput);
|
||||
}
|
||||
if (partialInput.remaining() >= 2) {
|
||||
metroHash64_2(partialInput);
|
||||
}
|
||||
if (partialInput.remaining() >= 1) {
|
||||
metroHash64_1(partialInput);
|
||||
}
|
||||
hash ^= Long.rotateRight(hash, 28);
|
||||
hash *= K0;
|
||||
hash ^= Long.rotateRight(hash, 29);
|
||||
return this;
|
||||
}
|
||||
|
||||
// region ----- private methods
|
||||
private void metroHash64_32() {
|
||||
v2 ^= Long.rotateRight(((v0 + v3) * K0) + v1, 37) * K1;
|
||||
v3 ^= Long.rotateRight(((v1 + v2) * K1) + v0, 37) * K0;
|
||||
v0 ^= Long.rotateRight(((v0 + v2) * K0) + v3, 37) * K1;
|
||||
v1 ^= Long.rotateRight(((v1 + v3) * K1) + v2, 37) * K0;
|
||||
hash += v0 ^ v1;
|
||||
}
|
||||
|
||||
private void metroHash64_16(final ByteBuffer bb) {
|
||||
v0 = hash + grab(bb, 8) * K2;
|
||||
v0 = Long.rotateRight(v0, 29) * K3;
|
||||
v1 = hash + grab(bb, 8) * K2;
|
||||
v1 = Long.rotateRight(v1, 29) * K3;
|
||||
v0 ^= Long.rotateRight(v0 * K0, 21) + v1;
|
||||
v1 ^= Long.rotateRight(v1 * K3, 21) + v0;
|
||||
hash += v1;
|
||||
}
|
||||
|
||||
private void metroHash64_8(final ByteBuffer bb) {
|
||||
hash += grab(bb, 8) * K3;
|
||||
hash ^= Long.rotateRight(hash, 55) * K1;
|
||||
}
|
||||
|
||||
private void metroHash64_4(final ByteBuffer bb) {
|
||||
hash += grab(bb, 4) * K3;
|
||||
hash ^= Long.rotateRight(hash, 26) * K1;
|
||||
}
|
||||
|
||||
private void metroHash64_2(final ByteBuffer bb) {
|
||||
hash += grab(bb, 2) * K3;
|
||||
hash ^= Long.rotateRight(hash, 48) * K1;
|
||||
}
|
||||
|
||||
private void metroHash64_1(final ByteBuffer bb) {
|
||||
hash += grab(bb, 1) * K3;
|
||||
hash ^= Long.rotateRight(hash, 37) * K1;
|
||||
}
|
||||
// endregion
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
/**
|
||||
* MetroHash算法实现<br>
|
||||
* <p>
|
||||
* 参考:https://github.com/postamar/java-metrohash
|
||||
*
|
||||
* @author postamar, looly
|
||||
*/
|
||||
package cn.hutool.core.codec.hash.metro;
|
@ -363,7 +363,7 @@ public class CollUtil {
|
||||
* 例如:集合1:[a, b, c, c, c],集合2:[a, b, c, c]<br>
|
||||
* 结果:[a, b, c],此结果中只保留了一个c
|
||||
*
|
||||
* @param <T> 集合元素类型
|
||||
* @param <T> 集合元素类型
|
||||
* @param colls 集合列表
|
||||
* @return 交集的集合,返回 {@link LinkedHashSet}
|
||||
* @since 5.3.9
|
||||
@ -1735,21 +1735,7 @@ public class CollUtil {
|
||||
list.sort(comparator);
|
||||
}
|
||||
|
||||
return page(pageNo, pageSize, list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 对指定List分页取值
|
||||
*
|
||||
* @param <T> 集合元素类型
|
||||
* @param pageNo 页码,从0开始计数,0表示第一页
|
||||
* @param pageSize 每页的条目数
|
||||
* @param list 列表
|
||||
* @return 分页后的段落内容
|
||||
* @since 4.1.20
|
||||
*/
|
||||
public static <T> List<T> page(final int pageNo, final int pageSize, final List<T> list) {
|
||||
return ListUtil.page(pageNo, pageSize, list);
|
||||
return ListUtil.page(list, pageNo, pageSize);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2015,7 +2001,7 @@ public class CollUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据元素的指定字段名分组,非Bean都放在第一个分组中
|
||||
* 根据元素的指定字段值分组,非Bean都放在第一个分组中
|
||||
*
|
||||
* @param <T> 元素类型
|
||||
* @param collection 集合
|
||||
@ -2023,8 +2009,24 @@ public class CollUtil {
|
||||
* @return 分组列表
|
||||
*/
|
||||
public static <T> List<List<T>> groupByField(final Collection<T> collection, final String fieldName) {
|
||||
return groupByFunc(collection, t -> BeanUtil.getFieldValue(t, fieldName));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据元素的指定字段值分组,非Bean都放在第一个分组中<br>
|
||||
* 例如:{@code
|
||||
* CollUtil.groupByFunc(list, TestBean::getAge)
|
||||
* }
|
||||
*
|
||||
* @param <T> 元素类型
|
||||
* @param collection 集合
|
||||
* @param getter getter方法引用
|
||||
* @return 分组列表
|
||||
* @since 6.0.0
|
||||
*/
|
||||
public static <T> List<List<T>> groupByFunc(final Collection<T> collection, final Function<T, ?> getter) {
|
||||
return group(collection, new Hash32<T>() {
|
||||
private final List<Object> fieldNameList = new ArrayList<>();
|
||||
private final List<Object> hashValList = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public int hash32(final T t) {
|
||||
@ -2032,14 +2034,13 @@ public class CollUtil {
|
||||
// 非Bean放在同一子分组中
|
||||
return 0;
|
||||
}
|
||||
final Object value = FieldUtil.getFieldValue(t, fieldName);
|
||||
final int hash = fieldNameList.indexOf(value);
|
||||
final Object value = getter.apply(t);
|
||||
int hash = hashValList.indexOf(value);
|
||||
if (hash < 0) {
|
||||
fieldNameList.add(value);
|
||||
return fieldNameList.size() - 1;
|
||||
} else {
|
||||
return hash;
|
||||
hashValList.add(value);
|
||||
hash = hashValList.size() - 1;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -8,20 +8,11 @@ import cn.hutool.core.collection.partition.RandomAccessPartition;
|
||||
import cn.hutool.core.comparator.PinyinComparator;
|
||||
import cn.hutool.core.comparator.PropertyComparator;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.math.PageInfo;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.ObjUtil;
|
||||
import cn.hutool.core.util.PageUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.RandomAccess;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
@ -34,7 +25,7 @@ import java.util.function.Predicate;
|
||||
public class ListUtil {
|
||||
|
||||
/**
|
||||
* 新建一个List<br>
|
||||
* 新建一个{@link ArrayList}<br>
|
||||
* 如果提供的初始化数组为空,新建默认初始长度的List
|
||||
*
|
||||
* @param <T> 集合元素类型
|
||||
@ -178,7 +169,7 @@ public class ListUtil {
|
||||
/**
|
||||
* 数组转为一个不可变List<br>
|
||||
* 类似于Java9中的List.of<br>
|
||||
* 不同于Arrays.asList,此方法的原数组修改时,并不会影响List。
|
||||
* 不同于Arrays.asList,此方法不允许修改数组
|
||||
*
|
||||
* @param ts 对象
|
||||
* @param <T> 对象类型
|
||||
@ -254,42 +245,53 @@ public class ListUtil {
|
||||
* 对指定List分页取值
|
||||
*
|
||||
* @param <T> 集合元素类型
|
||||
* @param pageNo 页码,第一页的页码取决于{@link PageUtil#getFirstPageNo()},默认0
|
||||
* @param pageNo 页码,第一页的页码,从0开始
|
||||
* @param pageSize 每页的条目数
|
||||
* @param list 列表
|
||||
* @return 分页后的段落内容
|
||||
* @since 4.1.20
|
||||
*/
|
||||
public static <T> List<T> page(final int pageNo, final int pageSize, final List<T> list) {
|
||||
public static <T> List<T> page(final List<T> list, final int pageNo, final int pageSize) {
|
||||
if (CollUtil.isEmpty(list)) {
|
||||
return new ArrayList<>(0);
|
||||
}
|
||||
return page(list, PageInfo.of(list.size(), pageSize)
|
||||
.setFirstPageNo(0).setPageNo(pageNo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 对指定List分页取值
|
||||
*
|
||||
* @param <T> 集合元素类型
|
||||
* @param pageInfo 分页信息
|
||||
* @param list 列表
|
||||
* @return 分页后的段落内容
|
||||
* @since 4.1.20
|
||||
*/
|
||||
public static <T> List<T> page(final List<T> list, final PageInfo pageInfo) {
|
||||
if (CollUtil.isEmpty(list)) {
|
||||
return new ArrayList<>(0);
|
||||
}
|
||||
|
||||
final int resultSize = list.size();
|
||||
// 每页条目数大于总数直接返回所有
|
||||
if (resultSize <= pageSize) {
|
||||
if (pageNo < (PageUtil.getFirstPageNo() + 1)) {
|
||||
final int total = list.size();
|
||||
final int pageSize = pageInfo.getPageSize();
|
||||
// 不满一页
|
||||
if (total <= pageSize) {
|
||||
if (pageInfo.isFirstPage()) {
|
||||
// 页码为1,返回所有
|
||||
return view(list);
|
||||
} else {
|
||||
// 越界直接返回空
|
||||
return new ArrayList<>(0);
|
||||
}
|
||||
}
|
||||
// 相乘可能会导致越界 临时用long
|
||||
if (((long) (pageNo - PageUtil.getFirstPageNo()) * pageSize) > resultSize) {
|
||||
|
||||
if (pageInfo.getBeginIndex() > total) {
|
||||
// 越界直接返回空
|
||||
return new ArrayList<>(0);
|
||||
}
|
||||
|
||||
final int[] startEnd = PageUtil.transToStartEnd(pageNo, pageSize);
|
||||
if (startEnd[1] > resultSize) {
|
||||
startEnd[1] = resultSize;
|
||||
if (startEnd[0] > startEnd[1]) {
|
||||
return new ArrayList<>(0);
|
||||
}
|
||||
}
|
||||
|
||||
return sub(list, startEnd[0], startEnd[1]);
|
||||
return sub(list, pageInfo.getBeginIndex(), pageInfo.getEndIndexExclude());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -307,16 +309,11 @@ public class ListUtil {
|
||||
}
|
||||
|
||||
final int total = list.size();
|
||||
final int totalPage = PageUtil.totalPage(total, pageSize);
|
||||
for (int pageNo = PageUtil.getFirstPageNo(); pageNo < totalPage + PageUtil.getFirstPageNo(); pageNo++) {
|
||||
// 获取当前页在列表中对应的起止序号
|
||||
final int[] startEnd = PageUtil.transToStartEnd(pageNo, pageSize);
|
||||
if (startEnd[1] > total) {
|
||||
startEnd[1] = total;
|
||||
}
|
||||
|
||||
final PageInfo pageInfo = PageInfo.of(total, pageSize);
|
||||
while(pageInfo.isValidPage()){
|
||||
// 返回数据
|
||||
pageListConsumer.accept(sub(list, startEnd[0], startEnd[1]));
|
||||
pageListConsumer.accept(sub(list, pageInfo.getBeginIndex(), pageInfo.getEndIndexExclude()));
|
||||
pageInfo.nextPage();
|
||||
}
|
||||
}
|
||||
|
||||
@ -346,7 +343,7 @@ public class ListUtil {
|
||||
if (CollUtil.isEmpty(list)) {
|
||||
return list;
|
||||
}
|
||||
if(null == c){
|
||||
if (null == c) {
|
||||
c = Comparator.nullsFirst((Comparator<? super T>) Comparator.naturalOrder());
|
||||
}
|
||||
list.sort(c);
|
||||
@ -386,6 +383,9 @@ public class ListUtil {
|
||||
* @since 4.0.6
|
||||
*/
|
||||
public static <T> List<T> reverse(final List<T> list) {
|
||||
if (CollUtil.isEmpty(list)) {
|
||||
return list;
|
||||
}
|
||||
Collections.reverse(list);
|
||||
return list;
|
||||
}
|
||||
@ -443,10 +443,10 @@ public class ListUtil {
|
||||
/**
|
||||
* 在指定位置设置元素。当index小于List的长度时,替换指定位置的值,否则追加{@code paddingElement}直到到达index后,设置值
|
||||
*
|
||||
* @param <T> 元素类型
|
||||
* @param list List列表
|
||||
* @param index 位置
|
||||
* @param element 新元素
|
||||
* @param <T> 元素类型
|
||||
* @param list List列表
|
||||
* @param index 位置
|
||||
* @param element 新元素
|
||||
* @param paddingElement 填充的值
|
||||
* @return 原List
|
||||
* @since 5.8.4
|
||||
@ -468,29 +468,29 @@ public class ListUtil {
|
||||
/**
|
||||
* 截取集合的部分
|
||||
*
|
||||
* @param <T> 集合元素类型
|
||||
* @param list 被截取的数组
|
||||
* @param start 开始位置(包含)
|
||||
* @param end 结束位置(不包含)
|
||||
* @param <T> 集合元素类型
|
||||
* @param list 被截取的数组
|
||||
* @param begionInclude 开始位置(包含)
|
||||
* @param endExclude 结束位置(不包含)
|
||||
* @return 截取后的数组,当开始位置超过最大时,返回空的List
|
||||
*/
|
||||
public static <T> List<T> sub(final List<T> list, final int start, final int end) {
|
||||
return sub(list, start, end, 1);
|
||||
public static <T> List<T> sub(final List<T> list, final int begionInclude, final int endExclude) {
|
||||
return sub(list, begionInclude, endExclude, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 截取集合的部分<br>
|
||||
* 此方法与{@link List#subList(int, int)} 不同在于子列表是新的副本,操作子列表不会影响原列表。
|
||||
*
|
||||
* @param <T> 集合元素类型
|
||||
* @param list 被截取的数组
|
||||
* @param start 开始位置(包含)
|
||||
* @param end 结束位置(不包含)
|
||||
* @param step 步进
|
||||
* @param <T> 集合元素类型
|
||||
* @param list 被截取的数组
|
||||
* @param begionInclude 开始位置(包含)
|
||||
* @param endExclude 结束位置(不包含)
|
||||
* @param step 步进
|
||||
* @return 截取后的数组,当开始位置超过最大时,返回空的List
|
||||
* @since 4.0.6
|
||||
*/
|
||||
public static <T> List<T> sub(final List<T> list, int start, int end, int step) {
|
||||
public static <T> List<T> sub(final List<T> list, int begionInclude, int endExclude, int step) {
|
||||
if (list == null) {
|
||||
return null;
|
||||
}
|
||||
@ -500,25 +500,25 @@ public class ListUtil {
|
||||
}
|
||||
|
||||
final int size = list.size();
|
||||
if (start < 0) {
|
||||
start += size;
|
||||
if (begionInclude < 0) {
|
||||
begionInclude += size;
|
||||
}
|
||||
if (end < 0) {
|
||||
end += size;
|
||||
if (endExclude < 0) {
|
||||
endExclude += size;
|
||||
}
|
||||
if (start == size) {
|
||||
if (begionInclude == size) {
|
||||
return new ArrayList<>(0);
|
||||
}
|
||||
if (start > end) {
|
||||
final int tmp = start;
|
||||
start = end;
|
||||
end = tmp;
|
||||
if (begionInclude > endExclude) {
|
||||
final int tmp = begionInclude;
|
||||
begionInclude = endExclude;
|
||||
endExclude = tmp;
|
||||
}
|
||||
if (end > size) {
|
||||
if (start >= size) {
|
||||
if (endExclude > size) {
|
||||
if (begionInclude >= size) {
|
||||
return new ArrayList<>(0);
|
||||
}
|
||||
end = size;
|
||||
endExclude = size;
|
||||
}
|
||||
|
||||
if (step < 1) {
|
||||
@ -526,7 +526,7 @@ public class ListUtil {
|
||||
}
|
||||
|
||||
final List<T> result = new ArrayList<>();
|
||||
for (int i = start; i < end; i += step) {
|
||||
for (int i = begionInclude; i < endExclude; i += step) {
|
||||
result.add(list.get(i));
|
||||
}
|
||||
return result;
|
||||
@ -716,8 +716,8 @@ public class ListUtil {
|
||||
* 通过删除或替换现有元素或者原地添加新的元素来修改列表,并以列表形式返回被修改的内容。此方法不会改变原列表。
|
||||
* 类似js的<a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/splice">splice</a>函数
|
||||
*
|
||||
* @param <T> 元素类型
|
||||
* @param list 列表
|
||||
* @param <T> 元素类型
|
||||
* @param list 列表
|
||||
* @param start 指定修改的开始位置(从 0 计数), 可以为负数, -1代表最后一个元素
|
||||
* @param deleteCount 删除个数,必须是正整数
|
||||
* @param items 放入的元素
|
||||
|
@ -95,6 +95,7 @@ public class LineIter extends ComputeIter<String> implements IterableIter<String
|
||||
* @param line 需要验证的行
|
||||
* @return 是否通过验证
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
protected boolean isValidLine(final String line) {
|
||||
return true;
|
||||
}
|
||||
|
@ -3,13 +3,7 @@ package cn.hutool.core.comparator;
|
||||
import cn.hutool.core.lang.Chain;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.BitSet;
|
||||
import java.util.Comparator;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 比较器链。此链包装了多个比较器,最终比较结果按照比较器顺序综合多个比较器结果。<br>
|
||||
@ -18,6 +12,7 @@ import java.util.Objects;
|
||||
*
|
||||
* @author looly
|
||||
* @since 3.0.7
|
||||
* @param <E> 被比较的对象
|
||||
*/
|
||||
public class ComparatorChain<E> implements Chain<Comparator<E>, ComparatorChain<E>>, Comparator<E>, Serializable {
|
||||
private static final long serialVersionUID = -2426725788913962429L;
|
||||
@ -38,11 +33,11 @@ public class ComparatorChain<E> implements Chain<Comparator<E>, ComparatorChain<
|
||||
//------------------------------------------------------------------------------------- Static method start
|
||||
|
||||
/**
|
||||
* 构建 {@link ComparatorChain}
|
||||
* 构建 {@code ComparatorChain}
|
||||
*
|
||||
* @param <E> 被比较对象类型
|
||||
* @param comparator 比较器
|
||||
* @return {@link ComparatorChain}
|
||||
* @return {@code ComparatorChain}
|
||||
* @since 5.4.3
|
||||
*/
|
||||
public static <E> ComparatorChain<E> of(final Comparator<E> comparator) {
|
||||
@ -50,12 +45,12 @@ public class ComparatorChain<E> implements Chain<Comparator<E>, ComparatorChain<
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建 {@link ComparatorChain}
|
||||
* 构建 {@code ComparatorChain}
|
||||
*
|
||||
* @param <E> 被比较对象类型
|
||||
* @param comparator 比较器
|
||||
* @param reverse 是否反向
|
||||
* @return {@link ComparatorChain}
|
||||
* @return {@code ComparatorChain}
|
||||
* @since 5.4.3
|
||||
*/
|
||||
public static <E> ComparatorChain<E> of(final Comparator<E> comparator, final boolean reverse) {
|
||||
@ -63,11 +58,11 @@ public class ComparatorChain<E> implements Chain<Comparator<E>, ComparatorChain<
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建 {@link ComparatorChain}
|
||||
* 构建 {@code ComparatorChain}
|
||||
*
|
||||
* @param <E> 被比较对象类型
|
||||
* @param comparators 比较器数组
|
||||
* @return {@link ComparatorChain}
|
||||
* @return {@code ComparatorChain}
|
||||
* @since 5.4.3
|
||||
*/
|
||||
@SafeVarargs
|
||||
@ -76,11 +71,11 @@ public class ComparatorChain<E> implements Chain<Comparator<E>, ComparatorChain<
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建 {@link ComparatorChain}
|
||||
* 构建 {@code ComparatorChain}
|
||||
*
|
||||
* @param <E> 被比较对象类型
|
||||
* @param comparators 比较器列表
|
||||
* @return {@link ComparatorChain}
|
||||
* @return {@code ComparatorChain}
|
||||
* @since 5.4.3
|
||||
*/
|
||||
public static <E> ComparatorChain<E> of(final List<Comparator<E>> comparators) {
|
||||
@ -88,12 +83,12 @@ public class ComparatorChain<E> implements Chain<Comparator<E>, ComparatorChain<
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建 {@link ComparatorChain}
|
||||
* 构建 {@code ComparatorChain}
|
||||
*
|
||||
* @param <E> 被比较对象类型
|
||||
* @param comparators 比较器列表
|
||||
* @param bits {@link Comparator} 列表对应的排序boolean值,true表示正序,false反序
|
||||
* @return {@link ComparatorChain}
|
||||
* @return {@code ComparatorChain}
|
||||
* @since 5.4.3
|
||||
*/
|
||||
public static <E> ComparatorChain<E> of(final List<Comparator<E>> comparators, final BitSet bits) {
|
||||
|
@ -1,34 +0,0 @@
|
||||
package cn.hutool.core.compiler;
|
||||
|
||||
import cn.hutool.core.exceptions.ExceptionUtil;
|
||||
import cn.hutool.core.text.StrUtil;
|
||||
|
||||
/**
|
||||
* 编译异常
|
||||
*
|
||||
* @author looly
|
||||
* @since 5.5.2
|
||||
*/
|
||||
public class CompilerException extends RuntimeException {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public CompilerException(final Throwable e) {
|
||||
super(ExceptionUtil.getMessage(e), e);
|
||||
}
|
||||
|
||||
public CompilerException(final String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public CompilerException(final String messageTemplate, final Object... params) {
|
||||
super(StrUtil.format(messageTemplate, params));
|
||||
}
|
||||
|
||||
public CompilerException(final String message, final Throwable throwable) {
|
||||
super(message, throwable);
|
||||
}
|
||||
|
||||
public CompilerException(final Throwable throwable, final String messageTemplate, final Object... params) {
|
||||
super(StrUtil.format(messageTemplate, params), throwable);
|
||||
}
|
||||
}
|
@ -1,80 +0,0 @@
|
||||
package cn.hutool.core.compiler;
|
||||
|
||||
import javax.tools.DiagnosticListener;
|
||||
import javax.tools.JavaCompiler;
|
||||
import javax.tools.JavaFileManager;
|
||||
import javax.tools.JavaFileObject;
|
||||
import javax.tools.StandardJavaFileManager;
|
||||
import javax.tools.ToolProvider;
|
||||
|
||||
/**
|
||||
* 源码编译工具类,主要封装{@link JavaCompiler} 相关功能
|
||||
*
|
||||
* @author looly
|
||||
* @since 5.5.2
|
||||
*/
|
||||
public class CompilerUtil {
|
||||
|
||||
/**
|
||||
* java 编译器
|
||||
*/
|
||||
public static final JavaCompiler SYSTEM_COMPILER = ToolProvider.getSystemJavaCompiler();
|
||||
|
||||
/**
|
||||
* 编译指定的源码文件
|
||||
*
|
||||
* @param sourceFiles 源码文件路径
|
||||
* @return 0表示成功,否则其他
|
||||
*/
|
||||
public static boolean compile(final String... sourceFiles) {
|
||||
return 0 == SYSTEM_COMPILER.run(null, null, null, sourceFiles);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取{@link StandardJavaFileManager}
|
||||
*
|
||||
* @return {@link StandardJavaFileManager}
|
||||
*/
|
||||
public static StandardJavaFileManager getFileManager() {
|
||||
return getFileManager(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取{@link StandardJavaFileManager}
|
||||
*
|
||||
* @param diagnosticListener 异常收集器
|
||||
* @return {@link StandardJavaFileManager}
|
||||
* @since 5.5.8
|
||||
*/
|
||||
public static StandardJavaFileManager getFileManager(final DiagnosticListener<? super JavaFileObject> diagnosticListener) {
|
||||
return SYSTEM_COMPILER.getStandardFileManager(diagnosticListener, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新建编译任务
|
||||
*
|
||||
* @param fileManager {@link JavaFileManager},用于管理已经编译好的文件
|
||||
* @param diagnosticListener 诊断监听
|
||||
* @param options 选项,例如 -cpXXX等
|
||||
* @param compilationUnits 编译单元,即需要编译的对象
|
||||
* @return {@link JavaCompiler.CompilationTask}
|
||||
*/
|
||||
public static JavaCompiler.CompilationTask getTask(
|
||||
final JavaFileManager fileManager,
|
||||
final DiagnosticListener<? super JavaFileObject> diagnosticListener,
|
||||
final Iterable<String> options,
|
||||
final Iterable<? extends JavaFileObject> compilationUnits) {
|
||||
return SYSTEM_COMPILER.getTask(null, fileManager, diagnosticListener, options, null, compilationUnits);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取{@link JavaSourceCompiler}
|
||||
*
|
||||
* @param parent 父{@link ClassLoader}
|
||||
* @return {@link JavaSourceCompiler}
|
||||
* @see JavaSourceCompiler#of(ClassLoader)
|
||||
*/
|
||||
public static JavaSourceCompiler getCompiler(final ClassLoader parent) {
|
||||
return JavaSourceCompiler.of(parent);
|
||||
}
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
package cn.hutool.core.compiler;
|
||||
|
||||
import javax.tools.DiagnosticCollector;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 诊断工具类
|
||||
*
|
||||
* @author looly
|
||||
* @since 5.5.2
|
||||
*/
|
||||
public class DiagnosticUtil {
|
||||
|
||||
/**
|
||||
* 获取{@link DiagnosticCollector}收集到的诊断信息,以文本返回
|
||||
*
|
||||
* @param collector {@link DiagnosticCollector}
|
||||
* @return 诊断消息
|
||||
*/
|
||||
public static String getMessages(final DiagnosticCollector<?> collector) {
|
||||
final List<?> diagnostics = collector.getDiagnostics();
|
||||
return diagnostics.stream().map(String::valueOf)
|
||||
.collect(Collectors.joining(System.lineSeparator()));
|
||||
}
|
||||
}
|
@ -1,78 +0,0 @@
|
||||
package cn.hutool.core.compiler;
|
||||
|
||||
import cn.hutool.core.io.resource.FileObjectResource;
|
||||
import cn.hutool.core.classloader.ResourceClassLoader;
|
||||
import cn.hutool.core.classloader.ClassLoaderUtil;
|
||||
import cn.hutool.core.util.ObjUtil;
|
||||
|
||||
import javax.tools.FileObject;
|
||||
import javax.tools.ForwardingJavaFileManager;
|
||||
import javax.tools.JavaFileManager;
|
||||
import javax.tools.JavaFileObject;
|
||||
import javax.tools.JavaFileObject.Kind;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Java 字节码文件对象管理器
|
||||
*
|
||||
* <p>
|
||||
* 正常我们使用javac命令编译源码时会将class文件写入到磁盘中,但在运行时动态编译类不适合保存在磁盘中
|
||||
* 我们采取此对象来管理运行时动态编译类生成的字节码。
|
||||
* </p>
|
||||
*
|
||||
* @author lzpeng
|
||||
* @since 5.5.2
|
||||
*/
|
||||
class JavaClassFileManager extends ForwardingJavaFileManager<JavaFileManager> {
|
||||
|
||||
/**
|
||||
* 存储java字节码文件对象映射
|
||||
*/
|
||||
private final Map<String, FileObjectResource> classFileObjectMap = new HashMap<>();
|
||||
|
||||
/**
|
||||
* 加载动态编译生成类的父类加载器
|
||||
*/
|
||||
private final ClassLoader parent;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param parent 父类加载器
|
||||
* @param fileManager 字节码文件管理器
|
||||
*/
|
||||
protected JavaClassFileManager(final ClassLoader parent, final JavaFileManager fileManager) {
|
||||
super(fileManager);
|
||||
this.parent = ObjUtil.defaultIfNull(parent, ClassLoaderUtil::getClassLoader);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得动态编译生成的类的类加载器
|
||||
*
|
||||
* @param location 源码位置
|
||||
* @return 动态编译生成的类的类加载器
|
||||
*/
|
||||
@Override
|
||||
public ClassLoader getClassLoader(final Location location) {
|
||||
return new ResourceClassLoader<>(this.parent, this.classFileObjectMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得Java字节码文件对象
|
||||
* 编译器编译源码时会将Java源码对象编译转为Java字节码对象
|
||||
*
|
||||
* @param location 源码位置
|
||||
* @param className 类名
|
||||
* @param kind 文件类型
|
||||
* @param sibling Java源码对象
|
||||
* @return Java字节码文件对象
|
||||
*/
|
||||
@Override
|
||||
public JavaFileObject getJavaFileForOutput(final Location location, final String className, final Kind kind, final FileObject sibling) {
|
||||
final JavaFileObject javaFileObject = new JavaClassFileObject(className);
|
||||
this.classFileObjectMap.put(className, new FileObjectResource(javaFileObject));
|
||||
return javaFileObject;
|
||||
}
|
||||
|
||||
}
|
@ -1,60 +0,0 @@
|
||||
package cn.hutool.core.compiler;
|
||||
|
||||
|
||||
import cn.hutool.core.util.CharUtil;
|
||||
import cn.hutool.core.net.url.URLUtil;
|
||||
|
||||
import javax.tools.SimpleJavaFileObject;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* Java 字节码文件对象,用于在内存中暂存class字节码,从而可以在ClassLoader中动态加载。
|
||||
*
|
||||
* @author lzpeng
|
||||
* @since 5.5.2
|
||||
*/
|
||||
class JavaClassFileObject extends SimpleJavaFileObject {
|
||||
|
||||
/**
|
||||
* 字节码输出流
|
||||
*/
|
||||
private final ByteArrayOutputStream byteArrayOutputStream;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param className 编译后的class文件的类名
|
||||
* @see JavaClassFileManager#getJavaFileForOutput(javax.tools.JavaFileManager.Location, java.lang.String, javax.tools.JavaFileObject.Kind, javax.tools.FileObject)
|
||||
*/
|
||||
protected JavaClassFileObject(final String className) {
|
||||
super(URLUtil.getStringURI(className.replace(CharUtil.DOT, CharUtil.SLASH) + Kind.CLASS.extension), Kind.CLASS);
|
||||
this.byteArrayOutputStream = new ByteArrayOutputStream();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得字节码输入流
|
||||
* 编译器编辑源码后,我们将通过此输出流获得编译后的字节码,以便运行时加载类
|
||||
*
|
||||
* @return 字节码输入流
|
||||
* @see JavaClassFileManager#getClassLoader(javax.tools.JavaFileManager.Location)
|
||||
*/
|
||||
@Override
|
||||
public InputStream openInputStream() {
|
||||
return new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得字节码输出流
|
||||
* 编译器编辑源码时,会将编译结果输出到本输出流中
|
||||
*
|
||||
* @return 字节码输出流
|
||||
*/
|
||||
@Override
|
||||
public OutputStream openOutputStream() {
|
||||
return this.byteArrayOutputStream;
|
||||
}
|
||||
|
||||
}
|
@ -1,112 +0,0 @@
|
||||
package cn.hutool.core.compiler;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.io.file.FileNameUtil;
|
||||
import cn.hutool.core.compress.ZipUtil;
|
||||
import cn.hutool.core.io.resource.FileResource;
|
||||
import cn.hutool.core.io.resource.Resource;
|
||||
|
||||
import javax.tools.JavaFileObject;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
/**
|
||||
* {@link JavaFileObject} 相关工具类封装
|
||||
*
|
||||
* @author lzpeng, looly
|
||||
* @since 5.5.2
|
||||
*/
|
||||
public class JavaFileObjectUtil {
|
||||
|
||||
/**
|
||||
* 获取指定资源下的所有待编译的java源码文件,并以{@link JavaFileObject}形式返回
|
||||
* <ul>
|
||||
* <li>如果资源为目录,则遍历目录找到目录中的.java或者.jar等文件加载之</li>
|
||||
* <li>如果资源为.jar或.zip等,解压读取其中的.java文件加载之</li>
|
||||
* <li>其他情况直接读取资源流并加载之</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param resource 资源,可以为目录、文件或流
|
||||
* @return 所有待编译的 {@link JavaFileObject}
|
||||
*/
|
||||
public static List<JavaFileObject> getJavaFileObjects(final Resource resource) {
|
||||
final List<JavaFileObject> result = new ArrayList<>();
|
||||
|
||||
if (resource instanceof FileResource) {
|
||||
final File file = ((FileResource) resource).getFile();
|
||||
result.addAll(JavaFileObjectUtil.getJavaFileObjects(file));
|
||||
} else {
|
||||
result.add(new JavaSourceFileObject(resource.getName(), resource.getStream()));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定文件下的所有待编译的java文件,并以{@link JavaFileObject}形式返回
|
||||
* <ul>
|
||||
* <li>如果文件为目录,则遍历目录找到目录中的.java或者.jar等文件加载之</li>
|
||||
* <li>如果文件为.jar或.zip等,解压读取其中的.java文件加载之</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param file 文件或目录,文件支持.java、.jar和.zip文件
|
||||
* @return 所有待编译的 {@link JavaFileObject}
|
||||
*/
|
||||
public static List<JavaFileObject> getJavaFileObjects(final File file) {
|
||||
final List<JavaFileObject> result = new ArrayList<>();
|
||||
if (file.isDirectory()) {
|
||||
FileUtil.walkFiles(file, (subFile) -> result.addAll(getJavaFileObjects(file)));
|
||||
} else {
|
||||
final String fileName = file.getName();
|
||||
if (isJavaFile(fileName)) {
|
||||
result.add(new JavaSourceFileObject(file.toURI()));
|
||||
} else if (isJarOrZipFile(fileName)) {
|
||||
result.addAll(getJavaFileObjectByZipOrJarFile(file));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否是jar 或 zip 文件<br>
|
||||
* 通过扩展名判定
|
||||
*
|
||||
* @param fileName 文件名
|
||||
* @return 是否是jar 或 zip 文件
|
||||
*/
|
||||
public static boolean isJarOrZipFile(final String fileName) {
|
||||
return FileNameUtil.isType(fileName, "jar", "zip");
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否是java文件<br>
|
||||
* 通过扩展名判定
|
||||
*
|
||||
* @param fileName 文件名
|
||||
* @return 是否是.java文件
|
||||
*/
|
||||
public static boolean isJavaFile(final String fileName) {
|
||||
return FileNameUtil.isType(fileName, "java");
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过zip包或jar包创建Java文件对象
|
||||
*
|
||||
* @param file 压缩文件
|
||||
* @return Java文件对象
|
||||
*/
|
||||
private static List<JavaFileObject> getJavaFileObjectByZipOrJarFile(final File file) {
|
||||
final List<JavaFileObject> collection = new ArrayList<>();
|
||||
final ZipFile zipFile = ZipUtil.toZipFile(file, null);
|
||||
ZipUtil.read(zipFile, (zipEntry) -> {
|
||||
final String name = zipEntry.getName();
|
||||
if (isJavaFile(name)) {
|
||||
collection.add(new JavaSourceFileObject(name, ZipUtil.getStream(zipFile, zipEntry)));
|
||||
}
|
||||
});
|
||||
return collection;
|
||||
}
|
||||
}
|
@ -1,234 +0,0 @@
|
||||
package cn.hutool.core.compiler;
|
||||
|
||||
import cn.hutool.core.classloader.ClassLoaderUtil;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.io.resource.FileResource;
|
||||
import cn.hutool.core.io.resource.Resource;
|
||||
import cn.hutool.core.io.resource.StringResource;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.net.url.URLUtil;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.ObjUtil;
|
||||
|
||||
import javax.tools.DiagnosticCollector;
|
||||
import javax.tools.JavaCompiler.CompilationTask;
|
||||
import javax.tools.JavaFileObject;
|
||||
import javax.tools.StandardLocation;
|
||||
import java.io.File;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Java 源码编译器
|
||||
* <p>通过此类可以动态编译java源码,并加载到ClassLoader,从而动态获取加载的类。</p>
|
||||
* <p>JavaSourceCompiler支持加载的源码类型包括:</p>
|
||||
* <ul>
|
||||
* <li>源码文件</li>
|
||||
* <li>源码文件源码字符串</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>使用方法如下:</p>
|
||||
* <pre>
|
||||
* ClassLoader classLoader = JavaSourceCompiler.create(null)
|
||||
* .addSource(FileUtil.file("test-compile/b/B.java"))
|
||||
* .addSource("c.C", FileUtil.readUtf8String("test-compile/c/C.java"))
|
||||
* // 增加编译依赖的类库
|
||||
* .addLibrary(libFile)
|
||||
* .compile();
|
||||
* Class<?> clazz = classLoader.loadClass("c.C");
|
||||
* </pre>
|
||||
*
|
||||
* @author lzpeng
|
||||
*/
|
||||
public class JavaSourceCompiler {
|
||||
|
||||
/**
|
||||
* 待编译的资源,支持:
|
||||
*
|
||||
* <ul>
|
||||
* <li>源码字符串,使用{@link StringResource}</li>
|
||||
* <li>源码文件、源码jar包或源码zip包,亦或者文件夹,使用{@link FileResource}</li>
|
||||
* </ul>
|
||||
* 可以是 .java文件 压缩文件 文件夹 递归搜索文件夹内的zip包和jar包
|
||||
*/
|
||||
private final List<Resource> sourceList = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 编译时需要加入classpath中的文件 可以是 压缩文件 文件夹递归搜索文件夹内的zip包和jar包
|
||||
*/
|
||||
private final List<File> libraryFileList = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 编译类时使用的父类加载器
|
||||
*/
|
||||
private final ClassLoader parentClassLoader;
|
||||
|
||||
/**
|
||||
* 创建Java源码编译器
|
||||
*
|
||||
* @param parent 父类加载器
|
||||
* @return Java源码编译器
|
||||
*/
|
||||
public static JavaSourceCompiler of(final ClassLoader parent) {
|
||||
return new JavaSourceCompiler(parent);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param parent 父类加载器,null则使用默认类加载器
|
||||
*/
|
||||
private JavaSourceCompiler(final ClassLoader parent) {
|
||||
this.parentClassLoader = ObjUtil.defaultIfNull(parent, ClassLoaderUtil::getClassLoader);
|
||||
}
|
||||
|
||||
/**
|
||||
* 向编译器中加入待编译的资源<br>
|
||||
* 支持 .java, 文件夹, 压缩文件 递归搜索文件夹内的压缩文件和jar包
|
||||
*
|
||||
* @param resources 待编译的资源,支持 .java, 文件夹, 压缩文件 递归搜索文件夹内的压缩文件和jar包
|
||||
* @return Java源码编译器
|
||||
*/
|
||||
public JavaSourceCompiler addSource(final Resource... resources) {
|
||||
if (ArrayUtil.isNotEmpty(resources)) {
|
||||
this.sourceList.addAll(Arrays.asList(resources));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 向编译器中加入待编译的文件<br>
|
||||
* 支持 .java, 文件夹, 压缩文件 递归搜索文件夹内的压缩文件和jar包
|
||||
*
|
||||
* @param files 待编译的文件 支持 .java, 文件夹, 压缩文件 递归搜索文件夹内的压缩文件和jar包
|
||||
* @return Java源码编译器
|
||||
*/
|
||||
public JavaSourceCompiler addSource(final File... files) {
|
||||
if (ArrayUtil.isNotEmpty(files)) {
|
||||
for (final File file : files) {
|
||||
this.sourceList.add(new FileResource(file));
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 向编译器中加入待编译的源码Map
|
||||
*
|
||||
* @param sourceCodeMap 源码Map key: 类名 value 源码
|
||||
* @return Java源码编译器
|
||||
*/
|
||||
public JavaSourceCompiler addSource(final Map<String, String> sourceCodeMap) {
|
||||
if (MapUtil.isNotEmpty(sourceCodeMap)) {
|
||||
sourceCodeMap.forEach(this::addSource);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 向编译器中加入待编译的源码
|
||||
*
|
||||
* @param className 类名
|
||||
* @param sourceCode 源码
|
||||
* @return Java文件编译器
|
||||
*/
|
||||
public JavaSourceCompiler addSource(final String className, final String sourceCode) {
|
||||
if (className != null && sourceCode != null) {
|
||||
this.sourceList.add(new StringResource(sourceCode, className));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 加入编译Java源码时所需要的jar包,jar包中必须为字节码
|
||||
*
|
||||
* @param files 编译Java源码时所需要的jar包
|
||||
* @return Java源码编译器
|
||||
*/
|
||||
public JavaSourceCompiler addLibrary(final File... files) {
|
||||
if (ArrayUtil.isNotEmpty(files)) {
|
||||
this.libraryFileList.addAll(Arrays.asList(files));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 编译所有文件并返回类加载器
|
||||
*
|
||||
* @return 类加载器
|
||||
*/
|
||||
public ClassLoader compile() {
|
||||
// 获得classPath
|
||||
final List<File> classPath = getClassPath();
|
||||
final URL[] urLs = URLUtil.getURLs(classPath.toArray(new File[0]));
|
||||
final URLClassLoader ucl = URLClassLoader.newInstance(urLs, this.parentClassLoader);
|
||||
if (sourceList.isEmpty()) {
|
||||
// 没有需要编译的源码文件返回加载zip或jar包的类加载器
|
||||
return ucl;
|
||||
}
|
||||
|
||||
// 创建编译器
|
||||
final JavaClassFileManager javaFileManager = new JavaClassFileManager(ucl, CompilerUtil.getFileManager());
|
||||
|
||||
// classpath
|
||||
final List<String> options = new ArrayList<>();
|
||||
if (false == classPath.isEmpty()) {
|
||||
final List<String> cp = CollUtil.map(classPath, File::getAbsolutePath, true);
|
||||
options.add("-cp");
|
||||
options.add(CollUtil.join(cp, FileUtil.isWindows() ? ";" : ":"));
|
||||
}
|
||||
|
||||
// 编译文件
|
||||
final DiagnosticCollector<? super JavaFileObject> diagnosticCollector = new DiagnosticCollector<>();
|
||||
final List<JavaFileObject> javaFileObjectList = getJavaFileObject();
|
||||
final CompilationTask task = CompilerUtil.getTask(javaFileManager, diagnosticCollector, options, javaFileObjectList);
|
||||
try {
|
||||
if (task.call()) {
|
||||
// 加载编译后的类
|
||||
return javaFileManager.getClassLoader(StandardLocation.CLASS_OUTPUT);
|
||||
}
|
||||
} finally {
|
||||
IoUtil.close(javaFileManager);
|
||||
}
|
||||
//编译失败,收集错误信息
|
||||
throw new CompilerException(DiagnosticUtil.getMessages(diagnosticCollector));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得编译源码时需要的classpath
|
||||
*
|
||||
* @return 编译源码时需要的classpath
|
||||
*/
|
||||
private List<File> getClassPath() {
|
||||
final List<File> classPathFileList = new ArrayList<>();
|
||||
for (final File file : libraryFileList) {
|
||||
final List<File> jarOrZipFile = FileUtil.loopFiles(file, (subFile) -> JavaFileObjectUtil.isJarOrZipFile(subFile.getName()));
|
||||
classPathFileList.addAll(jarOrZipFile);
|
||||
if (file.isDirectory()) {
|
||||
classPathFileList.add(file);
|
||||
}
|
||||
}
|
||||
return classPathFileList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得待编译的Java文件对象
|
||||
*
|
||||
* @return 待编译的Java文件对象
|
||||
*/
|
||||
private List<JavaFileObject> getJavaFileObject() {
|
||||
final List<JavaFileObject> list = new ArrayList<>();
|
||||
|
||||
for (final Resource resource : this.sourceList) {
|
||||
list.addAll(JavaFileObjectUtil.getJavaFileObjects(resource));
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
}
|
@ -1,90 +0,0 @@
|
||||
package cn.hutool.core.compiler;
|
||||
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.util.CharUtil;
|
||||
import cn.hutool.core.net.url.URLUtil;
|
||||
|
||||
import javax.tools.SimpleJavaFileObject;
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
/**
|
||||
* Java 源码文件对象,支持:<br>
|
||||
* <ol>
|
||||
* <li>源文件,通过文件的uri传入</li>
|
||||
* <li>代码内容,通过流传入</li>
|
||||
* </ol>
|
||||
*
|
||||
* @author lzpeng
|
||||
* @since 5.5.2
|
||||
*/
|
||||
class JavaSourceFileObject extends SimpleJavaFileObject {
|
||||
|
||||
/**
|
||||
* 输入流
|
||||
*/
|
||||
private InputStream inputStream;
|
||||
|
||||
/**
|
||||
* 构造,支持File等路径类型的源码
|
||||
*
|
||||
* @param uri 需要编译的文件uri
|
||||
*/
|
||||
protected JavaSourceFileObject(final URI uri) {
|
||||
super(uri, Kind.SOURCE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造,支持String类型的源码
|
||||
*
|
||||
* @param className 需要编译的类名
|
||||
* @param code 需要编译的类源码
|
||||
*/
|
||||
protected JavaSourceFileObject(final String className, final String code, final Charset charset) {
|
||||
this(className, IoUtil.toStream(code, charset));
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造,支持流中读取源码(例如zip或网络等)
|
||||
*
|
||||
* @param name 需要编译的文件名
|
||||
* @param inputStream 输入流
|
||||
*/
|
||||
protected JavaSourceFileObject(final String name, final InputStream inputStream) {
|
||||
this(URLUtil.getStringURI(name.replace(CharUtil.DOT, CharUtil.SLASH) + Kind.SOURCE.extension));
|
||||
this.inputStream = inputStream;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得类源码的输入流
|
||||
*
|
||||
* @return 类源码的输入流
|
||||
* @throws IOException IO 异常
|
||||
*/
|
||||
@Override
|
||||
public InputStream openInputStream() throws IOException {
|
||||
if (inputStream == null) {
|
||||
inputStream = toUri().toURL().openStream();
|
||||
}
|
||||
return new BufferedInputStream(inputStream);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得类源码
|
||||
* 编译器编辑源码前,会通过此方法获取类的源码
|
||||
*
|
||||
* @param ignoreEncodingErrors 是否忽略编码错误
|
||||
* @return 需要编译的类的源码
|
||||
* @throws IOException IO异常
|
||||
*/
|
||||
@Override
|
||||
public CharSequence getCharContent(final boolean ignoreEncodingErrors) throws IOException {
|
||||
try(final InputStream in = openInputStream()){
|
||||
return IoUtil.readUtf8(in);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
/**
|
||||
* 运行时编译java源码,动态从字符串或外部文件加载类
|
||||
*
|
||||
* @author : Lzpeng
|
||||
*/
|
||||
package cn.hutool.core.compiler;
|
@ -1,7 +1,7 @@
|
||||
package cn.hutool.core.compress;
|
||||
|
||||
import cn.hutool.core.exceptions.ValidateException;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.io.file.FileUtil;
|
||||
import cn.hutool.core.io.IORuntimeException;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.text.StrUtil;
|
||||
|
@ -2,9 +2,10 @@ package cn.hutool.core.compress;
|
||||
|
||||
import cn.hutool.core.collection.iter.EnumerationIter;
|
||||
import cn.hutool.core.exceptions.UtilException;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.io.file.FileUtil;
|
||||
import cn.hutool.core.io.IORuntimeException;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.io.file.FileNameUtil;
|
||||
import cn.hutool.core.io.file.FileSystemUtil;
|
||||
import cn.hutool.core.io.file.PathUtil;
|
||||
import cn.hutool.core.io.resource.Resource;
|
||||
@ -172,7 +173,7 @@ public class ZipUtil {
|
||||
* @throws UtilException IO异常
|
||||
*/
|
||||
public static File zip(final File srcFile, final Charset charset) throws UtilException {
|
||||
final File zipFile = FileUtil.file(srcFile.getParentFile(), FileUtil.mainName(srcFile) + ".zip");
|
||||
final File zipFile = FileUtil.file(srcFile.getParentFile(), FileNameUtil.mainName(srcFile) + ".zip");
|
||||
zip(zipFile, charset, false, srcFile);
|
||||
return zipFile;
|
||||
}
|
||||
@ -468,7 +469,7 @@ public class ZipUtil {
|
||||
* @since 3.2.2
|
||||
*/
|
||||
public static File unzip(final File zipFile, final Charset charset) throws UtilException {
|
||||
final File destDir = FileUtil.file(zipFile.getParentFile(), FileUtil.mainName(zipFile));
|
||||
final File destDir = FileUtil.file(zipFile.getParentFile(), FileNameUtil.mainName(zipFile));
|
||||
return unzip(zipFile, destDir, charset);
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
package cn.hutool.core.compress;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.io.file.FileUtil;
|
||||
import cn.hutool.core.io.IORuntimeException;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.io.resource.Resource;
|
||||
@ -100,6 +100,18 @@ public class ZipWriter implements Closeable {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置压缩方式
|
||||
*
|
||||
* @param method 压缩方式,支持{@link ZipOutputStream#DEFLATED}和{@link ZipOutputStream#STORED}
|
||||
* @return this
|
||||
* @since 6.0.0
|
||||
*/
|
||||
public ZipWriter setMethod(final int method) {
|
||||
this.out.setMethod(method);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取原始的{@link ZipOutputStream}
|
||||
*
|
||||
@ -233,9 +245,10 @@ public class ZipWriter implements Closeable {
|
||||
* @param filter 文件过滤器,通过实现此接口,自定义要过滤的文件(过滤掉哪些文件或文件夹不加入压缩),{@code null}表示不过滤
|
||||
* @throws IORuntimeException IO异常
|
||||
*/
|
||||
private ZipWriter _add(final File file, final String srcRootDir, final FileFilter filter) throws IORuntimeException {
|
||||
@SuppressWarnings("resource")
|
||||
private void _add(final File file, final String srcRootDir, final FileFilter filter) throws IORuntimeException {
|
||||
if (null == file || (null != filter && false == filter.accept(file))) {
|
||||
return this;
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取文件相对于压缩文件夹根目录的子路径
|
||||
@ -256,7 +269,6 @@ public class ZipWriter implements Closeable {
|
||||
// 如果是文件或其它符号,则直接压缩该文件
|
||||
putEntry(subPath, FileUtil.getInputStream(file));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -830,6 +830,7 @@ public class Convert {
|
||||
* @param notConvertSet 不替换的字符集合
|
||||
* @return 替换后的字符
|
||||
*/
|
||||
@SuppressWarnings("UnnecessaryUnicodeEscape")
|
||||
public static String toDBC(final String text, final Set<Character> notConvertSet) {
|
||||
if(StrUtil.isBlank(text)) {
|
||||
return text;
|
||||
|
@ -1,6 +1,6 @@
|
||||
package cn.hutool.core.convert.impl;
|
||||
|
||||
import cn.hutool.core.codec.BaseN.Base64;
|
||||
import cn.hutool.core.codec.binary.Base64;
|
||||
import cn.hutool.core.collection.ListUtil;
|
||||
import cn.hutool.core.convert.AbstractConverter;
|
||||
import cn.hutool.core.convert.Convert;
|
||||
@ -24,6 +24,9 @@ import java.util.List;
|
||||
public class ArrayConverter extends AbstractConverter {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 单例
|
||||
*/
|
||||
public static final ArrayConverter INSTANCE = new ArrayConverter();
|
||||
|
||||
/**
|
||||
|
@ -3,6 +3,7 @@ package cn.hutool.core.convert.impl;
|
||||
import cn.hutool.core.convert.AbstractConverter;
|
||||
import cn.hutool.core.date.DateTime;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.date.format.GlobalCustomFormat;
|
||||
import cn.hutool.core.text.StrUtil;
|
||||
import cn.hutool.core.util.ObjUtil;
|
||||
|
||||
@ -156,7 +157,15 @@ public class TemporalAccessorConverter extends AbstractConverter {
|
||||
return IsoEra.of(Math.toIntExact(time));
|
||||
}
|
||||
|
||||
return parseFromInstant(targetClass, Instant.ofEpochMilli(time), null);
|
||||
final Instant instant;
|
||||
if(GlobalCustomFormat.FORMAT_SECONDS.equals(this.format)){
|
||||
// https://gitee.com/dromara/hutool/issues/I6IS5B
|
||||
// Unix时间戳
|
||||
instant = Instant.ofEpochSecond(time);
|
||||
}else{
|
||||
instant = Instant.ofEpochMilli(time);
|
||||
}
|
||||
return parseFromInstant(targetClass, instant, null);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -73,7 +73,7 @@ public class BetweenFormatter implements Serializable {
|
||||
final int level = this.level.ordinal();
|
||||
int levelCount = 0;
|
||||
|
||||
if (isLevelCountValid(levelCount) && 0 != day && level >= Level.DAY.ordinal()) {
|
||||
if (isLevelCountValid(levelCount) && 0 != day) {
|
||||
sb.append(day).append(Level.DAY.name);
|
||||
levelCount++;
|
||||
}
|
||||
|
@ -2,12 +2,12 @@ package cn.hutool.core.date;
|
||||
|
||||
import cn.hutool.core.comparator.CompareUtil;
|
||||
import cn.hutool.core.convert.NumberChineseFormatter;
|
||||
import cn.hutool.core.date.format.GlobalCustomFormat;
|
||||
import cn.hutool.core.date.format.parser.DateParser;
|
||||
import cn.hutool.core.date.format.parser.FastDateParser;
|
||||
import cn.hutool.core.date.format.GlobalCustomFormat;
|
||||
import cn.hutool.core.date.format.parser.PositionDateParser;
|
||||
import cn.hutool.core.util.ObjUtil;
|
||||
import cn.hutool.core.text.StrUtil;
|
||||
import cn.hutool.core.util.ObjUtil;
|
||||
|
||||
import javax.xml.datatype.XMLGregorianCalendar;
|
||||
import java.text.ParsePosition;
|
||||
@ -15,7 +15,6 @@ import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
|
||||
@ -74,7 +73,7 @@ public class CalendarUtil {
|
||||
/**
|
||||
* 转换为Calendar对象
|
||||
*
|
||||
* @param millis 时间戳
|
||||
* @param millis 时间戳
|
||||
* @param timeZone 时区
|
||||
* @return Calendar对象
|
||||
* @since 5.7.22
|
||||
@ -105,6 +104,8 @@ public class CalendarUtil {
|
||||
return Calendar.PM == calendar.get(Calendar.AM_PM);
|
||||
}
|
||||
|
||||
// region ----- modify 时间修改
|
||||
|
||||
/**
|
||||
* 修改日期为某个时间字段起始时间
|
||||
*
|
||||
@ -355,6 +356,7 @@ public class CalendarUtil {
|
||||
public static Calendar endOfYear(final Calendar calendar) {
|
||||
return ceiling(calendar, DateField.YEAR);
|
||||
}
|
||||
// endregion
|
||||
|
||||
/**
|
||||
* 比较两个日期是否为同一天
|
||||
@ -375,8 +377,8 @@ public class CalendarUtil {
|
||||
/**
|
||||
* 比较两个日期是否为同一周
|
||||
*
|
||||
* @param cal1 日期1
|
||||
* @param cal2 日期2
|
||||
* @param cal1 日期1
|
||||
* @param cal2 日期2
|
||||
* @param isMon 是否为周一。国内第一天为星期一,国外第一天为星期日
|
||||
* @return 是否为同一周
|
||||
* @since 5.7.21
|
||||
@ -443,28 +445,6 @@ public class CalendarUtil {
|
||||
return date1.getTimeInMillis() == date2.getTimeInMillis();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得指定日期区间内的年份和季度<br>
|
||||
*
|
||||
* @param startDate 起始日期(包含)
|
||||
* @param endDate 结束日期(包含)
|
||||
* @return 季度列表 ,元素类似于 20132
|
||||
* @since 4.1.15
|
||||
*/
|
||||
public static LinkedHashSet<String> yearAndQuarter(long startDate, final long endDate) {
|
||||
final 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年第一季度
|
||||
@ -637,51 +617,7 @@ public class CalendarUtil {
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算相对于dateToCompare的年龄,长用于计算指定生日在某年的年龄
|
||||
*
|
||||
* @param birthday 生日
|
||||
* @param dateToCompare 需要对比的日期
|
||||
* @return 年龄
|
||||
*/
|
||||
protected static int age(final long birthday, final 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);
|
||||
//当前日期,则为0岁
|
||||
if (age == 0){
|
||||
return 0;
|
||||
}
|
||||
|
||||
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);
|
||||
// issue#I6E6ZG,法定生日当天不算年龄,从第二天开始计算
|
||||
if ((false == isLastDayOfMonth || false == isLastDayOfMonthBirth) && dayOfMonth <= dayOfMonthBirth) {
|
||||
// 如果生日在当月,但是未达到生日当天的日期,年龄减一
|
||||
age--;
|
||||
}
|
||||
} else if (month < monthBirth) {
|
||||
// 如果当前月份未达到生日的月份,年龄计算减一
|
||||
age--;
|
||||
}
|
||||
|
||||
return age;
|
||||
}
|
||||
|
||||
// region ----- parse
|
||||
/**
|
||||
* 通过给定的日期格式解析日期时间字符串。<br>
|
||||
* 传入的日期格式会逐个尝试,直到解析成功,返回{@link Calendar}对象,否则抛出{@link DateException}异常。
|
||||
@ -782,4 +718,50 @@ public class CalendarUtil {
|
||||
|
||||
return parser.parse(StrUtil.str(str), new ParsePosition(0), calendar) ? calendar : null;
|
||||
}
|
||||
// endregion
|
||||
|
||||
/**
|
||||
* 计算相对于dateToCompare的年龄,长用于计算指定生日在某年的年龄
|
||||
*
|
||||
* @param birthday 生日
|
||||
* @param dateToCompare 需要对比的日期
|
||||
* @return 年龄
|
||||
*/
|
||||
protected static int age(final long birthday, final 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);
|
||||
//当前日期,则为0岁
|
||||
if (age == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
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);
|
||||
// issue#I6E6ZG,法定生日当天不算年龄,从第二天开始计算
|
||||
if ((false == isLastDayOfMonth || false == isLastDayOfMonthBirth) && dayOfMonth <= dayOfMonthBirth) {
|
||||
// 如果生日在当月,但是未达到生日当天的日期,年龄减一
|
||||
age--;
|
||||
}
|
||||
} else if (month < monthBirth) {
|
||||
// 如果当前月份未达到生日的月份,年龄计算减一
|
||||
age--;
|
||||
}
|
||||
|
||||
return age;
|
||||
}
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ import java.util.regex.Pattern;
|
||||
* <li>yyyy-MM-dd HH:mm:ss.SSS 示例:2022-08-05 12:59:59.559</li>
|
||||
* <li>yyyy-MM-dd HH:mm:ss.SSSZ 示例:2022-08-05 12:59:59.559+0800【东八区中国时区】、2022-08-05 04:59:59.559+0000【冰岛0时区】, 年月日 时分秒 毫秒 时区</li>
|
||||
* <li>yyyy-MM-dd HH:mm:ss.SSSz 示例:2022-08-05 12:59:59.559UTC【世界标准时间=0时区】、2022-08-05T12:59:59.599GMT【冰岛0时区】、2022-08-05T12:59:59.599CST【东八区中国时区】、2022-08-23T03:45:00.599EDT【美国东北纽约时间,-0400】 ,年月日 时分秒 毫秒 时区</li>
|
||||
* <li>yyyy-MM-dd'T'HH:mm:ss.SSS'Z' 示例:2022-08-05T12:59:59.559Z, 其中:''单引号表示转义字符,T:分隔符,Z:一般值UTC,0时区的时间含义</li>
|
||||
* <li>yyyy-MM-dd'T'HH:mm:ss.SSS'Z' 示例:2022-08-05T12:59:59.559Z, 其中:''单引号表示转义字符,T:分隔符,Z:一般指UTC,0时区的时间含义</li>
|
||||
* <li>yyyy-MM-dd'T'HH:mm:ss.SSSZ 示例:2022-08-05T11:59:59.559+0800, 其中:Z,表示时区</li>
|
||||
* <li>yyyy-MM-dd'T'HH:mm:ss.SSSX 示例:2022-08-05T12:59:59.559+08, 其中:X:两位时区,+08表示:东8区,中国时区</li>
|
||||
* <li>yyyy-MM-dd'T'HH:mm:ss.SSSXX 示例:2022-08-05T12:59:59.559+0800, 其中:XX:四位时区</li>
|
||||
@ -61,7 +61,7 @@ import java.util.regex.Pattern;
|
||||
* 如:“09:30 UTC”表示为“09:30Z”或“T0930Z”,其中:Z 是 +00:00 的缩写,意思是 UTC(零时分秒的偏移量).
|
||||
* </p>
|
||||
* <ul>
|
||||
* <li>yyyy-MM-dd'T'HH:mm:ssZ</li>
|
||||
* <li>yyyy-MM-dd'T'HH:mm:ss'Z'</li>
|
||||
* <li>2022-08-23T15:20:46UTC</li>
|
||||
* <li>2022-08-23T15:20:46 UTC</li>
|
||||
* <li>2022-08-23T15:20:46+0000</li>
|
||||
@ -189,15 +189,15 @@ public class DatePattern {
|
||||
/**
|
||||
* ISO8601日期时间格式,精确到毫秒:yyyy-MM-dd HH:mm:ss,SSS
|
||||
*/
|
||||
public static final String ISO8601_PATTERN = "yyyy-MM-dd HH:mm:ss,SSS";
|
||||
public static final String NORM_DATETIME_COMMA_MS_PATTERN = "yyyy-MM-dd HH:mm:ss,SSS";
|
||||
/**
|
||||
* ISO8601日期时间格式,精确到毫秒 {@link FastDateFormat}:yyyy-MM-dd HH:mm:ss,SSS
|
||||
*/
|
||||
public static final FastDateFormat ISO8601_FORMAT = FastDateFormat.getInstance(ISO8601_PATTERN);
|
||||
public static final FastDateFormat NORM_DATETIME_COMMA_MS_FORMAT = FastDateFormat.getInstance(NORM_DATETIME_COMMA_MS_PATTERN);
|
||||
/**
|
||||
* 标准日期格式 {@link DateTimeFormatter}:yyyy-MM-dd HH:mm:ss,SSS
|
||||
*/
|
||||
public static final DateTimeFormatter ISO8601_FORMATTER = createFormatter(ISO8601_PATTERN);
|
||||
public static final DateTimeFormatter NORM_DATETIME_COMMA_MS_FORMATTER = createFormatter(NORM_DATETIME_COMMA_MS_PATTERN);
|
||||
|
||||
/**
|
||||
* 标准日期格式:yyyy年MM月dd日
|
||||
@ -286,8 +286,7 @@ public class DatePattern {
|
||||
.toFormatter();
|
||||
// endregion
|
||||
|
||||
// region Others
|
||||
//================================================== Others ==================================================
|
||||
// region ----- Others
|
||||
/**
|
||||
* HTTP头中日期时间格式:EEE, dd MMM yyyy HH:mm:ss z
|
||||
*/
|
||||
@ -307,76 +306,78 @@ public class DatePattern {
|
||||
public static final FastDateFormat JDK_DATETIME_FORMAT = FastDateFormat.getInstance(JDK_DATETIME_PATTERN, Locale.US);
|
||||
|
||||
/**
|
||||
* UTC时间:yyyy-MM-dd'T'HH:mm:ss
|
||||
* ISO8601日期时间:yyyy-MM-dd'T'HH:mm:ss<br>
|
||||
* 按照ISO8601规范,默认使用T分隔日期和时间,末尾不加Z表示当地时区
|
||||
*/
|
||||
public static final String UTC_SIMPLE_PATTERN = "yyyy-MM-dd'T'HH:mm:ss";
|
||||
public static final String ISO8601_PATTERN = "yyyy-MM-dd'T'HH:mm:ss";
|
||||
/**
|
||||
* UTC时间{@link FastDateFormat}:yyyy-MM-dd'T'HH:mm:ss
|
||||
* ISO8601日期时间{@link FastDateFormat}:yyyy-MM-dd'T'HH:mm:ss
|
||||
*/
|
||||
public static final FastDateFormat UTC_SIMPLE_FORMAT = FastDateFormat.getInstance(UTC_SIMPLE_PATTERN, TimeZone.getTimeZone("UTC"));
|
||||
public static final FastDateFormat ISO8601_FORMAT = FastDateFormat.getInstance(ISO8601_PATTERN);
|
||||
|
||||
/**
|
||||
* UTC时间:yyyy-MM-dd'T'HH:mm:ss.SSS
|
||||
*/
|
||||
public static final String UTC_SIMPLE_MS_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSS";
|
||||
public static final String ISO8601_MS_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSS";
|
||||
/**
|
||||
* UTC时间{@link FastDateFormat}:yyyy-MM-dd'T'HH:mm:ss.SSS
|
||||
*/
|
||||
public static final FastDateFormat UTC_SIMPLE_MS_FORMAT = FastDateFormat.getInstance(UTC_SIMPLE_MS_PATTERN, TimeZone.getTimeZone("UTC"));
|
||||
public static final FastDateFormat ISO8601_MS_FORMAT = FastDateFormat.getInstance(ISO8601_MS_PATTERN);
|
||||
|
||||
/**
|
||||
* UTC时间:yyyy-MM-dd'T'HH:mm:ss'Z'
|
||||
* UTC时间:yyyy-MM-dd'T'HH:mm:ss'Z'<br>
|
||||
* 按照ISO8601规范,后缀加Z表示UTC时间
|
||||
*/
|
||||
public static final String UTC_PATTERN = "yyyy-MM-dd'T'HH:mm:ss'Z'";
|
||||
/**
|
||||
* UTC时间{@link FastDateFormat}:yyyy-MM-dd'T'HH:mm:ss'Z'
|
||||
* ISO8601时间{@link FastDateFormat}:yyyy-MM-dd'T'HH:mm:ss'Z'
|
||||
*/
|
||||
public static final FastDateFormat UTC_FORMAT = FastDateFormat.getInstance(UTC_PATTERN, TimeZone.getTimeZone("UTC"));
|
||||
public static final FastDateFormat UTC_FORMAT = FastDateFormat.getInstance(UTC_PATTERN, ZoneUtil.ZONE_UTC);
|
||||
|
||||
/**
|
||||
* UTC时间:yyyy-MM-dd'T'HH:mm:ssZ
|
||||
* ISO8601时间:yyyy-MM-dd'T'HH:mm:ssZ,Z表示一个时间偏移,如+0800
|
||||
*/
|
||||
public static final String UTC_WITH_ZONE_OFFSET_PATTERN = "yyyy-MM-dd'T'HH:mm:ssZ";
|
||||
public static final String ISO8601_WITH_ZONE_OFFSET_PATTERN = "yyyy-MM-dd'T'HH:mm:ssZ";
|
||||
/**
|
||||
* UTC时间{@link FastDateFormat}:yyyy-MM-dd'T'HH:mm:ssZ
|
||||
* ISO8601时间{@link FastDateFormat}:yyyy-MM-dd'T'HH:mm:ssZ,Z表示一个时间偏移,如+0800
|
||||
*/
|
||||
public static final FastDateFormat UTC_WITH_ZONE_OFFSET_FORMAT = FastDateFormat.getInstance(UTC_WITH_ZONE_OFFSET_PATTERN, TimeZone.getTimeZone("UTC"));
|
||||
public static final FastDateFormat ISO8601_WITH_ZONE_OFFSET_FORMAT = FastDateFormat.getInstance(ISO8601_WITH_ZONE_OFFSET_PATTERN);
|
||||
|
||||
/**
|
||||
* UTC时间:yyyy-MM-dd'T'HH:mm:ssXXX
|
||||
* ISO8601时间:yyyy-MM-dd'T'HH:mm:ssXXX
|
||||
*/
|
||||
public static final String UTC_WITH_XXX_OFFSET_PATTERN = "yyyy-MM-dd'T'HH:mm:ssXXX";
|
||||
public static final String ISO8601_WITH_XXX_OFFSET_PATTERN = "yyyy-MM-dd'T'HH:mm:ssXXX";
|
||||
/**
|
||||
* UTC时间{@link FastDateFormat}:yyyy-MM-dd'T'HH:mm:ssXXX
|
||||
* ISO8601时间{@link FastDateFormat}:yyyy-MM-dd'T'HH:mm:ssXXX
|
||||
*/
|
||||
public static final FastDateFormat UTC_WITH_XXX_OFFSET_FORMAT = FastDateFormat.getInstance(UTC_WITH_XXX_OFFSET_PATTERN);
|
||||
public static final FastDateFormat ISO8601_WITH_XXX_OFFSET_FORMAT = FastDateFormat.getInstance(ISO8601_WITH_XXX_OFFSET_PATTERN);
|
||||
|
||||
/**
|
||||
* UTC时间:yyyy-MM-dd'T'HH:mm:ss.SSS'Z'
|
||||
* ISO8601时间:yyyy-MM-dd'T'HH:mm:ss.SSS'Z'
|
||||
*/
|
||||
public static final String UTC_MS_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";
|
||||
/**
|
||||
* UTC时间{@link FastDateFormat}:yyyy-MM-dd'T'HH:mm:ss.SSS'Z'
|
||||
* ISO8601时间{@link FastDateFormat}:yyyy-MM-dd'T'HH:mm:ss.SSS'Z'
|
||||
*/
|
||||
public static final FastDateFormat UTC_MS_FORMAT = FastDateFormat.getInstance(UTC_MS_PATTERN, TimeZone.getTimeZone("UTC"));
|
||||
public static final FastDateFormat UTC_MS_FORMAT = FastDateFormat.getInstance(UTC_MS_PATTERN, ZoneUtil.ZONE_UTC);
|
||||
|
||||
/**
|
||||
* UTC时间:yyyy-MM-dd'T'HH:mm:ss.SSSZ
|
||||
* ISO8601时间:yyyy-MM-dd'T'HH:mm:ss.SSSZ
|
||||
*/
|
||||
public static final String UTC_MS_WITH_ZONE_OFFSET_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSSZ";
|
||||
public static final String ISO8601_MS_WITH_ZONE_OFFSET_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSSZ";
|
||||
/**
|
||||
* UTC时间{@link FastDateFormat}:yyyy-MM-dd'T'HH:mm:ss.SSSZ
|
||||
* ISO8601时间{@link FastDateFormat}:yyyy-MM-dd'T'HH:mm:ss.SSSZ
|
||||
*/
|
||||
public static final FastDateFormat UTC_MS_WITH_ZONE_OFFSET_FORMAT = FastDateFormat.getInstance(UTC_MS_WITH_ZONE_OFFSET_PATTERN, TimeZone.getTimeZone("UTC"));
|
||||
public static final FastDateFormat ISO8601_MS_WITH_ZONE_OFFSET_FORMAT = FastDateFormat.getInstance(ISO8601_MS_WITH_ZONE_OFFSET_PATTERN);
|
||||
|
||||
/**
|
||||
* UTC时间:yyyy-MM-dd'T'HH:mm:ss.SSSXXX
|
||||
* ISO8601时间:yyyy-MM-dd'T'HH:mm:ss.SSSXXX
|
||||
*/
|
||||
public static final String UTC_MS_WITH_XXX_OFFSET_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX";
|
||||
public static final String ISO8601_MS_WITH_XXX_OFFSET_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX";
|
||||
/**
|
||||
* UTC时间{@link FastDateFormat}:yyyy-MM-dd'T'HH:mm:ss.SSSXXX
|
||||
*/
|
||||
public static final FastDateFormat UTC_MS_WITH_XXX_OFFSET_FORMAT = FastDateFormat.getInstance(UTC_MS_WITH_XXX_OFFSET_PATTERN);
|
||||
public static final FastDateFormat ISO8601_MS_WITH_XXX_OFFSET_FORMAT = FastDateFormat.getInstance(ISO8601_MS_WITH_XXX_OFFSET_PATTERN);
|
||||
// endregion
|
||||
|
||||
/**
|
||||
|
@ -5,12 +5,7 @@ import cn.hutool.core.comparator.CompareUtil;
|
||||
import cn.hutool.core.date.format.DatePrinter;
|
||||
import cn.hutool.core.date.format.FastDateFormat;
|
||||
import cn.hutool.core.date.format.GlobalCustomFormat;
|
||||
import cn.hutool.core.date.format.parser.CSTDateParser;
|
||||
import cn.hutool.core.date.format.parser.NormalDateParser;
|
||||
import cn.hutool.core.date.format.parser.PositionDateParser;
|
||||
import cn.hutool.core.date.format.parser.PureDateParser;
|
||||
import cn.hutool.core.date.format.parser.TimeParser;
|
||||
import cn.hutool.core.date.format.parser.UTCDateParser;
|
||||
import cn.hutool.core.date.format.parser.*;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.math.NumberUtil;
|
||||
import cn.hutool.core.regex.PatternPool;
|
||||
@ -26,14 +21,7 @@ import java.time.LocalDateTime;
|
||||
import java.time.Year;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.temporal.TemporalAccessor;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
@ -513,21 +501,7 @@ public class DateUtil extends CalendarUtil {
|
||||
return yearAndQuarter(calendar(date));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得指定日期区间内的年份和季节<br>
|
||||
*
|
||||
* @param startDate 起始日期(包含)
|
||||
* @param endDate 结束日期(包含)
|
||||
* @return 季度列表 ,元素类似于 20132
|
||||
*/
|
||||
public static LinkedHashSet<String> yearAndQuarter(final Date startDate, final Date endDate) {
|
||||
if (startDate == null || endDate == null) {
|
||||
return new LinkedHashSet<>(0);
|
||||
}
|
||||
return yearAndQuarter(startDate.getTime(), endDate.getTime());
|
||||
}
|
||||
// ------------------------------------ Format start ----------------------------------------------
|
||||
|
||||
// region ----- format
|
||||
/**
|
||||
* 格式化日期时间<br>
|
||||
* 格式 yyyy-MM-dd HH:mm:ss
|
||||
@ -696,10 +670,9 @@ public class DateUtil extends CalendarUtil {
|
||||
|
||||
return CalendarUtil.formatChineseDate(CalendarUtil.calendar(date), withTime);
|
||||
}
|
||||
// ------------------------------------ Format end ----------------------------------------------
|
||||
|
||||
// ------------------------------------ Parse start ----------------------------------------------
|
||||
// endregion
|
||||
|
||||
// region ----- parse
|
||||
/**
|
||||
* 构建DateTime对象
|
||||
*
|
||||
@ -840,8 +813,8 @@ public class DateUtil extends CalendarUtil {
|
||||
// Wed Aug 01 00:00:00 CST 2012
|
||||
return CSTDateParser.INSTANCE.parse(dateStr);
|
||||
} else if (StrUtil.contains(dateStr, 'T')) {
|
||||
// UTC时间
|
||||
return UTCDateParser.INSTANCE.parse(dateStr);
|
||||
// ISO8601标准时间
|
||||
return ISO8601DateParser.INSTANCE.parse(dateStr);
|
||||
}
|
||||
|
||||
//标准日期格式(包括单个数字的日期时间)
|
||||
@ -853,10 +826,9 @@ public class DateUtil extends CalendarUtil {
|
||||
// 没有更多匹配的时间格式
|
||||
throw new DateException("No format fit for date String [{}] !", dateStr);
|
||||
}
|
||||
// ------------------------------------ Parse end ----------------------------------------------
|
||||
|
||||
// ------------------------------------ Offset start ----------------------------------------------
|
||||
// endregion
|
||||
|
||||
// region ----- offset
|
||||
/**
|
||||
* 修改日期为某个时间字段起始时间
|
||||
*
|
||||
@ -1096,7 +1068,6 @@ public class DateUtil extends CalendarUtil {
|
||||
public static DateTime endOfYear(final Date date) {
|
||||
return new DateTime(endOfYear(calendar(date)));
|
||||
}
|
||||
// --------------------------------------------------- Offset for now
|
||||
|
||||
/**
|
||||
* 昨天
|
||||
@ -1243,9 +1214,9 @@ public class DateUtil extends CalendarUtil {
|
||||
public static DateTime offset(final Date date, final DateField dateField, final int offset) {
|
||||
return dateNew(date).offset(dateField, offset);
|
||||
}
|
||||
// endregion
|
||||
|
||||
// ------------------------------------ Offset end ----------------------------------------------
|
||||
|
||||
// region ----- between
|
||||
/**
|
||||
* 判断两个日期相差的时长,只保留绝对值
|
||||
*
|
||||
@ -1351,7 +1322,9 @@ public class DateUtil extends CalendarUtil {
|
||||
public static long betweenYear(final Date beginDate, final Date endDate, final boolean isReset) {
|
||||
return new DateBetween(beginDate, endDate).betweenYear(isReset);
|
||||
}
|
||||
// endregion
|
||||
|
||||
// region ----- formatBetween
|
||||
/**
|
||||
* 格式化日期间隔输出
|
||||
*
|
||||
@ -1397,6 +1370,7 @@ public class DateUtil extends CalendarUtil {
|
||||
public static String formatBetween(final long betweenMs) {
|
||||
return new BetweenFormatter(betweenMs, BetweenFormatter.Level.MILLISECOND).format();
|
||||
}
|
||||
// endregion
|
||||
|
||||
/**
|
||||
* 当前日期是否在日期指定范围内<br>
|
||||
@ -1992,7 +1966,7 @@ public class DateUtil extends CalendarUtil {
|
||||
* @return 单位简写名称
|
||||
* @since 5.7.16
|
||||
*/
|
||||
public static String getShotName(final TimeUnit unit) {
|
||||
public static String getShortName(final TimeUnit unit) {
|
||||
switch (unit) {
|
||||
case NANOSECONDS:
|
||||
return "ns";
|
||||
|
@ -3,23 +3,29 @@ package cn.hutool.core.date;
|
||||
/**
|
||||
* 季度枚举
|
||||
*
|
||||
* @author zhfish(https : / / github.com / zhfish)
|
||||
* @see #Q1
|
||||
* @see #Q2
|
||||
* @see #Q3
|
||||
* @see #Q4
|
||||
*
|
||||
* @author zhfish(https://github.com/zhfish)
|
||||
*
|
||||
*/
|
||||
public enum Quarter {
|
||||
|
||||
/** 第一季度 */
|
||||
/**
|
||||
* 第一季度
|
||||
*/
|
||||
Q1(1),
|
||||
/** 第二季度 */
|
||||
/**
|
||||
* 第二季度
|
||||
*/
|
||||
Q2(2),
|
||||
/** 第三季度 */
|
||||
/**
|
||||
* 第三季度
|
||||
*/
|
||||
Q3(3),
|
||||
/** 第四季度 */
|
||||
/**
|
||||
* 第四季度
|
||||
*/
|
||||
Q4(4);
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
@ -29,6 +35,11 @@ public enum Quarter {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取季度值
|
||||
*
|
||||
* @return 季度值
|
||||
*/
|
||||
public int getValue() {
|
||||
return this.value;
|
||||
}
|
||||
@ -36,13 +47,12 @@ public enum Quarter {
|
||||
/**
|
||||
* 将 季度int转换为Season枚举对象<br>
|
||||
*
|
||||
* @param intValue 季度int表示
|
||||
* @return {@code Quarter}
|
||||
* @see #Q1
|
||||
* @see #Q2
|
||||
* @see #Q3
|
||||
* @see #Q4
|
||||
*
|
||||
* @param intValue 季度int表示
|
||||
* @return {@link Quarter}
|
||||
*/
|
||||
public static Quarter of(final int intValue) {
|
||||
switch (intValue) {
|
||||
|
@ -1,6 +1,6 @@
|
||||
package cn.hutool.core.date;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.io.file.FileUtil;
|
||||
import cn.hutool.core.text.StrUtil;
|
||||
|
||||
import java.text.NumberFormat;
|
||||
@ -352,7 +352,7 @@ public class StopWatch {
|
||||
unit = TimeUnit.NANOSECONDS;
|
||||
}
|
||||
return StrUtil.format("StopWatch '{}': running time = {} {}",
|
||||
this.id, getTotal(unit), DateUtil.getShotName(unit));
|
||||
this.id, getTotal(unit), DateUtil.getShortName(unit));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -382,20 +382,27 @@ public class StopWatch {
|
||||
sb.append("No task info kept");
|
||||
} else {
|
||||
sb.append("---------------------------------------------").append(FileUtil.getLineSeparator());
|
||||
sb.append(DateUtil.getShotName(unit)).append(" % Task name").append(FileUtil.getLineSeparator());
|
||||
sb.append(DateUtil.getShortName(unit)).append(" % Task name").append(FileUtil.getLineSeparator());
|
||||
sb.append("---------------------------------------------").append(FileUtil.getLineSeparator());
|
||||
|
||||
final NumberFormat nf = NumberFormat.getNumberInstance();
|
||||
nf.setMinimumIntegerDigits(9);
|
||||
nf.setGroupingUsed(false);
|
||||
|
||||
final NumberFormat pf = NumberFormat.getPercentInstance();
|
||||
pf.setMinimumIntegerDigits(2);
|
||||
pf.setGroupingUsed(false);
|
||||
|
||||
for (final TaskInfo task : getTaskInfo()) {
|
||||
sb.append(nf.format(task.getTime(unit))).append(" ");
|
||||
sb.append(pf.format((double) task.getTimeNanos() / getTotalTimeNanos())).append(" ");
|
||||
final String taskTimeStr = nf.format(task.getTime(unit));
|
||||
sb.append(taskTimeStr);
|
||||
if(taskTimeStr.length() < 11){
|
||||
sb.append(StrUtil.repeat(' ', 11 - taskTimeStr.length()));
|
||||
}
|
||||
|
||||
final String percentStr = pf.format((double) task.getTimeNanos() / getTotalTimeNanos());
|
||||
if(percentStr.length() < 4){
|
||||
sb.append(StrUtil.repeat(' ', 4 - percentStr.length()));
|
||||
}
|
||||
sb.append(percentStr).append(" ");
|
||||
sb.append(task.getTaskName()).append(FileUtil.getLineSeparator());
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ import java.util.concurrent.TimeUnit;
|
||||
* System.currentTimeMillis()的调用比new一个普通对象要耗时的多(具体耗时高出多少我还没测试过,有人说是100倍左右)
|
||||
* System.currentTimeMillis()之所以慢是因为去跟系统打了一次交道
|
||||
* 后台定时更新时钟,JVM退出时,线程自动回收
|
||||
*
|
||||
* <p>
|
||||
* see: <a href="http://git.oschina.net/yu120/sequence">http://git.oschina.net/yu120/sequence</a>
|
||||
* @author lry, looly
|
||||
*/
|
||||
|
@ -164,8 +164,6 @@ public class TemporalAccessorUtil extends TemporalUtil{
|
||||
result = ((OffsetTime) temporalAccessor).atDate(LocalDate.now()).toInstant();
|
||||
} else {
|
||||
// issue#1891@Github
|
||||
// Instant.from不能完成日期转换
|
||||
//result = Instant.from(temporalAccessor);
|
||||
result = toInstant(TimeUtil.of(temporalAccessor));
|
||||
}
|
||||
|
||||
|
@ -1,30 +1,18 @@
|
||||
package cn.hutool.core.date;
|
||||
|
||||
import cn.hutool.core.date.format.GlobalCustomFormat;
|
||||
import cn.hutool.core.lang.func.LambdaUtil;
|
||||
import cn.hutool.core.text.StrUtil;
|
||||
import cn.hutool.core.util.ObjUtil;
|
||||
|
||||
import java.time.DayOfWeek;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.Period;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.*;
|
||||
import java.time.chrono.ChronoLocalDate;
|
||||
import java.time.chrono.ChronoLocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.temporal.ChronoField;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.time.temporal.Temporal;
|
||||
import java.time.temporal.TemporalAccessor;
|
||||
import java.time.temporal.TemporalAdjusters;
|
||||
import java.time.temporal.TemporalUnit;
|
||||
import java.time.temporal.WeekFields;
|
||||
import java.time.temporal.*;
|
||||
import java.util.Date;
|
||||
import java.util.TimeZone;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* JDK8+中的{@link java.time} 工具类封装
|
||||
@ -35,11 +23,6 @@ import java.util.TimeZone;
|
||||
* @since 6.0.0
|
||||
*/
|
||||
public class TimeUtil extends TemporalAccessorUtil {
|
||||
/**
|
||||
* UTC 的 ZoneID
|
||||
*/
|
||||
public static final ZoneId ZONE_ID_UTC = ZoneId.of("UTC");
|
||||
|
||||
/**
|
||||
* 当前时间,默认时区
|
||||
*
|
||||
@ -65,7 +48,7 @@ public class TimeUtil extends TemporalAccessorUtil {
|
||||
* @return {@link LocalDateTime}
|
||||
*/
|
||||
public static LocalDateTime ofUTC(final Instant instant) {
|
||||
return of(instant, ZONE_ID_UTC);
|
||||
return of(instant, ZoneUtil.ZONE_ID_UTC);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -234,7 +217,7 @@ public class TimeUtil extends TemporalAccessorUtil {
|
||||
|
||||
/**
|
||||
* 解析日期时间字符串为{@link LocalDateTime},格式支持日期时间、日期、时间<br>
|
||||
* 如果formatter为{code null},则使用{@link DateTimeFormatter#ISO_LOCAL_DATE_TIME}
|
||||
* 如果formatter为{@code null},则使用{@link DateTimeFormatter#ISO_LOCAL_DATE_TIME}
|
||||
*
|
||||
* @param text 日期时间字符串
|
||||
* @param formatter 日期格式化器,预定义的格式见:{@link DateTimeFormatter}
|
||||
@ -355,6 +338,17 @@ public class TimeUtil extends TemporalAccessorUtil {
|
||||
return format(date, DatePattern.NORM_DATE_FORMATTER);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 格式化时间函数
|
||||
*
|
||||
* @param dateTimeFormatter {@link DateTimeFormatter}
|
||||
* @return 格式化时间的函数
|
||||
*/
|
||||
public static Function<TemporalAccessor, String> formatFunc(final DateTimeFormatter dateTimeFormatter) {
|
||||
return LambdaUtil.toFunction(TimeUtil::format, dateTimeFormatter);
|
||||
}
|
||||
|
||||
/**
|
||||
* 日期偏移,根据field不同加不同值(偏移会修改传入的对象)
|
||||
*
|
||||
|
@ -11,6 +11,15 @@ import java.util.TimeZone;
|
||||
*/
|
||||
public class ZoneUtil {
|
||||
|
||||
/**
|
||||
* UTC 的 ZoneID
|
||||
*/
|
||||
public static final TimeZone ZONE_UTC = TimeZone.getTimeZone("UTC");
|
||||
/**
|
||||
* UTC 的 TimeZone
|
||||
*/
|
||||
public static final ZoneId ZONE_ID_UTC = ZONE_UTC.toZoneId();
|
||||
|
||||
/**
|
||||
* {@link ZoneId}转换为{@link TimeZone},{@code null}则返回系统默认值
|
||||
*
|
||||
|
@ -573,17 +573,11 @@ public class FastDatePrinter extends SimpleDateBasic implements DatePrinter {
|
||||
mValue = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public int estimateLength() {
|
||||
return mValue.length();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
|
||||
buffer.append(mValue);
|
||||
@ -610,9 +604,6 @@ public class FastDatePrinter extends SimpleDateBasic implements DatePrinter {
|
||||
mValues = values;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public int estimateLength() {
|
||||
int max = 0;
|
||||
@ -625,9 +616,6 @@ public class FastDatePrinter extends SimpleDateBasic implements DatePrinter {
|
||||
return max;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
|
||||
buffer.append(mValues[calendar.get(mField)]);
|
||||
@ -651,25 +639,16 @@ public class FastDatePrinter extends SimpleDateBasic implements DatePrinter {
|
||||
mField = field;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public int estimateLength() {
|
||||
return 4;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
|
||||
appendTo(buffer, calendar.get(mField));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public final void appendTo(final Appendable buffer, final int value) throws IOException {
|
||||
if (value < 10) {
|
||||
@ -697,25 +676,16 @@ public class FastDatePrinter extends SimpleDateBasic implements DatePrinter {
|
||||
UnpaddedMonthField() {
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public int estimateLength() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
|
||||
appendTo(buffer, calendar.get(Calendar.MONTH) + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public final void appendTo(final Appendable buffer, final int value) throws IOException {
|
||||
if (value < 10) {
|
||||
@ -750,25 +720,16 @@ public class FastDatePrinter extends SimpleDateBasic implements DatePrinter {
|
||||
mSize = size;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public int estimateLength() {
|
||||
return mSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
|
||||
appendTo(buffer, calendar.get(mField));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public final void appendTo(final Appendable buffer, final int value) throws IOException {
|
||||
appendFullDigits(buffer, value, mSize);
|
||||
@ -792,25 +753,16 @@ public class FastDatePrinter extends SimpleDateBasic implements DatePrinter {
|
||||
mField = field;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public int estimateLength() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
|
||||
appendTo(buffer, calendar.get(mField));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public final void appendTo(final Appendable buffer, final int value) throws IOException {
|
||||
if (value < 100) {
|
||||
@ -835,25 +787,16 @@ public class FastDatePrinter extends SimpleDateBasic implements DatePrinter {
|
||||
TwoDigitYearField() {
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public int estimateLength() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
|
||||
appendTo(buffer, calendar.get(Calendar.YEAR) % 100);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public final void appendTo(final Appendable buffer, final int value) throws IOException {
|
||||
appendDigits(buffer, value);
|
||||
@ -874,25 +817,16 @@ public class FastDatePrinter extends SimpleDateBasic implements DatePrinter {
|
||||
TwoDigitMonthField() {
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public int estimateLength() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
|
||||
appendTo(buffer, calendar.get(Calendar.MONTH) + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public final void appendTo(final Appendable buffer, final int value) throws IOException {
|
||||
appendDigits(buffer, value);
|
||||
@ -916,17 +850,11 @@ public class FastDatePrinter extends SimpleDateBasic implements DatePrinter {
|
||||
mRule = rule;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public int estimateLength() {
|
||||
return mRule.estimateLength();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
|
||||
int value = calendar.get(Calendar.HOUR);
|
||||
@ -936,9 +864,6 @@ public class FastDatePrinter extends SimpleDateBasic implements DatePrinter {
|
||||
mRule.appendTo(buffer, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void appendTo(final Appendable buffer, final int value) throws IOException {
|
||||
mRule.appendTo(buffer, value);
|
||||
@ -962,17 +887,11 @@ public class FastDatePrinter extends SimpleDateBasic implements DatePrinter {
|
||||
mRule = rule;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public int estimateLength() {
|
||||
return mRule.estimateLength();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
|
||||
int value = calendar.get(Calendar.HOUR_OF_DAY);
|
||||
@ -982,9 +901,6 @@ public class FastDatePrinter extends SimpleDateBasic implements DatePrinter {
|
||||
mRule.appendTo(buffer, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void appendTo(final Appendable buffer, final int value) throws IOException {
|
||||
mRule.appendTo(buffer, value);
|
||||
@ -1103,9 +1019,6 @@ public class FastDatePrinter extends SimpleDateBasic implements DatePrinter {
|
||||
mDaylight = getTimeZoneDisplay(timeZone, true, style, locale);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public int estimateLength() {
|
||||
// We have no access to the Calendar object that will be passed to
|
||||
@ -1114,9 +1027,6 @@ public class FastDatePrinter extends SimpleDateBasic implements DatePrinter {
|
||||
return Math.max(mStandard.length(), mDaylight.length());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
|
||||
final TimeZone zone = calendar.getTimeZone();
|
||||
@ -1148,17 +1058,11 @@ public class FastDatePrinter extends SimpleDateBasic implements DatePrinter {
|
||||
mColon = colon;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public int estimateLength() {
|
||||
return 5;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
|
||||
|
||||
@ -1227,17 +1131,11 @@ public class FastDatePrinter extends SimpleDateBasic implements DatePrinter {
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public int estimateLength() {
|
||||
return length;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void appendTo(final Appendable buffer, final Calendar calendar) throws IOException {
|
||||
int offset = calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET);
|
||||
@ -1298,17 +1196,11 @@ public class FastDatePrinter extends SimpleDateBasic implements DatePrinter {
|
||||
mLocale = locale;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return (mStyle * 31 + mLocale.hashCode()) * 31 + mTimeZone.hashCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
if (this == obj) {
|
||||
|
@ -18,7 +18,13 @@ import java.util.function.Function;
|
||||
*/
|
||||
public class GlobalCustomFormat {
|
||||
|
||||
/**
|
||||
* 格式:秒时间戳(Unix时间戳)
|
||||
*/
|
||||
public static final String FORMAT_SECONDS = "#sss";
|
||||
/**
|
||||
* 格式:毫秒时间戳
|
||||
*/
|
||||
public static final String FORMAT_MILLISECONDS = "#SSS";
|
||||
|
||||
private static final Map<CharSequence, Function<Date, String>> formatterMap;
|
||||
|
@ -16,7 +16,10 @@ import cn.hutool.core.date.format.DefaultDateBasic;
|
||||
* @since 6.0.0
|
||||
*/
|
||||
public class CSTDateParser extends DefaultDateBasic implements DateParser {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
/**
|
||||
* 单例对象
|
||||
*/
|
||||
public static CSTDateParser INSTANCE = new CSTDateParser();
|
||||
|
||||
@Override
|
||||
|
@ -9,7 +9,7 @@ import cn.hutool.core.text.StrUtil;
|
||||
import cn.hutool.core.util.CharUtil;
|
||||
|
||||
/**
|
||||
* UTC日期字符串(JDK的Date对象toString默认格式)解析,支持格式;
|
||||
* ISO8601日期字符串(JDK的Date对象toString默认格式)解析,支持格式;
|
||||
* <ol>
|
||||
* <li>yyyy-MM-dd'T'HH:mm:ss'Z'</li>
|
||||
* <li>yyyy-MM-dd'T'HH:mm:ss.SSS'Z'</li>
|
||||
@ -22,13 +22,13 @@ import cn.hutool.core.util.CharUtil;
|
||||
* @author looly
|
||||
* @since 6.0.0
|
||||
*/
|
||||
public class UTCDateParser extends DefaultDateBasic implements DateParser {
|
||||
public class ISO8601DateParser extends DefaultDateBasic implements DateParser {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 单例对象
|
||||
*/
|
||||
public static UTCDateParser INSTANCE = new UTCDateParser();
|
||||
public static ISO8601DateParser INSTANCE = new ISO8601DateParser();
|
||||
|
||||
@Override
|
||||
public DateTime parse(String source) {
|
||||
@ -61,10 +61,10 @@ public class UTCDateParser extends DefaultDateBasic implements DateParser {
|
||||
if (StrUtil.contains(source, CharUtil.DOT)) {
|
||||
// 带毫秒,格式类似:2018-09-13T05:34:31.999+08:00
|
||||
source = normalizeMillSeconds(source, ".", "+");
|
||||
return new DateTime(source, DatePattern.UTC_MS_WITH_XXX_OFFSET_FORMAT);
|
||||
return new DateTime(source, DatePattern.ISO8601_MS_WITH_XXX_OFFSET_FORMAT);
|
||||
} else {
|
||||
// 格式类似:2018-09-13T05:34:31+08:00
|
||||
return new DateTime(source, DatePattern.UTC_WITH_XXX_OFFSET_FORMAT);
|
||||
return new DateTime(source, DatePattern.ISO8601_WITH_XXX_OFFSET_FORMAT);
|
||||
}
|
||||
} else if(ReUtil.contains("-\\d{2}:?00", source)){
|
||||
// Issue#2612,类似 2022-09-14T23:59:00-08:00 或者 2022-09-14T23:59:00-0800
|
||||
@ -78,22 +78,22 @@ public class UTCDateParser extends DefaultDateBasic implements DateParser {
|
||||
if (StrUtil.contains(source, CharUtil.DOT)) {
|
||||
// 带毫秒,格式类似:2018-09-13T05:34:31.999-08:00
|
||||
source = normalizeMillSeconds(source, ".", "-");
|
||||
return new DateTime(source, DatePattern.UTC_MS_WITH_XXX_OFFSET_FORMAT);
|
||||
return new DateTime(source, DatePattern.ISO8601_MS_WITH_XXX_OFFSET_FORMAT);
|
||||
} else {
|
||||
// 格式类似:2018-09-13T05:34:31-08:00
|
||||
return new DateTime(source, DatePattern.UTC_WITH_XXX_OFFSET_FORMAT);
|
||||
return new DateTime(source, DatePattern.ISO8601_WITH_XXX_OFFSET_FORMAT);
|
||||
}
|
||||
} else {
|
||||
if (length == DatePattern.UTC_SIMPLE_PATTERN.length() - 2) {
|
||||
if (length == DatePattern.ISO8601_PATTERN.length() - 2) {
|
||||
// 格式类似:2018-09-13T05:34:31
|
||||
return new DateTime(source, DatePattern.UTC_SIMPLE_FORMAT);
|
||||
} else if (length == DatePattern.UTC_SIMPLE_PATTERN.length() - 5) {
|
||||
return new DateTime(source, DatePattern.ISO8601_FORMAT);
|
||||
} else if (length == DatePattern.ISO8601_PATTERN.length() - 5) {
|
||||
// 格式类似:2018-09-13T05:34
|
||||
return new DateTime(source + ":00", DatePattern.UTC_SIMPLE_FORMAT);
|
||||
return new DateTime(source + ":00", DatePattern.ISO8601_FORMAT);
|
||||
} else if (StrUtil.contains(source, CharUtil.DOT)) {
|
||||
// 可能为: 2021-03-17T06:31:33.99
|
||||
source = normalizeMillSeconds(source, ".", null);
|
||||
return new DateTime(source, DatePattern.UTC_SIMPLE_MS_FORMAT);
|
||||
return new DateTime(source, DatePattern.ISO8601_MS_FORMAT);
|
||||
}
|
||||
}
|
||||
// 没有更多匹配的时间格式
|
||||
@ -109,6 +109,7 @@ public class UTCDateParser extends DefaultDateBasic implements DateParser {
|
||||
* @param after 毫秒部分的后一个字符
|
||||
* @return 规范之后的毫秒部分
|
||||
*/
|
||||
@SuppressWarnings("SameParameterValue")
|
||||
private static String normalizeMillSeconds(final String dateStr, final CharSequence before, final CharSequence after) {
|
||||
if (StrUtil.isBlank(after)) {
|
||||
final String millOrNaco = StrUtil.subPre(StrUtil.subAfter(dateStr, before, true), 3);
|
@ -21,7 +21,11 @@ import cn.hutool.core.util.CharUtil;
|
||||
* @since 6.0.0
|
||||
*/
|
||||
public class NormalDateParser extends DefaultDateBasic implements DateParser {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 单例
|
||||
*/
|
||||
public static NormalDateParser INSTANCE = new NormalDateParser();
|
||||
|
||||
@Override
|
||||
|
@ -16,6 +16,7 @@ import java.util.Locale;
|
||||
* @since 6.0.0
|
||||
*/
|
||||
public class PatternsDateParser extends DefaultDateBasic implements DateParser {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 创建 PatternsDateParser
|
||||
|
@ -18,7 +18,11 @@ import cn.hutool.core.date.format.DefaultDateBasic;
|
||||
* @since 6.0.0
|
||||
*/
|
||||
public class PureDateParser extends DefaultDateBasic implements DateParser {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 单例
|
||||
*/
|
||||
public static PureDateParser INSTANCE = new PureDateParser();
|
||||
|
||||
@Override
|
||||
|
@ -17,7 +17,11 @@ import cn.hutool.core.text.StrUtil;
|
||||
* @since 6.0.0
|
||||
*/
|
||||
public class TimeParser extends DefaultDateBasic implements DateParser {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 单例
|
||||
*/
|
||||
public static TimeParser INSTANCE = new TimeParser();
|
||||
|
||||
@Override
|
||||
|
@ -6,28 +6,66 @@ package cn.hutool.core.exceptions;
|
||||
* @author looly
|
||||
*/
|
||||
public class InvocationTargetRuntimeException extends UtilException {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param e 异常
|
||||
*/
|
||||
public InvocationTargetRuntimeException(final Throwable e) {
|
||||
super(e);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param message 消息
|
||||
*/
|
||||
public InvocationTargetRuntimeException(final String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param messageTemplate 消息模板
|
||||
* @param params 参数
|
||||
*/
|
||||
public InvocationTargetRuntimeException(final String messageTemplate, final Object... params) {
|
||||
super(messageTemplate, params);
|
||||
}
|
||||
|
||||
public InvocationTargetRuntimeException(final String message, final Throwable throwable) {
|
||||
super(message, throwable);
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param message 消息
|
||||
* @param cause 被包装的子异常
|
||||
*/
|
||||
public InvocationTargetRuntimeException(final String message, final Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public InvocationTargetRuntimeException(final String message, final Throwable throwable, final boolean enableSuppression, final boolean writableStackTrace) {
|
||||
super(message, throwable, enableSuppression, writableStackTrace);
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param message 消息
|
||||
* @param cause 被包装的子异常
|
||||
* @param enableSuppression 是否启用抑制
|
||||
* @param writableStackTrace 堆栈跟踪是否应该是可写的
|
||||
*/
|
||||
public InvocationTargetRuntimeException(final String message, final Throwable cause, final boolean enableSuppression, final boolean writableStackTrace) {
|
||||
super(message, cause, enableSuppression, writableStackTrace);
|
||||
}
|
||||
|
||||
public InvocationTargetRuntimeException(final Throwable throwable, final String messageTemplate, final Object... params) {
|
||||
super(throwable, messageTemplate, params);
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param cause 被包装的子异常
|
||||
* @param messageTemplate 消息模板
|
||||
* @param params 参数
|
||||
*/
|
||||
public InvocationTargetRuntimeException(final Throwable cause, final String messageTemplate, final Object... params) {
|
||||
super(cause, messageTemplate, params);
|
||||
}
|
||||
}
|
||||
|
@ -10,8 +10,8 @@ import cn.hutool.core.text.StrUtil;
|
||||
public class UtilException extends RuntimeException {
|
||||
private static final long serialVersionUID = 8247610319171014183L;
|
||||
|
||||
public UtilException(final Throwable e) {
|
||||
super(ExceptionUtil.getMessage(e), e);
|
||||
public UtilException(final Throwable cause) {
|
||||
super(ExceptionUtil.getMessage(cause), cause);
|
||||
}
|
||||
|
||||
public UtilException(final String message) {
|
||||
@ -22,15 +22,15 @@ public class UtilException extends RuntimeException {
|
||||
super(StrUtil.format(messageTemplate, params));
|
||||
}
|
||||
|
||||
public UtilException(final String message, final Throwable throwable) {
|
||||
super(message, throwable);
|
||||
public UtilException(final String message, final Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public UtilException(final String message, final Throwable throwable, final boolean enableSuppression, final boolean writableStackTrace) {
|
||||
super(message, throwable, enableSuppression, writableStackTrace);
|
||||
public UtilException(final String message, final Throwable cause, final boolean enableSuppression, final boolean writableStackTrace) {
|
||||
super(message, cause, enableSuppression, writableStackTrace);
|
||||
}
|
||||
|
||||
public UtilException(final Throwable throwable, final String messageTemplate, final Object... params) {
|
||||
super(StrUtil.format(messageTemplate, params), throwable);
|
||||
public UtilException(final Throwable cause, final String messageTemplate, final Object... params) {
|
||||
super(StrUtil.format(messageTemplate, params), cause);
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,11 @@ public class AppendableWriter extends Writer implements Appendable {
|
||||
private final boolean flushable;
|
||||
private boolean closed;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param appendable {@link Appendable}
|
||||
*/
|
||||
public AppendableWriter(final Appendable appendable) {
|
||||
this.appendable = appendable;
|
||||
this.flushable = appendable instanceof Flushable;
|
||||
|
@ -1,6 +1,7 @@
|
||||
package cn.hutool.core.io;
|
||||
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.io.file.FileUtil;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
|
||||
import java.io.File;
|
||||
|
@ -23,7 +23,6 @@ import java.io.ByteArrayOutputStream;
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.Flushable;
|
||||
import java.io.IOException;
|
||||
@ -38,6 +37,8 @@ import java.io.UnsupportedEncodingException;
|
||||
import java.io.Writer;
|
||||
import java.nio.CharBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
import java.util.Objects;
|
||||
|
||||
@ -525,15 +526,27 @@ public class IoUtil extends NioUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件转为{@link FileInputStream}
|
||||
* 文件转为{@link InputStream}
|
||||
*
|
||||
* @param file 文件
|
||||
* @return {@link FileInputStream}
|
||||
* @param file 文件,非空
|
||||
* @return {@link InputStream}
|
||||
*/
|
||||
public static FileInputStream toStream(final File file) {
|
||||
public static InputStream toStream(final File file) {
|
||||
Assert.notNull(file);
|
||||
return toStream(file.toPath());
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件转为{@link InputStream}
|
||||
*
|
||||
* @param path {@link Path},非空
|
||||
* @return {@link InputStream}
|
||||
*/
|
||||
public static InputStream toStream(final Path path) {
|
||||
Assert.notNull(path);
|
||||
try {
|
||||
return new FileInputStream(file);
|
||||
} catch (final FileNotFoundException e) {
|
||||
return Files.newInputStream(path);
|
||||
} catch (final IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
}
|
||||
@ -869,6 +882,19 @@ public class IoUtil extends NioUtil {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭<br>
|
||||
* 关闭失败不会抛出异常
|
||||
*
|
||||
* @param closeable 被关闭的对象
|
||||
* @throws IOException IO异常
|
||||
*/
|
||||
public static void nullSafeClose(final Closeable closeable) throws IOException {
|
||||
if (null != closeable) {
|
||||
closeable.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 尝试关闭指定对象<br>
|
||||
* 判断对象如果实现了{@link AutoCloseable},则调用之
|
||||
|
@ -6,9 +6,9 @@ import cn.hutool.core.io.stream.EmptyOutputStream;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.util.zip.CRC32;
|
||||
import java.util.zip.CheckedInputStream;
|
||||
import java.util.zip.Checksum;
|
||||
@ -57,8 +57,8 @@ public class ChecksumUtil {
|
||||
throw new IllegalArgumentException("Checksums can't be computed on directories");
|
||||
}
|
||||
try {
|
||||
return checksum(new FileInputStream(file), checksum);
|
||||
} catch (final FileNotFoundException e) {
|
||||
return checksum(Files.newInputStream(file.toPath()), checksum);
|
||||
} catch (final IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
@ -1,313 +0,0 @@
|
||||
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.text.StrUtil;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.CopyOption;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* 文件拷贝器<br>
|
||||
* 支持以下几种情况:
|
||||
* <pre>
|
||||
* 1、文件复制到文件
|
||||
* 2、文件复制到目录
|
||||
* 3、目录复制到目录
|
||||
* 4、目录下的文件和目录复制到另一个目录
|
||||
* </pre>
|
||||
*
|
||||
* @author Looly
|
||||
* @since 3.0.9
|
||||
*/
|
||||
public class FileCopier extends SrcToDestCopier<File, FileCopier> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 是否覆盖目标文件
|
||||
*/
|
||||
private boolean isOverride;
|
||||
/**
|
||||
* 是否拷贝所有属性
|
||||
*/
|
||||
private boolean isCopyAttributes;
|
||||
/**
|
||||
* 当拷贝来源是目录时是否只拷贝目录下的内容
|
||||
*/
|
||||
private boolean isCopyContentIfDir;
|
||||
/**
|
||||
* 当拷贝来源是目录时是否只拷贝文件而忽略子目录
|
||||
*/
|
||||
private boolean isOnlyCopyFile;
|
||||
|
||||
//-------------------------------------------------------------------------------------------------------- static method start
|
||||
|
||||
/**
|
||||
* 新建一个文件复制器
|
||||
*
|
||||
* @param srcPath 源文件路径(相对ClassPath路径或绝对路径)
|
||||
* @param destPath 目标文件路径(相对ClassPath路径或绝对路径)
|
||||
* @return this
|
||||
*/
|
||||
public static FileCopier of(final String srcPath, final String destPath) {
|
||||
return new FileCopier(FileUtil.file(srcPath), FileUtil.file(destPath));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新建一个文件复制器
|
||||
*
|
||||
* @param src 源文件
|
||||
* @param dest 目标文件
|
||||
* @return this
|
||||
*/
|
||||
public static FileCopier of(final File src, final File dest) {
|
||||
return new FileCopier(src, dest);
|
||||
}
|
||||
//-------------------------------------------------------------------------------------------------------- static method end
|
||||
|
||||
//-------------------------------------------------------------------------------------------------------- Constructor start
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param src 源文件
|
||||
* @param dest 目标文件
|
||||
*/
|
||||
public FileCopier(final File src, final File dest) {
|
||||
this.src = src;
|
||||
this.dest = dest;
|
||||
}
|
||||
//-------------------------------------------------------------------------------------------------------- Constructor end
|
||||
|
||||
//-------------------------------------------------------------------------------------------------------- Getters and Setters start
|
||||
|
||||
/**
|
||||
* 是否覆盖目标文件
|
||||
*
|
||||
* @return 是否覆盖目标文件
|
||||
*/
|
||||
public boolean isOverride() {
|
||||
return isOverride;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置是否覆盖目标文件
|
||||
*
|
||||
* @param isOverride 是否覆盖目标文件
|
||||
* @return this
|
||||
*/
|
||||
public FileCopier setOverride(final boolean isOverride) {
|
||||
this.isOverride = isOverride;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否拷贝所有属性
|
||||
*
|
||||
* @return 是否拷贝所有属性
|
||||
*/
|
||||
public boolean isCopyAttributes() {
|
||||
return isCopyAttributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置是否拷贝所有属性
|
||||
*
|
||||
* @param isCopyAttributes 是否拷贝所有属性
|
||||
* @return this
|
||||
*/
|
||||
public FileCopier setCopyAttributes(final boolean isCopyAttributes) {
|
||||
this.isCopyAttributes = isCopyAttributes;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 当拷贝来源是目录时是否只拷贝目录下的内容
|
||||
*
|
||||
* @return 当拷贝来源是目录时是否只拷贝目录下的内容
|
||||
*/
|
||||
public boolean isCopyContentIfDir() {
|
||||
return isCopyContentIfDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* 当拷贝来源是目录时是否只拷贝目录下的内容
|
||||
*
|
||||
* @param isCopyContentIfDir 是否只拷贝目录下的内容
|
||||
* @return this
|
||||
*/
|
||||
public FileCopier setCopyContentIfDir(final boolean isCopyContentIfDir) {
|
||||
this.isCopyContentIfDir = isCopyContentIfDir;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 当拷贝来源是目录时是否只拷贝文件而忽略子目录
|
||||
*
|
||||
* @return 当拷贝来源是目录时是否只拷贝文件而忽略子目录
|
||||
* @since 4.1.5
|
||||
*/
|
||||
public boolean isOnlyCopyFile() {
|
||||
return isOnlyCopyFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置当拷贝来源是目录时是否只拷贝文件而忽略子目录
|
||||
*
|
||||
* @param isOnlyCopyFile 当拷贝来源是目录时是否只拷贝文件而忽略子目录
|
||||
* @return this
|
||||
* @since 4.1.5
|
||||
*/
|
||||
public FileCopier setOnlyCopyFile(final boolean isOnlyCopyFile) {
|
||||
this.isOnlyCopyFile = isOnlyCopyFile;
|
||||
return this;
|
||||
}
|
||||
//-------------------------------------------------------------------------------------------------------- Getters and Setters end
|
||||
|
||||
/**
|
||||
* 执行拷贝<br>
|
||||
* 拷贝规则为:
|
||||
* <pre>
|
||||
* 1、源为文件,目标为已存在目录,则拷贝到目录下,文件名不变
|
||||
* 2、源为文件,目标为不存在路径,则目标以文件对待(自动创建父级目录)比如:/dest/aaa,如果aaa不存在,则aaa被当作文件名
|
||||
* 3、源为文件,目标是一个已存在的文件,则当{@link #setOverride(boolean)}设为true时会被覆盖,默认不覆盖
|
||||
* 4、源为目录,目标为已存在目录,当{@link #setCopyContentIfDir(boolean)}为true时,只拷贝目录中的内容到目标目录中,否则整个源目录连同其目录拷贝到目标目录中
|
||||
* 5、源为目录,目标为不存在路径,则自动创建目标为新目录,然后按照规则4复制
|
||||
* 6、源为目录,目标为文件,抛出IO异常
|
||||
* 7、源路径和目标路径相同时,抛出IO异常
|
||||
* </pre>
|
||||
*
|
||||
* @return 拷贝后目标的文件或目录
|
||||
* @throws IORuntimeException IO异常
|
||||
*/
|
||||
@Override
|
||||
public File copy() throws IORuntimeException {
|
||||
final File src = this.src;
|
||||
File dest = this.dest;
|
||||
// check
|
||||
Assert.notNull(src, "Source File is null !");
|
||||
if (false == src.exists()) {
|
||||
throw new IORuntimeException("File not exist: " + src);
|
||||
}
|
||||
Assert.notNull(dest, "Destination File or directiory is null !");
|
||||
if (FileUtil.equals(src, dest)) {
|
||||
throw new IORuntimeException("Files '{}' and '{}' are equal", src, dest);
|
||||
}
|
||||
|
||||
if (src.isDirectory()) {// 复制目录
|
||||
if (dest.exists() && false == dest.isDirectory()) {
|
||||
//源为目录,目标为文件,抛出IO异常
|
||||
throw new IORuntimeException("Src is a directory but dest is a file!");
|
||||
}
|
||||
if (FileUtil.isSub(src, dest)) {
|
||||
throw new IORuntimeException("Dest is a sub directory of src !");
|
||||
}
|
||||
|
||||
final File subTarget = isCopyContentIfDir ? dest : FileUtil.mkdir(FileUtil.file(dest, src.getName()));
|
||||
internalCopyDirContent(src, subTarget);
|
||||
} else {// 复制文件
|
||||
dest = internalCopyFile(src, dest);
|
||||
}
|
||||
return dest;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------- Private method start
|
||||
|
||||
/**
|
||||
* 拷贝目录内容,只用于内部,不做任何安全检查<br>
|
||||
* 拷贝内容的意思为源目录下的所有文件和目录拷贝到另一个目录下,而不拷贝源目录本身
|
||||
*
|
||||
* @param src 源目录
|
||||
* @param dest 目标目录
|
||||
* @throws IORuntimeException IO异常
|
||||
*/
|
||||
private void internalCopyDirContent(final File src, final File dest) throws IORuntimeException {
|
||||
if (null != copyPredicate && false == copyPredicate.test(src)) {
|
||||
//被过滤的目录跳过
|
||||
return;
|
||||
}
|
||||
|
||||
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 (final String file : files) {
|
||||
srcFile = new File(src, file);
|
||||
destFile = this.isOnlyCopyFile ? dest : new File(dest, file);
|
||||
// 递归复制
|
||||
if (srcFile.isDirectory()) {
|
||||
internalCopyDirContent(srcFile, destFile);
|
||||
} else {
|
||||
internalCopyFile(srcFile, destFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 拷贝文件,只用于内部,不做任何安全检查<br>
|
||||
* 情况如下:
|
||||
* <pre>
|
||||
* 1、如果目标是一个不存在的路径,则目标以文件对待(自动创建父级目录)比如:/dest/aaa,如果aaa不存在,则aaa被当作文件名
|
||||
* 2、如果目标是一个已存在的目录,则文件拷贝到此目录下,文件名与原文件名一致
|
||||
* </pre>
|
||||
*
|
||||
* @param src 源文件,必须为文件
|
||||
* @param dest 目标文件,如果非覆盖模式必须为目录
|
||||
* @return 目标文件
|
||||
* @throws IORuntimeException IO异常
|
||||
*/
|
||||
private File internalCopyFile(final File src, File dest) throws IORuntimeException {
|
||||
if (null != copyPredicate && false == copyPredicate.test(src)) {
|
||||
//被过滤的文件跳过
|
||||
return dest;
|
||||
}
|
||||
|
||||
// 如果已经存在目标文件,切为不覆盖模式,跳过之
|
||||
if (dest.exists()) {
|
||||
if (dest.isDirectory()) {
|
||||
//目标为目录,目录下创建同名文件
|
||||
dest = new File(dest, src.getName());
|
||||
}
|
||||
|
||||
if (dest.exists() && false == isOverride) {
|
||||
//非覆盖模式跳过
|
||||
return dest;
|
||||
}
|
||||
} else {
|
||||
//路径不存在则创建父目录
|
||||
FileUtil.mkParentDirs(dest);
|
||||
}
|
||||
|
||||
final ArrayList<CopyOption> optionList = new ArrayList<>(2);
|
||||
if (isOverride) {
|
||||
optionList.add(StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
if (isCopyAttributes) {
|
||||
optionList.add(StandardCopyOption.COPY_ATTRIBUTES);
|
||||
}
|
||||
|
||||
try {
|
||||
Files.copy(src.toPath(), dest.toPath(), optionList.toArray(new CopyOption[0]));
|
||||
} catch (final IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
return dest;
|
||||
}
|
||||
//----------------------------------------------------------------------------------------- Private method end
|
||||
}
|
775
hutool-core/src/main/java/cn/hutool/core/io/FileMagicNumber.java → hutool-core/src/main/java/cn/hutool/core/io/file/FileMagicNumber.java
Executable file → Normal file
775
hutool-core/src/main/java/cn/hutool/core/io/FileMagicNumber.java → hutool-core/src/main/java/cn/hutool/core/io/file/FileMagicNumber.java
Executable file → Normal file
File diff suppressed because it is too large
Load Diff
@ -1,9 +1,10 @@
|
||||
package cn.hutool.core.io.file;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.exceptions.UtilException;
|
||||
import cn.hutool.core.io.IORuntimeException;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.lang.func.SerConsumer;
|
||||
import cn.hutool.core.lang.func.SerFunction;
|
||||
import cn.hutool.core.text.StrUtil;
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
|
||||
@ -14,6 +15,7 @@ import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.file.Files;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
@ -207,14 +209,20 @@ public class FileReader extends FileWrapper {
|
||||
* @return 从文件中read出的数据
|
||||
* @throws IORuntimeException IO异常
|
||||
*/
|
||||
public <T> T read(final ReaderHandler<T> readerHandler) throws IORuntimeException {
|
||||
public <T> T read(final SerFunction<BufferedReader, T> readerHandler) throws IORuntimeException {
|
||||
BufferedReader reader = null;
|
||||
T result;
|
||||
try {
|
||||
reader = FileUtil.getReader(this.file, charset);
|
||||
result = readerHandler.handle(reader);
|
||||
} catch (final IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
result = readerHandler.applying(reader);
|
||||
} catch (final Exception e) {
|
||||
if(e instanceof IOException){
|
||||
throw new IORuntimeException(e);
|
||||
} else if(e instanceof RuntimeException){
|
||||
throw (RuntimeException)e;
|
||||
} else{
|
||||
throw new UtilException(e);
|
||||
}
|
||||
} finally {
|
||||
IoUtil.close(reader);
|
||||
}
|
||||
@ -239,7 +247,7 @@ public class FileReader extends FileWrapper {
|
||||
*/
|
||||
public BufferedInputStream getInputStream() throws IORuntimeException {
|
||||
try {
|
||||
return new BufferedInputStream(new FileInputStream(this.file));
|
||||
return new BufferedInputStream(Files.newInputStream(this.file.toPath()));
|
||||
} catch (final IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
@ -277,19 +285,6 @@ public class FileReader extends FileWrapper {
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------- Interface start
|
||||
/**
|
||||
* Reader处理接口
|
||||
*
|
||||
* @author Luxiaolei
|
||||
*
|
||||
* @param <T> Reader处理返回结果类型
|
||||
*/
|
||||
public interface ReaderHandler<T> {
|
||||
T handle(BufferedReader reader) throws IOException;
|
||||
}
|
||||
// -------------------------------------------------------------------------- Interface end
|
||||
|
||||
/**
|
||||
* 检查文件
|
||||
*
|
||||
|
@ -1,10 +1,11 @@
|
||||
package cn.hutool.core.io;
|
||||
package cn.hutool.core.io.file;
|
||||
|
||||
import cn.hutool.core.codec.HexUtil;
|
||||
import cn.hutool.core.io.IORuntimeException;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.text.StrUtil;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Map;
|
||||
@ -32,7 +33,7 @@ public class FileTypeUtil {
|
||||
* @param extName 文件扩展名
|
||||
* @return 之前已经存在的文件扩展名
|
||||
*/
|
||||
public static String putFileType(String fileStreamHexHead, String extName) {
|
||||
public static String putFileType(final String fileStreamHexHead, final String extName) {
|
||||
return FILE_TYPE_MAP.put(fileStreamHexHead, extName);
|
||||
}
|
||||
|
||||
@ -42,7 +43,7 @@ public class FileTypeUtil {
|
||||
* @param fileStreamHexHead 文件流头部Hex信息
|
||||
* @return 移除的文件扩展名
|
||||
*/
|
||||
public static String removeFileType(String fileStreamHexHead) {
|
||||
public static String removeFileType(final String fileStreamHexHead) {
|
||||
return FILE_TYPE_MAP.remove(fileStreamHexHead);
|
||||
}
|
||||
|
||||
@ -52,13 +53,13 @@ public class FileTypeUtil {
|
||||
* @param fileStreamHexHead 文件流头部16进制字符串
|
||||
* @return 文件类型,未找到为{@code null}
|
||||
*/
|
||||
public static String getType(String fileStreamHexHead) {
|
||||
for (Entry<String, String> fileTypeEntry : FILE_TYPE_MAP.entrySet()) {
|
||||
public static String getType(final String fileStreamHexHead) {
|
||||
for (final Entry<String, String> fileTypeEntry : FILE_TYPE_MAP.entrySet()) {
|
||||
if (StrUtil.startWithIgnoreCase(fileStreamHexHead, fileTypeEntry.getKey())) {
|
||||
return fileTypeEntry.getValue();
|
||||
}
|
||||
}
|
||||
byte[] bytes = (HexUtil.decodeHex(fileStreamHexHead));
|
||||
final byte[] bytes = (HexUtil.decodeHex(fileStreamHexHead));
|
||||
return FileMagicNumber.getMagicNumber(bytes).getExtension();
|
||||
}
|
||||
|
||||
@ -68,8 +69,9 @@ public class FileTypeUtil {
|
||||
* @param in 文件流
|
||||
* @param fileHeadSize 自定义读取文件头部的大小
|
||||
* @return 文件类型,未找到为{@code null}
|
||||
* @throws IORuntimeException IO异常
|
||||
*/
|
||||
public static String getType(InputStream in,int fileHeadSize) throws IORuntimeException {
|
||||
public static String getType(final InputStream in, final int fileHeadSize) throws IORuntimeException {
|
||||
return getType((IoUtil.readHex(in, fileHeadSize,false)));
|
||||
}
|
||||
|
||||
@ -82,7 +84,7 @@ public class FileTypeUtil {
|
||||
* @return 类型,文件的扩展名,未找到为{@code null}
|
||||
* @throws IORuntimeException 读取流引起的异常
|
||||
*/
|
||||
public static String getType(InputStream in,boolean isExact) throws IORuntimeException {
|
||||
public static String getType(final InputStream in, final boolean isExact) throws IORuntimeException {
|
||||
return isExact
|
||||
?getType(readHex8192Upper(in))
|
||||
:getType(readHex64Upper(in));
|
||||
@ -96,7 +98,7 @@ public class FileTypeUtil {
|
||||
* @return 类型,文件的扩展名,未找到为{@code null}
|
||||
* @throws IORuntimeException 读取流引起的异常
|
||||
*/
|
||||
public static String getType(InputStream in) throws IORuntimeException {
|
||||
public static String getType(final InputStream in) throws IORuntimeException {
|
||||
return getType(in,false);
|
||||
}
|
||||
|
||||
@ -116,7 +118,7 @@ public class FileTypeUtil {
|
||||
* @return 类型,文件的扩展名,未找到为{@code null}
|
||||
* @throws IORuntimeException 读取流引起的异常
|
||||
*/
|
||||
public static String getType(InputStream in, String filename) throws IORuntimeException {
|
||||
public static String getType(final InputStream in, final String filename) throws IORuntimeException {
|
||||
return getType(in,filename,false);
|
||||
}
|
||||
|
||||
@ -136,14 +138,14 @@ public class FileTypeUtil {
|
||||
* @return 类型,文件的扩展名,未找到为{@code null}
|
||||
* @throws IORuntimeException 读取流引起的异常
|
||||
*/
|
||||
public static String getType(InputStream in, String filename,boolean isExact) throws IORuntimeException {
|
||||
public static String getType(final InputStream in, final String filename, final boolean isExact) throws IORuntimeException {
|
||||
String typeName = getType(in,isExact);
|
||||
if (null == typeName) {
|
||||
// 未成功识别类型,扩展名辅助识别
|
||||
typeName = FileUtil.extName(filename);
|
||||
typeName = FileNameUtil.extName(filename);
|
||||
} else if ("zip".equals(typeName)) {
|
||||
// zip可能为docx、xlsx、pptx、jar、war、ofd等格式,扩展名辅助判断
|
||||
final String extName = FileUtil.extName(filename);
|
||||
final String extName = FileNameUtil.extName(filename);
|
||||
if ("docx".equalsIgnoreCase(extName)) {
|
||||
typeName = "docx";
|
||||
} else if ("xlsx".equalsIgnoreCase(extName)) {
|
||||
@ -161,7 +163,7 @@ public class FileTypeUtil {
|
||||
}
|
||||
} else if ("jar".equals(typeName)) {
|
||||
// wps编辑过的.xlsx文件与.jar的开头相同,通过扩展名判断
|
||||
final String extName = FileUtil.extName(filename);
|
||||
final String extName = FileNameUtil.extName(filename);
|
||||
if ("xlsx".equalsIgnoreCase(extName)) {
|
||||
typeName = "xlsx";
|
||||
} else if ("docx".equalsIgnoreCase(extName)) {
|
||||
@ -193,8 +195,8 @@ public class FileTypeUtil {
|
||||
* @return 类型,文件的扩展名,未找到为{@code null}
|
||||
* @throws IORuntimeException 读取文件引起的异常
|
||||
*/
|
||||
public static String getType(File file,boolean isExact) throws IORuntimeException {
|
||||
FileInputStream in = null;
|
||||
public static String getType(final File file, final boolean isExact) throws IORuntimeException {
|
||||
InputStream in = null;
|
||||
try {
|
||||
in = IoUtil.toStream(file);
|
||||
return getType(in, file.getName(),isExact);
|
||||
@ -216,7 +218,7 @@ public class FileTypeUtil {
|
||||
* @return 类型,文件的扩展名,未找到为{@code null}
|
||||
* @throws IORuntimeException 读取文件引起的异常
|
||||
*/
|
||||
public static String getType(File file) throws IORuntimeException {
|
||||
public static String getType(final File file) throws IORuntimeException {
|
||||
return getType(file,false);
|
||||
}
|
||||
|
||||
@ -228,7 +230,7 @@ public class FileTypeUtil {
|
||||
* @return 类型
|
||||
* @throws IORuntimeException 读取文件引起的异常
|
||||
*/
|
||||
public static String getTypeByPath(String path,boolean isExact) throws IORuntimeException {
|
||||
public static String getTypeByPath(final String path, final boolean isExact) throws IORuntimeException {
|
||||
return getType(FileUtil.file(path),isExact);
|
||||
}
|
||||
|
||||
@ -239,7 +241,7 @@ public class FileTypeUtil {
|
||||
* @return 类型
|
||||
* @throws IORuntimeException 读取文件引起的异常
|
||||
*/
|
||||
public static String getTypeByPath(String path) throws IORuntimeException {
|
||||
public static String getTypeByPath(final String path) throws IORuntimeException {
|
||||
return getTypeByPath(path,false);
|
||||
}
|
||||
|
||||
@ -252,7 +254,6 @@ public class FileTypeUtil {
|
||||
*/
|
||||
private static String readHex8192Upper(final InputStream in) throws IORuntimeException {
|
||||
try {
|
||||
final int i = in.available();
|
||||
return IoUtil.readHex(in, Math.min(8192, in.available()), false);
|
||||
} catch (final IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
@ -266,7 +267,7 @@ public class FileTypeUtil {
|
||||
* @return 16进制字符串
|
||||
* @throws IORuntimeException IO异常
|
||||
*/
|
||||
private static String readHex64Upper(InputStream in) throws IORuntimeException {
|
||||
private static String readHex64Upper(final InputStream in) throws IORuntimeException {
|
||||
return IoUtil.readHex(in, 64, false);
|
||||
}
|
||||
}
|
449
hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java → hutool-core/src/main/java/cn/hutool/core/io/file/FileUtil.java
Executable file → Normal file
449
hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java → hutool-core/src/main/java/cn/hutool/core/io/file/FileUtil.java
Executable file → Normal file
@ -1,62 +1,29 @@
|
||||
package cn.hutool.core.io;
|
||||
package cn.hutool.core.io.file;
|
||||
|
||||
import cn.hutool.core.compress.ZipUtil;
|
||||
import cn.hutool.core.io.file.FileCopier;
|
||||
import cn.hutool.core.io.file.FileMode;
|
||||
import cn.hutool.core.io.file.FileNameUtil;
|
||||
import cn.hutool.core.io.file.FileReader;
|
||||
import cn.hutool.core.io.file.FileReader.ReaderHandler;
|
||||
import cn.hutool.core.io.file.FileWriter;
|
||||
import cn.hutool.core.io.file.LineSeparator;
|
||||
import cn.hutool.core.io.file.PathUtil;
|
||||
import cn.hutool.core.io.file.Tailer;
|
||||
import cn.hutool.core.io.BomReader;
|
||||
import cn.hutool.core.io.IORuntimeException;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.io.resource.ResourceUtil;
|
||||
import cn.hutool.core.io.stream.BOMInputStream;
|
||||
import cn.hutool.core.io.unit.DataSizeUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.lang.func.SerConsumer;
|
||||
import cn.hutool.core.lang.func.SerFunction;
|
||||
import cn.hutool.core.net.url.URLUtil;
|
||||
import cn.hutool.core.reflect.ClassUtil;
|
||||
import cn.hutool.core.regex.ReUtil;
|
||||
import cn.hutool.core.text.StrUtil;
|
||||
import cn.hutool.core.thread.ThreadUtil;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.CharUtil;
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
import cn.hutool.core.util.ObjUtil;
|
||||
import cn.hutool.core.util.SystemUtil;
|
||||
import cn.hutool.core.util.*;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileFilter;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.LineNumberReader;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.io.Reader;
|
||||
import java.io.*;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.file.DirectoryNotEmptyException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.nio.file.*;
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.regex.Pattern;
|
||||
@ -693,11 +660,10 @@ public class FileUtil extends PathUtil {
|
||||
* 某个文件删除失败会终止删除操作
|
||||
*
|
||||
* @param fullFileOrDirPath 文件或者目录的路径
|
||||
* @return 成功与否
|
||||
* @throws IORuntimeException IO异常
|
||||
*/
|
||||
public static boolean del(final String fullFileOrDirPath) throws IORuntimeException {
|
||||
return del(file(fullFileOrDirPath));
|
||||
public static void del(final String fullFileOrDirPath) throws IORuntimeException {
|
||||
del(file(fullFileOrDirPath));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -705,42 +671,13 @@ public class FileUtil extends PathUtil {
|
||||
* 注意:删除文件夹时不会判断文件夹是否为空,如果不空则递归删除子文件或文件夹<br>
|
||||
* 某个文件删除失败会终止删除操作
|
||||
*
|
||||
* <p>
|
||||
* 从5.7.6开始,删除文件使用{@link Files#delete(Path)}代替 {@link File#delete()}<br>
|
||||
* 因为前者遇到文件被占用等原因时,抛出异常,而非返回false,异常会指明具体的失败原因。
|
||||
* </p>
|
||||
*
|
||||
* @param file 文件对象
|
||||
* @return 成功与否
|
||||
* @throws IORuntimeException IO异常
|
||||
* @see Files#delete(Path)
|
||||
*/
|
||||
public static boolean del(final File file) throws IORuntimeException {
|
||||
if (file == null || false == file.exists()) {
|
||||
// 如果文件不存在或已被删除,此处返回true表示删除成功
|
||||
return true;
|
||||
}
|
||||
|
||||
if (file.isDirectory()) {
|
||||
// 清空目录下所有文件和目录
|
||||
final boolean isOk = clean(file);
|
||||
if (false == isOk) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 删除文件或清空后的目录
|
||||
final Path path = file.toPath();
|
||||
try {
|
||||
delFile(path);
|
||||
} catch (final DirectoryNotEmptyException e) {
|
||||
// 遍历清空目录没有成功,此时补充删除一次(可能存在部分软链)
|
||||
del(path);
|
||||
} catch (final IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
|
||||
return true;
|
||||
public static void del(final File file) throws IORuntimeException {
|
||||
Assert.notNull(file, "File must be not null!");
|
||||
PathUtil.del(file.toPath());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -749,12 +686,10 @@ public class FileUtil extends PathUtil {
|
||||
* 某个文件删除失败会终止删除操作
|
||||
*
|
||||
* @param dirPath 文件夹路径
|
||||
* @return 成功与否
|
||||
* @throws IORuntimeException IO异常
|
||||
* @since 4.0.8
|
||||
*/
|
||||
public static boolean clean(final String dirPath) throws IORuntimeException {
|
||||
return clean(file(dirPath));
|
||||
public static void clean(final String dirPath) throws IORuntimeException {
|
||||
clean(file(dirPath));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -763,52 +698,11 @@ public class FileUtil extends PathUtil {
|
||||
* 某个文件删除失败会终止删除操作
|
||||
*
|
||||
* @param directory 文件夹
|
||||
* @return 成功与否
|
||||
* @throws IORuntimeException IO异常
|
||||
* @since 3.0.6
|
||||
*/
|
||||
public static boolean clean(final File directory) throws IORuntimeException {
|
||||
if (directory == null || directory.exists() == false || false == directory.isDirectory()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
final File[] files = directory.listFiles();
|
||||
if (null != files) {
|
||||
for (final File childFile : files) {
|
||||
if (false == del(childFile)) {
|
||||
// 删除一个出错则本次删除任务失败
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理空文件夹<br>
|
||||
* 此方法用于递归删除空的文件夹,不删除文件<br>
|
||||
* 如果传入的文件夹本身就是空的,删除这个文件夹
|
||||
*
|
||||
* @param directory 文件夹
|
||||
* @return 成功与否
|
||||
* @throws IORuntimeException IO异常
|
||||
* @since 4.5.5
|
||||
*/
|
||||
public static boolean cleanEmpty(final File directory) throws IORuntimeException {
|
||||
if (directory == null || false == directory.exists() || false == directory.isDirectory()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
final File[] files = directory.listFiles();
|
||||
if (ArrayUtil.isEmpty(files)) {
|
||||
// 空文件夹则删除之
|
||||
return directory.delete();
|
||||
}
|
||||
|
||||
for (final File childFile : files) {
|
||||
cleanEmpty(childFile);
|
||||
}
|
||||
return true;
|
||||
public static void clean(final File directory) throws IORuntimeException {
|
||||
Assert.notNull(directory, "File must be not null!");
|
||||
PathUtil.clean(directory.toPath());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -986,43 +880,6 @@ public class FileUtil extends PathUtil {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过JDK7+的 Files#copy(Path, Path, CopyOption...) 方法拷贝文件
|
||||
*
|
||||
* @param src 源文件路径
|
||||
* @param dest 目标文件或目录路径,如果为目录使用与源文件相同的文件名
|
||||
* @param options {@link StandardCopyOption}
|
||||
* @return File
|
||||
* @throws IORuntimeException IO异常
|
||||
*/
|
||||
public static File copyFile(final String src, final String dest, final StandardCopyOption... options) throws IORuntimeException {
|
||||
Assert.notBlank(src, "Source File path is blank !");
|
||||
Assert.notBlank(dest, "Destination File path is blank !");
|
||||
return copyFile(Paths.get(src), Paths.get(dest), options).toFile();
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过JDK7+的 Files#copy(Path, Path, CopyOption...) 方法拷贝文件
|
||||
*
|
||||
* @param src 源文件
|
||||
* @param dest 目标文件或目录,如果为目录使用与源文件相同的文件名
|
||||
* @param options {@link StandardCopyOption}
|
||||
* @return 目标文件
|
||||
* @throws IORuntimeException IO异常
|
||||
*/
|
||||
public static File copyFile(final File src, final File dest, final StandardCopyOption... options) throws IORuntimeException {
|
||||
// check
|
||||
Assert.notNull(src, "Source File is null !");
|
||||
if (false == src.exists()) {
|
||||
throw new IORuntimeException("File not exist: " + src);
|
||||
}
|
||||
Assert.notNull(dest, "Destination File or directiory is null !");
|
||||
if (equals(src, dest)) {
|
||||
throw new IORuntimeException("Files '{}' and '{}' are equal", src, dest);
|
||||
}
|
||||
return copyFile(src.toPath(), dest.toPath(), options).toFile();
|
||||
}
|
||||
|
||||
/**
|
||||
* 复制文件或目录<br>
|
||||
* 如果目标文件为目录,则将源文件以相同文件名拷贝到目标目录
|
||||
@ -1048,13 +905,19 @@ public class FileUtil extends PathUtil {
|
||||
* </pre>
|
||||
*
|
||||
* @param src 源文件
|
||||
* @param dest 目标文件或目录,目标不存在会自动创建(目录、文件都创建)
|
||||
* @param target 目标文件或目录,目标不存在会自动创建(目录、文件都创建)
|
||||
* @param isOverride 是否覆盖目标文件
|
||||
* @return 目标目录或文件
|
||||
* @throws IORuntimeException IO异常
|
||||
*/
|
||||
public static File copy(final File src, final File dest, final boolean isOverride) throws IORuntimeException {
|
||||
return FileCopier.of(src, dest).setOverride(isOverride).copy();
|
||||
public static File copy(final File src, final File target, final boolean isOverride) throws IORuntimeException {
|
||||
Assert.notNull(src, "Src file must be not null!");
|
||||
Assert.notNull(target, "target file must be not null!");
|
||||
return PathUtil.copy(
|
||||
src.toPath(),
|
||||
target.toPath(),
|
||||
isOverride ? new CopyOption[]{StandardCopyOption.REPLACE_EXISTING} : new CopyOption[]{})
|
||||
.toFile();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1068,65 +931,43 @@ public class FileUtil extends PathUtil {
|
||||
* </pre>
|
||||
*
|
||||
* @param src 源文件
|
||||
* @param dest 目标文件或目录,目标不存在会自动创建(目录、文件都创建)
|
||||
* @param target 目标文件或目录,目标不存在会自动创建(目录、文件都创建)
|
||||
* @param isOverride 是否覆盖目标文件
|
||||
* @return 目标目录或文件
|
||||
* @throws IORuntimeException IO异常
|
||||
*/
|
||||
public static File copyContent(final File src, final File dest, final boolean isOverride) throws IORuntimeException {
|
||||
return FileCopier.of(src, dest).setCopyContentIfDir(true).setOverride(isOverride).copy();
|
||||
public static File copyContent(final File src, final File target, final boolean isOverride) throws IORuntimeException {
|
||||
Assert.notNull(src, "Src file must be not null!");
|
||||
Assert.notNull(target, "target file must be not null!");
|
||||
return PathUtil.copyContent(
|
||||
src.toPath(),
|
||||
target.toPath(),
|
||||
isOverride ? new CopyOption[]{StandardCopyOption.REPLACE_EXISTING} : new CopyOption[]{})
|
||||
.toFile();
|
||||
}
|
||||
|
||||
/**
|
||||
* 复制文件或目录<br>
|
||||
* 情况如下:
|
||||
* 移动文件或目录到目标中,例如:
|
||||
* <ul>
|
||||
* <li>如果src为文件,target为目录,则移动到目标目录下,存在同名文件则按照是否覆盖参数执行。</li>
|
||||
* <li>如果src为文件,target为文件,则按照是否覆盖参数执行。</li>
|
||||
* <li>如果src为文件,target为不存在的路径,则重命名源文件到目标指定的文件,如moveContent("/a/b", "/c/d"), d不存在,则b变成d。</li>
|
||||
* <li>如果src为目录,target为文件,抛出{@link IllegalArgumentException}</li>
|
||||
* <li>如果src为目录,target为目录,则将源目录及其内容移动到目标路径目录中,如move("/a/b", "/c/d"),结果为"/c/d/b"</li>
|
||||
* <li>如果src为目录,target为不存在的路径,则创建目标路径为目录,将源目录及其内容移动到目标路径目录中,如move("/a/b", "/c/d"),结果为"/c/d/b"</li>
|
||||
* </ul>
|
||||
*
|
||||
* <pre>
|
||||
* 1、src和dest都为目录,则将src下所有文件(包括子目录)拷贝到dest下
|
||||
* 2、src和dest都为文件,直接复制,名字为dest
|
||||
* 3、src为文件,dest为目录,将src拷贝到dest目录下
|
||||
* </pre>
|
||||
*
|
||||
* @param src 源文件
|
||||
* @param dest 目标文件或目录,目标不存在会自动创建(目录、文件都创建)
|
||||
* @param src 源文件或目录路径
|
||||
* @param target 目标路径,如果为目录,则移动到此目录下
|
||||
* @param isOverride 是否覆盖目标文件
|
||||
* @return 目标目录或文件
|
||||
* @throws IORuntimeException IO异常
|
||||
* @since 4.1.5
|
||||
*/
|
||||
public static File copyFilesFromDir(final File src, final File dest, final boolean isOverride) throws IORuntimeException {
|
||||
return FileCopier.of(src, dest).setCopyContentIfDir(true).setOnlyCopyFile(true).setOverride(isOverride).copy();
|
||||
}
|
||||
|
||||
/**
|
||||
* 移动文件或者目录
|
||||
*
|
||||
* @param src 源文件或者目录
|
||||
* @param target 目标文件或者目录
|
||||
* @param isOverride 是否覆盖目标,只有目标为文件才覆盖
|
||||
* @return 目标文件或目录
|
||||
* @throws IORuntimeException IO异常
|
||||
* @see PathUtil#move(Path, Path, boolean)
|
||||
*/
|
||||
public static void move(final File src, final File target, final boolean isOverride) throws IORuntimeException {
|
||||
public static File move(final File src, final File target, final boolean isOverride) throws IORuntimeException {
|
||||
Assert.notNull(src, "Src file must be not null!");
|
||||
Assert.notNull(target, "target file must be not null!");
|
||||
move(src.toPath(), target.toPath(), isOverride);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移动文件或者目录
|
||||
*
|
||||
* @param src 源文件或者目录
|
||||
* @param target 目标文件或者目录
|
||||
* @param isOverride 是否覆盖目标,只有目标为文件才覆盖
|
||||
* @throws IORuntimeException IO异常
|
||||
* @see PathUtil#moveContent(Path, Path, boolean)
|
||||
* @since 5.7.9
|
||||
*/
|
||||
public static void moveContent(final File src, final File target, final boolean isOverride) throws IORuntimeException {
|
||||
Assert.notNull(src, "Src file must be not null!");
|
||||
Assert.notNull(target, "target file must be not null!");
|
||||
moveContent(src.toPath(), target.toPath(), isOverride);
|
||||
return move(src.toPath(), target.toPath(), isOverride).toFile();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1172,7 +1013,7 @@ public class FileUtil extends PathUtil {
|
||||
*/
|
||||
public static File rename(final File file, String newName, final boolean isRetainExt, final boolean isOverride) {
|
||||
if (isRetainExt) {
|
||||
final String extName = FileUtil.extName(file);
|
||||
final String extName = FileNameUtil.extName(file);
|
||||
if (StrUtil.isNotBlank(extName)) {
|
||||
newName = newName.concat(".").concat(extName);
|
||||
}
|
||||
@ -1604,129 +1445,6 @@ public class FileUtil extends PathUtil {
|
||||
return filePath;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------------------------- name start
|
||||
|
||||
/**
|
||||
* 返回文件名
|
||||
*
|
||||
* @param file 文件
|
||||
* @return 文件名
|
||||
* @see FileNameUtil#getName(File)
|
||||
* @since 4.1.13
|
||||
*/
|
||||
public static String getName(final File file) {
|
||||
return FileNameUtil.getName(file);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回文件名<br>
|
||||
* <pre>
|
||||
* "d:/test/aaa" 返回 "aaa"
|
||||
* "/test/aaa.jpg" 返回 "aaa.jpg"
|
||||
* </pre>
|
||||
*
|
||||
* @param filePath 文件
|
||||
* @return 文件名
|
||||
* @see FileNameUtil#getName(String)
|
||||
* @since 4.1.13
|
||||
*/
|
||||
public static String getName(final String filePath) {
|
||||
return FileNameUtil.getName(filePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件后缀名,扩展名不带“.”
|
||||
*
|
||||
* @param file 文件
|
||||
* @return 扩展名
|
||||
* @see FileNameUtil#getSuffix(File)
|
||||
* @since 5.3.8
|
||||
*/
|
||||
public static String getSuffix(final File file) {
|
||||
return FileNameUtil.getSuffix(file);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得文件后缀名,扩展名不带“.”
|
||||
*
|
||||
* @param fileName 文件名
|
||||
* @return 扩展名
|
||||
* @see FileNameUtil#getSuffix(String)
|
||||
* @since 5.3.8
|
||||
*/
|
||||
public static String getSuffix(final String fileName) {
|
||||
return FileNameUtil.getSuffix(fileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回主文件名
|
||||
*
|
||||
* @param file 文件
|
||||
* @return 主文件名
|
||||
* @see FileNameUtil#getPrefix(File)
|
||||
* @since 5.3.8
|
||||
*/
|
||||
public static String getPrefix(final File file) {
|
||||
return FileNameUtil.getPrefix(file);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回主文件名
|
||||
*
|
||||
* @param fileName 完整文件名
|
||||
* @return 主文件名
|
||||
* @see FileNameUtil#getPrefix(String)
|
||||
* @since 5.3.8
|
||||
*/
|
||||
public static String getPrefix(final String fileName) {
|
||||
return FileNameUtil.getPrefix(fileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回主文件名
|
||||
*
|
||||
* @param file 文件
|
||||
* @return 主文件名
|
||||
* @see FileNameUtil#mainName(File)
|
||||
*/
|
||||
public static String mainName(final File file) {
|
||||
return FileNameUtil.mainName(file);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回主文件名
|
||||
*
|
||||
* @param fileName 完整文件名
|
||||
* @return 主文件名
|
||||
* @see FileNameUtil#mainName(String)
|
||||
*/
|
||||
public static String mainName(final String fileName) {
|
||||
return FileNameUtil.mainName(fileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件扩展名(后缀名),扩展名不带“.”
|
||||
*
|
||||
* @param file 文件
|
||||
* @return 扩展名
|
||||
* @see FileNameUtil#extName(File)
|
||||
*/
|
||||
public static String extName(final File file) {
|
||||
return FileNameUtil.extName(file);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得文件的扩展名(后缀名),扩展名不带“.”
|
||||
*
|
||||
* @param fileName 文件名
|
||||
* @return 扩展名
|
||||
* @see FileNameUtil#extName(String)
|
||||
*/
|
||||
public static String extName(final String fileName) {
|
||||
return FileNameUtil.extName(fileName);
|
||||
}
|
||||
// -------------------------------------------------------------------------------------------- name end
|
||||
|
||||
/**
|
||||
* 判断文件路径是否有指定后缀,忽略大小写<br>
|
||||
* 常用语判断扩展名
|
||||
@ -1765,6 +1483,7 @@ public class FileUtil extends PathUtil {
|
||||
* @param file 文件
|
||||
* @return 输入流
|
||||
* @throws IORuntimeException 文件未找到
|
||||
* @see IoUtil#toStream(File)
|
||||
*/
|
||||
public static BufferedInputStream getInputStream(final File file) throws IORuntimeException {
|
||||
return IoUtil.toBuffered(IoUtil.toStream(file));
|
||||
@ -1790,8 +1509,7 @@ public class FileUtil extends PathUtil {
|
||||
*/
|
||||
public static BOMInputStream getBOMInputStream(final File file) throws IORuntimeException {
|
||||
try {
|
||||
//noinspection IOStreamConstructor
|
||||
return new BOMInputStream(new FileInputStream(file));
|
||||
return new BOMInputStream(Files.newInputStream(file.toPath()));
|
||||
} catch (final IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
@ -2258,10 +1976,9 @@ public class FileUtil extends PathUtil {
|
||||
* @param path 文件的绝对路径
|
||||
* @return 从文件中load出的数据
|
||||
* @throws IORuntimeException IO异常
|
||||
* @since 3.1.1
|
||||
*/
|
||||
public static <T> T loadUtf8(final String path, final ReaderHandler<T> readerHandler) throws IORuntimeException {
|
||||
return load(path, CharsetUtil.UTF_8, readerHandler);
|
||||
public static <T> T readUtf8(final String path, final SerFunction<BufferedReader, T> readerHandler) throws IORuntimeException {
|
||||
return read(path, CharsetUtil.UTF_8, readerHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2275,23 +1992,8 @@ public class FileUtil extends PathUtil {
|
||||
* @throws IORuntimeException IO异常
|
||||
* @since 3.1.1
|
||||
*/
|
||||
public static <T> T load(final String path, final String charset, final ReaderHandler<T> readerHandler) throws IORuntimeException {
|
||||
return FileReader.of(file(path), CharsetUtil.charset(charset)).read(readerHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按照给定的readerHandler读取文件中的数据
|
||||
*
|
||||
* @param <T> 集合类型
|
||||
* @param readerHandler Reader处理类
|
||||
* @param path 文件的绝对路径
|
||||
* @param charset 字符集
|
||||
* @return 从文件中load出的数据
|
||||
* @throws IORuntimeException IO异常
|
||||
* @since 3.1.1
|
||||
*/
|
||||
public static <T> T load(final String path, final Charset charset, final ReaderHandler<T> readerHandler) throws IORuntimeException {
|
||||
return FileReader.of(file(path), charset).read(readerHandler);
|
||||
public static <T> T read(final String path, final Charset charset, final SerFunction<BufferedReader, T> readerHandler) throws IORuntimeException {
|
||||
return read(file(path), charset, readerHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2304,8 +2006,8 @@ public class FileUtil extends PathUtil {
|
||||
* @throws IORuntimeException IO异常
|
||||
* @since 3.1.1
|
||||
*/
|
||||
public static <T> T loadUtf8(final File file, final ReaderHandler<T> readerHandler) throws IORuntimeException {
|
||||
return load(file, CharsetUtil.UTF_8, readerHandler);
|
||||
public static <T> T readUtf8(final File file, final SerFunction<BufferedReader, T> readerHandler) throws IORuntimeException {
|
||||
return read(file, CharsetUtil.UTF_8, readerHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2319,7 +2021,7 @@ public class FileUtil extends PathUtil {
|
||||
* @throws IORuntimeException IO异常
|
||||
* @since 3.1.1
|
||||
*/
|
||||
public static <T> T load(final File file, final Charset charset, final ReaderHandler<T> readerHandler) throws IORuntimeException {
|
||||
public static <T> T read(final File file, final Charset charset, final SerFunction<BufferedReader, T> readerHandler) throws IORuntimeException {
|
||||
return FileReader.of(file, charset).read(readerHandler);
|
||||
}
|
||||
|
||||
@ -2335,8 +2037,7 @@ public class FileUtil extends PathUtil {
|
||||
public static BufferedOutputStream getOutputStream(final File file) throws IORuntimeException {
|
||||
final OutputStream out;
|
||||
try {
|
||||
//noinspection IOStreamConstructor
|
||||
out = new FileOutputStream(touch(file));
|
||||
out = Files.newOutputStream(touch(file).toPath());
|
||||
} catch (final IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
@ -3005,30 +2706,6 @@ public class FileUtil extends PathUtil {
|
||||
return FileWriter.of(file, charset).writeLines(lines, lineSeparator, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除文件名中的在Windows下不支持的非法字符,包括: \ / : * ? " < > |
|
||||
*
|
||||
* @param fileName 文件名(必须不包括路径,否则路径符将被替换)
|
||||
* @return 清理后的文件名
|
||||
* @see FileNameUtil#cleanInvalid(String)
|
||||
* @since 3.3.1
|
||||
*/
|
||||
public static String cleanInvalid(final String fileName) {
|
||||
return FileNameUtil.cleanInvalid(fileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件名中是否包含在Windows下不支持的非法字符,包括: \ / : * ? " < > |
|
||||
*
|
||||
* @param fileName 文件名(必须不包括路径,否则路径符将被替换)
|
||||
* @return 是否包含非法字符
|
||||
* @see FileNameUtil#containsInvalid(String)
|
||||
* @since 3.3.1
|
||||
*/
|
||||
public static boolean containsInvalid(final String fileName) {
|
||||
return FileNameUtil.containsInvalid(fileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Web项目下的web root路径<br>
|
||||
* 原理是首先获取ClassPath路径,由于在web项目中ClassPath位于 WEB-INF/classes/下,故向上获取两级目录即可。
|
||||
@ -3051,7 +2728,7 @@ public class FileUtil extends PathUtil {
|
||||
* getParent(file("d:/aaa/bbb/cc/ddd")) -》 "d:/aaa/bbb/cc"
|
||||
* </pre>
|
||||
*
|
||||
* @param file 目录或文件
|
||||
* @param file 目录或文件
|
||||
* @return 路径File,如果不存在返回null
|
||||
* @since 6.0.0
|
||||
*/
|
@ -1,6 +1,5 @@
|
||||
package cn.hutool.core.io.file;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.lang.func.Wrapper;
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
|
||||
|
@ -1,11 +1,10 @@
|
||||
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.lang.Assert;
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
import cn.hutool.core.text.StrUtil;
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.BufferedWriter;
|
||||
@ -13,9 +12,11 @@ import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.PrintWriter;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
@ -336,9 +337,9 @@ public class FileWriter extends FileWrapper {
|
||||
* @since 5.5.2
|
||||
*/
|
||||
public File writeFromStream(final InputStream in, final boolean isCloseIn) throws IORuntimeException {
|
||||
FileOutputStream out = null;
|
||||
OutputStream out = null;
|
||||
try {
|
||||
out = new FileOutputStream(FileUtil.touch(file));
|
||||
out = Files.newOutputStream(FileUtil.touch(file).toPath());
|
||||
IoUtil.copy(in, out);
|
||||
} catch (final IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
@ -359,7 +360,7 @@ public class FileWriter extends FileWrapper {
|
||||
*/
|
||||
public BufferedOutputStream getOutputStream() throws IORuntimeException {
|
||||
try {
|
||||
return new BufferedOutputStream(new FileOutputStream(FileUtil.touch(file)));
|
||||
return new BufferedOutputStream(Files.newOutputStream(FileUtil.touch(file).toPath()));
|
||||
} catch (final IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
package cn.hutool.core.io.file;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.io.IORuntimeException;
|
||||
import cn.hutool.core.io.watch.SimpleWatcher;
|
||||
import cn.hutool.core.lang.func.SerConsumer;
|
||||
|
164
hutool-core/src/main/java/cn/hutool/core/io/file/PathCopier.java
Executable file
164
hutool-core/src/main/java/cn/hutool/core/io/file/PathCopier.java
Executable file
@ -0,0 +1,164 @@
|
||||
package cn.hutool.core.io.file;
|
||||
|
||||
import cn.hutool.core.io.IORuntimeException;
|
||||
import cn.hutool.core.io.file.visitor.CopyVisitor;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.lang.copier.SrcToDestCopier;
|
||||
import cn.hutool.core.util.ObjUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.*;
|
||||
|
||||
/**
|
||||
* 文件复制封装
|
||||
*
|
||||
* @author looly
|
||||
* @since 6.0.0
|
||||
*/
|
||||
public class PathCopier extends SrcToDestCopier<Path, PathCopier> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 创建文件或目录拷贝器
|
||||
*
|
||||
* @param src 源文件或目录
|
||||
* @param target 目标文件或目录
|
||||
* @param isOverride 是否覆盖目标文件
|
||||
* @return {@code PathCopier}
|
||||
*/
|
||||
public static PathCopier of(final Path src, final Path target, final boolean isOverride) {
|
||||
return of(src, target, isOverride ? new CopyOption[]{StandardCopyOption.REPLACE_EXISTING} : new CopyOption[]{});
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建文件或目录拷贝器
|
||||
*
|
||||
* @param src 源文件或目录
|
||||
* @param target 目标文件或目录
|
||||
* @param options 拷贝参数
|
||||
* @return {@code PathCopier}
|
||||
*/
|
||||
public static PathCopier of(final Path src, final Path target, final CopyOption[] options) {
|
||||
return new PathCopier(src, target, options);
|
||||
}
|
||||
|
||||
private final CopyOption[] options;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param src 源文件或目录,不能为{@code null}且必须存在
|
||||
* @param target 目标文件或目录
|
||||
* @param options 移动参数
|
||||
*/
|
||||
public PathCopier(final Path src, final Path target, final CopyOption[] options) {
|
||||
Assert.notNull(target, "Src path must be not null !");
|
||||
if (false == PathUtil.exists(src, false)) {
|
||||
throw new IllegalArgumentException("Src path is not exist!");
|
||||
}
|
||||
this.src = src;
|
||||
this.target = Assert.notNull(target, "Target path must be not null !");
|
||||
this.options = ObjUtil.defaultIfNull(options, new CopyOption[]{});
|
||||
}
|
||||
|
||||
/**
|
||||
* 复制src到target中
|
||||
* <ul>
|
||||
* <li>src路径和target路径相同时,不执行操作</li>
|
||||
* <li>src为文件,target为已存在目录,则拷贝到目录下,文件名不变。</li>
|
||||
* <li>src为文件,target为不存在路径,则目标以文件对待(自动创建父级目录),相当于拷贝后重命名,比如:/dest/aaa,如果aaa不存在,则aaa被当作文件名</li>
|
||||
* <li>src为文件,target是一个已存在的文件,则当{@link CopyOption}设为覆盖时会被覆盖,默认不覆盖,抛出{@link FileAlreadyExistsException}</li>
|
||||
* <li>src为目录,target为已存在目录,整个src目录连同其目录拷贝到目标目录中</li>
|
||||
* <li>src为目录,target为不存在路径,则自动创建目标为新目录,并只拷贝src内容到目标目录中,相当于重命名目录。</li>
|
||||
* <li>src为目录,target为文件,抛出{@link IllegalArgumentException}</li>
|
||||
* </ul>
|
||||
*
|
||||
* @return 目标Path
|
||||
* @throws IORuntimeException IO异常
|
||||
*/
|
||||
@Override
|
||||
public Path copy() throws IORuntimeException {
|
||||
if (PathUtil.isDirectory(src)) {
|
||||
if (PathUtil.exists(target, false)) {
|
||||
if (PathUtil.isDirectory(target)) {
|
||||
return _copyContent(src, target.resolve(src.getFileName()), options);
|
||||
} else {
|
||||
// src目录,target文件,无法拷贝
|
||||
throw new IllegalArgumentException("Can not copy directory to a file!");
|
||||
}
|
||||
} else {
|
||||
// 目标不存在,按照重命名对待
|
||||
return _copyContent(src, target, options);
|
||||
}
|
||||
}
|
||||
return copyFile(src, target, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* 复制src的内容到target中
|
||||
* <ul>
|
||||
* <li>src路径和target路径相同时,不执行操作</li>
|
||||
* <li>src为文件,target为已存在目录,则拷贝到目录下,文件名不变。</li>
|
||||
* <li>src为文件,target为不存在路径,则目标以文件对待(自动创建父级目录),相当于拷贝后重命名,比如:/dest/aaa,如果aaa不存在,则aaa被当作文件名</li>
|
||||
* <li>src为文件,target是一个已存在的文件,则当{@link CopyOption}设为覆盖时会被覆盖,默认不覆盖,抛出{@link FileAlreadyExistsException}</li>
|
||||
* <li>src为目录,target为已存在目录,整个src目录下的内容拷贝到目标目录中</li>
|
||||
* <li>src为目录,target为不存在路径,则自动创建目标为新目录,整个src目录下的内容拷贝到目标目录中,相当于重命名目录。</li>
|
||||
* <li>src为目录,target为文件,抛出IO异常</li>
|
||||
* </ul>
|
||||
*
|
||||
* @return 目标Path
|
||||
* @throws IORuntimeException IO异常
|
||||
*/
|
||||
public Path copyContent() throws IORuntimeException {
|
||||
if (PathUtil.isDirectory(src, false)) {
|
||||
return _copyContent(src, target, options);
|
||||
}
|
||||
return copyFile(src, target, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* 拷贝目录下的所有文件或目录到目标目录中,此方法不支持文件对文件的拷贝。
|
||||
* <ul>
|
||||
* <li>源文件为目录,目标也为目录或不存在,则拷贝目录下所有文件和目录到目标目录下</li>
|
||||
* <li>源文件为文件,目标为目录或不存在,则拷贝文件到目标目录下</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param src 源文件路径,如果为目录只在目标中创建新目录
|
||||
* @param target 目标目录,如果为目录使用与源文件相同的文件名
|
||||
* @param options {@link StandardCopyOption}
|
||||
* @return Path
|
||||
* @throws IORuntimeException IO异常
|
||||
*/
|
||||
private static Path _copyContent(final Path src, final Path target, final CopyOption... options) throws IORuntimeException {
|
||||
try {
|
||||
Files.walkFileTree(src, new CopyVisitor(src, target, options));
|
||||
} catch (final IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过JDK7+的 {@link Files#copy(Path, Path, CopyOption...)} 方法拷贝文件<br>
|
||||
* 此方法不支持递归拷贝目录,如果src传入是目录,只会在目标目录中创建空目录
|
||||
*
|
||||
* @param src 源文件路径,如果为目录只在目标中创建新目录
|
||||
* @param target 目标文件或目录,如果为目录使用与源文件相同的文件名
|
||||
* @param options {@link StandardCopyOption}
|
||||
* @return Path
|
||||
* @throws IORuntimeException IO异常
|
||||
*/
|
||||
private static Path copyFile(final Path src, final Path target, final CopyOption... options) throws IORuntimeException {
|
||||
Assert.notNull(src, "Source File is null !");
|
||||
Assert.notNull(target, "Destination File or directory is null !");
|
||||
|
||||
final Path targetPath = PathUtil.isDirectory(target) ? target.resolve(src.getFileName()) : target;
|
||||
// 创建级联父目录
|
||||
PathUtil.mkParentDirs(targetPath);
|
||||
try {
|
||||
return Files.copy(src, targetPath, options);
|
||||
} catch (final IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
104
hutool-core/src/main/java/cn/hutool/core/io/file/PathDeleter.java
Executable file
104
hutool-core/src/main/java/cn/hutool/core/io/file/PathDeleter.java
Executable file
@ -0,0 +1,104 @@
|
||||
package cn.hutool.core.io.file;
|
||||
|
||||
import cn.hutool.core.io.IORuntimeException;
|
||||
import cn.hutool.core.io.file.visitor.DelVisitor;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.*;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* 文件删除封装
|
||||
*
|
||||
* @author looly
|
||||
* @since 6.0.0
|
||||
*/
|
||||
public class PathDeleter {
|
||||
|
||||
/**
|
||||
* 创建文件或目录移动器
|
||||
*
|
||||
* @param src 源文件或目录
|
||||
* @return {@code PathMover}
|
||||
*/
|
||||
public static PathDeleter of(final Path src) {
|
||||
return new PathDeleter(src);
|
||||
}
|
||||
|
||||
private final Path path;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param path 文件或目录,不能为{@code null}且必须存在
|
||||
*/
|
||||
public PathDeleter(final Path path) {
|
||||
this.path = Assert.notNull(path, "Path must be not null !");
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除文件或者文件夹,不追踪软链<br>
|
||||
* 注意:删除文件夹时不会判断文件夹是否为空,如果不空则递归删除子文件或文件夹<br>
|
||||
* 某个文件删除失败会终止删除操作
|
||||
*
|
||||
* @throws IORuntimeException IO异常
|
||||
*/
|
||||
public void del() throws IORuntimeException {
|
||||
final Path path = this.path;
|
||||
if (Files.notExists(path)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (PathUtil.isDirectory(path)) {
|
||||
_del(path);
|
||||
} else {
|
||||
delFile(path);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空目录
|
||||
*/
|
||||
public void clean() {
|
||||
try (final Stream<Path> list = Files.list(this.path)){
|
||||
list.forEach(PathUtil::del);
|
||||
} catch (final IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除目录
|
||||
*
|
||||
* @param path 目录路径
|
||||
*/
|
||||
private static void _del(final Path path) {
|
||||
try {
|
||||
Files.walkFileTree(path, DelVisitor.INSTANCE);
|
||||
} catch (final IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除文件或空目录,不追踪软链
|
||||
*
|
||||
* @param path 文件对象
|
||||
* @throws IORuntimeException IO异常
|
||||
* @since 5.7.7
|
||||
*/
|
||||
private static void delFile(final Path path) throws IORuntimeException {
|
||||
try {
|
||||
Files.delete(path);
|
||||
} catch (final IOException e) {
|
||||
if (e instanceof AccessDeniedException) {
|
||||
// 可能遇到只读文件,无法删除.使用 file 方法删除
|
||||
if (path.toFile().delete()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user