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 <garydgreg...@gmail.com> 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. } - }