mirror of
https://gitee.com/chinabugotech/hutool.git
synced 2025-04-19 03:01:48 +08:00
add PathMover
This commit is contained in:
parent
9784e8e2b4
commit
7713db1730
@ -1097,34 +1097,27 @@ public class FileUtil extends PathUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 移动文件或者目录
|
||||
* 移动文件或目录到目标中,例如:
|
||||
* <ul>
|
||||
* <li>如果src为文件,target为目录,则移动到目标目录下,存在同名文件则按照是否覆盖参数执行。</li>
|
||||
* <li>如果src为文件,target为文件,则按照是否覆盖参数执行。</li>
|
||||
* <li>如果src为文件,target为不存在的路径,则重命名源文件到目标指定的文件,如moveContent("/a/b", "/c/d"), d不存在,则b变成d。</li>
|
||||
* <li>如果src为目录,target为文件,抛出{@link IllegalArgumentException}</li>
|
||||
* <li>如果src为目录,target为目录,则将源目录及其内容移动到目标路径目录中,如move("/a/b", "/c/d"),结果为"/c/d/b"</li>
|
||||
* <li>如果src为目录,target为不存在的路径,则创建目标路径为目录,将源目录及其内容移动到目标路径目录中,如move("/a/b", "/c/d"),结果为"/c/d/b"</li>
|
||||
* </ul>
|
||||
*
|
||||
* @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"
|
||||
* </pre>
|
||||
*
|
||||
* @param file 目录或文件
|
||||
* @param file 目录或文件
|
||||
* @return 路径File,如果不存在返回null
|
||||
* @since 6.0.0
|
||||
*/
|
||||
|
148
hutool-core/src/main/java/cn/hutool/core/io/file/PathMover.java
Executable file
148
hutool-core/src/main/java/cn/hutool/core/io/file/PathMover.java
Executable file
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 移动文件或目录到目标中,例如:
|
||||
* <ul>
|
||||
* <li>如果src为文件,target为目录,则移动到目标目录下,存在同名文件则按照是否覆盖参数执行。</li>
|
||||
* <li>如果src为文件,target为文件,则按照是否覆盖参数执行。</li>
|
||||
* <li>如果src为文件,target为不存在的路径,则重命名源文件到目标指定的文件,如moveContent("/a/b", "/c/d"), d不存在,则b变成d。</li>
|
||||
* <li>如果src为目录,target为文件,抛出{@link IllegalArgumentException}</li>
|
||||
* <li>如果src为目录,target为目录,则将源目录及其内容移动到目标路径目录中,如move("/a/b", "/c/d"),结果为"/c/d/b"</li>
|
||||
* <li>如果src为目录,target为不存在的路径,则创建目标路径为目录,将源目录及其内容移动到目标路径目录中,如move("/a/b", "/c/d"),结果为"/c/d/b"</li>
|
||||
* </ul>
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 移动文件或目录内容到目标中,例如:
|
||||
* <ul>
|
||||
* <li>如果src为文件,target为目录,则移动到目标目录下,存在同名文件则按照是否覆盖参数执行。</li>
|
||||
* <li>如果src为文件,target为文件,则按照是否覆盖参数执行。</li>
|
||||
* <li>如果src为文件,target为不存在的路径,则重命名源文件到目标指定的文件,如moveContent("/a/b", "/c/d"), d不存在,则b变成d。</li>
|
||||
* <li>如果src为目录,target为文件,抛出{@link IllegalArgumentException}</li>
|
||||
* <li>如果src为目录,target为目录,则将源目录下的内容移动到目标路径目录中。</li>
|
||||
* <li>如果src为目录,target为不存在的路径,则创建目标路径为目录,将源目录下的内容移动到目标路径目录中。</li>
|
||||
* </ul>
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
}
|
@ -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 {
|
||||
}
|
||||
|
||||
/**
|
||||
* 移动文件或目录<br>
|
||||
* 当目标是目录时,会将源文件或文件夹整体移动至目标目录下<br>
|
||||
* 例如:
|
||||
* 移动文件或目录到目标中,例如:
|
||||
* <ul>
|
||||
* <li>move("/usr/aaa/abc.txt", "/usr/bbb")结果为:"/usr/bbb/abc.txt"</li>
|
||||
* <li>move("/usr/aaa", "/usr/bbb")结果为:"/usr/bbb/aaa"</li>
|
||||
* <li>如果src为文件,target为目录,则移动到目标目录下,存在同名文件则按照是否覆盖参数执行。</li>
|
||||
* <li>如果src为文件,target为文件,则按照是否覆盖参数执行。</li>
|
||||
* <li>如果src为文件,target为不存在的路径,则重命名源文件到目标指定的文件,如moveContent("/a/b", "/c/d"), d不存在,则b变成d。</li>
|
||||
* <li>如果src为目录,target为文件,抛出{@link IllegalArgumentException}</li>
|
||||
* <li>如果src为目录,target为目录,则将源目录及其内容移动到目标路径目录中,如move("/a/b", "/c/d"),结果为"/c/d/b"</li>
|
||||
* <li>如果src为目录,target为不存在的路径,则创建目标路径为目录,将源目录及其内容移动到目标路径目录中,如move("/a/b", "/c/d"),结果为"/c/d/b"</li>
|
||||
* </ul>
|
||||
*
|
||||
* @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();
|
||||
}
|
||||
|
||||
/**
|
||||
* 移动文件或目录内容到目标目录中,例如:
|
||||
* 移动文件或目录内容到目标中,例如:
|
||||
* <ul>
|
||||
* <li>moveContent("/usr/aaa/abc.txt", "/usr/bbb")结果为:"/usr/bbb/abc.txt"</li>
|
||||
* <li>moveContent("/usr/aaa", "/usr/bbb")结果为:"/usr/bbb"</li>
|
||||
* <li>如果src为文件,target为目录,则移动到目标目录下,存在同名文件则按照是否覆盖参数执行。</li>
|
||||
* <li>如果src为文件,target为文件,则按照是否覆盖参数执行。</li>
|
||||
* <li>如果src为文件,target为不存在的路径,则重命名源文件到目标指定的文件,如moveContent("/a/b", "/c/d"), d不存在,则b变成d。</li>
|
||||
* <li>如果src为目录,target为文件,抛出{@link IllegalArgumentException}</li>
|
||||
* <li>如果src为目录,target为目录,则将源目录下的内容移动到目标路径目录中。</li>
|
||||
* <li>如果src为目录,target为不存在的路径,则创建目标路径为目录,将源目录下的内容移动到目标路径目录中。</li>
|
||||
* </ul>
|
||||
*
|
||||
* @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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
47
hutool-core/src/test/java/cn/hutool/core/io/file/IssueI666HBTest.java
Executable file
47
hutool-core/src/test/java/cn/hutool/core/io/file/IssueI666HBTest.java
Executable file
@ -0,0 +1,47 @@
|
||||
package cn.hutool.core.io.file;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* 移动情况测试,环境:
|
||||
* <pre>
|
||||
* d:/test/dir1/test1.txt 文件
|
||||
* d:/test/dir2/ 空目录
|
||||
* </pre>
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user