From c646ba04efdcb5011b722f44f8fef04f3e8d3a57 Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 12 Nov 2020 00:35:49 +0800 Subject: [PATCH 01/20] fix convert --- CHANGELOG.md | 4 +- .../core/convert/impl/NumberConverter.java | 75 +++++---- .../core/convert/impl/PrimitiveConverter.java | 148 ++++-------------- .../java/cn/hutool/core/util/StrUtil.java | 2 +- .../cn/hutool/core/bean/BeanUtilTest.java | 1 + .../cn/hutool/core/convert/ConvertTest.java | 25 +++ .../java/cn/hutool/core/util/StrUtilTest.java | 1 + 7 files changed, 111 insertions(+), 145 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 983da7d72..e94076896 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ------------------------------------------------------------------------------------------------------------- -# 5.5.0 (2020-11-09) +# 5.5.0 (2020-11-11) ### 新特性 * 【core 】 NumberUtil.parseInt等支持123,2.00这类数字(issue#I23ORQ@Gitee) @@ -14,6 +14,7 @@ * 【poi 】 ExcelWriter增加setCurrentRowToEnd方法(issue#I24A2R@Gitee) * 【core 】 ExcelWriter增加setCurrentRowToEnd方法(issue#I24A2R@Gitee) * 【extra 】 增加表达式引擎封装(ExpressionUtil)(pr#1203@Github) +* 【core 】 增加enum转数字支持(issue#I24QZY@Gitee) ### Bug修复 * 【core 】 修复DateUtil.current使用System.nanoTime的问题(issue#1198@Github) @@ -24,6 +25,7 @@ * 【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) ------------------------------------------------------------------------------------------------------------- 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/util/StrUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/StrUtil.java index 891a41ca6..6ddb37c70 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 @@ -4019,7 +4019,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/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/util/StrUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/StrUtilTest.java index 260238a6c..3479a5d8a 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 From 36e599f70b65d1bfd26b359ab2d8d4fee4021c49 Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 12 Nov 2020 01:09:43 +0800 Subject: [PATCH 02/20] fix big excel writer bug --- CHANGELOG.md | 2 ++ .../java/cn/hutool/core/util/NumberUtil.java | 34 +++++++++++-------- .../cn/hutool/poi/excel/BigExcelWriter.java | 19 +++++++++++ .../poi/excel/test/BigExcelWriteTest.java | 32 +++++++++++++++++ 4 files changed, 73 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e94076896..b67dbbce0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ * 【core 】 ExcelWriter增加setCurrentRowToEnd方法(issue#I24A2R@Gitee) * 【extra 】 增加表达式引擎封装(ExpressionUtil)(pr#1203@Github) * 【core 】 增加enum转数字支持(issue#I24QZY@Gitee) +* 【core 】 NumberUtil.toBigDecimal空白符转换为0(issue#I24MRP@Gitee) ### Bug修复 * 【core 】 修复DateUtil.current使用System.nanoTime的问题(issue#1198@Github) @@ -26,6 +27,7 @@ * 【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) ------------------------------------------------------------------------------------------------------------- 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-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/test/java/cn/hutool/poi/excel/test/BigExcelWriteTest.java b/hutool-poi/src/test/java/cn/hutool/poi/excel/test/BigExcelWriteTest.java index addcd618d..6cf85898d 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 @@ -18,6 +18,7 @@ import org.junit.Test; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -217,4 +218,35 @@ public class BigExcelWriteTest { writer.write(data).flush(); } } + + @Test + @Ignore + public void issue1210() { + // 通过工具类创建writer + 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() { + private static final long serialVersionUID = 1L; + + { + put("id", 1); + put("userName", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); + }}); + + list.add(new HashMap() { + private static final long serialVersionUID = 1L; + + { + put("id", 2); + put("userName", "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"); + }}); + writer.write(list, true); + writer.autoSizeColumnAll(); + writer.close(); + } } From 80caef3135b1ef3140b814433268df54b861fd53 Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 12 Nov 2020 09:20:42 +0800 Subject: [PATCH 03/20] change readme --- README-EN.md | 5 +++++ README.md | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/README-EN.md b/README-EN.md index 518d10262..10205daec 100644 --- a/README-EN.md +++ b/README-EN.md @@ -45,6 +45,11 @@

+ +------------------------------------------------------------------------------- + +[**中文说明**](README.md) + ------------------------------------------------------------------------------- ## Introduction diff --git a/README.md b/README.md index 7c9f42d41..89d2545e3 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,10 @@ ------------------------------------------------------------------------------- +[**English Documentation**](README-EN.md) + +------------------------------------------------------------------------------- + ## 简介 Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。 From 36d6e75017cc49aeead032d68167a4300aa4a12c Mon Sep 17 00:00:00 2001 From: ZhongXin Date: Thu, 12 Nov 2020 11:33:54 +0800 Subject: [PATCH 04/20] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=AF=B9Collection?= =?UTF-8?q?=E5=92=8Citerator=E8=8E=B7=E5=8F=96size=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cn/hutool/core/collection/CollUtil.java | 49 +++++++++++++++++++ .../cn/hutool/core/collection/IterUtil.java | 34 +++++++++++++ 2 files changed, 83 insertions(+) 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..99b80a977 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 @@ -21,6 +21,7 @@ import cn.hutool.core.util.ReflectUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.TypeUtil; +import java.lang.reflect.Array; import java.lang.reflect.Type; import java.util.AbstractCollection; import java.util.ArrayList; @@ -2930,4 +2931,52 @@ public class CollUtil { void accept(K key, V value, int index); } // ---------------------------------------------------------------------------------------------- Interface end + + /** + * 获取Collection或者iterator的大小 + *

+ * 此方法可以处理的对象类型如下 + *

    + *
  • Collection - the collection size + *
  • Map - the map size + *
  • Array - the array size + *
  • Iterator - the number of elements remaining in the iterator + *
  • Enumeration - the number of elements remaining in the enumeration + *
+ * + * @param object 可以为空的对象 + * @return 如果object为空则返回0 + * @throws IllegalArgumentException 参数object不是Collection或者iterator + * @since 5.4.8 + */ + 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 Object[]) { + total = ((Object[]) object).length; + } 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 { + try { + total = Array.getLength(object); + } catch (IllegalArgumentException e) { + 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..b54718e55 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,37 @@ public class IterUtil { public static Iterator trans(Iterator iterator, Function function) { return new TransIter<>(iterator, function); } + + /** + * 返回 Iterable 对象的元素数量 + * + * @param iterable Iterable对象 + * @return Iterable对象的元素数量 + * @since 5.4.8 + */ + public static int size(final Iterable iterable) { + if (iterable instanceof Collection) { + return ((Collection) iterable).size(); + } else { + return size(iterable != null ? iterable.iterator() : empty()); + } + } + + /** + * 返回 Iterator 对象的元素数量 + * + * @param iterator Iterator对象 + * @return Iterator对象的元素数量 + * @since 5.4.8 + */ + public static int size(final Iterator iterator) { + int size = 0; + if (iterator != null) { + while (iterator.hasNext()) { + iterator.next(); + size++; + } + } + return size; + } } From 072a116b0b250438bb5d66b5afa62e7873cb9310 Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 12 Nov 2020 11:50:20 +0800 Subject: [PATCH 05/20] fix code --- hutool-core/src/main/java/cn/hutool/core/img/Img.java | 1 + 1 file changed, 1 insertion(+) 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; } From af118cd95352effd44a4a17ed72076e78c481abb Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 12 Nov 2020 16:03:31 +0800 Subject: [PATCH 06/20] add size method --- CHANGELOG.md | 1 + .../cn/hutool/core/collection/CollUtil.java | 52 ++++++++----------- .../cn/hutool/core/collection/IterUtil.java | 10 ++-- 3 files changed, 31 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b67dbbce0..d6a882a93 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ * 【extra 】 增加表达式引擎封装(ExpressionUtil)(pr#1203@Github) * 【core 】 增加enum转数字支持(issue#I24QZY@Gitee) * 【core 】 NumberUtil.toBigDecimal空白符转换为0(issue#I24MRP@Gitee) +* 【core 】 CollUtil和IterUtil增加size方法(pr#208@Gitee) ### Bug修复 * 【core 】 修复DateUtil.current使用System.nanoTime的问题(issue#1198@Github) 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 99b80a977..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 @@ -21,7 +21,6 @@ import cn.hutool.core.util.ReflectUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.TypeUtil; -import java.lang.reflect.Array; import java.lang.reflect.Type; import java.util.AbstractCollection; import java.util.ArrayList; @@ -1384,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); @@ -2932,36 +2931,33 @@ public class CollUtil { } // ---------------------------------------------------------------------------------------------- Interface end - /** - * 获取Collection或者iterator的大小 - *

- * 此方法可以处理的对象类型如下 - *

    - *
  • Collection - the collection size - *
  • Map - the map size - *
  • Array - the array size - *
  • Iterator - the number of elements remaining in the iterator - *
  • Enumeration - the number of elements remaining in the enumeration - *
- * - * @param object 可以为空的对象 - * @return 如果object为空则返回0 - * @throws IllegalArgumentException 参数object不是Collection或者iterator - * @since 5.4.8 - */ - public static int size(final Object object) { + /** + * 获取Collection或者iterator的大小,此方法可以处理的对象类型如下: + *
    + *
  • Collection - the collection size + *
  • Map - the map size + *
  • Array - the array size + *
  • Iterator - the number of elements remaining in the iterator + *
  • Enumeration - the number of elements remaining in the enumeration + *
+ * + * @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) { + 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 Object[]) { - total = ((Object[]) object).length; } else if (object instanceof Iterator) { total = IterUtil.size((Iterator) object); } else if (object instanceof Enumeration) { @@ -2970,13 +2966,11 @@ public class CollUtil { total++; it.nextElement(); } + } else if (ArrayUtil.isArray(object)) { + total = ArrayUtil.length(object); } else { - try { - total = Array.getLength(object); - } catch (IllegalArgumentException e) { - throw new IllegalArgumentException("Unsupported object type: " + object.getClass().getName()); - } + 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 b54718e55..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 @@ -828,13 +828,17 @@ public class IterUtil { * * @param iterable Iterable对象 * @return Iterable对象的元素数量 - * @since 5.4.8 + * @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 != null ? iterable.iterator() : empty()); + return size(iterable.iterator()); } } @@ -843,7 +847,7 @@ public class IterUtil { * * @param iterator Iterator对象 * @return Iterator对象的元素数量 - * @since 5.4.8 + * @since 5.5.0 */ public static int size(final Iterator iterator) { int size = 0; From cd1e9fbaadd2d9b3035a02a646d28b4bc2e50137 Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 12 Nov 2020 19:07:05 +0800 Subject: [PATCH 07/20] add SimpleFtpServer --- CHANGELOG.md | 1 + hutool-extra/pom.xml | 13 ++ .../cn/hutool/extra/ftp/SimpleFtpServer.java | 221 ++++++++++++++++++ .../hutool/extra/ftp/SimpleFtpServerTest.java | 11 + 4 files changed, 246 insertions(+) create mode 100644 hutool-extra/src/main/java/cn/hutool/extra/ftp/SimpleFtpServer.java create mode 100644 hutool-extra/src/test/java/cn/hutool/extra/ftp/SimpleFtpServerTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index d6a882a93..d38d14e5b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ * 【core 】 增加enum转数字支持(issue#I24QZY@Gitee) * 【core 】 NumberUtil.toBigDecimal空白符转换为0(issue#I24MRP@Gitee) * 【core 】 CollUtil和IterUtil增加size方法(pr#208@Gitee) +* 【extra 】 新增SimpleFtpServer ### Bug修复 * 【core 】 修复DateUtil.current使用System.nanoTime的问题(issue#1198@Github) diff --git a/hutool-extra/pom.xml b/hutool-extra/pom.xml index 8b0a57b26..2f448e775 100644 --- a/hutool-extra/pom.xml +++ b/hutool-extra/pom.xml @@ -146,6 +146,19 @@ compile true + + org.apache.ftpserver + ftpserver-core + 1.1.1 + compile + + + slf4j-api + org.slf4j + + + true + com.vdurmont 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..cc8c0341d --- /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 { + + FtpServerFactory serverFactory; + ListenerFactory listenerFactory; + + /** + * 构造 + */ + public SimpleFtpServer() { + serverFactory = new FtpServerFactory(); + listenerFactory = new ListenerFactory(); + } + + /** + * 创建FTP服务器,调用{@link SimpleFtpServer#start()}启动即可 + * + * @return SimpleFtpServer + */ + public static SimpleFtpServer create() { + return new SimpleFtpServer(); + } + + /** + * 获取 {@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/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(); + } +} From 4b3941b2f81571a73cb57a4a07aede1ae38720c8 Mon Sep 17 00:00:00 2001 From: Looly Date: Fri, 13 Nov 2020 02:45:10 +0800 Subject: [PATCH 08/20] add compress --- CHANGELOG.md | 1 + hutool-extra/pom.xml | 13 ++ .../extra/compress/CompressException.java | 33 ++++ .../hutool/extra/compress/CompressUtil.java | 66 +++++++ .../cn/hutool/extra/compress/Extractor.java | 4 + .../extra/compress/archiver/Archiver.java | 59 ++++++ .../compress/archiver/SevenZArchiver.java | 136 ++++++++++++++ .../compress/archiver/StreamArchiver.java | 169 ++++++++++++++++++ .../hutool/extra/compress/package-info.java | 13 ++ .../cn/hutool/extra/ftp/SimpleFtpServer.java | 18 +- .../hutool/extra/compress/ArchiverTest.java | 50 ++++++ 11 files changed, 553 insertions(+), 9 deletions(-) create mode 100644 hutool-extra/src/main/java/cn/hutool/extra/compress/CompressException.java create mode 100644 hutool-extra/src/main/java/cn/hutool/extra/compress/CompressUtil.java create mode 100644 hutool-extra/src/main/java/cn/hutool/extra/compress/Extractor.java create mode 100644 hutool-extra/src/main/java/cn/hutool/extra/compress/archiver/Archiver.java create mode 100644 hutool-extra/src/main/java/cn/hutool/extra/compress/archiver/SevenZArchiver.java create mode 100644 hutool-extra/src/main/java/cn/hutool/extra/compress/archiver/StreamArchiver.java create mode 100644 hutool-extra/src/main/java/cn/hutool/extra/compress/package-info.java create mode 100644 hutool-extra/src/test/java/cn/hutool/extra/compress/ArchiverTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index d38d14e5b..da5e4695e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ * 【core 】 NumberUtil.toBigDecimal空白符转换为0(issue#I24MRP@Gitee) * 【core 】 CollUtil和IterUtil增加size方法(pr#208@Gitee) * 【extra 】 新增SimpleFtpServer +* 【extra 】 新增CompressUtil压缩封装 ### Bug修复 * 【core 】 修复DateUtil.current使用System.nanoTime的问题(issue#1198@Github) diff --git a/hutool-extra/pom.xml b/hutool-extra/pom.xml index 2f448e775..88db4ba94 100644 --- a/hutool-extra/pom.xml +++ b/hutool-extra/pom.xml @@ -411,5 +411,18 @@ 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..5924ec054 --- /dev/null +++ b/hutool-extra/src/main/java/cn/hutool/extra/compress/CompressUtil.java @@ -0,0 +1,66 @@ +package cn.hutool.extra.compress; + +import cn.hutool.extra.compress.archiver.Archiver; +import cn.hutool.extra.compress.archiver.SevenZArchiver; +import cn.hutool.extra.compress.archiver.StreamArchiver; +import org.apache.commons.compress.archivers.ArchiveStreamFactory; + +import java.io.File; +import java.io.OutputStream; +import java.nio.charset.Charset; + +/** + * 压缩工具类
+ * 基于commons-compress的压缩解压封装 + * + * @since 5.5.0 + * @author looly + */ +public class CompressUtil { + + /** + * 创建归档器,支持: + *
    + *
  • {@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); + } +} diff --git a/hutool-extra/src/main/java/cn/hutool/extra/compress/Extractor.java b/hutool-extra/src/main/java/cn/hutool/extra/compress/Extractor.java new file mode 100644 index 000000000..7819de07f --- /dev/null +++ b/hutool-extra/src/main/java/cn/hutool/extra/compress/Extractor.java @@ -0,0 +1,4 @@ +package cn.hutool.extra.compress; + +public class Extractor { +} 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..c13f3a4e7 --- /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.util.StrUtil; + +import java.io.Closeable; +import java.io.File; +import java.io.FileFilter; + +/** + * 数据归档封装,归档即将几个文件或目录打成一个压缩包
+ * + * @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 FileFilter#accept(File)}为true时加入。 + * @return this + */ + default Archiver add(File file, FileFilter filter) { + return add(file, StrUtil.SLASH, filter); + } + + /** + * 将文件或目录加入归档包,目录采取递归读取方式按照层级加入 + * + * @param file 文件或目录 + * @param path 文件或目录的初始路径,null表示位于根路径 + * @param filter 文件过滤器,指定哪些文件或目录可以加入,当{@link FileFilter#accept(File)}为true时加入。 + * @return this + */ + Archiver add(File file, String path, FileFilter 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..9612defd7 --- /dev/null +++ b/hutool-extra/src/main/java/cn/hutool/extra/compress/archiver/SevenZArchiver.java @@ -0,0 +1,136 @@ +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.util.StrUtil; +import org.apache.commons.compress.archivers.sevenz.SevenZOutputFile; +import org.apache.commons.compress.utils.SeekableInMemoryByteChannel; + +import java.io.File; +import java.io.FileFilter; +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); + } + } + + @Override + public SevenZArchiver add(File file, String path, FileFilter 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 FileFilter#accept(File)}为true时加入。 + */ + private void addInternal(File file, String path, FileFilter 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..fb2feac82 --- /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.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.FileFilter; +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 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 FileFilter#accept(File)}为true时加入。 + * @return this + * @throws IORuntimeException IO异常 + */ + @Override + public StreamArchiver add(File file, String path, FileFilter 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 FileFilter#accept(File)}为true时加入。 + */ + private void addInternal(File file, String path, FileFilter 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/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 index cc8c0341d..f317c6e3c 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/ftp/SimpleFtpServer.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/ftp/SimpleFtpServer.java @@ -27,6 +27,15 @@ import java.util.List; */ public class SimpleFtpServer { + /** + * 创建FTP服务器,调用{@link SimpleFtpServer#start()}启动即可 + * + * @return SimpleFtpServer + */ + public static SimpleFtpServer create() { + return new SimpleFtpServer(); + } + FtpServerFactory serverFactory; ListenerFactory listenerFactory; @@ -38,15 +47,6 @@ public class SimpleFtpServer { listenerFactory = new ListenerFactory(); } - /** - * 创建FTP服务器,调用{@link SimpleFtpServer#start()}启动即可 - * - * @return SimpleFtpServer - */ - public static SimpleFtpServer create() { - return new SimpleFtpServer(); - } - /** * 获取 {@link FtpServerFactory},用于设置FTP服务器相关信息 * 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..0b0ee0918 --- /dev/null +++ b/hutool-extra/src/test/java/cn/hutool/extra/compress/ArchiverTest.java @@ -0,0 +1,50 @@ +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 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"), (f)->{ + Console.log("Add: {}", f.getPath()); + return true; + }) + .finish().close(); + } +} From 1a0c9a3b515c0f8b4b0f13b7714d61b1377227e8 Mon Sep 17 00:00:00 2001 From: Looly Date: Fri, 13 Nov 2020 02:49:23 +0800 Subject: [PATCH 09/20] fix comment --- .../src/main/java/cn/hutool/extra/validation/ValidationUtil.java | 1 + 1 file changed, 1 insertion(+) 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..0c0f91a05 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 验证分组 From 99eeb30ffeba8b4c13b2386eb4b5344934201f93 Mon Sep 17 00:00:00 2001 From: Looly Date: Fri, 13 Nov 2020 17:54:42 +0800 Subject: [PATCH 10/20] add code --- .../main/java/cn/hutool/core/io/FileUtil.java | 33 +++++++- .../java/cn/hutool/core/util/ZipUtil.java | 32 +------- .../cn/hutool/extra/compress/Extractor.java | 4 - .../extra/compress/archiver/Archiver.java | 2 +- .../compress/archiver/StreamArchiver.java | 2 +- .../extra/compress/archiver/package-info.java | 11 +++ .../extra/compress/extractor/Extractor.java | 11 +++ .../compress/extractor/StreamExtractor.java | 69 +++++++++++++++++ .../compress/extractor/package-info.java | 11 +++ .../engine/freemarker/FreemarkerEngine.java | 76 ++++++++++--------- .../engine/freemarker/package-info.java | 6 +- .../engine/velocity/VelocityEngine.java | 75 +++++++++--------- .../engine/velocity/package-info.java | 6 +- 13 files changed, 222 insertions(+), 116 deletions(-) delete mode 100644 hutool-extra/src/main/java/cn/hutool/extra/compress/Extractor.java create mode 100644 hutool-extra/src/main/java/cn/hutool/extra/compress/archiver/package-info.java create mode 100644 hutool-extra/src/main/java/cn/hutool/extra/compress/extractor/Extractor.java create mode 100644 hutool-extra/src/main/java/cn/hutool/extra/compress/extractor/StreamExtractor.java create mode 100644 hutool-extra/src/main/java/cn/hutool/extra/compress/extractor/package-info.java 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..04f8c5b26 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)); } /** @@ -3277,4 +3278,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/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-extra/src/main/java/cn/hutool/extra/compress/Extractor.java b/hutool-extra/src/main/java/cn/hutool/extra/compress/Extractor.java deleted file mode 100644 index 7819de07f..000000000 --- a/hutool-extra/src/main/java/cn/hutool/extra/compress/Extractor.java +++ /dev/null @@ -1,4 +0,0 @@ -package cn.hutool.extra.compress; - -public class Extractor { -} 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 index c13f3a4e7..e91117296 100644 --- 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 @@ -7,7 +7,7 @@ import java.io.File; import java.io.FileFilter; /** - * 数据归档封装,归档即将几个文件或目录打成一个压缩包
+ * 数据归档封装,归档即将几个文件或目录打成一个压缩包 * * @author looly */ 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 index fb2feac82..6c01b8fe0 100644 --- 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 @@ -56,7 +56,7 @@ public class StreamArchiver implements Archiver { return new StreamArchiver(charset, archiverName, out); } - private ArchiveOutputStream out; + private final ArchiveOutputStream out; /** * 构造 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..2ff894107 --- /dev/null +++ b/hutool-extra/src/main/java/cn/hutool/extra/compress/extractor/Extractor.java @@ -0,0 +1,11 @@ +package cn.hutool.extra.compress.extractor; + +/** + * 数据解包封装,用于将zip、tar等包解包为文件 + * + * @author looly + * @since 5.5.0 + */ +public interface Extractor { + +} 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..338417d34 --- /dev/null +++ b/hutool-extra/src/main/java/cn/hutool/extra/compress/extractor/StreamExtractor.java @@ -0,0 +1,69 @@ +package cn.hutool.extra.compress.extractor; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.lang.Assert; +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; + +public class StreamExtractor { + + private final ArchiveInputStream in; + + public StreamExtractor(Charset charset, InputStream in) { + final ArchiveStreamFactory factory = new ArchiveStreamFactory(charset.name()); + try { + this.in = factory.createArchiveInputStream(in); + } catch (ArchiveException e) { + throw new CompressException(e); + } + } + + /** + * 释放(解压)到指定目录 + * + * @param targetDir 目标目录 + */ + public void extract(File targetDir) { + try { + extractInternal(targetDir); + } catch (IOException e) { + throw new IORuntimeException(e); + } + } + + /** + * 释放(解压)到指定目录 + * + * @param targetDir 目标目录 + * @throws IOException IO异常 + */ + private void extractInternal(File targetDir) throws IOException { + Assert.isTrue(null != targetDir && targetDir.isDirectory(), "target must be dir."); + final ArchiveInputStream in = this.in; + ArchiveEntry entry; + File outItemFile; + while(null != (entry = this.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); + } + } + } +} 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/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 From 71ab8335cedd030d27307e0e2b53c3840b43928a Mon Sep 17 00:00:00 2001 From: Looly Date: Sat, 14 Nov 2020 00:54:37 +0800 Subject: [PATCH 11/20] change name --- .../cn/hutool/core/date/BetweenFormater.java | 202 +--------------- .../cn/hutool/core/date/BetweenFormatter.java | 215 ++++++++++++++++++ .../java/cn/hutool/core/date/DateBetween.java | 4 +- .../java/cn/hutool/core/date/DateTime.java | 2 +- .../java/cn/hutool/core/date/DateUtil.java | 8 +- .../hutool/core/date/BetweenFormaterTest.java | 8 +- .../cn/hutool/core/date/DateBetweenTest.java | 2 +- .../cn/hutool/core/date/DateUtilTest.java | 2 +- 8 files changed, 235 insertions(+), 208 deletions(-) create mode 100644 hutool-core/src/main/java/cn/hutool/core/date/BetweenFormatter.java 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/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; From e0250968963dd533252c2cf81901d673843ae915 Mon Sep 17 00:00:00 2001 From: Looly Date: Sat, 14 Nov 2020 03:09:13 +0800 Subject: [PATCH 12/20] add 7zip --- CHANGELOG.md | 2 +- .../hutool/extra/compress/CompressUtil.java | 109 +++++++++++++- .../extra/compress/archiver/Archiver.java | 10 +- .../compress/archiver/SevenZArchiver.java | 8 +- .../compress/archiver/StreamArchiver.java | 10 +- .../extra/compress/extractor/Extractor.java | 32 +++- .../compress/extractor/SenvenZExtractor.java | 142 ++++++++++++++++++ .../extractor/Seven7EntryInputStream.java | 45 ++++++ .../compress/extractor/StreamExtractor.java | 82 ++++++++-- .../hutool/extra/compress/ArchiverTest.java | 14 +- .../hutool/extra/compress/ExtractorTest.java | 30 ++++ 11 files changed, 454 insertions(+), 30 deletions(-) create mode 100644 hutool-extra/src/main/java/cn/hutool/extra/compress/extractor/SenvenZExtractor.java create mode 100644 hutool-extra/src/main/java/cn/hutool/extra/compress/extractor/Seven7EntryInputStream.java create mode 100644 hutool-extra/src/test/java/cn/hutool/extra/compress/ExtractorTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index da5e4695e..00729e860 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ------------------------------------------------------------------------------------------------------------- -# 5.5.0 (2020-11-11) +# 5.5.0 (2020-11-14) ### 新特性 * 【core 】 NumberUtil.parseInt等支持123,2.00这类数字(issue#I23ORQ@Gitee) 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 index 5924ec054..07db936c7 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/compress/CompressUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/compress/CompressUtil.java @@ -3,9 +3,14 @@ package cn.hutool.extra.compress; 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 java.io.File; +import java.io.InputStream; import java.io.OutputStream; import java.nio.charset.Charset; @@ -13,8 +18,8 @@ import java.nio.charset.Charset; * 压缩工具类
* 基于commons-compress的压缩解压封装 * - * @since 5.5.0 * @author looly + * @since 5.5.0 */ public class CompressUtil { @@ -54,7 +59,7 @@ public class CompressUtil { * * @param charset 编码 * @param archiverName 归档类型名称,见{@link ArchiveStreamFactory} - * @param out 归档输出的流 + * @param out 归档输出的流 * @return Archiver */ public static Archiver createArchiver(Charset charset, String archiverName, OutputStream out) { @@ -63,4 +68,104 @@ public class CompressUtil { } 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 index e91117296..4211811ff 100644 --- 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 @@ -1,10 +1,10 @@ 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; -import java.io.FileFilter; /** * 数据归档封装,归档即将几个文件或目录打成一个压缩包 @@ -27,10 +27,10 @@ public interface Archiver extends Closeable { * 将文件或目录加入归档,目录采取递归读取方式按照层级加入 * * @param file 文件或目录 - * @param filter 文件过滤器,指定哪些文件或目录可以加入,当{@link FileFilter#accept(File)}为true时加入。 + * @param filter 文件过滤器,指定哪些文件或目录可以加入,当{@link Filter#accept(Object)}为true时加入。 * @return this */ - default Archiver add(File file, FileFilter filter) { + default Archiver add(File file, Filter filter) { return add(file, StrUtil.SLASH, filter); } @@ -39,10 +39,10 @@ public interface Archiver extends Closeable { * * @param file 文件或目录 * @param path 文件或目录的初始路径,null表示位于根路径 - * @param filter 文件过滤器,指定哪些文件或目录可以加入,当{@link FileFilter#accept(File)}为true时加入。 + * @param filter 文件过滤器,指定哪些文件或目录可以加入,当{@link Filter#accept(Object)}为true时加入。 * @return this */ - Archiver add(File file, String path, FileFilter filter); + Archiver add(File file, String path, Filter filter); /** * 结束已经增加的文件归档,此方法不会关闭归档流,可以继续添加文件 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 index 9612defd7..9f371a9c6 100644 --- 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 @@ -3,12 +3,12 @@ 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.FileFilter; import java.io.IOException; import java.io.OutputStream; import java.nio.channels.SeekableByteChannel; @@ -67,7 +67,7 @@ public class SevenZArchiver implements Archiver { } @Override - public SevenZArchiver add(File file, String path, FileFilter filter) { + public SevenZArchiver add(File file, String path, Filter filter) { try { addInternal(file, path, filter); } catch (IOException e) { @@ -108,9 +108,9 @@ public class SevenZArchiver implements Archiver { * * @param file 文件或目录 * @param path 文件或目录的初始路径,null表示位于根路径 - * @param filter 文件过滤器,指定哪些文件或目录可以加入,当{@link FileFilter#accept(File)}为true时加入。 + * @param filter 文件过滤器,指定哪些文件或目录可以加入,当{@link Filter#accept(Object)}为true时加入。 */ - private void addInternal(File file, String path, FileFilter filter) throws IOException { + private void addInternal(File file, String path, Filter filter) throws IOException { if (null != filter && false == filter.accept(file)) { return; } 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 index 6c01b8fe0..60eb6a6f0 100644 --- 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 @@ -3,6 +3,7 @@ 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; @@ -12,7 +13,6 @@ import org.apache.commons.compress.archivers.ar.ArArchiveOutputStream; import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream; import java.io.File; -import java.io.FileFilter; import java.io.IOException; import java.io.OutputStream; import java.nio.charset.Charset; @@ -97,12 +97,12 @@ public class StreamArchiver implements Archiver { * * @param file 文件或目录 * @param path 文件或目录的初始路径,null表示位于根路径 - * @param filter 文件过滤器,指定哪些文件或目录可以加入,当{@link FileFilter#accept(File)}为true时加入。 + * @param filter 文件过滤器,指定哪些文件或目录可以加入,当{@link Filter#accept(Object)}为true时加入。 * @return this * @throws IORuntimeException IO异常 */ @Override - public StreamArchiver add(File file, String path, FileFilter filter) throws IORuntimeException { + public StreamArchiver add(File file, String path, Filter filter) throws IORuntimeException { try { addInternal(file, path, filter); } catch (IOException e) { @@ -141,9 +141,9 @@ public class StreamArchiver implements Archiver { * * @param file 文件或目录 * @param path 文件或目录的初始路径,null表示位于根路径 - * @param filter 文件过滤器,指定哪些文件或目录可以加入,当{@link FileFilter#accept(File)}为true时加入。 + * @param filter 文件过滤器,指定哪些文件或目录可以加入,当{@link Filter#accept(Object)}为true时加入。 */ - private void addInternal(File file, String path, FileFilter filter) throws IOException { + private void addInternal(File file, String path, Filter filter) throws IOException { if (null != filter && false == filter.accept(file)) { return; } 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 index 2ff894107..aa2253674 100644 --- 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 @@ -1,11 +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等包解包为文件 + * 归档数据解包封装,用于将zip、tar等包解包为文件 * * @author looly * @since 5.5.0 */ -public interface Extractor { +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 index 338417d34..69e198c5b 100644 --- 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 @@ -2,7 +2,10 @@ 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; @@ -14,29 +17,82 @@ import java.io.IOException; import java.io.InputStream; import java.nio.charset.Charset; -public class StreamExtractor { +/** + * 数据解压器,即将归档打包的数据释放 + * + * @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 { - this.in = factory.createArchiveInputStream(in); + 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时释放。 */ - public void extract(File targetDir) { + @Override + public void extract(File targetDir, Filter filter) { try { - extractInternal(targetDir); + extractInternal(targetDir, filter); } catch (IOException e) { throw new IORuntimeException(e); + } finally { + close(); } } @@ -44,20 +100,21 @@ public class StreamExtractor { * 释放(解压)到指定目录 * * @param targetDir 目标目录 + * @param filter 解压文件过滤器,用于指定需要释放的文件,null表示不过滤。当{@link Filter#accept(Object)}为true时释放。 * @throws IOException IO异常 */ - private void extractInternal(File targetDir) throws IOException { - Assert.isTrue(null != targetDir && targetDir.isDirectory(), "target must be dir."); + 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 = this.in.getNextEntry())){ - if(false == in.canReadEntryData(entry)){ + while (null != (entry = in.getNextEntry())) { + if (false == in.canReadEntryData(entry)) { // 无法读取的文件直接跳过 continue; } outItemFile = FileUtil.file(targetDir, entry.getName()); - if(entry.isDirectory()){ + if (entry.isDirectory()) { // 创建对应目录 //noinspection ResultOfMethodCallIgnored outItemFile.mkdirs(); @@ -66,4 +123,9 @@ public class StreamExtractor { } } } + + @Override + public void close() { + IoUtil.close(this.in); + } } 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 index 0b0ee0918..d8ec5e4b7 100644 --- a/hutool-extra/src/test/java/cn/hutool/extra/compress/ArchiverTest.java +++ b/hutool-extra/src/test/java/cn/hutool/extra/compress/ArchiverTest.java @@ -12,6 +12,18 @@ 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(){ @@ -41,7 +53,7 @@ public class ArchiverTest { 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"), (f)->{ + .add(FileUtil.file("d:/Java/apache-maven-3.6.3"), (f)->{ Console.log("Add: {}", f.getPath()); return true; }) 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/")); + } +} From 03fd34b039f547d82cab7638a2ce4d3f520d37a8 Mon Sep 17 00:00:00 2001 From: gotanks Date: Sat, 14 Nov 2020 09:48:40 +0800 Subject: [PATCH 13/20] =?UTF-8?q?StrUtil=E5=B7=A5=E5=85=B7=E7=B1=BB?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0subBetweenAl(str,=20beforeAndAfter)=E6=96=B9?= =?UTF-8?q?=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/cn/hutool/core/util/StrUtil.java | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) 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 6ddb37c70..2b61b7a5e 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 @@ -2415,6 +2415,43 @@ public class StrUtil { 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 beforeAndAfter 截取开始和结束的字符串标识 + * @return 截取后的字符串 + * @author gotanks + * @since 5.4.7 + */ + public static String[] subBetweenAll(CharSequence str, CharSequence beforeAndAfter) { + String[] resultArr = new String[0]; + if (hasEmpty(str, beforeAndAfter) || !contains(str, beforeAndAfter)) { + return resultArr; + } + + final List result = new LinkedList<>(); + String[] split = split(str, beforeAndAfter); + for (int i = 1, length = split.length - 1; i < length; i = i + 2) { + result.add(split[i]); + } + return result.toArray(resultArr); + } + /** * 给定字符串是否被字符包围 * From eb65d42afe7afb050d0e50fca9c4840cd3beafbe Mon Sep 17 00:00:00 2001 From: chengqiang <437004246@qq.com> Date: Sat, 14 Nov 2020 22:33:00 +0800 Subject: [PATCH 14/20] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=8C=85=E6=8F=8F?= =?UTF-8?q?=E8=BF=B0,=E5=A2=9E=E5=8A=A0=E9=94=99=E8=AF=AF=E5=AD=97?= =?UTF-8?q?=E6=AE=B5=E8=BF=94=E5=9B=9E=E5=80=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../extra/validation/BeanValidationResult.java | 15 ++++++++++++++- .../hutool/extra/validation/ValidationUtil.java | 3 ++- .../cn/hutool/extra/validation/package-info.java | 4 ++-- 3 files changed, 18 insertions(+), 4 deletions(-) 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 0c0f91a05..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 @@ -98,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; From d8fd1e16aa3231a20a8cbd113be03f52b2e50d2a Mon Sep 17 00:00:00 2001 From: Looly Date: Sun, 15 Nov 2020 00:36:07 +0800 Subject: [PATCH 15/20] add null edit --- CHANGELOG.md | 11 +- .../java/cn/hutool/core/util/StrUtilTest.java | 9 + .../hutool/extra/compress/CompressUtil.java | 74 +++++- .../compress/archiver/SevenZArchiver.java | 13 +- .../java/cn/hutool/poi/excel/RowUtil.java | 2 +- .../cn/hutool/poi/excel/cell/CellUtil.java | 26 +- .../cn/hutool/poi/excel/cell/NullCell.java | 240 ++++++++++++++++++ .../hutool/poi/excel/test/ExcelReadTest.java | 17 ++ .../src/test/resources/null_cell_test.xlsx | Bin 0 -> 8789 bytes 9 files changed, 374 insertions(+), 18 deletions(-) create mode 100644 hutool-poi/src/main/java/cn/hutool/poi/excel/cell/NullCell.java create mode 100644 hutool-poi/src/test/resources/null_cell_test.xlsx diff --git a/CHANGELOG.md b/CHANGELOG.md index 00729e860..58d538385 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,20 +5,23 @@ # 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) -* 【extra 】 新增SimpleFtpServer -* 【extra 】 新增CompressUtil压缩封装 +* 【poi 】 ExcelReader的read方法读取空单元格增加CellEditor处理(issue#1213@Github) ### Bug修复 * 【core 】 修复DateUtil.current使用System.nanoTime的问题(issue#1198@Github) 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 3479a5d8a..8cbdb7eb2 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 @@ -1,5 +1,6 @@ package cn.hutool.core.util; +import cn.hutool.core.lang.Console; import cn.hutool.core.lang.Dict; import org.junit.Assert; import org.junit.Test; @@ -433,6 +434,14 @@ public class StrUtilTest { Assert.assertEquals(0, results2.length); } + @Test + public void subBetweenAllTest3() { + String src1 = "'abc'and'123'"; + + final String[] strings = StrUtil.subBetweenAll(src1, "'", "'"); + Console.log(strings); + } + @Test public void briefTest() { String str = RandomUtil.randomString(1000); 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 index 07db936c7..fdd0cda3c 100644 --- a/hutool-extra/src/main/java/cn/hutool/extra/compress/CompressUtil.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/compress/CompressUtil.java @@ -1,5 +1,7 @@ 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; @@ -8,6 +10,10 @@ 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; @@ -23,6 +29,62 @@ import java.nio.charset.Charset; */ 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); + } + } + /** * 创建归档器,支持: *
    @@ -108,11 +170,11 @@ public class CompressUtil { if (ArchiveStreamFactory.SEVEN_Z.equalsIgnoreCase(archiverName)) { return new SenvenZExtractor(file); } - try{ + try { return new StreamExtractor(charset, archiverName, file); - } catch (CompressException e){ + } catch (CompressException e) { final Throwable cause = e.getCause(); - if(cause instanceof StreamingNotSupportedException && cause.getMessage().contains("7z")){ + if (cause instanceof StreamingNotSupportedException && cause.getMessage().contains("7z")) { return new SenvenZExtractor(file); } throw e; @@ -158,11 +220,11 @@ public class CompressUtil { if (ArchiveStreamFactory.SEVEN_Z.equalsIgnoreCase(archiverName)) { return new SenvenZExtractor(in); } - try{ + try { return new StreamExtractor(charset, archiverName, in); - } catch (CompressException e){ + } catch (CompressException e) { final Throwable cause = e.getCause(); - if(cause instanceof StreamingNotSupportedException && cause.getMessage().contains("7z")){ + 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/SevenZArchiver.java b/hutool-extra/src/main/java/cn/hutool/extra/compress/archiver/SevenZArchiver.java index 9f371a9c6..4a926765b 100644 --- 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 @@ -66,6 +66,15 @@ public class SevenZArchiver implements Archiver { } } + /** + * 获取{@link SevenZOutputFile}以便自定义相关设置 + * + * @return {@link SevenZOutputFile} + */ + public SevenZOutputFile getSevenZOutputFile() { + return this.sevenZOutputFile; + } + @Override public SevenZArchiver add(File file, String path, Filter filter) { try { @@ -93,9 +102,9 @@ public class SevenZArchiver implements Archiver { } catch (Exception ignore) { //ignore } - if(null != out && this.channel instanceof SeekableInMemoryByteChannel){ + if (null != out && this.channel instanceof SeekableInMemoryByteChannel) { try { - out.write(((SeekableInMemoryByteChannel)this.channel).array()); + out.write(((SeekableInMemoryByteChannel) this.channel).array()); } catch (IOException e) { throw new IORuntimeException(e); } 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/ExcelReadTest.java b/hutool-poi/src/test/java/cn/hutool/poi/excel/test/ExcelReadTest.java index 6137f47de..459348ec1 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; @@ -201,4 +202,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 0000000000000000000000000000000000000000..9329db5683612a1dd3726ef70a6737991a571316 GIT binary patch literal 8789 zcmeHNg%CmR?;m*YncvKr^P98Rob$|D``ORhqoM#r0Rzwg7ytm^4nTt6Z`c+I06;+n00;pX z$a+$CwvMK@jz$`;_NGt+b{89K>P!@5rgQ)@;{1QN|KSlR(CD>o&-Vl>+(hA84 z&dbA~sH4!MTStHG67!g@YL%4f(v#6lRkIYMm0s0)SdZaQgrsnmK}S-2*_!=aN15&k z*8A{6hyD9k57T7)vJ14r%cH{XrIX<7?xKxXqCcvcHDs?FXRY->>8oTu zOiYTtk@-s=^0(>D`@L;C$O?B~k{AoP_^Yvz$$!#rXi(+m(V{DU3#HU#k2?*&3occu ztvH9sZS;+S(+x_WlL|>e#4QSta&5RF*gQd)_@!Ln18Q?0G!EMNy1p9jTkW$h9#1x9 zN$OITi>h84T?+e+M!nKZSDq+yuC-i=#5zoZ9&YjWyekZ0>(e#eQaZ)_?m}Yfacsw$ zeMsy(yiT3U6}9i$7=Qr{!nc?Qe5%_fIHG_SBCliaA8GLu?-5_b;YNHE5Qt2k1tdB@ zC5|otuv#om%~cs_mdisD;79Ctn?Qv&iiVIPE-(*^;$w_;J;NBy| zr&tJ?8abF+Lpj)gy#6cC|6xu3<<<*g6qVaKv4eIWUIzD^PRvK)ODZ@^JgmQ?;pHPY zhgTV%MMpm0%t(Q+LFNyX^=|aK?1RsXgm3iRIa%T@2m^tIX{($IgA&hQyhLYaa7cLY zqF}Luz-i)m;v`8{-j&IzF_JmICO1Q&cY*f7_`Y;0{xG{X87AR7iXfuG#=-+E6acd|bFebDv$Og!oBgR7B*c`4K>6R^3e*%8 zIuI!x>w7S#^INCe1T&5tG+UaRco@AEci{|oc)X4l$eHU59#1N80PTWYcL%$jPVeC^ z0SS*=S#m=`C~kz-yTYh;gNK7a4CC!WDnD6NLeSR6X8tx7?i(m!t!PXSYbsda%NY|r zX>e8!jr39NSP-w}5a&y7OXvt?AA&Re6)QgH0qR?YeihZ#)Nk&ZtzOqk!B5cA{9GZU zhoqQtA~>#q(e;maSk78yPB0D{F3l8Ohpbw&&aqc1@)fA^?%nqYa-^1E;>qH^xbWSAF%cx?V7Fhc26uIe(8TwW~<|zauGoT7!Ux!L!5!2%wNe;5UUtD z%ZVSf8-EUAUGR^^FZ033SIZ66(9BQRNe$<@L-ZYHZH}aQwVOgGUq4}ZG7`H@dXaQ` zJe_}$ueCy&oHz;QP3nmAA*hP`qA>yqRvt|TL2B;SmZ9Bp11j%M?Jg9uPo_jpreY$$ z;&@IeS^jJwjEwh`5bo3Z$TZ~(QsPdB$Ffgi6B>qYDQ^Tcr0H?bj1~3S1h#>bfDSeDTzW~w6n#t)90rYyNJ>u`k2gy*u)upcV_gyOUIGls}hm&cqH~DwmkUOk=Q1 zee>6BFu5(IzJp+aBqD);e`f>q`BMkeXX=g)7PjWlANeCYMnWEi3t#5c&l#iAd2GCm zS}H~d8)HX*9ZAx8&@o-TH#-k~4aKnaor^%L-fJ|}sgncs%kJwm?)pT5JRTz45Y_^1 z9HwmcW8Vm()U+MD9r03^jjPufZ2D18*NTd<%X2sAt2kjRg_CnQB=?Ke9^@rTF^?+5 zujUF8AKk};fn?|T1|P^mFf>|o6ykc6ZN60vyA6ok!DHn!>?&(S7C5^42cAKAJ;85^M#4Bmx^5u5A1m=H5H&& zP(uUn7Egp8{;3vW8*YWxALD~i4DC;~K%bkMIzs=*wZ9#3{WsGFM7D;uae`$|f#0QE z++!yLv8B}cX=*grfX3&uXz;|SQ}C60IfZG<>Z<+lD&N}+kq_i%xY4X1>yQIiOo*SC z?77dfHA|EVY|Mc%*+inAj88%3anKPi=pp`)WGhw<0_tBDPu0Lk|yz1LOpwo`_~X6zq}N zKJYP?*mOQgwerM8$*~#eKRm(w)d=+GF$8C$0RXXNKbA|srZ%P=zrFr;+gmz% zVdVT^FODNI?8BWizU6+9T}ijom>EB&4kLYJ8Jm0D#H)dapTaSHqeS8*Golh2!16}t zD)K-a-U_@cj`^6V9I;HI+={WEm)aTU*;bmu+D$H zLd*2v68r5UNt5DZ&>f`W-UNNcTUQ}sRDzHTTNSG4DMy9)*2eq`(8RUXMX6sFL3c~YDeZ?Q{wKiEee5OAVG9( z<*sTvx65TJ;x#jkr3?xUjjo;@m)%;v3bo`(!qpCUOWmEAWx2Q|-YHb1y*uGn8Gj#D zJd5CncQq4!Bs{1zHC#f*`OWI{ZKlfI!o`a#K*Mvy`_2oZ zw2v&l=0Dy}P3|45)o)JVG$04YbzE4j#~9_K^?xd&CVas0cu{d-bBZNtp8|EJq>p{3 zBcRh*y+~%ALE#(_B_4_rLRtEH5tB@U8yyz&R5F>88|Tix8Iu`Bs6xD2vqs?c!IZjk zAMJB2+QO<8)JJE!(JRY)s*+}zt6tbz1~>;ty|G#Dk&!!B9*)=FM;jsKRq=PTKB0n@ zkKH($E|!XmcZIya9}R|~8|5fMx}lWUTfxd{R|oL^0jWr+w#e1SXnA*Js_5nAw^KaU zNjN6i#vW1#r!)Ja)EaYGxt%95qUYx9kKbNg|16Gkh@q`kA{m!PD-pYAd_vv4q+OMSt;h3cnAF#T4JqUNia_0ZJ~5lp-oU#fl`*T4o3d(2%a66l z1!o|svGCKNWPG|rNYmDcKlsGgNDDr4cleQVEsqJsj&J!6(^fFBLH?S1NxW-ieo9{BL5xqqg%Dju*CUW7R>*l7e&!xGD9`+T zTc*X+(Wozp8M+T%tlmnm3cbaeO!3&ZQhz~V80m>ZAQM%;z&lyw{`J1k$(Gstg0UN< zfP9!K+Df#od=Ys)qkCe`EQ|Ckb;i55a*_(|_~9=SQqEg@ExPKGf>XoLcJDxx2bAzd@{aVylU z(@^E$YG=mgNl2q8QOIW|u+%sAp^>rRsLu7>#t(2#&?kJhYCwxF!SDZykx&HWHeCt& z`t|5GSajmnrnctQU~3t}TdA7Uw*3sA?86miKk(tE>DLynMf*#2rA9#p`_x)cnaFj7 z;-jRk4fIgv;4uej^E-WJV1ohm8qnx%%Hyg7-;;!6Qi{5rwnYu4l53aA`>uHjspHZ- zX*@WZ(d9V?j}|`B6_@rgs`_g(&s)MWkE z!Yubbq%8S~A4=Q!FK9snCJ60>4y78Gbyyf-t95GhO?B5JyA0EN3@}+@CEqm-``hw0 zt`G59&&QxE8hlS+bD$YAg4ASJN!^|8n1vI+PzYEar&)V1&+x;@9(+O)ewx3UYQ+9h zI9`~u6voMX@pWDSf}6QcQ}3&~YE{iSdQh-E0F#l)vDFC;t*HyVt8HD)33P2<5<-z) zb1EFO(|Vhto1tTKie*?m&cy4l`+lSl_1LzV{4Ct;I%1Q8sbs1M&9HEa>0_US%AO^? zT=OXF$42>g+Y|8c)TyReHk0(V4<65&0H@4`aPMDE>(*Vfk6I8Vj}>BK!}+7^adfpd z{k`7NRInQt$8VzDhiJJwUp=IV>2Z>Iqnn2UQVZ*NX;K`kc}lQt8wypg-W8TlN;0fGIYUkhzcc8Tam2?2`4SCzEaS!8siqlAdHH2_ z%pF+?&VAIUplS@L>LnhG`rM)V_!fOtN7?P=fgN(zSJGT!dJ6A)LxB@Q6tTz?i_)^5 z0S{hsz@7wZJ3q0}du2{0R49fC_7P+9z zcB;&={-PS?&tkmeID9DZF zIMM4-Ys08=Y&}Xp)s6Ke>Ga3)BIH!il27D3@SHIS)YD9`*VTEbSAN{(SvcFaE3bnU z8%4TEs%Vc;Q7G14S;&L`qP8?}@+HPzhY6@;`+J!08iV|u_5fK*<9M~|V8Uf>?9_`)_z zoL*ixnzEX>x(f_hnW}gujAWO2=+<5oXG5*#5oJ`X6i7(yKY0@G%N4{iYmu-N`96%e zeAR-=b^Hm}aThI_d$~*L(b9geaqWP)1UT7)j5V0CAjmA*6tiKWcCkd$*jf7Y&Xadvj& zocLV}wEL8-%|_oHDYH~#EUfNv(a}l!o)$sn3c5s1>bLveuf)dQfwV(TEu?UA)l1tX%D_FIo&+ig9c7T6Py zRye*jQMX}LdSK*;U&h05r7&^rsg6%V_!E24J4ynEk%QdVi|fumG^nXqlGBtYsLhaRw@TtsL@YZIK9%c39T_HEn;pIB2s=GgPQ^yD`Q1eOwJvOM%{`W z-J^b&T?W^I!qaZAn{B?9qS5zuktwbdXQz#S8e)D5~3uGWq^n*V*Dr*XDt)HE${o100vv`;}-_&SPKY<$* zd72u#aQ`&M`lZUtBjBWF_z;02MyPn$11-12tZE0!g^V88BKl%-96=#Jsr;oA2Ew3_ zuC_26?8ipFVaRgA438pCK9L8N61uC*(YI#!*Vq&+iN-kv;}9EppbOAH3%TvWUb|KU z>@uUvBvcC}uxPTG2WGdZ_#Mv-bXs1DZ4%@7vziOX%+>~Pq2rSpP)WJKkC>orInq)s+oksXJ+ z?jbJ%44O+!OFu*O=>EAVFMp3>B8eE*ImDCk_mpOja4Q^59n?)79e>ys95;@+*o`f0 zji01rbCz+I%p;Mt77Vwv^tAMHhQR}9QG1rmVNZ;a#|Rz`h>u0L21vR`Rq{v*Qoq53 zNgR1oi&rD~{fo;GGB_f5gP=b?BJbk=mHtod?f+#zV#xm3l4DwJ;XiD8)N|aRo$zE^ zrXX=G)od2G=@NjPo3^PC5wn6{2U%~bX(A=s7LL;_+_8l!t0Fu{39fEO=e;x~tG46; zpDqY|{mL2jt&&56?|2iht$NAv#>NU^KZseId6N=sjUJNHzf4;S6BU;R-lfG#yro4O zM7kz7HImDFXC_+h+cZ-)uWd~+bJpV6<^>wX!MKRna*2#ReH`OcyB@S|ov__|Y>hi- zV}q8&7>`54@C!z7S;&8zSYMAV=DcW_c!CNE^IK0y-_2@G89;@+H~tVGC`j5bKZD{Y zM%-~P*VY2d)Ho)yQwNL8b0FrMl9jOOOriY9BDg?vlqoo1q7p|A0*~VC^Y-?-e|fck zy=K=c2eI@Nu%+z&l9had1E%0}LIa{>1_!YC6OC0{X3#VtiBJMhv4J~22+nFvGXt2Ci-x<+*WZ+P7|gKwIlKZ9|wehdDG zHF^`_rd{|Gferh=U;qD^hc^Llx^F)LIS}g>#Qog#;%)-mTQ`*2LSZH>zTm!6 literal 0 HcmV?d00001 From 4fa9794d06fa92188895883a1dae020b01fb0e2e Mon Sep 17 00:00:00 2001 From: Looly Date: Sun, 15 Nov 2020 00:50:25 +0800 Subject: [PATCH 16/20] fix bug --- CHANGELOG.md | 1 + .../java/cn/hutool/core/util/StrUtil.java | 35 +++++++++---------- .../java/cn/hutool/core/util/StrUtilTest.java | 17 +++++++-- 3 files changed, 32 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 58d538385..6f02357ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,7 @@ * 【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/hutool-core/src/main/java/cn/hutool/core/util/StrUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/StrUtil.java index 2b61b7a5e..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,10 +2405,19 @@ 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)); + } } } @@ -2433,23 +2442,13 @@ public class StrUtil { * * * @param str 被切割的字符串 - * @param beforeAndAfter 截取开始和结束的字符串标识 + * @param prefixAndSuffix 截取开始和结束的字符串标识 * @return 截取后的字符串 * @author gotanks - * @since 5.4.7 + * @since 5.5.0 */ - public static String[] subBetweenAll(CharSequence str, CharSequence beforeAndAfter) { - String[] resultArr = new String[0]; - if (hasEmpty(str, beforeAndAfter) || !contains(str, beforeAndAfter)) { - return resultArr; - } - - final List result = new LinkedList<>(); - String[] split = split(str, beforeAndAfter); - for (int i = 1, length = split.length - 1; i < length; i = i + 2) { - result.add(split[i]); - } - return result.toArray(resultArr); + public static String[] subBetweenAll(CharSequence str, CharSequence prefixAndSuffix) { + return subBetweenAll(str, prefixAndSuffix, prefixAndSuffix); } /** 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 8cbdb7eb2..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 @@ -1,6 +1,5 @@ package cn.hutool.core.util; -import cn.hutool.core.lang.Console; import cn.hutool.core.lang.Dict; import org.junit.Assert; import org.junit.Test; @@ -437,9 +436,21 @@ public class StrUtilTest { @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]); - final String[] strings = StrUtil.subBetweenAll(src1, "'", "'"); - Console.log(strings); + 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 From 8b5c1299d9ff660669eb6020799c6d1ef5accea1 Mon Sep 17 00:00:00 2001 From: Looly Date: Sun, 15 Nov 2020 01:29:55 +0800 Subject: [PATCH 17/20] update dependency --- hutool-db/pom.xml | 2 +- hutool-extra/pom.xml | 10 +++++----- hutool-log/pom.xml | 2 +- hutool-system/pom.xml | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml index 08384dea5..8d8fce7f3 100644 --- a/hutool-db/pom.xml +++ b/hutool-db/pom.xml @@ -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-extra/pom.xml b/hutool-extra/pom.xml index 88db4ba94..9e04a359f 100644 --- a/hutool-extra/pom.xml +++ b/hutool-extra/pom.xml @@ -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 @@ -228,7 +228,7 @@ org.apache.lucene lucene-analyzers-smartcn - 8.6.3 + 8.7.0 true @@ -406,7 +406,7 @@ org.springframework spring-expression - 5.3.0 + 5.3.1 compile true diff --git a/hutool-log/pom.xml b/hutool-log/pom.xml index 551ad4e1d..44ae087cf 100644 --- a/hutool-log/pom.xml +++ b/hutool-log/pom.xml @@ -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-system/pom.xml b/hutool-system/pom.xml index a8aff2038..3d0a2ab20 100644 --- a/hutool-system/pom.xml +++ b/hutool-system/pom.xml @@ -26,7 +26,7 @@ com.github.oshi oshi-core - 5.3.1 + 5.3.5 provided From 2d09d09434a82a699d3088f2118149b1d556ae8e Mon Sep 17 00:00:00 2001 From: Looly Date: Sun, 15 Nov 2020 01:32:28 +0800 Subject: [PATCH 18/20] release 5.5.0 --- README-EN.md | 8 ++++---- README.md | 8 ++++---- bin/version.txt | 2 +- docs/js/version.js | 2 +- hutool-all/pom.xml | 2 +- hutool-aop/pom.xml | 2 +- hutool-bloomFilter/pom.xml | 2 +- hutool-bom/pom.xml | 2 +- hutool-cache/pom.xml | 2 +- hutool-captcha/pom.xml | 2 +- hutool-core/pom.xml | 2 +- hutool-cron/pom.xml | 2 +- hutool-crypto/pom.xml | 2 +- hutool-db/pom.xml | 2 +- hutool-dfa/pom.xml | 2 +- hutool-extra/pom.xml | 2 +- hutool-http/pom.xml | 2 +- hutool-json/pom.xml | 2 +- hutool-log/pom.xml | 2 +- hutool-poi/pom.xml | 2 +- hutool-script/pom.xml | 2 +- hutool-setting/pom.xml | 2 +- hutool-socket/pom.xml | 2 +- hutool-system/pom.xml | 2 +- pom.xml | 2 +- 25 files changed, 31 insertions(+), 31 deletions(-) diff --git a/README-EN.md b/README-EN.md index 10205daec..027da0435 100644 --- a/README-EN.md +++ b/README-EN.md @@ -125,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.0 ``` ### Gradle ``` -compile 'cn.hutool:hutool-all:5.4.8' +compile 'cn.hutool:hutool-all:5.5.0' ``` ## 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.0/) +- [Maven2](http://repo2.maven.org/maven2/cn/hutool/hutool-all/5.5.0/) > 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 89d2545e3..ea7a059c2 100644 --- a/README.md +++ b/README.md @@ -123,21 +123,21 @@ Hutool的存在就是为了减少代码搜索成本,避免网络上参差不 cn.hutool hutool-all - 5.4.8 + 5.5.0 ``` ### Gradle ``` -compile 'cn.hutool:hutool-all:5.4.8' +compile 'cn.hutool:hutool-all:5.5.0' ``` ### 非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.0/) +- [Maven中央库2](http://repo2.maven.org/maven2/cn/hutool/hutool-all/5.5.0/) > 注意 > Hutool 5.x支持JDK8+,对Android平台没有测试,不能保证所有工具类或工具方法可用。 diff --git a/bin/version.txt b/bin/version.txt index f5b71c928..d50359de1 100755 --- a/bin/version.txt +++ b/bin/version.txt @@ -1 +1 @@ -5.4.8 +5.5.0 diff --git a/docs/js/version.js b/docs/js/version.js index eee6b5653..22d3da0ae 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.0' \ No newline at end of file diff --git a/hutool-all/pom.xml b/hutool-all/pom.xml index fb74363dc..8e44b8b55 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.0 hutool-all diff --git a/hutool-aop/pom.xml b/hutool-aop/pom.xml index cc4dd1044..6ad8d0afc 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.0 hutool-aop diff --git a/hutool-bloomFilter/pom.xml b/hutool-bloomFilter/pom.xml index 7a8804c1c..f45e69537 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.0 hutool-bloomFilter diff --git a/hutool-bom/pom.xml b/hutool-bom/pom.xml index 298fe51f8..b91a36ce4 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.0 hutool-bom diff --git a/hutool-cache/pom.xml b/hutool-cache/pom.xml index 79bb11aa9..29bb707cd 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.0 hutool-cache diff --git a/hutool-captcha/pom.xml b/hutool-captcha/pom.xml index b2cb99015..444764a7a 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.0 hutool-captcha diff --git a/hutool-core/pom.xml b/hutool-core/pom.xml index a6c6f7d84..d196a1831 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.0 hutool-core diff --git a/hutool-cron/pom.xml b/hutool-cron/pom.xml index 0695b07f3..d084dc523 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.0 hutool-cron diff --git a/hutool-crypto/pom.xml b/hutool-crypto/pom.xml index f58b23bb9..541cb7e8a 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.0 hutool-crypto diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml index 8d8fce7f3..3b33d755c 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.0 hutool-db diff --git a/hutool-dfa/pom.xml b/hutool-dfa/pom.xml index 6c35642d3..8c2ba89bb 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.0 hutool-dfa diff --git a/hutool-extra/pom.xml b/hutool-extra/pom.xml index 9e04a359f..1f7cb7628 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.0 hutool-extra diff --git a/hutool-http/pom.xml b/hutool-http/pom.xml index 4365b0976..dc9c7a592 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.0 hutool-http diff --git a/hutool-json/pom.xml b/hutool-json/pom.xml index 478b359e9..e240237bf 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.0 hutool-json diff --git a/hutool-log/pom.xml b/hutool-log/pom.xml index 44ae087cf..fb41b6e5c 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.0 hutool-log diff --git a/hutool-poi/pom.xml b/hutool-poi/pom.xml index 156a0306d..bd43b4765 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.0 hutool-poi diff --git a/hutool-script/pom.xml b/hutool-script/pom.xml index caaaf2578..3e02cfb55 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.0 hutool-script diff --git a/hutool-setting/pom.xml b/hutool-setting/pom.xml index 658a71733..f6a66aae5 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.0 hutool-setting diff --git a/hutool-socket/pom.xml b/hutool-socket/pom.xml index 196d1091f..423a1ce44 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.0 hutool-socket diff --git a/hutool-system/pom.xml b/hutool-system/pom.xml index 3d0a2ab20..e13fbb065 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.0 hutool-system diff --git a/pom.xml b/pom.xml index f59e802de..b83ec8c4d 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.4.8-SNAPSHOT + 5.5.0 hutool Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。 https://github.com/looly/hutool From 8102c899037dab3c7891a33b9cad14e0e0a2a099 Mon Sep 17 00:00:00 2001 From: Looly Date: Sun, 15 Nov 2020 08:18:08 +0800 Subject: [PATCH 19/20] prepare 5.5.1 --- CHANGELOG.md | 7 +++++++ README-EN.md | 8 ++++---- README.md | 8 ++++---- bin/version.txt | 2 +- docs/js/version.js | 2 +- hutool-all/pom.xml | 2 +- hutool-aop/pom.xml | 2 +- hutool-bloomFilter/pom.xml | 2 +- hutool-bom/pom.xml | 2 +- hutool-cache/pom.xml | 2 +- hutool-captcha/pom.xml | 2 +- hutool-core/pom.xml | 2 +- hutool-cron/pom.xml | 2 +- hutool-crypto/pom.xml | 2 +- hutool-db/pom.xml | 2 +- hutool-dfa/pom.xml | 2 +- hutool-extra/pom.xml | 2 +- hutool-http/pom.xml | 2 +- hutool-json/pom.xml | 2 +- hutool-log/pom.xml | 2 +- hutool-poi/pom.xml | 2 +- hutool-script/pom.xml | 2 +- hutool-setting/pom.xml | 2 +- hutool-socket/pom.xml | 2 +- hutool-system/pom.xml | 2 +- pom.xml | 2 +- 26 files changed, 38 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f02357ac..074560b79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,13 @@ ------------------------------------------------------------------------------------------------------------- +# 5.5.1 (2020-11-15) + +### 新特性 +### Bug修复 + +------------------------------------------------------------------------------------------------------------- + # 5.5.0 (2020-11-14) ### 大版本特性 diff --git a/README-EN.md b/README-EN.md index 027da0435..0f1c0da29 100644 --- a/README-EN.md +++ b/README-EN.md @@ -125,19 +125,19 @@ Each module can be introduced individually, or all modules can be introduced by cn.hutool hutool-all - 5.5.0 + 5.5.1 ``` ### Gradle ``` -compile 'cn.hutool:hutool-all:5.5.0' +compile 'cn.hutool:hutool-all:5.5.1' ``` ## Download -- [Maven1](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.5.0/) -- [Maven2](http://repo2.maven.org/maven2/cn/hutool/hutool-all/5.5.0/) +- [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 ea7a059c2..04316f385 100644 --- a/README.md +++ b/README.md @@ -123,21 +123,21 @@ Hutool的存在就是为了减少代码搜索成本,避免网络上参差不 cn.hutool hutool-all - 5.5.0 + 5.5.1 ``` ### Gradle ``` -compile 'cn.hutool:hutool-all:5.5.0' +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.5.0/) -- [Maven中央库2](http://repo2.maven.org/maven2/cn/hutool/hutool-all/5.5.0/) +- [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 d50359de1..7acd1cb0e 100755 --- a/bin/version.txt +++ b/bin/version.txt @@ -1 +1 @@ -5.5.0 +5.5.1 diff --git a/docs/js/version.js b/docs/js/version.js index 22d3da0ae..02594b515 100644 --- a/docs/js/version.js +++ b/docs/js/version.js @@ -1 +1 @@ -var version = '5.5.0' \ 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 8e44b8b55..c159e9f38 100644 --- a/hutool-all/pom.xml +++ b/hutool-all/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.5.0 + 5.5.1-SNAPSHOT hutool-all diff --git a/hutool-aop/pom.xml b/hutool-aop/pom.xml index 6ad8d0afc..72ce688c1 100644 --- a/hutool-aop/pom.xml +++ b/hutool-aop/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.5.0 + 5.5.1-SNAPSHOT hutool-aop diff --git a/hutool-bloomFilter/pom.xml b/hutool-bloomFilter/pom.xml index f45e69537..b83689bfd 100644 --- a/hutool-bloomFilter/pom.xml +++ b/hutool-bloomFilter/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.5.0 + 5.5.1-SNAPSHOT hutool-bloomFilter diff --git a/hutool-bom/pom.xml b/hutool-bom/pom.xml index b91a36ce4..9f2e88b40 100644 --- a/hutool-bom/pom.xml +++ b/hutool-bom/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.5.0 + 5.5.1-SNAPSHOT hutool-bom diff --git a/hutool-cache/pom.xml b/hutool-cache/pom.xml index 29bb707cd..ee465b8cf 100644 --- a/hutool-cache/pom.xml +++ b/hutool-cache/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.5.0 + 5.5.1-SNAPSHOT hutool-cache diff --git a/hutool-captcha/pom.xml b/hutool-captcha/pom.xml index 444764a7a..353279f29 100644 --- a/hutool-captcha/pom.xml +++ b/hutool-captcha/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.5.0 + 5.5.1-SNAPSHOT hutool-captcha diff --git a/hutool-core/pom.xml b/hutool-core/pom.xml index d196a1831..733fc0911 100644 --- a/hutool-core/pom.xml +++ b/hutool-core/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.5.0 + 5.5.1-SNAPSHOT hutool-core diff --git a/hutool-cron/pom.xml b/hutool-cron/pom.xml index d084dc523..13decede6 100644 --- a/hutool-cron/pom.xml +++ b/hutool-cron/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.5.0 + 5.5.1-SNAPSHOT hutool-cron diff --git a/hutool-crypto/pom.xml b/hutool-crypto/pom.xml index 541cb7e8a..4ec1562f9 100644 --- a/hutool-crypto/pom.xml +++ b/hutool-crypto/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.5.0 + 5.5.1-SNAPSHOT hutool-crypto diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml index 3b33d755c..34d0e41ba 100644 --- a/hutool-db/pom.xml +++ b/hutool-db/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.5.0 + 5.5.1-SNAPSHOT hutool-db diff --git a/hutool-dfa/pom.xml b/hutool-dfa/pom.xml index 8c2ba89bb..77235408a 100644 --- a/hutool-dfa/pom.xml +++ b/hutool-dfa/pom.xml @@ -7,7 +7,7 @@ cn.hutool hutool-parent - 5.5.0 + 5.5.1-SNAPSHOT hutool-dfa diff --git a/hutool-extra/pom.xml b/hutool-extra/pom.xml index 1f7cb7628..fa6ce1a33 100644 --- a/hutool-extra/pom.xml +++ b/hutool-extra/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.5.0 + 5.5.1-SNAPSHOT hutool-extra diff --git a/hutool-http/pom.xml b/hutool-http/pom.xml index dc9c7a592..c607532d7 100644 --- a/hutool-http/pom.xml +++ b/hutool-http/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.5.0 + 5.5.1-SNAPSHOT hutool-http diff --git a/hutool-json/pom.xml b/hutool-json/pom.xml index e240237bf..de45a615f 100644 --- a/hutool-json/pom.xml +++ b/hutool-json/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.5.0 + 5.5.1-SNAPSHOT hutool-json diff --git a/hutool-log/pom.xml b/hutool-log/pom.xml index fb41b6e5c..6da95e8f2 100644 --- a/hutool-log/pom.xml +++ b/hutool-log/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.5.0 + 5.5.1-SNAPSHOT hutool-log diff --git a/hutool-poi/pom.xml b/hutool-poi/pom.xml index bd43b4765..7b89b04f3 100644 --- a/hutool-poi/pom.xml +++ b/hutool-poi/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.5.0 + 5.5.1-SNAPSHOT hutool-poi diff --git a/hutool-script/pom.xml b/hutool-script/pom.xml index 3e02cfb55..839985257 100644 --- a/hutool-script/pom.xml +++ b/hutool-script/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.5.0 + 5.5.1-SNAPSHOT hutool-script diff --git a/hutool-setting/pom.xml b/hutool-setting/pom.xml index f6a66aae5..b6311760c 100644 --- a/hutool-setting/pom.xml +++ b/hutool-setting/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.5.0 + 5.5.1-SNAPSHOT hutool-setting diff --git a/hutool-socket/pom.xml b/hutool-socket/pom.xml index 423a1ce44..3ff398fe7 100644 --- a/hutool-socket/pom.xml +++ b/hutool-socket/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.5.0 + 5.5.1-SNAPSHOT hutool-socket diff --git a/hutool-system/pom.xml b/hutool-system/pom.xml index e13fbb065..2644ad9e8 100644 --- a/hutool-system/pom.xml +++ b/hutool-system/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.5.0 + 5.5.1-SNAPSHOT hutool-system diff --git a/pom.xml b/pom.xml index b83ec8c4d..98e10c50b 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.5.0 + 5.5.1-SNAPSHOT hutool Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。 https://github.com/looly/hutool From ea3060cddf8b15282409e2593fc5bca826c719ed Mon Sep 17 00:00:00 2001 From: Looly Date: Sun, 15 Nov 2020 11:15:03 +0800 Subject: [PATCH 20/20] fix move bug --- CHANGELOG.md | 1 + .../main/java/cn/hutool/core/io/FileUtil.java | 36 ++-------------- .../cn/hutool/core/io/file/FileCopier.java | 8 ++-- .../java/cn/hutool/core/io/file/PathUtil.java | 41 ++++++++++++++++--- .../java/cn/hutool/core/io/FileUtilTest.java | 6 +++ .../cn/hutool/core/io/file/PathUtilTest.java | 6 +++ .../hutool/poi/excel/test/ExcelReadTest.java | 1 + 7 files changed, 58 insertions(+), 41 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 074560b79..0ca3fa474 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ ### 新特性 ### Bug修复 +* 【core 】 修复在Linux下FileUtil.move失败问题(issue#I254Y3@Gitee) ------------------------------------------------------------------------------------------------------------- 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 04f8c5b26..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 @@ -968,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); } /** 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/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-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 459348ec1..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 @@ -187,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));