修复FileUtil.moveContent会删除源目录的问题 修复HttpBase.body导致的空指针问题

This commit is contained in:
Looly 2023-03-05 22:45:36 +08:00
parent 51c29abcad
commit 52617b2032
5 changed files with 208 additions and 52 deletions

View File

@ -3,10 +3,14 @@
-------------------------------------------------------------------------------------------------------------
# 5.8.14.M1 (2023-03-03)
# 5.8.14.M1 (2023-03-05)
### 🐣新特性
* 【core 】 增加PathMoverissue#I666HB@Github
### 🐞Bug修复
* 【core 】 修复FileUtil.moveContent会删除源目录的问题issue#I666HB@Github
* 【http 】 修复HttpBase.body导致的空指针问题
-------------------------------------------------------------------------------------------------------------

View File

@ -0,0 +1,166 @@
package cn.hutool.core.io.file;
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 5.8.14
*/
public class PathMover {
/**
* 创建文件或目录移动器
*
* @param src 源文件或目录
* @param target 目标文件或目录
* @param isOverride 是否覆盖目标文件
* @return {@code 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 {@code 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 源文件或目录不能为{@code null}且必须存在
* @param target 目标文件或目录
* @param options 移动参数
*/
public PathMover(final Path src, final Path target, final CopyOption[] options) {
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.options = ObjUtil.defaultIfNull(options, new CopyOption[]{});;
}
/**
* 移动文件或目录到目标中例如
* <ul>
* <li>如果src和target为同一文件或目录直接返回target</li>
* <li>如果src为文件target为目录则移动到目标目录下存在同名文件则按照是否覆盖参数执行</li>
* <li>如果src为文件target为文件则按照是否覆盖参数执行</li>
* <li>如果src为文件target为不存在的路径则重命名源文件到目标指定的文件如move("/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为不存在的路径则重命名src到target如move("/a/b", "/c/d")结果为"/c/d/"相当于b重命名为d</li>
* </ul>
*
* @return 目标文件Path
*/
public Path move() {
final Path src = this.src;
Path target = this.target;
final CopyOption[] options = this.options;
if (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);
}
// 移动失败可能是跨分区移动导致的采用递归移动方式
walkMove(src, target, options);
// 移动后删除空目录
PathUtil.del(src);
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;
if (PathUtil.isExistsAndNotDirectory(target, false)) {
// 文件移动调用move方法
return move();
}
final Path target = this.target;
if (PathUtil.isExistsAndNotDirectory(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 {
// 移动源目录下的内容而不删除目录
Files.walkFileTree(src, new MoveVisitor(src, target, options));
} catch (final IOException e) {
throw new IORuntimeException(e);
}
}
}

View File

@ -470,74 +470,44 @@ 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为同一文件或目录直接返回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为文件抛出{@link IllegalArgumentException}</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>
*
* @param src 源文件或目录路径
* @param target 目标路径如果为目录则移动到此目录下
* @param isOverride 是否覆盖目标文件
* @return 目标文件Path
* @since 5.5.1
*/
public static Path move(Path src, Path target, 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;
}
if (isDirectory(target)) {
target = target.resolve(src.getFileName());
}
return moveContent(src, target, 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(Path src, Path target, boolean isOverride) {
Assert.notNull(src, "Src path must be not null !");
Assert.notNull(target, "Target path must be not null !");
final CopyOption[] options = isOverride ? new CopyOption[]{StandardCopyOption.REPLACE_EXISTING} : new CopyOption[]{};
// 自动创建目标的父目录
mkParentDirs(target);
try {
return Files.move(src, target, options);
} catch (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 (IOException e2) {
throw new IORuntimeException(e2);
}
return target;
}
return PathMover.of(src, target, isOverride).moveContent();
}
/**
@ -599,6 +569,22 @@ public class PathUtil {
return Files.exists(path, options);
}
/**
* 判断是否存在且为非目录
* <ul>
* <li>如果path为{@code null}返回{@code false}</li>
* <li>如果path不存在返回{@code false}</li>
* </ul>
*
* @param path {@link Path}
* @param isFollowLinks 是否追踪到软链对应的真实地址
* @return 如果为目录true
* @since 5.8.14
*/
public static boolean isExistsAndNotDirectory(final Path path, final boolean isFollowLinks) {
return exists(path, isFollowLinks) && false == isDirectory(path, isFollowLinks);
}
/**
* 判断给定的目录是否为给定文件或文件夹的子目录
*

View File

@ -304,7 +304,7 @@ public abstract class HttpBase<T> {
* @return byte[]
*/
public byte[] bodyBytes() {
return this.body.readBytes();
return this.body == null ? null : this.body.readBytes();
}
/**
@ -355,7 +355,7 @@ public abstract class HttpBase<T> {
}
sb.append("Request Body: ").append(StrUtil.CRLF);
sb.append(" ").append(StrUtil.str(this.body.readBytes(), this.charset)).append(StrUtil.CRLF);
sb.append(" ").append(StrUtil.str(this.bodyBytes(), this.charset)).append(StrUtil.CRLF);
return sb.toString();
}

View File

@ -241,7 +241,7 @@ public class HttpResponse extends HttpBase<HttpResponse> implements Closeable {
if (isAsync) {
return this.in;
}
return this.body.getStream();
return null == this.body ? null : this.body.getStream();
}
/**
@ -253,7 +253,7 @@ public class HttpResponse extends HttpBase<HttpResponse> implements Closeable {
@Override
public byte[] bodyBytes() {
sync();
return this.body.readBytes();
return super.bodyBytes();
}
/**