diff --git a/CHANGELOG.md b/CHANGELOG.md index 987bc7c9a..712b4eb3c 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ * 【http 】 UserAgent增加百度浏览器识别(issue#I847JY@Gitee) * 【core 】 ReflectUtil.getFieldsValue增加Filter重载(pr#1090@Gitee) * 【core 】 Snowflake增加方法:根据传入时间戳,计算ID起终点(pr#1096@Gitee) +* 【core 】 PathUtil增加loopFiles重载,可选是否追踪软链(issue#3353@Github) ### 🐞Bug修复 * 【cron 】 修复Cron表达式range解析错误问题(issue#I82CSH@Gitee) 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 d04a8a929..cf3b6568e 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 @@ -1,5 +1,6 @@ package cn.hutool.core.io.file; +import cn.hutool.core.collection.CollUtil; import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.io.IoUtil; import cn.hutool.core.io.file.visitor.CopyVisitor; @@ -31,6 +32,7 @@ import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; import java.util.EnumSet; import java.util.List; +import java.util.Set; /** * NIO中Path对象操作封装 @@ -78,11 +80,26 @@ public class PathUtil { * @since 5.4.1 */ public static List loopFiles(Path path, int maxDepth, FileFilter fileFilter) { + return loopFiles(path, maxDepth, false, fileFilter); + } + + /** + * 递归遍历目录以及子目录中的所有文件
+ * 如果提供path为文件,直接返回过滤结果 + * + * @param path 当前遍历文件或目录 + * @param maxDepth 遍历最大深度,-1表示遍历到没有目录为止 + * @param isFollowLinks 是否跟踪软链(快捷方式) + * @param fileFilter 文件过滤规则对象,选择要保留的文件,只对文件有效,不过滤目录,null表示接收全部文件 + * @return 文件列表 + * @since 5.4.1 + */ + public static List loopFiles(final Path path, final int maxDepth, final boolean isFollowLinks, final FileFilter fileFilter) { final List fileList = new ArrayList<>(); - if (null == path || false == Files.exists(path)) { + if (!exists(path, isFollowLinks)) { return fileList; - } else if (false == isDirectory(path)) { + } else if (!isDirectory(path, isFollowLinks)) { final File file = path.toFile(); if (null == fileFilter || fileFilter.accept(file)) { fileList.add(file); @@ -90,10 +107,10 @@ public class PathUtil { return fileList; } - walkFiles(path, maxDepth, new SimpleFileVisitor() { + walkFiles(path, maxDepth, isFollowLinks, new SimpleFileVisitor() { @Override - public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) { + public FileVisitResult visitFile(final Path path, final BasicFileAttributes attrs) { final File file = path.toFile(); if (null == fileFilter || fileFilter.accept(file)) { fileList.add(file); @@ -127,14 +144,28 @@ public class PathUtil { * @since 4.6.3 */ public static void walkFiles(Path start, int maxDepth, FileVisitor visitor) { + walkFiles(start, maxDepth, false, visitor); + } + + /** + * 遍历指定path下的文件并做处理 + * + * @param start 起始路径,必须为目录 + * @param maxDepth 最大遍历深度,-1表示不限制深度 + * @param visitor {@link FileVisitor} 接口,用于自定义在访问文件时,访问目录前后等节点做的操作 + * @param isFollowLinks 是否追踪到软链对应的真实地址 + * @see Files#walkFileTree(Path, java.util.Set, int, FileVisitor) + * @since 5.8.23 + */ + public static void walkFiles(final Path start, int maxDepth, final boolean isFollowLinks, final FileVisitor visitor) { if (maxDepth < 0) { // < 0 表示遍历到最底层 maxDepth = Integer.MAX_VALUE; } try { - Files.walkFileTree(start, EnumSet.noneOf(FileVisitOption.class), maxDepth, visitor); - } catch (IOException e) { + Files.walkFileTree(start, getFileVisitOption(isFollowLinks), maxDepth, visitor); + } catch (final IOException e) { throw new IORuntimeException(e); } } @@ -281,8 +312,7 @@ public class PathUtil { if (null == path) { return false; } - final LinkOption[] options = isFollowLinks ? new LinkOption[0] : new LinkOption[]{LinkOption.NOFOLLOW_LINKS}; - return Files.isDirectory(path, options); + return Files.isDirectory(path, getLinkOptions(isFollowLinks)); } /** @@ -366,9 +396,8 @@ public class PathUtil { return null; } - final LinkOption[] options = isFollowLinks ? new LinkOption[0] : new LinkOption[]{LinkOption.NOFOLLOW_LINKS}; try { - return Files.readAttributes(path, BasicFileAttributes.class, options); + return Files.readAttributes(path, BasicFileAttributes.class, getLinkOptions(isFollowLinks)); } catch (IOException e) { throw new IORuntimeException(e); } @@ -539,8 +568,7 @@ public class PathUtil { if (null == path) { return false; } - final LinkOption[] options = isFollowLinks ? new LinkOption[0] : new LinkOption[]{LinkOption.NOFOLLOW_LINKS}; - return Files.isRegularFile(path, options); + return Files.isRegularFile(path, getLinkOptions(isFollowLinks)); } /** @@ -563,8 +591,7 @@ public class PathUtil { * @since 5.5.3 */ public static boolean exists(Path path, boolean isFollowLinks) { - final LinkOption[] options = isFollowLinks ? new LinkOption[0] : new LinkOption[]{LinkOption.NOFOLLOW_LINKS}; - return Files.exists(path, options); + return Files.exists(path, getLinkOptions(isFollowLinks)); } /** @@ -712,4 +739,27 @@ public class PathUtil { } } } + + /** + * 构建是否追踪软链的选项 + * + * @param isFollowLinks 是否追踪软链 + * @return 选项 + * @since 5.8.23 + */ + public static LinkOption[] getLinkOptions(final boolean isFollowLinks) { + return isFollowLinks ? new LinkOption[0] : new LinkOption[]{LinkOption.NOFOLLOW_LINKS}; + } + + /** + * 构建是否追踪软链的选项 + * + * @param isFollowLinks 是否追踪软链 + * @return 选项 + * @since 5.8.23 + */ + public static Set getFileVisitOption(final boolean isFollowLinks) { + return isFollowLinks ? EnumSet.of(FileVisitOption.FOLLOW_LINKS) : + EnumSet.noneOf(FileVisitOption.class); + } }