Skip to content

Commit 32e881a

Browse files
committed
[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
1 parent 72ae92d commit 32e881a

1 file changed

Lines changed: 37 additions & 44 deletions

File tree

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

Lines changed: 37 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -103,44 +103,45 @@ private static final class RelativeSortedPaths {
103103
* @param list2 the second list.
104104
* @return whether the lists are equal.
105105
*/
106-
private static boolean equals(final List<Path> list1, final List<Path> list2) {
106+
private static boolean equalsIgnoreFileSystem(final List<Path> list1, final List<Path> list2) {
107107
if (list1.size() != list2.size()) {
108108
return false;
109109
}
110110
// compare both lists using iterators
111111
final Iterator<Path> iterator1 = list1.iterator();
112112
final Iterator<Path> iterator2 = list2.iterator();
113113
while (iterator1.hasNext() && iterator2.hasNext()) {
114-
final Path path1 = iterator1.next();
115-
final Path path2 = iterator2.next();
116-
final FileSystem fileSystem1 = path1.getFileSystem();
117-
final FileSystem fileSystem2 = path2.getFileSystem();
118-
if (fileSystem1 == fileSystem2) {
119-
if (!path1.equals(path2)) {
120-
return false;
121-
}
122-
} else if (fileSystem1.getSeparator().equals(fileSystem2.getSeparator())) {
123-
// Separators are the same, so we can use toString comparison
124-
if (!path1.toString().equals(path2.toString())) {
125-
return false;
126-
}
127-
} else {
128-
// Compare paths from different file systems component by component.
129-
// Cant use toString() string comparison which may fail due to different path separators.
130-
final Iterator<Path> path1Iterator = path1.iterator();
131-
final Iterator<Path> path2Iterator = path2.iterator();
132-
while (path1Iterator.hasNext() && path2Iterator.hasNext()) {
133-
if (!path1Iterator.next().toString().equals(path2Iterator.next().toString())) {
134-
return false;
135-
}
136-
}
137-
// Check that both iterators are exhausted (paths have same number of components)
138-
return !path1Iterator.hasNext() && !path2Iterator.hasNext();
114+
if (!equalsIgnoreFileSystem(iterator1.next(), iterator2.next())) {
115+
return false;
139116
}
140117
}
141118
return true;
142119
}
143120

121+
private static boolean equalsIgnoreFileSystem(final Path path1, final Path path2) {
122+
final FileSystem fileSystem1 = path1.getFileSystem();
123+
final FileSystem fileSystem2 = path2.getFileSystem();
124+
if (fileSystem1 == fileSystem2) {
125+
return path1.equals(path2);
126+
}
127+
final String separator1 = fileSystem1.getSeparator();
128+
final String separator2 = fileSystem2.getSeparator();
129+
final String string1 = path1.toString();
130+
final String string2 = path2.toString();
131+
if (separator1.equals(separator2)) {
132+
// Separators are the same, so we can use toString comparison
133+
return string1.equals(string2);
134+
}
135+
// Compare paths from different file systems component by component.
136+
return extractKey(separator1, string1).equals(extractKey(separator2, string2));
137+
//return Arrays.equals(string1.split("\\" + separator1), string2.split("\\" + separator2));
138+
}
139+
140+
static String extractKey(final String separator, final String string) {
141+
// Replace the file separator in a path string with a string that is not legal in a path on Windows, Linux, and macOS.
142+
return string.replaceAll("\\" + separator, ">");
143+
}
144+
144145
final boolean equals;
145146
// final List<Path> relativeDirList1; // might need later?
146147
// final List<Path> relativeDirList2; // might need later?
@@ -180,12 +181,12 @@ private RelativeSortedPaths(final Path dir1, final Path dir2, final int maxDepth
180181
} else {
181182
tmpRelativeDirList1 = visitor1.relativizeDirectories(dir1, true, null);
182183
tmpRelativeDirList2 = visitor2.relativizeDirectories(dir2, true, null);
183-
if (!equals(tmpRelativeDirList1, tmpRelativeDirList2)) {
184+
if (!equalsIgnoreFileSystem(tmpRelativeDirList1, tmpRelativeDirList2)) {
184185
equals = false;
185186
} else {
186187
tmpRelativeFileList1 = visitor1.relativizeFiles(dir1, true, null);
187188
tmpRelativeFileList2 = visitor2.relativizeFiles(dir2, true, null);
188-
equals = equals(tmpRelativeFileList1, tmpRelativeFileList2);
189+
equals = equalsIgnoreFileSystem(tmpRelativeFileList1, tmpRelativeFileList2);
189190
}
190191
}
191192
}
@@ -198,40 +199,33 @@ private RelativeSortedPaths(final Path dir1, final Path dir2, final int maxDepth
198199
}
199200

200201
private static final OpenOption[] OPEN_OPTIONS_TRUNCATE = { StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING };
201-
202202
private static final OpenOption[] OPEN_OPTIONS_APPEND = { StandardOpenOption.CREATE, StandardOpenOption.APPEND };
203-
204203
/**
205204
* Empty {@link CopyOption} array.
206205
*
207206
* @since 2.8.0
208207
*/
209208
public static final CopyOption[] EMPTY_COPY_OPTIONS = {};
210-
211209
/**
212210
* Empty {@link DeleteOption} array.
213211
*
214212
* @since 2.8.0
215213
*/
216214
public static final DeleteOption[] EMPTY_DELETE_OPTION_ARRAY = {};
217-
218215
/**
219216
* Empty {@link FileAttribute} array.
220217
*
221218
* @since 2.13.0
222219
*/
223220
public static final FileAttribute<?>[] EMPTY_FILE_ATTRIBUTE_ARRAY = {};
224-
225221
/**
226222
* Empty {@link FileVisitOption} array.
227223
*/
228224
public static final FileVisitOption[] EMPTY_FILE_VISIT_OPTION_ARRAY = {};
229-
230225
/**
231226
* Empty {@link LinkOption} array.
232227
*/
233228
public static final LinkOption[] EMPTY_LINK_OPTION_ARRAY = {};
234-
235229
/**
236230
* {@link LinkOption} array for {@link LinkOption#NOFOLLOW_LINKS}.
237231
*
@@ -240,19 +234,16 @@ private RelativeSortedPaths(final Path dir1, final Path dir2, final int maxDepth
240234
*/
241235
@Deprecated
242236
public static final LinkOption[] NOFOLLOW_LINK_OPTION_ARRAY = { LinkOption.NOFOLLOW_LINKS };
243-
244237
/**
245238
* A LinkOption used to follow link in this class, the inverse of {@link LinkOption#NOFOLLOW_LINKS}.
246239
*
247240
* @since 2.12.0
248241
*/
249242
static final LinkOption NULL_LINK_OPTION = null;
250-
251243
/**
252244
* Empty {@link OpenOption} array.
253245
*/
254246
public static final OpenOption[] EMPTY_OPEN_OPTION_ARRAY = {};
255-
256247
/**
257248
* Empty {@link Path} array.
258249
*
@@ -712,8 +703,9 @@ public static boolean directoryAndFileContentEquals(final Path path1, final Path
712703
final boolean sameFileSystem = isSameFileSystem(path1, path2);
713704
for (final Path path : fileList1) {
714705
final int binarySearch = sameFileSystem ? Collections.binarySearch(fileList2, path)
715-
: Collections.binarySearch(fileList2, path, Comparator.comparing(Path::toString));
716-
if (binarySearch <= -1) {
706+
: Collections.binarySearch(fileList2, path,
707+
Comparator.comparing(p -> RelativeSortedPaths.extractKey(p.getFileSystem().getSeparator(), p.toString())));
708+
if (binarySearch < 0) {
717709
throw new IllegalStateException("Unexpected mismatch.");
718710
}
719711
if (sameFileSystem && !fileContentEquals(path1.resolve(path), path2.resolve(path), linkOptions, openOptions)) {
@@ -949,6 +941,7 @@ public static DosFileAttributeView getDosFileAttributeView(final Path path, fina
949941
* <p>
950942
* This method returns the textual part of the Path after the last period.
951943
* </p>
944+
*
952945
* <pre>
953946
* foo.txt --&gt; "txt"
954947
* a/b/c.jpg --&gt; "jpg"
@@ -971,8 +964,8 @@ public static String getExtension(final Path path) {
971964
/**
972965
* Gets the Path's file name and apply the given function if the file name is non-null.
973966
*
974-
* @param <R> The function's result type.
975-
* @param path the path to query.
967+
* @param <R> The function's result type.
968+
* @param path the path to query.
976969
* @param function function to apply to the file name.
977970
* @return the Path's file name as a string or null.
978971
* @see Path#getFileName()
@@ -1744,7 +1737,8 @@ public static BigInteger sizeOfDirectoryAsBigInteger(final Path directory) throw
17441737
return countDirectoryAsBigInteger(directory).getByteCounter().getBigInteger();
17451738
}
17461739

1747-
private static Path stripTrailingSeparator(final Path dir) {
1740+
private static Path stripTrailingSeparator(final Path cdir) {
1741+
final Path dir = cdir.normalize();
17481742
final String separator = dir.getFileSystem().getSeparator();
17491743
final String fileName = dir.getFileName().toString();
17501744
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,
19521946
private PathUtils() {
19531947
// do not instantiate.
19541948
}
1955-
19561949
}

0 commit comments

Comments
 (0)