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 95cb90dab PathUtils.copyFileToDirectory() creates incorrect file names when copying between different file systems that use different file system separators ("/" versus "\") 95cb90dab is described below commit 95cb90dab5a3220274fa9dfd4ad4219f9ddba200 Author: Gary Gregory <garydgreg...@gmail.com> AuthorDate: Sat Apr 5 15:24:07 2025 -0400 PathUtils.copyFileToDirectory() creates incorrect file names when copying between different file systems that use different file system separators ("/" versus "\") --- .../commons/io/file/CopyDirectoryVisitor.java | 15 +-- .../java/org/apache/commons/io/file/PathUtils.java | 115 +++++++++++---------- 2 files changed, 62 insertions(+), 68 deletions(-) diff --git a/src/main/java/org/apache/commons/io/file/CopyDirectoryVisitor.java b/src/main/java/org/apache/commons/io/file/CopyDirectoryVisitor.java index 0fa09f200..2716357b8 100644 --- a/src/main/java/org/apache/commons/io/file/CopyDirectoryVisitor.java +++ b/src/main/java/org/apache/commons/io/file/CopyDirectoryVisitor.java @@ -19,7 +19,6 @@ import java.io.IOException; import java.nio.file.CopyOption; -import java.nio.file.FileSystem; import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; @@ -155,18 +154,6 @@ public FileVisitResult preVisitDirectory(final Path directory, final BasicFileAt return super.preVisitDirectory(directory, attributes); } - private Path resolve(final Path otherPath) { - final FileSystem fileSystemTarget = targetDirectory.getFileSystem(); - final FileSystem fileSystemSource = sourceDirectory.getFileSystem(); - if (fileSystemTarget == fileSystemSource) { - return targetDirectory.resolve(otherPath); - } - final String separatorSource = fileSystemSource.getSeparator(); - final String separatorTarget = fileSystemTarget.getSeparator(); - final String otherString = otherPath.toString(); - return targetDirectory.resolve(Objects.equals(separatorSource, separatorTarget) ? otherString : otherString.replace(separatorSource, separatorTarget)); - } - /** * Relativizes against {@code sourceDirectory}, then resolves against {@code targetDirectory}. * <p> @@ -178,7 +165,7 @@ private Path resolve(final Path otherPath) { * @return a new path, relativized against sourceDirectory, then resolved against targetDirectory. */ private Path resolveRelativeAsString(final Path directory) { - return resolve(sourceDirectory.relativize(directory)); + return PathUtils.resolve(targetDirectory, sourceDirectory.relativize(directory)); } @Override 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 3ad5db134..4ea36dd71 100644 --- a/src/main/java/org/apache/commons/io/file/PathUtils.java +++ b/src/main/java/org/apache/commons/io/file/PathUtils.java @@ -303,6 +303,44 @@ private static int compareLastModifiedTimeTo(final Path file, final FileTime fil return getLastModifiedTime(file, options).compareTo(fileTime); } + /** + * Compares the files of two FileSystems to determine if they are equal or not while considering file contents. The comparison includes all files in all + * subdirectories. + * <p> + * For example, to compare two ZIP files: + * </p> + * + * <pre> + * final Path zipPath1 = Paths.get("file1.zip"); + * final Path zipPath2 = Paths.get("file2.zip"); + * try (FileSystem fileSystem1 = FileSystems.newFileSystem(zipPath1, null); FileSystem fileSystem2 = FileSystems.newFileSystem(zipPath2, null)) { + * assertTrue(PathUtils.directoryAndFileContentEquals(dir1, dir2)); + * } + * </pre> + * + * @param fileSystem1 The first FileSystem. + * @param fileSystem2 The second FileSystem. + * @return Whether the two FileSystem contain the same files while considering file contents. + * @throws IOException if an I/O error is thrown by a visitor method. + * @since 2.19.0 + */ + public static boolean contentEquals(final FileSystem fileSystem1, final FileSystem fileSystem2) throws IOException { + if (Objects.equals(fileSystem1, fileSystem2)) { + return true; + } + final List<Path> sortedList1 = toSortedList(fileSystem1.getRootDirectories()); + final List<Path> sortedList2 = toSortedList(fileSystem2.getRootDirectories()); + if (sortedList1.size() != sortedList2.size()) { + return false; + } + for (int i = 0; i < sortedList1.size(); i++) { + if (!directoryAndFileContentEquals(sortedList1.get(i), sortedList2.get(i))) { + return false; + } + } + return true; + } + /** * Copies the InputStream from the supplier with {@link Files#copy(InputStream, Path, CopyOption...)}. * @@ -362,12 +400,7 @@ public static Path copyFile(final URL sourceFile, final Path targetFile, final C public static Path copyFileToDirectory(final Path sourceFile, final Path targetDirectory, final CopyOption... copyOptions) throws IOException { // 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 (isSameFileSystem(sourceFileName, targetDirectory)) { - targetFile = targetDirectory.resolve(sourceFileName); - } else { - targetFile = targetDirectory.resolve(sourceFileName.toString()); - } + final Path targetFile = resolve(targetDirectory, sourceFileName); return Files.copy(sourceFile, targetFile, copyOptions); } @@ -667,54 +700,6 @@ public static boolean directoryAndFileContentEquals(final Path path1, final Path return directoryAndFileContentEquals(path1, path2, EMPTY_LINK_OPTION_ARRAY, EMPTY_OPEN_OPTION_ARRAY, EMPTY_FILE_VISIT_OPTION_ARRAY); } - /** - * Compares the files of two FileSystems to determine if they are equal or not while considering file contents. The comparison includes all files in all - * subdirectories. - * <p> - * For example, to compare two ZIP files: - * </p> - * - * <pre> - * final Path zipPath1 = Paths.get("file1.zip"); - * final Path zipPath2 = Paths.get("file2.zip"); - * try (FileSystem fileSystem1 = FileSystems.newFileSystem(zipPath1, null); FileSystem fileSystem2 = FileSystems.newFileSystem(zipPath2, null)) { - * assertTrue(PathUtils.directoryAndFileContentEquals(dir1, dir2)); - * } - * </pre> - * - * @param fileSystem1 The first FileSystem. - * @param fileSystem2 The second FileSystem. - * @return Whether the two FileSystem contain the same files while considering file contents. - * @throws IOException if an I/O error is thrown by a visitor method. - * @since 2.19.0 - */ - public static boolean contentEquals(final FileSystem fileSystem1, final FileSystem fileSystem2) throws IOException { - if (Objects.equals(fileSystem1, fileSystem2)) { - return true; - } - final List<Path> sortedList1 = toSortedList(fileSystem1.getRootDirectories()); - final List<Path> sortedList2 = toSortedList(fileSystem2.getRootDirectories()); - if (sortedList1.size() != sortedList2.size()) { - return false; - } - for (int i = 0; i < sortedList1.size(); i++) { - if (!directoryAndFileContentEquals(sortedList1.get(i), sortedList2.get(i))) { - return false; - } - } - return true; - } - - private static List<Path> toSortedList(final Iterable<Path> rootDirectories) { - final List<Path> list = toList(rootDirectories); - list.sort(Comparator.comparing(Function.identity())); - return list; - } - - private static <T> List<T> toList(final Iterable<T> iterable) { - return StreamSupport.stream(iterable.spliterator(), false).collect(Collectors.toList()); - } - /** * 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 * subdirectories. @@ -1572,6 +1557,18 @@ private static Path requireExists(final Path file, final String fileParamName, f return file; } + static Path resolve(final Path targetDirectory, final Path otherPath) { + final FileSystem fileSystemTarget = targetDirectory.getFileSystem(); + final FileSystem fileSystemSource = otherPath.getFileSystem(); + if (fileSystemTarget == fileSystemSource) { + return targetDirectory.resolve(otherPath); + } + final String separatorSource = fileSystemSource.getSeparator(); + final String separatorTarget = fileSystemTarget.getSeparator(); + final String otherString = otherPath.toString(); + return targetDirectory.resolve(Objects.equals(separatorSource, separatorTarget) ? otherString : otherString.replace(separatorSource, separatorTarget)); + } + private static boolean setDosReadOnly(final Path path, final boolean readOnly, final LinkOption... linkOptions) throws IOException { final DosFileAttributeView dosFileAttributeView = getDosFileAttributeView(path, linkOptions); if (dosFileAttributeView != null) { @@ -1800,6 +1797,16 @@ static Set<FileVisitOption> toFileVisitOptionSet(final FileVisitOption... fileVi return fileVisitOptions == null ? EnumSet.noneOf(FileVisitOption.class) : Stream.of(fileVisitOptions).collect(Collectors.toSet()); } + private static <T> List<T> toList(final Iterable<T> iterable) { + return StreamSupport.stream(iterable.spliterator(), false).collect(Collectors.toList()); + } + + private static List<Path> toSortedList(final Iterable<Path> rootDirectories) { + final List<Path> list = toList(rootDirectories); + Collections.sort(list); + return list; + } + /** * Implements behavior similar to the Unix "touch" utility. Creates a new file with size 0, or, if the file exists, just updates the file's modified time. * this method creates parent directories if they do not exist.