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 <[email protected]>
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));
}
}