mirror of
https://gitee.com/chinabugotech/hutool.git
synced 2025-05-09 23:51:34 +08:00
解决冲突
This commit is contained in:
commit
9b6f7f2d14
25
CHANGELOG.md
25
CHANGELOG.md
@ -3,17 +3,33 @@
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------
|
||||
|
||||
# 5.5.0 (2020-11-09)
|
||||
# 5.5.1 (2020-11-15)
|
||||
|
||||
### 新特性
|
||||
### Bug修复
|
||||
* 【core 】 修复在Linux下FileUtil.move失败问题(issue#I254Y3@Gitee)
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------
|
||||
|
||||
# 5.5.0 (2020-11-14)
|
||||
|
||||
### 大版本特性
|
||||
* 【extra 】 增加jakarta.validation-api封装:ValidationUtil(pr#207@Gitee)
|
||||
* 【extra 】 增加表达式引擎封装:ExpressionUtil(pr#1203@Github)
|
||||
* 【extra 】 新增基于Apache-FtpServer封装:SimpleFtpServer
|
||||
* 【extra 】 新增基于Commons-Compress封装:CompressUtil
|
||||
|
||||
### 新特性
|
||||
* 【core 】 NumberUtil.parseInt等支持123,2.00这类数字(issue#I23ORQ@Gitee)
|
||||
* 【core 】 增加ArrayUtil.isSub、indexOfSub、lastIndexOfSub方法(issue#I23O1K@Gitee)
|
||||
* 【extra 】 增加ValidationUtil(pr#207@Gitee)
|
||||
* 【core 】 反射调用支持传递参数的值为null(pr#1205@Github)
|
||||
* 【core 】 HexUtil增加format方法(issue#I245NF@Gitee)
|
||||
* 【poi 】 ExcelWriter增加setCurrentRowToEnd方法(issue#I24A2R@Gitee)
|
||||
* 【core 】 ExcelWriter增加setCurrentRowToEnd方法(issue#I24A2R@Gitee)
|
||||
* 【extra 】 增加表达式引擎封装(ExpressionUtil)(pr#1203@Github)
|
||||
* 【core 】 增加enum转数字支持(issue#I24QZY@Gitee)
|
||||
* 【core 】 NumberUtil.toBigDecimal空白符转换为0(issue#I24MRP@Gitee)
|
||||
* 【core 】 CollUtil和IterUtil增加size方法(pr#208@Gitee)
|
||||
* 【poi 】 ExcelReader的read方法读取空单元格增加CellEditor处理(issue#1213@Github)
|
||||
|
||||
### Bug修复
|
||||
* 【core 】 修复DateUtil.current使用System.nanoTime的问题(issue#1198@Github)
|
||||
@ -24,6 +40,9 @@
|
||||
* 【poi 】 修复Excel07SaxReader读取公式的错误的问题(issue#I23VFL@Gitee)
|
||||
* 【http 】 修复HttpUtil.isHttp判断问题(pr#1208@Github)
|
||||
* 【http 】 修复Snowflake时间回拨导致ID重复的bug(issue#1206@Github)
|
||||
* 【core 】 修复StrUtil.lastIndexOf查找位于首位的字符串找不到的bug(issue#I24RSV@Gitee)
|
||||
* 【poi 】 修复BigExcelWriter的autoSizeColumnAll问题(pr#1221@Github)
|
||||
* 【core 】 修复StrUtil.subBetweenAll不支持相同字符的问题(pr#1217@Github)
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
13
README-EN.md
13
README-EN.md
@ -45,6 +45,11 @@
|
||||
<p align="center">
|
||||
<a href="https://qm.qq.com/cgi-bin/qm/qr?k=0wwldaU0E8r-ZzHl_wma33W7420zwXYi&jump_from=webapi"><img src="https://img.shields.io/badge/QQ%20Group-956375658-orange"/></a>
|
||||
</p>
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
[**中文说明**](README.md)
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
## Introduction
|
||||
@ -120,19 +125,19 @@ Each module can be introduced individually, or all modules can be introduced by
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
<version>5.4.8</version>
|
||||
<version>5.5.1</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
### Gradle
|
||||
```
|
||||
compile 'cn.hutool:hutool-all:5.4.8'
|
||||
compile 'cn.hutool:hutool-all:5.5.1'
|
||||
```
|
||||
|
||||
## Download
|
||||
|
||||
- [Maven1](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.4.8/)
|
||||
- [Maven2](http://repo2.maven.org/maven2/cn/hutool/hutool-all/5.4.8/)
|
||||
- [Maven1](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.5.1/)
|
||||
- [Maven2](http://repo2.maven.org/maven2/cn/hutool/hutool-all/5.5.1/)
|
||||
|
||||
> 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.
|
||||
|
12
README.md
12
README.md
@ -48,6 +48,10 @@
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
[**English Documentation**](README-EN.md)
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
## 简介
|
||||
Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。
|
||||
|
||||
@ -119,21 +123,21 @@ Hutool的存在就是为了减少代码搜索成本,避免网络上参差不
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
<version>5.4.8</version>
|
||||
<version>5.5.1</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
### Gradle
|
||||
```
|
||||
compile 'cn.hutool:hutool-all:5.4.8'
|
||||
compile 'cn.hutool:hutool-all:5.5.1'
|
||||
```
|
||||
|
||||
### 非Maven项目
|
||||
|
||||
点击以下任一链接,下载`hutool-all-X.X.X.jar`即可:
|
||||
|
||||
- [Maven中央库1](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.4.8/)
|
||||
- [Maven中央库2](http://repo2.maven.org/maven2/cn/hutool/hutool-all/5.4.8/)
|
||||
- [Maven中央库1](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.5.1/)
|
||||
- [Maven中央库2](http://repo2.maven.org/maven2/cn/hutool/hutool-all/5.5.1/)
|
||||
|
||||
> 注意
|
||||
> Hutool 5.x支持JDK8+,对Android平台没有测试,不能保证所有工具类或工具方法可用。
|
||||
|
@ -1 +1 @@
|
||||
5.4.8
|
||||
5.5.1
|
||||
|
@ -1 +1 @@
|
||||
var version = '5.4.8'
|
||||
var version = '5.5.1'
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.4.8-SNAPSHOT</version>
|
||||
<version>5.5.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-all</artifactId>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.4.8-SNAPSHOT</version>
|
||||
<version>5.5.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-aop</artifactId>
|
||||
|
@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.4.8-SNAPSHOT</version>
|
||||
<version>5.5.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-bloomFilter</artifactId>
|
||||
|
@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.4.8-SNAPSHOT</version>
|
||||
<version>5.5.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-bom</artifactId>
|
||||
|
@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.4.8-SNAPSHOT</version>
|
||||
<version>5.5.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-cache</artifactId>
|
||||
|
@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.4.8-SNAPSHOT</version>
|
||||
<version>5.5.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-captcha</artifactId>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.4.8-SNAPSHOT</version>
|
||||
<version>5.5.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-core</artifactId>
|
||||
|
@ -1383,7 +1383,7 @@ public class CollUtil {
|
||||
|
||||
R value;
|
||||
for (T t : collection) {
|
||||
if(null == t && ignoreNull){
|
||||
if (null == t && ignoreNull) {
|
||||
continue;
|
||||
}
|
||||
value = func.apply(t);
|
||||
@ -2930,4 +2930,47 @@ public class CollUtil {
|
||||
void accept(K key, V value, int index);
|
||||
}
|
||||
// ---------------------------------------------------------------------------------------------- Interface end
|
||||
|
||||
/**
|
||||
* 获取Collection或者iterator的大小,此方法可以处理的对象类型如下:
|
||||
* <ul>
|
||||
* <li>Collection - the collection size
|
||||
* <li>Map - the map size
|
||||
* <li>Array - the array size
|
||||
* <li>Iterator - the number of elements remaining in the iterator
|
||||
* <li>Enumeration - the number of elements remaining in the enumeration
|
||||
* </ul>
|
||||
*
|
||||
* @param object 可以为空的对象
|
||||
* @return 如果object为空则返回0
|
||||
* @throws IllegalArgumentException 参数object不是Collection或者iterator
|
||||
* @since 5.5.0
|
||||
*/
|
||||
public static int size(final Object object) {
|
||||
if (object == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int total = 0;
|
||||
if (object instanceof Map<?, ?>) {
|
||||
total = ((Map<?, ?>) object).size();
|
||||
} else if (object instanceof Collection<?>) {
|
||||
total = ((Collection<?>) object).size();
|
||||
} else if (object instanceof Iterable<?>) {
|
||||
total = IterUtil.size((Iterable<?>) object);
|
||||
} else if (object instanceof Iterator<?>) {
|
||||
total = IterUtil.size((Iterator<?>) object);
|
||||
} else if (object instanceof Enumeration<?>) {
|
||||
final Enumeration<?> it = (Enumeration<?>) object;
|
||||
while (it.hasMoreElements()) {
|
||||
total++;
|
||||
it.nextElement();
|
||||
}
|
||||
} else if (ArrayUtil.isArray(object)) {
|
||||
total = ArrayUtil.length(object);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unsupported object type: " + object.getClass().getName());
|
||||
}
|
||||
return total;
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import cn.hutool.core.util.ReflectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
@ -821,4 +822,41 @@ public class IterUtil {
|
||||
public static <F, T> Iterator<T> trans(Iterator<F> iterator, Function<? super F, ? extends T> function) {
|
||||
return new TransIter<>(iterator, function);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回 Iterable 对象的元素数量
|
||||
*
|
||||
* @param iterable Iterable对象
|
||||
* @return Iterable对象的元素数量
|
||||
* @since 5.5.0
|
||||
*/
|
||||
public static int size(final Iterable<?> iterable) {
|
||||
if(null == iterable){
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (iterable instanceof Collection<?>) {
|
||||
return ((Collection<?>) iterable).size();
|
||||
} else {
|
||||
return size(iterable.iterator());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回 Iterator 对象的元素数量
|
||||
*
|
||||
* @param iterator Iterator对象
|
||||
* @return Iterator对象的元素数量
|
||||
* @since 5.5.0
|
||||
*/
|
||||
public static int size(final Iterator<?> iterator) {
|
||||
int size = 0;
|
||||
if (iterator != null) {
|
||||
while (iterator.hasNext()) {
|
||||
iterator.next();
|
||||
size++;
|
||||
}
|
||||
}
|
||||
return size;
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.concurrent.atomic.DoubleAdder;
|
||||
import java.util.concurrent.atomic.LongAdder;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* 数字转换器<br>
|
||||
@ -54,17 +55,31 @@ public class NumberConverter extends AbstractConverter<Number> {
|
||||
|
||||
@Override
|
||||
protected Number convertInternal(Object value) {
|
||||
return convertInternal(value, this.targetType);
|
||||
return convert(value, this.targetType, this::convertToStr);
|
||||
}
|
||||
|
||||
private Number convertInternal(Object value, Class<?> targetType) {
|
||||
/**
|
||||
* 转换对象为数字
|
||||
*
|
||||
* @param value 对象值
|
||||
* @param targetType 目标的数字类型
|
||||
* @param toStrFunc 转换为字符串的函数
|
||||
* @return 转换后的数字
|
||||
* @since 5.5.0
|
||||
*/
|
||||
protected static Number convert(Object value, Class<?> targetType, Function<Object, String> toStrFunc) {
|
||||
// 枚举转换为数字默认为其顺序
|
||||
if (value instanceof Enum) {
|
||||
return convert(((Enum<?>) value).ordinal(), targetType, toStrFunc);
|
||||
}
|
||||
|
||||
if (Byte.class == targetType) {
|
||||
if (value instanceof Number) {
|
||||
return ((Number) value).byteValue();
|
||||
} else if (value instanceof Boolean) {
|
||||
return BooleanUtil.toByteObj((Boolean) value);
|
||||
}
|
||||
final String valueStr = convertToStr(value);
|
||||
final String valueStr = toStrFunc.apply(value);
|
||||
return StrUtil.isBlank(valueStr) ? null : Byte.valueOf(valueStr);
|
||||
} else if (Short.class == targetType) {
|
||||
if (value instanceof Number) {
|
||||
@ -72,7 +87,7 @@ public class NumberConverter extends AbstractConverter<Number> {
|
||||
} else if (value instanceof Boolean) {
|
||||
return BooleanUtil.toShortObj((Boolean) value);
|
||||
}
|
||||
final String valueStr = convertToStr(value);
|
||||
final String valueStr = toStrFunc.apply((value));
|
||||
return StrUtil.isBlank(valueStr) ? null : Short.valueOf(valueStr);
|
||||
} else if (Integer.class == targetType) {
|
||||
if (value instanceof Number) {
|
||||
@ -80,16 +95,16 @@ public class NumberConverter extends AbstractConverter<Number> {
|
||||
} else if (value instanceof Boolean) {
|
||||
return BooleanUtil.toInteger((Boolean) value);
|
||||
} else if (value instanceof Date) {
|
||||
return (int)((Date) value).getTime();
|
||||
return (int) ((Date) value).getTime();
|
||||
} else if (value instanceof Calendar) {
|
||||
return (int)((Calendar) value).getTimeInMillis();
|
||||
return (int) ((Calendar) value).getTimeInMillis();
|
||||
} else if (value instanceof TemporalAccessor) {
|
||||
return (int)DateUtil.toInstant((TemporalAccessor) value).toEpochMilli();
|
||||
return (int) DateUtil.toInstant((TemporalAccessor) value).toEpochMilli();
|
||||
}
|
||||
final String valueStr = convertToStr(value);
|
||||
final String valueStr = toStrFunc.apply((value));
|
||||
return StrUtil.isBlank(valueStr) ? null : NumberUtil.parseInt(valueStr);
|
||||
} else if (AtomicInteger.class == targetType) {
|
||||
final Number number = convertInternal(value, Integer.class);
|
||||
final Number number = convert(value, Integer.class, toStrFunc);
|
||||
if (null != number) {
|
||||
final AtomicInteger intValue = new AtomicInteger();
|
||||
intValue.set(number.intValue());
|
||||
@ -107,18 +122,18 @@ public class NumberConverter extends AbstractConverter<Number> {
|
||||
} else if (value instanceof TemporalAccessor) {
|
||||
return DateUtil.toInstant((TemporalAccessor) value).toEpochMilli();
|
||||
}
|
||||
final String valueStr = convertToStr(value);
|
||||
final String valueStr = toStrFunc.apply((value));
|
||||
return StrUtil.isBlank(valueStr) ? null : NumberUtil.parseLong(valueStr);
|
||||
} else if (AtomicLong.class == targetType) {
|
||||
final Number number = convertInternal(value, Long.class);
|
||||
final Number number = convert(value, Long.class, toStrFunc);
|
||||
if (null != number) {
|
||||
final AtomicLong longValue = new AtomicLong();
|
||||
longValue.set(number.longValue());
|
||||
return longValue;
|
||||
}
|
||||
}else if (LongAdder.class == targetType) {
|
||||
} else if (LongAdder.class == targetType) {
|
||||
//jdk8 新增
|
||||
final Number number = convertInternal(value, Long.class);
|
||||
final Number number = convert(value, Long.class, toStrFunc);
|
||||
if (null != number) {
|
||||
final LongAdder longValue = new LongAdder();
|
||||
longValue.add(number.longValue());
|
||||
@ -130,7 +145,7 @@ public class NumberConverter extends AbstractConverter<Number> {
|
||||
} else if (value instanceof Boolean) {
|
||||
return BooleanUtil.toFloatObj((Boolean) value);
|
||||
}
|
||||
final String valueStr = convertToStr(value);
|
||||
final String valueStr = toStrFunc.apply((value));
|
||||
return StrUtil.isBlank(valueStr) ? null : Float.valueOf(valueStr);
|
||||
|
||||
} else if (Double.class == targetType) {
|
||||
@ -139,31 +154,31 @@ public class NumberConverter extends AbstractConverter<Number> {
|
||||
} else if (value instanceof Boolean) {
|
||||
return BooleanUtil.toDoubleObj((Boolean) value);
|
||||
}
|
||||
final String valueStr = convertToStr(value);
|
||||
final String valueStr = toStrFunc.apply((value));
|
||||
return StrUtil.isBlank(valueStr) ? null : Double.valueOf(valueStr);
|
||||
}else if (DoubleAdder.class == targetType) {
|
||||
} else if (DoubleAdder.class == targetType) {
|
||||
//jdk8 新增
|
||||
final Number number = convertInternal(value, Long.class);
|
||||
final Number number = convert(value, Long.class, toStrFunc);
|
||||
if (null != number) {
|
||||
final DoubleAdder doubleAdder = new DoubleAdder();
|
||||
doubleAdder.add(number.doubleValue());
|
||||
return doubleAdder;
|
||||
}
|
||||
} else if (BigDecimal.class == targetType) {
|
||||
return toBigDecimal(value);
|
||||
return toBigDecimal(value, toStrFunc);
|
||||
} else if (BigInteger.class == targetType) {
|
||||
return toBigInteger(value);
|
||||
return toBigInteger(value, toStrFunc);
|
||||
} else if (Number.class == targetType) {
|
||||
if (value instanceof Number) {
|
||||
return (Number) value;
|
||||
} else if (value instanceof Boolean) {
|
||||
return BooleanUtil.toInteger((Boolean) value);
|
||||
}
|
||||
final String valueStr = convertToStr(value);
|
||||
final String valueStr = toStrFunc.apply((value));
|
||||
return StrUtil.isBlank(valueStr) ? null : NumberUtil.parseNumber(valueStr);
|
||||
}
|
||||
|
||||
throw new UnsupportedOperationException(StrUtil.format("Unsupport Number type: {}", this.targetType.getName()));
|
||||
throw new UnsupportedOperationException(StrUtil.format("Unsupport Number type: {}", targetType.getName()));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -171,10 +186,11 @@ public class NumberConverter extends AbstractConverter<Number> {
|
||||
* 如果给定的值为空,或者转换失败,返回默认值<br>
|
||||
* 转换失败不会报错
|
||||
*
|
||||
* @param value 被转换的值
|
||||
* @param value 被转换的值
|
||||
* @param toStrFunc 转换为字符串的函数规则
|
||||
* @return 结果
|
||||
*/
|
||||
private BigDecimal toBigDecimal(Object value) {
|
||||
private static BigDecimal toBigDecimal(Object value, Function<Object, String> toStrFunc) {
|
||||
if (value instanceof Number) {
|
||||
return NumberUtil.toBigDecimal((Number) value);
|
||||
} else if (value instanceof Boolean) {
|
||||
@ -182,7 +198,7 @@ public class NumberConverter extends AbstractConverter<Number> {
|
||||
}
|
||||
|
||||
//对于Double类型,先要转换为String,避免精度问题
|
||||
return NumberUtil.toBigDecimal(convertToStr(value));
|
||||
return NumberUtil.toBigDecimal(toStrFunc.apply(value));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -190,25 +206,26 @@ public class NumberConverter extends AbstractConverter<Number> {
|
||||
* 如果给定的值为空,或者转换失败,返回默认值<br>
|
||||
* 转换失败不会报错
|
||||
*
|
||||
* @param value 被转换的值
|
||||
* @param value 被转换的值
|
||||
* @param toStrFunc 转换为字符串的函数规则
|
||||
* @return 结果
|
||||
*/
|
||||
private BigInteger toBigInteger(Object value) {
|
||||
private static BigInteger toBigInteger(Object value, Function<Object, String> toStrFunc) {
|
||||
if (value instanceof Long) {
|
||||
return BigInteger.valueOf((Long) value);
|
||||
} else if (value instanceof Boolean) {
|
||||
return BigInteger.valueOf((boolean) value ? 1 : 0);
|
||||
}
|
||||
|
||||
return NumberUtil.toBigInteger(convertToStr(value));
|
||||
return NumberUtil.toBigInteger(toStrFunc.apply(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String convertToStr(Object value) {
|
||||
String result = StrUtil.trim(super.convertToStr(value));
|
||||
if(StrUtil.isNotEmpty(result)){
|
||||
if (StrUtil.isNotEmpty(result)) {
|
||||
final char c = Character.toUpperCase(result.charAt(result.length() - 1));
|
||||
if(c == 'D' || c == 'L' || c == 'F'){
|
||||
if (c == 'D' || c == 'L' || c == 'F') {
|
||||
// 类型标识形式(例如123.6D)
|
||||
return StrUtil.subPre(result, -1);
|
||||
}
|
||||
|
@ -1,15 +1,12 @@
|
||||
package cn.hutool.core.convert.impl;
|
||||
|
||||
import cn.hutool.core.convert.AbstractConverter;
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.convert.ConvertException;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.util.BooleanUtil;
|
||||
import cn.hutool.core.util.NumberUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import java.time.temporal.TemporalAccessor;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* 原始类型转换器<br>
|
||||
@ -49,114 +46,7 @@ public class PrimitiveConverter extends AbstractConverter<Object> {
|
||||
|
||||
@Override
|
||||
protected Object convertInternal(Object value) {
|
||||
if (byte.class == this.targetType) {
|
||||
if (value instanceof Number) {
|
||||
return ((Number) value).byteValue();
|
||||
} else if (value instanceof Boolean) {
|
||||
return BooleanUtil.toByte((Boolean) value);
|
||||
}
|
||||
final String valueStr = convertToStr(value);
|
||||
if (StrUtil.isBlank(valueStr)) {
|
||||
return 0;
|
||||
}
|
||||
return Byte.parseByte(valueStr);
|
||||
|
||||
} else if (short.class == this.targetType) {
|
||||
if (value instanceof Number) {
|
||||
return ((Number) value).shortValue();
|
||||
} else if (value instanceof Boolean) {
|
||||
return BooleanUtil.toShort((Boolean) value);
|
||||
}
|
||||
final String valueStr = convertToStr(value);
|
||||
if (StrUtil.isBlank(valueStr)) {
|
||||
return 0;
|
||||
}
|
||||
return Short.parseShort(valueStr);
|
||||
|
||||
} else if (int.class == this.targetType) {
|
||||
if (value instanceof Number) {
|
||||
return ((Number) value).intValue();
|
||||
} else if (value instanceof Boolean) {
|
||||
return BooleanUtil.toInt((Boolean) value);
|
||||
} else if (value instanceof Date) {
|
||||
return ((Date) value).getTime();
|
||||
} else if (value instanceof Calendar) {
|
||||
return ((Calendar) value).getTimeInMillis();
|
||||
} else if (value instanceof TemporalAccessor) {
|
||||
return DateUtil.toInstant((TemporalAccessor) value).toEpochMilli();
|
||||
}
|
||||
|
||||
final String valueStr = convertToStr(value);
|
||||
if (StrUtil.isBlank(valueStr)) {
|
||||
return 0;
|
||||
}
|
||||
return NumberUtil.parseInt(valueStr);
|
||||
|
||||
} else if (long.class == this.targetType) {
|
||||
if (value instanceof Number) {
|
||||
return ((Number) value).longValue();
|
||||
} else if (value instanceof Boolean) {
|
||||
return BooleanUtil.toLong((Boolean) value);
|
||||
} else if (value instanceof Date) {
|
||||
return ((Date) value).getTime();
|
||||
} else if (value instanceof Calendar) {
|
||||
return ((Calendar) value).getTimeInMillis();
|
||||
} else if (value instanceof TemporalAccessor) {
|
||||
return DateUtil.toInstant((TemporalAccessor) value).toEpochMilli();
|
||||
}
|
||||
|
||||
final String valueStr = convertToStr(value);
|
||||
if (StrUtil.isBlank(valueStr)) {
|
||||
return 0;
|
||||
}
|
||||
return NumberUtil.parseLong(valueStr);
|
||||
|
||||
} else if (float.class == this.targetType) {
|
||||
if (value instanceof Number) {
|
||||
return ((Number) value).floatValue();
|
||||
} else if (value instanceof Boolean) {
|
||||
return BooleanUtil.toFloat((Boolean) value);
|
||||
}
|
||||
final String valueStr = convertToStr(value);
|
||||
if (StrUtil.isBlank(valueStr)) {
|
||||
return 0;
|
||||
}
|
||||
return Float.parseFloat(valueStr);
|
||||
|
||||
} else if (double.class == this.targetType) {
|
||||
if (value instanceof Number) {
|
||||
return ((Number) value).doubleValue();
|
||||
} else if (value instanceof Boolean) {
|
||||
return BooleanUtil.toDouble((Boolean) value);
|
||||
}
|
||||
final String valueStr = convertToStr(value);
|
||||
if (StrUtil.isBlank(valueStr)) {
|
||||
return 0;
|
||||
}
|
||||
return Double.parseDouble(valueStr);
|
||||
|
||||
} else if (char.class == this.targetType) {
|
||||
if (value instanceof Character) {
|
||||
//noinspection UnnecessaryUnboxing
|
||||
return ((Character) value).charValue();
|
||||
} else if (value instanceof Boolean) {
|
||||
return BooleanUtil.toChar((Boolean) value);
|
||||
}
|
||||
final String valueStr = convertToStr(value);
|
||||
if (StrUtil.isBlank(valueStr)) {
|
||||
return 0;
|
||||
}
|
||||
return valueStr.charAt(0);
|
||||
} else if (boolean.class == this.targetType) {
|
||||
if (value instanceof Boolean) {
|
||||
//noinspection UnnecessaryUnboxing
|
||||
return ((Boolean) value).booleanValue();
|
||||
}
|
||||
final String valueStr = convertToStr(value);
|
||||
return BooleanUtil.toBoolean(valueStr);
|
||||
}
|
||||
|
||||
throw new ConvertException("Unsupported target type: {}", this.targetType);
|
||||
return PrimitiveConverter.convert(value, this.targetType, this::convertToStr);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -169,4 +59,34 @@ public class PrimitiveConverter extends AbstractConverter<Object> {
|
||||
public Class<Object> getTargetType() {
|
||||
return (Class<Object>) this.targetType;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将指定值转换为原始类型的值
|
||||
* @param value 值
|
||||
* @param primitiveClass 原始类型
|
||||
* @param toStringFunc 当无法直接转换时,转为字符串后再转换的函数
|
||||
* @return 转换结果
|
||||
* @since 5.5.0
|
||||
*/
|
||||
protected static Object convert(Object value, Class<?> primitiveClass, Function<Object, String> toStringFunc) {
|
||||
if (byte.class == primitiveClass) {
|
||||
return ObjectUtil.defaultIfNull(NumberConverter.convert(value, Byte.class, toStringFunc), 0);
|
||||
} else if (short.class == primitiveClass) {
|
||||
return ObjectUtil.defaultIfNull(NumberConverter.convert(value, Short.class, toStringFunc), 0);
|
||||
} else if (int.class == primitiveClass) {
|
||||
return ObjectUtil.defaultIfNull(NumberConverter.convert(value, Integer.class, toStringFunc), 0);
|
||||
} else if (long.class == primitiveClass) {
|
||||
return ObjectUtil.defaultIfNull(NumberConverter.convert(value, Long.class, toStringFunc), 0);
|
||||
} else if (float.class == primitiveClass) {
|
||||
return ObjectUtil.defaultIfNull(NumberConverter.convert(value, Float.class, toStringFunc), 0);
|
||||
} else if (double.class == primitiveClass) {
|
||||
return ObjectUtil.defaultIfNull(NumberConverter.convert(value, Double.class, toStringFunc), 0);
|
||||
} else if (char.class == primitiveClass) {
|
||||
return Convert.convert(Character.class, value);
|
||||
} else if (boolean.class == primitiveClass) {
|
||||
return Convert.convert(Boolean.class, value);
|
||||
}
|
||||
|
||||
throw new ConvertException("Unsupported target type: {}", primitiveClass);
|
||||
}
|
||||
}
|
||||
|
@ -1,208 +1,20 @@
|
||||
package cn.hutool.core.date;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 时长格式化器
|
||||
* 时长格式化器<br>
|
||||
*
|
||||
*
|
||||
* @author Looly
|
||||
* @deprecated 拼写错误,请使用{@link BetweenFormatter}
|
||||
*/
|
||||
public class BetweenFormater implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
@Deprecated
|
||||
public class BetweenFormater extends BetweenFormatter {
|
||||
|
||||
/**
|
||||
* 时长毫秒数
|
||||
*/
|
||||
private long betweenMs;
|
||||
/**
|
||||
* 格式化级别
|
||||
*/
|
||||
private Level level;
|
||||
/**
|
||||
* 格式化级别的最大个数
|
||||
*/
|
||||
private final int levelMaxCount;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param betweenMs 日期间隔
|
||||
* @param level 级别,按照天、小时、分、秒、毫秒分为5个等级,根据传入等级,格式化到相应级别
|
||||
*/
|
||||
public BetweenFormater(long betweenMs, Level level) {
|
||||
this(betweenMs, level, 0);
|
||||
super(betweenMs, level);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param betweenMs 日期间隔
|
||||
* @param level 级别,按照天、小时、分、秒、毫秒分为5个等级,根据传入等级,格式化到相应级别
|
||||
* @param levelMaxCount 格式化级别的最大个数,假如级别个数为1,但是级别到秒,那只显示一个级别
|
||||
*/
|
||||
public BetweenFormater(long betweenMs, Level level, int levelMaxCount) {
|
||||
this.betweenMs = betweenMs;
|
||||
this.level = level;
|
||||
this.levelMaxCount = levelMaxCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化日期间隔输出<br>
|
||||
*
|
||||
* @return 格式化后的字符串
|
||||
*/
|
||||
public String format() {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
if (betweenMs > 0) {
|
||||
long day = betweenMs / DateUnit.DAY.getMillis();
|
||||
long hour = betweenMs / DateUnit.HOUR.getMillis() - day * 24;
|
||||
long minute = betweenMs / DateUnit.MINUTE.getMillis() - day * 24 * 60 - hour * 60;
|
||||
|
||||
final long BetweenOfSecond = ((day * 24 + hour) * 60 + minute) * 60;
|
||||
long second = betweenMs / DateUnit.SECOND.getMillis() - BetweenOfSecond;
|
||||
long millisecond = betweenMs - (BetweenOfSecond + second) * 1000;
|
||||
|
||||
final int level = this.level.ordinal();
|
||||
int levelCount = 0;
|
||||
|
||||
if (isLevelCountValid(levelCount) && 0 != day && level >= Level.DAY.ordinal()) {
|
||||
sb.append(day).append(Level.DAY.name);
|
||||
levelCount++;
|
||||
}
|
||||
if (isLevelCountValid(levelCount) && 0 != hour && level >= Level.HOUR.ordinal()) {
|
||||
sb.append(hour).append(Level.HOUR.name);
|
||||
levelCount++;
|
||||
}
|
||||
if (isLevelCountValid(levelCount) && 0 != minute && level >= Level.MINUTE.ordinal()) {
|
||||
sb.append(minute).append(Level.MINUTE.name);
|
||||
levelCount++;
|
||||
}
|
||||
if (isLevelCountValid(levelCount) && 0 != second && level >= Level.SECOND.ordinal()) {
|
||||
sb.append(second).append(Level.SECOND.name);
|
||||
levelCount++;
|
||||
}
|
||||
if (isLevelCountValid(levelCount) && 0 != millisecond && level >= Level.MILLISECOND.ordinal()) {
|
||||
sb.append(millisecond).append(Level.MILLISECOND.name);
|
||||
// levelCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (StrUtil.isEmpty(sb)) {
|
||||
sb.append(0).append(this.level.name);
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得 时长毫秒数
|
||||
*
|
||||
* @return 时长毫秒数
|
||||
*/
|
||||
public long getBetweenMs() {
|
||||
return betweenMs;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 时长毫秒数
|
||||
*
|
||||
* @param betweenMs 时长毫秒数
|
||||
*/
|
||||
public void setBetweenMs(long betweenMs) {
|
||||
this.betweenMs = betweenMs;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得 格式化级别
|
||||
*
|
||||
* @return 格式化级别
|
||||
*/
|
||||
public Level getLevel() {
|
||||
return level;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置格式化级别
|
||||
*
|
||||
* @param level 格式化级别
|
||||
*/
|
||||
public void setLevel(Level level) {
|
||||
this.level = level;
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化等级枚举
|
||||
*
|
||||
* @author Looly
|
||||
*/
|
||||
public enum Level {
|
||||
|
||||
/**
|
||||
* 天
|
||||
*/
|
||||
DAY("天"),
|
||||
/**
|
||||
* 小时
|
||||
*/
|
||||
HOUR("小时"),
|
||||
/**
|
||||
* 分钟
|
||||
*/
|
||||
MINUTE("分"),
|
||||
/**
|
||||
* 秒
|
||||
*/
|
||||
SECOND("秒"),
|
||||
/**
|
||||
* 毫秒
|
||||
* @deprecated 拼写错误,请使用{@link #MILLISECOND}
|
||||
*/
|
||||
@Deprecated
|
||||
MILLSECOND("毫秒"),
|
||||
/**
|
||||
* 毫秒
|
||||
*/
|
||||
MILLISECOND("毫秒");
|
||||
|
||||
/**
|
||||
* 级别名称
|
||||
*/
|
||||
private final String name;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param name 级别名称
|
||||
*/
|
||||
Level(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取级别名称
|
||||
*
|
||||
* @return 级别名称
|
||||
*/
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return format();
|
||||
}
|
||||
|
||||
/**
|
||||
* 等级数量是否有效<br>
|
||||
* 有效的定义是:levelMaxCount大于0(被设置),当前等级数量没有超过这个最大值
|
||||
*
|
||||
* @param levelCount 登记数量
|
||||
* @return 是否有效
|
||||
*/
|
||||
private boolean isLevelCountValid(int levelCount) {
|
||||
return this.levelMaxCount <= 0 || levelCount < this.levelMaxCount;
|
||||
super(betweenMs, level, levelMaxCount);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,215 @@
|
||||
package cn.hutool.core.date;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 时长格式化器,用于格式化输出两个日期相差的时长<br>
|
||||
* 根据{@link Level}不同,调用{@link #format()}方法后返回类似于:
|
||||
* <ul>
|
||||
* <li>XX小时XX分XX秒</li>
|
||||
* <li>XX天XX小时</li>
|
||||
* <li>XX月XX天XX小时</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author Looly
|
||||
*/
|
||||
public class BetweenFormatter implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 时长毫秒数
|
||||
*/
|
||||
private long betweenMs;
|
||||
/**
|
||||
* 格式化级别
|
||||
*/
|
||||
private Level level;
|
||||
/**
|
||||
* 格式化级别的最大个数
|
||||
*/
|
||||
private final int levelMaxCount;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param betweenMs 日期间隔
|
||||
* @param level 级别,按照天、小时、分、秒、毫秒分为5个等级,根据传入等级,格式化到相应级别
|
||||
*/
|
||||
public BetweenFormatter(long betweenMs, Level level) {
|
||||
this(betweenMs, level, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param betweenMs 日期间隔
|
||||
* @param level 级别,按照天、小时、分、秒、毫秒分为5个等级,根据传入等级,格式化到相应级别
|
||||
* @param levelMaxCount 格式化级别的最大个数,假如级别个数为1,但是级别到秒,那只显示一个级别
|
||||
*/
|
||||
public BetweenFormatter(long betweenMs, Level level, int levelMaxCount) {
|
||||
this.betweenMs = betweenMs;
|
||||
this.level = level;
|
||||
this.levelMaxCount = levelMaxCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化日期间隔输出<br>
|
||||
*
|
||||
* @return 格式化后的字符串
|
||||
*/
|
||||
public String format() {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
if (betweenMs > 0) {
|
||||
long day = betweenMs / DateUnit.DAY.getMillis();
|
||||
long hour = betweenMs / DateUnit.HOUR.getMillis() - day * 24;
|
||||
long minute = betweenMs / DateUnit.MINUTE.getMillis() - day * 24 * 60 - hour * 60;
|
||||
|
||||
final long BetweenOfSecond = ((day * 24 + hour) * 60 + minute) * 60;
|
||||
long second = betweenMs / DateUnit.SECOND.getMillis() - BetweenOfSecond;
|
||||
long millisecond = betweenMs - (BetweenOfSecond + second) * 1000;
|
||||
|
||||
final int level = this.level.ordinal();
|
||||
int levelCount = 0;
|
||||
|
||||
if (isLevelCountValid(levelCount) && 0 != day && level >= Level.DAY.ordinal()) {
|
||||
sb.append(day).append(Level.DAY.name);
|
||||
levelCount++;
|
||||
}
|
||||
if (isLevelCountValid(levelCount) && 0 != hour && level >= Level.HOUR.ordinal()) {
|
||||
sb.append(hour).append(Level.HOUR.name);
|
||||
levelCount++;
|
||||
}
|
||||
if (isLevelCountValid(levelCount) && 0 != minute && level >= Level.MINUTE.ordinal()) {
|
||||
sb.append(minute).append(Level.MINUTE.name);
|
||||
levelCount++;
|
||||
}
|
||||
if (isLevelCountValid(levelCount) && 0 != second && level >= Level.SECOND.ordinal()) {
|
||||
sb.append(second).append(Level.SECOND.name);
|
||||
levelCount++;
|
||||
}
|
||||
if (isLevelCountValid(levelCount) && 0 != millisecond && level >= Level.MILLISECOND.ordinal()) {
|
||||
sb.append(millisecond).append(Level.MILLISECOND.name);
|
||||
// levelCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (StrUtil.isEmpty(sb)) {
|
||||
sb.append(0).append(this.level.name);
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得 时长毫秒数
|
||||
*
|
||||
* @return 时长毫秒数
|
||||
*/
|
||||
public long getBetweenMs() {
|
||||
return betweenMs;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 时长毫秒数
|
||||
*
|
||||
* @param betweenMs 时长毫秒数
|
||||
*/
|
||||
public void setBetweenMs(long betweenMs) {
|
||||
this.betweenMs = betweenMs;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得 格式化级别
|
||||
*
|
||||
* @return 格式化级别
|
||||
*/
|
||||
public Level getLevel() {
|
||||
return level;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置格式化级别
|
||||
*
|
||||
* @param level 格式化级别
|
||||
*/
|
||||
public void setLevel(Level level) {
|
||||
this.level = level;
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化等级枚举
|
||||
*
|
||||
* @author Looly
|
||||
*/
|
||||
public enum Level {
|
||||
|
||||
/**
|
||||
* 天
|
||||
*/
|
||||
DAY("天"),
|
||||
/**
|
||||
* 小时
|
||||
*/
|
||||
HOUR("小时"),
|
||||
/**
|
||||
* 分钟
|
||||
*/
|
||||
MINUTE("分"),
|
||||
/**
|
||||
* 秒
|
||||
*/
|
||||
SECOND("秒"),
|
||||
/**
|
||||
* 毫秒
|
||||
*
|
||||
* @deprecated 拼写错误,请使用{@link #MILLISECOND}
|
||||
*/
|
||||
@Deprecated
|
||||
MILLSECOND("毫秒"),
|
||||
/**
|
||||
* 毫秒
|
||||
*/
|
||||
MILLISECOND("毫秒");
|
||||
|
||||
/**
|
||||
* 级别名称
|
||||
*/
|
||||
private final String name;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param name 级别名称
|
||||
*/
|
||||
Level(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取级别名称
|
||||
*
|
||||
* @return 级别名称
|
||||
*/
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return format();
|
||||
}
|
||||
|
||||
/**
|
||||
* 等级数量是否有效<br>
|
||||
* 有效的定义是:levelMaxCount大于0(被设置),当前等级数量没有超过这个最大值
|
||||
*
|
||||
* @param levelCount 登记数量
|
||||
* @return 是否有效
|
||||
*/
|
||||
private boolean isLevelCountValid(int levelCount) {
|
||||
return this.levelMaxCount <= 0 || levelCount < this.levelMaxCount;
|
||||
}
|
||||
}
|
@ -159,12 +159,12 @@ public class DateBetween implements Serializable{
|
||||
* @param level 级别
|
||||
* @return 字符串
|
||||
*/
|
||||
public String toString(BetweenFormater.Level level) {
|
||||
public String toString(BetweenFormatter.Level level) {
|
||||
return DateUtil.formatBetween(between(DateUnit.MS), level);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return toString(BetweenFormater.Level.MILLISECOND);
|
||||
return toString(BetweenFormatter.Level.MILLISECOND);
|
||||
}
|
||||
}
|
||||
|
@ -695,7 +695,7 @@ public class DateTime extends Date {
|
||||
* @param formatLevel 格式化级别
|
||||
* @return 相差时长
|
||||
*/
|
||||
public String between(Date date, DateUnit unit, BetweenFormater.Level formatLevel) {
|
||||
public String between(Date date, DateUnit unit, BetweenFormatter.Level formatLevel) {
|
||||
return new DateBetween(this, date).toString(formatLevel);
|
||||
}
|
||||
|
||||
|
@ -1409,7 +1409,7 @@ public class DateUtil extends CalendarUtil {
|
||||
* @param level 级别,按照天、小时、分、秒、毫秒分为5个等级
|
||||
* @return XX天XX小时XX分XX秒
|
||||
*/
|
||||
public static String formatBetween(Date beginDate, Date endDate, BetweenFormater.Level level) {
|
||||
public static String formatBetween(Date beginDate, Date endDate, BetweenFormatter.Level level) {
|
||||
return formatBetween(between(beginDate, endDate, DateUnit.MS), level);
|
||||
}
|
||||
|
||||
@ -1432,8 +1432,8 @@ public class DateUtil extends CalendarUtil {
|
||||
* @param level 级别,按照天、小时、分、秒、毫秒分为5个等级
|
||||
* @return XX天XX小时XX分XX秒XX毫秒
|
||||
*/
|
||||
public static String formatBetween(long betweenMs, BetweenFormater.Level level) {
|
||||
return new BetweenFormater(betweenMs, level).format();
|
||||
public static String formatBetween(long betweenMs, BetweenFormatter.Level level) {
|
||||
return new BetweenFormatter(betweenMs, level).format();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1444,7 +1444,7 @@ public class DateUtil extends CalendarUtil {
|
||||
* @since 3.0.1
|
||||
*/
|
||||
public static String formatBetween(long betweenMs) {
|
||||
return new BetweenFormater(betweenMs, BetweenFormater.Level.MILLISECOND).format();
|
||||
return new BetweenFormatter(betweenMs, BetweenFormatter.Level.MILLISECOND).format();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -553,6 +553,7 @@ public class Img implements Serializable {
|
||||
Graphics2D graphics2d = targetImg.createGraphics();
|
||||
graphics2d.drawImage(image, 0, 0, width, height, width, 0, 0, height, null);
|
||||
graphics2d.dispose();
|
||||
|
||||
this.targetImage = targetImg;
|
||||
return this;
|
||||
}
|
||||
|
@ -306,6 +306,7 @@ public class FileUtil extends PathUtil {
|
||||
|
||||
/**
|
||||
* 创建File对象<br>
|
||||
* 根据的路径构建文件,在Win下直接构建,在Linux下拆分路径单独构建
|
||||
* 此方法会检查slip漏洞,漏洞说明见http://blog.nsfocus.net/zip-slip-2/
|
||||
*
|
||||
* @param parent 父文件对象
|
||||
@ -316,7 +317,7 @@ public class FileUtil extends PathUtil {
|
||||
if (StrUtil.isBlank(path)) {
|
||||
throw new NullPointerException("File path is blank!");
|
||||
}
|
||||
return checkSlip(parent, new File(parent, path));
|
||||
return checkSlip(parent, buildFile(parent, path));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -967,41 +968,13 @@ public class FileUtil extends PathUtil {
|
||||
* 移动文件或者目录
|
||||
*
|
||||
* @param src 源文件或者目录
|
||||
* @param dest 目标文件或者目录
|
||||
* @param target 目标文件或者目录
|
||||
* @param isOverride 是否覆盖目标,只有目标为文件才覆盖
|
||||
* @throws IORuntimeException IO异常
|
||||
* @see PathUtil#move(Path, Path, boolean)
|
||||
*/
|
||||
public static void move(File src, File dest, boolean isOverride) throws IORuntimeException {
|
||||
// check
|
||||
if (false == src.exists()) {
|
||||
throw new IORuntimeException("File not found: " + src);
|
||||
}
|
||||
|
||||
// 来源为文件夹,目标为文件
|
||||
if (src.isDirectory() && dest.isFile()) {
|
||||
throw new IORuntimeException(StrUtil.format("Can not move directory [{}] to file [{}]", src, dest));
|
||||
}
|
||||
|
||||
if (isOverride && dest.isFile()) {// 只有目标为文件的情况下覆盖之
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
dest.delete();
|
||||
}
|
||||
|
||||
// 来源为文件,目标为文件夹
|
||||
if (src.isFile() && dest.isDirectory()) {
|
||||
dest = new File(dest, src.getName());
|
||||
}
|
||||
|
||||
if (false == src.renameTo(dest)) {
|
||||
// 在文件系统不同的情况下,renameTo会失败,此时使用copy,然后删除原文件
|
||||
try {
|
||||
copy(src, dest, isOverride);
|
||||
} catch (Exception e) {
|
||||
throw new IORuntimeException(StrUtil.format("Move [{}] to [{}] failed!", src, dest), e);
|
||||
}
|
||||
// 复制后删除源
|
||||
del(src);
|
||||
}
|
||||
public static void move(File src, File target, boolean isOverride) throws IORuntimeException {
|
||||
move(src.toPath(), target.toPath(), isOverride);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -3277,4 +3250,34 @@ public class FileUtil extends PathUtil {
|
||||
public static void tail(File file, Charset charset) {
|
||||
tail(file, charset, Tailer.CONSOLE_HANDLER);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据压缩包中的路径构建目录结构,在Win下直接构建,在Linux下拆分路径单独构建
|
||||
*
|
||||
* @param outFile 最外部路径
|
||||
* @param fileName 文件名,可以包含路径
|
||||
* @return 文件或目录
|
||||
* @since 5.0.5
|
||||
*/
|
||||
private static File buildFile(File outFile, String fileName) {
|
||||
// 替换Windows路径分隔符为Linux路径分隔符,便于统一处理
|
||||
fileName = fileName.replace('\\', '/');
|
||||
if (false == FileUtil.isWindows()
|
||||
// 检查文件名中是否包含"/",不考虑以"/"结尾的情况
|
||||
&& fileName.lastIndexOf(CharUtil.SLASH, fileName.length() - 2) > 0) {
|
||||
// 在Linux下多层目录创建存在问题,/会被当成文件名的一部分,此处做处理
|
||||
// 使用/拆分路径(zip中无\),级联创建父目录
|
||||
final List<String> pathParts = StrUtil.split(fileName, '/', false, true);
|
||||
final int lastPartIndex = pathParts.size() - 1;//目录个数
|
||||
for (int i = 0; i < lastPartIndex; i++) {
|
||||
//由于路径拆分,slip不检查,在最后一步检查
|
||||
outFile = new File(outFile, pathParts.get(i));
|
||||
}
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
outFile.mkdirs();
|
||||
// 最后一个部分如果非空,作为文件名
|
||||
fileName = pathParts.get(lastPartIndex);
|
||||
}
|
||||
return new File(outFile, fileName);
|
||||
}
|
||||
}
|
||||
|
@ -44,7 +44,7 @@ public class FileCopier extends SrcToDestCopier<File, FileCopier>{
|
||||
* 新建一个文件复制器
|
||||
* @param srcPath 源文件路径(相对ClassPath路径或绝对路径)
|
||||
* @param destPath 目标文件路径(相对ClassPath路径或绝对路径)
|
||||
* @return {@link FileCopier}
|
||||
* @return this
|
||||
*/
|
||||
public static FileCopier create(String srcPath, String destPath) {
|
||||
return new FileCopier(FileUtil.file(srcPath), FileUtil.file(destPath));
|
||||
@ -54,7 +54,7 @@ public class FileCopier extends SrcToDestCopier<File, FileCopier>{
|
||||
* 新建一个文件复制器
|
||||
* @param src 源文件
|
||||
* @param dest 目标文件
|
||||
* @return {@link FileCopier}
|
||||
* @return this
|
||||
*/
|
||||
public static FileCopier create(File src, File dest) {
|
||||
return new FileCopier(src, dest);
|
||||
@ -188,8 +188,8 @@ public class FileCopier extends SrcToDestCopier<File, FileCopier>{
|
||||
throw new IORuntimeException("Dest is a sub directory of src !");
|
||||
}
|
||||
|
||||
final File subDest = isCopyContentIfDir ? dest : FileUtil.mkdir(FileUtil.file(dest, src.getName()));
|
||||
internalCopyDirContent(src, subDest);
|
||||
final File subTarget = isCopyContentIfDir ? dest : FileUtil.mkdir(FileUtil.file(dest, src.getName()));
|
||||
internalCopyDirContent(src, subTarget);
|
||||
} else {// 复制文件
|
||||
internalCopyFile(src, dest);
|
||||
}
|
||||
|
@ -66,7 +66,7 @@ public class PathUtil {
|
||||
|
||||
if (null == path || false == Files.exists(path)) {
|
||||
return fileList;
|
||||
} else if (false == Files.isDirectory(path)) {
|
||||
} else if (false == isDirectory(path)) {
|
||||
final File file = path.toFile();
|
||||
if (null == fileFilter || fileFilter.accept(file)) {
|
||||
fileList.add(file);
|
||||
@ -112,7 +112,7 @@ public class PathUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除文件或者文件夹<br>
|
||||
* 删除文件或者文件夹,不追踪软链<br>
|
||||
* 注意:删除文件夹时不会判断文件夹是否为空,如果不空则递归删除子文件或文件夹<br>
|
||||
* 某个文件删除失败会终止删除操作
|
||||
*
|
||||
@ -127,7 +127,7 @@ public class PathUtil {
|
||||
}
|
||||
|
||||
try {
|
||||
if (Files.isDirectory(path)) {
|
||||
if (isDirectory(path)) {
|
||||
Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
|
||||
|
||||
@Override
|
||||
@ -182,7 +182,7 @@ public class PathUtil {
|
||||
Assert.notNull(src, "Source File is null !");
|
||||
Assert.notNull(dest, "Destination File or directiory is null !");
|
||||
|
||||
Path destPath = dest.toFile().isDirectory() ? dest.resolve(src.getFileName()) : dest;
|
||||
Path destPath = isDirectory(dest) ? dest.resolve(src.getFileName()) : dest;
|
||||
try {
|
||||
return Files.copy(src, destPath, options);
|
||||
} catch (IOException e) {
|
||||
@ -190,6 +190,18 @@ public class PathUtil {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为目录,如果file为null,则返回false<br>
|
||||
* 此方法不会追踪到软链对应的真实地址,即软链被当作文件
|
||||
*
|
||||
* @param path {@link Path}
|
||||
* @return 如果为目录true
|
||||
* @since 5.5.1
|
||||
*/
|
||||
public static boolean isDirectory(Path path) {
|
||||
return isDirectory(path, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为目录,如果file为null,则返回false
|
||||
*
|
||||
@ -370,9 +382,28 @@ public class PathUtil {
|
||||
* @since 5.4.1
|
||||
*/
|
||||
public static Path rename(Path path, String newName, boolean isOverride) {
|
||||
return move(path, path.resolveSibling(newName), isOverride);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移动文件或目录<br>
|
||||
* 当目标是目录时,会将源文件或文件夹整体移动至目标目录下
|
||||
*
|
||||
* @param src 源文件或目录路径
|
||||
* @param target 目标路径,如果为目录,则移动到此目录下
|
||||
* @param isOverride 是否覆盖目标文件
|
||||
* @return 目标文件Path
|
||||
* @since 5.5.1
|
||||
*/
|
||||
public static Path move(Path src, Path target, boolean isOverride) {
|
||||
Assert.notNull(src, "Src path must be not null !");
|
||||
Assert.notNull(target, "Target path must be not null !");
|
||||
final CopyOption[] options = isOverride ? new CopyOption[]{StandardCopyOption.REPLACE_EXISTING} : new CopyOption[]{};
|
||||
if (isDirectory(target)) {
|
||||
target = target.resolve(src.getFileName());
|
||||
}
|
||||
try {
|
||||
return Files.move(path, path.resolveSibling(newName), options);
|
||||
return Files.move(src, target, options);
|
||||
} catch (IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
|
@ -1283,7 +1283,7 @@ public class NumberUtil {
|
||||
* @param begin 最小数字(包含该数)
|
||||
* @param end 最大数字(不包含该数)
|
||||
* @param size 指定产生随机数的个数
|
||||
* @param seed 种子,用于取随机数的int池
|
||||
* @param seed 种子,用于取随机数的int池
|
||||
* @return 随机int数组
|
||||
* @since 5.4.5
|
||||
*/
|
||||
@ -1976,8 +1976,8 @@ public class NumberUtil {
|
||||
Assert.notNull(number, "Number is null !");
|
||||
|
||||
// BigDecimal单独处理,使用非科学计数法
|
||||
if(number instanceof BigDecimal){
|
||||
return toStr((BigDecimal)number);
|
||||
if (number instanceof BigDecimal) {
|
||||
return toStr((BigDecimal) number);
|
||||
}
|
||||
|
||||
Assert.isTrue(isValidNumber(number), "Number is non-finite!");
|
||||
@ -2008,7 +2008,9 @@ public class NumberUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 数字转{@link BigDecimal}
|
||||
* 数字转{@link BigDecimal}<br>
|
||||
* Float、Double等有精度问题,转换为字符串后再转换<br>
|
||||
* null转换为0
|
||||
*
|
||||
* @param number 数字
|
||||
* @return {@link BigDecimal}
|
||||
@ -2019,7 +2021,7 @@ public class NumberUtil {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
if(number instanceof BigDecimal){
|
||||
if (number instanceof BigDecimal) {
|
||||
return (BigDecimal) number;
|
||||
} else if (number instanceof Long) {
|
||||
return new BigDecimal((Long) number);
|
||||
@ -2029,22 +2031,25 @@ public class NumberUtil {
|
||||
return new BigDecimal((BigInteger) number);
|
||||
}
|
||||
|
||||
// Float、Double等有精度问题,转换为字符串后再转换
|
||||
return toBigDecimal(number.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* 数字转{@link BigDecimal}
|
||||
* 数字转{@link BigDecimal}<br>
|
||||
* null或""或空白符转换为0
|
||||
*
|
||||
* @param number 数字
|
||||
* @param number 数字字符串
|
||||
* @return {@link BigDecimal}
|
||||
* @since 4.0.9
|
||||
*/
|
||||
public static BigDecimal toBigDecimal(String number) {
|
||||
return (null == number) ? BigDecimal.ZERO : new BigDecimal(number);
|
||||
return StrUtil.isBlank(number) ? BigDecimal.ZERO : new BigDecimal(number);
|
||||
}
|
||||
|
||||
/**
|
||||
* 数字转{@link BigInteger}
|
||||
* 数字转{@link BigInteger}<br>
|
||||
* null转换为0
|
||||
*
|
||||
* @param number 数字
|
||||
* @return {@link BigInteger}
|
||||
@ -2055,7 +2060,7 @@ public class NumberUtil {
|
||||
return BigInteger.ZERO;
|
||||
}
|
||||
|
||||
if(number instanceof BigInteger){
|
||||
if (number instanceof BigInteger) {
|
||||
return (BigInteger) number;
|
||||
} else if (number instanceof Long) {
|
||||
return BigInteger.valueOf((Long) number);
|
||||
@ -2065,14 +2070,15 @@ public class NumberUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 数字转{@link BigInteger}
|
||||
* 数字转{@link BigInteger}<br>
|
||||
* null或""或空白符转换为0
|
||||
*
|
||||
* @param number 数字
|
||||
* @param number 数字字符串
|
||||
* @return {@link BigInteger}
|
||||
* @since 5.4.5
|
||||
*/
|
||||
public static BigInteger toBigInteger(String number) {
|
||||
return (null == number) ? BigInteger.ZERO : new BigInteger(number);
|
||||
return StrUtil.isBlank(number) ? BigInteger.ZERO : new BigInteger(number);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2512,7 +2518,7 @@ public class NumberUtil {
|
||||
*/
|
||||
private static String removeNumberFlag(String number) {
|
||||
// 去掉千位分隔符
|
||||
if(StrUtil.contains(number, CharUtil.COMMA)){
|
||||
if (StrUtil.contains(number, CharUtil.COMMA)) {
|
||||
number = StrUtil.removeAll(number, CharUtil.COMMA);
|
||||
}
|
||||
// 去掉类型标识的结尾
|
||||
|
@ -2405,16 +2405,52 @@ public class StrUtil {
|
||||
}
|
||||
|
||||
final List<String> result = new LinkedList<>();
|
||||
for (String fragment : split(str, prefix)) {
|
||||
int suffixIndex = fragment.indexOf(suffix.toString());
|
||||
if (suffixIndex > 0) {
|
||||
result.add(fragment.substring(0, suffixIndex));
|
||||
final String[] split = split(str, prefix);
|
||||
if(prefix.equals(suffix)){
|
||||
// 前后缀字符相同,单独处理
|
||||
for (int i = 1, length = split.length - 1; i < length; i += 2) {
|
||||
result.add(split[i]);
|
||||
}
|
||||
} else{
|
||||
int suffixIndex;
|
||||
for (String fragment : split) {
|
||||
suffixIndex = fragment.indexOf(suffix.toString());
|
||||
if (suffixIndex > 0) {
|
||||
result.add(fragment.substring(0, suffixIndex));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result.toArray(new String[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 截取指定字符串多段中间部分,不包括标识字符串<br>
|
||||
* <p>
|
||||
* 栗子:
|
||||
*
|
||||
* <pre>
|
||||
* StrUtil.subBetweenAll(null, *) = []
|
||||
* StrUtil.subBetweenAll(*, null) = []
|
||||
* StrUtil.subBetweenAll(*, *) = []
|
||||
* StrUtil.subBetweenAll("", "") = []
|
||||
* StrUtil.subBetweenAll("", "#") = []
|
||||
* StrUtil.subBetweenAll("gotanks", "") = []
|
||||
* StrUtil.subBetweenAll("#gotanks#", "#") = ["gotanks"]
|
||||
* StrUtil.subBetweenAll("#hello# #world#!", "#") = ["hello", "world"]
|
||||
* StrUtil.subBetweenAll("#hello# world#!", "#"); = ["hello"]
|
||||
* </pre>
|
||||
*
|
||||
* @param str 被切割的字符串
|
||||
* @param prefixAndSuffix 截取开始和结束的字符串标识
|
||||
* @return 截取后的字符串
|
||||
* @author gotanks
|
||||
* @since 5.5.0
|
||||
*/
|
||||
public static String[] subBetweenAll(CharSequence str, CharSequence prefixAndSuffix) {
|
||||
return subBetweenAll(str, prefixAndSuffix, prefixAndSuffix);
|
||||
}
|
||||
|
||||
/**
|
||||
* 给定字符串是否被字符包围
|
||||
*
|
||||
@ -4019,7 +4055,7 @@ public class StrUtil {
|
||||
return str.toString().lastIndexOf(searchStr.toString(), fromIndex);
|
||||
}
|
||||
|
||||
for (int i = fromIndex; i > 0; i--) {
|
||||
for (int i = fromIndex; i >= 0; i--) {
|
||||
if (isSubEquals(str, i, searchStr, 0, searchStr.length(), true)) {
|
||||
return i;
|
||||
}
|
||||
|
@ -471,7 +471,7 @@ public class ZipUtil {
|
||||
while (em.hasMoreElements()) {
|
||||
zipEntry = em.nextElement();
|
||||
// FileUtil.file会检查slip漏洞,漏洞说明见http://blog.nsfocus.net/zip-slip-2/
|
||||
outItemFile = buildFile(outFile, zipEntry.getName());
|
||||
outItemFile = FileUtil.file(outFile, zipEntry.getName());
|
||||
if (zipEntry.isDirectory()) {
|
||||
// 创建对应目录
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
@ -1106,36 +1106,6 @@ public class ZipUtil {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据压缩包中的路径构建目录结构,在Win下直接构建,在Linux下拆分路径单独构建
|
||||
*
|
||||
* @param outFile 最外部路径
|
||||
* @param fileName 文件名,可以包含路径
|
||||
* @return 文件或目录
|
||||
* @since 5.0.5
|
||||
*/
|
||||
private static File buildFile(File outFile, String fileName) {
|
||||
// 替换Windows路径分隔符为Linux路径分隔符,便于统一处理
|
||||
fileName = fileName.replace('\\', '/');
|
||||
if (false == FileUtil.isWindows()
|
||||
// 检查文件名中是否包含"/",不考虑以"/"结尾的情况
|
||||
&& fileName.lastIndexOf(CharUtil.SLASH, fileName.length() - 2) > 0) {
|
||||
// 在Linux下多层目录创建存在问题,/会被当成文件名的一部分,此处做处理
|
||||
// 使用/拆分路径(zip中无\),级联创建父目录
|
||||
final List<String> pathParts = StrUtil.split(fileName, '/', false, true);
|
||||
final int lastPartIndex = pathParts.size() - 1;//目录个数
|
||||
for (int i = 0; i < lastPartIndex; i++) {
|
||||
//由于路径拆分,slip不检查,在最后一步检查
|
||||
outFile = new File(outFile, pathParts.get(i));
|
||||
}
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
outFile.mkdirs();
|
||||
// 最后一个部分如果非空,作为文件名
|
||||
fileName = pathParts.get(lastPartIndex);
|
||||
}
|
||||
return FileUtil.file(outFile, fileName);
|
||||
}
|
||||
// ---------------------------------------------------------------------------------------------- Private method end
|
||||
|
||||
}
|
@ -4,6 +4,7 @@ import cn.hutool.core.annotation.Alias;
|
||||
import cn.hutool.core.bean.copier.CopyOptions;
|
||||
import cn.hutool.core.bean.copier.ValueProvider;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.lang.Console;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import lombok.Data;
|
||||
import lombok.Getter;
|
||||
|
@ -2,6 +2,7 @@ package cn.hutool.core.convert;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.Getter;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
@ -253,4 +254,28 @@ public class ConvertTest {
|
||||
private String cName;
|
||||
private String version;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void enumToIntTest(){
|
||||
final Integer integer = Convert.toInt(BuildingType.CUO);
|
||||
Assert.assertEquals(1, integer.intValue());
|
||||
}
|
||||
|
||||
@Getter
|
||||
public enum BuildingType {
|
||||
PING(1, "平层"),
|
||||
CUO(2, "错层"),
|
||||
YUE(3, "跃层"),
|
||||
FUSHI(4, "复式"),
|
||||
KAIJIAN(5, "开间"),
|
||||
OTHER(6, "其他");
|
||||
|
||||
private final int id;
|
||||
private final String name;
|
||||
|
||||
BuildingType(int id, String name){
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
package cn.hutool.core.date;
|
||||
|
||||
import cn.hutool.core.date.BetweenFormater.Level;
|
||||
import cn.hutool.core.date.BetweenFormatter.Level;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
@ -9,20 +9,20 @@ public class BetweenFormaterTest {
|
||||
@Test
|
||||
public void formatTest(){
|
||||
long betweenMs = DateUtil.betweenMs(DateUtil.parse("2017-01-01 22:59:59"), DateUtil.parse("2017-01-02 23:59:58"));
|
||||
BetweenFormater formater = new BetweenFormater(betweenMs, Level.MILLISECOND, 1);
|
||||
BetweenFormatter formater = new BetweenFormatter(betweenMs, Level.MILLISECOND, 1);
|
||||
Assert.assertEquals(formater.toString(), "1天");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void formatBetweenTest(){
|
||||
long betweenMs = DateUtil.betweenMs(DateUtil.parse("2018-07-16 11:23:19"), DateUtil.parse("2018-07-16 11:23:20"));
|
||||
BetweenFormater formater = new BetweenFormater(betweenMs, Level.SECOND, 1);
|
||||
BetweenFormatter formater = new BetweenFormatter(betweenMs, Level.SECOND, 1);
|
||||
Assert.assertEquals(formater.toString(), "1秒");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void formatTest2(){
|
||||
BetweenFormater formater = new BetweenFormater(584, Level.SECOND, 1);
|
||||
BetweenFormatter formater = new BetweenFormatter(584, Level.SECOND, 1);
|
||||
Assert.assertEquals(formater.toString(), "0秒");
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
package cn.hutool.core.date;
|
||||
|
||||
import cn.hutool.core.date.BetweenFormater.Level;
|
||||
import cn.hutool.core.date.BetweenFormatter.Level;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
package cn.hutool.core.date;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.date.BetweenFormater.Level;
|
||||
import cn.hutool.core.date.BetweenFormatter.Level;
|
||||
import cn.hutool.core.date.format.FastDateFormat;
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
import org.junit.Assert;
|
||||
|
@ -73,6 +73,12 @@ public class FileUtilTest {
|
||||
FileUtil.rename(FileUtil.file("d:/test/3.jpg"), "2.jpg", false);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void renameTest2() {
|
||||
FileUtil.move(FileUtil.file("d:/test/a"), FileUtil.file("d:/test/b"), false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void copyTest() {
|
||||
File srcFile = FileUtil.file("hutool.jpg");
|
||||
|
@ -18,4 +18,10 @@ public class PathUtilTest {
|
||||
StandardCopyOption.REPLACE_EXISTING
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void moveTest(){
|
||||
PathUtil.move(Paths.get("d:/lombok.jar"), Paths.get("d:/test/"), false);
|
||||
}
|
||||
}
|
||||
|
@ -164,6 +164,7 @@ public class StrUtilTest {
|
||||
Assert.assertEquals(-1, StrUtil.lastIndexOfIgnoreCase("aabaabaa", "B", -1));
|
||||
Assert.assertEquals(2, StrUtil.lastIndexOfIgnoreCase("aabaabaa", "", 2));
|
||||
Assert.assertEquals(3, StrUtil.lastIndexOfIgnoreCase("abc", "", 9));
|
||||
Assert.assertEquals(0, StrUtil.lastIndexOfIgnoreCase("AAAcsd", "aaa"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -432,6 +433,26 @@ public class StrUtilTest {
|
||||
Assert.assertEquals(0, results2.length);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void subBetweenAllTest3() {
|
||||
String src1 = "'abc'and'123'";
|
||||
String[] strings = StrUtil.subBetweenAll(src1, "'", "'");
|
||||
Assert.assertEquals(2, strings.length);
|
||||
Assert.assertEquals("abc", strings[0]);
|
||||
Assert.assertEquals("123", strings[1]);
|
||||
|
||||
String src2 = "'abc''123'";
|
||||
strings = StrUtil.subBetweenAll(src2, "'", "'");
|
||||
Assert.assertEquals(2, strings.length);
|
||||
Assert.assertEquals("abc", strings[0]);
|
||||
Assert.assertEquals("123", strings[1]);
|
||||
|
||||
String src3 = "'abc'123'";
|
||||
strings = StrUtil.subBetweenAll(src3, "'", "'");
|
||||
Assert.assertEquals(1, strings.length);
|
||||
Assert.assertEquals("abc", strings[0]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void briefTest() {
|
||||
String str = RandomUtil.randomString(1000);
|
||||
|
@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.4.8-SNAPSHOT</version>
|
||||
<version>5.5.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-cron</artifactId>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.4.8-SNAPSHOT</version>
|
||||
<version>5.5.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-crypto</artifactId>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.4.8-SNAPSHOT</version>
|
||||
<version>5.5.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-db</artifactId>
|
||||
@ -21,7 +21,7 @@
|
||||
<c3p0.version>0.9.5.5</c3p0.version>
|
||||
<dbcp2.version>2.8.0</dbcp2.version>
|
||||
<tomcat-jdbc.version>9.0.30</tomcat-jdbc.version>
|
||||
<druid.version>1.2.1</druid.version>
|
||||
<druid.version>1.2.3</druid.version>
|
||||
<hikariCP.version>2.4.13</hikariCP.version>
|
||||
<mongo.version>3.12.7</mongo.version>
|
||||
<sqlite.version>3.32.3.2</sqlite.version>
|
||||
|
@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.4.8-SNAPSHOT</version>
|
||||
<version>5.5.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-dfa</artifactId>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.4.8-SNAPSHOT</version>
|
||||
<version>5.5.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-extra</artifactId>
|
||||
@ -19,10 +19,10 @@
|
||||
<properties>
|
||||
<!-- versions -->
|
||||
<velocity.version>2.2</velocity.version>
|
||||
<beetl.version>3.2.2.RELEASE</beetl.version>
|
||||
<beetl.version>3.2.4.RELEASE</beetl.version>
|
||||
<rythm.version>1.3.0</rythm.version>
|
||||
<freemarker.version>2.3.30</freemarker.version>
|
||||
<enjoy.version>4.9.02</enjoy.version>
|
||||
<enjoy.version>4.9.03</enjoy.version>
|
||||
<thymeleaf.version>3.0.11.RELEASE</thymeleaf.version>
|
||||
<mail.version>1.6.2</mail.version>
|
||||
<jsch.version>0.1.55</jsch.version>
|
||||
@ -30,7 +30,7 @@
|
||||
<net.version>3.7.2</net.version>
|
||||
<emoji-java.version>5.1.1</emoji-java.version>
|
||||
<servlet-api.version>4.0.1</servlet-api.version>
|
||||
<spring-boot.version>2.3.5.RELEASE</spring-boot.version>
|
||||
<spring-boot.version>2.4.0</spring-boot.version>
|
||||
<cglib.version>3.3.0</cglib.version>
|
||||
</properties>
|
||||
|
||||
@ -146,6 +146,19 @@
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.ftpserver</groupId>
|
||||
<artifactId>ftpserver-core</artifactId>
|
||||
<version>1.1.1</version>
|
||||
<scope>compile</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<groupId>org.slf4j</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<!-- Emoji工具依赖 -->
|
||||
<dependency>
|
||||
<groupId>com.vdurmont</groupId>
|
||||
@ -215,7 +228,7 @@
|
||||
<dependency>
|
||||
<groupId>org.apache.lucene</groupId>
|
||||
<artifactId>lucene-analyzers-smartcn</artifactId>
|
||||
<version>8.6.3</version>
|
||||
<version>8.7.0</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@ -393,10 +406,23 @@
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-expression</artifactId>
|
||||
<version>5.3.0</version>
|
||||
<version>5.3.1</version>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<!-- 表达式引擎可选依赖 end -->
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-compress</artifactId>
|
||||
<version>1.20</version>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.tukaani</groupId>
|
||||
<artifactId>xz</artifactId>
|
||||
<version>1.8</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
@ -0,0 +1,33 @@
|
||||
package cn.hutool.extra.compress;
|
||||
|
||||
import cn.hutool.core.exceptions.ExceptionUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
/**
|
||||
* 压缩解压异常语言异常
|
||||
*
|
||||
* @author Looly
|
||||
*/
|
||||
public class CompressException extends RuntimeException {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public CompressException(Throwable e) {
|
||||
super(ExceptionUtil.getMessage(e), e);
|
||||
}
|
||||
|
||||
public CompressException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public CompressException(String messageTemplate, Object... params) {
|
||||
super(StrUtil.format(messageTemplate, params));
|
||||
}
|
||||
|
||||
public CompressException(String message, Throwable throwable) {
|
||||
super(message, throwable);
|
||||
}
|
||||
|
||||
public CompressException(Throwable throwable, String messageTemplate, Object... params) {
|
||||
super(StrUtil.format(messageTemplate, params), throwable);
|
||||
}
|
||||
}
|
@ -0,0 +1,233 @@
|
||||
package cn.hutool.extra.compress;
|
||||
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.extra.compress.archiver.Archiver;
|
||||
import cn.hutool.extra.compress.archiver.SevenZArchiver;
|
||||
import cn.hutool.extra.compress.archiver.StreamArchiver;
|
||||
import cn.hutool.extra.compress.extractor.Extractor;
|
||||
import cn.hutool.extra.compress.extractor.SenvenZExtractor;
|
||||
import cn.hutool.extra.compress.extractor.StreamExtractor;
|
||||
import org.apache.commons.compress.archivers.ArchiveStreamFactory;
|
||||
import org.apache.commons.compress.archivers.StreamingNotSupportedException;
|
||||
import org.apache.commons.compress.compressors.CompressorException;
|
||||
import org.apache.commons.compress.compressors.CompressorInputStream;
|
||||
import org.apache.commons.compress.compressors.CompressorOutputStream;
|
||||
import org.apache.commons.compress.compressors.CompressorStreamFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
/**
|
||||
* 压缩工具类<br>
|
||||
* 基于commons-compress的压缩解压封装
|
||||
*
|
||||
* @author looly
|
||||
* @since 5.5.0
|
||||
*/
|
||||
public class CompressUtil {
|
||||
|
||||
/**
|
||||
* 获取压缩输出流,用于压缩指定内容,支持的格式例如:
|
||||
* <ul>
|
||||
* <li>{@value CompressorStreamFactory#GZIP}</li>
|
||||
* <li>{@value CompressorStreamFactory#BZIP2}</li>
|
||||
* <li>{@value CompressorStreamFactory#XZ}</li>
|
||||
* <li>{@value CompressorStreamFactory#PACK200}</li>
|
||||
* <li>{@value CompressorStreamFactory#SNAPPY_FRAMED}</li>
|
||||
* <li>{@value CompressorStreamFactory#LZ4_BLOCK}</li>
|
||||
* <li>{@value CompressorStreamFactory#LZ4_FRAMED}</li>
|
||||
* <li>{@value CompressorStreamFactory#ZSTANDARD}</li>
|
||||
* <li>{@value CompressorStreamFactory#DEFLATE}</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param compressorName 压缩名称,见:{@link CompressorStreamFactory}
|
||||
* @param out 输出流,可以输出到内存、网络或文件
|
||||
* @return {@link CompressorOutputStream}
|
||||
*/
|
||||
public CompressorOutputStream getOut(String compressorName, OutputStream out) {
|
||||
try {
|
||||
return new CompressorStreamFactory().createCompressorOutputStream(compressorName, out);
|
||||
} catch (CompressorException e) {
|
||||
throw new CompressException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取压缩输入流,用于解压缩指定内容,支持的格式例如:
|
||||
* <ul>
|
||||
* <li>{@value CompressorStreamFactory#GZIP}</li>
|
||||
* <li>{@value CompressorStreamFactory#BZIP2}</li>
|
||||
* <li>{@value CompressorStreamFactory#XZ}</li>
|
||||
* <li>{@value CompressorStreamFactory#PACK200}</li>
|
||||
* <li>{@value CompressorStreamFactory#SNAPPY_FRAMED}</li>
|
||||
* <li>{@value CompressorStreamFactory#LZ4_BLOCK}</li>
|
||||
* <li>{@value CompressorStreamFactory#LZ4_FRAMED}</li>
|
||||
* <li>{@value CompressorStreamFactory#ZSTANDARD}</li>
|
||||
* <li>{@value CompressorStreamFactory#DEFLATE}</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param compressorName 压缩名称,见:{@link CompressorStreamFactory},null表示自动检测
|
||||
* @param in 输出流,可以输出到内存、网络或文件
|
||||
* @return {@link CompressorOutputStream}
|
||||
*/
|
||||
public CompressorInputStream getIn(String compressorName, InputStream in) {
|
||||
in = IoUtil.toMarkSupportStream(in);
|
||||
try {
|
||||
if(StrUtil.isBlank(compressorName)){
|
||||
compressorName = CompressorStreamFactory.detect(in);
|
||||
}
|
||||
return new CompressorStreamFactory().createCompressorInputStream(compressorName, in);
|
||||
} catch (CompressorException e) {
|
||||
throw new CompressException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建归档器,支持:
|
||||
* <ul>
|
||||
* <li>{@link ArchiveStreamFactory#AR}</li>
|
||||
* <li>{@link ArchiveStreamFactory#CPIO}</li>
|
||||
* <li>{@link ArchiveStreamFactory#JAR}</li>
|
||||
* <li>{@link ArchiveStreamFactory#TAR}</li>
|
||||
* <li>{@link ArchiveStreamFactory#ZIP}</li>
|
||||
* <li>{@link ArchiveStreamFactory#SEVEN_Z}</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param charset 编码
|
||||
* @param archiverName 归档类型名称,见{@link ArchiveStreamFactory}
|
||||
* @param file 归档输出的文件
|
||||
* @return Archiver
|
||||
*/
|
||||
public static Archiver createArchiver(Charset charset, String archiverName, File file) {
|
||||
if (ArchiveStreamFactory.SEVEN_Z.equalsIgnoreCase(archiverName)) {
|
||||
return new SevenZArchiver(file);
|
||||
}
|
||||
return StreamArchiver.create(charset, archiverName, file);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建归档器,支持:
|
||||
* <ul>
|
||||
* <li>{@link ArchiveStreamFactory#AR}</li>
|
||||
* <li>{@link ArchiveStreamFactory#CPIO}</li>
|
||||
* <li>{@link ArchiveStreamFactory#JAR}</li>
|
||||
* <li>{@link ArchiveStreamFactory#TAR}</li>
|
||||
* <li>{@link ArchiveStreamFactory#ZIP}</li>
|
||||
* <li>{@link ArchiveStreamFactory#SEVEN_Z}</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param charset 编码
|
||||
* @param archiverName 归档类型名称,见{@link ArchiveStreamFactory}
|
||||
* @param out 归档输出的流
|
||||
* @return Archiver
|
||||
*/
|
||||
public static Archiver createArchiver(Charset charset, String archiverName, OutputStream out) {
|
||||
if (ArchiveStreamFactory.SEVEN_Z.equalsIgnoreCase(archiverName)) {
|
||||
return new SevenZArchiver(out);
|
||||
}
|
||||
return StreamArchiver.create(charset, archiverName, out);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建归档解包器,支持:
|
||||
* <ul>
|
||||
* <li>{@link ArchiveStreamFactory#AR}</li>
|
||||
* <li>{@link ArchiveStreamFactory#CPIO}</li>
|
||||
* <li>{@link ArchiveStreamFactory#JAR}</li>
|
||||
* <li>{@link ArchiveStreamFactory#TAR}</li>
|
||||
* <li>{@link ArchiveStreamFactory#ZIP}</li>
|
||||
* <li>{@link ArchiveStreamFactory#SEVEN_Z}</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param charset 编码,7z格式此参数无效
|
||||
* @param file 归档文件
|
||||
* @return {@link Extractor}
|
||||
*/
|
||||
public static Extractor createExtractor(Charset charset, File file) {
|
||||
return createExtractor(charset, null, file);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建归档解包器,支持:
|
||||
* <ul>
|
||||
* <li>{@link ArchiveStreamFactory#AR}</li>
|
||||
* <li>{@link ArchiveStreamFactory#CPIO}</li>
|
||||
* <li>{@link ArchiveStreamFactory#JAR}</li>
|
||||
* <li>{@link ArchiveStreamFactory#TAR}</li>
|
||||
* <li>{@link ArchiveStreamFactory#ZIP}</li>
|
||||
* <li>{@link ArchiveStreamFactory#SEVEN_Z}</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param charset 编码,7z格式此参数无效
|
||||
* @param archiverName 归档类型名称,见{@link ArchiveStreamFactory},null表示自动识别
|
||||
* @param file 归档文件
|
||||
* @return {@link Extractor}
|
||||
*/
|
||||
public static Extractor createExtractor(Charset charset, String archiverName, File file) {
|
||||
if (ArchiveStreamFactory.SEVEN_Z.equalsIgnoreCase(archiverName)) {
|
||||
return new SenvenZExtractor(file);
|
||||
}
|
||||
try {
|
||||
return new StreamExtractor(charset, archiverName, file);
|
||||
} catch (CompressException e) {
|
||||
final Throwable cause = e.getCause();
|
||||
if (cause instanceof StreamingNotSupportedException && cause.getMessage().contains("7z")) {
|
||||
return new SenvenZExtractor(file);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建归档解包器,支持:
|
||||
* <ul>
|
||||
* <li>{@link ArchiveStreamFactory#AR}</li>
|
||||
* <li>{@link ArchiveStreamFactory#CPIO}</li>
|
||||
* <li>{@link ArchiveStreamFactory#JAR}</li>
|
||||
* <li>{@link ArchiveStreamFactory#TAR}</li>
|
||||
* <li>{@link ArchiveStreamFactory#ZIP}</li>
|
||||
* <li>{@link ArchiveStreamFactory#SEVEN_Z}</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param charset 编码,7z格式此参数无效
|
||||
* @param in 归档输入的流
|
||||
* @return {@link Extractor}
|
||||
*/
|
||||
public static Extractor createExtractor(Charset charset, InputStream in) {
|
||||
return createExtractor(charset, null, in);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建归档解包器,支持:
|
||||
* <ul>
|
||||
* <li>{@link ArchiveStreamFactory#AR}</li>
|
||||
* <li>{@link ArchiveStreamFactory#CPIO}</li>
|
||||
* <li>{@link ArchiveStreamFactory#JAR}</li>
|
||||
* <li>{@link ArchiveStreamFactory#TAR}</li>
|
||||
* <li>{@link ArchiveStreamFactory#ZIP}</li>
|
||||
* <li>{@link ArchiveStreamFactory#SEVEN_Z}</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param charset 编码,7z格式此参数无效
|
||||
* @param archiverName 归档类型名称,见{@link ArchiveStreamFactory},null表示自动识别
|
||||
* @param in 归档输入的流
|
||||
* @return {@link Extractor}
|
||||
*/
|
||||
public static Extractor createExtractor(Charset charset, String archiverName, InputStream in) {
|
||||
if (ArchiveStreamFactory.SEVEN_Z.equalsIgnoreCase(archiverName)) {
|
||||
return new SenvenZExtractor(in);
|
||||
}
|
||||
try {
|
||||
return new StreamExtractor(charset, archiverName, in);
|
||||
} catch (CompressException e) {
|
||||
final Throwable cause = e.getCause();
|
||||
if (cause instanceof StreamingNotSupportedException && cause.getMessage().contains("7z")) {
|
||||
return new SenvenZExtractor(in);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
package cn.hutool.extra.compress.archiver;
|
||||
|
||||
import cn.hutool.core.lang.Filter;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* 数据归档封装,归档即将几个文件或目录打成一个压缩包
|
||||
*
|
||||
* @author looly
|
||||
*/
|
||||
public interface Archiver extends Closeable {
|
||||
|
||||
/**
|
||||
* 将文件或目录加入归档,目录采取递归读取方式按照层级加入
|
||||
*
|
||||
* @param file 文件或目录
|
||||
* @return this
|
||||
*/
|
||||
default Archiver add(File file) {
|
||||
return add(file, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将文件或目录加入归档,目录采取递归读取方式按照层级加入
|
||||
*
|
||||
* @param file 文件或目录
|
||||
* @param filter 文件过滤器,指定哪些文件或目录可以加入,当{@link Filter#accept(Object)}为true时加入。
|
||||
* @return this
|
||||
*/
|
||||
default Archiver add(File file, Filter<File> filter) {
|
||||
return add(file, StrUtil.SLASH, filter);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将文件或目录加入归档包,目录采取递归读取方式按照层级加入
|
||||
*
|
||||
* @param file 文件或目录
|
||||
* @param path 文件或目录的初始路径,null表示位于根路径
|
||||
* @param filter 文件过滤器,指定哪些文件或目录可以加入,当{@link Filter#accept(Object)}为true时加入。
|
||||
* @return this
|
||||
*/
|
||||
Archiver add(File file, String path, Filter<File> filter);
|
||||
|
||||
/**
|
||||
* 结束已经增加的文件归档,此方法不会关闭归档流,可以继续添加文件
|
||||
*
|
||||
* @return this
|
||||
*/
|
||||
Archiver finish();
|
||||
|
||||
/**
|
||||
* 无异常关闭
|
||||
*/
|
||||
@Override
|
||||
void close();
|
||||
}
|
@ -0,0 +1,145 @@
|
||||
package cn.hutool.extra.compress.archiver;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.io.IORuntimeException;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.lang.Filter;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import org.apache.commons.compress.archivers.sevenz.SevenZOutputFile;
|
||||
import org.apache.commons.compress.utils.SeekableInMemoryByteChannel;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.channels.SeekableByteChannel;
|
||||
|
||||
/**
|
||||
* 7zip格式的归档封装
|
||||
*
|
||||
* @author looly
|
||||
*/
|
||||
public class SevenZArchiver implements Archiver {
|
||||
|
||||
private final SevenZOutputFile sevenZOutputFile;
|
||||
|
||||
private SeekableByteChannel channel;
|
||||
private OutputStream out;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param file 归档输出的文件
|
||||
*/
|
||||
public SevenZArchiver(File file) {
|
||||
try {
|
||||
this.sevenZOutputFile = new SevenZOutputFile(file);
|
||||
} catch (IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param out 归档输出的流
|
||||
*/
|
||||
public SevenZArchiver(OutputStream out) {
|
||||
this.out = out;
|
||||
this.channel = new SeekableInMemoryByteChannel();
|
||||
try {
|
||||
this.sevenZOutputFile = new SevenZOutputFile(channel);
|
||||
} catch (IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param channel 归档输出的文件
|
||||
*/
|
||||
public SevenZArchiver(SeekableByteChannel channel) {
|
||||
try {
|
||||
this.sevenZOutputFile = new SevenZOutputFile(channel);
|
||||
} catch (IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取{@link SevenZOutputFile}以便自定义相关设置
|
||||
*
|
||||
* @return {@link SevenZOutputFile}
|
||||
*/
|
||||
public SevenZOutputFile getSevenZOutputFile() {
|
||||
return this.sevenZOutputFile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SevenZArchiver add(File file, String path, Filter<File> filter) {
|
||||
try {
|
||||
addInternal(file, path, filter);
|
||||
} catch (IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SevenZArchiver finish() {
|
||||
try {
|
||||
this.sevenZOutputFile.finish();
|
||||
} catch (IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
try {
|
||||
finish();
|
||||
} catch (Exception ignore) {
|
||||
//ignore
|
||||
}
|
||||
if (null != out && this.channel instanceof SeekableInMemoryByteChannel) {
|
||||
try {
|
||||
out.write(((SeekableInMemoryByteChannel) this.channel).array());
|
||||
} catch (IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
}
|
||||
IoUtil.close(this.sevenZOutputFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将文件或目录加入归档包,目录采取递归读取方式按照层级加入
|
||||
*
|
||||
* @param file 文件或目录
|
||||
* @param path 文件或目录的初始路径,null表示位于根路径
|
||||
* @param filter 文件过滤器,指定哪些文件或目录可以加入,当{@link Filter#accept(Object)}为true时加入。
|
||||
*/
|
||||
private void addInternal(File file, String path, Filter<File> filter) throws IOException {
|
||||
if (null != filter && false == filter.accept(file)) {
|
||||
return;
|
||||
}
|
||||
final SevenZOutputFile out = this.sevenZOutputFile;
|
||||
|
||||
final String entryName = StrUtil.addSuffixIfNot(StrUtil.nullToEmpty(path), StrUtil.SLASH) + file.getName();
|
||||
out.putArchiveEntry(out.createArchiveEntry(file, entryName));
|
||||
|
||||
if (file.isDirectory()) {
|
||||
// 目录遍历写入
|
||||
final File[] files = file.listFiles();
|
||||
for (File childFile : files) {
|
||||
addInternal(childFile, entryName, filter);
|
||||
}
|
||||
} else {
|
||||
if (file.isFile()) {
|
||||
// 文件直接写入
|
||||
out.write(FileUtil.readBytes(file));
|
||||
}
|
||||
out.closeArchiveEntry();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,169 @@
|
||||
package cn.hutool.extra.compress.archiver;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.io.IORuntimeException;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.lang.Filter;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.extra.compress.CompressException;
|
||||
import org.apache.commons.compress.archivers.ArchiveException;
|
||||
import org.apache.commons.compress.archivers.ArchiveOutputStream;
|
||||
import org.apache.commons.compress.archivers.ArchiveStreamFactory;
|
||||
import org.apache.commons.compress.archivers.ar.ArArchiveOutputStream;
|
||||
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
/**
|
||||
* 数据归档封装,归档即将几个文件或目录打成一个压缩包<br>
|
||||
* 支持的归档文件格式为:
|
||||
* <ul>
|
||||
* <li>{@link ArchiveStreamFactory#AR}</li>
|
||||
* <li>{@link ArchiveStreamFactory#CPIO}</li>
|
||||
* <li>{@link ArchiveStreamFactory#JAR}</li>
|
||||
* <li>{@link ArchiveStreamFactory#TAR}</li>
|
||||
* <li>{@link ArchiveStreamFactory#ZIP}</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author looly
|
||||
*/
|
||||
public class StreamArchiver implements Archiver {
|
||||
|
||||
/**
|
||||
* 创建归档器
|
||||
*
|
||||
* @param charset 编码
|
||||
* @param archiverName 归档类型名称,见{@link ArchiveStreamFactory}
|
||||
* @param file 归档输出的文件
|
||||
* @return StreamArchiver
|
||||
*/
|
||||
public static StreamArchiver create(Charset charset, String archiverName, File file) {
|
||||
return new StreamArchiver(charset, archiverName, file);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建归档器
|
||||
*
|
||||
* @param charset 编码
|
||||
* @param archiverName 归档类型名称,见{@link ArchiveStreamFactory}
|
||||
* @param out 归档输出的流
|
||||
* @return StreamArchiver
|
||||
*/
|
||||
public static StreamArchiver create(Charset charset, String archiverName, OutputStream out) {
|
||||
return new StreamArchiver(charset, archiverName, out);
|
||||
}
|
||||
|
||||
private final ArchiveOutputStream out;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param charset 编码
|
||||
* @param archiverName 归档类型名称,见{@link ArchiveStreamFactory}
|
||||
* @param file 归档输出的文件
|
||||
*/
|
||||
public StreamArchiver(Charset charset, String archiverName, File file) {
|
||||
this(charset, archiverName, FileUtil.getOutputStream(file));
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param charset 编码
|
||||
* @param archiverName 归档类型名称,见{@link ArchiveStreamFactory}
|
||||
* @param targetStream 归档输出的流
|
||||
*/
|
||||
public StreamArchiver(Charset charset, String archiverName, OutputStream targetStream) {
|
||||
final ArchiveStreamFactory factory = new ArchiveStreamFactory(charset.name());
|
||||
try {
|
||||
this.out = factory.createArchiveOutputStream(archiverName, targetStream);
|
||||
} catch (ArchiveException e) {
|
||||
throw new CompressException(e);
|
||||
}
|
||||
|
||||
//特殊设置
|
||||
if(this.out instanceof TarArchiveOutputStream){
|
||||
((TarArchiveOutputStream)out).setLongFileMode(TarArchiveOutputStream.LONGFILE_GNU);
|
||||
} else if(this.out instanceof ArArchiveOutputStream){
|
||||
((ArArchiveOutputStream)out).setLongFileMode(ArArchiveOutputStream.LONGFILE_BSD);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将文件或目录加入归档包,目录采取递归读取方式按照层级加入
|
||||
*
|
||||
* @param file 文件或目录
|
||||
* @param path 文件或目录的初始路径,null表示位于根路径
|
||||
* @param filter 文件过滤器,指定哪些文件或目录可以加入,当{@link Filter#accept(Object)}为true时加入。
|
||||
* @return this
|
||||
* @throws IORuntimeException IO异常
|
||||
*/
|
||||
@Override
|
||||
public StreamArchiver add(File file, String path, Filter<File> filter) throws IORuntimeException {
|
||||
try {
|
||||
addInternal(file, path, filter);
|
||||
} catch (IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 结束已经增加的文件归档,此方法不会关闭归档流,可以继续添加文件
|
||||
*
|
||||
* @return this
|
||||
*/
|
||||
@Override
|
||||
public StreamArchiver finish() {
|
||||
try {
|
||||
this.out.finish();
|
||||
} catch (IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
try {
|
||||
finish();
|
||||
} catch (Exception ignore) {
|
||||
//ignore
|
||||
}
|
||||
IoUtil.close(this.out);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将文件或目录加入归档包,目录采取递归读取方式按照层级加入
|
||||
*
|
||||
* @param file 文件或目录
|
||||
* @param path 文件或目录的初始路径,null表示位于根路径
|
||||
* @param filter 文件过滤器,指定哪些文件或目录可以加入,当{@link Filter#accept(Object)}为true时加入。
|
||||
*/
|
||||
private void addInternal(File file, String path, Filter<File> filter) throws IOException {
|
||||
if (null != filter && false == filter.accept(file)) {
|
||||
return;
|
||||
}
|
||||
final ArchiveOutputStream out = this.out;
|
||||
|
||||
final String entryName = StrUtil.addSuffixIfNot(StrUtil.nullToEmpty(path), StrUtil.SLASH) + file.getName();
|
||||
out.putArchiveEntry(out.createArchiveEntry(file, entryName));
|
||||
|
||||
if (file.isDirectory()) {
|
||||
// 目录遍历写入
|
||||
final File[] files = file.listFiles();
|
||||
for (File childFile : files) {
|
||||
addInternal(childFile, entryName, filter);
|
||||
}
|
||||
} else {
|
||||
if (file.isFile()) {
|
||||
// 文件直接写入
|
||||
FileUtil.writeToStream(file, out);
|
||||
}
|
||||
out.closeArchiveEntry();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
/**
|
||||
* 基于commons-compress的打包(压缩)封装
|
||||
*
|
||||
* <p>
|
||||
* 见:https://commons.apache.org/proper/commons-compress/
|
||||
* </p>
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
package cn.hutool.extra.compress.archiver;
|
@ -0,0 +1,39 @@
|
||||
package cn.hutool.extra.compress.extractor;
|
||||
|
||||
import cn.hutool.core.lang.Filter;
|
||||
import org.apache.commons.compress.archivers.ArchiveEntry;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* 归档数据解包封装,用于将zip、tar等包解包为文件
|
||||
*
|
||||
* @author looly
|
||||
* @since 5.5.0
|
||||
*/
|
||||
public interface Extractor extends Closeable {
|
||||
|
||||
/**
|
||||
* 释放(解压)到指定目录,结束后自动关闭流,此方法只能调用一次
|
||||
*
|
||||
* @param targetDir 目标目录
|
||||
*/
|
||||
default void extract(File targetDir){
|
||||
extract(targetDir, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 释放(解压)到指定目录,结束后自动关闭流,此方法只能调用一次
|
||||
*
|
||||
* @param targetDir 目标目录
|
||||
* @param filter 解压文件过滤器,用于指定需要释放的文件,null表示不过滤。当{@link Filter#accept(Object)}为true时释放。
|
||||
*/
|
||||
void extract(File targetDir, Filter<ArchiveEntry> filter);
|
||||
|
||||
/**
|
||||
* 无异常关闭
|
||||
*/
|
||||
@Override
|
||||
void close();
|
||||
}
|
@ -0,0 +1,142 @@
|
||||
package cn.hutool.extra.compress.extractor;
|
||||
|
||||
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.lang.Filter;
|
||||
import org.apache.commons.compress.archivers.ArchiveEntry;
|
||||
import org.apache.commons.compress.archivers.sevenz.SevenZArchiveEntry;
|
||||
import org.apache.commons.compress.archivers.sevenz.SevenZFile;
|
||||
import org.apache.commons.compress.utils.SeekableInMemoryByteChannel;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.channels.SeekableByteChannel;
|
||||
|
||||
/**
|
||||
* 7z格式数据解压器,即将归档打包的数据释放
|
||||
*
|
||||
* @author looly
|
||||
* @since 5.5.0
|
||||
*/
|
||||
public class SenvenZExtractor implements Extractor {
|
||||
|
||||
private final SevenZFile sevenZFile;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param file 包文件
|
||||
*/
|
||||
public SenvenZExtractor(File file) {
|
||||
this(file, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param file 包文件
|
||||
* @param password 密码,null表示无密码
|
||||
*/
|
||||
public SenvenZExtractor(File file, char[] password) {
|
||||
try {
|
||||
this.sevenZFile = new SevenZFile(file, password);
|
||||
} catch (IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param in 包流
|
||||
*/
|
||||
public SenvenZExtractor(InputStream in) {
|
||||
this(in, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param in 包流
|
||||
* @param password 密码,null表示无密码
|
||||
*/
|
||||
public SenvenZExtractor(InputStream in, char[] password) {
|
||||
this(new SeekableInMemoryByteChannel(IoUtil.readBytes(in)), password);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param channel {@link SeekableByteChannel}
|
||||
*/
|
||||
public SenvenZExtractor(SeekableByteChannel channel) {
|
||||
this(channel, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param channel {@link SeekableByteChannel}
|
||||
* @param password 密码,null表示无密码
|
||||
*/
|
||||
public SenvenZExtractor(SeekableByteChannel channel, char[] password) {
|
||||
try {
|
||||
this.sevenZFile = new SevenZFile(channel, password);
|
||||
} catch (IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 释放(解压)到指定目录,结束后自动关闭流,此方法只能调用一次
|
||||
*
|
||||
* @param targetDir 目标目录
|
||||
* @param filter 解压文件过滤器,用于指定需要释放的文件,null表示不过滤。当{@link Filter#accept(Object)}为true时释放。
|
||||
*/
|
||||
@Override
|
||||
public void extract(File targetDir, Filter<ArchiveEntry> filter) {
|
||||
try {
|
||||
extractInternal(targetDir, filter);
|
||||
} catch (IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
} finally {
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 释放(解压)到指定目录
|
||||
*
|
||||
* @param targetDir 目标目录
|
||||
* @param filter 解压文件过滤器,用于指定需要释放的文件,null表示不过滤。当{@link Filter#accept(Object)}为true时释放。
|
||||
* @throws IOException IO异常
|
||||
*/
|
||||
private void extractInternal(File targetDir, Filter<ArchiveEntry> filter) throws IOException {
|
||||
Assert.isTrue(null != targetDir && ((false == targetDir.exists()) || targetDir.isDirectory()), "target must be dir.");
|
||||
final SevenZFile sevenZFile = this.sevenZFile;
|
||||
SevenZArchiveEntry entry;
|
||||
File outItemFile;
|
||||
while (null != (entry = this.sevenZFile.getNextEntry())) {
|
||||
outItemFile = FileUtil.file(targetDir, entry.getName());
|
||||
if (entry.isDirectory()) {
|
||||
// 创建对应目录
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
outItemFile.mkdirs();
|
||||
} else if(entry.hasStream()){
|
||||
// 读取entry对应数据流
|
||||
FileUtil.writeFromStream(new Seven7EntryInputStream(sevenZFile, entry), outItemFile);
|
||||
} else {
|
||||
// 无数据流的文件创建为空文件
|
||||
FileUtil.touch(outItemFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
IoUtil.close(this.sevenZFile);
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
package cn.hutool.extra.compress.extractor;
|
||||
|
||||
import org.apache.commons.compress.archivers.sevenz.SevenZArchiveEntry;
|
||||
import org.apache.commons.compress.archivers.sevenz.SevenZFile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* 7z解压中文件流读取的封装
|
||||
*
|
||||
* @author looly
|
||||
* @since 5.5.0
|
||||
*/
|
||||
public class Seven7EntryInputStream extends InputStream {
|
||||
|
||||
private final SevenZFile sevenZFile;
|
||||
private final long size;
|
||||
private long readSize = 0;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
* @param sevenZFile {@link SevenZFile}
|
||||
* @param entry {@link SevenZArchiveEntry}
|
||||
*/
|
||||
public Seven7EntryInputStream(SevenZFile sevenZFile, SevenZArchiveEntry entry) {
|
||||
this.sevenZFile = sevenZFile;
|
||||
this.size = entry.getSize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int available() throws IOException {
|
||||
try{
|
||||
return Math.toIntExact(this.size);
|
||||
} catch (ArithmeticException e){
|
||||
throw new IOException("Entry size is too large!(max than Integer.MAX)", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
this.readSize++;
|
||||
return this.sevenZFile.read();
|
||||
}
|
||||
}
|
@ -0,0 +1,131 @@
|
||||
package cn.hutool.extra.compress.extractor;
|
||||
|
||||
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.lang.Filter;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.extra.compress.CompressException;
|
||||
import org.apache.commons.compress.archivers.ArchiveEntry;
|
||||
import org.apache.commons.compress.archivers.ArchiveException;
|
||||
import org.apache.commons.compress.archivers.ArchiveInputStream;
|
||||
import org.apache.commons.compress.archivers.ArchiveStreamFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
/**
|
||||
* 数据解压器,即将归档打包的数据释放
|
||||
*
|
||||
* @author looly
|
||||
* @since 5.5.0
|
||||
*/
|
||||
public class StreamExtractor implements Extractor{
|
||||
|
||||
private final ArchiveInputStream in;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param charset 编码
|
||||
* @param file 包文件
|
||||
*/
|
||||
public StreamExtractor(Charset charset, File file) {
|
||||
this(charset, null, file);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param charset 编码
|
||||
* @param archiverName 归档包格式,null表示自动检测
|
||||
* @param file 包文件
|
||||
*/
|
||||
public StreamExtractor(Charset charset, String archiverName, File file) {
|
||||
this(charset, archiverName, FileUtil.getInputStream(file));
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param charset 编码
|
||||
* @param in 包流
|
||||
*/
|
||||
public StreamExtractor(Charset charset, InputStream in) {
|
||||
this(charset, null, in);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param charset 编码
|
||||
* @param archiverName 归档包格式,null表示自动检测
|
||||
* @param in 包流
|
||||
*/
|
||||
public StreamExtractor(Charset charset, String archiverName, InputStream in) {
|
||||
final ArchiveStreamFactory factory = new ArchiveStreamFactory(charset.name());
|
||||
try {
|
||||
in = IoUtil.toBuffered(in);
|
||||
if (StrUtil.isBlank(archiverName)) {
|
||||
this.in = factory.createArchiveInputStream(in);
|
||||
} else {
|
||||
this.in = factory.createArchiveInputStream(archiverName, in);
|
||||
}
|
||||
} catch (ArchiveException e) {
|
||||
throw new CompressException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 释放(解压)到指定目录,结束后自动关闭流,此方法只能调用一次
|
||||
*
|
||||
* @param targetDir 目标目录
|
||||
* @param filter 解压文件过滤器,用于指定需要释放的文件,null表示不过滤。当{@link Filter#accept(Object)}为true时释放。
|
||||
*/
|
||||
@Override
|
||||
public void extract(File targetDir, Filter<ArchiveEntry> filter) {
|
||||
try {
|
||||
extractInternal(targetDir, filter);
|
||||
} catch (IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
} finally {
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 释放(解压)到指定目录
|
||||
*
|
||||
* @param targetDir 目标目录
|
||||
* @param filter 解压文件过滤器,用于指定需要释放的文件,null表示不过滤。当{@link Filter#accept(Object)}为true时释放。
|
||||
* @throws IOException IO异常
|
||||
*/
|
||||
private void extractInternal(File targetDir, Filter<ArchiveEntry> filter) throws IOException {
|
||||
Assert.isTrue(null != targetDir && ((false == targetDir.exists()) || targetDir.isDirectory()), "target must be dir.");
|
||||
final ArchiveInputStream in = this.in;
|
||||
ArchiveEntry entry;
|
||||
File outItemFile;
|
||||
while (null != (entry = in.getNextEntry())) {
|
||||
if (false == in.canReadEntryData(entry)) {
|
||||
// 无法读取的文件直接跳过
|
||||
continue;
|
||||
}
|
||||
outItemFile = FileUtil.file(targetDir, entry.getName());
|
||||
if (entry.isDirectory()) {
|
||||
// 创建对应目录
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
outItemFile.mkdirs();
|
||||
} else {
|
||||
FileUtil.writeFromStream(in, outItemFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
IoUtil.close(this.in);
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
/**
|
||||
* 基于commons-compress的解包(解压缩)封装
|
||||
*
|
||||
* <p>
|
||||
* 见:https://commons.apache.org/proper/commons-compress/
|
||||
* </p>
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
package cn.hutool.extra.compress.extractor;
|
@ -0,0 +1,13 @@
|
||||
/**
|
||||
* 基于commons-compress的压缩解压封装<br>
|
||||
* 支持包括:gzip, bzip2, xz, lzma, Pack200, DEFLATE, Brotli, DEFLATE64, ZStandard and Z, the archiver formats are 7z,<br>
|
||||
* ar, arj, cpio, dump, tar and zip等格式。
|
||||
*
|
||||
* <p>
|
||||
* 见:https://commons.apache.org/proper/commons-compress/
|
||||
* </p>
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
package cn.hutool.extra.compress;
|
@ -0,0 +1,221 @@
|
||||
package cn.hutool.extra.ftp;
|
||||
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.net.NetUtil;
|
||||
import org.apache.ftpserver.ConnectionConfig;
|
||||
import org.apache.ftpserver.FtpServerFactory;
|
||||
import org.apache.ftpserver.ftplet.Authority;
|
||||
import org.apache.ftpserver.ftplet.Ftplet;
|
||||
import org.apache.ftpserver.ftplet.User;
|
||||
import org.apache.ftpserver.ftplet.UserManager;
|
||||
import org.apache.ftpserver.listener.ListenerFactory;
|
||||
import org.apache.ftpserver.ssl.SslConfiguration;
|
||||
import org.apache.ftpserver.ssl.SslConfigurationFactory;
|
||||
import org.apache.ftpserver.usermanager.PropertiesUserManagerFactory;
|
||||
import org.apache.ftpserver.usermanager.impl.BaseUser;
|
||||
import org.apache.ftpserver.usermanager.impl.WritePermission;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 基于 Apache FtpServer(http://mina.apache.org/ftpserver-project/)的FTP服务端简单封装。
|
||||
*
|
||||
* @author looly
|
||||
* @since 5.5.0
|
||||
*/
|
||||
public class SimpleFtpServer {
|
||||
|
||||
/**
|
||||
* 创建FTP服务器,调用{@link SimpleFtpServer#start()}启动即可
|
||||
*
|
||||
* @return SimpleFtpServer
|
||||
*/
|
||||
public static SimpleFtpServer create() {
|
||||
return new SimpleFtpServer();
|
||||
}
|
||||
|
||||
FtpServerFactory serverFactory;
|
||||
ListenerFactory listenerFactory;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*/
|
||||
public SimpleFtpServer() {
|
||||
serverFactory = new FtpServerFactory();
|
||||
listenerFactory = new ListenerFactory();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 {@link FtpServerFactory},用于设置FTP服务器相关信息
|
||||
*
|
||||
* @return {@link FtpServerFactory}
|
||||
*/
|
||||
public FtpServerFactory getServerFactory() {
|
||||
return this.serverFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置连接相关配置,使用ConnectionConfigFactory创建{@link ConnectionConfig}对象
|
||||
*
|
||||
* @param connectionConfig 连接配置
|
||||
* @return this
|
||||
*/
|
||||
public SimpleFtpServer setConnectionConfig(ConnectionConfig connectionConfig) {
|
||||
this.serverFactory.setConnectionConfig(connectionConfig);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取{@link ListenerFactory},用于设置端口、用户、SSL等信息
|
||||
*
|
||||
* @return {@link ListenerFactory}
|
||||
*/
|
||||
public ListenerFactory getListenerFactory() {
|
||||
return this.listenerFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义默认端口,如果不设置,使用默认端口:21
|
||||
*
|
||||
* @param port 端口
|
||||
* @return this
|
||||
*/
|
||||
public SimpleFtpServer setPort(int port) {
|
||||
Assert.isTrue(NetUtil.isValidPort(port), "Invalid port!");
|
||||
this.listenerFactory.setPort(port);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户管理器,用于新增、查找和删除用户信息
|
||||
*
|
||||
* @return 用户管理器
|
||||
*/
|
||||
public UserManager getUserManager() {
|
||||
return this.serverFactory.getUserManager();
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加FTP用户
|
||||
*
|
||||
* @param user FTP用户信息
|
||||
* @return this
|
||||
*/
|
||||
public SimpleFtpServer addUser(User user) {
|
||||
try {
|
||||
getUserManager().save(user);
|
||||
} catch (org.apache.ftpserver.ftplet.FtpException e) {
|
||||
throw new FtpException(e);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加匿名用户
|
||||
*
|
||||
* @param homePath 用户路径,匿名用户对此路径有读写权限
|
||||
* @return this
|
||||
*/
|
||||
public SimpleFtpServer addAnonymous(String homePath) {
|
||||
BaseUser user = new BaseUser();
|
||||
user.setName("anonymous");
|
||||
user.setHomeDirectory(homePath);
|
||||
List<Authority> authorities = new ArrayList<>();
|
||||
// 添加用户读写权限
|
||||
authorities.add(new WritePermission());
|
||||
user.setAuthorities(authorities);
|
||||
return addUser(user);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除用户
|
||||
*
|
||||
* @param userName 用户名
|
||||
* @return this
|
||||
*/
|
||||
public SimpleFtpServer delUser(String userName) {
|
||||
try {
|
||||
getUserManager().delete(userName);
|
||||
} catch (org.apache.ftpserver.ftplet.FtpException e) {
|
||||
throw new FtpException(e);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用SSL安全连接,可以使用SslConfigurationFactory创建{@link SslConfiguration}
|
||||
*
|
||||
* @param ssl {@link SslConfiguration}
|
||||
* @return this
|
||||
*/
|
||||
public SimpleFtpServer setSsl(SslConfiguration ssl) {
|
||||
this.listenerFactory.setSslConfiguration(ssl);
|
||||
listenerFactory.setImplicitSsl(true);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用SSL安全连接
|
||||
*
|
||||
* @param keystoreFile 密钥文件
|
||||
* @param password 密钥文件密码
|
||||
* @return this
|
||||
*/
|
||||
public SimpleFtpServer setSsl(File keystoreFile, String password) {
|
||||
SslConfigurationFactory sslFactory = new SslConfigurationFactory();
|
||||
sslFactory.setKeystoreFile(keystoreFile);
|
||||
sslFactory.setKeystorePassword(password);
|
||||
return setSsl(sslFactory.createSslConfiguration());
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义用户管理器,一般用于使用配置文件配置用户信息
|
||||
*
|
||||
* @param userManager {@link UserManager}
|
||||
* @return this
|
||||
*/
|
||||
public SimpleFtpServer setUserManager(UserManager userManager) {
|
||||
this.serverFactory.setUserManager(userManager);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义用户信息配置文件,此方法会重置用户管理器
|
||||
*
|
||||
* @param propertiesFile 配置文件
|
||||
* @return this
|
||||
*/
|
||||
public SimpleFtpServer setUsersConfig(File propertiesFile) {
|
||||
final PropertiesUserManagerFactory userManagerFactory = new PropertiesUserManagerFactory();
|
||||
userManagerFactory.setFile(propertiesFile);
|
||||
return this.setUserManager(userManagerFactory.createUserManager());
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加FTP动作行为监听处理器,通过实现{@link Ftplet},可以对用户的行为监听并执行相应动作
|
||||
*
|
||||
* @param name 名称
|
||||
* @param ftplet {@link Ftplet},用户自定义监听规则
|
||||
* @return this
|
||||
*/
|
||||
public SimpleFtpServer addFtplet(String name, Ftplet ftplet) {
|
||||
this.serverFactory.getFtplets().put(name, ftplet);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动FTP服务,阻塞当前线程
|
||||
*/
|
||||
public void start() {
|
||||
// 一个Listener对应一个监听端口
|
||||
// 可以创建多个监听,此处默认只监听一个
|
||||
serverFactory.addListener("default", listenerFactory.createListener());
|
||||
try {
|
||||
serverFactory.createServer().start();
|
||||
} catch (org.apache.ftpserver.ftplet.FtpException e) {
|
||||
throw new cn.hutool.extra.ftp.FtpException(e);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,5 @@
|
||||
package cn.hutool.extra.template.engine.freemarker;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.io.IORuntimeException;
|
||||
import cn.hutool.core.util.ClassUtil;
|
||||
@ -13,9 +11,12 @@ import freemarker.cache.ClassTemplateLoader;
|
||||
import freemarker.cache.FileTemplateLoader;
|
||||
import freemarker.template.Configuration;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Beetl模板引擎封装
|
||||
*
|
||||
* FreeMarker模板引擎封装<br>
|
||||
* 见:https://freemarker.apache.org/
|
||||
*
|
||||
* @author looly
|
||||
*/
|
||||
public class FreemarkerEngine implements TemplateEngine {
|
||||
@ -23,14 +24,16 @@ public class FreemarkerEngine implements TemplateEngine {
|
||||
private Configuration cfg;
|
||||
|
||||
// --------------------------------------------------------------------------------- Constructor start
|
||||
|
||||
/**
|
||||
* 默认构造
|
||||
*/
|
||||
public FreemarkerEngine() {}
|
||||
public FreemarkerEngine() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
*
|
||||
* @param config 模板配置
|
||||
*/
|
||||
public FreemarkerEngine(TemplateConfig config) {
|
||||
@ -39,7 +42,7 @@ public class FreemarkerEngine implements TemplateEngine {
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
*
|
||||
* @param freemarkerCfg {@link Configuration}
|
||||
*/
|
||||
public FreemarkerEngine(Configuration freemarkerCfg) {
|
||||
@ -49,7 +52,7 @@ public class FreemarkerEngine implements TemplateEngine {
|
||||
|
||||
@Override
|
||||
public TemplateEngine init(TemplateConfig config) {
|
||||
if(null == config){
|
||||
if (null == config) {
|
||||
config = TemplateConfig.DEFAULT;
|
||||
}
|
||||
init(createCfg(config));
|
||||
@ -58,29 +61,30 @@ public class FreemarkerEngine implements TemplateEngine {
|
||||
|
||||
/**
|
||||
* 初始化引擎
|
||||
*
|
||||
* @param freemarkerCfg Configuration
|
||||
*/
|
||||
private void init(Configuration freemarkerCfg){
|
||||
private void init(Configuration freemarkerCfg) {
|
||||
this.cfg = freemarkerCfg;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Template getTemplate(String resource) {
|
||||
if(null == this.cfg){
|
||||
if (null == this.cfg) {
|
||||
init(TemplateConfig.DEFAULT);
|
||||
}
|
||||
try {
|
||||
return FreemarkerTemplate.wrap(this.cfg.getTemplate(resource));
|
||||
} catch(IOException e) {
|
||||
} catch (IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}catch (Exception e) {
|
||||
} catch (Exception e) {
|
||||
throw new TemplateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建配置项
|
||||
*
|
||||
*
|
||||
* @param config 模板配置
|
||||
* @return {@link Configuration }
|
||||
*/
|
||||
@ -94,30 +98,30 @@ public class FreemarkerEngine implements TemplateEngine {
|
||||
cfg.setDefaultEncoding(config.getCharset().toString());
|
||||
|
||||
switch (config.getResourceMode()) {
|
||||
case CLASSPATH:
|
||||
cfg.setTemplateLoader(new ClassTemplateLoader(ClassUtil.getClassLoader(), config.getPath()));
|
||||
break;
|
||||
case FILE:
|
||||
try {
|
||||
cfg.setTemplateLoader(new FileTemplateLoader(FileUtil.file(config.getPath())));
|
||||
} catch (IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
break;
|
||||
case WEB_ROOT:
|
||||
try {
|
||||
cfg.setTemplateLoader(new FileTemplateLoader(FileUtil.file(FileUtil.getWebRoot(), config.getPath())));
|
||||
} catch (IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
break;
|
||||
case STRING:
|
||||
cfg.setTemplateLoader(new SimpleStringTemplateLoader());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
case CLASSPATH:
|
||||
cfg.setTemplateLoader(new ClassTemplateLoader(ClassUtil.getClassLoader(), config.getPath()));
|
||||
break;
|
||||
case FILE:
|
||||
try {
|
||||
cfg.setTemplateLoader(new FileTemplateLoader(FileUtil.file(config.getPath())));
|
||||
} catch (IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
break;
|
||||
case WEB_ROOT:
|
||||
try {
|
||||
cfg.setTemplateLoader(new FileTemplateLoader(FileUtil.file(FileUtil.getWebRoot(), config.getPath())));
|
||||
} catch (IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
break;
|
||||
case STRING:
|
||||
cfg.setTemplateLoader(new SimpleStringTemplateLoader());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
return cfg;
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Freemarker实现
|
||||
*
|
||||
* @author looly
|
||||
* Freemarker实现<br>
|
||||
* 见:https://freemarker.apache.org/
|
||||
*
|
||||
* @author looly
|
||||
*/
|
||||
package cn.hutool.extra.template.engine.freemarker;
|
@ -7,10 +7,10 @@ import cn.hutool.extra.template.TemplateEngine;
|
||||
import org.apache.velocity.app.Velocity;
|
||||
|
||||
/**
|
||||
* Velocity模板引擎
|
||||
*
|
||||
* @author looly
|
||||
* Velocity模板引擎<br>
|
||||
* 见:http://velocity.apache.org/
|
||||
*
|
||||
* @author looly
|
||||
*/
|
||||
public class VelocityEngine implements TemplateEngine {
|
||||
|
||||
@ -18,14 +18,16 @@ public class VelocityEngine implements TemplateEngine {
|
||||
private TemplateConfig config;
|
||||
|
||||
// --------------------------------------------------------------------------------- Constructor start
|
||||
|
||||
/**
|
||||
* 默认构造
|
||||
*/
|
||||
public VelocityEngine() {}
|
||||
public VelocityEngine() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
*
|
||||
* @param config 模板配置
|
||||
*/
|
||||
public VelocityEngine(TemplateConfig config) {
|
||||
@ -34,7 +36,7 @@ public class VelocityEngine implements TemplateEngine {
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
*
|
||||
* @param engine {@link org.apache.velocity.app.VelocityEngine}
|
||||
*/
|
||||
public VelocityEngine(org.apache.velocity.app.VelocityEngine engine) {
|
||||
@ -44,7 +46,7 @@ public class VelocityEngine implements TemplateEngine {
|
||||
|
||||
@Override
|
||||
public TemplateEngine init(TemplateConfig config) {
|
||||
if(null == config){
|
||||
if (null == config) {
|
||||
config = TemplateConfig.DEFAULT;
|
||||
}
|
||||
this.config = config;
|
||||
@ -54,15 +56,16 @@ public class VelocityEngine implements TemplateEngine {
|
||||
|
||||
/**
|
||||
* 初始化引擎
|
||||
*
|
||||
* @param engine 引擎
|
||||
*/
|
||||
private void init(org.apache.velocity.app.VelocityEngine engine){
|
||||
private void init(org.apache.velocity.app.VelocityEngine engine) {
|
||||
this.engine = engine;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取原始的引擎对象
|
||||
*
|
||||
*
|
||||
* @return 原始引擎对象
|
||||
* @since 4.3.0
|
||||
*/
|
||||
@ -72,7 +75,7 @@ public class VelocityEngine implements TemplateEngine {
|
||||
|
||||
@Override
|
||||
public Template getTemplate(String resource) {
|
||||
if(null == this.engine){
|
||||
if (null == this.engine) {
|
||||
init(TemplateConfig.DEFAULT);
|
||||
}
|
||||
|
||||
@ -80,15 +83,15 @@ public class VelocityEngine implements TemplateEngine {
|
||||
String root;
|
||||
// 自定义编码
|
||||
String charsetStr = null;
|
||||
if(null != this.config){
|
||||
if (null != this.config) {
|
||||
root = this.config.getPath();
|
||||
charsetStr = this.config.getCharsetStr();
|
||||
|
||||
// 修正template目录,在classpath或者web_root模式下,按照配置添加默认前缀
|
||||
// 如果用户已经自行添加了前缀,则忽略之
|
||||
final TemplateConfig.ResourceMode resourceMode = this.config.getResourceMode();
|
||||
if(TemplateConfig.ResourceMode.CLASSPATH == resourceMode
|
||||
|| TemplateConfig.ResourceMode.WEB_ROOT == resourceMode){
|
||||
if (TemplateConfig.ResourceMode.CLASSPATH == resourceMode
|
||||
|| TemplateConfig.ResourceMode.WEB_ROOT == resourceMode) {
|
||||
resource = StrUtil.addPrefixIfNot(resource, StrUtil.addSuffixIfNot(root, "/"));
|
||||
}
|
||||
}
|
||||
@ -98,7 +101,7 @@ public class VelocityEngine implements TemplateEngine {
|
||||
|
||||
/**
|
||||
* 创建引擎
|
||||
*
|
||||
*
|
||||
* @param config 模板配置
|
||||
* @return {@link org.apache.velocity.app.VelocityEngine}
|
||||
*/
|
||||
@ -116,29 +119,29 @@ public class VelocityEngine implements TemplateEngine {
|
||||
|
||||
// loader
|
||||
switch (config.getResourceMode()) {
|
||||
case CLASSPATH:
|
||||
// 新版Velocity弃用
|
||||
case CLASSPATH:
|
||||
// 新版Velocity弃用
|
||||
// ve.setProperty("file.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
|
||||
ve.setProperty("resource.loader.file.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
|
||||
break;
|
||||
case FILE:
|
||||
// path
|
||||
final String path = config.getPath();
|
||||
if (null != path) {
|
||||
ve.setProperty(Velocity.FILE_RESOURCE_LOADER_PATH, path);
|
||||
}
|
||||
break;
|
||||
case WEB_ROOT:
|
||||
ve.setProperty(Velocity.RESOURCE_LOADER, "webapp");
|
||||
ve.setProperty("webapp.resource.loader.class", "org.apache.velocity.tools.view.servlet.WebappLoader");
|
||||
ve.setProperty("webapp.resource.loader.path", StrUtil.nullToDefault(config.getPath(), StrUtil.SLASH));
|
||||
break;
|
||||
case STRING:
|
||||
ve.setProperty(Velocity.RESOURCE_LOADER, "str");
|
||||
ve.setProperty("str.resource.loader.class", SimpleStringResourceLoader.class.getName());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
ve.setProperty("resource.loader.file.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
|
||||
break;
|
||||
case FILE:
|
||||
// path
|
||||
final String path = config.getPath();
|
||||
if (null != path) {
|
||||
ve.setProperty(Velocity.FILE_RESOURCE_LOADER_PATH, path);
|
||||
}
|
||||
break;
|
||||
case WEB_ROOT:
|
||||
ve.setProperty(Velocity.RESOURCE_LOADER, "webapp");
|
||||
ve.setProperty("webapp.resource.loader.class", "org.apache.velocity.tools.view.servlet.WebappLoader");
|
||||
ve.setProperty("webapp.resource.loader.path", StrUtil.nullToDefault(config.getPath(), StrUtil.SLASH));
|
||||
break;
|
||||
case STRING:
|
||||
ve.setProperty(Velocity.RESOURCE_LOADER, "str");
|
||||
ve.setProperty("str.resource.loader.class", SimpleStringResourceLoader.class.getName());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
ve.init();
|
||||
|
@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Velocity实现
|
||||
*
|
||||
* @author looly
|
||||
* Velocity实现<br>
|
||||
* 见:http://velocity.apache.org/
|
||||
*
|
||||
* @author looly
|
||||
*/
|
||||
package cn.hutool.extra.template.engine.velocity;
|
@ -80,7 +80,7 @@ public class BeanValidationResult {
|
||||
}
|
||||
|
||||
/**
|
||||
* 错误消息,包括字段名(字段路径)和消息内容
|
||||
* 错误消息,包括字段名(字段路径)、消息内容和字段值
|
||||
*/
|
||||
public static class ErrorMessage {
|
||||
/**
|
||||
@ -91,6 +91,10 @@ public class BeanValidationResult {
|
||||
* 错误信息
|
||||
*/
|
||||
private String message;
|
||||
/**
|
||||
* 错误值
|
||||
*/
|
||||
private Object value;
|
||||
|
||||
public String getPropertyName() {
|
||||
return propertyName;
|
||||
@ -108,11 +112,20 @@ public class BeanValidationResult {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public Object getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(Object value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ErrorMessage{" +
|
||||
"propertyName='" + propertyName + '\'' +
|
||||
", message='" + message + '\'' +
|
||||
", value=" + value +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
@ -76,6 +76,7 @@ public class ValidationUtil {
|
||||
/**
|
||||
* 校验bean的某一个属性
|
||||
*
|
||||
* @param <T> bean类型
|
||||
* @param bean bean
|
||||
* @param propertyName 属性名称
|
||||
* @param groups 验证分组
|
||||
@ -97,7 +98,8 @@ public class ValidationUtil {
|
||||
ErrorMessage errorMessage = new ErrorMessage();
|
||||
errorMessage.setPropertyName(constraintViolation.getPropertyPath().toString());
|
||||
errorMessage.setMessage(constraintViolation.getMessage());
|
||||
result.getErrorMessages().add(errorMessage);
|
||||
errorMessage.setValue(constraintViolation.getInvalidValue());
|
||||
result.addErrorMessage(errorMessage);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
/**
|
||||
* 基于JSR-303标准的校验工具类,封装了javax.validation的API
|
||||
* 基于JSR-380标准的校验工具类,封装了javax.validation的API
|
||||
*
|
||||
* @author chengqiang
|
||||
*/
|
||||
package cn.hutool.extra.validation;
|
||||
package cn.hutool.extra.validation;
|
||||
|
@ -0,0 +1,62 @@
|
||||
package cn.hutool.extra.compress;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.lang.Console;
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
import cn.hutool.extra.compress.archiver.StreamArchiver;
|
||||
import org.apache.commons.compress.archivers.ArchiveStreamFactory;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class ArchiverTest {
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void zipTest(){
|
||||
final File file = FileUtil.file("d:/test/compress/test.zip");
|
||||
StreamArchiver.create(CharsetUtil.CHARSET_UTF_8, ArchiveStreamFactory.ZIP, file)
|
||||
.add(FileUtil.file("d:/Java"), (f)->{
|
||||
Console.log("Add: {}", f.getPath());
|
||||
return true;
|
||||
})
|
||||
.finish().close();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void tarTest(){
|
||||
final File file = FileUtil.file("d:/test/compress/test.tar");
|
||||
StreamArchiver.create(CharsetUtil.CHARSET_UTF_8, ArchiveStreamFactory.TAR, file)
|
||||
.add(FileUtil.file("d:/Java"), (f)->{
|
||||
Console.log("Add: {}", f.getPath());
|
||||
return true;
|
||||
})
|
||||
.finish().close();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void cpioTest(){
|
||||
final File file = FileUtil.file("d:/test/compress/test.cpio");
|
||||
StreamArchiver.create(CharsetUtil.CHARSET_UTF_8, ArchiveStreamFactory.CPIO, file)
|
||||
.add(FileUtil.file("d:/Java"), (f)->{
|
||||
Console.log("Add: {}", f.getPath());
|
||||
return true;
|
||||
})
|
||||
.finish().close();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void senvenZTest(){
|
||||
final File file = FileUtil.file("d:/test/compress/test.7z");
|
||||
CompressUtil.createArchiver(CharsetUtil.CHARSET_UTF_8, ArchiveStreamFactory.SEVEN_Z, file)
|
||||
.add(FileUtil.file("d:/Java/apache-maven-3.6.3"), (f)->{
|
||||
Console.log("Add: {}", f.getPath());
|
||||
return true;
|
||||
})
|
||||
.finish().close();
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package cn.hutool.extra.compress;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
import cn.hutool.extra.compress.extractor.Extractor;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
public class ExtractorTest {
|
||||
|
||||
@Test
|
||||
// @Ignore
|
||||
public void zipTest(){
|
||||
Extractor extractor = CompressUtil.createExtractor(
|
||||
CharsetUtil.defaultCharset(),
|
||||
FileUtil.file("d:/test/compress/test.zip"));
|
||||
|
||||
extractor.extract(FileUtil.file("d:/test/compress/test2/"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void sevenZTest(){
|
||||
Extractor extractor = CompressUtil.createExtractor(
|
||||
CharsetUtil.defaultCharset(),
|
||||
FileUtil.file("d:/test/compress/test.7z"));
|
||||
|
||||
extractor.extract(FileUtil.file("d:/test/compress/test2/"));
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package cn.hutool.extra.ftp;
|
||||
|
||||
public class SimpleFtpServerTest {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SimpleFtpServer
|
||||
.create()
|
||||
.addAnonymous("d:/test/ftp/")
|
||||
.start();
|
||||
}
|
||||
}
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.4.8-SNAPSHOT</version>
|
||||
<version>5.5.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-http</artifactId>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.4.8-SNAPSHOT</version>
|
||||
<version>5.5.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-json</artifactId>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.4.8-SNAPSHOT</version>
|
||||
<version>5.5.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-log</artifactId>
|
||||
@ -21,7 +21,7 @@
|
||||
<slf4j.version>1.7.30</slf4j.version>
|
||||
<logback.version>1.3.0-alpha5</logback.version>
|
||||
<log4j.version>1.2.17</log4j.version>
|
||||
<log4j2.version>2.13.3</log4j2.version>
|
||||
<log4j2.version>2.14.0</log4j2.version>
|
||||
<commons-logging.version>1.2</commons-logging.version>
|
||||
<tinylog.version>1.3.6</tinylog.version>
|
||||
<jboss-logging.version>3.4.1.Final</jboss-logging.version>
|
||||
|
@ -8,7 +8,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.4.8-SNAPSHOT</version>
|
||||
<version>5.5.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-poi</artifactId>
|
||||
|
@ -3,6 +3,7 @@ package cn.hutool.poi.excel;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.io.IORuntimeException;
|
||||
import org.apache.poi.ss.usermodel.Sheet;
|
||||
import org.apache.poi.xssf.streaming.SXSSFSheet;
|
||||
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
|
||||
|
||||
import java.io.File;
|
||||
@ -122,6 +123,24 @@ public class BigExcelWriter extends ExcelWriter {
|
||||
|
||||
// -------------------------------------------------------------------------- Constructor end
|
||||
|
||||
@Override
|
||||
public BigExcelWriter autoSizeColumn(int columnIndex) {
|
||||
final SXSSFSheet sheet = (SXSSFSheet)this.sheet;
|
||||
sheet.trackColumnForAutoSizing(columnIndex);
|
||||
super.autoSizeColumn(columnIndex);
|
||||
sheet.untrackColumnForAutoSizing(columnIndex);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigExcelWriter autoSizeColumnAll() {
|
||||
final SXSSFSheet sheet = (SXSSFSheet)this.sheet;
|
||||
sheet.trackAllColumnsForAutoSizing();
|
||||
super.autoSizeColumnAll();
|
||||
sheet.untrackAllColumnsForAutoSizing();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExcelWriter flush(OutputStream out, boolean isCloseOut) throws IORuntimeException {
|
||||
if(false == isFlushed){
|
||||
|
@ -73,7 +73,7 @@ public class RowUtil {
|
||||
Object cellValue;
|
||||
boolean isAllNull = true;
|
||||
for (int i = startCellNumInclude; i < size; i++) {
|
||||
cellValue = CellUtil.getCellValue(row.getCell(i), cellEditor);
|
||||
cellValue = CellUtil.getCellValue(CellUtil.getCell(row, i), cellEditor);
|
||||
isAllNull &= StrUtil.isEmptyIfStr(cellValue);
|
||||
cellValues.add(cellValue);
|
||||
}
|
||||
|
@ -74,10 +74,7 @@ public class CellUtil {
|
||||
* @return 值,类型可能为:Date、Double、Boolean、String
|
||||
*/
|
||||
public static Object getCellValue(Cell cell, CellEditor cellEditor) {
|
||||
if (null == cell) {
|
||||
return null;
|
||||
}
|
||||
return getCellValue(cell, cell.getCellTypeEnum(), cellEditor);
|
||||
return getCellValue(cell, null, cellEditor);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -105,6 +102,9 @@ public class CellUtil {
|
||||
if (null == cell) {
|
||||
return null;
|
||||
}
|
||||
if(cell instanceof NullCell){
|
||||
return null == cellEditor ? null : cellEditor.edit(cell, null);
|
||||
}
|
||||
if (null == cellType) {
|
||||
cellType = cell.getCellTypeEnum();
|
||||
}
|
||||
@ -235,7 +235,23 @@ public class CellUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取已有行或创建新行
|
||||
*获取单元格,如果单元格不存在,返回{@link NullCell}
|
||||
*
|
||||
* @param row Excel表的行
|
||||
* @param cellIndex 列号
|
||||
* @return {@link Row}
|
||||
* @since 5.5.0
|
||||
*/
|
||||
public static Cell getCell(Row row, int cellIndex) {
|
||||
Cell cell = row.getCell(cellIndex);
|
||||
if (null == cell) {
|
||||
return new NullCell(row, cellIndex);
|
||||
}
|
||||
return cell;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取已有单元格或创建新单元格
|
||||
*
|
||||
* @param row Excel表的行
|
||||
* @param cellIndex 列号
|
||||
|
240
hutool-poi/src/main/java/cn/hutool/poi/excel/cell/NullCell.java
Normal file
240
hutool-poi/src/main/java/cn/hutool/poi/excel/cell/NullCell.java
Normal file
@ -0,0 +1,240 @@
|
||||
package cn.hutool.poi.excel.cell;
|
||||
|
||||
import org.apache.poi.ss.formula.FormulaParseException;
|
||||
import org.apache.poi.ss.usermodel.Cell;
|
||||
import org.apache.poi.ss.usermodel.CellStyle;
|
||||
import org.apache.poi.ss.usermodel.CellType;
|
||||
import org.apache.poi.ss.usermodel.Comment;
|
||||
import org.apache.poi.ss.usermodel.Hyperlink;
|
||||
import org.apache.poi.ss.usermodel.RichTextString;
|
||||
import org.apache.poi.ss.usermodel.Row;
|
||||
import org.apache.poi.ss.usermodel.Sheet;
|
||||
import org.apache.poi.ss.util.CellAddress;
|
||||
import org.apache.poi.ss.util.CellRangeAddress;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 当单元格不存在时使用此对象表示,得到的值都为null,此对象只用于标注单元格所在位置信息。
|
||||
*
|
||||
* @author looly
|
||||
* @since 5.5.0
|
||||
*/
|
||||
public class NullCell implements Cell {
|
||||
|
||||
private final Row row;
|
||||
private final int columnIndex;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param row 行
|
||||
* @param columnIndex 列号,从0开始
|
||||
*/
|
||||
public NullCell(Row row, int columnIndex) {
|
||||
this.row = row;
|
||||
this.columnIndex = columnIndex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColumnIndex() {
|
||||
return this.columnIndex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRowIndex() {
|
||||
return getRow().getRowNum();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Sheet getSheet() {
|
||||
return getRow().getSheet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Row getRow() {
|
||||
return this.row;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCellType(CellType cellType) {
|
||||
throw new UnsupportedOperationException("Can not set any thing to null cell!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBlank() {
|
||||
throw new UnsupportedOperationException("Can not set any thing to null cell!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public CellType getCellType() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CellType getCellTypeEnum() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CellType getCachedFormulaResultType() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CellType getCachedFormulaResultTypeEnum() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCellValue(double value) {
|
||||
throw new UnsupportedOperationException("Can not set any thing to null cell!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCellValue(Date value) {
|
||||
throw new UnsupportedOperationException("Can not set any thing to null cell!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCellValue(LocalDateTime value) {
|
||||
throw new UnsupportedOperationException("Can not set any thing to null cell!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCellValue(Calendar value) {
|
||||
throw new UnsupportedOperationException("Can not set any thing to null cell!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCellValue(RichTextString value) {
|
||||
throw new UnsupportedOperationException("Can not set any thing to null cell!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCellValue(String value) {
|
||||
throw new UnsupportedOperationException("Can not set any thing to null cell!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCellFormula(String formula) throws FormulaParseException, IllegalStateException {
|
||||
throw new UnsupportedOperationException("Can not set any thing to null cell!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeFormula() throws IllegalStateException {
|
||||
throw new UnsupportedOperationException("Can not set any thing to null cell!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCellFormula() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getNumericCellValue() {
|
||||
throw new UnsupportedOperationException("Cell value is null!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date getDateCellValue() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalDateTime getLocalDateTimeCellValue() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RichTextString getRichStringCellValue() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getStringCellValue() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCellValue(boolean value) {
|
||||
throw new UnsupportedOperationException("Can not set any thing to null cell!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCellErrorValue(byte value) {
|
||||
throw new UnsupportedOperationException("Can not set any thing to null cell!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getBooleanCellValue() {
|
||||
throw new UnsupportedOperationException("Cell value is null!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getErrorCellValue() {
|
||||
throw new UnsupportedOperationException("Cell value is null!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCellStyle(CellStyle style) {
|
||||
throw new UnsupportedOperationException("Can not set any thing to null cell!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public CellStyle getCellStyle() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAsActiveCell() {
|
||||
throw new UnsupportedOperationException("Can not set any thing to null cell!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public CellAddress getAddress() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCellComment(Comment comment) {
|
||||
throw new UnsupportedOperationException("Can not set any thing to null cell!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Comment getCellComment() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeCellComment() {
|
||||
throw new UnsupportedOperationException("Can not set any thing to null cell!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Hyperlink getHyperlink() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHyperlink(Hyperlink link) {
|
||||
throw new UnsupportedOperationException("Can not set any thing to null cell!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeHyperlink() {
|
||||
throw new UnsupportedOperationException("Can not set any thing to null cell!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public CellRangeAddress getArrayFormulaRange() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPartOfArrayFormulaGroup() {
|
||||
throw new UnsupportedOperationException("Cell value is null!");
|
||||
}
|
||||
}
|
@ -16,7 +16,12 @@ import org.apache.poi.ss.usermodel.IndexedColors;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 写出Excel单元测试
|
||||
@ -218,17 +223,24 @@ public class BigExcelWriteTest {
|
||||
@Ignore
|
||||
public void issue1210() {
|
||||
// 通过工具类创建writer
|
||||
String path = "e:/issue1210.xlsx";
|
||||
String path = "d:/test/issue1210.xlsx";
|
||||
FileUtil.del(path);
|
||||
BigExcelWriter writer = ExcelUtil.getBigWriter(path);
|
||||
writer.addHeaderAlias("id", "SN");
|
||||
writer.addHeaderAlias("userName", "User Name");
|
||||
List<Map<String, Object>> list = new ArrayList<>();
|
||||
list.add(new HashMap<String, Object>() {{
|
||||
list.add(new HashMap<String, Object>() {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
{
|
||||
put("id", 1);
|
||||
put("userName", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
|
||||
}});
|
||||
list.add(new HashMap<String, Object>() {{
|
||||
|
||||
list.add(new HashMap<String, Object>() {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
{
|
||||
put("id", 2);
|
||||
put("userName", "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");
|
||||
}});
|
||||
|
@ -3,6 +3,7 @@ package cn.hutool.poi.excel.test;
|
||||
import cn.hutool.core.io.resource.ResourceUtil;
|
||||
import cn.hutool.core.lang.Console;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.poi.excel.ExcelReader;
|
||||
import cn.hutool.poi.excel.ExcelUtil;
|
||||
import lombok.Data;
|
||||
@ -186,6 +187,7 @@ public class ExcelReadTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void readCellsTest() {
|
||||
final ExcelReader reader = ExcelUtil.getReader("merge_test.xlsx");
|
||||
reader.read((cell, value)-> Console.log("{}, {} {}", cell.getRowIndex(), cell.getColumnIndex(), value));
|
||||
@ -201,4 +203,20 @@ public class ExcelReadTest {
|
||||
Console.log(list);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nullValueEditTest(){
|
||||
final ExcelReader reader = ExcelUtil.getReader("null_cell_test.xlsx");
|
||||
reader.setCellEditor((cell, value)-> ObjectUtil.defaultIfNull(value, "#"));
|
||||
final List<List<Object>> read = reader.read();
|
||||
|
||||
// 对于任意一个单元格有值的情况下,之前的单元格值按照null处理
|
||||
Assert.assertEquals(1, read.get(1).size());
|
||||
Assert.assertEquals(2, read.get(2).size());
|
||||
Assert.assertEquals(3, read.get(3).size());
|
||||
|
||||
Assert.assertEquals("#", read.get(2).get(0));
|
||||
Assert.assertEquals("#", read.get(3).get(0));
|
||||
Assert.assertEquals("#", read.get(3).get(1));
|
||||
}
|
||||
}
|
||||
|
BIN
hutool-poi/src/test/resources/null_cell_test.xlsx
Normal file
BIN
hutool-poi/src/test/resources/null_cell_test.xlsx
Normal file
Binary file not shown.
@ -8,7 +8,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.4.8-SNAPSHOT</version>
|
||||
<version>5.5.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-script</artifactId>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.4.8-SNAPSHOT</version>
|
||||
<version>5.5.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-setting</artifactId>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.4.8-SNAPSHOT</version>
|
||||
<version>5.5.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-socket</artifactId>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.4.8-SNAPSHOT</version>
|
||||
<version>5.5.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-system</artifactId>
|
||||
@ -26,7 +26,7 @@
|
||||
<dependency>
|
||||
<groupId>com.github.oshi</groupId>
|
||||
<artifactId>oshi-core</artifactId>
|
||||
<version>5.3.1</version>
|
||||
<version>5.3.5</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
2
pom.xml
2
pom.xml
@ -8,7 +8,7 @@
|
||||
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.4.8-SNAPSHOT</version>
|
||||
<version>5.5.1-SNAPSHOT</version>
|
||||
<name>hutool</name>
|
||||
<description>Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。</description>
|
||||
<url>https://github.com/looly/hutool</url>
|
||||
|
Loading…
x
Reference in New Issue
Block a user