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 32e881a31 [IO-872] PathUtils.directoryAndFileContentEquals doesn't
work across FileSystems
32e881a31 is described below
commit 32e881a31ac960871c8362c190bee964602efde4
Author: Gary Gregory <[email protected]>
AuthorDate: Fri Apr 4 08:07:06 2025 -0400
[IO-872] PathUtils.directoryAndFileContentEquals doesn't work across
FileSystems
- Fix for Windows
- Already worked on macOS and Linux
- Must account for file system separator differences
---
.../java/org/apache/commons/io/file/PathUtils.java | 81 ++++++++++------------
1 file changed, 37 insertions(+), 44 deletions(-)
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 679770f9b..460234bc7 100644
--- a/src/main/java/org/apache/commons/io/file/PathUtils.java
+++ b/src/main/java/org/apache/commons/io/file/PathUtils.java
@@ -103,7 +103,7 @@ private static final class RelativeSortedPaths {
* @param list2 the second list.
* @return whether the lists are equal.
*/
- private static boolean equals(final List<Path> list1, final List<Path>
list2) {
+ private static boolean equalsIgnoreFileSystem(final List<Path> list1,
final List<Path> list2) {
if (list1.size() != list2.size()) {
return false;
}
@@ -111,36 +111,37 @@ private static boolean equals(final List<Path> list1,
final List<Path> list2) {
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();
+ if (!equalsIgnoreFileSystem(iterator1.next(),
iterator2.next())) {
+ return false;
}
}
return true;
}
+ private static boolean equalsIgnoreFileSystem(final Path path1, final
Path path2) {
+ final FileSystem fileSystem1 = path1.getFileSystem();
+ final FileSystem fileSystem2 = path2.getFileSystem();
+ if (fileSystem1 == fileSystem2) {
+ return path1.equals(path2);
+ }
+ final String separator1 = fileSystem1.getSeparator();
+ final String separator2 = fileSystem2.getSeparator();
+ final String string1 = path1.toString();
+ final String string2 = path2.toString();
+ if (separator1.equals(separator2)) {
+ // Separators are the same, so we can use toString comparison
+ return string1.equals(string2);
+ }
+ // Compare paths from different file systems component by
component.
+ return extractKey(separator1,
string1).equals(extractKey(separator2, string2));
+ //return Arrays.equals(string1.split("\\" + separator1),
string2.split("\\" + separator2));
+ }
+
+ static String extractKey(final String separator, final String string) {
+ // Replace the file separator in a path string with a string that
is not legal in a path on Windows, Linux, and macOS.
+ return string.replaceAll("\\" + separator, ">");
+ }
+
final boolean equals;
// final List<Path> relativeDirList1; // might need later?
// final List<Path> relativeDirList2; // might need later?
@@ -180,12 +181,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 (!equals(tmpRelativeDirList1, tmpRelativeDirList2))
{
+ if (!equalsIgnoreFileSystem(tmpRelativeDirList1,
tmpRelativeDirList2)) {
equals = false;
} else {
tmpRelativeFileList1 =
visitor1.relativizeFiles(dir1, true, null);
tmpRelativeFileList2 =
visitor2.relativizeFiles(dir2, true, null);
- equals = equals(tmpRelativeFileList1,
tmpRelativeFileList2);
+ equals =
equalsIgnoreFileSystem(tmpRelativeFileList1, tmpRelativeFileList2);
}
}
}
@@ -198,40 +199,33 @@ private RelativeSortedPaths(final Path dir1, final Path
dir2, final int maxDepth
}
private static final OpenOption[] OPEN_OPTIONS_TRUNCATE = {
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING };
-
private static final OpenOption[] OPEN_OPTIONS_APPEND = {
StandardOpenOption.CREATE, StandardOpenOption.APPEND };
-
/**
* Empty {@link CopyOption} array.
*
* @since 2.8.0
*/
public static final CopyOption[] EMPTY_COPY_OPTIONS = {};
-
/**
* Empty {@link DeleteOption} array.
*
* @since 2.8.0
*/
public static final DeleteOption[] EMPTY_DELETE_OPTION_ARRAY = {};
-
/**
* Empty {@link FileAttribute} array.
*
* @since 2.13.0
*/
public static final FileAttribute<?>[] EMPTY_FILE_ATTRIBUTE_ARRAY = {};
-
/**
* Empty {@link FileVisitOption} array.
*/
public static final FileVisitOption[] EMPTY_FILE_VISIT_OPTION_ARRAY = {};
-
/**
* Empty {@link LinkOption} array.
*/
public static final LinkOption[] EMPTY_LINK_OPTION_ARRAY = {};
-
/**
* {@link LinkOption} array for {@link LinkOption#NOFOLLOW_LINKS}.
*
@@ -240,19 +234,16 @@ private RelativeSortedPaths(final Path dir1, final Path
dir2, final int maxDepth
*/
@Deprecated
public static final LinkOption[] NOFOLLOW_LINK_OPTION_ARRAY = {
LinkOption.NOFOLLOW_LINKS };
-
/**
* A LinkOption used to follow link in this class, the inverse of {@link
LinkOption#NOFOLLOW_LINKS}.
*
* @since 2.12.0
*/
static final LinkOption NULL_LINK_OPTION = null;
-
/**
* Empty {@link OpenOption} array.
*/
public static final OpenOption[] EMPTY_OPEN_OPTION_ARRAY = {};
-
/**
* Empty {@link Path} array.
*
@@ -712,8 +703,9 @@ public static boolean directoryAndFileContentEquals(final
Path path1, final Path
final boolean sameFileSystem = isSameFileSystem(path1, path2);
for (final Path path : fileList1) {
final int binarySearch = sameFileSystem ?
Collections.binarySearch(fileList2, path)
- : Collections.binarySearch(fileList2, path,
Comparator.comparing(Path::toString));
- if (binarySearch <= -1) {
+ : Collections.binarySearch(fileList2, path,
+ Comparator.comparing(p ->
RelativeSortedPaths.extractKey(p.getFileSystem().getSeparator(),
p.toString())));
+ if (binarySearch < 0) {
throw new IllegalStateException("Unexpected mismatch.");
}
if (sameFileSystem && !fileContentEquals(path1.resolve(path),
path2.resolve(path), linkOptions, openOptions)) {
@@ -949,6 +941,7 @@ public static DosFileAttributeView
getDosFileAttributeView(final Path path, fina
* <p>
* This method returns the textual part of the Path after the last period.
* </p>
+ *
* <pre>
* foo.txt --> "txt"
* a/b/c.jpg --> "jpg"
@@ -971,8 +964,8 @@ public static String getExtension(final Path path) {
/**
* Gets the Path's file name and apply the given function if the file name
is non-null.
*
- * @param <R> The function's result type.
- * @param path the path to query.
+ * @param <R> The function's result type.
+ * @param path the path to query.
* @param function function to apply to the file name.
* @return the Path's file name as a string or null.
* @see Path#getFileName()
@@ -1744,7 +1737,8 @@ public static BigInteger
sizeOfDirectoryAsBigInteger(final Path directory) throw
return
countDirectoryAsBigInteger(directory).getByteCounter().getBigInteger();
}
- private static Path stripTrailingSeparator(final Path dir) {
+ private static Path stripTrailingSeparator(final Path cdir) {
+ final Path dir = cdir.normalize();
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;
@@ -1952,5 +1946,4 @@ public static Path writeString(final Path path, final
CharSequence charSequence,
private PathUtils() {
// do not instantiate.
}
-
}