This is an automated email from the ASF dual-hosted git repository.

desruisseaux pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/maven-clean-plugin.git


The following commit(s) were added to refs/heads/master by this push:
     new 99080f3  Refactor the code of the background thread used in "fast 
delete" mode (#286)
99080f3 is described below

commit 99080f3d1768d323aa26449001a8e0f4585b6e15
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Wed Nov 26 20:01:11 2025 +0100

    Refactor the code of the background thread used in "fast delete" mode (#286)
    
    - Move the `FAST_MODE_*` string constants to an enumeration.
    - Move the code for background execution in a separated `BakgroundCleaner` 
subclass.
    - Use `java.util.concurrent.ExecutorService` instead of a thread with our 
own lifecycle management.
    - Add explanation about why a directory may be moved twice (same algorithm 
as before, just explained).
    - Reduce the number of directory levels by one (no need for a directory 
which will always contain exactly one directory).
    - Consolidation of exception handling, including the addition of error 
reporting at the end.
    - Add integration test for "fast-delete" using a non-default directory and 
complete existing test.
---
 src/it/fast-delete-default/invoker.properties      |  18 ++
 .../{fast-delete => fast-delete-default}/pom.xml   |   4 +-
 src/it/fast-delete-default/target/dummy.txt        |  16 +
 .../verify.groovy                                  |   6 +
 src/it/fast-delete/pom.xml                         |   3 +-
 src/it/fast-delete/target/dummy.txt                |  16 +
 src/it/fast-delete/verify.groovy                   |  11 +
 .../maven/plugins/clean/BackgroundCleaner.java     | 342 +++++++++++++++++++++
 .../org/apache/maven/plugins/clean/CleanMojo.java  |  83 ++---
 .../org/apache/maven/plugins/clean/Cleaner.java    | 318 +++++--------------
 .../org/apache/maven/plugins/clean/FastMode.java   |  75 +++++
 .../apache/maven/plugins/clean/CleanMojoTest.java  |   4 +-
 .../apache/maven/plugins/clean/CleanerTest.java    |  10 +-
 13 files changed, 604 insertions(+), 302 deletions(-)

diff --git a/src/it/fast-delete-default/invoker.properties 
b/src/it/fast-delete-default/invoker.properties
new file mode 100644
index 0000000..808c49e
--- /dev/null
+++ b/src/it/fast-delete-default/invoker.properties
@@ -0,0 +1,18 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+invoker.goals = install clean -Dorg.slf4j.simpleLogger.showThreadName=true -X 
-e
diff --git a/src/it/fast-delete/pom.xml b/src/it/fast-delete-default/pom.xml
similarity index 88%
copy from src/it/fast-delete/pom.xml
copy to src/it/fast-delete-default/pom.xml
index f501863..2cbc996 100644
--- a/src/it/fast-delete/pom.xml
+++ b/src/it/fast-delete-default/pom.xml
@@ -25,7 +25,8 @@ under the License.
   <version>1.0-SNAPSHOT</version>
 
   <name>Fast delete</name>
-  <description>Check that fast delete is invoked.</description>
+  <description>Check fast delete with default configuration. The default 
directory is `target/.clean`,
+    which implies additional work as it is a directory inside the directory to 
delete.</description>
 
   <properties>
     <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
@@ -39,7 +40,6 @@ under the License.
         <version>@project.version@</version>
         <configuration>
           <fast>true</fast>
-          <fastDir>${project.basedir}${file.separator}.fastdir</fastDir>
         </configuration>
       </plugin>
     </plugins>
diff --git a/src/it/fast-delete-default/target/dummy.txt 
b/src/it/fast-delete-default/target/dummy.txt
new file mode 100644
index 0000000..978b68a
--- /dev/null
+++ b/src/it/fast-delete-default/target/dummy.txt
@@ -0,0 +1,16 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+# 
+#   http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
diff --git a/src/it/fast-delete/verify.groovy 
b/src/it/fast-delete-default/verify.groovy
similarity index 86%
copy from src/it/fast-delete/verify.groovy
copy to src/it/fast-delete-default/verify.groovy
index 59f5940..173271e 100644
--- a/src/it/fast-delete/verify.groovy
+++ b/src/it/fast-delete-default/verify.groovy
@@ -17,5 +17,11 @@
  * under the License.
  */
 
+if ( new File( basedir, "target" ).exists() )
+{
+    System.out.println( "FAILURE:  'target' has not been deleted." );
+    return false;
+}
+
 File buildLog = new File(basedir, 'build.log')
 return buildLog.text.contains('mvn-background-cleaner')
diff --git a/src/it/fast-delete/pom.xml b/src/it/fast-delete/pom.xml
index f501863..658cf28 100644
--- a/src/it/fast-delete/pom.xml
+++ b/src/it/fast-delete/pom.xml
@@ -25,7 +25,8 @@ under the License.
   <version>1.0-SNAPSHOT</version>
 
   <name>Fast delete</name>
-  <description>Check that fast delete is invoked.</description>
+  <description>Check that fast delete is invoked. For making this test simpler,
+    this test uses a `.fastdir` directory outside the `target` 
directory.</description>
 
   <properties>
     <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
diff --git a/src/it/fast-delete/target/dummy.txt 
b/src/it/fast-delete/target/dummy.txt
new file mode 100644
index 0000000..978b68a
--- /dev/null
+++ b/src/it/fast-delete/target/dummy.txt
@@ -0,0 +1,16 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+# 
+#   http://www.apache.org/licenses/LICENSE-2.0
+# 
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
diff --git a/src/it/fast-delete/verify.groovy b/src/it/fast-delete/verify.groovy
index 59f5940..f25ed80 100644
--- a/src/it/fast-delete/verify.groovy
+++ b/src/it/fast-delete/verify.groovy
@@ -17,5 +17,16 @@
  * under the License.
  */
 
+if ( new File( basedir, "target" ).exists() )
+{
+    System.out.println( "FAILURE:  'target' has not been deleted." );
+    return false;
+}
+if ( new File( basedir, ".fastdir" ).exists() )
+{
+    System.out.println( "FAILURE:  '.fastdir' has not been deleted." );
+    return false;
+}
+
 File buildLog = new File(basedir, 'build.log')
 return buildLog.text.contains('mvn-background-cleaner')
diff --git 
a/src/main/java/org/apache/maven/plugins/clean/BackgroundCleaner.java 
b/src/main/java/org/apache/maven/plugins/clean/BackgroundCleaner.java
new file mode 100644
index 0000000..f180a43
--- /dev/null
+++ b/src/main/java/org/apache/maven/plugins/clean/BackgroundCleaner.java
@@ -0,0 +1,342 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.plugins.clean;
+
+import java.io.IOException;
+import java.nio.file.DirectoryNotEmptyException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.maven.api.Event;
+import org.apache.maven.api.EventType;
+import org.apache.maven.api.Listener;
+import org.apache.maven.api.Session;
+import org.apache.maven.api.annotations.Nonnull;
+import org.apache.maven.api.plugin.Log;
+import org.apache.maven.api.services.PathMatcherFactory;
+
+/**
+ * A cleaner potentially executed by background threads.
+ *
+ * <h4>Limitations</h4>
+ * This class can be used for deleting {@link Path} only, not {@link Fileset}, 
because this class cannot handle
+ * the case where only a subset of the files should be deleted. It cannot 
handle following symbolic links neither.
+ *
+ * @author Benjamin Bentmann
+ * @author Martin Desruisseaux
+ */
+final class BackgroundCleaner extends Cleaner implements Listener, Runnable {
+    /**
+     * The maven session.
+     */
+    @Nonnull
+    private final Session session;
+
+    /**
+     * The directory where to temporarily move the files to delete.
+     */
+    @Nonnull
+    private final Path fastDir;
+
+    /**
+     * Mode to use when using fast clean. Values are:
+     * {@code background} to start deletion immediately and waiting for all 
files to be deleted when the session ends,
+     * {@code at-end} to indicate that the actual deletion should be performed 
synchronously when the session ends, or
+     * {@code defer} to specify that the actual file deletion should be 
started in the background when the session ends.
+     */
+    @Nonnull
+    private final FastMode fastMode;
+
+    /**
+     * The executor to use for deleting files in background threads.
+     */
+    @Nonnull
+    private final ExecutorService executor;
+
+    /**
+     * Files to delete at the end of the session instead of in background 
thread.
+     * This is unused ({@code null}) for {@link FastMode#BACKGROUND}.
+     */
+    private final List<Path> filesToDeleteAtEnd;
+
+    /**
+     * Directories to delete last, after the executor has been shutdown, and 
only if they are empty.
+     * This is the directory that contains the temporary directories where the 
files to delete have been moved.
+     * We can obviously not delete that directory before all deletion tasks in 
the background thread finished.
+     * The directory is deleted only if empty because other plugins (e.g. 
compiler plugin) may have wrote new
+     * files after the clean.
+     */
+    @Nonnull
+    private final Set<Path> directoriesToDeleteIfEmpty;
+
+    /**
+     * Errors that occurred during the deletion.
+     */
+    private IOException errors;
+
+    /**
+     * Whether at least one deletion task has been queued.
+     */
+    private boolean started;
+
+    /**
+     * Whether to disable the deletion of files in background threads.
+     * This is used for avoiding to repeat the same warning many times
+     * when the {@link #fastDir} directory does not exist.
+     */
+    private boolean disabled;
+
+    /**
+     * Creates a new cleaner to be executed in a background thread.
+     *
+     * @param session         the Maven session to be used
+     * @param matcherFactory  the service to use for creating include and 
exclude filters.
+     * @param logger          the logger to use
+     * @param verbose         whether to perform verbose logging
+     * @param fastDir         the explicit configured directory or to be 
deleted in fast mode
+     * @param fastMode        the fast deletion mode
+     * @param followSymlinks  whether to follow symlinks
+     * @param force           whether to force the deletion of read-only files
+     * @param failOnError     whether to abort with an exception in case a 
selected file/directory could not be deleted
+     * @param retryOnError    whether to undertake additional delete attempts 
in case the first attempt failed
+     */
+    @SuppressWarnings("checkstyle:ParameterNumber")
+    BackgroundCleaner(
+            @Nonnull Session session,
+            @Nonnull PathMatcherFactory matcherFactory,
+            @Nonnull Log logger,
+            boolean verbose,
+            @Nonnull Path fastDir,
+            @Nonnull FastMode fastMode,
+            boolean followSymlinks,
+            boolean force,
+            boolean failOnError,
+            boolean retryOnError) {
+        super(matcherFactory, logger, verbose, followSymlinks, force, 
failOnError, retryOnError);
+        this.session = session;
+        this.fastDir = fastDir;
+        this.fastMode = fastMode;
+        filesToDeleteAtEnd = (fastMode != FastMode.BACKGROUND) ? new 
ArrayList<>() : null;
+        directoriesToDeleteIfEmpty = new LinkedHashSet<>(); // Will need to 
delete in order.
+        executor = Executors.newSingleThreadExecutor((task) -> new 
Thread(task, "mvn-background-cleaner"));
+    }
+
+    /**
+     * Returns an error message to show to user if the fast delete failed.
+     */
+    @Override
+    String fastDeleteError(IOException e) {
+        disabled = true;
+        var message = new StringBuilder("Unable to fast delete directory");
+        if (!Files.isDirectory(fastDir)) {
+            message.append(" as the path ")
+                    .append(fastDir)
+                    .append(" does not point to a directory or cannot be 
created");
+        }
+        return message.append(". Fallback to immediate mode.").toString();
+    }
+
+    /**
+     * Deletes the specified directory and its contents in a background thread.
+     *
+     * @param basedir the directory to delete, must not be {@code null}
+     * @return whether this method was able to register the background task
+     * @throws IOException if an error occurred while preparing the task 
before execution in a background thread
+     */
+    @Override
+    boolean fastDelete(Path baseDir) throws IOException {
+        if (disabled) {
+            return false;
+        }
+        final Path parent = baseDir.getParent();
+        if (parent == null) {
+            return false;
+        }
+        if (!started) {
+            started = true;
+            session.registerListener(this);
+        }
+        /*
+         * The default directory is 
`${maven.multiModuleProjectDirectory}/target/.clean`.
+         * This is fine when cleaning a multi-project, in which case this 
directory will
+         * be shared by all sub-projects and should not interfere with any 
sub-project.
+         * However, when cleaning a single project, that default directory may 
be inside
+         * the `target` directory to delete. In such case, we need a 3 steps 
process:
+         *
+         *  1) The `target` directory is renamed to temporary name inside the 
same parent directory.
+         *  2) A new `target` directory is created with a `.clean` sub-folder 
(after this `if` block).
+         *  3) The directory at 1 is moved to 2 as if it was the target 
directory of a sub-project.
+         *
+         * Note that we have to use `toAbsolutePath()` instead of 
`toRealPath()`
+         * because `fastDir` may not exist yet.
+         */
+        directoriesToDeleteIfEmpty.add(fastDir); // Should be before `baseDir`.
+        if (fastDir.toAbsolutePath().startsWith(baseDir.toAbsolutePath())) {
+            String prefix = baseDir.getFileName().toString() + '-';
+            Path tmpDir = Files.createTempDirectory(parent, prefix);
+
+            // After `baseDir` has been moved, it will be implicitly recreated 
by `createDirectories(fastDir)` below.
+            // Register for another deletion, but after `fastDir` for giving a 
chance to `baseDir` to become empty.
+            directoriesToDeleteIfEmpty.add(baseDir);
+            try {
+                baseDir = Files.move(baseDir, tmpDir, 
StandardCopyOption.REPLACE_EXISTING);
+            } catch (IOException e) {
+                try {
+                    Files.delete(tmpDir);
+                } catch (IOException s) {
+                    e.addSuppressed(s);
+                }
+                throw e;
+            }
+        }
+        /*
+         * Create a temporary directory inside `fastDir` and all parent 
directories if needed.
+         * The prefix is the name of parent directory, which is usually the 
sub-project name.
+         * It allows to recognize the target directory when all of them are 
moved to the same
+         * `${maven.multiModuleProjectDirectory}/target/.clean` directory.
+         */
+        String prefix = parent.getFileName().toString() + '-';
+        Path tmpDir = 
Files.createTempDirectory(Files.createDirectories(fastDir), prefix);
+        /*
+         * Note: a previous version used `ATOMIC_MOVE` standard option instead 
of `REPLACE_EXISTING` in order
+         * to increse the chances to have an exception if the path leads to a 
directory on another mountpoint.
+         * However, `ATOMIC_MOVE` causes the `REPLACE_EXISTING` option to be 
ignored and it is implementation
+         * specific if the existing `tmpDir` is replaced or if the method 
fails by throwing an `IOException`.
+         * For avoiding this risk, it should be okay to not use the atomic 
move given that the `Files.move(…)`
+         * Javadoc specifies:
+         *
+         *   > When invoked to move a directory that is not empty then the 
directory is moved if it does not
+         *   > require moving the entries in the directory. For example, 
renaming a directory on the same
+         *   > `FileStore` will usually not require moving the entries in the 
directory. When moving a directory
+         *   > requires that its entries be moved then this method fails (by 
throwing an `IOException`).
+         *
+         * If an exception occurs, the usual deletion will be performed.
+         */
+        try {
+            final Path dir = Files.move(baseDir, tmpDir, 
StandardCopyOption.REPLACE_EXISTING);
+            if (filesToDeleteAtEnd != null) {
+                filesToDeleteAtEnd.add(dir);
+            } else {
+                executor.submit(() -> deleteSilently(dir));
+            }
+        } catch (IOException | RuntimeException e) {
+            try {
+                Files.delete(tmpDir);
+            } catch (IOException s) {
+                e.addSuppressed(s);
+            }
+            throw e;
+        }
+        return true;
+    }
+
+    /**
+     * Deletes the given directory without logging messages and without 
throwing {@link IOException}.
+     * The exceptions are stored for reporting after the end of the session.
+     *
+     * <h4>Thread safety</h4>
+     * Contrarily to most other methods in {@code BackgroundCleaner}, this 
method is
+     * thread-safe because it uses a copy of this cleaner for walking in the 
file tree.
+     */
+    private void deleteSilently(final Path dir) {
+        try {
+            Files.walkFileTree(dir, Set.of(), Integer.MAX_VALUE, new 
Cleaner(this));
+        } catch (IOException e) {
+            errorOccurred(e);
+        }
+    }
+
+    /**
+     * Stores the given error for later reporting. This method can be invoked 
from any thread.
+     */
+    private synchronized void errorOccurred(IOException e) {
+        if (errors == null) {
+            errors = e;
+        } else {
+            errors.addSuppressed(e);
+        }
+    }
+
+    /**
+     * Invoked at the end of the session for waiting the completion of 
background tasks.
+     * There's no clean API to do that properly as this is a very unusual use 
case for a
+     * plugin to outlive its main execution.
+     */
+    @Override
+    public void onEvent(Event event) {
+        if (event.getType() != EventType.SESSION_ENDED) {
+            return;
+        }
+        session.unregisterListener(this);
+        if (filesToDeleteAtEnd != null) {
+            filesToDeleteAtEnd.forEach((dir) -> executor.submit(() -> 
deleteSilently(dir)));
+        }
+        if (fastMode == FastMode.DEFER) {
+            executor.submit(this);
+            executor.shutdown();
+            return;
+        }
+        executor.shutdown();
+        try {
+            // Wait for a short time for logging only if it takes longer.
+            if (!executor.awaitTermination(2, TimeUnit.SECONDS)) {
+                logger.info("Waiting for background file deletion.");
+                if (!executor.awaitTermination(1, TimeUnit.HOURS)) {
+                    logger.warn("Timeout while waiting for background file 
deletion."
+                            + " Some directories may not have been deleted.");
+                }
+            }
+        } catch (InterruptedException e) {
+            // Someone decided that we waited long enough.
+            logger.warn(e);
+        }
+        run();
+    }
+
+    /**
+     * Invoked after most other executor tasks are finished. It should be the 
very last task,
+     * but it may not be really last if a timeout occurred while waiting for 
the completion of
+     * other tasks, or if the wait has been interrupted, or if using a 
multi-threaded executor
+     * with {@link FastMode#DEFER}.
+     */
+    @Override
+    public synchronized void run() {
+        for (Path dir : directoriesToDeleteIfEmpty) {
+            try {
+                Files.deleteIfExists(dir);
+            } catch (DirectoryNotEmptyException e) {
+                // Ignore as per method contract. Maybe another plugin started 
to write its output.
+            } catch (IOException e) {
+                errorOccurred(e);
+            }
+        }
+        if (errors != null) {
+            logger.warn("Errors during background file deletion.", errors);
+            errors = null;
+        }
+    }
+}
diff --git a/src/main/java/org/apache/maven/plugins/clean/CleanMojo.java 
b/src/main/java/org/apache/maven/plugins/clean/CleanMojo.java
index 2bea405..9d2daf7 100644
--- a/src/main/java/org/apache/maven/plugins/clean/CleanMojo.java
+++ b/src/main/java/org/apache/maven/plugins/clean/CleanMojo.java
@@ -47,18 +47,12 @@ import org.apache.maven.api.services.ProjectManager;
  * </p>
  *
  * @author <a href="mailto:[email protected]";>Emmanuel Venisse</a>
+ * @author Martin Desruisseaux
  * @see Fileset
  * @since 2.0
  */
 @Mojo(name = "clean")
 public class CleanMojo implements org.apache.maven.api.plugin.Mojo {
-
-    public static final String FAST_MODE_BACKGROUND = "background";
-
-    public static final String FAST_MODE_AT_END = "at-end";
-
-    public static final String FAST_MODE_DEFER = "defer";
-
     /**
      * The logger where to send information about what the plugin is doing.
      */
@@ -189,19 +183,23 @@ public class CleanMojo implements 
org.apache.maven.api.plugin.Mojo {
      * until all the files have been deleted.  If any problem occurs during 
the atomic move of the directories,
      * the plugin will default to the traditional deletion mechanism.
      *
+     * <p>Note that for small projects with few files to delete, the "fast" 
clean tends to be actually slower.
+     * It is also more at risk that errors occurring during the deletion of a 
file get unnoticed, or are noticed
+     * late in the build process. This option should be used only when it has 
been verified to be worth.</p>
+     *
      * @since 3.2
      */
     @Parameter(property = "maven.clean.fast", defaultValue = "false")
     private boolean fast;
 
     /**
-     * When fast clean is specified, the {@code fastDir} property will be used 
as the location where directories
-     * to be deleted will be moved prior to background deletion.  If not 
specified, the
-     * {@code ${maven.multiModuleProjectDirectory}/target/.clean} directory 
will be used.
+     * When fast clean is enabled,
+     * the location where directories to be deleted will be moved prior to 
background deletion.
+     * If not specified, the {@code 
${maven.multiModuleProjectDirectory}/target/.clean} directory will be used.
      * If the {@code ${build.directory}} has been modified, you'll have to 
adjust this property explicitly.
      * In order for fast clean to work correctly, this directory and the 
various directories that will be deleted
      * should usually reside on the same volume.  The exact conditions are 
system-dependent though, but if an atomic
-     * move is not supported, the standard deletion mechanism will be used.
+     * move is not supported, the immediate deletion mechanism will be used.
      *
      * @since 3.2
      * @see #fast
@@ -210,16 +208,16 @@ public class CleanMojo implements 
org.apache.maven.api.plugin.Mojo {
     private Path fastDir;
 
     /**
-     * Mode to use when using fast clean.  Values are: {@code background} to 
start deletion immediately and
-     * waiting for all files to be deleted when the session ends, {@code 
at-end} to indicate that the actual
-     * deletion should be performed synchronously when the session ends, or 
{@code defer} to specify that
-     * the actual file deletion should be started in the background when the 
session ends.
+     * Mode to use when using fast clean. Values are:
+     * {@code background} to start deletion immediately and waiting for all 
files to be deleted when the session ends,
+     * {@code at-end} to indicate that the actual deletion should be performed 
synchronously when the session ends, or
+     * {@code defer} to specify that the actual file deletion should be 
started in the background when the session ends.
      * This should only be used when maven is embedded in a long-running 
process.
      *
      * @since 3.2
      * @see #fast
      */
-    @Parameter(property = "maven.clean.fastMode", defaultValue = 
FAST_MODE_BACKGROUND)
+    @Parameter(property = "maven.clean.fastMode", defaultValue = "background")
     private String fastMode;
 
     /**
@@ -263,42 +261,27 @@ public class CleanMojo implements 
org.apache.maven.api.plugin.Mojo {
             logger.info("Clean is skipped.");
             return;
         }
-
-        String multiModuleProjectDirectory =
-                session != null ? 
session.getSystemProperties().get("maven.multiModuleProjectDirectory") : null;
-
-        @SuppressWarnings("LocalVariableHidesMemberVariable")
-        final Path fastDir;
-        if (fast && this.fastDir != null) {
-            fastDir = this.fastDir;
-        } else if (fast && multiModuleProjectDirectory != null) {
-            fastDir = Path.of(multiModuleProjectDirectory, "target", ".clean");
-        } else {
-            fastDir = null;
-            if (fast) {
-                logger.warn("Fast clean requires maven 3.3.1 or newer, "
-                        + "or an explicit directory to be specified with the 
'fastDir' configuration of "
-                        + "this plugin, or the 'maven.clean.fastDir' user 
property to be set.");
+        Cleaner cleaner;
+        if (fast && session != null) {
+            Path tmpDir = fastDir;
+            if (tmpDir == null) {
+                tmpDir = 
session.getRootDirectory().resolve("target").resolve(".clean");
             }
+            cleaner = new BackgroundCleaner(
+                    session,
+                    matcherFactory,
+                    logger,
+                    isVerbose(),
+                    tmpDir,
+                    FastMode.caseInsensitiveValueOf(fastMode),
+                    followSymLinks,
+                    force,
+                    failOnError,
+                    retryOnError);
+        } else {
+            cleaner =
+                    new Cleaner(matcherFactory, logger, isVerbose(), 
followSymLinks, force, failOnError, retryOnError);
         }
-        if (fast
-                && !FAST_MODE_BACKGROUND.equals(fastMode)
-                && !FAST_MODE_AT_END.equals(fastMode)
-                && !FAST_MODE_DEFER.equals(fastMode)) {
-            throw new IllegalArgumentException("Illegal value '" + fastMode + 
"' for fastMode. Allowed values are '"
-                    + FAST_MODE_BACKGROUND + "', '" + FAST_MODE_AT_END + "' 
and '" + FAST_MODE_DEFER + "'.");
-        }
-        final var cleaner = new Cleaner(
-                session,
-                matcherFactory,
-                logger,
-                isVerbose(),
-                fastDir,
-                fastMode,
-                followSymLinks,
-                force,
-                failOnError,
-                retryOnError);
         try {
             for (Path directoryItem : getDirectories()) {
                 cleaner.delete(directoryItem);
diff --git a/src/main/java/org/apache/maven/plugins/clean/Cleaner.java 
b/src/main/java/org/apache/maven/plugins/clean/Cleaner.java
index 2cfce01..bcdff20 100644
--- a/src/main/java/org/apache/maven/plugins/clean/Cleaner.java
+++ b/src/main/java/org/apache/maven/plugins/clean/Cleaner.java
@@ -28,57 +28,44 @@ import java.nio.file.Files;
 import java.nio.file.NotDirectoryException;
 import java.nio.file.Path;
 import java.nio.file.PathMatcher;
-import java.nio.file.StandardCopyOption;
 import java.nio.file.attribute.BasicFileAttributes;
 import java.nio.file.attribute.DosFileAttributeView;
 import java.nio.file.attribute.PosixFileAttributeView;
 import java.nio.file.attribute.PosixFilePermission;
-import java.util.ArrayDeque;
 import java.util.BitSet;
-import java.util.Deque;
 import java.util.EnumSet;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
-import java.util.stream.Stream;
 
-import org.apache.maven.api.Event;
-import org.apache.maven.api.EventType;
-import org.apache.maven.api.Listener;
-import org.apache.maven.api.Session;
-import org.apache.maven.api.SessionData;
 import org.apache.maven.api.annotations.Nonnull;
-import org.apache.maven.api.annotations.Nullable;
 import org.apache.maven.api.plugin.Log;
 import org.apache.maven.api.services.PathMatcherFactory;
 
 /**
- * Cleans directories.
+ * Cleans directories. This base class deletes the files in the current thread.
+ * The {@link BackgroundCleaner} subclass adds the capability to run in 
background.
+ *
+ * <h4>Limitations</h4>
+ * This class is not thread-safe: each instance shall be be executed in only 
one thread at a time.
+ * All directories specified as {@link Path} shall be deleted before 
directories specified as {@link Fileset},
+ * because the {@link Path} deletions rely on default values that are modified 
by {@link Fileset} deletions.
  *
  * @author Benjamin Bentmann
  * @author Martin Desruisseaux
  */
-final class Cleaner implements FileVisitor<Path> {
+class Cleaner implements FileVisitor<Path> {
     /**
      * Whether the host operating system is from the Windows family.
      */
     private static final boolean ON_WINDOWS = (File.separatorChar == '\\');
 
-    private static final SessionData.Key<Path> LAST_DIRECTORY_TO_DELETE =
-            SessionData.key(Path.class, Cleaner.class.getName() + 
".lastDirectoryToDelete");
-
-    /**
-     * The maven session. This is typically non-null in a real run, but it can 
be during unit tests.
-     */
-    @Nonnull
-    private final Session session;
-
     /**
      * The logger where to send information or warning messages.
      */
     @Nonnull
-    private final Log logger;
+    protected final Log logger;
 
     /**
      * Whether to send to the logger some information that would normally be 
at the "debug" level.
@@ -102,12 +89,6 @@ final class Cleaner implements FileVisitor<Path> {
      */
     private boolean reallyDeletedLastFile;
 
-    @Nonnull
-    private final Path fastDir;
-
-    @Nonnull
-    private final String fastMode;
-
     /**
      * The service to use for creating include and exclude filters.
      * Used for setting a value to {@link #fileMatcher} and {@link 
#directoryMatcher}.
@@ -181,7 +162,7 @@ final class Cleaner implements FileVisitor<Path> {
      * A bit is used for each directory level, with {@link #currentDepth}
      * telling which bit is for the current directory.
      */
-    private final BitSet nonEmptyDirectoryLevels;
+    private final BitSet nonEmptyDirectoryLevels = new BitSet();
 
     /**
      * 0 for the base directory, and incremented for each subdirectory.
@@ -194,54 +175,72 @@ final class Cleaner implements FileVisitor<Path> {
      * does not exclude the base directory and does not follow symbolic links.
      * These properties can be modified by {@link #delete(Fileset)}.
      *
-     * @param session         the Maven session to be used
      * @param matcherFactory  the service to use for creating include and 
exclude filters.
      * @param logger          the logger to use
      * @param verbose         whether to perform verbose logging
-     * @param fastDir         the explicit configured directory or to be 
deleted in fast mode
-     * @param fastMode        the fast deletion mode
      * @param followSymlinks  whether to follow symlinks
      * @param force           whether to force the deletion of read-only files
      * @param failOnError     whether to abort with an exception in case a 
selected file/directory could not be deleted
      * @param retryOnError    whether to undertake additional delete attempts 
in case the first attempt failed
      */
-    @SuppressWarnings("checkstyle:ParameterNumber")
     Cleaner(
-            @Nullable Session session,
             @Nonnull PathMatcherFactory matcherFactory,
             @Nonnull Log logger,
             boolean verbose,
-            @Nullable Path fastDir,
-            @Nonnull String fastMode,
             boolean followSymlinks,
             boolean force,
             boolean failOnError,
             boolean retryOnError) {
-        this.session = session;
         this.matcherFactory = matcherFactory;
         this.logger = logger;
         this.verbose = verbose;
-        this.fastDir = fastDir;
-        this.fastMode = fastMode;
         this.followSymlinks = followSymlinks;
         this.force = force;
         this.failOnError = failOnError;
         this.retryOnError = retryOnError;
         listDeletedFiles = verbose ? logger.isInfoEnabled() : 
logger.isDebugEnabled();
-        nonEmptyDirectoryLevels = new BitSet();
         fileMatcher = matcherFactory.includesAll();
         directoryMatcher = fileMatcher;
     }
 
     /**
-     * Deletes the specified fileset.
+     * Creates a new cleaner with the configuration of the given cleaner as it 
was a construction time.
+     * An exception is the {@link #followSymlinks} flag which is set to {@code 
false}. This constructor
+     * is invoked by {@link BackgroundCleaner} before to invoke {@link 
#delete(Path)} at a moment which
+     * is potentially after some {@link #delete(Fileset)} executions. Because 
{@link BackgroundCleaner}
+     * can be used only when {@link #followSymlinks} is {@code false}, we know 
that this flag can be
+     * cleared unconditionally.
+     *
+     * @param other the cleaner from which to copy the configuration
+     */
+    Cleaner(Cleaner other) {
+        // Copy only final fields.
+        matcherFactory = other.matcherFactory;
+        logger = other.logger;
+        verbose = other.verbose;
+        force = other.force;
+        failOnError = other.failOnError;
+        retryOnError = other.retryOnError;
+        listDeletedFiles = other.listDeletedFiles;
+
+        // Non-final fields.
+        fileMatcher = matcherFactory.includesAll();
+        directoryMatcher = fileMatcher;
+    }
+
+    /**
+     * Deletes the specified fileset in the current thread.
      * This method modifies the include and exclude filters,
      * whether to exclude the base directory and whether to follow symbolic 
links.
      *
+     * <h4>Configuration</h4>
+     * {@link Fileset} deletions should be done after all {@link Path} 
deletions,
+     * because this method modifies this {@code Cleaner} configuration.
+     *
      * @param fileset the fileset to delete
      * @throws IOException if a file/directory could not be deleted and {@link 
#failOnError} is {@code true}
      */
-    public void delete(@Nonnull Fileset fileset) throws IOException {
+    public final void delete(@Nonnull Fileset fileset) throws IOException {
         fileMatcher = matcherFactory.createPathMatcher(
                 fileset.getDirectory(), fileset.getIncludes(), 
fileset.getExcludes(), fileset.useDefaultExcludes());
         directoryMatcher = matcherFactory.deriveDirectoryMatcher(fileMatcher);
@@ -252,7 +251,7 @@ final class Cleaner implements FileVisitor<Path> {
 
     /**
      * Deletes the specified directory and its contents using the current 
configuration.
-     * Non-existing directories will be silently ignored.
+     * Non-existing directories will be ignored with a warning logged at the 
debug level.
      *
      * <h4>Configuration</h4>
      * The behavior of this method depends on the {@code Cleaner} 
configuration.
@@ -264,28 +263,35 @@ final class Cleaner implements FileVisitor<Path> {
      * @param basedir the directory to delete, must not be {@code null}
      * @throws IOException if a file/directory could not be deleted and {@code 
failOnError} is {@code true}
      */
-    public void delete(@Nonnull Path basedir) throws IOException {
+    public final void delete(@Nonnull Path basedir) throws IOException {
         if (!Files.isDirectory(basedir)) {
             if (Files.notExists(basedir)) {
-                if (logger.isDebugEnabled()) {
-                    logger.debug("Skipping non-existing directory " + basedir);
-                }
+                logger.debug("Skipping non-existing directory \"" + basedir + 
"\".");
                 return;
             }
-            throw new NotDirectoryException("Invalid base directory " + 
basedir);
+            throw new NotDirectoryException("Invalid base directory \"" + 
basedir + "\".");
         }
         if (logger.isInfoEnabled()) {
-            logger.info("Deleting " + basedir + (isClearAll() ? "" : " (" + 
fileMatcher + ')'));
+            StringBuilder message =
+                    new StringBuilder("Deleting 
\"").append(basedir).append('"');
+            if (!isClearAll()) {
+                message.append(" (").append(fileMatcher).append(')');
+            }
+            logger.info(message.append('.').toString());
         }
         var options = EnumSet.noneOf(FileVisitOption.class);
         if (followSymlinks) {
             options.add(FileVisitOption.FOLLOW_LINKS);
             basedir = getCanonicalPath(basedir, null);
         }
-        if (isClearAll() && !followSymlinks && fastDir != null && session != 
null) {
+        if (isClearAll() && !followSymlinks) {
             // If anything wrong happens, we'll just use the usual deletion 
mechanism
-            if (fastDelete(basedir)) {
-                return;
+            try {
+                if (fastDelete(basedir)) {
+                    return;
+                }
+            } catch (IOException e) {
+                logger.debug(fastDeleteError(e), e);
             }
         }
         Files.walkFileTree(basedir, options, Integer.MAX_VALUE, this);
@@ -296,56 +302,26 @@ final class Cleaner implements FileVisitor<Path> {
      * This is a required condition for allowing the use of {@link 
#fastDelete(Path)}.
      */
     private boolean isClearAll() {
-        return fileMatcher == matcherFactory.includesAll();
+        return matcherFactory.isIncludesAll(fileMatcher);
     }
 
-    private boolean fastDelete(Path baseDir) {
-        // Handle the case where we use 
${maven.multiModuleProjectDirectory}/target/.clean for example
-        if (fastDir.toAbsolutePath().startsWith(baseDir.toAbsolutePath())) {
-            try {
-                String prefix = baseDir.getFileName().toString() + '.';
-                Path tmpDir = Files.createTempDirectory(baseDir.getParent(), 
prefix);
-                try {
-                    Files.move(baseDir, tmpDir, 
StandardCopyOption.REPLACE_EXISTING);
-                    if (session != null) {
-                        session.getData().set(LAST_DIRECTORY_TO_DELETE, 
baseDir);
-                    }
-                    baseDir = tmpDir;
-                } catch (IOException e) {
-                    Files.delete(tmpDir);
-                    throw e;
-                }
-            } catch (IOException e) {
-                logger.debug("Unable to fast delete directory", e);
-                return false;
-            }
-        }
-        // Create fastDir and the needed parents if needed
-        try {
-            if (!Files.isDirectory(fastDir)) {
-                Files.createDirectories(fastDir);
-            }
-        } catch (IOException e) {
-            logger.debug(
-                    "Unable to fast delete directory as the path " + fastDir
-                            + " does not point to a directory or cannot be 
created",
-                    e);
-            return false;
-        }
-        try {
-            Path tmpDir = Files.createTempDirectory(fastDir, "");
-            Path dstDir = tmpDir.resolve(baseDir.getFileName());
-            // Note that by specifying the ATOMIC_MOVE, we expect an exception 
to be thrown
-            // if the path leads to a directory on another mountpoint.  If 
this is the case
-            // or any other exception occurs, an exception will be thrown in 
which case
-            // the method will return false and the usual deletion will be 
performed.
-            Files.move(baseDir, dstDir, StandardCopyOption.ATOMIC_MOVE);
-            BackgroundCleaner.delete(this, tmpDir, fastMode);
-            return true;
-        } catch (IOException e) {
-            logger.debug("Unable to fast delete directory", e);
-            return false;
-        }
+    /**
+     * Deletes the specified directory and its contents in a background thread.
+     * The default implementation returns {@code false}.
+     *
+     * @param basedir the directory to delete, must not be {@code null}
+     * @return whether this method was able to register the background task
+     * @throws IOException if an error occurred while preparing the task 
before execution in a background thread
+     */
+    boolean fastDelete(Path baseDir) throws IOException {
+        return false;
+    }
+
+    /**
+     * Returns an error message to show to user if the fast delete failed.
+     */
+    String fastDeleteError(IOException e) {
+        return e.toString();
     }
 
     /**
@@ -611,146 +587,4 @@ final class Cleaner implements FileVisitor<Path> {
             logger.debug(message);
         }
     }
-
-    private static class BackgroundCleaner extends Thread {
-
-        private static BackgroundCleaner instance;
-
-        private final Deque<Path> filesToDelete = new ArrayDeque<>();
-
-        private final Cleaner cleaner;
-
-        private final String fastMode;
-
-        private static final int NEW = 0;
-        private static final int RUNNING = 1;
-        private static final int STOPPED = 2;
-
-        private int status = NEW;
-
-        public static void delete(Cleaner cleaner, Path dir, String fastMode) {
-            synchronized (BackgroundCleaner.class) {
-                if (instance == null || !instance.doDelete(dir)) {
-                    instance = new BackgroundCleaner(cleaner, dir, fastMode);
-                }
-            }
-        }
-
-        static void sessionEnd() {
-            synchronized (BackgroundCleaner.class) {
-                if (instance != null) {
-                    instance.doSessionEnd();
-                }
-            }
-        }
-
-        private BackgroundCleaner(Cleaner cleaner, Path dir, String fastMode) {
-            super("mvn-background-cleaner");
-            this.cleaner = cleaner;
-            this.fastMode = fastMode;
-            init(cleaner.fastDir, dir);
-        }
-
-        @Override
-        public void run() {
-            var options = EnumSet.noneOf(FileVisitOption.class);
-            if (cleaner.followSymlinks) {
-                options.add(FileVisitOption.FOLLOW_LINKS);
-            }
-            Path basedir;
-            while ((basedir = pollNext()) != null) {
-                try {
-                    Files.walkFileTree(basedir, options, Integer.MAX_VALUE, 
cleaner);
-                } catch (IOException e) {
-                    // do not display errors
-                }
-            }
-        }
-
-        synchronized void init(Path fastDir, Path dir) {
-            if (Files.isDirectory(fastDir)) {
-                try {
-                    try (Stream<Path> children = Files.list(fastDir)) {
-                        children.forEach(this::doDelete);
-                    }
-                } catch (IOException e) {
-                    throw new RuntimeException(e);
-                }
-            }
-            doDelete(dir);
-        }
-
-        synchronized Path pollNext() {
-            Path basedir = filesToDelete.poll();
-            if (basedir == null) {
-                if (cleaner.session != null) {
-                    SessionData data = cleaner.session.getData();
-                    Path lastDir = data.get(LAST_DIRECTORY_TO_DELETE);
-                    if (lastDir != null) {
-                        data.set(LAST_DIRECTORY_TO_DELETE, null);
-                        return lastDir;
-                    }
-                }
-                status = STOPPED;
-                notifyAll();
-            }
-            return basedir;
-        }
-
-        synchronized boolean doDelete(Path dir) {
-            if (status == STOPPED) {
-                return false;
-            }
-            filesToDelete.add(dir);
-            if (status == NEW && 
CleanMojo.FAST_MODE_BACKGROUND.equals(fastMode)) {
-                status = RUNNING;
-                notifyAll();
-                start();
-            }
-            wrapExecutionListener();
-            return true;
-        }
-
-        /**
-         * If this has not been done already, we wrap the ExecutionListener 
inside a proxy
-         * which simply delegates call to the previous listener.  When the 
session ends, it will
-         * also call {@link BackgroundCleaner#sessionEnd()}.
-         * There's no clean API to do that properly as this is a very unusual 
use case for a plugin
-         * to outlive its main execution.
-         */
-        private void wrapExecutionListener() {
-            synchronized (CleanerListener.class) {
-                if (cleaner.session.getListeners().stream().noneMatch(l -> l 
instanceof CleanerListener)) {
-                    cleaner.session.registerListener(new CleanerListener());
-                }
-            }
-        }
-
-        synchronized void doSessionEnd() {
-            if (status != STOPPED) {
-                if (status == NEW) {
-                    start();
-                }
-                if (!CleanMojo.FAST_MODE_DEFER.equals(fastMode)) {
-                    try {
-                        cleaner.logger.info("Waiting for background file 
deletion");
-                        while (status != STOPPED) {
-                            wait();
-                        }
-                    } catch (InterruptedException e) {
-                        // ignore
-                    }
-                }
-            }
-        }
-    }
-
-    static class CleanerListener implements Listener {
-        @Override
-        public void onEvent(Event event) {
-            if (event.getType() == EventType.SESSION_ENDED) {
-                BackgroundCleaner.sessionEnd();
-            }
-        }
-    }
 }
diff --git a/src/main/java/org/apache/maven/plugins/clean/FastMode.java 
b/src/main/java/org/apache/maven/plugins/clean/FastMode.java
new file mode 100644
index 0000000..86445e1
--- /dev/null
+++ b/src/main/java/org/apache/maven/plugins/clean/FastMode.java
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.plugins.clean;
+
+import java.util.Locale;
+
+/**
+ * Specifies when to delete files when fast clean is used.
+ * This enumeration defines the value values for {@code maven.clean.fastMode},
+ * in upper case and with {@code '-'} replaced by {@code '_'}.
+ */
+enum FastMode {
+    /**
+     * Start deletion immediately and wait for all files to be deleted when 
the session ends.
+     * This is the default mode when the deletion of files in a background 
thread is enabled.
+     */
+    BACKGROUND,
+
+    /**
+     * Actual deletion should be performed synchronously when the session ends.
+     * The plugin waits for all files to be deleted when the session end.
+     */
+    AT_END,
+
+    /**
+     * Actual file deletion should be started in the background when the 
session ends.
+     * The plugin does not wait for files to be deleted.
+     */
+    DEFER;
+
+    /**
+     * {@return the enumeration value for the given configuration option}
+     *
+     * @param  option  the configuration option, case insensitive
+     * @throws IllegalArgumentException if the given option is invalid
+     */
+    public static FastMode caseInsensitiveValueOf(String option) {
+        try {
+            return valueOf(option.trim().toUpperCase(Locale.US).replace('-', 
'_'));
+        } catch (NullPointerException | IllegalArgumentException e) {
+            StringBuilder sb =
+                    new StringBuilder("Illegal value 
'").append(option).append("' for fastMode. Allowed values are '");
+            FastMode[] values = values();
+            int last = values.length;
+            for (int i = 0; i <= last; i++) {
+                sb.append(values[i]).append(i < last ? "', '" : "' and '");
+            }
+            throw new IllegalArgumentException(sb.append("'.").toString(), e);
+        }
+    }
+
+    /**
+     * {@return the lower-case variant of this enumeration value}
+     */
+    @Override
+    public String toString() {
+        return name().replace('_', '-').toLowerCase(Locale.US);
+    }
+}
diff --git a/src/test/java/org/apache/maven/plugins/clean/CleanMojoTest.java 
b/src/test/java/org/apache/maven/plugins/clean/CleanMojoTest.java
index c7935de..a6a138e 100644
--- a/src/test/java/org/apache/maven/plugins/clean/CleanMojoTest.java
+++ b/src/test/java/org/apache/maven/plugins/clean/CleanMojoTest.java
@@ -255,7 +255,7 @@ class CleanMojoTest {
     }
 
     private void testSymlink(LinkCreator linkCreator) throws Exception {
-        Cleaner cleaner = new Cleaner(null, matcherFactory, log, false, null, 
null, false, false, true, false);
+        Cleaner cleaner = new Cleaner(matcherFactory, log, false, false, 
false, true, false);
         Path testDir = 
Paths.get("target/test-classes/unit/test-dir").toAbsolutePath();
         Path dirWithLnk = testDir.resolve("dir");
         Path orgDir = testDir.resolve("org-dir");
@@ -281,7 +281,7 @@ class CleanMojoTest {
         Files.write(file, Collections.singleton("Hello world"));
         linkCreator.createLink(jctDir, orgDir);
         // delete
-        cleaner = new Cleaner(null, matcherFactory, log, false, null, null, 
true, false, true, false);
+        cleaner = new Cleaner(matcherFactory, log, false, true, false, true, 
false);
         cleaner.delete(dirWithLnk);
         // verify
         assertFalse(Files.exists(file));
diff --git a/src/test/java/org/apache/maven/plugins/clean/CleanerTest.java 
b/src/test/java/org/apache/maven/plugins/clean/CleanerTest.java
index 8c84cee..22ac91f 100644
--- a/src/test/java/org/apache/maven/plugins/clean/CleanerTest.java
+++ b/src/test/java/org/apache/maven/plugins/clean/CleanerTest.java
@@ -70,7 +70,7 @@ class CleanerTest {
     void deleteSucceedsDeeply(@TempDir Path tempDir) throws Exception {
         final Path basedir = 
createDirectory(tempDir.resolve("target")).toRealPath();
         final Path file = createFile(basedir.resolve("file"));
-        final var cleaner = new Cleaner(null, matcherFactory, log, false, 
null, null, false, false, true, false);
+        final var cleaner = new Cleaner(matcherFactory, log, false, false, 
false, true, false);
         cleaner.delete(basedir);
         assertFalse(exists(basedir));
         assertFalse(exists(file));
@@ -85,7 +85,7 @@ class CleanerTest {
         // Remove the executable flag to prevent directory listing, which will 
result in a DirectoryNotEmptyException.
         final Set<PosixFilePermission> permissions = 
PosixFilePermissions.fromString("rw-rw-r--");
         setPosixFilePermissions(basedir, permissions);
-        final var cleaner = new Cleaner(null, matcherFactory, log, false, 
null, null, false, false, true, false);
+        final var cleaner = new Cleaner(matcherFactory, log, false, false, 
false, true, false);
         final var exception = assertThrows(AccessDeniedException.class, () -> 
cleaner.delete(basedir));
         verify(log, times(1)).warn(any(CharSequence.class), 
any(Throwable.class));
         assertTrue(exception.getMessage().contains(basedir.toString()));
@@ -99,7 +99,7 @@ class CleanerTest {
         // Remove the executable flag to prevent directory listing, which will 
result in a DirectoryNotEmptyException.
         final Set<PosixFilePermission> permissions = 
PosixFilePermissions.fromString("rw-rw-r--");
         setPosixFilePermissions(basedir, permissions);
-        final var cleaner = new Cleaner(null, matcherFactory, log, false, 
null, null, false, false, true, true);
+        final var cleaner = new Cleaner(matcherFactory, log, false, false, 
false, true, true);
         final var exception = assertThrows(AccessDeniedException.class, () -> 
cleaner.delete(basedir));
         assertTrue(exception.getMessage().contains(basedir.toString()));
     }
@@ -113,7 +113,7 @@ class CleanerTest {
         // Remove the writable flag to prevent deletion of the file, which 
will result in an AccessDeniedException.
         final Set<PosixFilePermission> permissions = 
PosixFilePermissions.fromString("r-xr-xr-x");
         setPosixFilePermissions(basedir, permissions);
-        final var cleaner = new Cleaner(null, matcherFactory, log, false, 
null, null, false, false, false, false);
+        final var cleaner = new Cleaner(matcherFactory, log, false, false, 
false, false, false);
         assertDoesNotThrow(() -> cleaner.delete(basedir));
         verify(log, times(1)).warn(any(CharSequence.class), 
any(Throwable.class));
         InOrder inOrder = inOrder(log);
@@ -131,7 +131,7 @@ class CleanerTest {
         // Remove the writable flag to prevent deletion of the file, which 
will result in an AccessDeniedException.
         final Set<PosixFilePermission> permissions = 
PosixFilePermissions.fromString("r-xr-xr-x");
         setPosixFilePermissions(basedir, permissions);
-        final var cleaner = new Cleaner(null, matcherFactory, log, false, 
null, null, false, false, false, false);
+        final var cleaner = new Cleaner(matcherFactory, log, false, false, 
false, false, false);
         assertDoesNotThrow(() -> cleaner.delete(basedir));
         verify(log, never()).warn(any(CharSequence.class), 
any(Throwable.class));
     }

Reply via email to