This is an automated email from the ASF dual-hosted git repository. ggregory pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/commons-io.git
The following commit(s) were added to refs/heads/master by this push: new f4b721c63 [IO-872] PathUtils.directoryAndFileContentEquals doesn't work across FileSystems f4b721c63 is described below commit f4b721c63e9e1db2f72dbf44d2c651f380391367 Author: Gary D. Gregory <garydgreg...@gmail.com> AuthorDate: Thu Apr 3 21:29:36 2025 -0400 [IO-872] PathUtils.directoryAndFileContentEquals doesn't work across FileSystems - Add SimplePathVisitor.AbstractBuilder - Add CountingPathVisitor.AbstractBuilder and CountingPathVisitor.Builder - Add AccumulatorPathVisitor.Builder and builder( - For now, keep as much of the new code private or package-private as possible --- src/changes/changes.xml | 3 + src/main/java/org/apache/commons/io/FileUtils.java | 10 +- .../commons/io/file/AccumulatorPathVisitor.java | 56 +++++++- .../commons/io/file/CountingPathVisitor.java | 159 +++++++++++++++++---- .../java/org/apache/commons/io/file/PathUtils.java | 75 +++++++++- .../apache/commons/io/file/SimplePathVisitor.java | 47 +++++- .../io/file/AccumulatorPathVisitorTest.java | 2 +- .../io/file/PathUtilsContentEqualsTest.java | 4 +- 8 files changed, 310 insertions(+), 46 deletions(-) diff --git a/src/changes/changes.xml b/src/changes/changes.xml index c690882d0..b94b1676f 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -90,6 +90,9 @@ The <action> type attribute can be add,update,fix,remove. <action dev="ggregory" type="add" due-to="Gary Gregory">Add Uncheck.getAsBoolean(IOBooleanSupplier).</action> <action dev="ggregory" type="add" due-to="Gary Gregory">Add FileChannels.contentEquals(SeekableByteChannel, SeekableByteChannel, int).</action> <action dev="ggregory" type="add" due-to="Gary Gregory">Add FileChannels.contentEquals(ReadableByteChannel, ReadableByteChannel, int).</action> + <action dev="ggregory" type="add" issue="IO-872" due-to="Gary Gregory">Add SimplePathVisitor.AbstractBuilder.</action> + <action dev="ggregory" type="add" issue="IO-872" due-to="Gary Gregory">Add CountingPathVisitor.AbstractBuilder and CountingPathVisitor.Builder.</action> + <action dev="ggregory" type="add" issue="IO-872" due-to="Gary Gregory">Add AccumulatorPathVisitor.Builder and builder().</action> <!-- UPDATE --> <action dev="ggregory" type="update" due-to="Dependabot, Gary Gregory">Bump commons.bytebuddy.version from 1.15.10 to 1.17.5 #710, #715, #720, #734, #735.</action> <action dev="ggregory" type="update" due-to="Gary Gregory">Bump commons-codec:commons-codec from 1.17.1 to 1.18.0. #717.</action> diff --git a/src/main/java/org/apache/commons/io/FileUtils.java b/src/main/java/org/apache/commons/io/FileUtils.java index 701791e26..e04977670 100644 --- a/src/main/java/org/apache/commons/io/FileUtils.java +++ b/src/main/java/org/apache/commons/io/FileUtils.java @@ -2282,8 +2282,14 @@ private static AccumulatorPathVisitor listAccumulate(final File directory, final final boolean isDirFilterSet = dirFilter != null; final FileEqualsFileFilter rootDirFilter = new FileEqualsFileFilter(directory); final PathFilter dirPathFilter = isDirFilterSet ? rootDirFilter.or(dirFilter) : rootDirFilter; - final AccumulatorPathVisitor visitor = new AccumulatorPathVisitor(Counters.noopPathCounters(), fileFilter, dirPathFilter, - (p, e) -> FileVisitResult.CONTINUE); + // @formatter:off + final AccumulatorPathVisitor visitor = AccumulatorPathVisitor.builder() + .setPathCounters(Counters.noopPathCounters()) + .setFileFilter(fileFilter) + .setDirectoryFilter(dirPathFilter) + .setVisitFileFailedFunction((p, e) -> FileVisitResult.CONTINUE) + .get(); + // @formatter:on final Set<FileVisitOption> optionSet = new HashSet<>(); if (options != null) { Collections.addAll(optionSet, options); diff --git a/src/main/java/org/apache/commons/io/file/AccumulatorPathVisitor.java b/src/main/java/org/apache/commons/io/file/AccumulatorPathVisitor.java index 0d4bf2d91..c8e3e06af 100644 --- a/src/main/java/org/apache/commons/io/file/AccumulatorPathVisitor.java +++ b/src/main/java/org/apache/commons/io/file/AccumulatorPathVisitor.java @@ -60,13 +60,38 @@ */ public class AccumulatorPathVisitor extends CountingPathVisitor { + /** + * Builds instances of {@link AccumulatorPathVisitor}. + * + * @since 2.18.0 + */ + public static class Builder extends AbstractBuilder<AccumulatorPathVisitor, Builder> { + @Override + public AccumulatorPathVisitor get() { + return new AccumulatorPathVisitor(this); + } + + } + + /** + * Builds instances of {@link AccumulatorPathVisitor}. + * + * @return a new builder. + * @since 2.18.0 + */ + public static Builder builder() { + return new Builder(); + } + /** * Constructs a new instance configured with a BigInteger {@link PathCounters}. * * @return a new instance configured with a BigInteger {@link PathCounters}. + * @see #builder() + * @see Builder */ public static AccumulatorPathVisitor withBigIntegerCounters() { - return new AccumulatorPathVisitor(Counters.bigIntegerPathCounters()); + return builder().setPathCounters(Counters.bigIntegerPathCounters()).get(); } /** @@ -75,20 +100,23 @@ public static AccumulatorPathVisitor withBigIntegerCounters() { * @param fileFilter Filters files to accumulate and count. * @param dirFilter Filters directories to accumulate and count. * @return a new instance configured with a long {@link PathCounters}. + * @see #builder() + * @see Builder * @since 2.9.0 */ - public static AccumulatorPathVisitor withBigIntegerCounters(final PathFilter fileFilter, - final PathFilter dirFilter) { - return new AccumulatorPathVisitor(Counters.bigIntegerPathCounters(), fileFilter, dirFilter); + public static AccumulatorPathVisitor withBigIntegerCounters(final PathFilter fileFilter, final PathFilter dirFilter) { + return builder().setPathCounters(Counters.bigIntegerPathCounters()).setFileFilter(fileFilter).setDirectoryFilter(dirFilter).get(); } /** * Constructs a new instance configured with a long {@link PathCounters}. * * @return a new instance configured with a long {@link PathCounters}. + * @see #builder() + * @see Builder */ public static AccumulatorPathVisitor withLongCounters() { - return new AccumulatorPathVisitor(Counters.longPathCounters()); + return builder().setPathCounters(Counters.longPathCounters()).get(); } /** @@ -97,10 +125,12 @@ public static AccumulatorPathVisitor withLongCounters() { * @param fileFilter Filters files to accumulate and count. * @param dirFilter Filters directories to accumulate and count. * @return a new instance configured with a long {@link PathCounters}. + * @see #builder() + * @see Builder * @since 2.9.0 */ public static AccumulatorPathVisitor withLongCounters(final PathFilter fileFilter, final PathFilter dirFilter) { - return new AccumulatorPathVisitor(Counters.longPathCounters(), fileFilter, dirFilter); + return builder().setPathCounters(Counters.longPathCounters()).setFileFilter(fileFilter).setDirectoryFilter(dirFilter).get(); } private final List<Path> dirList = new ArrayList<>(); @@ -108,19 +138,27 @@ public static AccumulatorPathVisitor withLongCounters(final PathFilter fileFilte private final List<Path> fileList = new ArrayList<>(); /** - * Constructs a new instance. + * Constructs a new instance with a noop path counter. * * @since 2.9.0 + * @deprecated Use {@link #builder()}. */ + @Deprecated public AccumulatorPathVisitor() { super(Counters.noopPathCounters()); } + private AccumulatorPathVisitor(final Builder builder) { + super(builder); + } + /** * Constructs a new instance that counts file system elements. * * @param pathCounter How to count path visits. + * @deprecated Use {@link #builder()}. */ + @Deprecated public AccumulatorPathVisitor(final PathCounters pathCounter) { super(pathCounter); } @@ -132,7 +170,9 @@ public AccumulatorPathVisitor(final PathCounters pathCounter) { * @param fileFilter Filters which files to count. * @param dirFilter Filters which directories to count. * @since 2.9.0 + * @deprecated Use {@link #builder()}. */ + @Deprecated public AccumulatorPathVisitor(final PathCounters pathCounter, final PathFilter fileFilter, final PathFilter dirFilter) { super(pathCounter, fileFilter, dirFilter); } @@ -145,7 +185,9 @@ public AccumulatorPathVisitor(final PathCounters pathCounter, final PathFilter f * @param dirFilter Filters which directories to count. * @param visitFileFailed Called on {@link #visitFileFailed(Path, IOException)}. * @since 2.12.0 + * @deprecated Use {@link #builder()}. */ + @Deprecated public AccumulatorPathVisitor(final PathCounters pathCounter, final PathFilter fileFilter, final PathFilter dirFilter, final IOBiFunction<Path, IOException, FileVisitResult> visitFileFailed) { super(pathCounter, fileFilter, dirFilter, visitFileFailed); diff --git a/src/main/java/org/apache/commons/io/file/CountingPathVisitor.java b/src/main/java/org/apache/commons/io/file/CountingPathVisitor.java index 00368ef90..8611cf5c7 100644 --- a/src/main/java/org/apache/commons/io/file/CountingPathVisitor.java +++ b/src/main/java/org/apache/commons/io/file/CountingPathVisitor.java @@ -24,6 +24,7 @@ import java.nio.file.Path; import java.nio.file.attribute.BasicFileAttributes; import java.util.Objects; +import java.util.function.UnaryOperator; import org.apache.commons.io.file.Counters.PathCounters; import org.apache.commons.io.filefilter.IOFileFilter; @@ -38,23 +39,119 @@ */ public class CountingPathVisitor extends SimplePathVisitor { + /** + * Builds instances of {@link CountingPathVisitor}. + * + * @param <T> The CountingPathVisitor type. + * @param <B> The AbstractBuilder type. + * @since 2.18.0 + */ + public abstract static class AbstractBuilder<T, B extends AbstractBuilder<T, B>> extends SimplePathVisitor.AbstractBuilder<T, B> { + + private PathCounters pathCounters = defaultPathCounters(); + private PathFilter fileFilter = defaultFileFilter(); + private PathFilter directoryFilter = defaultDirectoryFilter(); + private UnaryOperator<Path> directoryPostTransformer = defaultDirectoryTransformer(); + + PathFilter getDirectoryFilter() { + return directoryFilter; + } + + UnaryOperator<Path> getDirectoryPostTransformer() { + return directoryPostTransformer; + } + + PathFilter getFileFilter() { + return fileFilter; + } + + PathCounters getPathCounters() { + return pathCounters; + } + + /** + * Sets how to filter directories. + * + * @param directoryFilter how to filter files. + * @return this instance. + */ + public B setDirectoryFilter(final PathFilter directoryFilter) { + this.directoryFilter = directoryFilter != null ? directoryFilter : defaultDirectoryFilter(); + return asThis(); + } + + /** + * Sets how to transform directories, defaults to {@link UnaryOperator#identity()}. + * + * @param directoryTransformer how to filter files. + * @return this instance. + */ + public B setDirectoryPostTransformer(final UnaryOperator<Path> directoryTransformer) { + this.directoryPostTransformer = directoryTransformer != null ? directoryTransformer : defaultDirectoryTransformer(); + return asThis(); + } + + /** + * Sets how to filter files. + * + * @param fileFilter how to filter files. + * @return this instance. + */ + public B setFileFilter(final PathFilter fileFilter) { + this.fileFilter = fileFilter != null ? fileFilter : defaultFileFilter(); + return asThis(); + } + + /** + * Sets how to count path visits. + * + * @param pathCounters How to count path visits. + * @return this instance. + */ + public B setPathCounters(final PathCounters pathCounters) { + this.pathCounters = pathCounters != null ? pathCounters : defaultPathCounters(); + return asThis(); + } + } + + /** + * Builds instances of {@link CountingPathVisitor}. + * + * @since 2.18.0 + */ + public static class Builder extends AbstractBuilder<CountingPathVisitor, Builder> { + + @Override + public CountingPathVisitor get() { + return new CountingPathVisitor(this); + } + } + static final String[] EMPTY_STRING_ARRAY = {}; - static IOFileFilter defaultDirFilter() { + static IOFileFilter defaultDirectoryFilter() { return TrueFileFilter.INSTANCE; } + static UnaryOperator<Path> defaultDirectoryTransformer() { + return UnaryOperator.identity(); + } + static IOFileFilter defaultFileFilter() { return new SymbolicLinkFileFilter(FileVisitResult.TERMINATE, FileVisitResult.CONTINUE); } + static PathCounters defaultPathCounters() { + return Counters.longPathCounters(); + } + /** * Constructs a new instance configured with a {@link BigInteger} {@link PathCounters}. * * @return a new instance configured with a {@link BigInteger} {@link PathCounters}. */ public static CountingPathVisitor withBigIntegerCounters() { - return new CountingPathVisitor(Counters.bigIntegerPathCounters()); + return new Builder().setPathCounters(Counters.bigIntegerPathCounters()).get(); } /** @@ -63,51 +160,66 @@ public static CountingPathVisitor withBigIntegerCounters() { * @return a new instance configured with a {@code long} {@link PathCounters}. */ public static CountingPathVisitor withLongCounters() { - return new CountingPathVisitor(Counters.longPathCounters()); + return new Builder().setPathCounters(Counters.longPathCounters()).get(); } private final PathCounters pathCounters; private final PathFilter fileFilter; - private final PathFilter dirFilter; + private final PathFilter directoryFilter; + private final UnaryOperator<Path> directoryPostTransformer; + + CountingPathVisitor(final AbstractBuilder<?, ?> builder) { + super(builder); + this.pathCounters = builder.getPathCounters(); + this.fileFilter = builder.getFileFilter(); + this.directoryFilter = builder.getDirectoryFilter(); + this.directoryPostTransformer = builder.getDirectoryPostTransformer(); + } /** * Constructs a new instance. * - * @param pathCounter How to count path visits. + * @param pathCounters How to count path visits. + * @see Builder */ - public CountingPathVisitor(final PathCounters pathCounter) { - this(pathCounter, defaultFileFilter(), defaultDirFilter()); + public CountingPathVisitor(final PathCounters pathCounters) { + this(new Builder().setPathCounters(pathCounters)); } /** * Constructs a new instance. * - * @param pathCounter How to count path visits. - * @param fileFilter Filters which files to count. - * @param dirFilter Filters which directories to count. + * @param pathCounters How to count path visits. + * @param fileFilter Filters which files to count. + * @param directoryFilter Filters which directories to count. + * @see Builder * @since 2.9.0 */ - public CountingPathVisitor(final PathCounters pathCounter, final PathFilter fileFilter, final PathFilter dirFilter) { - this.pathCounters = Objects.requireNonNull(pathCounter, "pathCounter"); + public CountingPathVisitor(final PathCounters pathCounters, final PathFilter fileFilter, final PathFilter directoryFilter) { + this.pathCounters = Objects.requireNonNull(pathCounters, "pathCounters"); this.fileFilter = Objects.requireNonNull(fileFilter, "fileFilter"); - this.dirFilter = Objects.requireNonNull(dirFilter, "dirFilter"); + this.directoryFilter = Objects.requireNonNull(directoryFilter, "directoryFilter"); + this.directoryPostTransformer = UnaryOperator.identity(); } /** * Constructs a new instance. * - * @param pathCounter How to count path visits. - * @param fileFilter Filters which files to count. - * @param dirFilter Filters which directories to count. + * @param pathCounters How to count path visits. + * @param fileFilter Filters which files to count. + * @param directoryFilter Filters which directories to count. * @param visitFileFailed Called on {@link #visitFileFailed(Path, IOException)}. * @since 2.12.0 + * @deprecated Use {@link Builder}. */ - public CountingPathVisitor(final PathCounters pathCounter, final PathFilter fileFilter, final PathFilter dirFilter, - final IOBiFunction<Path, IOException, FileVisitResult> visitFileFailed) { + @Deprecated + public CountingPathVisitor(final PathCounters pathCounters, final PathFilter fileFilter, final PathFilter directoryFilter, + final IOBiFunction<Path, IOException, FileVisitResult> visitFileFailed) { super(visitFileFailed); - this.pathCounters = Objects.requireNonNull(pathCounter, "pathCounter"); + this.pathCounters = Objects.requireNonNull(pathCounters, "pathCounters"); this.fileFilter = Objects.requireNonNull(fileFilter, "fileFilter"); - this.dirFilter = Objects.requireNonNull(dirFilter, "dirFilter"); + this.directoryFilter = Objects.requireNonNull(directoryFilter, "directoryFilter"); + this.directoryPostTransformer = UnaryOperator.identity(); } @Override @@ -138,13 +250,13 @@ public int hashCode() { @Override public FileVisitResult postVisitDirectory(final Path dir, final IOException exc) throws IOException { - updateDirCounter(dir, exc); + updateDirCounter(directoryPostTransformer.apply(dir), exc); return FileVisitResult.CONTINUE; } @Override public FileVisitResult preVisitDirectory(final Path dir, final BasicFileAttributes attributes) throws IOException { - final FileVisitResult accept = dirFilter.accept(dir, attributes); + final FileVisitResult accept = directoryFilter.accept(dir, attributes); return accept != FileVisitResult.CONTINUE ? FileVisitResult.SKIP_SUBTREE : FileVisitResult.CONTINUE; } @@ -167,7 +279,7 @@ protected void updateDirCounter(final Path dir, final IOException exc) { /** * Updates the counters for visiting the given file. * - * @param file the visited file. + * @param file the visited file. * @param attributes the visited file attributes. */ protected void updateFileCounters(final Path file, final BasicFileAttributes attributes) { @@ -183,5 +295,4 @@ public FileVisitResult visitFile(final Path file, final BasicFileAttributes attr } return FileVisitResult.CONTINUE; } - } diff --git a/src/main/java/org/apache/commons/io/file/PathUtils.java b/src/main/java/org/apache/commons/io/file/PathUtils.java index 0d30404c4..679770f9b 100644 --- a/src/main/java/org/apache/commons/io/file/PathUtils.java +++ b/src/main/java/org/apache/commons/io/file/PathUtils.java @@ -30,6 +30,7 @@ import java.nio.file.AccessDeniedException; import java.nio.file.CopyOption; import java.nio.file.DirectoryStream; +import java.nio.file.FileSystem; import java.nio.file.FileVisitOption; import java.nio.file.FileVisitResult; import java.nio.file.FileVisitor; @@ -61,6 +62,7 @@ import java.util.Comparator; import java.util.EnumSet; import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Objects; import java.util.Set; @@ -94,6 +96,51 @@ public final class PathUtils { */ private static final class RelativeSortedPaths { + /** + * Compares lists of paths regardless of their file systems. + * + * @param list1 the first list. + * @param list2 the second list. + * @return whether the lists are equal. + */ + private static boolean equals(final List<Path> list1, final List<Path> list2) { + if (list1.size() != list2.size()) { + return false; + } + // compare both lists using iterators + final Iterator<Path> iterator1 = list1.iterator(); + final Iterator<Path> iterator2 = list2.iterator(); + while (iterator1.hasNext() && iterator2.hasNext()) { + final Path path1 = iterator1.next(); + final Path path2 = iterator2.next(); + final FileSystem fileSystem1 = path1.getFileSystem(); + final FileSystem fileSystem2 = path2.getFileSystem(); + if (fileSystem1 == fileSystem2) { + if (!path1.equals(path2)) { + return false; + } + } else if (fileSystem1.getSeparator().equals(fileSystem2.getSeparator())) { + // Separators are the same, so we can use toString comparison + if (!path1.toString().equals(path2.toString())) { + return false; + } + } else { + // Compare paths from different file systems component by component. + // Cant use toString() string comparison which may fail due to different path separators. + final Iterator<Path> path1Iterator = path1.iterator(); + final Iterator<Path> path2Iterator = path2.iterator(); + while (path1Iterator.hasNext() && path2Iterator.hasNext()) { + if (!path1Iterator.next().toString().equals(path2Iterator.next().toString())) { + return false; + } + } + // Check that both iterators are exhausted (paths have same number of components) + return !path1Iterator.hasNext() && !path2Iterator.hasNext(); + } + } + return true; + } + final boolean equals; // final List<Path> relativeDirList1; // might need later? // final List<Path> relativeDirList2; // might need later? @@ -133,12 +180,12 @@ private RelativeSortedPaths(final Path dir1, final Path dir2, final int maxDepth } else { tmpRelativeDirList1 = visitor1.relativizeDirectories(dir1, true, null); tmpRelativeDirList2 = visitor2.relativizeDirectories(dir2, true, null); - if (!tmpRelativeDirList1.equals(tmpRelativeDirList2)) { + if (!equals(tmpRelativeDirList1, tmpRelativeDirList2)) { equals = false; } else { tmpRelativeFileList1 = visitor1.relativizeFiles(dir1, true, null); tmpRelativeFileList2 = visitor2.relativizeFiles(dir2, true, null); - equals = tmpRelativeFileList1.equals(tmpRelativeFileList2); + equals = equals(tmpRelativeFileList1, tmpRelativeFileList2); } } } @@ -223,7 +270,8 @@ private RelativeSortedPaths(final Path dir1, final Path dir2, final int maxDepth * @return file tree information. */ private static AccumulatorPathVisitor accumulate(final Path directory, final int maxDepth, final FileVisitOption[] fileVisitOptions) throws IOException { - return visitFileTree(AccumulatorPathVisitor.withLongCounters(), directory, toFileVisitOptionSet(fileVisitOptions), maxDepth); + return visitFileTree(AccumulatorPathVisitor.builder().setDirectoryPostTransformer(PathUtils::stripTrailingSeparator).get(), directory, + toFileVisitOptionSet(fileVisitOptions), maxDepth); } /** @@ -325,7 +373,7 @@ public static Path copyFileToDirectory(final Path sourceFile, final Path targetD // Path.resolve() naturally won't work across FileSystem unless we convert to a String final Path sourceFileName = Objects.requireNonNull(sourceFile.getFileName(), "source file name"); final Path targetFile; - if (sourceFileName.getFileSystem() == targetDirectory.getFileSystem()) { + if (isSameFileSystem(sourceFileName, targetDirectory)) { targetFile = targetDirectory.resolve(sourceFileName); } else { targetFile = targetDirectory.resolve(sourceFileName.toString()); @@ -661,12 +709,17 @@ public static boolean directoryAndFileContentEquals(final Path path1, final Path // Both visitors contain the same normalized paths, we can compare file contents. final List<Path> fileList1 = relativeSortedPaths.relativeFileList1; final List<Path> fileList2 = relativeSortedPaths.relativeFileList2; + final boolean sameFileSystem = isSameFileSystem(path1, path2); for (final Path path : fileList1) { - final int binarySearch = Collections.binarySearch(fileList2, path); + final int binarySearch = sameFileSystem ? Collections.binarySearch(fileList2, path) + : Collections.binarySearch(fileList2, path, Comparator.comparing(Path::toString)); if (binarySearch <= -1) { throw new IllegalStateException("Unexpected mismatch."); } - if (!fileContentEquals(path1.resolve(path), path2.resolve(path), linkOptions, openOptions)) { + if (sameFileSystem && !fileContentEquals(path1.resolve(path), path2.resolve(path), linkOptions, openOptions)) { + return false; + } + if (!fileContentEquals(path1.resolve(path.toString()), path2.resolve(path.toString()), linkOptions, openOptions)) { return false; } } @@ -1262,6 +1315,10 @@ public static boolean isRegularFile(final Path path, final LinkOption... options return path != null && Files.isRegularFile(path, options); } + static boolean isSameFileSystem(final Path path1, final Path path2) { + return path1.getFileSystem() == path2.getFileSystem(); + } + /** * Creates a new DirectoryStream for Paths rooted at the given directory. * <p> @@ -1687,6 +1744,12 @@ public static BigInteger sizeOfDirectoryAsBigInteger(final Path directory) throw return countDirectoryAsBigInteger(directory).getByteCounter().getBigInteger(); } + private static Path stripTrailingSeparator(final Path dir) { + final String separator = dir.getFileSystem().getSeparator(); + final String fileName = dir.getFileName().toString(); + return fileName.endsWith(separator) ? dir.resolveSibling(fileName.substring(0, fileName.length() - 1)) : dir; + } + /** * Converts an array of {@link FileVisitOption} to a {@link Set}. * diff --git a/src/main/java/org/apache/commons/io/file/SimplePathVisitor.java b/src/main/java/org/apache/commons/io/file/SimplePathVisitor.java index 1c3b1ddcc..4762a126b 100644 --- a/src/main/java/org/apache/commons/io/file/SimplePathVisitor.java +++ b/src/main/java/org/apache/commons/io/file/SimplePathVisitor.java @@ -23,6 +23,7 @@ import java.nio.file.SimpleFileVisitor; import java.util.Objects; +import org.apache.commons.io.build.AbstractSupplier; import org.apache.commons.io.function.IOBiFunction; /** @@ -32,6 +33,37 @@ */ public abstract class SimplePathVisitor extends SimpleFileVisitor<Path> implements PathVisitor { + /** + * Abstracts builder for subclasses. + * + * @param <T> The SimplePathVisitor type. + * @param <B> The builder type. + * @since 2.18.0 + */ + protected abstract static class AbstractBuilder<T, B extends AbstractSupplier<T, B>> extends AbstractSupplier<T, B> { + + private IOBiFunction<Path, IOException, FileVisitResult> visitFileFailedFunction; + + IOBiFunction<Path, IOException, FileVisitResult> getVisitFileFailedFunction() { + return visitFileFailedFunction; + } + + /** + * Sets the function to call on {@link #visitFileFailed(Path, IOException)}. + * <p> + * Defaults to {@link SimpleFileVisitor#visitFileFailed(Object, IOException)} on construction. + * </p> + * + * @param visitFileFailedFunction the function to call on {@link #visitFileFailed(Path, IOException)}. + * @return this instance. + */ + public B setVisitFileFailedFunction(final IOBiFunction<Path, IOException, FileVisitResult> visitFileFailedFunction) { + this.visitFileFailedFunction = visitFileFailedFunction; + return asThis(); + } + + } + private final IOBiFunction<Path, IOException, FileVisitResult> visitFileFailedFunction; /** @@ -44,10 +76,19 @@ protected SimplePathVisitor() { /** * Constructs a new instance. * - * @param visitFileFailed Called on {@link #visitFileFailed(Path, IOException)}. + * @param builder The builder provided by a subclass. + */ + SimplePathVisitor(final AbstractBuilder<?, ?> builder) { + this.visitFileFailedFunction = builder.visitFileFailedFunction != null ? builder.visitFileFailedFunction : super::visitFileFailed; + } + + /** + * Constructs a new instance. + * + * @param visitFileFailedFunction Called on {@link #visitFileFailed(Path, IOException)}. */ - protected SimplePathVisitor(final IOBiFunction<Path, IOException, FileVisitResult> visitFileFailed) { - this.visitFileFailedFunction = Objects.requireNonNull(visitFileFailed, "visitFileFailed"); + protected SimplePathVisitor(final IOBiFunction<Path, IOException, FileVisitResult> visitFileFailedFunction) { + this.visitFileFailedFunction = Objects.requireNonNull(visitFileFailedFunction, "visitFileFailedFunction"); } @Override diff --git a/src/test/java/org/apache/commons/io/file/AccumulatorPathVisitorTest.java b/src/test/java/org/apache/commons/io/file/AccumulatorPathVisitorTest.java index 45b5513e9..9a019f211 100644 --- a/src/test/java/org/apache/commons/io/file/AccumulatorPathVisitorTest.java +++ b/src/test/java/org/apache/commons/io/file/AccumulatorPathVisitorTest.java @@ -73,7 +73,7 @@ static Stream<Arguments> testParametersIgnoreFailures() { return Stream.of( Arguments.of((Supplier<AccumulatorPathVisitor>) () -> new AccumulatorPathVisitor( Counters.bigIntegerPathCounters(), - CountingPathVisitor.defaultDirFilter(), + CountingPathVisitor.defaultDirectoryFilter(), CountingPathVisitor.defaultFileFilter()))); // @formatter:on } diff --git a/src/test/java/org/apache/commons/io/file/PathUtilsContentEqualsTest.java b/src/test/java/org/apache/commons/io/file/PathUtilsContentEqualsTest.java index baa99cb09..45be9d739 100644 --- a/src/test/java/org/apache/commons/io/file/PathUtilsContentEqualsTest.java +++ b/src/test/java/org/apache/commons/io/file/PathUtilsContentEqualsTest.java @@ -29,7 +29,6 @@ import java.nio.file.Path; import java.nio.file.Paths; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; @@ -106,13 +105,12 @@ public void testDirectoryAndFileContentEquals() throws Exception { * * @throws Exception on test failure. */ - @Disabled @Test public void testDirectoryAndFileContentEqualsDifferentFileSystems() throws Exception { final Path dir1 = Paths.get("src/test/resources/dir-equals-tests"); try (FileSystem fileSystem = FileSystems.newFileSystem(dir1.resolveSibling(dir1.getFileName() + ".zip"), null)) { final Path dir2 = fileSystem.getPath("/dir-equals-tests"); - // WindowsPath and ZipPath equals() methods always return false if the argument is not of the same instance as itself. + // WindowsPath, UnixPath, and ZipPath equals() methods always return false if the argument is not of the same instance as itself. assertTrue(PathUtils.directoryAndFileContentEquals(dir1, dir2)); } }