diff --git a/CHANGELOG.md b/CHANGELOG.md index 983da7d72..0ca3fa474 100644 --- a/CHANGELOG.md +++ b/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) ------------------------------------------------------------------------------------------------------------- diff --git a/README-EN.md b/README-EN.md index 518d10262..0f1c0da29 100644 --- a/README-EN.md +++ b/README-EN.md @@ -45,6 +45,11 @@

+ +------------------------------------------------------------------------------- + +[**中文说明**](README.md) + ------------------------------------------------------------------------------- ## Introduction @@ -120,19 +125,19 @@ Each module can be introduced individually, or all modules can be introduced by cn.hutool hutool-all - 5.4.8 + 5.5.1 ``` ### 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. diff --git a/README.md b/README.md index 7c9f42d41..04316f385 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,10 @@ ------------------------------------------------------------------------------- +[**English Documentation**](README-EN.md) + +------------------------------------------------------------------------------- + ## 简介 Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。 @@ -119,21 +123,21 @@ Hutool的存在就是为了减少代码搜索成本,避免网络上参差不 cn.hutool hutool-all - 5.4.8 + 5.5.1 ``` ### 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平台没有测试,不能保证所有工具类或工具方法可用。 diff --git a/bin/version.txt b/bin/version.txt index f5b71c928..7acd1cb0e 100755 --- a/bin/version.txt +++ b/bin/version.txt @@ -1 +1 @@ -5.4.8 +5.5.1 diff --git a/docs/js/version.js b/docs/js/version.js index eee6b5653..02594b515 100644 --- a/docs/js/version.js +++ b/docs/js/version.js @@ -1 +1 @@ -var version = '5.4.8' \ No newline at end of file +var version = '5.5.1' \ No newline at end of file diff --git a/hutool-all/pom.xml b/hutool-all/pom.xml index fb74363dc..c159e9f38 100644 --- a/hutool-all/pom.xml +++ b/hutool-all/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.4.8-SNAPSHOT + 5.5.1-SNAPSHOT hutool-all diff --git a/hutool-aop/pom.xml b/hutool-aop/pom.xml index cc4dd1044..72ce688c1 100644 --- a/hutool-aop/pom.xml +++ b/hutool-aop/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.4.8-SNAPSHOT + 5.5.1-SNAPSHOT hutool-aop diff --git a/hutool-bloomFilter/pom.xml b/hutool-bloomFilter/pom.xml index 7a8804c1c..b83689bfd 100644 --- a/hutool-bloomFilter/pom.xml +++ b/hutool-bloomFilter/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.4.8-SNAPSHOT + 5.5.1-SNAPSHOT hutool-bloomFilter diff --git a/hutool-bom/pom.xml b/hutool-bom/pom.xml index 298fe51f8..9f2e88b40 100644 --- a/hutool-bom/pom.xml +++ b/hutool-bom/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.4.8-SNAPSHOT + 5.5.1-SNAPSHOT hutool-bom diff --git a/hutool-cache/pom.xml b/hutool-cache/pom.xml index 79bb11aa9..ee465b8cf 100644 --- a/hutool-cache/pom.xml +++ b/hutool-cache/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.4.8-SNAPSHOT + 5.5.1-SNAPSHOT hutool-cache diff --git a/hutool-captcha/pom.xml b/hutool-captcha/pom.xml index b2cb99015..353279f29 100644 --- a/hutool-captcha/pom.xml +++ b/hutool-captcha/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.4.8-SNAPSHOT + 5.5.1-SNAPSHOT hutool-captcha diff --git a/hutool-core/pom.xml b/hutool-core/pom.xml index a6c6f7d84..733fc0911 100644 --- a/hutool-core/pom.xml +++ b/hutool-core/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.4.8-SNAPSHOT + 5.5.1-SNAPSHOT hutool-core diff --git a/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java b/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java index dffb2a987..b56c239bd 100644 --- a/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java @@ -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的大小,此方法可以处理的对象类型如下: + * + * + * @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; + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/collection/IterUtil.java b/hutool-core/src/main/java/cn/hutool/core/collection/IterUtil.java index 3a5881dd0..afe74433d 100644 --- a/hutool-core/src/main/java/cn/hutool/core/collection/IterUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/collection/IterUtil.java @@ -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 Iterator trans(Iterator iterator, Function 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; + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/convert/impl/NumberConverter.java b/hutool-core/src/main/java/cn/hutool/core/convert/impl/NumberConverter.java index 1f366f4ad..7d41bbb4c 100644 --- a/hutool-core/src/main/java/cn/hutool/core/convert/impl/NumberConverter.java +++ b/hutool-core/src/main/java/cn/hutool/core/convert/impl/NumberConverter.java @@ -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; /** * 数字转换器
@@ -54,17 +55,31 @@ public class NumberConverter extends AbstractConverter { @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 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 { } 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 { } 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 { } 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 { } 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 { } 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 { * 如果给定的值为空,或者转换失败,返回默认值
* 转换失败不会报错 * - * @param value 被转换的值 + * @param value 被转换的值 + * @param toStrFunc 转换为字符串的函数规则 * @return 结果 */ - private BigDecimal toBigDecimal(Object value) { + private static BigDecimal toBigDecimal(Object value, Function toStrFunc) { if (value instanceof Number) { return NumberUtil.toBigDecimal((Number) value); } else if (value instanceof Boolean) { @@ -182,7 +198,7 @@ public class NumberConverter extends AbstractConverter { } //对于Double类型,先要转换为String,避免精度问题 - return NumberUtil.toBigDecimal(convertToStr(value)); + return NumberUtil.toBigDecimal(toStrFunc.apply(value)); } /** @@ -190,25 +206,26 @@ public class NumberConverter extends AbstractConverter { * 如果给定的值为空,或者转换失败,返回默认值
* 转换失败不会报错 * - * @param value 被转换的值 + * @param value 被转换的值 + * @param toStrFunc 转换为字符串的函数规则 * @return 结果 */ - private BigInteger toBigInteger(Object value) { + private static BigInteger toBigInteger(Object value, Function 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); } diff --git a/hutool-core/src/main/java/cn/hutool/core/convert/impl/PrimitiveConverter.java b/hutool-core/src/main/java/cn/hutool/core/convert/impl/PrimitiveConverter.java index d039056cd..475bbd54d 100644 --- a/hutool-core/src/main/java/cn/hutool/core/convert/impl/PrimitiveConverter.java +++ b/hutool-core/src/main/java/cn/hutool/core/convert/impl/PrimitiveConverter.java @@ -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; /** * 原始类型转换器
@@ -49,114 +46,7 @@ public class PrimitiveConverter extends AbstractConverter { @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 { public Class getTargetType() { return (Class) this.targetType; } + + /** + * 将指定值转换为原始类型的值 + * @param value 值 + * @param primitiveClass 原始类型 + * @param toStringFunc 当无法直接转换时,转为字符串后再转换的函数 + * @return 转换结果 + * @since 5.5.0 + */ + protected static Object convert(Object value, Class primitiveClass, Function 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); + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/date/BetweenFormater.java b/hutool-core/src/main/java/cn/hutool/core/date/BetweenFormater.java index bd83e1adf..94d8ea82a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/date/BetweenFormater.java +++ b/hutool-core/src/main/java/cn/hutool/core/date/BetweenFormater.java @@ -1,208 +1,20 @@ package cn.hutool.core.date; -import cn.hutool.core.util.StrUtil; - -import java.io.Serializable; - /** - * 时长格式化器 + * 时长格式化器
+ * * * @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; - } - - /** - * 格式化日期间隔输出
- * - * @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(); - } - - /** - * 等级数量是否有效
- * 有效的定义是:levelMaxCount大于0(被设置),当前等级数量没有超过这个最大值 - * - * @param levelCount 登记数量 - * @return 是否有效 - */ - private boolean isLevelCountValid(int levelCount) { - return this.levelMaxCount <= 0 || levelCount < this.levelMaxCount; + super(betweenMs, level, levelMaxCount); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/date/BetweenFormatter.java b/hutool-core/src/main/java/cn/hutool/core/date/BetweenFormatter.java new file mode 100644 index 000000000..27ca3f423 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/date/BetweenFormatter.java @@ -0,0 +1,215 @@ +package cn.hutool.core.date; + +import cn.hutool.core.util.StrUtil; + +import java.io.Serializable; + +/** + * 时长格式化器,用于格式化输出两个日期相差的时长
+ * 根据{@link Level}不同,调用{@link #format()}方法后返回类似于: + *
    + *
  • XX小时XX分XX秒
  • + *
  • XX天XX小时
  • + *
  • XX月XX天XX小时
  • + *
+ * + * @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; + } + + /** + * 格式化日期间隔输出
+ * + * @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(); + } + + /** + * 等级数量是否有效
+ * 有效的定义是:levelMaxCount大于0(被设置),当前等级数量没有超过这个最大值 + * + * @param levelCount 登记数量 + * @return 是否有效 + */ + private boolean isLevelCountValid(int levelCount) { + return this.levelMaxCount <= 0 || levelCount < this.levelMaxCount; + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/date/DateBetween.java b/hutool-core/src/main/java/cn/hutool/core/date/DateBetween.java index 7a28fb572..f37b869be 100644 --- a/hutool-core/src/main/java/cn/hutool/core/date/DateBetween.java +++ b/hutool-core/src/main/java/cn/hutool/core/date/DateBetween.java @@ -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); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/date/DateTime.java b/hutool-core/src/main/java/cn/hutool/core/date/DateTime.java index 69d722f87..be693e020 100644 --- a/hutool-core/src/main/java/cn/hutool/core/date/DateTime.java +++ b/hutool-core/src/main/java/cn/hutool/core/date/DateTime.java @@ -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); } diff --git a/hutool-core/src/main/java/cn/hutool/core/date/DateUtil.java b/hutool-core/src/main/java/cn/hutool/core/date/DateUtil.java index e5bcc8827..5815c3632 100644 --- a/hutool-core/src/main/java/cn/hutool/core/date/DateUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/date/DateUtil.java @@ -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(); } /** diff --git a/hutool-core/src/main/java/cn/hutool/core/img/Img.java b/hutool-core/src/main/java/cn/hutool/core/img/Img.java index 41c8cb519..646f33d43 100644 --- a/hutool-core/src/main/java/cn/hutool/core/img/Img.java +++ b/hutool-core/src/main/java/cn/hutool/core/img/Img.java @@ -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; } diff --git a/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java index 48442bb4f..6583e98d2 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java @@ -306,6 +306,7 @@ public class FileUtil extends PathUtil { /** * 创建File对象
+ * 根据的路径构建文件,在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 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); + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/io/file/FileCopier.java b/hutool-core/src/main/java/cn/hutool/core/io/file/FileCopier.java index 25abe5ce1..4fa339681 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/file/FileCopier.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/file/FileCopier.java @@ -44,7 +44,7 @@ public class FileCopier extends SrcToDestCopier{ * 新建一个文件复制器 * @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{ * 新建一个文件复制器 * @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{ 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); } diff --git a/hutool-core/src/main/java/cn/hutool/core/io/file/PathUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/file/PathUtil.java index c4e5bddd2..567532dc9 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/file/PathUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/file/PathUtil.java @@ -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 { } /** - * 删除文件或者文件夹
+ * 删除文件或者文件夹,不追踪软链
* 注意:删除文件夹时不会判断文件夹是否为空,如果不空则递归删除子文件或文件夹
* 某个文件删除失败会终止删除操作 * @@ -127,7 +127,7 @@ public class PathUtil { } try { - if (Files.isDirectory(path)) { + if (isDirectory(path)) { Files.walkFileTree(path, new SimpleFileVisitor() { @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
+ * 此方法不会追踪到软链对应的真实地址,即软链被当作文件 + * + * @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); + } + + /** + * 移动文件或目录
+ * 当目标是目录时,会将源文件或文件夹整体移动至目标目录下 + * + * @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); } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/NumberUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/NumberUtil.java index c5e11acb6..5c7781f4b 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/NumberUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/NumberUtil.java @@ -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}
+ * Float、Double等有精度问题,转换为字符串后再转换
+ * 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}
+ * 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}
+ * 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}
+ * 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); } // 去掉类型标识的结尾 diff --git a/hutool-core/src/main/java/cn/hutool/core/util/StrUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/StrUtil.java index 891a41ca6..cb4cb4b85 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/StrUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/StrUtil.java @@ -2405,16 +2405,52 @@ public class StrUtil { } final List 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]); } + /** + * 截取指定字符串多段中间部分,不包括标识字符串
+ *

+ * 栗子: + * + *

+	 * 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"]
+	 * 
+ * + * @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; } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java index a78212d1c..a8d025c5d 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java @@ -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 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 } \ No newline at end of file diff --git a/hutool-core/src/test/java/cn/hutool/core/bean/BeanUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/bean/BeanUtilTest.java index 56ec38dac..fa7a0aead 100644 --- a/hutool-core/src/test/java/cn/hutool/core/bean/BeanUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/bean/BeanUtilTest.java @@ -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; diff --git a/hutool-core/src/test/java/cn/hutool/core/convert/ConvertTest.java b/hutool-core/src/test/java/cn/hutool/core/convert/ConvertTest.java index c55dcf783..0f076f9d0 100644 --- a/hutool-core/src/test/java/cn/hutool/core/convert/ConvertTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/convert/ConvertTest.java @@ -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; + } + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/date/BetweenFormaterTest.java b/hutool-core/src/test/java/cn/hutool/core/date/BetweenFormaterTest.java index 5374d596f..c188ffdf4 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/BetweenFormaterTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/BetweenFormaterTest.java @@ -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秒"); } } diff --git a/hutool-core/src/test/java/cn/hutool/core/date/DateBetweenTest.java b/hutool-core/src/test/java/cn/hutool/core/date/DateBetweenTest.java index 23e1284be..788e0ad96 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/DateBetweenTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/DateBetweenTest.java @@ -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; diff --git a/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java index fb563707e..0500d3827 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java @@ -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; diff --git a/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java index db058d3aa..d1f45e077 100644 --- a/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java @@ -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"); diff --git a/hutool-core/src/test/java/cn/hutool/core/io/file/PathUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/io/file/PathUtilTest.java index 0fed2a0a3..2d5b14dfd 100644 --- a/hutool-core/src/test/java/cn/hutool/core/io/file/PathUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/io/file/PathUtilTest.java @@ -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); + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/StrUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/StrUtilTest.java index 260238a6c..3a162786b 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/StrUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/StrUtilTest.java @@ -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); diff --git a/hutool-cron/pom.xml b/hutool-cron/pom.xml index 0695b07f3..13decede6 100644 --- a/hutool-cron/pom.xml +++ b/hutool-cron/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.4.8-SNAPSHOT + 5.5.1-SNAPSHOT hutool-cron diff --git a/hutool-crypto/pom.xml b/hutool-crypto/pom.xml index f58b23bb9..4ec1562f9 100644 --- a/hutool-crypto/pom.xml +++ b/hutool-crypto/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.4.8-SNAPSHOT + 5.5.1-SNAPSHOT hutool-crypto diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml index 08384dea5..34d0e41ba 100644 --- a/hutool-db/pom.xml +++ b/hutool-db/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.4.8-SNAPSHOT + 5.5.1-SNAPSHOT hutool-db @@ -21,7 +21,7 @@ 0.9.5.5 2.8.0 9.0.30 - 1.2.1 + 1.2.3 2.4.13 3.12.7 3.32.3.2 diff --git a/hutool-dfa/pom.xml b/hutool-dfa/pom.xml index 6c35642d3..77235408a 100644 --- a/hutool-dfa/pom.xml +++ b/hutool-dfa/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.4.8-SNAPSHOT + 5.5.1-SNAPSHOT hutool-dfa diff --git a/hutool-extra/pom.xml b/hutool-extra/pom.xml index 8b0a57b26..fa6ce1a33 100644 --- a/hutool-extra/pom.xml +++ b/hutool-extra/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.4.8-SNAPSHOT + 5.5.1-SNAPSHOT hutool-extra @@ -19,10 +19,10 @@ 2.2 - 3.2.2.RELEASE + 3.2.4.RELEASE 1.3.0 2.3.30 - 4.9.02 + 4.9.03 3.0.11.RELEASE 1.6.2 0.1.55 @@ -30,7 +30,7 @@ 3.7.2 5.1.1 4.0.1 - 2.3.5.RELEASE + 2.4.0 3.3.0 @@ -146,6 +146,19 @@ compile true + + org.apache.ftpserver + ftpserver-core + 1.1.1 + compile + + + slf4j-api + org.slf4j + + + true + com.vdurmont @@ -215,7 +228,7 @@ org.apache.lucene lucene-analyzers-smartcn - 8.6.3 + 8.7.0 true @@ -393,10 +406,23 @@ org.springframework spring-expression - 5.3.0 + 5.3.1 compile true + + org.apache.commons + commons-compress + 1.20 + compile + true + + + org.tukaani + xz + 1.8 + test + diff --git a/hutool-extra/src/main/java/cn/hutool/extra/compress/CompressException.java b/hutool-extra/src/main/java/cn/hutool/extra/compress/CompressException.java new file mode 100644 index 000000000..c182b1151 --- /dev/null +++ b/hutool-extra/src/main/java/cn/hutool/extra/compress/CompressException.java @@ -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); + } +} diff --git a/hutool-extra/src/main/java/cn/hutool/extra/compress/CompressUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/compress/CompressUtil.java new file mode 100644 index 000000000..fdd0cda3c --- /dev/null +++ b/hutool-extra/src/main/java/cn/hutool/extra/compress/CompressUtil.java @@ -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; + +/** + * 压缩工具类
+ * 基于commons-compress的压缩解压封装 + * + * @author looly + * @since 5.5.0 + */ +public class CompressUtil { + + /** + * 获取压缩输出流,用于压缩指定内容,支持的格式例如: + *
    + *
  • {@value CompressorStreamFactory#GZIP}
  • + *
  • {@value CompressorStreamFactory#BZIP2}
  • + *
  • {@value CompressorStreamFactory#XZ}
  • + *
  • {@value CompressorStreamFactory#PACK200}
  • + *
  • {@value CompressorStreamFactory#SNAPPY_FRAMED}
  • + *
  • {@value CompressorStreamFactory#LZ4_BLOCK}
  • + *
  • {@value CompressorStreamFactory#LZ4_FRAMED}
  • + *
  • {@value CompressorStreamFactory#ZSTANDARD}
  • + *
  • {@value CompressorStreamFactory#DEFLATE}
  • + *
+ * + * @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); + } + } + + /** + * 获取压缩输入流,用于解压缩指定内容,支持的格式例如: + *
    + *
  • {@value CompressorStreamFactory#GZIP}
  • + *
  • {@value CompressorStreamFactory#BZIP2}
  • + *
  • {@value CompressorStreamFactory#XZ}
  • + *
  • {@value CompressorStreamFactory#PACK200}
  • + *
  • {@value CompressorStreamFactory#SNAPPY_FRAMED}
  • + *
  • {@value CompressorStreamFactory#LZ4_BLOCK}
  • + *
  • {@value CompressorStreamFactory#LZ4_FRAMED}
  • + *
  • {@value CompressorStreamFactory#ZSTANDARD}
  • + *
  • {@value CompressorStreamFactory#DEFLATE}
  • + *
+ * + * @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); + } + } + + /** + * 创建归档器,支持: + *
    + *
  • {@link ArchiveStreamFactory#AR}
  • + *
  • {@link ArchiveStreamFactory#CPIO}
  • + *
  • {@link ArchiveStreamFactory#JAR}
  • + *
  • {@link ArchiveStreamFactory#TAR}
  • + *
  • {@link ArchiveStreamFactory#ZIP}
  • + *
  • {@link ArchiveStreamFactory#SEVEN_Z}
  • + *
+ * + * @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); + } + + /** + * 创建归档器,支持: + *
    + *
  • {@link ArchiveStreamFactory#AR}
  • + *
  • {@link ArchiveStreamFactory#CPIO}
  • + *
  • {@link ArchiveStreamFactory#JAR}
  • + *
  • {@link ArchiveStreamFactory#TAR}
  • + *
  • {@link ArchiveStreamFactory#ZIP}
  • + *
  • {@link ArchiveStreamFactory#SEVEN_Z}
  • + *
+ * + * @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); + } + + /** + * 创建归档解包器,支持: + *
    + *
  • {@link ArchiveStreamFactory#AR}
  • + *
  • {@link ArchiveStreamFactory#CPIO}
  • + *
  • {@link ArchiveStreamFactory#JAR}
  • + *
  • {@link ArchiveStreamFactory#TAR}
  • + *
  • {@link ArchiveStreamFactory#ZIP}
  • + *
  • {@link ArchiveStreamFactory#SEVEN_Z}
  • + *
+ * + * @param charset 编码,7z格式此参数无效 + * @param file 归档文件 + * @return {@link Extractor} + */ + public static Extractor createExtractor(Charset charset, File file) { + return createExtractor(charset, null, file); + } + + /** + * 创建归档解包器,支持: + *
    + *
  • {@link ArchiveStreamFactory#AR}
  • + *
  • {@link ArchiveStreamFactory#CPIO}
  • + *
  • {@link ArchiveStreamFactory#JAR}
  • + *
  • {@link ArchiveStreamFactory#TAR}
  • + *
  • {@link ArchiveStreamFactory#ZIP}
  • + *
  • {@link ArchiveStreamFactory#SEVEN_Z}
  • + *
+ * + * @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; + } + } + + /** + * 创建归档解包器,支持: + *
    + *
  • {@link ArchiveStreamFactory#AR}
  • + *
  • {@link ArchiveStreamFactory#CPIO}
  • + *
  • {@link ArchiveStreamFactory#JAR}
  • + *
  • {@link ArchiveStreamFactory#TAR}
  • + *
  • {@link ArchiveStreamFactory#ZIP}
  • + *
  • {@link ArchiveStreamFactory#SEVEN_Z}
  • + *
+ * + * @param charset 编码,7z格式此参数无效 + * @param in 归档输入的流 + * @return {@link Extractor} + */ + public static Extractor createExtractor(Charset charset, InputStream in) { + return createExtractor(charset, null, in); + } + + /** + * 创建归档解包器,支持: + *
    + *
  • {@link ArchiveStreamFactory#AR}
  • + *
  • {@link ArchiveStreamFactory#CPIO}
  • + *
  • {@link ArchiveStreamFactory#JAR}
  • + *
  • {@link ArchiveStreamFactory#TAR}
  • + *
  • {@link ArchiveStreamFactory#ZIP}
  • + *
  • {@link ArchiveStreamFactory#SEVEN_Z}
  • + *
+ * + * @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; + } + } +} diff --git a/hutool-extra/src/main/java/cn/hutool/extra/compress/archiver/Archiver.java b/hutool-extra/src/main/java/cn/hutool/extra/compress/archiver/Archiver.java new file mode 100644 index 000000000..4211811ff --- /dev/null +++ b/hutool-extra/src/main/java/cn/hutool/extra/compress/archiver/Archiver.java @@ -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 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 filter); + + /** + * 结束已经增加的文件归档,此方法不会关闭归档流,可以继续添加文件 + * + * @return this + */ + Archiver finish(); + + /** + * 无异常关闭 + */ + @Override + void close(); +} diff --git a/hutool-extra/src/main/java/cn/hutool/extra/compress/archiver/SevenZArchiver.java b/hutool-extra/src/main/java/cn/hutool/extra/compress/archiver/SevenZArchiver.java new file mode 100644 index 000000000..4a926765b --- /dev/null +++ b/hutool-extra/src/main/java/cn/hutool/extra/compress/archiver/SevenZArchiver.java @@ -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 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 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(); + } + } +} diff --git a/hutool-extra/src/main/java/cn/hutool/extra/compress/archiver/StreamArchiver.java b/hutool-extra/src/main/java/cn/hutool/extra/compress/archiver/StreamArchiver.java new file mode 100644 index 000000000..60eb6a6f0 --- /dev/null +++ b/hutool-extra/src/main/java/cn/hutool/extra/compress/archiver/StreamArchiver.java @@ -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; + +/** + * 数据归档封装,归档即将几个文件或目录打成一个压缩包
+ * 支持的归档文件格式为: + *
    + *
  • {@link ArchiveStreamFactory#AR}
  • + *
  • {@link ArchiveStreamFactory#CPIO}
  • + *
  • {@link ArchiveStreamFactory#JAR}
  • + *
  • {@link ArchiveStreamFactory#TAR}
  • + *
  • {@link ArchiveStreamFactory#ZIP}
  • + *
+ * + * @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 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 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(); + } + } +} diff --git a/hutool-extra/src/main/java/cn/hutool/extra/compress/archiver/package-info.java b/hutool-extra/src/main/java/cn/hutool/extra/compress/archiver/package-info.java new file mode 100644 index 000000000..097f9a385 --- /dev/null +++ b/hutool-extra/src/main/java/cn/hutool/extra/compress/archiver/package-info.java @@ -0,0 +1,11 @@ +/** + * 基于commons-compress的打包(压缩)封装 + * + *

+ * 见:https://commons.apache.org/proper/commons-compress/ + *

+ * + * @author looly + * + */ +package cn.hutool.extra.compress.archiver; \ No newline at end of file diff --git a/hutool-extra/src/main/java/cn/hutool/extra/compress/extractor/Extractor.java b/hutool-extra/src/main/java/cn/hutool/extra/compress/extractor/Extractor.java new file mode 100644 index 000000000..aa2253674 --- /dev/null +++ b/hutool-extra/src/main/java/cn/hutool/extra/compress/extractor/Extractor.java @@ -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 filter); + + /** + * 无异常关闭 + */ + @Override + void close(); +} diff --git a/hutool-extra/src/main/java/cn/hutool/extra/compress/extractor/SenvenZExtractor.java b/hutool-extra/src/main/java/cn/hutool/extra/compress/extractor/SenvenZExtractor.java new file mode 100644 index 000000000..fd6ae48a6 --- /dev/null +++ b/hutool-extra/src/main/java/cn/hutool/extra/compress/extractor/SenvenZExtractor.java @@ -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 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 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); + } +} diff --git a/hutool-extra/src/main/java/cn/hutool/extra/compress/extractor/Seven7EntryInputStream.java b/hutool-extra/src/main/java/cn/hutool/extra/compress/extractor/Seven7EntryInputStream.java new file mode 100644 index 000000000..b3d768cee --- /dev/null +++ b/hutool-extra/src/main/java/cn/hutool/extra/compress/extractor/Seven7EntryInputStream.java @@ -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(); + } +} diff --git a/hutool-extra/src/main/java/cn/hutool/extra/compress/extractor/StreamExtractor.java b/hutool-extra/src/main/java/cn/hutool/extra/compress/extractor/StreamExtractor.java new file mode 100644 index 000000000..69e198c5b --- /dev/null +++ b/hutool-extra/src/main/java/cn/hutool/extra/compress/extractor/StreamExtractor.java @@ -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 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 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); + } +} diff --git a/hutool-extra/src/main/java/cn/hutool/extra/compress/extractor/package-info.java b/hutool-extra/src/main/java/cn/hutool/extra/compress/extractor/package-info.java new file mode 100644 index 000000000..676207e15 --- /dev/null +++ b/hutool-extra/src/main/java/cn/hutool/extra/compress/extractor/package-info.java @@ -0,0 +1,11 @@ +/** + * 基于commons-compress的解包(解压缩)封装 + * + *

+ * 见:https://commons.apache.org/proper/commons-compress/ + *

+ * + * @author looly + * + */ +package cn.hutool.extra.compress.extractor; \ No newline at end of file diff --git a/hutool-extra/src/main/java/cn/hutool/extra/compress/package-info.java b/hutool-extra/src/main/java/cn/hutool/extra/compress/package-info.java new file mode 100644 index 000000000..60482c213 --- /dev/null +++ b/hutool-extra/src/main/java/cn/hutool/extra/compress/package-info.java @@ -0,0 +1,13 @@ +/** + * 基于commons-compress的压缩解压封装
+ * 支持包括:gzip, bzip2, xz, lzma, Pack200, DEFLATE, Brotli, DEFLATE64, ZStandard and Z, the archiver formats are 7z,
+ * ar, arj, cpio, dump, tar and zip等格式。 + * + *

+ * 见:https://commons.apache.org/proper/commons-compress/ + *

+ * + * @author looly + * + */ +package cn.hutool.extra.compress; \ No newline at end of file diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ftp/SimpleFtpServer.java b/hutool-extra/src/main/java/cn/hutool/extra/ftp/SimpleFtpServer.java new file mode 100644 index 000000000..f317c6e3c --- /dev/null +++ b/hutool-extra/src/main/java/cn/hutool/extra/ftp/SimpleFtpServer.java @@ -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 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); + } + } +} diff --git a/hutool-extra/src/main/java/cn/hutool/extra/template/engine/freemarker/FreemarkerEngine.java b/hutool-extra/src/main/java/cn/hutool/extra/template/engine/freemarker/FreemarkerEngine.java index e46f0f0f5..c36bc7c21 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/template/engine/freemarker/FreemarkerEngine.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/template/engine/freemarker/FreemarkerEngine.java @@ -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模板引擎封装
+ * 见: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; } } diff --git a/hutool-extra/src/main/java/cn/hutool/extra/template/engine/freemarker/package-info.java b/hutool-extra/src/main/java/cn/hutool/extra/template/engine/freemarker/package-info.java index f4ac9bb43..a0f328711 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/template/engine/freemarker/package-info.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/template/engine/freemarker/package-info.java @@ -1,7 +1,7 @@ /** - * Freemarker实现 - * - * @author looly + * Freemarker实现
+ * 见:https://freemarker.apache.org/ * + * @author looly */ package cn.hutool.extra.template.engine.freemarker; \ No newline at end of file diff --git a/hutool-extra/src/main/java/cn/hutool/extra/template/engine/velocity/VelocityEngine.java b/hutool-extra/src/main/java/cn/hutool/extra/template/engine/velocity/VelocityEngine.java index 73a97efb1..3630b972c 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/template/engine/velocity/VelocityEngine.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/template/engine/velocity/VelocityEngine.java @@ -7,10 +7,10 @@ import cn.hutool.extra.template.TemplateEngine; import org.apache.velocity.app.Velocity; /** - * Velocity模板引擎 - * - * @author looly + * Velocity模板引擎
+ * 见: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(); diff --git a/hutool-extra/src/main/java/cn/hutool/extra/template/engine/velocity/package-info.java b/hutool-extra/src/main/java/cn/hutool/extra/template/engine/velocity/package-info.java index 933ed31da..f6eebce8f 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/template/engine/velocity/package-info.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/template/engine/velocity/package-info.java @@ -1,7 +1,7 @@ /** - * Velocity实现 - * - * @author looly + * Velocity实现
+ * 见:http://velocity.apache.org/ * + * @author looly */ package cn.hutool.extra.template.engine.velocity; \ No newline at end of file diff --git a/hutool-extra/src/main/java/cn/hutool/extra/validation/BeanValidationResult.java b/hutool-extra/src/main/java/cn/hutool/extra/validation/BeanValidationResult.java index 73e87eb61..54f9243dd 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/validation/BeanValidationResult.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/validation/BeanValidationResult.java @@ -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 + '}'; } } diff --git a/hutool-extra/src/main/java/cn/hutool/extra/validation/ValidationUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/validation/ValidationUtil.java index 72f9e304d..cb9a4cd1c 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/validation/ValidationUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/validation/ValidationUtil.java @@ -76,6 +76,7 @@ public class ValidationUtil { /** * 校验bean的某一个属性 * + * @param 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; } diff --git a/hutool-extra/src/main/java/cn/hutool/extra/validation/package-info.java b/hutool-extra/src/main/java/cn/hutool/extra/validation/package-info.java index fd0d5f94a..2107f1cc2 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/validation/package-info.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/validation/package-info.java @@ -1,6 +1,6 @@ /** - * 基于JSR-303标准的校验工具类,封装了javax.validation的API + * 基于JSR-380标准的校验工具类,封装了javax.validation的API * * @author chengqiang */ -package cn.hutool.extra.validation; \ No newline at end of file +package cn.hutool.extra.validation; diff --git a/hutool-extra/src/test/java/cn/hutool/extra/compress/ArchiverTest.java b/hutool-extra/src/test/java/cn/hutool/extra/compress/ArchiverTest.java new file mode 100644 index 000000000..d8ec5e4b7 --- /dev/null +++ b/hutool-extra/src/test/java/cn/hutool/extra/compress/ArchiverTest.java @@ -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(); + } +} diff --git a/hutool-extra/src/test/java/cn/hutool/extra/compress/ExtractorTest.java b/hutool-extra/src/test/java/cn/hutool/extra/compress/ExtractorTest.java new file mode 100644 index 000000000..ed2a03728 --- /dev/null +++ b/hutool-extra/src/test/java/cn/hutool/extra/compress/ExtractorTest.java @@ -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/")); + } +} diff --git a/hutool-extra/src/test/java/cn/hutool/extra/ftp/SimpleFtpServerTest.java b/hutool-extra/src/test/java/cn/hutool/extra/ftp/SimpleFtpServerTest.java new file mode 100644 index 000000000..9610c8a6e --- /dev/null +++ b/hutool-extra/src/test/java/cn/hutool/extra/ftp/SimpleFtpServerTest.java @@ -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(); + } +} diff --git a/hutool-http/pom.xml b/hutool-http/pom.xml index 4365b0976..c607532d7 100644 --- a/hutool-http/pom.xml +++ b/hutool-http/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.4.8-SNAPSHOT + 5.5.1-SNAPSHOT hutool-http diff --git a/hutool-json/pom.xml b/hutool-json/pom.xml index 478b359e9..de45a615f 100644 --- a/hutool-json/pom.xml +++ b/hutool-json/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.4.8-SNAPSHOT + 5.5.1-SNAPSHOT hutool-json diff --git a/hutool-log/pom.xml b/hutool-log/pom.xml index 551ad4e1d..6da95e8f2 100644 --- a/hutool-log/pom.xml +++ b/hutool-log/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.4.8-SNAPSHOT + 5.5.1-SNAPSHOT hutool-log @@ -21,7 +21,7 @@ 1.7.30 1.3.0-alpha5 1.2.17 - 2.13.3 + 2.14.0 1.2 1.3.6 3.4.1.Final diff --git a/hutool-poi/pom.xml b/hutool-poi/pom.xml index 156a0306d..7b89b04f3 100644 --- a/hutool-poi/pom.xml +++ b/hutool-poi/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.4.8-SNAPSHOT + 5.5.1-SNAPSHOT hutool-poi diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/BigExcelWriter.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/BigExcelWriter.java index 28a5e101c..8804aea1c 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/BigExcelWriter.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/BigExcelWriter.java @@ -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){ diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/RowUtil.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/RowUtil.java index c7a9f0d94..d2711a065 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/RowUtil.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/RowUtil.java @@ -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); } diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/CellUtil.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/CellUtil.java index 0d24577d7..2b71e08aa 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/CellUtil.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/CellUtil.java @@ -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 列号 diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/NullCell.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/NullCell.java new file mode 100644 index 000000000..ac750b061 --- /dev/null +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/cell/NullCell.java @@ -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!"); + } +} diff --git a/hutool-poi/src/test/java/cn/hutool/poi/excel/test/BigExcelWriteTest.java b/hutool-poi/src/test/java/cn/hutool/poi/excel/test/BigExcelWriteTest.java index b8447dd9d..04c2bf4a1 100644 --- a/hutool-poi/src/test/java/cn/hutool/poi/excel/test/BigExcelWriteTest.java +++ b/hutool-poi/src/test/java/cn/hutool/poi/excel/test/BigExcelWriteTest.java @@ -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> list = new ArrayList<>(); - list.add(new HashMap() {{ + list.add(new HashMap() { + private static final long serialVersionUID = 1L; + + { put("id", 1); put("userName", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); }}); - list.add(new HashMap() {{ + + list.add(new HashMap() { + private static final long serialVersionUID = 1L; + + { put("id", 2); put("userName", "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"); }}); diff --git a/hutool-poi/src/test/java/cn/hutool/poi/excel/test/ExcelReadTest.java b/hutool-poi/src/test/java/cn/hutool/poi/excel/test/ExcelReadTest.java index 6137f47de..1ed8526e1 100644 --- a/hutool-poi/src/test/java/cn/hutool/poi/excel/test/ExcelReadTest.java +++ b/hutool-poi/src/test/java/cn/hutool/poi/excel/test/ExcelReadTest.java @@ -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> 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)); + } } diff --git a/hutool-poi/src/test/resources/null_cell_test.xlsx b/hutool-poi/src/test/resources/null_cell_test.xlsx new file mode 100644 index 000000000..9329db568 Binary files /dev/null and b/hutool-poi/src/test/resources/null_cell_test.xlsx differ diff --git a/hutool-script/pom.xml b/hutool-script/pom.xml index caaaf2578..839985257 100644 --- a/hutool-script/pom.xml +++ b/hutool-script/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.4.8-SNAPSHOT + 5.5.1-SNAPSHOT hutool-script diff --git a/hutool-setting/pom.xml b/hutool-setting/pom.xml index 658a71733..b6311760c 100644 --- a/hutool-setting/pom.xml +++ b/hutool-setting/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.4.8-SNAPSHOT + 5.5.1-SNAPSHOT hutool-setting diff --git a/hutool-socket/pom.xml b/hutool-socket/pom.xml index 196d1091f..3ff398fe7 100644 --- a/hutool-socket/pom.xml +++ b/hutool-socket/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.4.8-SNAPSHOT + 5.5.1-SNAPSHOT hutool-socket diff --git a/hutool-system/pom.xml b/hutool-system/pom.xml index a8aff2038..2644ad9e8 100644 --- a/hutool-system/pom.xml +++ b/hutool-system/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.4.8-SNAPSHOT + 5.5.1-SNAPSHOT hutool-system @@ -26,7 +26,7 @@ com.github.oshi oshi-core - 5.3.1 + 5.3.5 provided diff --git a/pom.xml b/pom.xml index f59e802de..98e10c50b 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.4.8-SNAPSHOT + 5.5.1-SNAPSHOT hutool Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。 https://github.com/looly/hutool