New Upstream Release - maven-clean-plugin

Ready changes

Summary

Merged new upstream version: 3.3.1 (was: 3.2.0).

Diff

diff --git a/.asf.yaml b/.asf.yaml
index 9709dda..853ac71 100644
--- a/.asf.yaml
+++ b/.asf.yaml
@@ -24,3 +24,14 @@ github:
     - maven-plugins
     - maven-clean-plugin
     - maven
+  enabled_merge_buttons:
+    squash: true
+    merge: false
+    rebase: true
+  autolink_jira:
+    - MCLEAN
+notifications:
+  commits: commits@maven.apache.org
+  issues: issues@maven.apache.org
+  pullrequests: issues@maven.apache.org
+  jira_options: link label comment
diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs
new file mode 100644
index 0000000..b5ce43a
--- /dev/null
+++ b/.git-blame-ignore-revs
@@ -0,0 +1,21 @@
+#
+# 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.
+#
+
+# Change maven code style
+7b053970f4b9443651fa5dffe42c8b025c596dff
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index f0138ad..d5588b4 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -20,6 +20,9 @@ updates:
     directory: "/"
     schedule:
       interval: "daily"
+    ignore:
+      # Ignore Maven Core updates
+      - dependency-name: "org.apache.maven:*"
   - package-ecosystem: "github-actions"
     directory: "/"
     schedule:
diff --git a/.github/workflows/maven-verify.yml b/.github/workflows/maven-verify.yml
index bbd7786..4d67fdc 100644
--- a/.github/workflows/maven-verify.yml
+++ b/.github/workflows/maven-verify.yml
@@ -24,4 +24,4 @@ on:
 jobs:
   build:
     name: Verify
-    uses: apache/maven-gh-actions-shared/.github/workflows/maven-verify.yml@v2
+    uses: apache/maven-gh-actions-shared/.github/workflows/maven-verify.yml@v3
diff --git a/README.md b/README.md
index ec63879..5e3a867 100644
--- a/README.md
+++ b/README.md
@@ -20,6 +20,7 @@ Contributing to [Apache Maven Clean Plugin](https://maven.apache.org/plugins/mav
 [![ASF Jira](https://img.shields.io/endpoint?url=https%3A%2F%2Fmaven.apache.org%2Fbadges%2Fasf_jira-MCLEAN.json)][jira]
 [![Apache License, Version 2.0, January 2004](https://img.shields.io/github/license/apache/maven.svg?label=License)][license]
 [![Maven Central](https://img.shields.io/maven-central/v/org.apache.maven.plugins/maven-clean-plugin.svg?label=Maven%20Central)](https://search.maven.org/artifact/org.apache.maven.plugins/maven-clean-plugin)
+[![Reproducible Builds](https://img.shields.io/badge/Reproducible_Builds-ok-green?labelColor=blue)](https://github.com/jvm-repo-rebuild/reproducible-central/blob/master/content/org/apache/maven/plugins/maven-clean-plugin/README.md)
 [![Jenkins Status](https://img.shields.io/jenkins/s/https/ci-maven.apache.org/job/Maven/job/maven-box/job/maven-clean-plugin/job/master.svg?)][build]
 [![Jenkins tests](https://img.shields.io/jenkins/t/https/ci-maven.apache.org/job/Maven/job/maven-box/job/maven-clean-plugin/job/master.svg?)][test-results]
 
diff --git a/debian/changelog b/debian/changelog
index 6daf752..91b4a59 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+maven-clean-plugin (3.3.1-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Fri, 28 Jul 2023 07:10:56 -0000
+
 maven-clean-plugin (3.2.0-2) unstable; urgency=medium
 
   * Depend on libmaven-parent-java (Closes: #1028850)
diff --git a/pom.xml b/pom.xml
index a1c52cf..fdf0c5f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,5 +1,4 @@
-<?xml version='1.0' encoding='UTF-8'?>
-
+<?xml version="1.0" encoding="UTF-8"?>
 <!--
 Licensed to the Apache Software Foundation (ASF) under one
 or more contributor license agreements.  See the NOTICE file
@@ -18,25 +17,22 @@ KIND, either express or implied.  See the License for the
 specific language governing permissions and limitations
 under the License.
 -->
-
 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
   <modelVersion>4.0.0</modelVersion>
 
   <parent>
     <groupId>org.apache.maven.plugins</groupId>
     <artifactId>maven-plugins</artifactId>
-    <version>35</version>
+    <version>39</version>
     <relativePath />
   </parent>
 
   <artifactId>maven-clean-plugin</artifactId>
-  <version>3.2.0</version>
+  <version>3.3.1</version>
   <packaging>maven-plugin</packaging>
 
   <name>Apache Maven Clean Plugin</name>
-  <description>
-    The Maven Clean Plugin is a plugin that removes files generated at build-time in a project's directory.
-  </description>
+  <description>The Maven Clean Plugin is a plugin that removes files generated at build-time in a project's directory.</description>
   <inceptionYear>2001</inceptionYear>
 
   <prerequisites>
@@ -46,8 +42,8 @@ under the License.
   <scm>
     <connection>scm:git:https://gitbox.apache.org/repos/asf/maven-clean-plugin.git</connection>
     <developerConnection>scm:git:https://gitbox.apache.org/repos/asf/maven-clean-plugin.git</developerConnection>
+    <tag>maven-clean-plugin-3.3.1</tag>
     <url>https://github.com/apache/maven-clean-plugin/tree/${project.scm.tag}</url>
-    <tag>maven-clean-plugin-3.2.0</tag>
   </scm>
   <issueManagement>
     <system>JIRA</system>
@@ -55,7 +51,7 @@ under the License.
   </issueManagement>
   <ciManagement>
     <system>Jenkins</system>
-    <url>https://ci-builds.apache.org/job/Maven/job/maven-box/job/maven-clean-plugin/</url>
+    <url>https://ci-maven.apache.org/job/Maven/job/maven-box/job/maven-clean-plugin/</url>
   </ciManagement>
   <distributionManagement>
     <site>
@@ -66,10 +62,7 @@ under the License.
 
   <properties>
     <mavenVersion>3.2.5</mavenVersion>
-    <javaVersion>8</javaVersion>
-    <surefire.version>2.22.2</surefire.version>
-    <mavenPluginToolsVersion>3.6.4</mavenPluginToolsVersion>
-    <project.build.outputTimestamp>2022-04-01T21:20:29Z</project.build.outputTimestamp>
+    <project.build.outputTimestamp>2023-06-14T18:50:40Z</project.build.outputTimestamp>
   </properties>
 
   <dependencies>
@@ -80,9 +73,15 @@ under the License.
       <scope>provided</scope>
     </dependency>
     <dependency>
-      <groupId>org.apache.maven.shared</groupId>
-      <artifactId>maven-shared-utils</artifactId>
-      <version>3.3.4</version>
+      <groupId>org.codehaus.plexus</groupId>
+      <artifactId>plexus-utils</artifactId>
+      <version>4.0.0</version>
+    </dependency>
+    <dependency>
+      <groupId>org.codehaus.plexus</groupId>
+      <artifactId>plexus-xml</artifactId>
+      <version>4.0.0</version>
+      <scope>provided</scope>
     </dependency>
 
     <!-- dependencies to annotations -->
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 4b5ba86..3dff361 100644
--- a/src/main/java/org/apache/maven/plugins/clean/CleanMojo.java
+++ b/src/main/java/org/apache/maven/plugins/clean/CleanMojo.java
@@ -1,5 +1,3 @@
-package org.apache.maven.plugins.clean;
-
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements.  See the NOTICE file
@@ -18,6 +16,10 @@ package org.apache.maven.plugins.clean;
  * specific language governing permissions and limitations
  * under the License.
  */
+package org.apache.maven.plugins.clean;
+
+import java.io.File;
+import java.io.IOException;
 
 import org.apache.maven.execution.MavenSession;
 import org.apache.maven.plugin.AbstractMojo;
@@ -25,9 +27,6 @@ import org.apache.maven.plugin.MojoExecutionException;
 import org.apache.maven.plugins.annotations.Mojo;
 import org.apache.maven.plugins.annotations.Parameter;
 
-import java.io.File;
-import java.io.IOException;
-
 /**
  * Goal which cleans the build.
  * <p>
@@ -44,10 +43,8 @@ import java.io.IOException;
  * @see org.apache.maven.plugins.clean.Fileset
  * @since 2.0
  */
-@Mojo( name = "clean", threadSafe = true )
-public class CleanMojo
-    extends AbstractMojo
-{
+@Mojo(name = "clean", threadSafe = true)
+public class CleanMojo extends AbstractMojo {
 
     public static final String FAST_MODE_BACKGROUND = "background";
 
@@ -58,19 +55,19 @@ public class CleanMojo
     /**
      * This is where build results go.
      */
-    @Parameter( defaultValue = "${project.build.directory}", readonly = true, required = true )
+    @Parameter(defaultValue = "${project.build.directory}", readonly = true, required = true)
     private File directory;
 
     /**
      * This is where compiled classes go.
      */
-    @Parameter( defaultValue = "${project.build.outputDirectory}", readonly = true, required = true )
+    @Parameter(defaultValue = "${project.build.outputDirectory}", readonly = true, required = true)
     private File outputDirectory;
 
     /**
      * This is where compiled test classes go.
      */
-    @Parameter( defaultValue = "${project.build.testOutputDirectory}", readonly = true, required = true )
+    @Parameter(defaultValue = "${project.build.testOutputDirectory}", readonly = true, required = true)
     private File testOutputDirectory;
 
     /**
@@ -78,7 +75,7 @@ public class CleanMojo
      *
      * @since 2.1.1
      */
-    @Parameter( defaultValue = "${project.build.outputDirectory}", readonly = true, required = true )
+    @Parameter(defaultValue = "${project.build.outputDirectory}", readonly = true, required = true)
     private File reportDirectory;
 
     /**
@@ -86,15 +83,15 @@ public class CleanMojo
      * global debug flag (compare command line switch <code>-X</code>). <br/>
      * Starting with <b>3.0.0</b> the property has been renamed from <code>clean.verbose</code> to
      * <code>maven.clean.verbose</code>.
-     * 
+     *
      * @since 2.1
      */
-    @Parameter( property = "maven.clean.verbose" )
+    @Parameter(property = "maven.clean.verbose")
     private Boolean verbose;
 
     /**
      * The list of file sets to delete, in addition to the default directories. For example:
-     * 
+     *
      * <pre>
      * &lt;filesets&gt;
      *   &lt;fileset&gt;
@@ -123,20 +120,20 @@ public class CleanMojo
      * performance by setting this parameter to <code>true</code>. <br/>
      * Starting with <code>3.0.0</code> the property has been renamed from <code>clean.followSymLinks</code> to
      * <code>maven.clean.followSymLinks</code>.
-     * 
+     *
      * @since 2.1
      */
-    @Parameter( property = "maven.clean.followSymLinks", defaultValue = "false" )
+    @Parameter(property = "maven.clean.followSymLinks", defaultValue = "false")
     private boolean followSymLinks;
 
     /**
      * Disables the plugin execution. <br/>
      * Starting with <code>3.0.0</code> the property has been renamed from <code>clean.skip</code> to
      * <code>maven.clean.skip</code>.
-     * 
+     *
      * @since 2.2
      */
-    @Parameter( property = "maven.clean.skip", defaultValue = "false" )
+    @Parameter(property = "maven.clean.skip", defaultValue = "false")
     private boolean skip;
 
     /**
@@ -144,7 +141,7 @@ public class CleanMojo
      *
      * @since 2.2
      */
-    @Parameter( property = "maven.clean.failOnError", defaultValue = "true" )
+    @Parameter(property = "maven.clean.failOnError", defaultValue = "true")
     private boolean failOnError;
 
     /**
@@ -154,7 +151,7 @@ public class CleanMojo
      *
      * @since 2.4.2
      */
-    @Parameter( property = "maven.clean.retryOnError", defaultValue = "true" )
+    @Parameter(property = "maven.clean.retryOnError", defaultValue = "true")
     private boolean retryOnError;
 
     /**
@@ -165,7 +162,7 @@ public class CleanMojo
      *
      * @since 2.3
      */
-    @Parameter( property = "maven.clean.excludeDefaultDirectories", defaultValue = "false" )
+    @Parameter(property = "maven.clean.excludeDefaultDirectories", defaultValue = "false")
     private boolean excludeDefaultDirectories;
 
     /**
@@ -177,7 +174,7 @@ public class CleanMojo
      *
      * @since 3.2
      */
-    @Parameter( property = "maven.clean.fast", defaultValue = "false" )
+    @Parameter(property = "maven.clean.fast", defaultValue = "false")
     private boolean fast;
 
     /**
@@ -192,7 +189,7 @@ public class CleanMojo
      * @since 3.2
      * @see #fast
      */
-    @Parameter( property = "maven.clean.fastDir" )
+    @Parameter(property = "maven.clean.fastDir")
     private File fastDir;
 
     /**
@@ -205,10 +202,10 @@ public class CleanMojo
      * @since 3.2
      * @see #fast
      */
-    @Parameter( property = "maven.clean.fastMode", defaultValue = FAST_MODE_BACKGROUND )
+    @Parameter(property = "maven.clean.fastMode", defaultValue = FAST_MODE_BACKGROUND)
     private String fastMode;
 
-    @Parameter( defaultValue = "${session}", readonly = true )
+    @Parameter(defaultValue = "${session}", readonly = true)
     private MavenSession session;
 
     /**
@@ -218,74 +215,57 @@ public class CleanMojo
      * @throws MojoExecutionException When a directory failed to get deleted.
      * @see org.apache.maven.plugin.Mojo#execute()
      */
-    public void execute()
-        throws MojoExecutionException
-    {
-        if ( skip )
-        {
-            getLog().info( "Clean is skipped." );
+    public void execute() throws MojoExecutionException {
+        if (skip) {
+            getLog().info("Clean is skipped.");
             return;
         }
 
-        String multiModuleProjectDirectory = session != null
-                ? session.getSystemProperties().getProperty( "maven.multiModuleProjectDirectory" ) : null;
+        String multiModuleProjectDirectory =
+                session != null ? session.getSystemProperties().getProperty("maven.multiModuleProjectDirectory") : null;
         File fastDir;
-        if ( fast && this.fastDir != null )
-        {
+        if (fast && this.fastDir != null) {
             fastDir = this.fastDir;
-        }
-        else if ( fast && multiModuleProjectDirectory != null )
-        {
-            fastDir = new File( multiModuleProjectDirectory, "target/.clean" );
-        }
-        else
-        {
+        } else if (fast && multiModuleProjectDirectory != null) {
+            fastDir = new File(multiModuleProjectDirectory, "target/.clean");
+        } else {
             fastDir = null;
-            if ( fast )
-            {
-                getLog().warn( "Fast clean requires maven 3.3.1 or newer, "
+            if (fast) {
+                getLog().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." );
+                        + "this plugin, or the 'maven.clean.fastDir' user property to be set.");
             }
         }
-        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 + "'." );
+        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 + "'.");
         }
 
-        Cleaner cleaner = new Cleaner( session, getLog(), isVerbose(), fastDir, fastMode );
+        Cleaner cleaner = new Cleaner(session, getLog(), isVerbose(), fastDir, fastMode);
 
-        try
-        {
-            for ( File directoryItem : getDirectories() )
-            {
-                if ( directoryItem != null )
-                {
-                    cleaner.delete( directoryItem, null, followSymLinks, failOnError, retryOnError );
+        try {
+            for (File directoryItem : getDirectories()) {
+                if (directoryItem != null) {
+                    cleaner.delete(directoryItem, null, followSymLinks, failOnError, retryOnError);
                 }
             }
 
-            if ( filesets != null )
-            {
-                for ( Fileset fileset : filesets )
-                {
-                    if ( fileset.getDirectory() == null )
-                    {
-                        throw new MojoExecutionException( "Missing base directory for " + fileset );
+            if (filesets != null) {
+                for (Fileset fileset : filesets) {
+                    if (fileset.getDirectory() == null) {
+                        throw new MojoExecutionException("Missing base directory for " + fileset);
                     }
-                    GlobSelector selector = new GlobSelector( fileset.getIncludes(), fileset.getExcludes(),
-                                                              fileset.isUseDefaultExcludes() );
-                    cleaner.delete( fileset.getDirectory(), selector, fileset.isFollowSymlinks(), failOnError,
-                                    retryOnError );
+                    GlobSelector selector = new GlobSelector(
+                            fileset.getIncludes(), fileset.getExcludes(), fileset.isUseDefaultExcludes());
+                    cleaner.delete(
+                            fileset.getDirectory(), selector, fileset.isFollowSymlinks(), failOnError, retryOnError);
                 }
             }
-        }
-        catch ( IOException e )
-        {
-            throw new MojoExecutionException( "Failed to clean project: " + e.getMessage(), e );
+        } catch (IOException e) {
+            throw new MojoExecutionException("Failed to clean project: " + e.getMessage(), e);
         }
     }
 
@@ -294,9 +274,8 @@ public class CleanMojo
      *
      * @return <code>true</code> if verbose output is enabled, <code>false</code> otherwise.
      */
-    private boolean isVerbose()
-    {
-        return ( verbose != null ) ? verbose : getLog().isDebugEnabled();
+    private boolean isVerbose() {
+        return (verbose != null) ? verbose : getLog().isDebugEnabled();
     }
 
     /**
@@ -304,18 +283,13 @@ public class CleanMojo
      *
      * @return The directories to clean or an empty array if none, never <code>null</code>.
      */
-    private File[] getDirectories()
-    {
+    private File[] getDirectories() {
         File[] directories;
-        if ( excludeDefaultDirectories )
-        {
+        if (excludeDefaultDirectories) {
             directories = new File[0];
-        }
-        else
-        {
-            directories = new File[] { directory, outputDirectory, testOutputDirectory, reportDirectory };
+        } else {
+            directories = new File[] {directory, outputDirectory, testOutputDirectory, reportDirectory};
         }
         return directories;
     }
-
 }
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 4e6e9d2..97ef594 100644
--- a/src/main/java/org/apache/maven/plugins/clean/Cleaner.java
+++ b/src/main/java/org/apache/maven/plugins/clean/Cleaner.java
@@ -1,5 +1,3 @@
-package org.apache.maven.plugins.clean;
-
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements.  See the NOTICE file
@@ -18,6 +16,7 @@ package org.apache.maven.plugins.clean;
  * specific language governing permissions and limitations
  * under the License.
  */
+package org.apache.maven.plugins.clean;
 
 import java.io.File;
 import java.io.IOException;
@@ -25,15 +24,17 @@ import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.Method;
 import java.lang.reflect.Proxy;
 import java.nio.file.Files;
+import java.nio.file.LinkOption;
 import java.nio.file.Path;
 import java.nio.file.StandardCopyOption;
+import java.nio.file.attribute.BasicFileAttributes;
 import java.util.ArrayDeque;
 import java.util.Deque;
 
 import org.apache.maven.execution.ExecutionListener;
 import org.apache.maven.execution.MavenSession;
 import org.apache.maven.plugin.logging.Log;
-import org.apache.maven.shared.utils.Os;
+import org.codehaus.plexus.util.Os;
 import org.eclipse.aether.SessionData;
 
 import static org.apache.maven.plugins.clean.CleanMojo.FAST_MODE_BACKGROUND;
@@ -41,13 +42,12 @@ import static org.apache.maven.plugins.clean.CleanMojo.FAST_MODE_DEFER;
 
 /**
  * Cleans directories.
- * 
+ *
  * @author Benjamin Bentmann
  */
-class Cleaner
-{
+class Cleaner {
 
-    private static final boolean ON_WINDOWS = Os.isFamily( Os.FAMILY_WINDOWS );
+    private static final boolean ON_WINDOWS = Os.isFamily(Os.FAMILY_WINDOWS);
 
     private static final String LAST_DIRECTORY_TO_DELETE = Cleaner.class.getName() + ".lastDirectoryToDelete";
 
@@ -74,13 +74,12 @@ class Cleaner
      * @param verbose Whether to perform verbose logging.
      * @param fastMode The fast deletion mode
      */
-    Cleaner( MavenSession session, final Log log, boolean verbose, File fastDir, String fastMode )
-    {
-        logDebug = ( log == null || !log.isDebugEnabled() ) ? null : log::debug;
+    Cleaner(MavenSession session, final Log log, boolean verbose, File fastDir, String fastMode) {
+        logDebug = (log == null || !log.isDebugEnabled()) ? null : log::debug;
 
-        logInfo = ( log == null || !log.isInfoEnabled() ) ? null : log::info;
+        logInfo = (log == null || !log.isInfoEnabled()) ? null : log::info;
 
-        logWarn = ( log == null || !log.isWarnEnabled() ) ? null : log::warn;
+        logWarn = (log == null || !log.isWarnEnabled()) ? null : log::warn;
 
         logVerbose = verbose ? logInfo : logDebug;
 
@@ -91,7 +90,7 @@ class Cleaner
 
     /**
      * Deletes the specified directories and its contents.
-     * 
+     *
      * @param basedir The directory to delete, must not be <code>null</code>. Non-existing directories will be silently
      *            ignored.
      * @param selector The selector used to determine what contents to delete, may be <code>null</code> to delete
@@ -101,115 +100,89 @@ class Cleaner
      * @param retryOnError Whether to undertake additional delete attempts in case the first attempt failed.
      * @throws IOException If a file/directory could not be deleted and <code>failOnError</code> is <code>true</code>.
      */
-    public void delete( File basedir, Selector selector, boolean followSymlinks, boolean failOnError,
-                        boolean retryOnError )
-        throws IOException
-    {
-        if ( !basedir.isDirectory() )
-        {
-            if ( !basedir.exists() )
-            {
-                if ( logDebug != null )
-                {
-                    logDebug.log( "Skipping non-existing directory " + basedir );
+    public void delete(
+            File basedir, Selector selector, boolean followSymlinks, boolean failOnError, boolean retryOnError)
+            throws IOException {
+        if (!basedir.isDirectory()) {
+            if (!basedir.exists()) {
+                if (logDebug != null) {
+                    logDebug.log("Skipping non-existing directory " + basedir);
                 }
                 return;
             }
-            throw new IOException( "Invalid base directory " + basedir );
+            throw new IOException("Invalid base directory " + basedir);
         }
 
-        if ( logInfo != null )
-        {
-            logInfo.log( "Deleting " + basedir + ( selector != null ? " (" + selector + ")" : "" ) );
+        if (logInfo != null) {
+            logInfo.log("Deleting " + basedir + (selector != null ? " (" + selector + ")" : ""));
         }
 
         File file = followSymlinks ? basedir : basedir.getCanonicalFile();
 
-        if ( selector == null && !followSymlinks && fastDir != null && session != null )
-        {
+        if (selector == null && !followSymlinks && fastDir != null && session != null) {
             // If anything wrong happens, we'll just use the usual deletion mechanism
-            if ( fastDelete( file ) )
-            {
+            if (fastDelete(file)) {
                 return;
             }
         }
 
-        delete( file, "", selector, followSymlinks, failOnError, retryOnError );
+        delete(file, "", selector, followSymlinks, failOnError, retryOnError);
     }
 
-    private boolean fastDelete( File baseDirFile )
-    {
+    private boolean fastDelete(File baseDirFile) {
         Path baseDir = baseDirFile.toPath();
         Path fastDir = this.fastDir.toPath();
         // Handle the case where we use ${maven.multiModuleProjectDirectory}/target/.clean for example
-        if ( fastDir.toAbsolutePath().startsWith( baseDir.toAbsolutePath() ) )
-        {
-            try
-            {
+        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.getRepositorySession().getData().set( LAST_DIRECTORY_TO_DELETE, baseDir.toFile() );
+                Path tmpDir = Files.createTempDirectory(baseDir.getParent(), prefix);
+                try {
+                    Files.move(baseDir, tmpDir, StandardCopyOption.REPLACE_EXISTING);
+                    if (session != null) {
+                        session.getRepositorySession().getData().set(LAST_DIRECTORY_TO_DELETE, baseDir.toFile());
                     }
                     baseDir = tmpDir;
-                }
-                catch ( IOException e )
-                {
-                    Files.delete( tmpDir );
+                } catch (IOException e) {
+                    Files.delete(tmpDir);
                     throw e;
                 }
-            }
-            catch ( IOException e )
-            {
-                if ( logDebug != null )
-                {
+            } catch (IOException e) {
+                if (logDebug != null) {
                     // TODO: this Logger interface cannot log exceptions and needs refactoring
-                    logDebug.log( "Unable to fast delete directory: " + e );
+                    logDebug.log("Unable to fast delete directory: " + e);
                 }
                 return false;
             }
         }
         // Create fastDir and the needed parents if needed
-        try
-        {
-            if ( !Files.isDirectory( fastDir ) )
-            {
-                Files.createDirectories( fastDir );
+        try {
+            if (!Files.isDirectory(fastDir)) {
+                Files.createDirectories(fastDir);
             }
-        }
-        catch ( IOException e )
-        {
-            if ( logDebug != null )
-            {
+        } catch (IOException e) {
+            if (logDebug != null) {
                 // TODO: this Logger interface cannot log exceptions and needs refactoring
-                logDebug.log( "Unable to fast delete directory as the path "
-                        + fastDir + " does not point to a directory or cannot be created: " + e );
+                logDebug.log("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() );
+        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.toFile(), fastMode );
+            Files.move(baseDir, dstDir, StandardCopyOption.ATOMIC_MOVE);
+            BackgroundCleaner.delete(this, tmpDir.toFile(), fastMode);
             return true;
-        }
-        catch ( IOException e )
-        {
-            if ( logDebug != null )
-            {
+        } catch (IOException e) {
+            if (logDebug != null) {
                 // TODO: this Logger interface cannot log exceptions and needs refactoring
-                logDebug.log( "Unable to fast delete directory: " + e );
+                logDebug.log("Unable to fast delete directory: " + e);
             }
             return false;
         }
@@ -217,7 +190,7 @@ class Cleaner
 
     /**
      * Deletes the specified file or directory.
-     * 
+     *
      * @param file The file/directory to delete, must not be <code>null</code>. If <code>followSymlinks</code> is
      *            <code>false</code>, it is assumed that the parent file is canonical.
      * @param pathname The relative pathname of the file, using {@link File#separatorChar}, must not be
@@ -230,128 +203,104 @@ class Cleaner
      * @return The result of the cleaning, never <code>null</code>.
      * @throws IOException If a file/directory could not be deleted and <code>failOnError</code> is <code>true</code>.
      */
-    private Result delete( File file, String pathname, Selector selector, boolean followSymlinks, boolean failOnError,
-                           boolean retryOnError )
-        throws IOException
-    {
+    private Result delete(
+            File file,
+            String pathname,
+            Selector selector,
+            boolean followSymlinks,
+            boolean failOnError,
+            boolean retryOnError)
+            throws IOException {
         Result result = new Result();
 
         boolean isDirectory = file.isDirectory();
 
-        if ( isDirectory )
-        {
-            if ( selector == null || selector.couldHoldSelected( pathname ) )
-            {
-                final boolean isSymlink = Files.isSymbolicLink( file.toPath() );
-                File canonical = followSymlinks ? file : file.getCanonicalFile();
-                if ( followSymlinks || !isSymlink )
-                {
+        if (isDirectory) {
+            if (selector == null || selector.couldHoldSelected(pathname)) {
+                if (followSymlinks || !isSymbolicLink(file.toPath())) {
+                    File canonical = followSymlinks ? file : file.getCanonicalFile();
                     String[] filenames = canonical.list();
-                    if ( filenames != null )
-                    {
+                    if (filenames != null) {
                         String prefix = pathname.length() > 0 ? pathname + File.separatorChar : "";
-                        for ( int i = filenames.length - 1; i >= 0; i-- )
-                        {
+                        for (int i = filenames.length - 1; i >= 0; i--) {
                             String filename = filenames[i];
-                            File child = new File( canonical, filename );
-                            result.update( delete( child, prefix + filename, selector, followSymlinks, failOnError,
-                                                   retryOnError ) );
+                            File child = new File(canonical, filename);
+                            result.update(delete(
+                                    child, prefix + filename, selector, followSymlinks, failOnError, retryOnError));
                         }
                     }
+                } else if (logDebug != null) {
+                    logDebug.log("Not recursing into symlink " + file);
                 }
-                else if ( logDebug != null )
-                {
-                    logDebug.log( "Not recursing into symlink " + file );
-                }
-            }
-            else if ( logDebug != null )
-            {
-                logDebug.log( "Not recursing into directory without included files " + file );
+            } else if (logDebug != null) {
+                logDebug.log("Not recursing into directory without included files " + file);
             }
         }
 
-        if ( !result.excluded && ( selector == null || selector.isSelected( pathname ) ) )
-        {
-            if ( logVerbose != null )
-            {
-                if ( isDirectory )
-                {
-                    logVerbose.log( "Deleting directory " + file );
-                }
-                else if ( file.exists() )
-                {
-                    logVerbose.log( "Deleting file " + file );
-                }
-                else
-                {
-                    logVerbose.log( "Deleting dangling symlink " + file );
+        if (!result.excluded && (selector == null || selector.isSelected(pathname))) {
+            if (logVerbose != null) {
+                if (isDirectory) {
+                    logVerbose.log("Deleting directory " + file);
+                } else if (file.exists()) {
+                    logVerbose.log("Deleting file " + file);
+                } else {
+                    logVerbose.log("Deleting dangling symlink " + file);
                 }
             }
-            result.failures += delete( file, failOnError, retryOnError );
-        }
-        else
-        {
+            result.failures += delete(file, failOnError, retryOnError);
+        } else {
             result.excluded = true;
         }
 
         return result;
     }
 
+    private boolean isSymbolicLink(Path path) throws IOException {
+        BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS);
+        return attrs.isSymbolicLink()
+                // MCLEAN-93: NTFS junctions have isDirectory() and isOther() attributes set
+                || (attrs.isDirectory() && attrs.isOther());
+    }
+
     /**
      * Deletes the specified file, directory. If the path denotes a symlink, only the link is removed, its target is
      * left untouched.
-     * 
+     *
      * @param file The file/directory to delete, must not be <code>null</code>.
      * @param failOnError Whether to abort with an exception in case the file/directory could not be deleted.
      * @param retryOnError Whether to undertake additional delete attempts in case the first attempt failed.
      * @return <code>0</code> if the file was deleted, <code>1</code> otherwise.
      * @throws IOException If a file/directory could not be deleted and <code>failOnError</code> is <code>true</code>.
      */
-    private int delete( File file, boolean failOnError, boolean retryOnError )
-        throws IOException
-    {
-        if ( !file.delete() )
-        {
+    private int delete(File file, boolean failOnError, boolean retryOnError) throws IOException {
+        if (!file.delete()) {
             boolean deleted = false;
 
-            if ( retryOnError )
-            {
-                if ( ON_WINDOWS )
-                {
+            if (retryOnError) {
+                if (ON_WINDOWS) {
                     // try to release any locks held by non-closed files
                     System.gc();
                 }
 
-                final int[] delays = { 50, 250, 750 };
-                for ( int i = 0; !deleted && i < delays.length; i++ )
-                {
-                    try
-                    {
-                        Thread.sleep( delays[i] );
-                    }
-                    catch ( InterruptedException e )
-                    {
+                final int[] delays = {50, 250, 750};
+                for (int i = 0; !deleted && i < delays.length; i++) {
+                    try {
+                        Thread.sleep(delays[i]);
+                    } catch (InterruptedException e) {
                         // ignore
                     }
                     deleted = file.delete() || !file.exists();
                 }
-            }
-            else
-            {
+            } else {
                 deleted = !file.exists();
             }
 
-            if ( !deleted )
-            {
-                if ( failOnError )
-                {
-                    throw new IOException( "Failed to delete " + file );
-                }
-                else
-                {
-                    if ( logWarn != null )
-                    {
-                        logWarn.log( "Failed to delete " + file );
+            if (!deleted) {
+                if (failOnError) {
+                    throw new IOException("Failed to delete " + file);
+                } else {
+                    if (logWarn != null) {
+                        logWarn.log("Failed to delete " + file);
                     }
                     return 1;
                 }
@@ -361,30 +310,24 @@ class Cleaner
         return 0;
     }
 
-    private static class Result
-    {
+    private static class Result {
 
         private int failures;
 
         private boolean excluded;
 
-        public void update( Result result )
-        {
+        public void update(Result result) {
             failures += result.failures;
             excluded |= result.excluded;
         }
-
     }
 
-    private interface Logger
-    {
-
-        void log( CharSequence message );
+    private interface Logger {
 
+        void log(CharSequence message);
     }
 
-    private static class BackgroundCleaner extends Thread
-    {
+    private static class BackgroundCleaner extends Thread {
 
         private static BackgroundCleaner instance;
 
@@ -400,84 +343,63 @@ class Cleaner
 
         private int status = NEW;
 
-        public static void delete( Cleaner cleaner, File dir, String fastMode )
-        {
-            synchronized ( BackgroundCleaner.class )
-            {
-                if ( instance == null || !instance.doDelete( dir ) )
-                {
-                    instance = new BackgroundCleaner( cleaner, dir, fastMode );
+        public static void delete(Cleaner cleaner, File 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 )
-                {
+        static void sessionEnd() {
+            synchronized (BackgroundCleaner.class) {
+                if (instance != null) {
                     instance.doSessionEnd();
                 }
             }
         }
 
-        private BackgroundCleaner( Cleaner cleaner, File dir, String fastMode )
-        {
-            super( "mvn-background-cleaner" );
+        private BackgroundCleaner(Cleaner cleaner, File dir, String fastMode) {
+            super("mvn-background-cleaner");
             this.cleaner = cleaner;
             this.fastMode = fastMode;
-            init( cleaner.fastDir, dir );
+            init(cleaner.fastDir, dir);
         }
 
-        public void run()
-        {
-            while ( true )
-            {
+        public void run() {
+            while (true) {
                 File basedir = pollNext();
-                if ( basedir == null )
-                {
+                if (basedir == null) {
                     break;
                 }
-                try
-                {
-                    cleaner.delete( basedir, "", null, false, false, true );
-                }
-                catch ( IOException e )
-                {
+                try {
+                    cleaner.delete(basedir, "", null, false, false, true);
+                } catch (IOException e) {
                     // do not display errors
                 }
             }
         }
 
-        synchronized void init( File fastDir, File dir )
-        {
-            if ( fastDir.isDirectory() )
-            {
+        synchronized void init(File fastDir, File dir) {
+            if (fastDir.isDirectory()) {
                 File[] children = fastDir.listFiles();
-                if ( children != null && children.length > 0 )
-                {
-                    for ( File child : children )
-                    {
-                        doDelete( child );
+                if (children != null && children.length > 0) {
+                    for (File child : children) {
+                        doDelete(child);
                     }
                 }
             }
-            doDelete( dir );
+            doDelete(dir);
         }
 
-        synchronized File pollNext()
-        {
+        synchronized File pollNext() {
             File basedir = filesToDelete.poll();
-            if ( basedir == null )
-            {
-                if ( cleaner.session != null )
-                {
+            if (basedir == null) {
+                if (cleaner.session != null) {
                     SessionData data = cleaner.session.getRepositorySession().getData();
-                    File lastDir = ( File ) data.get( LAST_DIRECTORY_TO_DELETE );
-                    if ( lastDir != null )
-                    {
-                        data.set( LAST_DIRECTORY_TO_DELETE, null );
+                    File lastDir = (File) data.get(LAST_DIRECTORY_TO_DELETE);
+                    if (lastDir != null) {
+                        data.set(LAST_DIRECTORY_TO_DELETE, null);
                         return lastDir;
                     }
                 }
@@ -487,15 +409,12 @@ class Cleaner
             return basedir;
         }
 
-        synchronized boolean doDelete( File dir )
-        {
-            if ( status == STOPPED )
-            {
+        synchronized boolean doDelete(File dir) {
+            if (status == STOPPED) {
                 return false;
             }
-            filesToDelete.add( dir );
-            if ( status == NEW && FAST_MODE_BACKGROUND.equals( fastMode ) )
-            {
+            filesToDelete.add(dir);
+            if (status == NEW && FAST_MODE_BACKGROUND.equals(fastMode)) {
                 status = RUNNING;
                 notifyAll();
                 start();
@@ -511,72 +430,56 @@ class Cleaner
          * 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()
-        {
+        private void wrapExecutionListener() {
             ExecutionListener executionListener = cleaner.session.getRequest().getExecutionListener();
-            if ( executionListener == null
-                    || !Proxy.isProxyClass( executionListener.getClass() )
-                    || !( Proxy.getInvocationHandler( executionListener ) instanceof SpyInvocationHandler ) )
-            {
-                ExecutionListener listener = ( ExecutionListener ) Proxy.newProxyInstance(
+            if (executionListener == null
+                    || !Proxy.isProxyClass(executionListener.getClass())
+                    || !(Proxy.getInvocationHandler(executionListener) instanceof SpyInvocationHandler)) {
+                ExecutionListener listener = (ExecutionListener) Proxy.newProxyInstance(
                         ExecutionListener.class.getClassLoader(),
-                        new Class[] { ExecutionListener.class },
-                        new SpyInvocationHandler( executionListener ) );
-                cleaner.session.getRequest().setExecutionListener( listener );
+                        new Class[] {ExecutionListener.class},
+                        new SpyInvocationHandler(executionListener));
+                cleaner.session.getRequest().setExecutionListener(listener);
             }
         }
 
-        synchronized void doSessionEnd()
-        {
-            if ( status != STOPPED )
-            {
-                if ( status == NEW )
-                {
+        synchronized void doSessionEnd() {
+            if (status != STOPPED) {
+                if (status == NEW) {
                     start();
                 }
-                if ( !FAST_MODE_DEFER.equals( fastMode ) )
-                {
-                    try
-                    {
-                        cleaner.logInfo.log( "Waiting for background file deletion" );
-                        while ( status != STOPPED )
-                        {
+                if (!FAST_MODE_DEFER.equals(fastMode)) {
+                    try {
+                        if (cleaner.logInfo != null) {
+                            cleaner.logInfo.log("Waiting for background file deletion");
+                        }
+                        while (status != STOPPED) {
                             wait();
                         }
-                    }
-                    catch ( InterruptedException e )
-                    {
+                    } catch (InterruptedException e) {
                         // ignore
                     }
                 }
             }
         }
-
     }
 
-    static class SpyInvocationHandler implements InvocationHandler
-    {
+    static class SpyInvocationHandler implements InvocationHandler {
         private final ExecutionListener delegate;
 
-        SpyInvocationHandler( ExecutionListener delegate )
-        {
+        SpyInvocationHandler(ExecutionListener delegate) {
             this.delegate = delegate;
         }
 
         @Override
-        public Object invoke( Object proxy, Method method, Object[] args ) throws Throwable
-        {
-            if ( "sessionEnded".equals( method.getName() ) )
-            {
+        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+            if ("sessionEnded".equals(method.getName())) {
                 BackgroundCleaner.sessionEnd();
             }
-            if ( delegate != null )
-            {
-                return method.invoke( delegate, args );
+            if (delegate != null) {
+                return method.invoke(delegate, args);
             }
             return null;
         }
-
     }
-
 }
diff --git a/src/main/java/org/apache/maven/plugins/clean/Fileset.java b/src/main/java/org/apache/maven/plugins/clean/Fileset.java
index 2df6435..ecd88b0 100644
--- a/src/main/java/org/apache/maven/plugins/clean/Fileset.java
+++ b/src/main/java/org/apache/maven/plugins/clean/Fileset.java
@@ -1,5 +1,3 @@
-package org.apache.maven.plugins.clean;
-
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements.  See the NOTICE file
@@ -18,6 +16,7 @@ package org.apache.maven.plugins.clean;
  * specific language governing permissions and limitations
  * under the License.
  */
+package org.apache.maven.plugins.clean;
 
 import java.io.File;
 import java.util.Arrays;
@@ -31,8 +30,7 @@ import java.util.Arrays;
  *
  * @since 2.1
  */
-public class Fileset
-{
+public class Fileset {
 
     private File directory;
 
@@ -47,40 +45,35 @@ public class Fileset
     /**
      * @return {@link #directory}
      */
-    public File getDirectory()
-    {
+    public File getDirectory() {
         return directory;
     }
 
     /**
      * @return {@link #includes}
      */
-    public String[] getIncludes()
-    {
-        return ( includes != null ) ? includes : new String[0];
+    public String[] getIncludes() {
+        return (includes != null) ? includes : new String[0];
     }
 
     /**
      * @return {@link #excludes}
      */
-    public String[] getExcludes()
-    {
-        return ( excludes != null ) ? excludes : new String[0];
+    public String[] getExcludes() {
+        return (excludes != null) ? excludes : new String[0];
     }
 
     /**
      * @return {@link #followSymlinks}
      */
-    public boolean isFollowSymlinks()
-    {
+    public boolean isFollowSymlinks() {
         return followSymlinks;
     }
 
     /**
      * @return {@link #useDefaultExcludes}
      */
-    public boolean isUseDefaultExcludes()
-    {
+    public boolean isUseDefaultExcludes() {
         return useDefaultExcludes;
     }
 
@@ -94,10 +87,8 @@ public class Fileset
      * <I>[included files]</I>, excluded: <I>[excluded files]</I>)"</code>
      * @see java.lang.Object#toString()
      */
-    public String toString()
-    {
-        return "file set: " + getDirectory() + " (included: " + Arrays.asList( getIncludes() ) + ", excluded: "
-            + Arrays.asList( getExcludes() ) + ")";
+    public String toString() {
+        return "file set: " + getDirectory() + " (included: " + Arrays.asList(getIncludes()) + ", excluded: "
+                + Arrays.asList(getExcludes()) + ")";
     }
-
 }
diff --git a/src/main/java/org/apache/maven/plugins/clean/GlobSelector.java b/src/main/java/org/apache/maven/plugins/clean/GlobSelector.java
index ab791eb..2d2f99d 100644
--- a/src/main/java/org/apache/maven/plugins/clean/GlobSelector.java
+++ b/src/main/java/org/apache/maven/plugins/clean/GlobSelector.java
@@ -1,5 +1,3 @@
-package org.apache.maven.plugins.clean;
-
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements.  See the NOTICE file
@@ -18,22 +16,20 @@ package org.apache.maven.plugins.clean;
  * specific language governing permissions and limitations
  * under the License.
  */
-
-import org.apache.maven.shared.utils.io.DirectoryScanner;
-import org.apache.maven.shared.utils.io.SelectorUtils;
+package org.apache.maven.plugins.clean;
 
 import java.io.File;
 import java.util.Arrays;
 
+import org.codehaus.plexus.util.DirectoryScanner;
+import org.codehaus.plexus.util.SelectorUtils;
 
 /**
  * Selects paths based on Ant-like glob patterns.
- * 
+ *
  * @author Benjamin Bentmann
  */
-class GlobSelector
-    implements Selector
-{
+class GlobSelector implements Selector {
 
     private final String[] includes;
 
@@ -41,113 +37,87 @@ class GlobSelector
 
     private final String str;
 
-    GlobSelector( String[] includes, String[] excludes )
-    {
-        this( includes, excludes, false );
+    GlobSelector(String[] includes, String[] excludes) {
+        this(includes, excludes, false);
     }
 
-    GlobSelector( String[] includes, String[] excludes, boolean useDefaultExcludes )
-    {
-        this.str = "includes = " + toString( includes ) + ", excludes = " + toString( excludes );
-        this.includes = normalizePatterns( includes );
-        this.excludes = normalizePatterns( addDefaultExcludes( excludes, useDefaultExcludes ) );
+    GlobSelector(String[] includes, String[] excludes, boolean useDefaultExcludes) {
+        this.str = "includes = " + toString(includes) + ", excludes = " + toString(excludes);
+        this.includes = normalizePatterns(includes);
+        this.excludes = normalizePatterns(addDefaultExcludes(excludes, useDefaultExcludes));
     }
 
-    private static String toString( String[] patterns )
-    {
-        return ( patterns == null ) ? "[]" : Arrays.asList( patterns ).toString();
+    private static String toString(String[] patterns) {
+        return (patterns == null) ? "[]" : Arrays.asList(patterns).toString();
     }
 
-    private static String[] addDefaultExcludes( String[] excludes, boolean useDefaultExcludes )
-    {
+    private static String[] addDefaultExcludes(String[] excludes, boolean useDefaultExcludes) {
         String[] defaults = DirectoryScanner.DEFAULTEXCLUDES;
-        if ( !useDefaultExcludes )
-        {
+        if (!useDefaultExcludes) {
             return excludes;
-        }
-        else if ( excludes == null || excludes.length <= 0 )
-        {
+        } else if (excludes == null || excludes.length <= 0) {
             return defaults;
-        }
-        else
-        {
+        } else {
             String[] patterns = new String[excludes.length + defaults.length];
-            System.arraycopy( excludes, 0, patterns, 0, excludes.length );
-            System.arraycopy( defaults, 0, patterns, excludes.length, defaults.length );
+            System.arraycopy(excludes, 0, patterns, 0, excludes.length);
+            System.arraycopy(defaults, 0, patterns, excludes.length, defaults.length);
             return patterns;
         }
     }
 
-    private static String[] normalizePatterns( String[] patterns )
-    {
+    private static String[] normalizePatterns(String[] patterns) {
         String[] normalized;
 
-        if ( patterns != null )
-        {
+        if (patterns != null) {
             normalized = new String[patterns.length];
-            for ( int i = patterns.length - 1; i >= 0; i-- )
-            {
-                normalized[i] = normalizePattern( patterns[i] );
+            for (int i = patterns.length - 1; i >= 0; i--) {
+                normalized[i] = normalizePattern(patterns[i]);
             }
-        }
-        else
-        {
+        } else {
             normalized = new String[0];
         }
 
         return normalized;
     }
 
-    private static String normalizePattern( String pattern )
-    {
-        if ( pattern == null )
-        {
+    private static String normalizePattern(String pattern) {
+        if (pattern == null) {
             return "";
         }
 
-        String normalized = pattern.replace( ( File.separatorChar == '/' ) ? '\\' : '/', File.separatorChar );
+        String normalized = pattern.replace((File.separatorChar == '/') ? '\\' : '/', File.separatorChar);
 
-        if ( normalized.endsWith( File.separator ) )
-        {
+        if (normalized.endsWith(File.separator)) {
             normalized += "**";
         }
 
         return normalized;
     }
 
-    public boolean isSelected( String pathname )
-    {
-        return ( includes.length <= 0 || isMatched( pathname, includes ) )
-            && ( excludes.length <= 0 || !isMatched( pathname, excludes ) );
+    public boolean isSelected(String pathname) {
+        return (includes.length <= 0 || isMatched(pathname, includes))
+                && (excludes.length <= 0 || !isMatched(pathname, excludes));
     }
 
-    private static boolean isMatched( String pathname, String[] patterns )
-    {
-        for ( String pattern : patterns )
-        {
-            if ( SelectorUtils.matchPath( pattern, pathname ) )
-            {
+    private static boolean isMatched(String pathname, String[] patterns) {
+        for (String pattern : patterns) {
+            if (SelectorUtils.matchPath(pattern, pathname)) {
                 return true;
             }
         }
         return false;
     }
 
-    public boolean couldHoldSelected( String pathname )
-    {
-        for ( String include : includes )
-        {
-            if ( SelectorUtils.matchPatternStart( include, pathname ) )
-            {
+    public boolean couldHoldSelected(String pathname) {
+        for (String include : includes) {
+            if (SelectorUtils.matchPatternStart(include, pathname)) {
                 return true;
             }
         }
         return includes.length <= 0;
     }
 
-    public String toString()
-    {
+    public String toString() {
         return str;
     }
-
 }
diff --git a/src/main/java/org/apache/maven/plugins/clean/Selector.java b/src/main/java/org/apache/maven/plugins/clean/Selector.java
index aa80009..93a0695 100644
--- a/src/main/java/org/apache/maven/plugins/clean/Selector.java
+++ b/src/main/java/org/apache/maven/plugins/clean/Selector.java
@@ -1,5 +1,3 @@
-package org.apache.maven.plugins.clean;
-
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements.  See the NOTICE file
@@ -18,31 +16,30 @@ package org.apache.maven.plugins.clean;
  * specific language governing permissions and limitations
  * under the License.
  */
+package org.apache.maven.plugins.clean;
 
 /**
  * Determines whether a path is selected for deletion. The pathnames used for method parameters will be relative to some
  * base directory and use {@link java.io.File#separatorChar} as separator.
- * 
+ *
  * @author Benjamin Bentmann
  */
-interface Selector
-{
+interface Selector {
 
     /**
      * Determines whether a path is selected for deletion.
-     * 
+     *
      * @param pathname The pathname to test, must not be <code>null</code>.
      * @return <code>true</code> if the given path is selected for deletion, <code>false</code> otherwise.
      */
-    boolean isSelected( String pathname );
+    boolean isSelected(String pathname);
 
     /**
      * Determines whether a directory could contain selected paths.
-     * 
+     *
      * @param pathname The directory pathname to test, must not be <code>null</code>.
      * @return <code>true</code> if the given directory might contain selected paths, <code>false</code> if the
      *         directory will definitively not contain selected paths..
      */
-    boolean couldHoldSelected( String pathname );
-
+    boolean couldHoldSelected(String pathname);
 }
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 84b638f..f82e615 100644
--- a/src/test/java/org/apache/maven/plugins/clean/CleanMojoTest.java
+++ b/src/test/java/org/apache/maven/plugins/clean/CleanMojoTest.java
@@ -1,5 +1,3 @@
-package org.apache.maven.plugins.clean;
-
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements.  See the NOTICE file
@@ -18,50 +16,58 @@ package org.apache.maven.plugins.clean;
  * specific language governing permissions and limitations
  * under the License.
  */
+package org.apache.maven.plugins.clean;
 
-import org.apache.maven.plugin.MojoExecutionException;
-import org.apache.maven.plugin.testing.AbstractMojoTestCase;
-
+import java.io.ByteArrayOutputStream;
 import java.io.File;
+import java.io.IOException;
 import java.io.RandomAccessFile;
 import java.nio.channels.FileChannel;
 import java.nio.channels.FileLock;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Collections;
+
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.testing.AbstractMojoTestCase;
 
 import static org.apache.commons.io.FileUtils.copyDirectory;
+import static org.codehaus.plexus.util.IOUtil.copy;
 
 /**
  * Test the clean mojo.
  *
  * @author <a href="mailto:vincent.siveton@gmail.com">Vincent Siveton</a>
  */
-public class CleanMojoTest
-    extends AbstractMojoTestCase
-{
+public class CleanMojoTest extends AbstractMojoTestCase {
     /**
      * Tests the simple removal of directories
      *
      * @throws Exception in case of an error.
      */
-    public void testBasicClean()
-        throws Exception
-    {
+    public void testBasicClean() throws Exception {
         String pluginPom = getBasedir() + "/src/test/resources/unit/basic-clean-test/plugin-pom.xml";
 
         // safety
-        copyDirectory( new File( getBasedir(), "src/test/resources/unit/basic-clean-test" ),
-                                 new File( getBasedir(), "target/test-classes/unit/basic-clean-test" ) );
+        copyDirectory(
+                new File(getBasedir(), "src/test/resources/unit/basic-clean-test"),
+                new File(getBasedir(), "target/test-classes/unit/basic-clean-test"));
 
-        CleanMojo mojo = (CleanMojo) lookupMojo( "clean", pluginPom );
-        assertNotNull( mojo );
+        CleanMojo mojo = (CleanMojo) lookupMojo("clean", pluginPom);
+        assertNotNull(mojo);
 
         mojo.execute();
 
-        assertFalse( "Directory exists", checkExists( getBasedir() + "/target/test-classes/unit/"
-            + "basic-clean-test/buildDirectory" ) );
-        assertFalse( "Directory exists", checkExists( getBasedir() + "/target/test-classes/unit/basic-clean-test/"
-            + "buildOutputDirectory" ) );
-        assertFalse( "Directory exists", checkExists( getBasedir() + "/target/test-classes/unit/basic-clean-test/"
-            + "buildTestDirectory" ) );
+        assertFalse(
+                "Directory exists",
+                checkExists(getBasedir() + "/target/test-classes/unit/" + "basic-clean-test/buildDirectory"));
+        assertFalse(
+                "Directory exists",
+                checkExists(getBasedir() + "/target/test-classes/unit/basic-clean-test/" + "buildOutputDirectory"));
+        assertFalse(
+                "Directory exists",
+                checkExists(getBasedir() + "/target/test-classes/unit/basic-clean-test/" + "buildTestDirectory"));
     }
 
     /**
@@ -69,23 +75,22 @@ public class CleanMojoTest
      *
      * @throws Exception in case of an error.
      */
-    public void testCleanNestedStructure()
-        throws Exception
-    {
+    public void testCleanNestedStructure() throws Exception {
         String pluginPom = getBasedir() + "/src/test/resources/unit/nested-clean-test/plugin-pom.xml";
 
         // safety
-        copyDirectory( new File( getBasedir(), "src/test/resources/unit/nested-clean-test" ),
-                                 new File( getBasedir(), "target/test-classes/unit/nested-clean-test" ) );
+        copyDirectory(
+                new File(getBasedir(), "src/test/resources/unit/nested-clean-test"),
+                new File(getBasedir(), "target/test-classes/unit/nested-clean-test"));
 
-        CleanMojo mojo = (CleanMojo) lookupMojo( "clean", pluginPom );
-        assertNotNull( mojo );
+        CleanMojo mojo = (CleanMojo) lookupMojo("clean", pluginPom);
+        assertNotNull(mojo);
 
         mojo.execute();
 
-        assertFalse( checkExists( getBasedir() + "/target/test-classes/unit/nested-clean-test/target" ) );
-        assertFalse( checkExists( getBasedir() + "/target/test-classes/unit/nested-clean-test/target/classes" ) );
-        assertFalse( checkExists( getBasedir() + "/target/test-classes/unit/nested-clean-test/target/test-classes" ) );
+        assertFalse(checkExists(getBasedir() + "/target/test-classes/unit/nested-clean-test/target"));
+        assertFalse(checkExists(getBasedir() + "/target/test-classes/unit/nested-clean-test/target/classes"));
+        assertFalse(checkExists(getBasedir() + "/target/test-classes/unit/nested-clean-test/target/test-classes"));
     }
 
     /**
@@ -94,27 +99,26 @@ public class CleanMojoTest
      *
      * @throws Exception in case of an error.
      */
-    public void testCleanEmptyDirectories()
-        throws Exception
-    {
+    public void testCleanEmptyDirectories() throws Exception {
         String pluginPom = getBasedir() + "/src/test/resources/unit/empty-clean-test/plugin-pom.xml";
 
         // safety
-        copyDirectory( new File( getBasedir(), "src/test/resources/unit/empty-clean-test" ),
-                                 new File( getBasedir(), "target/test-classes/unit/empty-clean-test" ) );
+        copyDirectory(
+                new File(getBasedir(), "src/test/resources/unit/empty-clean-test"),
+                new File(getBasedir(), "target/test-classes/unit/empty-clean-test"));
 
-        CleanMojo mojo = (CleanMojo) lookupEmptyMojo( "clean", pluginPom );
-        assertNotNull( mojo );
+        CleanMojo mojo = (CleanMojo) lookupEmptyMojo("clean", pluginPom);
+        assertNotNull(mojo);
 
         mojo.execute();
 
-        assertTrue( checkExists( getBasedir() + "/target/test-classes/unit/empty-clean-test/testDirectoryStructure" ) );
-        assertTrue( checkExists( getBasedir() + "/target/test-classes/unit/empty-clean-test/"
-            + "testDirectoryStructure/file.txt" ) );
-        assertTrue( checkExists( getBasedir() + "/target/test-classes/unit/empty-clean-test/"
-            + "testDirectoryStructure/outputDirectory" ) );
-        assertTrue( checkExists( getBasedir() + "/target/test-classes/unit/empty-clean-test/"
-            + "testDirectoryStructure/outputDirectory/file.txt" ) );
+        assertTrue(checkExists(getBasedir() + "/target/test-classes/unit/empty-clean-test/testDirectoryStructure"));
+        assertTrue(checkExists(
+                getBasedir() + "/target/test-classes/unit/empty-clean-test/" + "testDirectoryStructure/file.txt"));
+        assertTrue(checkExists(getBasedir() + "/target/test-classes/unit/empty-clean-test/"
+                + "testDirectoryStructure/outputDirectory"));
+        assertTrue(checkExists(getBasedir() + "/target/test-classes/unit/empty-clean-test/"
+                + "testDirectoryStructure/outputDirectory/file.txt"));
     }
 
     /**
@@ -122,35 +126,34 @@ public class CleanMojoTest
      *
      * @throws Exception in case of an error.
      */
-    public void testFilesetsClean()
-        throws Exception
-    {
+    public void testFilesetsClean() throws Exception {
         String pluginPom = getBasedir() + "/src/test/resources/unit/fileset-clean-test/plugin-pom.xml";
 
         // safety
-        copyDirectory( new File( getBasedir(), "src/test/resources/unit/fileset-clean-test" ),
-                                 new File( getBasedir(), "target/test-classes/unit/fileset-clean-test" ) );
+        copyDirectory(
+                new File(getBasedir(), "src/test/resources/unit/fileset-clean-test"),
+                new File(getBasedir(), "target/test-classes/unit/fileset-clean-test"));
 
-        CleanMojo mojo = (CleanMojo) lookupMojo( "clean", pluginPom );
-        assertNotNull( mojo );
+        CleanMojo mojo = (CleanMojo) lookupMojo("clean", pluginPom);
+        assertNotNull(mojo);
 
         mojo.execute();
 
         // fileset 1
-        assertTrue( checkExists( getBasedir() + "/target/test-classes/unit/fileset-clean-test/target" ) );
-        assertTrue( checkExists( getBasedir() + "/target/test-classes/unit/fileset-clean-test/target/classes" ) );
-        assertFalse( checkExists( getBasedir() + "/target/test-classes/unit/fileset-clean-test/target/test-classes" ) );
-        assertTrue( checkExists( getBasedir() + "/target/test-classes/unit/fileset-clean-test/target/subdir" ) );
-        assertFalse( checkExists( getBasedir() + "/target/test-classes/unit/fileset-clean-test/target/classes/file.txt" ) );
-        assertTrue( checkEmpty( getBasedir() + "/target/test-classes/unit/fileset-clean-test/target/classes" ) );
-        assertFalse( checkEmpty( getBasedir() + "/target/test-classes/unit/fileset-clean-test/target/subdir" ) );
-        assertTrue( checkExists( getBasedir() + "/target/test-classes/unit/fileset-clean-test/target/subdir/file.txt" ) );
+        assertTrue(checkExists(getBasedir() + "/target/test-classes/unit/fileset-clean-test/target"));
+        assertTrue(checkExists(getBasedir() + "/target/test-classes/unit/fileset-clean-test/target/classes"));
+        assertFalse(checkExists(getBasedir() + "/target/test-classes/unit/fileset-clean-test/target/test-classes"));
+        assertTrue(checkExists(getBasedir() + "/target/test-classes/unit/fileset-clean-test/target/subdir"));
+        assertFalse(checkExists(getBasedir() + "/target/test-classes/unit/fileset-clean-test/target/classes/file.txt"));
+        assertTrue(checkEmpty(getBasedir() + "/target/test-classes/unit/fileset-clean-test/target/classes"));
+        assertFalse(checkEmpty(getBasedir() + "/target/test-classes/unit/fileset-clean-test/target/subdir"));
+        assertTrue(checkExists(getBasedir() + "/target/test-classes/unit/fileset-clean-test/target/subdir/file.txt"));
 
         // fileset 2
-        assertTrue( checkExists( getBasedir() + "/target/test-classes/unit/fileset-clean-test/"
-            + "buildOutputDirectory" ) );
-        assertFalse( checkExists( getBasedir() + "/target/test-classes/unit/fileset-clean-test/"
-            + "buildOutputDirectory/file.txt" ) );
+        assertTrue(
+                checkExists(getBasedir() + "/target/test-classes/unit/fileset-clean-test/" + "buildOutputDirectory"));
+        assertFalse(checkExists(
+                getBasedir() + "/target/test-classes/unit/fileset-clean-test/" + "buildOutputDirectory/file.txt"));
     }
 
     /**
@@ -158,27 +161,23 @@ public class CleanMojoTest
      *
      * @throws Exception in case of an error.
      */
-    public void testCleanInvalidDirectory()
-        throws Exception
-    {
+    public void testCleanInvalidDirectory() throws Exception {
         String pluginPom = getBasedir() + "/src/test/resources/unit/invalid-directory-test/plugin-pom.xml";
 
         // safety
-        copyDirectory( new File( getBasedir(), "src/test/resources/unit/invalid-directory-test" ),
-                                 new File( getBasedir(), "target/test-classes/unit/invalid-directory-test" ) );
+        copyDirectory(
+                new File(getBasedir(), "src/test/resources/unit/invalid-directory-test"),
+                new File(getBasedir(), "target/test-classes/unit/invalid-directory-test"));
 
-        CleanMojo mojo = (CleanMojo) lookupMojo( "clean", pluginPom );
-        assertNotNull( mojo );
+        CleanMojo mojo = (CleanMojo) lookupMojo("clean", pluginPom);
+        assertNotNull(mojo);
 
-        try
-        {
+        try {
             mojo.execute();
 
-            fail( "Should fail to delete a file treated as a directory" );
-        }
-        catch ( MojoExecutionException expected )
-        {
-            assertTrue( true );
+            fail("Should fail to delete a file treated as a directory");
+        } catch (MojoExecutionException expected) {
+            assertTrue(true);
         }
     }
 
@@ -187,21 +186,20 @@ public class CleanMojoTest
      *
      * @throws Exception in case of an error.
      */
-    public void testMissingDirectory()
-        throws Exception
-    {
+    public void testMissingDirectory() throws Exception {
         String pluginPom = getBasedir() + "/src/test/resources/unit/missing-directory-test/plugin-pom.xml";
 
         // safety
-        copyDirectory( new File( getBasedir(), "src/test/resources/unit/missing-directory-test" ),
-                                 new File( getBasedir(), "target/test-classes/unit/missing-directory-test" ) );
+        copyDirectory(
+                new File(getBasedir(), "src/test/resources/unit/missing-directory-test"),
+                new File(getBasedir(), "target/test-classes/unit/missing-directory-test"));
 
-        CleanMojo mojo = (CleanMojo) lookupMojo( "clean", pluginPom );
-        assertNotNull( mojo );
+        CleanMojo mojo = (CleanMojo) lookupMojo("clean", pluginPom);
+        assertNotNull(mojo);
 
         mojo.execute();
 
-        assertFalse( checkExists( getBasedir() + "/target/test-classes/unit/missing-directory-test/does-not-exist" ) );
+        assertFalse(checkExists(getBasedir() + "/target/test-classes/unit/missing-directory-test/does-not-exist"));
     }
 
     /**
@@ -212,34 +210,29 @@ public class CleanMojoTest
      *
      * @throws Exception in case of an error.
      */
-    public void testCleanLockedFile()
-        throws Exception
-    {
-        if (!System.getProperty("os.name").toLowerCase().contains("windows"))
-        {
-            assertTrue( "Ignored this test on none Windows based systems", true );
+    public void testCleanLockedFile() throws Exception {
+        if (!System.getProperty("os.name").toLowerCase().contains("windows")) {
+            assertTrue("Ignored this test on non Windows based systems", true);
             return;
         }
 
         String pluginPom = getBasedir() + "/src/test/resources/unit/locked-file-test/plugin-pom.xml";
 
         // safety
-        copyDirectory( new File( getBasedir(), "src/test/resources/unit/locked-file-test" ),
-                                 new File( getBasedir(), "target/test-classes/unit/locked-file-test" ) );
+        copyDirectory(
+                new File(getBasedir(), "src/test/resources/unit/locked-file-test"),
+                new File(getBasedir(), "target/test-classes/unit/locked-file-test"));
 
-        CleanMojo mojo = (CleanMojo) lookupMojo( "clean", pluginPom );
-        assertNotNull( mojo );
+        CleanMojo mojo = (CleanMojo) lookupMojo("clean", pluginPom);
+        assertNotNull(mojo);
 
-        File f = new File( getBasedir(), "target/test-classes/unit/locked-file-test/buildDirectory/file.txt" );
-        try ( FileChannel channel = new RandomAccessFile( f, "rw" ).getChannel();
-              FileLock ignored = channel.lock() )
-        {
+        File f = new File(getBasedir(), "target/test-classes/unit/locked-file-test/buildDirectory/file.txt");
+        try (FileChannel channel = new RandomAccessFile(f, "rw").getChannel();
+                FileLock ignored = channel.lock()) {
             mojo.execute();
-            fail( "Should fail to delete a file that is locked" );
-        }
-        catch ( MojoExecutionException expected )
-        {
-            assertTrue( true );
+            fail("Should fail to delete a file that is locked");
+        } catch (MojoExecutionException expected) {
+            assertTrue(true);
         }
     }
 
@@ -251,54 +244,131 @@ public class CleanMojoTest
      *
      * @throws Exception in case of an error.
      */
-    public void testCleanLockedFileWithNoError()
-        throws Exception
-    {
-        if (!System.getProperty("os.name").toLowerCase().contains("windows"))
-        {
-            assertTrue( "Ignored this test on none Windows based systems", true );
+    public void testCleanLockedFileWithNoError() throws Exception {
+        if (!System.getProperty("os.name").toLowerCase().contains("windows")) {
+            assertTrue("Ignore this test on non Windows based systems", true);
             return;
         }
 
         String pluginPom = getBasedir() + "/src/test/resources/unit/locked-file-test/plugin-pom.xml";
 
         // safety
-        copyDirectory( new File( getBasedir(), "src/test/resources/unit/locked-file-test" ),
-                                 new File( getBasedir(), "target/test-classes/unit/locked-file-test" ) );
+        copyDirectory(
+                new File(getBasedir(), "src/test/resources/unit/locked-file-test"),
+                new File(getBasedir(), "target/test-classes/unit/locked-file-test"));
 
-        CleanMojo mojo = (CleanMojo) lookupMojo( "clean", pluginPom );
-        setVariableValueToObject( mojo, "failOnError", Boolean.FALSE );
-        assertNotNull( mojo );
+        CleanMojo mojo = (CleanMojo) lookupMojo("clean", pluginPom);
+        setVariableValueToObject(mojo, "failOnError", Boolean.FALSE);
+        assertNotNull(mojo);
 
-        File f = new File( getBasedir(), "target/test-classes/unit/locked-file-test/buildDirectory/file.txt" );
-        try ( FileChannel channel = new RandomAccessFile( f, "rw" ).getChannel();
-              FileLock ignored = channel.lock())
-        {
+        File f = new File(getBasedir(), "target/test-classes/unit/locked-file-test/buildDirectory/file.txt");
+        try (FileChannel channel = new RandomAccessFile(f, "rw").getChannel();
+                FileLock ignored = channel.lock()) {
             mojo.execute();
-            assertTrue( true );
+            assertTrue(true);
+        } catch (MojoExecutionException expected) {
+            fail("Should display a warning when deleting a file that is locked");
+        }
+    }
+
+    /**
+     * Test the followLink option with windows junctions
+     * @throws Exception
+     */
+    public void testFollowLinksWithWindowsJunction() throws Exception {
+        if (!System.getProperty("os.name").toLowerCase().contains("windows")) {
+            assertTrue("Ignore this test on non Windows based systems", true);
+            return;
         }
-        catch ( MojoExecutionException expected )
-        {
-            fail( "Should display a warning when deleting a file that is locked" );
+
+        testSymlink((link, target) -> {
+            Process process = new ProcessBuilder()
+                    .directory(link.getParent().toFile())
+                    .command("cmd", "/c", "mklink", "/j", link.getFileName().toString(), target.toString())
+                    .start();
+            process.waitFor();
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            copy(process.getInputStream(), baos);
+            copy(process.getErrorStream(), baos);
+            if (!Files.exists(link)) {
+                throw new IOException("Unable to create junction: " + baos);
+            }
+        });
+    }
+
+    /**
+     * Test the followLink option with sym link
+     * @throws Exception
+     */
+    public void testFollowLinksWithSymLinkOnPosix() throws Exception {
+        if (System.getProperty("os.name").toLowerCase().contains("windows")) {
+            assertTrue("Ignore this test on Windows based systems", true);
+            return;
         }
+
+        testSymlink((link, target) -> {
+            try {
+                Files.createSymbolicLink(link, target);
+            } catch (IOException e) {
+                throw new IOException("Unable to create symbolic link", e);
+            }
+        });
+    }
+
+    @FunctionalInterface
+    interface LinkCreator {
+        void createLink(Path link, Path target) throws Exception;
+    }
+
+    private void testSymlink(LinkCreator linkCreator) throws Exception {
+        Cleaner cleaner = new Cleaner(null, null, false, null, null);
+        Path testDir = Paths.get("target/test-classes/unit/test-dir").toAbsolutePath();
+        Path dirWithLnk = testDir.resolve("dir");
+        Path orgDir = testDir.resolve("org-dir");
+        Path jctDir = dirWithLnk.resolve("jct-dir");
+        Path file = orgDir.resolve("file.txt");
+
+        // create directories, links and file
+        Files.createDirectories(dirWithLnk);
+        Files.createDirectories(orgDir);
+        Files.write(file, Collections.singleton("Hello world"));
+        linkCreator.createLink(jctDir, orgDir);
+        // delete
+        cleaner.delete(dirWithLnk.toFile(), null, false, true, false);
+        // verify
+        assertTrue(Files.exists(file));
+        assertFalse(Files.exists(jctDir));
+        assertTrue(Files.exists(orgDir));
+        assertFalse(Files.exists(dirWithLnk));
+
+        // create directories, links and file
+        Files.createDirectories(dirWithLnk);
+        Files.createDirectories(orgDir);
+        Files.write(file, Collections.singleton("Hello world"));
+        linkCreator.createLink(jctDir, orgDir);
+        // delete
+        cleaner.delete(dirWithLnk.toFile(), null, true, true, false);
+        // verify
+        assertFalse(Files.exists(file));
+        assertFalse(Files.exists(jctDir));
+        assertTrue(Files.exists(orgDir));
+        assertFalse(Files.exists(dirWithLnk));
     }
 
     /**
      * @param dir a dir or a file
      * @return true if a file/dir exists, false otherwise
      */
-    private boolean checkExists( String dir )
-    {
-        return new File( new File( dir ).getAbsolutePath() ).exists();
+    private boolean checkExists(String dir) {
+        return new File(new File(dir).getAbsolutePath()).exists();
     }
 
     /**
      * @param dir a directory
      * @return true if a dir is empty, false otherwise
      */
-    private boolean checkEmpty( String dir )
-    {
-        File[] files = new File( dir ).listFiles();
+    private boolean checkEmpty(String dir) {
+        File[] files = new File(dir).listFiles();
         return files == null || files.length == 0;
     }
 }
diff --git a/src/test/java/org/apache/maven/plugins/clean/Utils.java b/src/test/java/org/apache/maven/plugins/clean/Utils.java
index c6f1342..f96226d 100644
--- a/src/test/java/org/apache/maven/plugins/clean/Utils.java
+++ b/src/test/java/org/apache/maven/plugins/clean/Utils.java
@@ -1,5 +1,3 @@
-package org.apache.maven.plugins.clean;
-
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements.  See the NOTICE file
@@ -18,6 +16,7 @@ package org.apache.maven.plugins.clean;
  * specific language governing permissions and limitations
  * under the License.
  */
+package org.apache.maven.plugins.clean;
 
 import java.io.File;
 
@@ -26,35 +25,29 @@ import org.codehaus.plexus.util.cli.Commandline;
 
 /**
  * Testing helpers for the IT scripts.
- * 
+ *
  * @author Benjamin Bentmann
  */
-public class Utils
-{
+public class Utils {
 
     /**
      * Creates a symbolic link.
-     * 
+     *
      * @param target The target (file or directory) of the link, must not be <code>null</code>.
      * @param link The path to the link, must not be <code>null</code>.
      * @return <code>true</code> if the symlink could be created, <code>false</code> otherwise.
      */
-    public static boolean createSymlink( File target, File link )
-    {
-        try
-        {
+    public static boolean createSymlink(File target, File link) {
+        try {
             Commandline cli = new Commandline();
-            cli.setExecutable( "ln" );
-            cli.createArg().setValue( "-s" );
-            cli.createArg().setFile( target );
-            cli.createArg().setFile( link );
-            int code = CommandLineUtils.executeCommandLine( cli, System.out::println, System.err::println );
+            cli.setExecutable("ln");
+            cli.createArg().setValue("-s");
+            cli.createArg().setFile(target);
+            cli.createArg().setFile(link);
+            int code = CommandLineUtils.executeCommandLine(cli, System.out::println, System.err::println);
             return 0 == code;
-        }
-        catch ( Exception e )
-        {
+        } catch (Exception e) {
             return false;
         }
     }
-
 }

More details

Full run details

Historical runs