最终返回的数据类型
- * @return cn.hutool.core.lang.func.Func1
+ * @return {@link Func1Rt}
*/
public static Func1Rt
uncheck(Func1
expression) {
return uncheck(expression, new RuntimeException());
@@ -89,7 +89,7 @@ public class CheckedUtil {
*
* @param expression 运行时传入的参数类型
* @param
运行时传入的参数类型
- * @return cn.hutool.core.lang.func.VoidFunc
+ * @return {@link VoidFuncRt}
*/
public static
VoidFuncRt
uncheck(VoidFunc
expression) {
return uncheck(expression, new RuntimeException());
@@ -100,7 +100,7 @@ public class CheckedUtil {
* 如此一来,代码中就不用显示的try-catch转化成运行时异常
*
* @param expression 运行时传入的参数类型
- * @return cn.hutool.core.lang.func.VoidFunc0
+ * @return {@link VoidFunc0Rt}
*/
public static VoidFunc0Rt uncheck(VoidFunc0 expression) {
return uncheck(expression, new RuntimeException());
@@ -112,7 +112,7 @@ public class CheckedUtil {
*
* @param expression 运行时传入的参数类型
* @param
运行时传入的参数类型
- * @return cn.hutool.core.lang.func.VoidFunc1
+ * @return {@link VoidFunc1Rt}
*/
public static
VoidFunc1Rt
uncheck(VoidFunc1
expression) {
return uncheck(expression, new RuntimeException());
@@ -127,7 +127,7 @@ public class CheckedUtil {
* @param rte 期望抛出的运行时异常
* @param
运行时传入的参数类型
* @param 最终返回的数据类型
- * @return cn.hutool.core.lang.func.Func
+ * @return {@link FuncRt}
*/
public static FuncRt
uncheck(Func
expression, RuntimeException rte) {
Objects.requireNonNull(expression, "expression can not be null");
@@ -152,7 +152,7 @@ public class CheckedUtil {
* @param expression Lambda表达式
* @param rte 期望抛出的运行时异常
* @param 最终返回的数据类型
- * @return cn.hutool.core.lang.func.Func0
+ * @return {@link Func0Rt}
*/
public static Func0Rt uncheck(Func0 expression, RuntimeException rte) {
Objects.requireNonNull(expression, "expression can not be null");
@@ -178,7 +178,7 @@ public class CheckedUtil {
* @param rte 期望抛出的运行时异常
* @param 运行时传入的参数类型
* @param 最终返回的数据类型
- * @return cn.hutool.core.lang.func.Func1
+ * @return {@link Func1Rt}
*/
public static Func1Rt
uncheck(Func1
expression, RuntimeException rte) {
Objects.requireNonNull(expression, "expression can not be null");
@@ -203,7 +203,7 @@ public class CheckedUtil {
* @param expression Lambda表达式
* @param rte 期望抛出的运行时异常
* @param
运行时传入的参数类型
- * @return cn.hutool.core.lang.func.VoidFunc
+ * @return {@link VoidFuncRt}
*/
public static
VoidFuncRt
uncheck(VoidFunc
expression, RuntimeException rte) {
Objects.requireNonNull(expression, "expression can not be null");
@@ -228,7 +228,7 @@ public class CheckedUtil {
*
* @param expression Lambda表达式
* @param rte 期望抛出的运行时异常
- * @return cn.hutool.core.lang.func.VoidFunc0
+ * @return {@link VoidFunc0Rt}
*/
public static VoidFunc0Rt uncheck(VoidFunc0 expression, RuntimeException rte) {
Objects.requireNonNull(expression, "expression can not be null");
@@ -254,7 +254,7 @@ public class CheckedUtil {
* @param expression Lambda表达式
* @param rte 期望抛出的运行时异常
* @param
运行时传入的参数类型
- * @return cn.hutool.core.lang.func.VoidFunc1
+ * @return {@link VoidFunc1Rt}
*/
public static
VoidFunc1Rt
uncheck(VoidFunc1
expression, RuntimeException rte) {
Objects.requireNonNull(expression, "expression can not be null");
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 3d4b80858..72eb18ca6 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
@@ -13,6 +13,7 @@ import cn.hutool.core.io.file.Tailer;
import cn.hutool.core.io.resource.ResourceUtil;
import cn.hutool.core.io.unit.DataSizeUtil;
import cn.hutool.core.lang.Assert;
+import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.CharUtil;
import cn.hutool.core.util.CharsetUtil;
@@ -526,24 +527,39 @@ public class FileUtil extends PathUtil {
/**
* 计算目录或文件的总大小
* 当给定对象为文件时,直接调用 {@link File#length()}
- * 当给定对象为目录时,遍历目录下的所有文件和目录,递归计算其大小,求和返回
+ * 当给定对象为目录时,遍历目录下的所有文件和目录,递归计算其大小,求和返回
+ * 此方法不包括目录本身的占用空间大小。
*
* @param file 目录或文件,null或者文件不存在返回0
* @return 总大小,bytes长度
*/
public static long size(File file) {
+ return size(file, false);
+ }
+
+ /**
+ * 计算目录或文件的总大小
+ * 当给定对象为文件时,直接调用 {@link File#length()}
+ * 当给定对象为目录时,遍历目录下的所有文件和目录,递归计算其大小,求和返回
+ *
+ * @param file 目录或文件,null或者文件不存在返回0
+ * @param includeDirSize 是否包括每层目录本身的大小
+ * @return 总大小,bytes长度
+ * @since 5.7.21
+ */
+ public static long size(File file, boolean includeDirSize) {
if (null == file || false == file.exists() || isSymlink(file)) {
return 0;
}
if (file.isDirectory()) {
- long size = 0L;
+ long size = includeDirSize ? file.length() : 0;
File[] subFiles = file.listFiles();
if (ArrayUtil.isEmpty(subFiles)) {
return 0L;// empty directory
}
for (File subFile : subFiles) {
- size += size(subFile);
+ size += size(subFile, includeDirSize);
}
return size;
} else {
@@ -811,7 +827,7 @@ public class FileUtil extends PathUtil {
/**
* 创建文件夹,会递归自动创建其不存在的父文件夹,如果存在直接返回此文件夹
- * 此方法不对File对象类型做判断,如果File不存在,无法判断其类型
+ * 此方法不对File对象类型做判断,如果File不存在,无法判断其类型
*
* @param dir 目录
* @return 创建的目录
@@ -821,12 +837,48 @@ public class FileUtil extends PathUtil {
return null;
}
if (false == dir.exists()) {
- //noinspection ResultOfMethodCallIgnored
- dir.mkdirs();
+ mkdirsSafely(dir, 5, 1);
}
return dir;
}
+ /**
+ * 安全地级联创建目录 (确保并发环境下能创建成功)
+ *
+ *
+ * 并发环境下,假设 test 目录不存在,如果线程A mkdirs "test/A" 目录,线程B mkdirs "test/B"目录,
+ * 其中一个线程可能会失败,进而导致以下代码抛出 FileNotFoundException 异常
+ *
+ * file.getParentFile().mkdirs(); // 父目录正在被另一个线程创建中,返回 false
+ * file.createNewFile(); // 抛出 IO 异常,因为该线程无法感知到父目录已被创建
+ *
+ *
+ * @param dir 待创建的目录
+ * @param tryCount 最大尝试次数
+ * @param sleepMillis 线程等待的毫秒数
+ * @return true表示创建成功,false表示创建失败
+ * @since 5.7.21
+ * @author z8g
+ */
+ public static boolean mkdirsSafely(File dir, int tryCount, long sleepMillis) {
+ if (dir == null) {
+ return false;
+ }
+ if (dir.isDirectory()) {
+ return true;
+ }
+ for (int i = 1; i <= tryCount; i++) { // 高并发场景下,可以看到 i 处于 1 ~ 3 之间
+ // 如果文件已存在,也会返回 false,所以该值不能作为是否能创建的依据,因此不对其进行处理
+ //noinspection ResultOfMethodCallIgnored
+ dir.mkdirs();
+ if (dir.exists()) {
+ return true;
+ }
+ ThreadUtil.sleep(sleepMillis);
+ }
+ return dir.exists();
+ }
+
/**
* 创建临时文件
* 创建后的文件名为 prefix[Randon].tmp
diff --git a/hutool-core/src/main/java/cn/hutool/core/io/NioUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/NioUtil.java
index 2db343457..806105051 100644
--- a/hutool-core/src/main/java/cn/hutool/core/io/NioUtil.java
+++ b/hutool-core/src/main/java/cn/hutool/core/io/NioUtil.java
@@ -87,12 +87,49 @@ public class NioUtil {
Assert.notNull(outChannel, "Out channel is null!");
try {
- return inChannel.transferTo(0, inChannel.size(), outChannel);
+ return copySafely(inChannel, outChannel);
} catch (IOException e) {
throw new IORuntimeException(e);
}
}
+ /**
+ * 文件拷贝实现
+ *
+ *
+ * FileChannel#transferTo 或 FileChannel#transferFrom 的实现是平台相关的,需要确保低版本平台的兼容性
+ * 例如 android 7以下平台在使用 ZipInputStream 解压文件的过程中,
+ * 通过 FileChannel#transferFrom 传输到文件时,其返回值可能小于 totalBytes,不处理将导致文件内容缺失
+ *
+ * // 错误写法,dstChannel.transferFrom 返回值小于 zipEntry.getSize(),导致解压后文件内容缺失
+ * try (InputStream srcStream = zipFile.getInputStream(zipEntry);
+ * ReadableByteChannel srcChannel = Channels.newChannel(srcStream);
+ * FileOutputStream fos = new FileOutputStream(saveFile);
+ * FileChannel dstChannel = fos.getChannel()) {
+ * dstChannel.transferFrom(srcChannel, 0, zipEntry.getSize());
+ * }
+ *
+ *
+ * @param inChannel 输入通道
+ * @param outChannel 输出通道
+ * @return 输入通道的字节数
+ * @throws IOException 发生IO错误
+ * @link http://androidxref.com/6.0.1_r10/xref/libcore/luni/src/main/java/java/nio/FileChannelImpl.java
+ * @link http://androidxref.com/7.0.0_r1/xref/libcore/ojluni/src/main/java/sun/nio/ch/FileChannelImpl.java
+ * @link http://androidxref.com/7.0.0_r1/xref/libcore/ojluni/src/main/native/FileChannelImpl.c
+ * @author z8g
+ * @since 5.7.21
+ */
+ private static long copySafely(FileChannel inChannel, FileChannel outChannel) throws IOException {
+ final long totalBytes = inChannel.size();
+ for (long pos = 0, remaining = totalBytes; remaining > 0; ) { // 确保文件内容不会缺失
+ final long writeBytes = inChannel.transferTo(pos, remaining, outChannel); // 实际传输的字节数
+ pos += writeBytes;
+ remaining -= writeBytes;
+ }
+ return totalBytes;
+ }
+
/**
* 拷贝流,使用NIO,不会关闭channel
*
diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/func/Supplier1.java b/hutool-core/src/main/java/cn/hutool/core/lang/func/Supplier1.java
new file mode 100755
index 000000000..376c91a2a
--- /dev/null
+++ b/hutool-core/src/main/java/cn/hutool/core/lang/func/Supplier1.java
@@ -0,0 +1,32 @@
+package cn.hutool.core.lang.func;
+
+import java.util.function.Supplier;
+
+/**
+ * 1参数Supplier
+ *
+ * @param 目标 类型
+ * @param 参数一 类型
+ * @author TomXin
+ * @since 5.7.21
+ */
+@FunctionalInterface
+public interface Supplier1 {
+ /**
+ * 生成实例的方法
+ *
+ * @param p1 参数一
+ * @return 目标对象
+ */
+ T get(P1 p1);
+
+ /**
+ * 将带有参数的Supplier转换为无参{@link Supplier}
+ *
+ * @param p1 参数1
+ * @return {@link Supplier}
+ */
+ default Supplier toSupplier(P1 p1) {
+ return () -> get(p1);
+ }
+}
diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/func/Supplier2.java b/hutool-core/src/main/java/cn/hutool/core/lang/func/Supplier2.java
new file mode 100755
index 000000000..6d5e9ce80
--- /dev/null
+++ b/hutool-core/src/main/java/cn/hutool/core/lang/func/Supplier2.java
@@ -0,0 +1,36 @@
+package cn.hutool.core.lang.func;
+
+import java.util.function.Supplier;
+
+/**
+ * 两个参数的Supplier
+ *
+ * @param 目标 类型
+ * @param 参数一 类型
+ * @param 参数二 类型
+ * @author TomXin
+ * @since 5.7.21
+ */
+@FunctionalInterface
+public interface Supplier2 {
+
+ /**
+ * 生成实例的方法
+ *
+ * @param p1 参数一
+ * @param p2 参数二
+ * @return 目标对象
+ */
+ T get(P1 p1, P2 p2);
+
+ /**
+ * 将带有参数的Supplier转换为无参{@link Supplier}
+ *
+ * @param p1 参数1
+ * @param p2 参数2
+ * @return {@link Supplier}
+ */
+ default Supplier toSupplier(P1 p1, P2 p2) {
+ return () -> get(p1, p2);
+ }
+}
diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/func/Supplier3.java b/hutool-core/src/main/java/cn/hutool/core/lang/func/Supplier3.java
new file mode 100755
index 000000000..50324e55b
--- /dev/null
+++ b/hutool-core/src/main/java/cn/hutool/core/lang/func/Supplier3.java
@@ -0,0 +1,39 @@
+package cn.hutool.core.lang.func;
+
+import java.util.function.Supplier;
+
+/**
+ * 3参数Supplier
+ *
+ * @param 目标类型
+ * @param 参数一类型
+ * @param 参数二类型
+ * @param 参数三类型
+ * @author TomXin
+ * @since 5.7.21
+ */
+@FunctionalInterface
+public interface Supplier3 {
+
+ /**
+ * 生成实例的方法
+ *
+ * @param p1 参数一
+ * @param p2 参数二
+ * @param p3 参数三
+ * @return 目标对象
+ */
+ T get(P1 p1, P2 p2, P3 p3);
+
+ /**
+ * 将带有参数的Supplier转换为无参{@link Supplier}
+ *
+ * @param p1 参数1
+ * @param p2 参数2
+ * @param p3 参数3
+ * @return {@link Supplier}
+ */
+ default Supplier toSupplier(P1 p1, P2 p2, P3 p3) {
+ return () -> get(p1, p2, p3);
+ }
+}
diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/func/Supplier4.java b/hutool-core/src/main/java/cn/hutool/core/lang/func/Supplier4.java
new file mode 100755
index 000000000..c715b8693
--- /dev/null
+++ b/hutool-core/src/main/java/cn/hutool/core/lang/func/Supplier4.java
@@ -0,0 +1,42 @@
+package cn.hutool.core.lang.func;
+
+import java.util.function.Supplier;
+
+/**
+ * 4参数Supplier
+ *
+ * @param 目标 类型
+ * @param 参数一 类型
+ * @param 参数二 类型
+ * @param 参数三 类型
+ * @param 参数四 类型
+ * @author TomXin
+ * @since 5.7.21
+ */
+@FunctionalInterface
+public interface Supplier4 {
+
+ /**
+ * 生成实例的方法
+ *
+ * @param p1 参数一
+ * @param p2 参数二
+ * @param p3 参数三
+ * @param p4 参数四
+ * @return 目标对象
+ */
+ T get(P1 p1, P2 p2, P3 p3, P4 p4);
+
+ /**
+ * 将带有参数的Supplier转换为无参{@link Supplier}
+ *
+ * @param p1 参数1
+ * @param p2 参数2
+ * @param p3 参数3
+ * @param p4 参数4
+ * @return {@link Supplier}
+ */
+ default Supplier toSupplier(P1 p1, P2 p2, P3 p3, P4 p4) {
+ return () -> get(p1, p2, p3, p4);
+ }
+}
diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/func/Supplier5.java b/hutool-core/src/main/java/cn/hutool/core/lang/func/Supplier5.java
new file mode 100755
index 000000000..121a2ce9c
--- /dev/null
+++ b/hutool-core/src/main/java/cn/hutool/core/lang/func/Supplier5.java
@@ -0,0 +1,45 @@
+package cn.hutool.core.lang.func;
+
+import java.util.function.Supplier;
+
+/**
+ * 5参数Supplier
+ *
+ * @param 目标 类型
+ * @param 参数一 类型
+ * @param 参数二 类型
+ * @param 参数三 类型
+ * @param 参数四 类型
+ * @param 参数五 类型
+ * @author TomXin
+ * @since 5.7.21
+ */
+@FunctionalInterface
+public interface Supplier5 {
+
+ /**
+ * 生成实例的方法
+ *
+ * @param p1 参数一
+ * @param p2 参数二
+ * @param p3 参数三
+ * @param p4 参数四
+ * @param p5 参数五
+ * @return 目标对象
+ */
+ T get(P1 p1, P2 p2, P3 p3, P4 p4, P5 p5);
+
+ /**
+ * 将带有参数的Supplier转换为无参{@link Supplier}
+ *
+ * @param p1 参数1
+ * @param p2 参数2
+ * @param p3 参数3
+ * @param p4 参数4
+ * @param p5 参数5
+ * @return {@link Supplier}
+ */
+ default Supplier toSupplier(P1 p1, P2 p2, P3 p3, P4 p4, P5 p5) {
+ return () -> get(p1, p2, p3, p4, p5);
+ }
+}
diff --git a/hutool-core/src/main/java/cn/hutool/core/text/CharSequenceUtil.java b/hutool-core/src/main/java/cn/hutool/core/text/CharSequenceUtil.java
index 2d7bcad89..4a308c14d 100644
--- a/hutool-core/src/main/java/cn/hutool/core/text/CharSequenceUtil.java
+++ b/hutool-core/src/main/java/cn/hutool/core/text/CharSequenceUtil.java
@@ -3601,6 +3601,46 @@ public class CharSequenceUtil {
return stringBuilder.toString();
}
+ /**
+ * 替换指定字符串的指定区间内字符为指定字符串,字符串只重复一次
+ * 此方法使用{@link String#codePoints()}完成拆分替换
+ *
+ * @param str 字符串
+ * @param startInclude 开始位置(包含)
+ * @param endExclude 结束位置(不包含)
+ * @param replacedStr 被替换的字符串
+ * @return 替换后的字符串
+ * @since 3.2.1
+ */
+ public static String replace(CharSequence str, int startInclude, int endExclude, CharSequence replacedStr) {
+ if (isEmpty(str)) {
+ return str(str);
+ }
+ final String originalStr = str(str);
+ int[] strCodePoints = originalStr.codePoints().toArray();
+ final int strLength = strCodePoints.length;
+ if (startInclude > strLength) {
+ return originalStr;
+ }
+ if (endExclude > strLength) {
+ endExclude = strLength;
+ }
+ if (startInclude > endExclude) {
+ // 如果起始位置大于结束位置,不替换
+ return originalStr;
+ }
+
+ final StringBuilder stringBuilder = new StringBuilder();
+ for (int i = 0; i < startInclude; i++) {
+ stringBuilder.append(new String(strCodePoints, i, 1));
+ }
+ stringBuilder.append(replacedStr);
+ for (int i = endExclude; i < strLength; i++) {
+ stringBuilder.append(new String(strCodePoints, i, 1));
+ }
+ return stringBuilder.toString();
+ }
+
/**
* 替换所有正则匹配的文本,并使用自定义函数决定如何替换
* replaceFun可以通过{@link Matcher}提取出匹配到的内容的不同部分,然后经过重新处理、组装变成新的内容放回原位。
diff --git a/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvWriter.java b/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvWriter.java
index 878011082..5c386214e 100644
--- a/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvWriter.java
+++ b/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvWriter.java
@@ -1,6 +1,7 @@
package cn.hutool.core.text.csv;
import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.collection.ArrayIter;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.io.FileUtil;
@@ -45,6 +46,10 @@ public final class CsvWriter implements Closeable, Flushable, Serializable {
* 是否处于新行开始
*/
private boolean newline = true;
+ /**
+ * 是否首行,即CSV开始的位置,当初始化时默认为true,一旦写入内容,为false
+ */
+ private boolean isFirstLine = true;
// --------------------------------------------------------------------------------------------------- Constructor start
@@ -183,13 +188,7 @@ public final class CsvWriter implements Closeable, Flushable, Serializable {
* @throws IORuntimeException IO异常
*/
public CsvWriter write(String[]... lines) throws IORuntimeException {
- if (ArrayUtil.isNotEmpty(lines)) {
- for (final String[] values : lines) {
- appendLine(values);
- }
- flush();
- }
- return this;
+ return write(new ArrayIter<>(lines));
}
/**
@@ -320,9 +319,14 @@ public final class CsvWriter implements Closeable, Flushable, Serializable {
public CsvWriter writeComment(String comment) {
Assert.notNull(this.config.commentCharacter, "Comment is disable!");
try {
+ if(isFirstLine){
+ // 首行不补换行符
+ isFirstLine = false;
+ }else {
+ writer.write(config.lineDelimiter);
+ }
writer.write(this.config.commentCharacter);
writer.write(comment);
- writer.write(config.lineDelimiter);
newline = true;
} catch (IOException e) {
throw new IORuntimeException(e);
@@ -366,12 +370,17 @@ public final class CsvWriter implements Closeable, Flushable, Serializable {
* @param fields 字段列表 ({@code null} 值会被做为空值追加)
* @throws IOException IO异常
*/
- private void doAppendLine(final String... fields) throws IOException {
+ private void doAppendLine(String... fields) throws IOException {
if (null != fields) {
+ if(isFirstLine){
+ // 首行不补换行符
+ isFirstLine = false;
+ }else {
+ writer.write(config.lineDelimiter);
+ }
for (String field : fields) {
appendField(field);
}
- writer.write(config.lineDelimiter);
newline = true;
}
}
diff --git a/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java b/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java
new file mode 100644
index 000000000..84f6d4581
--- /dev/null
+++ b/hutool-core/src/test/java/cn/hutool/core/builder/GenericBuilderTest.java
@@ -0,0 +1,93 @@
+package cn.hutool.core.builder;
+
+import cn.hutool.core.util.StrUtil;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.ToString;
+import lombok.experimental.Accessors;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * {@link GenericBuilder} 单元测试类
+ *
+ * @author TomXin
+ */
+public class GenericBuilderTest {
+
+ @Test
+ public void test() {
+ Box box = GenericBuilder
+ .of(Box::new)
+ .with(Box::setId, 1024L)
+ .with(Box::setTitle, "Hello World!")
+ .with(Box::setLength, 9)
+ .with(Box::setWidth, 8)
+ .with(Box::setHeight, 7)
+ .build();
+
+ Assert.assertEquals(1024L, box.getId().longValue());
+ Assert.assertEquals("Hello World!", box.getTitle());
+ Assert.assertEquals(9, box.getLength().intValue());
+ Assert.assertEquals(8, box.getWidth().intValue());
+ Assert.assertEquals(7, box.getHeight().intValue());
+
+ // 对象修改
+ Box boxModified = GenericBuilder
+ .of(() -> box)
+ .with(Box::setTitle, "Hello Friend!")
+ .with(Box::setLength, 3)
+ .with(Box::setWidth, 4)
+ .with(Box::setHeight, 5)
+ .build();
+
+ Assert.assertEquals(1024L, boxModified.getId().longValue());
+ Assert.assertEquals("Hello Friend!", box.getTitle());
+ Assert.assertEquals(3, boxModified.getLength().intValue());
+ Assert.assertEquals(4, boxModified.getWidth().intValue());
+ Assert.assertEquals(5, boxModified.getHeight().intValue());
+
+ // 多参数构造
+ Box box1 = GenericBuilder
+ .of(Box::new, 2048L, "Hello Partner!", 222, 333, 444)
+ .with(Box::alis)
+ .build();
+
+ Assert.assertEquals(2048L, box1.getId().longValue());
+ Assert.assertEquals("Hello Partner!", box1.getTitle());
+ Assert.assertEquals(222, box1.getLength().intValue());
+ Assert.assertEquals(333, box1.getWidth().intValue());
+ Assert.assertEquals(444, box1.getHeight().intValue());
+ }
+
+ @Getter
+ @Setter
+ @ToString
+ @Accessors(chain = true)
+ public static class Box {
+ private Long id;
+ private String title;
+ private Integer length;
+ private Integer width;
+ private Integer height;
+ private String titleAlias;
+
+ public Box() {
+ }
+
+ public Box(Long id, String title, Integer length, Integer width, Integer height) {
+ this.id = id;
+ this.title = title;
+ this.length = length;
+ this.width = width;
+ this.height = height;
+ }
+
+ public void alis() {
+ if (StrUtil.isNotBlank(this.title)) {
+ this.titleAlias = "TomXin:\"" + title + "\"";
+ }
+ }
+ }
+
+}
diff --git a/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java
index 912df5904..5cecd5de3 100644
--- a/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java
+++ b/hutool-core/src/test/java/cn/hutool/core/collection/ListUtilTest.java
@@ -19,7 +19,7 @@ import java.util.Map;
public class ListUtilTest {
@Test
- public void splitTest(){
+ public void splitTest() {
List> lists = ListUtil.split(null, 3);
Assert.assertEquals(ListUtil.empty(), lists);
@@ -60,7 +60,7 @@ public class ListUtilTest {
}
@Test
- public void splitAvgTest(){
+ public void splitAvgTest() {
List> lists = ListUtil.splitAvg(null, 3);
Assert.assertEquals(ListUtil.empty(), lists);
@@ -80,13 +80,13 @@ public class ListUtilTest {
}
@Test(expected = IllegalArgumentException.class)
- public void splitAvgNotZero(){
+ public void splitAvgNotZero() {
// limit不能小于等于0
ListUtil.splitAvg(Arrays.asList(1, 2, 3, 4), 0);
}
@Test
- public void editTest(){
+ public void editTest() {
List a = ListUtil.toLinkedList("1", "2", "3");
final List filter = (List) CollUtil.edit(a, str -> "edit" + str);
Assert.assertEquals("edit1", filter.get(0));
@@ -104,7 +104,7 @@ public class ListUtilTest {
}
@Test
- public void pageTest(){
+ public void pageTest() {
List a = ListUtil.toLinkedList(1, 2, 3,4,5);
PageUtil.setFirstPageNo(1);
@@ -167,10 +167,13 @@ public class ListUtilTest {
Assert.assertArrayEquals(new int[]{}, pageListData.get(0).stream().mapToInt(Integer::valueOf).toArray());
Assert.assertArrayEquals(new int[]{3, 4}, pageListData.get(1).stream().mapToInt(Integer::valueOf).toArray());
Assert.assertArrayEquals(new int[]{5}, pageListData.get(2).stream().mapToInt(Integer::valueOf).toArray());
+
+ // 恢复默认值,避免影响其他测试用例
+ PageUtil.setFirstPageNo(0);
}
@Test
- public void subTest(){
+ public void subTest() {
final List of = ListUtil.of(1, 2, 3, 4);
final List sub = ListUtil.sub(of, 2, 4);
sub.remove(0);
@@ -181,10 +184,10 @@ public class ListUtilTest {
}
@Test
- public void sortByPropertyTest(){
+ public void sortByPropertyTest() {
@Data
@AllArgsConstructor
- class TestBean{
+ class TestBean {
private int order;
private String name;
}
diff --git a/hutool-core/src/test/java/cn/hutool/core/convert/DateConvertTest.java b/hutool-core/src/test/java/cn/hutool/core/convert/DateConvertTest.java
index f7ffeac24..e3815d7c0 100644
--- a/hutool-core/src/test/java/cn/hutool/core/convert/DateConvertTest.java
+++ b/hutool-core/src/test/java/cn/hutool/core/convert/DateConvertTest.java
@@ -1,15 +1,14 @@
package cn.hutool.core.convert;
+import cn.hutool.core.date.DateUtil;
+import org.junit.Assert;
+import org.junit.Test;
+
import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
-import org.junit.Assert;
-import org.junit.Test;
-
-import cn.hutool.core.date.DateUtil;
-
public class DateConvertTest {
@Test
@@ -28,7 +27,7 @@ public class DateConvertTest {
int dateLong = -1497600000;
Date value = Convert.toDate(dateLong);
Assert.assertNotNull(value);
- Assert.assertEquals("Mon Dec 15 00:00:00 CST 1969", value.toString());
+ Assert.assertEquals("Mon Dec 15 00:00:00 CST 1969", value.toString().replace("GMT+08:00", "CST"));
final java.sql.Date sqlDate = Convert.convert(java.sql.Date.class, dateLong);
Assert.assertNotNull(sqlDate);
@@ -53,18 +52,18 @@ public class DateConvertTest {
java.sql.Date value2 = Convert.convert(java.sql.Date.class, timeLong);
Assert.assertEquals(timeLong, value2.getTime());
}
-
+
@Test
public void toLocalDateTimeTest() {
Date src = new Date();
-
+
LocalDateTime ldt = Convert.toLocalDateTime(src);
Assert.assertEquals(ldt, DateUtil.toLocalDateTime(src));
-
+
Timestamp ts = Timestamp.from(src.toInstant());
ldt = Convert.toLocalDateTime(ts);
Assert.assertEquals(ldt, DateUtil.toLocalDateTime(src));
-
+
String str = "2020-12-12 12:12:12.0";
ldt = Convert.toLocalDateTime(str);
Assert.assertEquals(ldt.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.S")), str);
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 24e7321a8..1e65175fb 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
@@ -692,8 +692,11 @@ public class DateUtilTest {
@Test
public void parseCSTTest() {
String dateStr = "Wed Sep 16 11:26:23 CST 2009";
+ Console.log(TimeZone.getDefault().getDisplayName());
SimpleDateFormat sdf = new SimpleDateFormat(DatePattern.JDK_DATETIME_PATTERN, Locale.US);
+ // Asia/Shanghai是以地区命名的地区标准时,在中国叫CST,因此如果解析CST时不使用"Asia/Shanghai"而使用"GMT+08:00",会导致相差一个小时
+ sdf.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
final DateTime parse = DateUtil.parse(dateStr, sdf);
DateTime dateTime = DateUtil.parseCST(dateStr);
@@ -1004,4 +1007,17 @@ public class DateUtilTest {
final DateTime parse = DateUtil.parse("2021-12-01", DatePattern.NORM_DATE_FORMATTER);
Assert.assertEquals("2021-12-01 00:00:00", parse.toString());
}
+
+ @Test
+ public void isSameWeekTest() {
+ // 周六与周日比较
+ final boolean isSameWeek = DateUtil.isSameWeek(DateTime.of("2022-01-01", "yyyy-MM-dd"), DateTime.of("2022-01-02", "yyyy-MM-dd"), true);
+ Assert.assertTrue(isSameWeek);
+ // 周日与周一比较
+ final boolean isSameWeek1 = DateUtil.isSameWeek(DateTime.of("2022-01-02", "yyyy-MM-dd"), DateTime.of("2022-01-03", "yyyy-MM-dd"), false);
+ Assert.assertTrue(isSameWeek1);
+ // 跨月比较
+ final boolean isSameWeek2 = DateUtil.isSameWeek(DateTime.of("2021-12-29", "yyyy-MM-dd"), DateTime.of("2022-01-01", "yyyy-MM-dd"), true);
+ Assert.assertTrue(isSameWeek2);
+ }
}
diff --git a/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java
index 8f35c59a5..57d6eb717 100644
--- a/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java
+++ b/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java
@@ -197,4 +197,24 @@ public class LocalDateTimeUtilTest {
Assert.assertTrue(LocalDateTimeUtil.isOverlap(oneStartTime2,oneEndTime2,realStartTime,realEndTime));
Assert.assertFalse(LocalDateTimeUtil.isOverlap(oneStartTime3,oneEndTime3,realStartTime,realEndTime));
}
+
+ @Test
+ public void weekOfYearTest(){
+ LocalDate date1 = LocalDate.of(2021, 12, 31);
+ final int weekOfYear1 = LocalDateTimeUtil.weekOfYear(date1);
+ Assert.assertEquals(52, weekOfYear1);
+
+ final int weekOfYear2 = LocalDateTimeUtil.weekOfYear(date1.atStartOfDay());
+ Assert.assertEquals(52, weekOfYear2);
+ }
+
+ @Test
+ public void weekOfYearTest2(){
+ LocalDate date1 = LocalDate.of(2022, 1, 31);
+ final int weekOfYear1 = LocalDateTimeUtil.weekOfYear(date1);
+ Assert.assertEquals(5, weekOfYear1);
+
+ final int weekOfYear2 = LocalDateTimeUtil.weekOfYear(date1.atStartOfDay());
+ Assert.assertEquals(5, weekOfYear2);
+ }
}
diff --git a/hutool-core/src/test/java/cn/hutool/core/date/MonthTest.java b/hutool-core/src/test/java/cn/hutool/core/date/MonthTest.java
index dce94bb09..7a7929f39 100644
--- a/hutool-core/src/test/java/cn/hutool/core/date/MonthTest.java
+++ b/hutool-core/src/test/java/cn/hutool/core/date/MonthTest.java
@@ -37,4 +37,15 @@ public class MonthTest {
lastDay = Month.of(Calendar.DECEMBER).getLastDay(true);
Assert.assertEquals(31, lastDay);
}
+
+ @Test
+ public void toJdkMonthTest(){
+ final java.time.Month month = Month.AUGUST.toJdkMonth();
+ Assert.assertEquals(java.time.Month.AUGUST, month);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void toJdkMonthTest2(){
+ Month.UNDECIMBER.toJdkMonth();
+ }
}
diff --git a/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java
index fbfe82cb2..35be0f241 100644
--- a/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java
+++ b/hutool-core/src/test/java/cn/hutool/core/exceptions/CheckedUtilTest.java
@@ -21,11 +21,8 @@ public class CheckedUtilTest {
@Test
public void sleepTest() {
-
VoidFunc0 func = () -> Thread.sleep(1000L);
func.callWithRuntimeException();
-
-
}
@@ -39,7 +36,6 @@ public class CheckedUtilTest {
} catch (Exception re) {
Assert.assertTrue(re instanceof RuntimeException);
}
-
}
@SuppressWarnings("ConstantConditions")
diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java
index c08e5dbb1..9feb53927 100644
--- a/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java
+++ b/hutool-core/src/test/java/cn/hutool/core/lang/RangeTest.java
@@ -36,6 +36,32 @@ public class RangeTest {
Assert.assertFalse(range.hasNext());
}
+ @Test
+ public void dateRangeFuncTest() {
+ DateTime start = DateUtil.parse("2021-01-01");
+ DateTime end = DateUtil.parse("2021-01-03");
+
+ List dayOfMonthList = DateUtil.rangeFunc(start, end, DateField.DAY_OF_YEAR, a -> DateTime.of(a).dayOfMonth());
+ Assert.assertArrayEquals(dayOfMonthList.toArray(new Integer[]{}), new Integer[]{1, 2, 3});
+
+ List dayOfMonthList2 = DateUtil.rangeFunc(null, null, DateField.DAY_OF_YEAR, a -> DateTime.of(a).dayOfMonth());
+ Assert.assertArrayEquals(dayOfMonthList2.toArray(new Integer[]{}), new Integer[]{});
+ }
+
+ @Test
+ public void dateRangeConsumeTest() {
+ DateTime start = DateUtil.parse("2021-01-01");
+ DateTime end = DateUtil.parse("2021-01-03");
+
+ StringBuilder sb = new StringBuilder();
+ DateUtil.rangeConsume(start, end, DateField.DAY_OF_YEAR, a -> sb.append(DateTime.of(a).dayOfMonth()).append("#"));
+ Assert.assertEquals(sb.toString(), "1#2#3#");
+
+ StringBuilder sb2 = new StringBuilder();
+ DateUtil.rangeConsume(null, null, DateField.DAY_OF_YEAR, a -> sb.append(DateTime.of(a).dayOfMonth()).append("#"));
+ Assert.assertEquals(sb2.toString(), "");
+ }
+
@Test
public void dateRangeTest2() {
DateTime start = DateUtil.parse("2021-01-31");
diff --git a/hutool-core/src/test/java/cn/hutool/core/text/CharSequenceUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/text/CharSequenceUtilTest.java
index 18b84edb2..07afd2b4a 100644
--- a/hutool-core/src/test/java/cn/hutool/core/text/CharSequenceUtilTest.java
+++ b/hutool-core/src/test/java/cn/hutool/core/text/CharSequenceUtilTest.java
@@ -22,6 +22,13 @@ public class CharSequenceUtilTest {
Assert.assertEquals(replace, result);
}
+ @Test
+ public void replaceByStrTest(){
+ String replace = "SSM15930297701BeryAllen";
+ String result = CharSequenceUtil.replace(replace, 5, 12, "***");
+ Assert.assertEquals("SSM15***01BeryAllen", result);
+ }
+
@Test
public void addPrefixIfNotTest(){
String str = "hutool";
diff --git a/hutool-core/src/test/java/cn/hutool/core/text/NamingCaseTest.java b/hutool-core/src/test/java/cn/hutool/core/text/NamingCaseTest.java
index a8800578a..62545d934 100644
--- a/hutool-core/src/test/java/cn/hutool/core/text/NamingCaseTest.java
+++ b/hutool-core/src/test/java/cn/hutool/core/text/NamingCaseTest.java
@@ -12,4 +12,10 @@ public class NamingCaseTest {
.set("customerNickV2", "customer_nick_v2")
.forEach((key, value) -> Assert.assertEquals(value, NamingCase.toUnderlineCase(key)));
}
+
+ @Test
+ public void toUnderLineCaseTest2(){
+ final String wPRunOZTime = NamingCase.toUnderlineCase("wPRunOZTime");
+ Assert.assertEquals("w_P_run_OZ_time", wPRunOZTime);
+ }
}
diff --git a/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvWriterTest.java b/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvWriterTest.java
index 18203196c..e79c9b784 100644
--- a/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvWriterTest.java
+++ b/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvWriterTest.java
@@ -19,6 +19,8 @@ public class CsvWriterTest {
CharsetUtil.CHARSET_GBK, false, csvWriteConfig);
writer.writeHeaderLine("name", "gender", "address");
+ writer.writeLine("张三", "男", "XX市XX区");
+ writer.writeLine("李四", "男", "XX市XX区,01号");
writer.close();
}
}
diff --git a/hutool-core/src/test/java/cn/hutool/core/util/PageUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/PageUtilTest.java
index 46e6e606b..f382b8711 100644
--- a/hutool-core/src/test/java/cn/hutool/core/util/PageUtilTest.java
+++ b/hutool-core/src/test/java/cn/hutool/core/util/PageUtilTest.java
@@ -5,32 +5,31 @@ import org.junit.Test;
/**
* 分页单元测试
- * @author Looly
*
+ * @author Looly
*/
public class PageUtilTest {
-
+
@Test
- public void transToStartEndTest(){
- PageUtil.setFirstPageNo(0);
+ public void transToStartEndTest() {
int[] startEnd1 = PageUtil.transToStartEnd(0, 10);
Assert.assertEquals(0, startEnd1[0]);
Assert.assertEquals(10, startEnd1[1]);
-
+
int[] startEnd2 = PageUtil.transToStartEnd(1, 10);
Assert.assertEquals(10, startEnd2[0]);
Assert.assertEquals(20, startEnd2[1]);
}
-
+
@Test
- public void totalPage(){
+ public void totalPage() {
int totalPage = PageUtil.totalPage(20, 3);
Assert.assertEquals(7, totalPage);
}
-
+
@Test
public void rainbowTest() {
int[] rainbow = PageUtil.rainbow(5, 20, 6);
- Assert.assertArrayEquals(new int[] {3, 4, 5, 6, 7, 8}, rainbow);
+ Assert.assertArrayEquals(new int[]{3, 4, 5, 6, 7, 8}, rainbow);
}
}
diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml
index f314f3191..4785e323b 100644
--- a/hutool-db/pom.xml
+++ b/hutool-db/pom.xml
@@ -27,7 +27,7 @@
3.36.0.3
2.5.2
- 4.0.0
+ 4.1.1
@@ -81,7 +81,7 @@
com.github.chris2018998
beecp
- 3.3.0
+ 3.3.1
slf4j-api
@@ -143,13 +143,13 @@
mysql
mysql-connector-java
- 8.0.27
+ 8.0.28
test
org.postgresql
postgresql
- 42.3.1
+ 42.3.2
test
@@ -167,13 +167,13 @@
com.h2database
h2
- 2.0.206
+ 2.1.210
test
ru.yandex.clickhouse
clickhouse-jdbc
- 0.3.1
+ 0.3.2
test
diff --git a/hutool-db/src/main/java/cn/hutool/db/sql/ConditionGroup.java b/hutool-db/src/main/java/cn/hutool/db/sql/ConditionGroup.java
new file mode 100644
index 000000000..fb33305cd
--- /dev/null
+++ b/hutool-db/src/main/java/cn/hutool/db/sql/ConditionGroup.java
@@ -0,0 +1,54 @@
+package cn.hutool.db.sql;
+
+import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.StrUtil;
+
+import java.util.List;
+
+/**
+ * 条件组
+ * 用于构建复杂where条件
+ *
+ * @author tjh
+ * @since 5.7.21
+ */
+public class ConditionGroup extends Condition {
+ /**
+ * 条件列表
+ */
+ private Condition[] conditions;
+
+ /**
+ * 追加条件
+ *
+ * @param conditions 条件列表
+ */
+ public void addConditions(Condition... conditions) {
+ if (null == this.conditions) {
+ this.conditions = conditions;
+ } else {
+ this.conditions = ArrayUtil.addAll(this.conditions, conditions);
+ }
+ }
+
+ /**
+ * 将条件组转换为条件字符串,使用括号包裹,并回填占位符对应的参数值
+ *
+ * @param paramValues 参数列表,用于回填占位符对应参数值
+ * @return 条件字符串
+ */
+ @Override
+ public String toString(List