mirror of
https://gitee.com/dromara/hutool.git
synced 2025-04-05 17:20:07 +08:00
修复FileUtil.moveContent会删除源目录的问题 修复HttpBase.body导致的空指针问题
This commit is contained in:
parent
51c29abcad
commit
52617b2032
@ -3,10 +3,14 @@
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------
|
||||
|
||||
# 5.8.14.M1 (2023-03-03)
|
||||
# 5.8.14.M1 (2023-03-05)
|
||||
|
||||
### 🐣新特性
|
||||
* 【core 】 增加PathMover(issue#I666HB@Github)
|
||||
|
||||
### 🐞Bug修复
|
||||
* 【core 】 修复FileUtil.moveContent会删除源目录的问题(issue#I666HB@Github)
|
||||
* 【http 】 修复HttpBase.body导致的空指针问题
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
166
hutool-core/src/main/java/cn/hutool/core/io/file/PathMover.java
Executable file
166
hutool-core/src/main/java/cn/hutool/core/io/file/PathMover.java
Executable 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断给定的目录是否为给定文件或文件夹的子目录
|
||||
*
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user