Skip to content

Commit 95cb90d

Browse files
committed
PathUtils.copyFileToDirectory() creates incorrect file names when
copying between different file systems that use different file system separators ("/" versus "\")
1 parent 94da6de commit 95cb90d

File tree

2 files changed

+62
-68
lines changed

2 files changed

+62
-68
lines changed

src/main/java/org/apache/commons/io/file/CopyDirectoryVisitor.java

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919

2020
import java.io.IOException;
2121
import java.nio.file.CopyOption;
22-
import java.nio.file.FileSystem;
2322
import java.nio.file.FileVisitResult;
2423
import java.nio.file.Files;
2524
import java.nio.file.Path;
@@ -155,18 +154,6 @@ public FileVisitResult preVisitDirectory(final Path directory, final BasicFileAt
155154
return super.preVisitDirectory(directory, attributes);
156155
}
157156

158-
private Path resolve(final Path otherPath) {
159-
final FileSystem fileSystemTarget = targetDirectory.getFileSystem();
160-
final FileSystem fileSystemSource = sourceDirectory.getFileSystem();
161-
if (fileSystemTarget == fileSystemSource) {
162-
return targetDirectory.resolve(otherPath);
163-
}
164-
final String separatorSource = fileSystemSource.getSeparator();
165-
final String separatorTarget = fileSystemTarget.getSeparator();
166-
final String otherString = otherPath.toString();
167-
return targetDirectory.resolve(Objects.equals(separatorSource, separatorTarget) ? otherString : otherString.replace(separatorSource, separatorTarget));
168-
}
169-
170157
/**
171158
* Relativizes against {@code sourceDirectory}, then resolves against {@code targetDirectory}.
172159
* <p>
@@ -178,7 +165,7 @@ private Path resolve(final Path otherPath) {
178165
* @return a new path, relativized against sourceDirectory, then resolved against targetDirectory.
179166
*/
180167
private Path resolveRelativeAsString(final Path directory) {
181-
return resolve(sourceDirectory.relativize(directory));
168+
return PathUtils.resolve(targetDirectory, sourceDirectory.relativize(directory));
182169
}
183170

184171
@Override

src/main/java/org/apache/commons/io/file/PathUtils.java

Lines changed: 61 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,44 @@ private static int compareLastModifiedTimeTo(final Path file, final FileTime fil
303303
return getLastModifiedTime(file, options).compareTo(fileTime);
304304
}
305305

306+
/**
307+
* 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
308+
* subdirectories.
309+
* <p>
310+
* For example, to compare two ZIP files:
311+
* </p>
312+
*
313+
* <pre>
314+
* final Path zipPath1 = Paths.get("file1.zip");
315+
* final Path zipPath2 = Paths.get("file2.zip");
316+
* try (FileSystem fileSystem1 = FileSystems.newFileSystem(zipPath1, null); FileSystem fileSystem2 = FileSystems.newFileSystem(zipPath2, null)) {
317+
* assertTrue(PathUtils.directoryAndFileContentEquals(dir1, dir2));
318+
* }
319+
* </pre>
320+
*
321+
* @param fileSystem1 The first FileSystem.
322+
* @param fileSystem2 The second FileSystem.
323+
* @return Whether the two FileSystem contain the same files while considering file contents.
324+
* @throws IOException if an I/O error is thrown by a visitor method.
325+
* @since 2.19.0
326+
*/
327+
public static boolean contentEquals(final FileSystem fileSystem1, final FileSystem fileSystem2) throws IOException {
328+
if (Objects.equals(fileSystem1, fileSystem2)) {
329+
return true;
330+
}
331+
final List<Path> sortedList1 = toSortedList(fileSystem1.getRootDirectories());
332+
final List<Path> sortedList2 = toSortedList(fileSystem2.getRootDirectories());
333+
if (sortedList1.size() != sortedList2.size()) {
334+
return false;
335+
}
336+
for (int i = 0; i < sortedList1.size(); i++) {
337+
if (!directoryAndFileContentEquals(sortedList1.get(i), sortedList2.get(i))) {
338+
return false;
339+
}
340+
}
341+
return true;
342+
}
343+
306344
/**
307345
* Copies the InputStream from the supplier with {@link Files#copy(InputStream, Path, CopyOption...)}.
308346
*
@@ -362,12 +400,7 @@ public static Path copyFile(final URL sourceFile, final Path targetFile, final C
362400
public static Path copyFileToDirectory(final Path sourceFile, final Path targetDirectory, final CopyOption... copyOptions) throws IOException {
363401
// Path.resolve() naturally won't work across FileSystem unless we convert to a String
364402
final Path sourceFileName = Objects.requireNonNull(sourceFile.getFileName(), "source file name");
365-
final Path targetFile;
366-
if (isSameFileSystem(sourceFileName, targetDirectory)) {
367-
targetFile = targetDirectory.resolve(sourceFileName);
368-
} else {
369-
targetFile = targetDirectory.resolve(sourceFileName.toString());
370-
}
403+
final Path targetFile = resolve(targetDirectory, sourceFileName);
371404
return Files.copy(sourceFile, targetFile, copyOptions);
372405
}
373406

@@ -667,54 +700,6 @@ public static boolean directoryAndFileContentEquals(final Path path1, final Path
667700
return directoryAndFileContentEquals(path1, path2, EMPTY_LINK_OPTION_ARRAY, EMPTY_OPEN_OPTION_ARRAY, EMPTY_FILE_VISIT_OPTION_ARRAY);
668701
}
669702

670-
/**
671-
* 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
672-
* subdirectories.
673-
* <p>
674-
* For example, to compare two ZIP files:
675-
* </p>
676-
*
677-
* <pre>
678-
* final Path zipPath1 = Paths.get("file1.zip");
679-
* final Path zipPath2 = Paths.get("file2.zip");
680-
* try (FileSystem fileSystem1 = FileSystems.newFileSystem(zipPath1, null); FileSystem fileSystem2 = FileSystems.newFileSystem(zipPath2, null)) {
681-
* assertTrue(PathUtils.directoryAndFileContentEquals(dir1, dir2));
682-
* }
683-
* </pre>
684-
*
685-
* @param fileSystem1 The first FileSystem.
686-
* @param fileSystem2 The second FileSystem.
687-
* @return Whether the two FileSystem contain the same files while considering file contents.
688-
* @throws IOException if an I/O error is thrown by a visitor method.
689-
* @since 2.19.0
690-
*/
691-
public static boolean contentEquals(final FileSystem fileSystem1, final FileSystem fileSystem2) throws IOException {
692-
if (Objects.equals(fileSystem1, fileSystem2)) {
693-
return true;
694-
}
695-
final List<Path> sortedList1 = toSortedList(fileSystem1.getRootDirectories());
696-
final List<Path> sortedList2 = toSortedList(fileSystem2.getRootDirectories());
697-
if (sortedList1.size() != sortedList2.size()) {
698-
return false;
699-
}
700-
for (int i = 0; i < sortedList1.size(); i++) {
701-
if (!directoryAndFileContentEquals(sortedList1.get(i), sortedList2.get(i))) {
702-
return false;
703-
}
704-
}
705-
return true;
706-
}
707-
708-
private static List<Path> toSortedList(final Iterable<Path> rootDirectories) {
709-
final List<Path> list = toList(rootDirectories);
710-
list.sort(Comparator.comparing(Function.identity()));
711-
return list;
712-
}
713-
714-
private static <T> List<T> toList(final Iterable<T> iterable) {
715-
return StreamSupport.stream(iterable.spliterator(), false).collect(Collectors.toList());
716-
}
717-
718703
/**
719704
* 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
720705
* subdirectories.
@@ -1572,6 +1557,18 @@ private static Path requireExists(final Path file, final String fileParamName, f
15721557
return file;
15731558
}
15741559

1560+
static Path resolve(final Path targetDirectory, final Path otherPath) {
1561+
final FileSystem fileSystemTarget = targetDirectory.getFileSystem();
1562+
final FileSystem fileSystemSource = otherPath.getFileSystem();
1563+
if (fileSystemTarget == fileSystemSource) {
1564+
return targetDirectory.resolve(otherPath);
1565+
}
1566+
final String separatorSource = fileSystemSource.getSeparator();
1567+
final String separatorTarget = fileSystemTarget.getSeparator();
1568+
final String otherString = otherPath.toString();
1569+
return targetDirectory.resolve(Objects.equals(separatorSource, separatorTarget) ? otherString : otherString.replace(separatorSource, separatorTarget));
1570+
}
1571+
15751572
private static boolean setDosReadOnly(final Path path, final boolean readOnly, final LinkOption... linkOptions) throws IOException {
15761573
final DosFileAttributeView dosFileAttributeView = getDosFileAttributeView(path, linkOptions);
15771574
if (dosFileAttributeView != null) {
@@ -1800,6 +1797,16 @@ static Set<FileVisitOption> toFileVisitOptionSet(final FileVisitOption... fileVi
18001797
return fileVisitOptions == null ? EnumSet.noneOf(FileVisitOption.class) : Stream.of(fileVisitOptions).collect(Collectors.toSet());
18011798
}
18021799

1800+
private static <T> List<T> toList(final Iterable<T> iterable) {
1801+
return StreamSupport.stream(iterable.spliterator(), false).collect(Collectors.toList());
1802+
}
1803+
1804+
private static List<Path> toSortedList(final Iterable<Path> rootDirectories) {
1805+
final List<Path> list = toList(rootDirectories);
1806+
Collections.sort(list);
1807+
return list;
1808+
}
1809+
18031810
/**
18041811
* 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.
18051812
* this method creates parent directories if they do not exist.

0 commit comments

Comments
 (0)