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));
}