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 33016d60b..b41fbbd86 100755
--- a/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java
+++ b/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java
@@ -1097,34 +1097,27 @@ public class FileUtil extends PathUtil {
}
/**
- * 移动文件或者目录
+ * 移动文件或目录到目标中,例如:
+ *
+ * - 如果src为文件,target为目录,则移动到目标目录下,存在同名文件则按照是否覆盖参数执行。
+ * - 如果src为文件,target为文件,则按照是否覆盖参数执行。
+ * - 如果src为文件,target为不存在的路径,则重命名源文件到目标指定的文件,如moveContent("/a/b", "/c/d"), d不存在,则b变成d。
+ * - 如果src为目录,target为文件,抛出{@link IllegalArgumentException}
+ * - 如果src为目录,target为目录,则将源目录及其内容移动到目标路径目录中,如move("/a/b", "/c/d"),结果为"/c/d/b"
+ * - 如果src为目录,target为不存在的路径,则创建目标路径为目录,将源目录及其内容移动到目标路径目录中,如move("/a/b", "/c/d"),结果为"/c/d/b"
+ *
*
- * @param src 源文件或者目录
- * @param target 目标文件或者目录
- * @param isOverride 是否覆盖目标,只有目标为文件才覆盖
+ * @param src 源文件或目录路径
+ * @param target 目标路径,如果为目录,则移动到此目录下
+ * @param isOverride 是否覆盖目标文件
+ * @return 目标文件或目录
* @throws IORuntimeException IO异常
* @see PathUtil#move(Path, Path, boolean)
*/
- public static void move(final File src, final File target, final boolean isOverride) throws IORuntimeException {
+ public static File move(final File src, final File target, final boolean isOverride) throws IORuntimeException {
Assert.notNull(src, "Src file must be not null!");
Assert.notNull(target, "target file must be not null!");
- move(src.toPath(), target.toPath(), isOverride);
- }
-
- /**
- * 移动文件或者目录
- *
- * @param src 源文件或者目录
- * @param target 目标文件或者目录
- * @param isOverride 是否覆盖目标,只有目标为文件才覆盖
- * @throws IORuntimeException IO异常
- * @see PathUtil#moveContent(Path, Path, boolean)
- * @since 5.7.9
- */
- public static void moveContent(final File src, final File target, final boolean isOverride) throws IORuntimeException {
- Assert.notNull(src, "Src file must be not null!");
- Assert.notNull(target, "target file must be not null!");
- moveContent(src.toPath(), target.toPath(), isOverride);
+ return move(src.toPath(), target.toPath(), isOverride).toFile();
}
/**
@@ -1763,7 +1756,7 @@ public class FileUtil extends PathUtil {
* @param file 文件
* @return 输入流
* @throws IORuntimeException 文件未找到
- * @see IoUtil#toStream(File)
+ * @see IoUtil#toStream(File)
*/
public static BufferedInputStream getInputStream(final File file) throws IORuntimeException {
return IoUtil.toBuffered(IoUtil.toStream(file));
@@ -3048,7 +3041,7 @@ public class FileUtil extends PathUtil {
* getParent(file("d:/aaa/bbb/cc/ddd")) -》 "d:/aaa/bbb/cc"
*
*
- * @param file 目录或文件
+ * @param file 目录或文件
* @return 路径File,如果不存在返回null
* @since 6.0.0
*/
diff --git a/hutool-core/src/main/java/cn/hutool/core/io/file/PathMover.java b/hutool-core/src/main/java/cn/hutool/core/io/file/PathMover.java
new file mode 100755
index 000000000..30c53a11c
--- /dev/null
+++ b/hutool-core/src/main/java/cn/hutool/core/io/file/PathMover.java
@@ -0,0 +1,148 @@
+package cn.hutool.core.io.file;
+
+import cn.hutool.core.bean.copier.CopyOptions;
+import cn.hutool.core.io.IORuntimeException;
+import cn.hutool.core.io.file.visitor.MoveVisitor;
+import cn.hutool.core.lang.Assert;
+import cn.hutool.core.util.ObjUtil;
+
+import java.io.IOException;
+import java.nio.file.*;
+
+/**
+ * 文件移动封装
+ *
+ * @author looly
+ * @since 6.0.0
+ */
+public class PathMover {
+
+ /**
+ * 创建文件或目录移动器
+ *
+ * @param src 源文件或目录
+ * @param target 目标文件或目录
+ * @param isOverride 是否覆盖目标文件
+ * @return {@link PathMover}
+ */
+ public static PathMover of(final Path src, final Path target, final boolean isOverride) {
+ return of(src, target, isOverride ? new CopyOption[]{StandardCopyOption.REPLACE_EXISTING} : new CopyOption[]{});
+ }
+
+ /**
+ * 创建文件或目录移动器
+ *
+ * @param src 源文件或目录
+ * @param target 目标文件或目录
+ * @param options 移动参数
+ * @return {@link PathMover}
+ */
+ public static PathMover of(final Path src, final Path target, final CopyOption[] options) {
+ return new PathMover(src, target, options);
+ }
+
+ private final Path src;
+ private final Path target;
+ private final CopyOption[] options;
+
+ /**
+ * 构造
+ *
+ * @param src 源文件或目录
+ * @param target 目标文件或目录
+ * @param options 移动参数
+ */
+ public PathMover(final Path src, final Path target, final CopyOption[] options) {
+ this.src = Assert.notNull(src, "Src path must be not null !");
+ this.target = Assert.notNull(target, "Target path must be not null !");
+ this.options = options;
+ }
+
+ /**
+ * 移动文件或目录到目标中,例如:
+ *
+ * - 如果src为文件,target为目录,则移动到目标目录下,存在同名文件则按照是否覆盖参数执行。
+ * - 如果src为文件,target为文件,则按照是否覆盖参数执行。
+ * - 如果src为文件,target为不存在的路径,则重命名源文件到目标指定的文件,如moveContent("/a/b", "/c/d"), d不存在,则b变成d。
+ * - 如果src为目录,target为文件,抛出{@link IllegalArgumentException}
+ * - 如果src为目录,target为目录,则将源目录及其内容移动到目标路径目录中,如move("/a/b", "/c/d"),结果为"/c/d/b"
+ * - 如果src为目录,target为不存在的路径,则创建目标路径为目录,将源目录及其内容移动到目标路径目录中,如move("/a/b", "/c/d"),结果为"/c/d/b"
+ *
+ *
+ * @return 目标文件Path
+ */
+ public Path move() {
+ final Path src = this.src;
+ Path target = this.target;
+ final CopyOption[] options = ObjUtil.defaultIfNull(this.options, new CopyOption[]{});
+
+ if (false == PathUtil.exists(target, false) || PathUtil.isDirectory(target)) {
+ // 创建子路径的情况,1是目标是目录,需要移动到目录下,2是目标不能存在,自动创建目录
+ target = target.resolve(src.getFileName());
+ }
+
+ // issue#2893 target 不存在导致NoSuchFileException
+ if (Files.exists(target) && PathUtil.equals(src, target)) {
+ // issue#2845,当用户传入目标路径与源路径一致时,直接返回,否则会导致删除风险。
+ return target;
+ }
+
+ // 自动创建目标的父目录
+ PathUtil.mkParentDirs(target);
+ try {
+ return Files.move(src, target, options);
+ } catch (final IOException e) {
+ if (e instanceof FileAlreadyExistsException) {
+ // 目标文件已存在,直接抛出异常
+ // issue#I4QV0L@Gitee
+ throw new IORuntimeException(e);
+ }
+ // 移动失败,可能是跨分区移动导致的,采用递归移动方式
+ try {
+ Files.walkFileTree(src, new MoveVisitor(src, target, options));
+ // 移动后空目录没有删除,
+ PathUtil.del(src);
+ } catch (final IOException e2) {
+ throw new IORuntimeException(e2);
+ }
+ return target;
+ }
+ }
+
+ /**
+ * 移动文件或目录内容到目标中,例如:
+ *
+ * - 如果src为文件,target为目录,则移动到目标目录下,存在同名文件则按照是否覆盖参数执行。
+ * - 如果src为文件,target为文件,则按照是否覆盖参数执行。
+ * - 如果src为文件,target为不存在的路径,则重命名源文件到目标指定的文件,如moveContent("/a/b", "/c/d"), d不存在,则b变成d。
+ * - 如果src为目录,target为文件,抛出{@link IllegalArgumentException}
+ * - 如果src为目录,target为目录,则将源目录下的内容移动到目标路径目录中。
+ * - 如果src为目录,target为不存在的路径,则创建目标路径为目录,将源目录下的内容移动到目标路径目录中。
+ *
+ *
+ * @return 目标文件Path
+ */
+ public Path moveContent() {
+ final Path src = this.src;
+ final Path target = this.target;
+ final CopyOption[] options = ObjUtil.defaultIfNull(this.options, new CopyOption[]{});
+
+ // 移动失败,可能是跨分区移动导致的,采用递归移动方式
+ try {
+ if (false == PathUtil.isDirectory(src)) {
+ // 文件移动到目标目录或文件
+ return Files.move(src, target, options);
+ }
+
+ if (false == PathUtil.isDirectory(target)) {
+ throw new IllegalArgumentException("Can not move dir content to a file");
+ }
+
+ // 移动源目录下的内容而不删除目录
+ Files.walkFileTree(src, new MoveVisitor(src, target, options));
+ } catch (final IOException e) {
+ throw new IORuntimeException(e);
+ }
+ return target;
+ }
+}
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 84eb66659..e7bb8e108 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
@@ -4,31 +4,12 @@ import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.io.file.visitor.CopyVisitor;
import cn.hutool.core.io.file.visitor.DelVisitor;
-import cn.hutool.core.io.file.visitor.MoveVisitor;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.CharsetUtil;
-import java.io.BufferedInputStream;
-import java.io.BufferedOutputStream;
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileFilter;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
+import java.io.*;
import java.nio.charset.Charset;
-import java.nio.file.AccessDeniedException;
-import java.nio.file.CopyOption;
-import java.nio.file.DirectoryStream;
-import java.nio.file.FileAlreadyExistsException;
-import java.nio.file.FileVisitOption;
-import java.nio.file.FileVisitResult;
-import java.nio.file.FileVisitor;
-import java.nio.file.Files;
-import java.nio.file.LinkOption;
-import java.nio.file.Path;
-import java.nio.file.SimpleFileVisitor;
-import java.nio.file.StandardCopyOption;
+import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.EnumSet;
@@ -470,75 +451,43 @@ public class PathUtil {
}
/**
- * 移动文件或目录
- * 当目标是目录时,会将源文件或文件夹整体移动至目标目录下
- * 例如:
+ * 移动文件或目录到目标中,例如:
*
- * - move("/usr/aaa/abc.txt", "/usr/bbb")结果为:"/usr/bbb/abc.txt"
- * - move("/usr/aaa", "/usr/bbb")结果为:"/usr/bbb/aaa"
+ * - 如果src为文件,target为目录,则移动到目标目录下,存在同名文件则按照是否覆盖参数执行。
+ * - 如果src为文件,target为文件,则按照是否覆盖参数执行。
+ * - 如果src为文件,target为不存在的路径,则重命名源文件到目标指定的文件,如moveContent("/a/b", "/c/d"), d不存在,则b变成d。
+ * - 如果src为目录,target为文件,抛出{@link IllegalArgumentException}
+ * - 如果src为目录,target为目录,则将源目录及其内容移动到目标路径目录中,如move("/a/b", "/c/d"),结果为"/c/d/b"
+ * - 如果src为目录,target为不存在的路径,则创建目标路径为目录,将源目录及其内容移动到目标路径目录中,如move("/a/b", "/c/d"),结果为"/c/d/b"
*
*
* @param src 源文件或目录路径
* @param target 目标路径,如果为目录,则移动到此目录下
* @param isOverride 是否覆盖目标文件
* @return 目标文件Path
- * @since 5.5.1
*/
- public static Path move(final Path src, Path target, final boolean isOverride) {
- Assert.notNull(src, "Src path must be not null !");
- Assert.notNull(target, "Target path must be not null !");
-
- if (isDirectory(target)) {
- target = target.resolve(src.getFileName());
- }
- return moveContent(src, target, isOverride);
+ public static Path move(final Path src, final Path target, final boolean isOverride) {
+ return PathMover.of(src, target, isOverride).move();
}
/**
- * 移动文件或目录内容到目标目录中,例如:
+ * 移动文件或目录内容到目标中,例如:
*
- * - moveContent("/usr/aaa/abc.txt", "/usr/bbb")结果为:"/usr/bbb/abc.txt"
- * - moveContent("/usr/aaa", "/usr/bbb")结果为:"/usr/bbb"
+ * - 如果src为文件,target为目录,则移动到目标目录下,存在同名文件则按照是否覆盖参数执行。
+ * - 如果src为文件,target为文件,则按照是否覆盖参数执行。
+ * - 如果src为文件,target为不存在的路径,则重命名源文件到目标指定的文件,如moveContent("/a/b", "/c/d"), d不存在,则b变成d。
+ * - 如果src为目录,target为文件,抛出{@link IllegalArgumentException}
+ * - 如果src为目录,target为目录,则将源目录下的内容移动到目标路径目录中。
+ * - 如果src为目录,target为不存在的路径,则创建目标路径为目录,将源目录下的内容移动到目标路径目录中。
*
*
* @param src 源文件或目录路径
* @param target 目标路径,如果为目录,则移动到此目录下
* @param isOverride 是否覆盖目标文件
* @return 目标文件Path
- * @since 5.7.9
*/
public static Path moveContent(final Path src, final Path target, final boolean isOverride) {
- Assert.notNull(src, "Src path must be not null !");
- Assert.notNull(target, "Target path must be not null !");
-
- // issue#2893 target 不存在导致NoSuchFileException
- if(Files.exists(target) && equals(src, target)){
- // issue#2845,当用户传入目标路径与源路径一致时,直接返回,否则会导致删除风险。
- return target;
- }
-
- final CopyOption[] options = isOverride ? new CopyOption[]{StandardCopyOption.REPLACE_EXISTING} : new CopyOption[]{};
-
- // 自动创建目标的父目录
- mkParentDirs(target);
- try {
- return Files.move(src, target, options);
- } catch (final IOException e) {
- if(e instanceof FileAlreadyExistsException){
- // 目标文件已存在,直接抛出异常
- // issue#I4QV0L@Gitee
- throw new IORuntimeException(e);
- }
- // 移动失败,可能是跨分区移动导致的,采用递归移动方式
- try {
- Files.walkFileTree(src, new MoveVisitor(src, target, options));
- // 移动后空目录没有删除,
- del(src);
- } catch (final IOException e2) {
- throw new IORuntimeException(e2);
- }
- return target;
- }
+ return PathMover.of(src, target, isOverride).moveContent();
}
/**
diff --git a/hutool-core/src/test/java/cn/hutool/core/io/file/IssueI666HBTest.java b/hutool-core/src/test/java/cn/hutool/core/io/file/IssueI666HBTest.java
new file mode 100755
index 000000000..46285b176
--- /dev/null
+++ b/hutool-core/src/test/java/cn/hutool/core/io/file/IssueI666HBTest.java
@@ -0,0 +1,47 @@
+package cn.hutool.core.io.file;
+
+import cn.hutool.core.io.FileUtil;
+import org.junit.Ignore;
+import org.junit.Test;
+
+/**
+ * 移动情况测试,环境:
+ *
+ * d:/test/dir1/test1.txt 文件
+ * d:/test/dir2/ 空目录
+ *
+ */
+public class IssueI666HBTest {
+
+ @Test
+ @Ignore
+ public void moveDirToDirTest() {
+ // 目录移动到目录,将整个目录移动
+ // 会将dir1及其内容移动到dir2下,变成dir2/dir1
+ FileUtil.move(FileUtil.file("d:/test/dir1"), FileUtil.file("d:/test/dir2"), false);
+ }
+
+ @Test
+ @Ignore
+ public void moveFileToDirTest() {
+ // 文件移动到目录
+ // 会将test1.txt移动到dir2下,变成dir2/test1.txt
+ FileUtil.move(FileUtil.file("d:/test/dir1/test1.txt"), FileUtil.file("d:/test/dir2"), false);
+ }
+
+ @Test
+ @Ignore
+ public void moveDirToDirNotExistTest() {
+ // 目录移动到目标,dir3不存在,将整个目录移动
+ // 会将目录dir1变成目录dir3
+ FileUtil.move(FileUtil.file("d:/test/dir1"), FileUtil.file("d:/test/dir3"), false);
+ }
+
+ @Test
+ @Ignore
+ public void moveFileToTargetNotExistTest() {
+ // 目录移动到目录,将整个目录移动
+ // 会将test1.txt重命名为test2
+ FileUtil.move(FileUtil.file("d:/test/dir1/test1.txt"), FileUtil.file("d:/test/test2"), false);
+ }
+}