49
49
* @author Martin Desruisseaux
50
50
*/
51
51
final class Cleaner implements FileVisitor <Path > {
52
-
52
+ /**
53
+ * Whether the host operating system is from the Windows family.
54
+ */
53
55
private static final boolean ON_WINDOWS = (File .separatorChar == '\\' );
54
56
55
57
private static final SessionData .Key <Path > LAST_DIRECTORY_TO_DELETE =
@@ -171,13 +173,13 @@ public void delete(@Nonnull Fileset fileset) throws IOException {
171
173
}
172
174
173
175
/**
174
- * Deletes the specified directories and its contents.
176
+ * Deletes the specified directory and its contents.
175
177
* Non-existing directories will be silently ignored.
176
178
*
177
179
* @param basedir the directory to delete, must not be {@code null}
178
180
* @throws IOException if a file/directory could not be deleted and {@code failOnError} is {@code true}
179
181
*/
180
- public void delete (Path basedir ) throws IOException {
182
+ public void delete (@ Nonnull Path basedir ) throws IOException {
181
183
if (!Files .isDirectory (basedir )) {
182
184
if (Files .notExists (basedir )) {
183
185
if (logger .isDebugEnabled ()) {
@@ -193,7 +195,7 @@ public void delete(Path basedir) throws IOException {
193
195
var options = EnumSet .noneOf (FileVisitOption .class );
194
196
if (followSymlinks ) {
195
197
options .add (FileVisitOption .FOLLOW_LINKS );
196
- basedir = getCanonicalPath (basedir );
198
+ basedir = getCanonicalPath (basedir , null );
197
199
}
198
200
if (selector == null && !followSymlinks && fastDir != null && session != null ) {
199
201
// If anything wrong happens, we'll just use the usual deletion mechanism
@@ -259,6 +261,14 @@ private boolean fastDelete(Path baseDir) {
259
261
*/
260
262
@ Override
261
263
public FileVisitResult preVisitDirectory (Path dir , BasicFileAttributes attrs ) throws IOException {
264
+ if (ON_WINDOWS && !followSymlinks && attrs .isOther ()) {
265
+ /*
266
+ * MCLEAN-93: NTFS junctions have isDirectory() and isOther() attributes set.
267
+ * If not following symbolic links, then it should be handled as a file.
268
+ */
269
+ visitFile (dir , attrs );
270
+ return FileVisitResult .SKIP_SUBTREE ;
271
+ }
262
272
if (selector == null || selector .couldHoldSelected (dir )) {
263
273
nonEmptyDirectoryLevels .clear (++currentDepth );
264
274
return FileVisitResult .CONTINUE ;
@@ -333,11 +343,30 @@ public FileVisitResult postVisitDirectory(Path dir, IOException failure) throws
333
343
return FileVisitResult .CONTINUE ;
334
344
}
335
345
336
- private static Path getCanonicalPath (Path path ) {
346
+ /**
347
+ * Returns the real path of the given file. If the real path cannot be obtained,
348
+ * this method tries to get the real path of the parent and to append the rest of
349
+ * the filename.
350
+ *
351
+ * @param path the path to get as a canonical path
352
+ * @param mainError should be {@code null} (reserved to recursive calls of this method)
353
+ * @return the real path of the given path
354
+ * @throws IOException if the canonical path cannot be obtained
355
+ */
356
+ private static Path getCanonicalPath (final Path path , IOException mainError ) throws IOException {
337
357
try {
338
358
return path .toRealPath ();
339
359
} catch (IOException e ) {
340
- return getCanonicalPath (path .getParent ()).resolve (path .getFileName ());
360
+ if (mainError == null ) {
361
+ mainError = e ;
362
+ } else {
363
+ mainError .addSuppressed (e );
364
+ }
365
+ final Path parent = path .getParent ();
366
+ if (parent != null ) {
367
+ return getCanonicalPath (parent , mainError ).resolve (path .getFileName ());
368
+ }
369
+ throw e ;
341
370
}
342
371
}
343
372
0 commit comments