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 { } /** - * 移动文件或者目录 + * 移动文件或目录到目标中,例如: + * * - * @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; + } + + /** + * 移动文件或目录到目标中,例如: + * + * + * @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; + } + } + + /** + * 移动文件或目录内容到目标中,例如: + * + * + * @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 { } /** - * 移动文件或目录
- * 当目标是目录时,会将源文件或文件夹整体移动至目标目录下
- * 例如: + * 移动文件或目录到目标中,例如: * * * @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(); } /** - * 移动文件或目录内容到目标目录中,例如: + * 移动文件或目录内容到目标中,例如: * * * @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); + } +}