This commit is contained in:
Looly 2023-03-05 19:59:00 +08:00
parent 7713db1730
commit fb49d79f8b
3 changed files with 109 additions and 29 deletions

View File

@ -23,7 +23,7 @@ public class PathMover {
* @param src 源文件或目录 * @param src 源文件或目录
* @param target 目标文件或目录 * @param target 目标文件或目录
* @param isOverride 是否覆盖目标文件 * @param isOverride 是否覆盖目标文件
* @return {@link PathMover} * @return {@code PathMover}
*/ */
public static PathMover of(final Path src, final Path target, final boolean isOverride) { 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[]{}); return of(src, target, isOverride ? new CopyOption[]{StandardCopyOption.REPLACE_EXISTING} : new CopyOption[]{});
@ -35,7 +35,7 @@ public class PathMover {
* @param src 源文件或目录 * @param src 源文件或目录
* @param target 目标文件或目录 * @param target 目标文件或目录
* @param options 移动参数 * @param options 移动参数
* @return {@link PathMover} * @return {@code PathMover}
*/ */
public static PathMover of(final Path src, final Path target, final CopyOption[] options) { public static PathMover of(final Path src, final Path target, final CopyOption[] options) {
return new PathMover(src, target, options); return new PathMover(src, target, options);
@ -48,25 +48,30 @@ public class PathMover {
/** /**
* 构造 * 构造
* *
* @param src 源文件或目录 * @param src 源文件或目录不能为{@code null}且必须存在
* @param target 目标文件或目录 * @param target 目标文件或目录
* @param options 移动参数 * @param options 移动参数
*/ */
public PathMover(final Path src, final Path target, final CopyOption[] options) { public PathMover(final Path src, final Path target, final CopyOption[] options) {
this.src = Assert.notNull(src, "Src path must be not null !"); Assert.notNull(target, "Src path must be not null !");
if(false == PathUtil.exists(src, false)){
throw new IllegalArgumentException("Src path is not exist!");
}
this.src = src;
this.target = Assert.notNull(target, "Target path must be not null !"); this.target = Assert.notNull(target, "Target path must be not null !");
this.options = options; this.options = ObjUtil.defaultIfNull(options, new CopyOption[]{});;
} }
/** /**
* 移动文件或目录到目标中例如 * 移动文件或目录到目标中例如
* <ul> * <ul>
* <li>如果src和target为同一文件或目录直接返回target</li>
* <li>如果src为文件target为目录则移动到目标目录下存在同名文件则按照是否覆盖参数执行</li> * <li>如果src为文件target为目录则移动到目标目录下存在同名文件则按照是否覆盖参数执行</li>
* <li>如果src为文件target为文件则按照是否覆盖参数执行</li> * <li>如果src为文件target为文件则按照是否覆盖参数执行</li>
* <li>如果src为文件target为不存在的路径则重命名源文件到目标指定的文件如moveContent("/a/b", "/c/d"), d不存在则b变成d</li> * <li>如果src为文件target为不存在的路径则重命名源文件到目标指定的文件如moveContent("/a/b", "/c/d"), d不存在则b变成d</li>
* <li>如果src为目录target为文件抛出{@link IllegalArgumentException}</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>
* <li>如果src为目录target为不存在的路径创建目标路径为目录将源目录及其内容移动到目标路径目录中如move("/a/b", "/c/d")结果为"/c/d/b"</li> * <li>如果src为目录target为不存在的路径重命名src到target如move("/a/b", "/c/d")结果为"/c/d/"相当于b重命名为d</li>
* </ul> * </ul>
* *
* @return 目标文件Path * @return 目标文件Path
@ -74,9 +79,9 @@ public class PathMover {
public Path move() { public Path move() {
final Path src = this.src; final Path src = this.src;
Path target = this.target; Path target = this.target;
final CopyOption[] options = ObjUtil.defaultIfNull(this.options, new CopyOption[]{}); final CopyOption[] options = this.options;
if (false == PathUtil.exists(target, false) || PathUtil.isDirectory(target)) { if (PathUtil.isDirectory(target)) {
// 创建子路径的情况1是目标是目录需要移动到目录下2是目标不能存在自动创建目录 // 创建子路径的情况1是目标是目录需要移动到目录下2是目标不能存在自动创建目录
target = target.resolve(src.getFileName()); target = target.resolve(src.getFileName());
} }
@ -98,13 +103,9 @@ public class PathMover {
throw new IORuntimeException(e); throw new IORuntimeException(e);
} }
// 移动失败可能是跨分区移动导致的采用递归移动方式 // 移动失败可能是跨分区移动导致的采用递归移动方式
try { walkMove(src, target, options);
Files.walkFileTree(src, new MoveVisitor(src, target, options)); // 移动后删除空目录
// 移动后空目录没有删除 PathUtil.del(src);
PathUtil.del(src);
} catch (final IOException e2) {
throw new IORuntimeException(e2);
}
return target; return target;
} }
} }
@ -124,25 +125,43 @@ public class PathMover {
*/ */
public Path moveContent() { public Path moveContent() {
final Path src = this.src; final Path src = this.src;
if (PathUtil.isNotDirectory(target, false)) {
// 文件移动调用move方法
return move();
}
final Path target = this.target; final Path target = this.target;
final CopyOption[] options = ObjUtil.defaultIfNull(this.options, new CopyOption[]{}); if (PathUtil.isNotDirectory(target, false)) {
// 目标不能为文件
throw new IllegalArgumentException("Can not move dir content to a file");
}
// issue#2893 target 不存在导致NoSuchFileException
if (PathUtil.equals(src, target)) {
// issue#2845当用户传入目标路径与源路径一致时直接返回否则会导致删除风险
return target;
}
final CopyOption[] options = this.options;
// 移动失败可能是跨分区移动导致的采用递归移动方式 // 移动失败可能是跨分区移动导致的采用递归移动方式
walkMove(src, target, options);
return target;
}
/**
* 递归移动
*
* @param src 源目录
* @param target 目标目录
* @param options 移动参数
*/
private static void walkMove(final Path src, final Path target, final CopyOption... options) {
try { 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)); Files.walkFileTree(src, new MoveVisitor(src, target, options));
} catch (final IOException e) { } catch (final IOException e) {
throw new IORuntimeException(e); throw new IORuntimeException(e);
} }
return target;
} }
} }

View File

@ -252,6 +252,22 @@ public class PathUtil {
return isDirectory(path, false); return isDirectory(path, false);
} }
/**
* 判断是否为非目录
* <ul>
* <li>如果path为{@code null}返回{@code false}</li>
* <li>如果path不存在返回{@code false}</li>
* </ul>
*
* @param path {@link Path}
* @param isFollowLinks 是否追踪到软链对应的真实地址
* @return 如果为目录true
* @since 3.1.0
*/
public static boolean isNotDirectory(final Path path, final boolean isFollowLinks) {
return exists(path, isFollowLinks) && false == isDirectory(path, isFollowLinks);
}
/** /**
* 判断是否为目录如果file为null则返回false * 判断是否为目录如果file为null则返回false
* *
@ -438,6 +454,7 @@ public class PathUtil {
* *
* <pre> * <pre>
* FileUtil.rename(file, "aaa.jpg", false) xx/xx.png =xx/aaa.jpg * FileUtil.rename(file, "aaa.jpg", false) xx/xx.png =xx/aaa.jpg
* FileUtil.rename(dir, "dir2", false) xx/xx/ =xx/dir2/
* </pre> * </pre>
* *
* @param path 被修改的文件 * @param path 被修改的文件
@ -453,12 +470,13 @@ public class PathUtil {
/** /**
* 移动文件或目录到目标中例如 * 移动文件或目录到目标中例如
* <ul> * <ul>
* <li>如果src和target为同一文件或目录直接返回target</li>
* <li>如果src为文件target为目录则移动到目标目录下存在同名文件则按照是否覆盖参数执行</li> * <li>如果src为文件target为目录则移动到目标目录下存在同名文件则按照是否覆盖参数执行</li>
* <li>如果src为文件target为文件则按照是否覆盖参数执行</li> * <li>如果src为文件target为文件则按照是否覆盖参数执行</li>
* <li>如果src为文件target为不存在的路径则重命名源文件到目标指定的文件如moveContent("/a/b", "/c/d"), d不存在则b变成d</li> * <li>如果src为文件target为不存在的路径则重命名源文件到目标指定的文件如moveContent("/a/b", "/c/d"), d不存在则b变成d</li>
* <li>如果src为目录target为文件抛出{@link IllegalArgumentException}</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>
* <li>如果src为目录target为不存在的路径创建目标路径为目录将源目录及其内容移动到目标路径目录中如move("/a/b", "/c/d")结果为"/c/d/b"</li> * <li>如果src为目录target为不存在的路径重命名src到target如move("/a/b", "/c/d")结果为"/c/d/"相当于b重命名为d</li>
* </ul> * </ul>
* *
* @param src 源文件或目录路径 * @param src 源文件或目录路径
@ -539,12 +557,15 @@ public class PathUtil {
/** /**
* 判断文件或目录是否存在 * 判断文件或目录是否存在
* *
* @param path 文件 * @param path 文件{@code null}返回{@code false}
* @param isFollowLinks 是否跟踪软链快捷方式 * @param isFollowLinks 是否跟踪软链快捷方式
* @return 是否存在 * @return 是否存在
* @since 5.5.3 * @since 5.5.3
*/ */
public static boolean exists(final Path path, final boolean isFollowLinks) { public static boolean exists(final Path path, final boolean isFollowLinks) {
if (null == path) {
return false;
}
final LinkOption[] options = isFollowLinks ? new LinkOption[0] : new LinkOption[]{LinkOption.NOFOLLOW_LINKS}; final LinkOption[] options = isFollowLinks ? new LinkOption[0] : new LinkOption[]{LinkOption.NOFOLLOW_LINKS};
return Files.exists(path, options); return Files.exists(path, options);
} }

View File

@ -21,6 +21,16 @@ public class IssueI666HBTest {
FileUtil.move(FileUtil.file("d:/test/dir1"), FileUtil.file("d:/test/dir2"), false); FileUtil.move(FileUtil.file("d:/test/dir1"), FileUtil.file("d:/test/dir2"), false);
} }
@Test
@Ignore
public void moveContentDirToDirTest() {
// 目录内容移动到目录
// 移动内容不移除目录本身
PathUtil.moveContent(
FileUtil.file("d:/test/dir1").toPath(),
FileUtil.file("d:/test/dir2").toPath(), false);
}
@Test @Test
@Ignore @Ignore
public void moveFileToDirTest() { public void moveFileToDirTest() {
@ -29,6 +39,16 @@ public class IssueI666HBTest {
FileUtil.move(FileUtil.file("d:/test/dir1/test1.txt"), FileUtil.file("d:/test/dir2"), false); FileUtil.move(FileUtil.file("d:/test/dir1/test1.txt"), FileUtil.file("d:/test/dir2"), false);
} }
@Test
@Ignore
public void moveContentFileToDirTest() {
// 文件移动到目录
// 会将test1.txt移动到dir2下变成dir2/test1.txt
PathUtil.moveContent(
FileUtil.file("d:/test/dir1/test1.txt").toPath(),
FileUtil.file("d:/test/dir2").toPath(), false);
}
@Test @Test
@Ignore @Ignore
public void moveDirToDirNotExistTest() { public void moveDirToDirNotExistTest() {
@ -37,11 +57,31 @@ public class IssueI666HBTest {
FileUtil.move(FileUtil.file("d:/test/dir1"), FileUtil.file("d:/test/dir3"), false); FileUtil.move(FileUtil.file("d:/test/dir1"), FileUtil.file("d:/test/dir3"), false);
} }
@Test
@Ignore
public void moveContentDirToDirNotExistTest() {
// 目录移动到目标dir3不存在
// 会将目录dir1内容移动到dir3但是dir1目录不删除
PathUtil.moveContent(
FileUtil.file("d:/test/dir1").toPath(),
FileUtil.file("d:/test/dir3").toPath(), false);
}
@Test @Test
@Ignore @Ignore
public void moveFileToTargetNotExistTest() { public void moveFileToTargetNotExistTest() {
// 目录移动到目录将整个目录移动 // 文件移动到不存在的路径
// 会将test1.txt重命名为test2 // 会将test1.txt重命名为test2
FileUtil.move(FileUtil.file("d:/test/dir1/test1.txt"), FileUtil.file("d:/test/test2"), false); FileUtil.move(FileUtil.file("d:/test/dir1/test1.txt"), FileUtil.file("d:/test/test2"), false);
} }
@Test
@Ignore
public void moveContentFileToTargetNotExistTest() {
// 目录移动到目录将整个目录移动
// 会将test1.txt重命名为test2
PathUtil.moveContent(
FileUtil.file("d:/test/dir1/test1.txt").toPath(),
FileUtil.file("d:/test/test2").toPath(), false);
}
} }