4949 * @author Martin Desruisseaux
5050 */
5151final class Cleaner implements FileVisitor <Path > {
52-
52+ /**
53+ * Whether the host operating system is from the Windows family.
54+ */
5355 private static final boolean ON_WINDOWS = (File .separatorChar == '\\' );
5456
5557 private static final SessionData .Key <Path > LAST_DIRECTORY_TO_DELETE =
@@ -171,13 +173,13 @@ public void delete(@Nonnull Fileset fileset) throws IOException {
171173 }
172174
173175 /**
174- * Deletes the specified directories and its contents.
176+ * Deletes the specified directory and its contents.
175177 * Non-existing directories will be silently ignored.
176178 *
177179 * @param basedir the directory to delete, must not be {@code null}
178180 * @throws IOException if a file/directory could not be deleted and {@code failOnError} is {@code true}
179181 */
180- public void delete (Path basedir ) throws IOException {
182+ public void delete (@ Nonnull Path basedir ) throws IOException {
181183 if (!Files .isDirectory (basedir )) {
182184 if (Files .notExists (basedir )) {
183185 if (logger .isDebugEnabled ()) {
@@ -193,7 +195,7 @@ public void delete(Path basedir) throws IOException {
193195 var options = EnumSet .noneOf (FileVisitOption .class );
194196 if (followSymlinks ) {
195197 options .add (FileVisitOption .FOLLOW_LINKS );
196- basedir = getCanonicalPath (basedir );
198+ basedir = getCanonicalPath (basedir , null );
197199 }
198200 if (selector == null && !followSymlinks && fastDir != null && session != null ) {
199201 // If anything wrong happens, we'll just use the usual deletion mechanism
@@ -259,6 +261,14 @@ private boolean fastDelete(Path baseDir) {
259261 */
260262 @ Override
261263 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+ }
262272 if (selector == null || selector .couldHoldSelected (dir )) {
263273 nonEmptyDirectoryLevels .clear (++currentDepth );
264274 return FileVisitResult .CONTINUE ;
@@ -333,11 +343,30 @@ public FileVisitResult postVisitDirectory(Path dir, IOException failure) throws
333343 return FileVisitResult .CONTINUE ;
334344 }
335345
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 {
337357 try {
338358 return path .toRealPath ();
339359 } 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 ;
341370 }
342371 }
343372
0 commit comments