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 4d02b11 Implement directory content equality. (#100)
4d02b11 is described below
commit 4d02b112100d7dfcd21b2ad6d0ed8947fd7ffe95
Author: Gary Gregory <[email protected]>
AuthorDate: Wed Dec 25 10:37:28 2019 -0500
Implement directory content equality. (#100)
---
pom.xml | 1 +
src/main/java/org/apache/commons/io/FileUtils.java | 2 +-
.../commons/io/file/AccumulatorPathVisitor.java | 146 ++++++++
.../java/org/apache/commons/io/file/PathUtils.java | 410 +++++++++++++++++----
.../io/file/PathUtilsContentEqualsTest.java | 112 ++++++
.../directory-files-only1/file1.txt | 1 +
.../directory-files-only1/file2.txt | 1 +
.../dirs-and-files1/file1.txt | 1 +
.../dirs-and-files1/file2.txt | 1 +
.../directory-files-only2/file1.txt | 1 +
.../directory-files-only2/file2.txt | 1 +
.../dirs-and-files2/file1.txt | 1 +
.../dirs-and-files2/file2.txt | 1 +
.../dir1/directory-files-only1/file1.txt | 1 +
.../dir1/directory-files-only1/file2.txt | 1 +
.../dir2/directory-files-only1/file1.txt | 1 +
.../dir2/directory-files-only1/file2.txt | 1 +
.../directory-files-only1/file1.txt | 1 +
.../directory-files-only1/file2.txt | 1 +
.../directory-files-only2/file1.txt | 1 +
.../directory-files-only2/file2.txt | 1 +
.../directory-files-only1/file1.txt | 1 +
.../directory-files-only1/file2.txt | 1 +
.../directory-files-only2/file1.txt | 1 +
.../directory-files-only2/file2.txt | 1 +
25 files changed, 617 insertions(+), 74 deletions(-)
diff --git a/pom.xml b/pom.xml
index 8701476..443a293 100644
--- a/pom.xml
+++ b/pom.xml
@@ -304,6 +304,7 @@ file comparators, endian transformation classes, and much
more.
<configuration>
<excludes>
<exclude>src/test/resources/**/*.bin</exclude>
+ <exclude>src/test/resources/dir-equals-tests/**</exclude>
<exclude>test/**</exclude>
</excludes>
</configuration>
diff --git a/src/main/java/org/apache/commons/io/FileUtils.java
b/src/main/java/org/apache/commons/io/FileUtils.java
index 6e5233a..7f617b6 100644
--- a/src/main/java/org/apache/commons/io/FileUtils.java
+++ b/src/main/java/org/apache/commons/io/FileUtils.java
@@ -386,7 +386,7 @@ public class FileUtils {
* @return true if the content of the files are equal or they both don't
* exist, false otherwise
* @throws IOException in case of an I/O error
- * @see
org.apache.commons.io.file.PathUtils#fileContentEquals(Path,Path,java.nio.file.OpenOption...)
+ * @see
org.apache.commons.io.file.PathUtils#fileContentEquals(Path,Path,java.nio.file.LinkOption[],java.nio.file.OpenOption...)
*/
public static boolean contentEquals(final File file1, final File file2)
throws IOException {
if (file1 == null && file2 == null) {
diff --git
a/src/main/java/org/apache/commons/io/file/AccumulatorPathVisitor.java
b/src/main/java/org/apache/commons/io/file/AccumulatorPathVisitor.java
new file mode 100644
index 0000000..8fe7028
--- /dev/null
+++ b/src/main/java/org/apache/commons/io/file/AccumulatorPathVisitor.java
@@ -0,0 +1,146 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.io.file;
+
+import java.io.IOException;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Objects;
+
+import org.apache.commons.io.file.Counters.PathCounters;
+
+/**
+ * Accumulates normalized paths during visitation.
+ * <p>
+ * Use with care on large file trees as each visited Path element is
remembered.
+ * </p>
+ *
+ * @since 2.7
+ */
+public class AccumulatorPathVisitor extends CountingPathVisitor {
+
+ /**
+ * Creates a new instance configured with a BigInteger {@link
PathCounters}.
+ *
+ * @return a new instance configured with a BigInteger {@link
PathCounters}.
+ */
+ public static AccumulatorPathVisitor withBigIntegerCounters() {
+ return new AccumulatorPathVisitor(Counters.bigIntegerPathCounters());
+ }
+
+ /**
+ * Creates a new instance configured with a long {@link PathCounters}.
+ *
+ * @return a new instance configured with a long {@link PathCounters}.
+ */
+ public static AccumulatorPathVisitor withLongCounters() {
+ return new AccumulatorPathVisitor(Counters.longPathCounters());
+ }
+
+ private final List<Path> dirList = new ArrayList<>();
+
+ private final List<Path> fileList = new ArrayList<>();
+
+ /**
+ * Constructs a new instance.
+ *
+ * @param pathCounter How to count path visits.
+ */
+ public AccumulatorPathVisitor(PathCounters pathCounter) {
+ super(pathCounter);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!super.equals(obj)) {
+ return false;
+ }
+ if (!(obj instanceof AccumulatorPathVisitor)) {
+ return false;
+ }
+ AccumulatorPathVisitor other = (AccumulatorPathVisitor) obj;
+ return Objects.equals(dirList, other.dirList) &&
Objects.equals(fileList, other.fileList);
+ }
+
+ /**
+ * Gets the list of visited directories.
+ *
+ * @return the list of visited directories.
+ */
+ public List<Path> getDirList() {
+ return dirList;
+ }
+
+ /**
+ * Gets the list of visited files.
+ *
+ * @return the list of visited files.
+ */
+ public List<Path> getFileList() {
+ return fileList;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = super.hashCode();
+ result = prime * result + Objects.hash(dirList, fileList);
+ return result;
+ }
+
+ /**
+ * Relativizes each directory path with {@link Path#relativize(Path)}
against the given {@code parent}, optionally
+ * sorting the result.
+ *
+ * @param parent A parent path
+ * @param sort Whether to sort
+ * @param comparator How to sort, null uses default sorting.
+ * @return A new list
+ */
+ public List<Path> relativizeDirectories(final Path parent, boolean sort,
Comparator<? super Path> comparator) {
+ return PathUtils.relativize(getDirList(), parent, sort, comparator);
+ }
+
+ /**
+ * Relativizes each file path with {@link Path#relativize(Path)} against
the given {@code parent}, optionally
+ * sorting the result.
+ *
+ * @param parent A parent path
+ * @param sort Whether to sort
+ * @param comparator How to sort, null uses default sorting.
+ * @return A new list
+ */
+ public List<Path> relativizeFiles(final Path parent, boolean sort,
Comparator<? super Path> comparator) {
+ return PathUtils.relativize(getFileList(), parent, sort, comparator);
+ }
+
+ @Override
+ public FileVisitResult visitFile(Path file, BasicFileAttributes
attributes) throws IOException {
+ ((Files.isDirectory(file)) ? dirList : fileList).add(file.normalize());
+ return super.visitFile(file, attributes);
+ }
+
+}
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 5f3d0e0..317fd99 100644
--- a/src/main/java/org/apache/commons/io/file/PathUtils.java
+++ b/src/main/java/org/apache/commons/io/file/PathUtils.java
@@ -23,12 +23,23 @@ import java.net.URI;
import java.net.URL;
import java.nio.file.CopyOption;
import java.nio.file.DirectoryStream;
+import java.nio.file.FileVisitOption;
import java.nio.file.FileVisitor;
import java.nio.file.Files;
+import java.nio.file.LinkOption;
import java.nio.file.NotDirectoryException;
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.file.Counters.PathCounters;
@@ -41,6 +52,100 @@ import org.apache.commons.io.file.Counters.PathCounters;
public final class PathUtils {
/**
+ * Accumulates file tree information in a {@link AccumulatorPathVisitor}.
+ *
+ * @param directory The directory to accumulate information.
+ * @param maxDepth See {@link
Files#walkFileTree(Path,Set,int,FileVisitor)}.
+ * @param linkOptions Options indicating how symbolic links are handled.
+ * @param fileVisitOptions See {@link
Files#walkFileTree(Path,Set,int,FileVisitor)}.
+ * @throws IOException if an I/O error is thrown by a visitor method.
+ * @return file tree information.
+ */
+ private static AccumulatorPathVisitor accumulate(final Path directory,
final int maxDepth,
+ final LinkOption[] linkOptions, final FileVisitOption...
fileVisitOptions) throws IOException {
+ return visitFileTree(AccumulatorPathVisitor.withLongCounters(),
directory,
+ toFileVisitOptionSet(fileVisitOptions), maxDepth);
+ }
+
+ /**
+ * Private worker/holder that computes and tracks relative path names and
their equality. We reuse the sorted
+ * relative lists when comparing directories.
+ */
+ private static class RelativeSortedPaths {
+
+ final boolean equals;
+ final List<Path> relativeDirList1; // might need later?
+ final List<Path> relativeDirList2; // might need later?
+ final List<Path> relativeFileList1;
+ final List<Path> relativeFileList2;
+
+ /**
+ * Constructs and initializes a new instance by accumulating directory
and file info.
+ *
+ * @param dir1 First directory to compare.
+ * @param dir2 Seconds directory to compare.
+ * @param maxDepth See {@link
Files#walkFileTree(Path,Set,int,FileVisitor)}.
+ * @param linkOptions Options indicating how symbolic links are
handled.
+ * @param fileVisitOptions See {@link
Files#walkFileTree(Path,Set,int,FileVisitor)}.
+ * @throws IOException if an I/O error is thrown by a visitor method.
+ */
+ private RelativeSortedPaths(final Path dir1, final Path dir2, final
int maxDepth,
+ final LinkOption[] linkOptions, final FileVisitOption...
fileVisitOptions) throws IOException {
+ List<Path> tmpRelativeDirList1 = null;
+ List<Path> tmpRelativeDirList2 = null;
+ List<Path> tmpRelativeFileList1 = null;
+ List<Path> tmpRelativeFileList2 = null;
+ if (dir1 == null && dir2 == null) {
+ equals = true;
+ } else if (dir1 == null ^ dir2 == null) {
+ equals = false;
+ } else {
+ final boolean parentDirExists1 = Files.exists(dir1,
linkOptions);
+ final boolean parentDirExists2 = Files.exists(dir2,
linkOptions);
+ if (!parentDirExists1 || !parentDirExists2) {
+ equals = !parentDirExists1 && !parentDirExists2;
+ } else {
+ AccumulatorPathVisitor visitor1 = accumulate(dir1,
maxDepth, linkOptions, fileVisitOptions);
+ AccumulatorPathVisitor visitor2 = accumulate(dir2,
maxDepth, linkOptions, fileVisitOptions);
+ if (visitor1.getDirList().size() !=
visitor2.getDirList().size()
+ || visitor1.getFileList().size() !=
visitor2.getFileList().size()) {
+ equals = false;
+ } else {
+ tmpRelativeDirList1 =
visitor1.relativizeDirectories(dir1, true, null);
+ tmpRelativeDirList2 =
visitor2.relativizeDirectories(dir2, true, null);
+ if (!tmpRelativeDirList1.equals(tmpRelativeDirList2)) {
+ equals = false;
+ } else {
+ tmpRelativeFileList1 =
visitor1.relativizeFiles(dir1, true, null);
+ tmpRelativeFileList2 =
visitor2.relativizeFiles(dir2, true, null);
+ equals =
tmpRelativeFileList1.equals(tmpRelativeFileList2);
+ }
+ }
+ }
+ }
+ relativeDirList1 = tmpRelativeDirList1;
+ relativeDirList2 = tmpRelativeDirList2;
+ relativeFileList1 = tmpRelativeFileList1;
+ relativeFileList2 = tmpRelativeFileList2;
+ }
+ }
+
+ /**
+ * Empty {@link FileVisitOption} array.
+ */
+ public static final FileVisitOption[] EMPTY_FILE_VISIT_OPTION_ARRAY = new
FileVisitOption[0];
+
+ /**
+ * Empty {@link LinkOption} array.
+ */
+ public static final LinkOption[] EMPTY_LINK_OPTION_ARRAY = new
LinkOption[0];
+
+ /**
+ * Empty {@link OpenOption} array.
+ */
+ public static final OpenOption[] EMPTY_OPEN_OPTION_ARRAY = new
OpenOption[0];
+
+ /**
* Cleans a directory including sub-directories without deleting
directories.
*
* @param directory directory to clean.
@@ -52,60 +157,6 @@ public final class PathUtils {
}
/**
- * Compares the contents of two Paths to determine if they are equal or
not.
- * <p>
- * File content is accessed through {@link
Files#newInputStream(Path,OpenOption...)}.
- * </p>
- *
- * @param path1 the first stream.
- * @param path2 the second stream.
- * @param options options specifying how the files are opened.
- * @return true if the content of the streams are equal or they both don't
exist, false otherwise.
- * @throws NullPointerException if either input is null.
- * @throws IOException if an I/O error occurs.
- * @see org.apache.commons.io.FileUtils#contentEquals(java.io.File,
java.io.File)
- */
- public static boolean fileContentEquals(final Path path1, final Path
path2, final OpenOption... options) throws IOException {
- if (path1 == null && path2 == null) {
- return true;
- }
- if (path1 == null ^ path2 == null) {
- return false;
- }
- final Path nPath1 = path1.normalize();
- final Path nPath2 = path2.normalize();
- final boolean path1Exists = Files.exists(nPath1);
- if (path1Exists != Files.exists(nPath2)) {
- return false;
- }
- if (!path1Exists) {
- // Two not existing files are equal?
- // Same as FileUtils
- return true;
- }
- if (Files.isDirectory(nPath1)) {
- // don't compare directory contents.
- throw new IOException("Can't compare directories, only files: " +
nPath1);
- }
- if (Files.isDirectory(nPath2)) {
- // don't compare directory contents.
- throw new IOException("Can't compare directories, only files: " +
nPath2);
- }
- if (Files.size(nPath1) != Files.size(nPath2)) {
- // lengths differ, cannot be equal
- return false;
- }
- if (path1.equals(path2)) {
- // same file
- return true;
- }
- try (final InputStream inputStream1 = Files.newInputStream(nPath1,
options);
- final InputStream inputStream2 = Files.newInputStream(nPath2,
options)) {
- return IOUtils.contentEquals(inputStream1, inputStream2);
- }
- }
-
- /**
* Copies a directory to another directory.
*
* @param sourceDirectory The source directory.
@@ -122,6 +173,24 @@ public final class PathUtils {
}
/**
+ * Copies a URL to a directory.
+ *
+ * @param sourceFile The source URL.
+ * @param targetFile The target file.
+ * @param copyOptions Specifies how the copying should be done.
+ * @return The target file
+ * @throws IOException if an I/O error occurs
+ * @see Files#copy(InputStream, Path, CopyOption...)
+ */
+ public static Path copyFile(final URL sourceFile, final Path targetFile,
final CopyOption... copyOptions)
+ throws IOException {
+ try (final InputStream inputStream = sourceFile.openStream()) {
+ Files.copy(inputStream, targetFile, copyOptions);
+ return targetFile;
+ }
+ }
+
+ /**
* Copies a file to a directory.
*
* @param sourceFile The source file.
@@ -155,24 +224,6 @@ public final class PathUtils {
}
/**
- * Copies a URL to a directory.
- *
- * @param sourceFile The source URL.
- * @param targetFile The target file.
- * @param copyOptions Specifies how the copying should be done.
- * @return The target file
- * @throws IOException if an I/O error occurs
- * @see Files#copy(InputStream, Path, CopyOption...)
- */
- public static Path copyFile(final URL sourceFile, final Path targetFile,
- final CopyOption... copyOptions) throws IOException {
- try (final InputStream inputStream = sourceFile.openStream()) {
- Files.copy(inputStream, targetFile, copyOptions);
- return targetFile;
- }
- }
-
- /**
* Counts aspects of a directory including sub-directories.
*
* @param directory directory to delete.
@@ -236,6 +287,171 @@ public final class PathUtils {
}
/**
+ * Compares the file sets of two Paths to determine if they are equal or
not while considering file contents. The
+ * comparison includes all files in all sub-directories.
+ *
+ * @param path1 The first directory.
+ * @param path2 The second directory.
+ * @return Whether the two directories contain the same files while
considering file contents.
+ * @throws IOException if an I/O error is thrown by a visitor method
+ */
+ public static boolean directoryAndFileContentEquals(final Path path1,
final Path path2) throws IOException {
+ return directoryAndFileContentEquals(path1, path2,
EMPTY_LINK_OPTION_ARRAY, EMPTY_OPEN_OPTION_ARRAY,
+ EMPTY_FILE_VISIT_OPTION_ARRAY);
+ }
+
+ /**
+ * Compares the file sets of two Paths to determine if they are equal or
not while considering file contents. The
+ * comparison includes all files in all sub-directories.
+ *
+ * @param path1 The first directory.
+ * @param path2 The second directory.
+ * @param linkOptions options to follow links.
+ * @param openOptions options to open files.
+ * @param fileVisitOption options to configure traversal.
+ * @return Whether the two directories contain the same files while
considering file contents.
+ * @throws IOException if an I/O error is thrown by a visitor method
+ */
+ public static boolean directoryAndFileContentEquals(final Path path1,
final Path path2,
+ final LinkOption[] linkOptions, final OpenOption[] openOptions,
final FileVisitOption... fileVisitOption)
+ throws IOException {
+ // First walk both file trees and gather normalized paths.
+ if (path1 == null && path2 == null) {
+ return true;
+ }
+ if (path1 == null ^ path2 == null) {
+ return false;
+ }
+ if (!Files.exists(path1) && !Files.exists(path2)) {
+ return true;
+ }
+ final RelativeSortedPaths relativeSortedPaths = new
RelativeSortedPaths(path1, path2, Integer.MAX_VALUE,
+ linkOptions, fileVisitOption);
+ // If the normalized path names and counts are not the same, no need
to compare contents.
+ if (!relativeSortedPaths.equals) {
+ return false;
+ }
+ // Both visitors contain the same normalized paths, we can compare
file contents.
+ final List<Path> fileList1 = relativeSortedPaths.relativeFileList1;
+ final List<Path> fileList2 = relativeSortedPaths.relativeFileList2;
+ for (Path path : fileList1) {
+ final int binarySearch = Collections.binarySearch(fileList2, path);
+ if (binarySearch > -1) {
+ if (!fileContentEquals(path1.resolve(path),
path2.resolve(path), linkOptions, openOptions)) {
+ return false;
+ }
+ } else {
+ throw new IllegalStateException(String.format("Unexpected
mismatch."));
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Compares the file sets of two Paths to determine if they are equal or
not without considering file contents. The
+ * comparison includes all files in all sub-directories.
+ *
+ * @param path1 The first directory.
+ * @param path2 The second directory.
+ * @return Whether the two directories contain the same files without
considering file contents.
+ * @throws IOException if an I/O error is thrown by a visitor method
+ */
+ public static boolean directoryContentEquals(final Path path1, final Path
path2) throws IOException {
+ return directoryContentEquals(path1, path2, Integer.MAX_VALUE,
EMPTY_LINK_OPTION_ARRAY,
+ EMPTY_FILE_VISIT_OPTION_ARRAY);
+ }
+
+ /**
+ * Compares the file sets of two Paths to determine if they are equal or
not without considering file contents. The
+ * comparison includes all files in all sub-directories.
+ *
+ * @param path1 The first directory.
+ * @param path2 The second directory.
+ * @param maxDepth See {@link
Files#walkFileTree(Path,Set,int,FileVisitor)}.
+ * @param linkOptions options to follow links.
+ * @param fileVisitOptions options to configure the traversal
+ * @return Whether the two directories contain the same files without
considering file contents.
+ * @throws IOException if an I/O error is thrown by a visitor method
+ */
+ public static boolean directoryContentEquals(final Path path1, final Path
path2, final int maxDepth,
+ LinkOption[] linkOptions, FileVisitOption... fileVisitOptions)
throws IOException {
+ return new RelativeSortedPaths(path1, path2, maxDepth, linkOptions,
fileVisitOptions).equals;
+ }
+
+ /**
+ * Compares the file contents of two Paths to determine if they are equal
or not.
+ * <p>
+ * File content is accessed through {@link
Files#newInputStream(Path,OpenOption...)}.
+ * </p>
+ *
+ * @param path1 the first stream.
+ * @param path2 the second stream.
+ * @return true if the content of the streams are equal or they both don't
exist, false otherwise.
+ * @throws NullPointerException if either input is null.
+ * @throws IOException if an I/O error occurs.
+ * @see org.apache.commons.io.FileUtils#contentEquals(java.io.File,
java.io.File)
+ */
+ public static boolean fileContentEquals(final Path path1, final Path
path2) throws IOException {
+ return fileContentEquals(path1, path2, EMPTY_LINK_OPTION_ARRAY,
EMPTY_OPEN_OPTION_ARRAY);
+ }
+
+ /**
+ * Compares the file contents of two Paths to determine if they are equal
or not.
+ * <p>
+ * File content is accessed through {@link
Files#newInputStream(Path,OpenOption...)}.
+ * </p>
+ *
+ * @param path1 the first stream.
+ * @param path2 the second stream.
+ * @param linkOptions options specifying how files are followed.
+ * @param openOptions options specifying how files are opened.
+ * @return true if the content of the streams are equal or they both don't
exist, false otherwise.
+ * @throws NullPointerException if either input is null.
+ * @throws IOException if an I/O error occurs.
+ * @see org.apache.commons.io.FileUtils#contentEquals(java.io.File,
java.io.File)
+ */
+ public static boolean fileContentEquals(final Path path1, final Path
path2, final LinkOption[] linkOptions,
+ final OpenOption... openOptions) throws IOException {
+ if (path1 == null && path2 == null) {
+ return true;
+ }
+ if (path1 == null ^ path2 == null) {
+ return false;
+ }
+ final Path nPath1 = path1.normalize();
+ final Path nPath2 = path2.normalize();
+ final boolean path1Exists = Files.exists(nPath1, linkOptions);
+ if (path1Exists != Files.exists(nPath2, linkOptions)) {
+ return false;
+ }
+ if (!path1Exists) {
+ // Two not existing files are equal?
+ // Same as FileUtils
+ return true;
+ }
+ if (Files.isDirectory(nPath1, linkOptions)) {
+ // don't compare directory contents.
+ throw new IOException("Can't compare directories, only files: " +
nPath1);
+ }
+ if (Files.isDirectory(nPath2, linkOptions)) {
+ // don't compare directory contents.
+ throw new IOException("Can't compare directories, only files: " +
nPath2);
+ }
+ if (Files.size(nPath1) != Files.size(nPath2)) {
+ // lengths differ, cannot be equal
+ return false;
+ }
+ if (path1.equals(path2)) {
+ // same file
+ return true;
+ }
+ try (final InputStream inputStream1 = Files.newInputStream(nPath1,
openOptions);
+ final InputStream inputStream2 = Files.newInputStream(nPath2,
openOptions)) {
+ return IOUtils.contentEquals(inputStream1, inputStream2);
+ }
+ }
+
+ /**
* Returns whether the given file or directory is empty.
*
* @param path the the given file or directory to query.
@@ -274,13 +490,41 @@ public final class PathUtils {
}
/**
+ * Relativizes all files in the given {@code collection} against a {@code
parent}.
+ *
+ * @param collection The collection of paths to relativize.
+ * @param parent relativizes against this parent path.
+ * @param sort Whether to sort the result.
+ * @param comparator How to sort.
+ * @return A collection of relativized paths, optionally sorted.
+ */
+ static List<Path> relativize(Collection<Path> collection, Path parent,
boolean sort,
+ Comparator<? super Path> comparator) {
+ Stream<Path> stream = collection.stream().map(e ->
parent.relativize(e));
+ if (sort) {
+ stream = comparator == null ? stream.sorted() :
stream.sorted(comparator);
+ }
+ return stream.collect(Collectors.toList());
+ }
+
+ /**
+ * Converts an array of {@link FileVisitOption} to a {@link Set}.
+ *
+ * @param fileVisitOptions input array.
+ * @return a new Set.
+ */
+ static Set<FileVisitOption> toFileVisitOptionSet(final FileVisitOption...
fileVisitOptions) {
+ return fileVisitOptions == null ? EnumSet.noneOf(FileVisitOption.class)
+ : Arrays.stream(fileVisitOptions).collect(Collectors.toSet());
+ }
+
+ /**
* Performs {@link Files#walkFileTree(Path,FileVisitor)} and returns the
given visitor.
*
* Note that {@link Files#walkFileTree(Path,FileVisitor)} returns the
given path.
*
* @param visitor See {@link Files#walkFileTree(Path,FileVisitor)}.
* @param directory See {@link Files#walkFileTree(Path,FileVisitor)}.
- *
* @param <T> See {@link Files#walkFileTree(Path,FileVisitor)}.
* @return the given visitor.
*
@@ -297,6 +541,26 @@ public final class PathUtils {
*
* Note that {@link Files#walkFileTree(Path,FileVisitor)} returns the
given path.
*
+ * @param start See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}.
+ * @param options See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}.
+ * @param maxDepth See {@link
Files#walkFileTree(Path,Set,int,FileVisitor)}.
+ * @param visitor See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}.
+ * @param <T> See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}.
+ * @return the given visitor.
+ *
+ * @throws IOException if an I/O error is thrown by a visitor method
+ */
+ public static <T extends FileVisitor<? super Path>> T visitFileTree(T
visitor, Path start,
+ Set<FileVisitOption> options, int maxDepth) throws IOException {
+ Files.walkFileTree(start, options, maxDepth, visitor);
+ return visitor;
+ }
+
+ /**
+ * Performs {@link Files#walkFileTree(Path,FileVisitor)} and returns the
given visitor.
+ *
+ * Note that {@link Files#walkFileTree(Path,FileVisitor)} returns the
given path.
+ *
* @param visitor See {@link Files#walkFileTree(Path,FileVisitor)}.
* @param first See {@link Paths#get(String,String[])}.
* @param more See {@link Paths#get(String,String[])}.
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 7bcba2e..653a7ce 100644
--- a/src/test/java/org/apache/commons/io/file/PathUtilsContentEqualsTest.java
+++ b/src/test/java/org/apache/commons/io/file/PathUtilsContentEqualsTest.java
@@ -89,4 +89,116 @@ public class PathUtilsContentEqualsTest {
assertTrue(PathUtils.fileContentEquals(path1, path2));
}
+ @Test
+ public void testDirectoryContentEquals() throws Exception {
+ // Non-existent files
+ final Path path1 = new File(temporaryFolder, getName()).toPath();
+ final Path path2 = new File(temporaryFolder, getName() + "2").toPath();
+ assertTrue(PathUtils.directoryContentEquals(null, null));
+ assertFalse(PathUtils.directoryContentEquals(null, path1));
+ assertFalse(PathUtils.directoryContentEquals(path1, null));
+ // both don't exist
+ assertTrue(PathUtils.directoryContentEquals(path1, path1));
+ assertTrue(PathUtils.directoryContentEquals(path1, path2));
+ assertTrue(PathUtils.directoryContentEquals(path2, path2));
+ assertTrue(PathUtils.directoryContentEquals(path2, path1));
+ // Tree equals true tests
+ {
+ // Trees of files only that contain the same files.
+ final Path dir1 =
Paths.get("src/test/resources/dir-equals-tests/dir-equals-files-only/directory-files-only1");
+ final Path dir2 =
Paths.get("src/test/resources/dir-equals-tests/dir-equals-files-only/directory-files-only2");
+ assertTrue(PathUtils.directoryContentEquals(dir1, dir2));
+ assertTrue(PathUtils.directoryContentEquals(dir2, dir2));
+ assertTrue(PathUtils.directoryContentEquals(dir1, dir1));
+ assertTrue(PathUtils.directoryContentEquals(dir2, dir2));
+ }
+ {
+ // Trees of directories containing other directories.
+ final Path dir1 =
Paths.get("src/test/resources/dir-equals-tests/dir-equals-dirs-then-files/dir1");
+ final Path dir2 =
Paths.get("src/test/resources/dir-equals-tests/dir-equals-dirs-then-files/dir2");
+ assertTrue(PathUtils.directoryContentEquals(dir1, dir2));
+ assertTrue(PathUtils.directoryContentEquals(dir2, dir2));
+ assertTrue(PathUtils.directoryContentEquals(dir1, dir1));
+ assertTrue(PathUtils.directoryContentEquals(dir2, dir2));
+ }
+ {
+ // Trees of directories containing other directories and files.
+ final Path dir1 =
Paths.get("src/test/resources/dir-equals-tests/dir-equals-dirs-and-files/dirs-and-files1");
+ final Path dir2 =
Paths.get("src/test/resources/dir-equals-tests/dir-equals-dirs-and-files/dirs-and-files1");
+ assertTrue(PathUtils.directoryContentEquals(dir1, dir2));
+ assertTrue(PathUtils.directoryContentEquals(dir2, dir2));
+ assertTrue(PathUtils.directoryContentEquals(dir1, dir1));
+ assertTrue(PathUtils.directoryContentEquals(dir2, dir2));
+ }
+ // Tree equals false tests
+ {
+ final Path dir1 =
Paths.get("src/test/resources/dir-equals-tests/dir-equals-dirs-and-files/dirs-and-files1/directory-files-only1");
+ final Path dir2 =
Paths.get("src/test/resources/dir-equals-tests/dir-equals-dirs-and-files/dirs-and-files1/");
+ assertFalse(PathUtils.directoryContentEquals(dir1, dir2));
+ assertFalse(PathUtils.directoryContentEquals(dir2, dir1));
+ }
+ {
+ final Path dir1 =
Paths.get("src/test/resources/dir-equals-tests/dir-equals-dirs-and-files");
+ final Path dir2 =
Paths.get("src/test/resources/dir-equals-tests/dir-equals-dirs-then-files");
+ assertFalse(PathUtils.directoryContentEquals(dir1, dir2));
+ assertFalse(PathUtils.directoryContentEquals(dir2, dir1));
+ }
+ }
+
+ @Test
+ public void testDirectoryAndFileContentEquals() throws Exception {
+ // Non-existent files
+ final Path path1 = new File(temporaryFolder, getName()).toPath();
+ final Path path2 = new File(temporaryFolder, getName() + "2").toPath();
+ assertTrue(PathUtils.directoryAndFileContentEquals(null, null));
+ assertFalse(PathUtils.directoryAndFileContentEquals(null, path1));
+ assertFalse(PathUtils.directoryAndFileContentEquals(path1, null));
+ // both don't exist
+ assertTrue(PathUtils.directoryAndFileContentEquals(path1, path1));
+ assertTrue(PathUtils.directoryAndFileContentEquals(path1, path2));
+ assertTrue(PathUtils.directoryAndFileContentEquals(path2, path2));
+ assertTrue(PathUtils.directoryAndFileContentEquals(path2, path1));
+ // Tree equals true tests
+ {
+ // Trees of files only that contain the same files.
+ final Path dir1 =
Paths.get("src/test/resources/dir-equals-tests/dir-equals-files-only/directory-files-only1");
+ final Path dir2 =
Paths.get("src/test/resources/dir-equals-tests/dir-equals-files-only/directory-files-only2");
+ assertTrue(PathUtils.directoryAndFileContentEquals(dir1, dir2));
+ assertTrue(PathUtils.directoryAndFileContentEquals(dir2, dir2));
+ assertTrue(PathUtils.directoryAndFileContentEquals(dir1, dir1));
+ assertTrue(PathUtils.directoryAndFileContentEquals(dir2, dir2));
+ }
+ {
+ // Trees of directories containing other directories.
+ final Path dir1 =
Paths.get("src/test/resources/dir-equals-tests/dir-equals-dirs-then-files/dir1");
+ final Path dir2 =
Paths.get("src/test/resources/dir-equals-tests/dir-equals-dirs-then-files/dir2");
+ assertTrue(PathUtils.directoryAndFileContentEquals(dir1, dir2));
+ assertTrue(PathUtils.directoryAndFileContentEquals(dir2, dir2));
+ assertTrue(PathUtils.directoryAndFileContentEquals(dir1, dir1));
+ assertTrue(PathUtils.directoryAndFileContentEquals(dir2, dir2));
+ }
+ {
+ // Trees of directories containing other directories and files.
+ final Path dir1 =
Paths.get("src/test/resources/dir-equals-tests/dir-equals-dirs-and-files/dirs-and-files1");
+ final Path dir2 =
Paths.get("src/test/resources/dir-equals-tests/dir-equals-dirs-and-files/dirs-and-files1");
+ assertTrue(PathUtils.directoryAndFileContentEquals(dir1, dir2));
+ assertTrue(PathUtils.directoryAndFileContentEquals(dir2, dir2));
+ assertTrue(PathUtils.directoryAndFileContentEquals(dir1, dir1));
+ assertTrue(PathUtils.directoryAndFileContentEquals(dir2, dir2));
+ }
+ // Tree equals false tests
+ {
+ final Path dir1 =
Paths.get("src/test/resources/dir-equals-tests/dir-equals-dirs-and-files/dirs-and-files1/directory-files-only1");
+ final Path dir2 =
Paths.get("src/test/resources/dir-equals-tests/dir-equals-dirs-and-files/dirs-and-files1/");
+ assertFalse(PathUtils.directoryAndFileContentEquals(dir1, dir2));
+ assertFalse(PathUtils.directoryAndFileContentEquals(dir2, dir1));
+ }
+ {
+ final Path dir1 =
Paths.get("src/test/resources/dir-equals-tests/dir-equals-dirs-and-files");
+ final Path dir2 =
Paths.get("src/test/resources/dir-equals-tests/dir-equals-dirs-then-files");
+ assertFalse(PathUtils.directoryAndFileContentEquals(dir1, dir2));
+ assertFalse(PathUtils.directoryAndFileContentEquals(dir2, dir1));
+ }
+ }
+
}
diff --git
a/src/test/resources/dir-equals-tests/dir-equals-dirs-and-files/dirs-and-files1/directory-files-only1/file1.txt
b/src/test/resources/dir-equals-tests/dir-equals-dirs-and-files/dirs-and-files1/directory-files-only1/file1.txt
new file mode 100644
index 0000000..56a6051
--- /dev/null
+++
b/src/test/resources/dir-equals-tests/dir-equals-dirs-and-files/dirs-and-files1/directory-files-only1/file1.txt
@@ -0,0 +1 @@
+1
\ No newline at end of file
diff --git
a/src/test/resources/dir-equals-tests/dir-equals-dirs-and-files/dirs-and-files1/directory-files-only1/file2.txt
b/src/test/resources/dir-equals-tests/dir-equals-dirs-and-files/dirs-and-files1/directory-files-only1/file2.txt
new file mode 100644
index 0000000..d8263ee
--- /dev/null
+++
b/src/test/resources/dir-equals-tests/dir-equals-dirs-and-files/dirs-and-files1/directory-files-only1/file2.txt
@@ -0,0 +1 @@
+2
\ No newline at end of file
diff --git
a/src/test/resources/dir-equals-tests/dir-equals-dirs-and-files/dirs-and-files1/file1.txt
b/src/test/resources/dir-equals-tests/dir-equals-dirs-and-files/dirs-and-files1/file1.txt
new file mode 100644
index 0000000..56a6051
--- /dev/null
+++
b/src/test/resources/dir-equals-tests/dir-equals-dirs-and-files/dirs-and-files1/file1.txt
@@ -0,0 +1 @@
+1
\ No newline at end of file
diff --git
a/src/test/resources/dir-equals-tests/dir-equals-dirs-and-files/dirs-and-files1/file2.txt
b/src/test/resources/dir-equals-tests/dir-equals-dirs-and-files/dirs-and-files1/file2.txt
new file mode 100644
index 0000000..d8263ee
--- /dev/null
+++
b/src/test/resources/dir-equals-tests/dir-equals-dirs-and-files/dirs-and-files1/file2.txt
@@ -0,0 +1 @@
+2
\ No newline at end of file
diff --git
a/src/test/resources/dir-equals-tests/dir-equals-dirs-and-files/dirs-and-files2/directory-files-only2/file1.txt
b/src/test/resources/dir-equals-tests/dir-equals-dirs-and-files/dirs-and-files2/directory-files-only2/file1.txt
new file mode 100644
index 0000000..56a6051
--- /dev/null
+++
b/src/test/resources/dir-equals-tests/dir-equals-dirs-and-files/dirs-and-files2/directory-files-only2/file1.txt
@@ -0,0 +1 @@
+1
\ No newline at end of file
diff --git
a/src/test/resources/dir-equals-tests/dir-equals-dirs-and-files/dirs-and-files2/directory-files-only2/file2.txt
b/src/test/resources/dir-equals-tests/dir-equals-dirs-and-files/dirs-and-files2/directory-files-only2/file2.txt
new file mode 100644
index 0000000..d8263ee
--- /dev/null
+++
b/src/test/resources/dir-equals-tests/dir-equals-dirs-and-files/dirs-and-files2/directory-files-only2/file2.txt
@@ -0,0 +1 @@
+2
\ No newline at end of file
diff --git
a/src/test/resources/dir-equals-tests/dir-equals-dirs-and-files/dirs-and-files2/file1.txt
b/src/test/resources/dir-equals-tests/dir-equals-dirs-and-files/dirs-and-files2/file1.txt
new file mode 100644
index 0000000..56a6051
--- /dev/null
+++
b/src/test/resources/dir-equals-tests/dir-equals-dirs-and-files/dirs-and-files2/file1.txt
@@ -0,0 +1 @@
+1
\ No newline at end of file
diff --git
a/src/test/resources/dir-equals-tests/dir-equals-dirs-and-files/dirs-and-files2/file2.txt
b/src/test/resources/dir-equals-tests/dir-equals-dirs-and-files/dirs-and-files2/file2.txt
new file mode 100644
index 0000000..d8263ee
--- /dev/null
+++
b/src/test/resources/dir-equals-tests/dir-equals-dirs-and-files/dirs-and-files2/file2.txt
@@ -0,0 +1 @@
+2
\ No newline at end of file
diff --git
a/src/test/resources/dir-equals-tests/dir-equals-dirs-then-files/dir1/directory-files-only1/file1.txt
b/src/test/resources/dir-equals-tests/dir-equals-dirs-then-files/dir1/directory-files-only1/file1.txt
new file mode 100644
index 0000000..56a6051
--- /dev/null
+++
b/src/test/resources/dir-equals-tests/dir-equals-dirs-then-files/dir1/directory-files-only1/file1.txt
@@ -0,0 +1 @@
+1
\ No newline at end of file
diff --git
a/src/test/resources/dir-equals-tests/dir-equals-dirs-then-files/dir1/directory-files-only1/file2.txt
b/src/test/resources/dir-equals-tests/dir-equals-dirs-then-files/dir1/directory-files-only1/file2.txt
new file mode 100644
index 0000000..d8263ee
--- /dev/null
+++
b/src/test/resources/dir-equals-tests/dir-equals-dirs-then-files/dir1/directory-files-only1/file2.txt
@@ -0,0 +1 @@
+2
\ No newline at end of file
diff --git
a/src/test/resources/dir-equals-tests/dir-equals-dirs-then-files/dir2/directory-files-only1/file1.txt
b/src/test/resources/dir-equals-tests/dir-equals-dirs-then-files/dir2/directory-files-only1/file1.txt
new file mode 100644
index 0000000..56a6051
--- /dev/null
+++
b/src/test/resources/dir-equals-tests/dir-equals-dirs-then-files/dir2/directory-files-only1/file1.txt
@@ -0,0 +1 @@
+1
\ No newline at end of file
diff --git
a/src/test/resources/dir-equals-tests/dir-equals-dirs-then-files/dir2/directory-files-only1/file2.txt
b/src/test/resources/dir-equals-tests/dir-equals-dirs-then-files/dir2/directory-files-only1/file2.txt
new file mode 100644
index 0000000..d8263ee
--- /dev/null
+++
b/src/test/resources/dir-equals-tests/dir-equals-dirs-then-files/dir2/directory-files-only1/file2.txt
@@ -0,0 +1 @@
+2
\ No newline at end of file
diff --git
a/src/test/resources/dir-equals-tests/dir-equals-dirs-then-files/directory-files-only1/file1.txt
b/src/test/resources/dir-equals-tests/dir-equals-dirs-then-files/directory-files-only1/file1.txt
new file mode 100644
index 0000000..56a6051
--- /dev/null
+++
b/src/test/resources/dir-equals-tests/dir-equals-dirs-then-files/directory-files-only1/file1.txt
@@ -0,0 +1 @@
+1
\ No newline at end of file
diff --git
a/src/test/resources/dir-equals-tests/dir-equals-dirs-then-files/directory-files-only1/file2.txt
b/src/test/resources/dir-equals-tests/dir-equals-dirs-then-files/directory-files-only1/file2.txt
new file mode 100644
index 0000000..d8263ee
--- /dev/null
+++
b/src/test/resources/dir-equals-tests/dir-equals-dirs-then-files/directory-files-only1/file2.txt
@@ -0,0 +1 @@
+2
\ No newline at end of file
diff --git
a/src/test/resources/dir-equals-tests/dir-equals-dirs-then-files/directory-files-only2/file1.txt
b/src/test/resources/dir-equals-tests/dir-equals-dirs-then-files/directory-files-only2/file1.txt
new file mode 100644
index 0000000..56a6051
--- /dev/null
+++
b/src/test/resources/dir-equals-tests/dir-equals-dirs-then-files/directory-files-only2/file1.txt
@@ -0,0 +1 @@
+1
\ No newline at end of file
diff --git
a/src/test/resources/dir-equals-tests/dir-equals-dirs-then-files/directory-files-only2/file2.txt
b/src/test/resources/dir-equals-tests/dir-equals-dirs-then-files/directory-files-only2/file2.txt
new file mode 100644
index 0000000..d8263ee
--- /dev/null
+++
b/src/test/resources/dir-equals-tests/dir-equals-dirs-then-files/directory-files-only2/file2.txt
@@ -0,0 +1 @@
+2
\ No newline at end of file
diff --git
a/src/test/resources/dir-equals-tests/dir-equals-files-only/directory-files-only1/file1.txt
b/src/test/resources/dir-equals-tests/dir-equals-files-only/directory-files-only1/file1.txt
new file mode 100644
index 0000000..56a6051
--- /dev/null
+++
b/src/test/resources/dir-equals-tests/dir-equals-files-only/directory-files-only1/file1.txt
@@ -0,0 +1 @@
+1
\ No newline at end of file
diff --git
a/src/test/resources/dir-equals-tests/dir-equals-files-only/directory-files-only1/file2.txt
b/src/test/resources/dir-equals-tests/dir-equals-files-only/directory-files-only1/file2.txt
new file mode 100644
index 0000000..d8263ee
--- /dev/null
+++
b/src/test/resources/dir-equals-tests/dir-equals-files-only/directory-files-only1/file2.txt
@@ -0,0 +1 @@
+2
\ No newline at end of file
diff --git
a/src/test/resources/dir-equals-tests/dir-equals-files-only/directory-files-only2/file1.txt
b/src/test/resources/dir-equals-tests/dir-equals-files-only/directory-files-only2/file1.txt
new file mode 100644
index 0000000..56a6051
--- /dev/null
+++
b/src/test/resources/dir-equals-tests/dir-equals-files-only/directory-files-only2/file1.txt
@@ -0,0 +1 @@
+1
\ No newline at end of file
diff --git
a/src/test/resources/dir-equals-tests/dir-equals-files-only/directory-files-only2/file2.txt
b/src/test/resources/dir-equals-tests/dir-equals-files-only/directory-files-only2/file2.txt
new file mode 100644
index 0000000..d8263ee
--- /dev/null
+++
b/src/test/resources/dir-equals-tests/dir-equals-files-only/directory-files-only2/file2.txt
@@ -0,0 +1 @@
+2
\ No newline at end of file