You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by sl...@apache.org on 2020/06/22 22:38:36 UTC
[maven-shared-utils] 01/02: rework directory scanner to ensure it
can enforce exclusions and it uses Path
This is an automated email from the ASF dual-hosted git repository.
slachiewicz pushed a commit to branch rmannibucau/directory-scanner-rework
in repository https://gitbox.apache.org/repos/asf/maven-shared-utils.git
commit fc050ad089c8a0c4bfdaeb0c49cc8a3f446faba2
Author: Romain Manni-Bucau <rm...@gmail.com>
AuthorDate: Wed Jun 10 11:00:15 2020 +0200
rework directory scanner to ensure it can enforce exclusions and it uses Path
---
.../maven/shared/utils/io/DirectoryScanner.java | 455 +++++++++------------
.../apache/maven/shared/utils/io/MatchPattern.java | 2 -
.../maven/shared/utils/io/MatchPatterns.java | 2 -
.../maven/shared/utils/io/ScanConductor.java | 2 -
.../apache/maven/shared/utils/io/ScannerAware.java | 29 ++
.../maven/shared/utils/io/SelectorUtils.java | 3 -
.../io/conductor/EnforceExcludesOverIncludes.java | 58 +++
.../shared/utils/io/DirectoryScannerTest.java | 10 +-
.../conductor/EnforceExcludesOverIncludesTest.java | 92 +++++
9 files changed, 376 insertions(+), 277 deletions(-)
diff --git a/src/main/java/org/apache/maven/shared/utils/io/DirectoryScanner.java b/src/main/java/org/apache/maven/shared/utils/io/DirectoryScanner.java
index 5d03525..582336f 100644
--- a/src/main/java/org/apache/maven/shared/utils/io/DirectoryScanner.java
+++ b/src/main/java/org/apache/maven/shared/utils/io/DirectoryScanner.java
@@ -19,11 +19,19 @@ package org.apache.maven.shared.utils.io;
* under the License.
*/
+import org.apache.maven.shared.utils.io.conductor.EnforceExcludesOverIncludes;
+
import java.io.File;
import java.io.IOException;
+import java.nio.file.FileVisitOption;
+import java.nio.file.FileVisitResult;
import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -31,6 +39,8 @@ import java.util.Set;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
+import static java.nio.file.FileVisitOption.FOLLOW_LINKS;
+
/**
* Class for scanning a directory for files/directories which match certain criteria.
* <p/>
@@ -108,9 +118,7 @@ import javax.annotation.Nullable;
* @author Magesh Umasankar
* @author <a href="mailto:bruce@callenish.com">Bruce Atherton</a>
* @author <a href="mailto:levylambert@tiscali-dsl.de">Antoine Levy-Lambert</a>
- * @deprecated use {@code java.nio.file.DirectoryStream} and related classes
*/
-@Deprecated
public class DirectoryScanner
{
/**
@@ -158,7 +166,7 @@ public class DirectoryScanner
/**
* The base directory to be scanned.
*/
- private File basedir;
+ private Path basedir;
/**
* The patterns for the files to be included.
@@ -217,8 +225,6 @@ public class DirectoryScanner
/**
* Whether or not symbolic links should be followed.
- *
- *
*/
private boolean followSymlinks = true;
@@ -229,11 +235,6 @@ public class DirectoryScanner
private ScanConductor scanConductor = null;
/**
- * The last ScanAction. We need to store this in the instance as the scan() method doesn't return
- */
- private ScanConductor.ScanAction scanAction = null;
-
- /**
* Sole constructor.
*/
public DirectoryScanner()
@@ -259,6 +260,16 @@ public class DirectoryScanner
*/
public void setBasedir( @Nonnull final File basedir )
{
+ setBasedir( basedir.toPath() );
+ }
+
+ /**
+ * Sets the base directory to be scanned. This is the directory which is scanned recursively.
+ *
+ * @param basedir The base directory for scanning. Should not be <code>null</code>.
+ */
+ public void setBasedir( @Nonnull final Path basedir )
+ {
this.basedir = basedir;
}
@@ -269,7 +280,7 @@ public class DirectoryScanner
*/
public File getBasedir()
{
- return basedir;
+ return basedir.toFile();
}
/**
@@ -363,6 +374,15 @@ public class DirectoryScanner
}
/**
+ * Set {@link EnforceExcludesOverIncludes} scan conductor for a faster scanning
+ * and generally no functional side effect.
+ */
+ public void setEnforceExcludesOverIncludes()
+ {
+ setScanConductor( new EnforceExcludesOverIncludes() );
+ }
+
+ /**
* Scans the base directory for files which match at least one include pattern and don't match any exclude patterns.
* If there are selectors then the files must pass muster there, as well.
*
@@ -376,11 +396,11 @@ public class DirectoryScanner
{
throw new IllegalStateException( "No basedir set" );
}
- if ( !basedir.exists() )
+ if ( !Files.exists( basedir ) )
{
throw new IllegalStateException( "basedir " + basedir + " does not exist" );
}
- if ( !basedir.isDirectory() )
+ if ( !Files.isDirectory( basedir ) )
{
throw new IllegalStateException( "basedir " + basedir + " is not a directory" );
}
@@ -388,42 +408,164 @@ public class DirectoryScanner
setupDefaultFilters();
setupMatchPatterns();
- filesIncluded = new ArrayList<String>();
- filesNotIncluded = new ArrayList<String>();
- filesExcluded = new ArrayList<String>();
- dirsIncluded = new ArrayList<String>();
- dirsNotIncluded = new ArrayList<String>();
- dirsExcluded = new ArrayList<String>();
- scanAction = ScanConductor.ScanAction.CONTINUE;
+ if ( scanConductor instanceof ScannerAware ) // after the init
+ {
+ ( ( ScannerAware ) scanConductor ).setDirectoryScanner( this );
+ }
+
+ filesIncluded = new ArrayList<>();
+ filesNotIncluded = new ArrayList<>();
+ filesExcluded = new ArrayList<>();
+ dirsIncluded = new ArrayList<>();
+ dirsNotIncluded = new ArrayList<>();
+ dirsExcluded = new ArrayList<>();
+
+ doScan( basedir, followSymlinks ? EnumSet.of( FOLLOW_LINKS ) : EnumSet.noneOf( FileVisitOption.class ), true );
+ }
- if ( isIncluded( "" ) )
+ private void doScan( final Path root, final Set<FileVisitOption> options, final boolean fast )
+ {
+ try
{
- if ( !isExcluded( "" ) )
+ Files.walkFileTree( root, options, Integer.MAX_VALUE, new SimpleFileVisitor<Path>()
{
- if ( scanConductor != null )
+ @Override
+ public FileVisitResult preVisitDirectory( final Path dir, final BasicFileAttributes attrs )
+ throws IOException
+ {
+ final String name = root.relativize( dir ).toString();
+ if ( isIncluded( name ) )
+ {
+ if ( !isExcluded( name ) )
+ {
+ if ( scanConductor != null )
+ {
+ final ScanConductor.ScanAction scanAction = scanConductor.visitDirectory(
+ name, dir.toFile() );
+ if ( ScanConductor.ScanAction.ABORT.equals( scanAction )
+ || ScanConductor.ScanAction.ABORT_DIRECTORY.equals( scanAction ) )
+ {
+ return FileVisitResult.SKIP_SIBLINGS;
+ }
+ if ( ScanConductor.ScanAction.NO_RECURSE.equals( scanAction ) )
+ {
+ return FileVisitResult.SKIP_SUBTREE;
+ }
+ }
+ dirsIncluded.add( name );
+ }
+ else
+ {
+ dirsExcluded.add( name );
+ if ( fast && !couldHoldIncluded( name ) )
+ {
+ return FileVisitResult.SKIP_SUBTREE;
+ }
+ if ( scanConductor != null )
+ {
+ final FileVisitResult result = toVisitResult( dir, name );
+ if ( result != null )
+ {
+ return result;
+ }
+ }
+ // else continue to visit
+ }
+ }
+ else
+ {
+ if ( fast && couldHoldIncluded( name ) )
+ {
+ if ( scanConductor != null )
+ {
+ final FileVisitResult result = toVisitResult( dir, name );
+ if ( result != null )
+ {
+ return result;
+ }
+ }
+ dirsNotIncluded.add( name );
+ }
+ else if ( !fast )
+ {
+ final FileVisitResult result = toVisitResult( dir, name );
+ if ( result != null )
+ {
+ return result;
+ }
+ }
+ }
+ return super.preVisitDirectory( dir, attrs );
+ }
+
+ @Override
+ public FileVisitResult visitFile( final Path file, final BasicFileAttributes attrs ) throws IOException
{
- scanAction = scanConductor.visitDirectory( "", basedir );
+ final String name = root.relativize( file ).toString();
+ if ( !followSymlinks && Files.isSymbolicLink( file ) )
+ {
+ final Path resolved = file.toRealPath( );
+ if ( Files.isDirectory( resolved ) )
+ {
+ dirsIncluded.add( name );
+ return FileVisitResult.SKIP_SUBTREE;
+ }
+ }
+ if ( isIncluded( name ) )
+ {
+ if ( !isExcluded( name ) )
+ {
+ final ScanConductor.ScanAction scanAction;
+ if ( scanConductor != null )
+ {
+ scanAction = scanConductor.visitFile( name, file.toFile() );
+ }
+ else
+ {
+ scanAction = null;
+ }
+
+ if ( ScanConductor.ScanAction.ABORT.equals( scanAction )
+ || ScanConductor.ScanAction.ABORT_DIRECTORY.equals( scanAction ) )
+ {
+ return FileVisitResult.SKIP_SIBLINGS;
+ }
- if ( ScanConductor.ScanAction.ABORT.equals( scanAction )
- || ScanConductor.ScanAction.ABORT_DIRECTORY.equals( scanAction )
- || ScanConductor.ScanAction.NO_RECURSE.equals( scanAction ) )
+ filesIncluded.add( name );
+ }
+ else
+ {
+ filesExcluded.add( name );
+ }
+ }
+ else
{
- return;
+ filesNotIncluded.add( name );
}
+ return super.visitFile( file, attrs );
}
+ } );
+ }
+ catch ( final IOException e )
+ {
+ throw new IllegalStateException( e );
+ }
+ }
- dirsIncluded.add( "" );
- }
- else
- {
- dirsExcluded.add( "" );
- }
+ private FileVisitResult toVisitResult( final Path dir, final String name )
+ {
+ final ScanConductor.ScanAction scanAction = scanConductor.visitDirectory(
+ name, dir.toFile() );
+ if ( ScanConductor.ScanAction.ABORT.equals( scanAction )
+ || ScanConductor.ScanAction.ABORT_DIRECTORY.equals( scanAction ) )
+ {
+ return FileVisitResult.SKIP_SIBLINGS;
}
- else
+ if ( ScanConductor.ScanAction.NO_RECURSE.equals( scanAction ) )
{
- dirsNotIncluded.add( "" );
+ return FileVisitResult.SKIP_SUBTREE;
}
- scandir( basedir, "", true );
+ return null;
}
/**
@@ -523,15 +665,18 @@ public class DirectoryScanner
return;
}
- final String[] excl = dirsExcluded.toArray( new String[dirsExcluded.size()] );
+ final String[] excl = dirsExcluded.toArray( new String[ 0 ] );
+
+ final String[] notIncl = dirsNotIncluded.toArray( new String[ 0 ] );
- final String[] notIncl = dirsNotIncluded.toArray( new String[dirsNotIncluded.size()] );
+ final EnumSet<FileVisitOption> opts = followSymlinks
+ ? EnumSet.of( FOLLOW_LINKS ) : EnumSet.noneOf( FileVisitOption.class );
for ( String anExcl : excl )
{
if ( !couldHoldIncluded( anExcl ) )
{
- scandir( new File( basedir, anExcl ), anExcl + File.separator, false );
+ doScan( basedir.resolve( anExcl ), opts, false );
}
}
@@ -539,7 +684,7 @@ public class DirectoryScanner
{
if ( !couldHoldIncluded( aNotIncl ) )
{
- scandir( new File( basedir, aNotIncl ), aNotIncl + File.separator, false );
+ doScan( basedir.resolve( aNotIncl ), opts, false );
}
}
@@ -547,207 +692,6 @@ public class DirectoryScanner
}
/**
- * Scans the given directory for files and directories. Found files and directories are placed in their respective
- * collections, based on the matching of includes, excludes, and the selectors. When a directory is found, it is
- * scanned recursively.
- *
- * @param dir The directory to scan. Must not be <code>null</code>.
- * @param vpath The path relative to the base directory (needed to prevent problems with an absolute path when using
- * dir). Must not be <code>null</code>.
- * @param fast Whether or not this call is part of a fast scan.
- * @see #filesIncluded
- * @see #filesNotIncluded
- * @see #filesExcluded
- * @see #dirsIncluded
- * @see #dirsNotIncluded
- * @see #dirsExcluded
- * @see #slowScan
- */
- void scandir( @Nonnull final File dir, @Nonnull final String vpath, final boolean fast )
- {
- String[] newfiles = dir.list();
-
- if ( newfiles == null )
- {
- /*
- * two reasons are mentioned in the API docs for File.list (1) dir is not a directory. This is impossible as
- * we wouldn't get here in this case. (2) an IO error occurred (why doesn't it throw an exception then???)
- */
-
- /*
- * [jdcasey] (2) is apparently happening to me, as this is killing one of my tests... this is affecting the
- * assembly plugin, fwiw. I will initialize the newfiles array as zero-length for now. NOTE: I can't find
- * the problematic code, as it appears to come from a native method in UnixFileSystem...
- */
- newfiles = new String[0];
-
- // throw new IOException( "IO error scanning directory " + dir.getAbsolutePath() );
- }
-
- if ( !followSymlinks )
- {
- newfiles = doNotFollowSymbolicLinks( dir, vpath, newfiles );
- }
-
- for ( final String newfile : newfiles )
- {
- final String name = vpath + newfile;
- final File file = new File( dir, newfile );
- if ( file.isDirectory() )
- {
- if ( isIncluded( name ) )
- {
- if ( !isExcluded( name ) )
- {
- if ( scanConductor != null )
- {
- scanAction = scanConductor.visitDirectory( name, file );
-
- if ( ScanConductor.ScanAction.ABORT.equals( scanAction )
- || ScanConductor.ScanAction.ABORT_DIRECTORY.equals( scanAction ) )
- {
- return;
- }
- }
-
- if ( !ScanConductor.ScanAction.NO_RECURSE.equals( scanAction ) )
- {
- dirsIncluded.add( name );
- if ( fast )
- {
- scandir( file, name + File.separator, fast );
-
- if ( ScanConductor.ScanAction.ABORT.equals( scanAction ) )
- {
- return;
- }
- }
- }
- scanAction = null;
-
- }
- else
- {
- dirsExcluded.add( name );
- if ( fast && couldHoldIncluded( name ) )
- {
- scandir( file, name + File.separator, fast );
- if ( ScanConductor.ScanAction.ABORT.equals( scanAction ) )
- {
- return;
- }
- scanAction = null;
- }
- }
- }
- else
- {
- if ( fast && couldHoldIncluded( name ) )
- {
- if ( scanConductor != null )
- {
- scanAction = scanConductor.visitDirectory( name, file );
-
- if ( ScanConductor.ScanAction.ABORT.equals( scanAction )
- || ScanConductor.ScanAction.ABORT_DIRECTORY.equals( scanAction ) )
- {
- return;
- }
- }
- if ( !ScanConductor.ScanAction.NO_RECURSE.equals( scanAction ) )
- {
- dirsNotIncluded.add( name );
-
- scandir( file, name + File.separator, fast );
- if ( ScanConductor.ScanAction.ABORT.equals( scanAction ) )
- {
- return;
- }
- }
- scanAction = null;
- }
- }
- if ( !fast )
- {
- scandir( file, name + File.separator, fast );
- if ( ScanConductor.ScanAction.ABORT.equals( scanAction ) )
- {
- return;
- }
- scanAction = null;
- }
- }
- else if ( file.isFile() )
- {
- if ( isIncluded( name ) )
- {
- if ( !isExcluded( name ) )
- {
- if ( scanConductor != null )
- {
- scanAction = scanConductor.visitFile( name, file );
- }
-
- if ( ScanConductor.ScanAction.ABORT.equals( scanAction )
- || ScanConductor.ScanAction.ABORT_DIRECTORY.equals( scanAction ) )
- {
- return;
- }
-
- filesIncluded.add( name );
- }
- else
- {
- filesExcluded.add( name );
- }
- }
- else
- {
- filesNotIncluded.add( name );
- }
- }
- }
- }
-
- private String[] doNotFollowSymbolicLinks( final File dir, final String vpath, String[] newfiles )
- {
- final List<String> noLinks = new ArrayList<String>();
- for ( final String newfile : newfiles )
- {
- try
- {
- if ( isSymbolicLink( dir, newfile ) )
- {
- final String name = vpath + newfile;
- final File file = new File( dir, newfile );
- if ( file.isDirectory() )
- {
- dirsExcluded.add( name );
- }
- else
- {
- filesExcluded.add( name );
- }
- }
- else
- {
- noLinks.add( newfile );
- }
- }
- catch ( final IOException ioe )
- {
- final String msg =
- "IOException caught while checking " + "for links, couldn't get cannonical path!";
- // will be caught and redirected to Ant's logging system
- System.err.println( msg );
- noLinks.add( newfile );
- }
- }
- newfiles = noLinks.toArray( new String[noLinks.size()] );
- return newfiles;
- }
-
- /**
* Tests whether or not a name matches against at least one include pattern.
*
* @param name The name to match. Must not be <code>null</code>.
@@ -787,18 +731,16 @@ public class DirectoryScanner
* Returns the names of the files which matched at least one of the include patterns and none of the exclude
* patterns. The names are relative to the base directory.
*
- * @deprecated this method does not work correctly on Windows.
* @return the names of the files which matched at least one of the include patterns and none of the exclude
* patterns. May also contain symbolic links to files.
*/
- @Deprecated
public String[] getIncludedFiles()
{
if ( filesIncluded == null )
{
return new String[0];
}
- return filesIncluded.toArray( new String[filesIncluded.size()] );
+ return filesIncluded.toArray( new String[0] );
}
/**
@@ -891,21 +833,16 @@ public class DirectoryScanner
excludes = newExcludes;
}
- /**
- * Checks whether a given file is a symbolic link.
- * <p>
- * It doesn't really test for symbolic links but whether the canonical and absolute paths of the file are identical
- * - this may lead to false positives on some platforms.
- * </p>
- *
- * @param parent the parent directory of the file to test
- * @param name the name of the file to test.
- *
- */
- boolean isSymbolicLink( final File parent, final String name )
- throws IOException
+ public MatchPatterns getExcludesPatterns()
+ {
+ setupDefaultFilters();
+ return excludesPatterns;
+ }
+
+ public MatchPatterns getIncludesPatterns()
{
- return Files.isSymbolicLink( parent.toPath() );
+ setupDefaultFilters();
+ return includesPatterns;
}
private void setupDefaultFilters()
diff --git a/src/main/java/org/apache/maven/shared/utils/io/MatchPattern.java b/src/main/java/org/apache/maven/shared/utils/io/MatchPattern.java
index 8abff42..25b35a7 100644
--- a/src/main/java/org/apache/maven/shared/utils/io/MatchPattern.java
+++ b/src/main/java/org/apache/maven/shared/utils/io/MatchPattern.java
@@ -33,9 +33,7 @@ import javax.annotation.Nonnull;
* Significantly more efficient than using strings, since re-evaluation and re-tokenizing is avoided.
*
* @author Kristian Rosenvold
- * @deprecated use {@code java.nio.filejava.nio.file.DirectoryStream.Filter<T>} and related classes
*/
-@Deprecated
public class MatchPattern
{
private final String source;
diff --git a/src/main/java/org/apache/maven/shared/utils/io/MatchPatterns.java b/src/main/java/org/apache/maven/shared/utils/io/MatchPatterns.java
index 693acb1..8e59f48 100644
--- a/src/main/java/org/apache/maven/shared/utils/io/MatchPatterns.java
+++ b/src/main/java/org/apache/maven/shared/utils/io/MatchPatterns.java
@@ -27,9 +27,7 @@ import javax.annotation.Nonnull;
* A list of patterns to be matched
*
* @author Kristian Rosenvold
- * @deprecated use {@code java.nio.filejava.nio.file.DirectoryStream.Filter<T>} and related classes
*/
-@Deprecated
public class MatchPatterns
{
private final MatchPattern[] patterns;
diff --git a/src/main/java/org/apache/maven/shared/utils/io/ScanConductor.java b/src/main/java/org/apache/maven/shared/utils/io/ScanConductor.java
index 08d7a1c..20c9a10 100644
--- a/src/main/java/org/apache/maven/shared/utils/io/ScanConductor.java
+++ b/src/main/java/org/apache/maven/shared/utils/io/ScanConductor.java
@@ -34,9 +34,7 @@ import java.io.File;
*
* @author <a href="mailto:struberg@apache.org">Mark Struberg</a>
*
- * @deprecated use {@code java.nio.file.Files.walkFileTree()} and related classes
*/
-@Deprecated
public interface ScanConductor
{
/**
diff --git a/src/main/java/org/apache/maven/shared/utils/io/ScannerAware.java b/src/main/java/org/apache/maven/shared/utils/io/ScannerAware.java
new file mode 100644
index 0000000..8254203
--- /dev/null
+++ b/src/main/java/org/apache/maven/shared/utils/io/ScannerAware.java
@@ -0,0 +1,29 @@
+package org.apache.maven.shared.utils.io;
+
+/*
+ * 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.
+ */
+
+/**
+ * Enables a {@link ScanConductor} to access the {@link DirectoryScanner} configuration.
+ * It is set once the scanner is initialized.
+ */
+public interface ScannerAware
+{
+ void setDirectoryScanner( DirectoryScanner scanner );
+}
diff --git a/src/main/java/org/apache/maven/shared/utils/io/SelectorUtils.java b/src/main/java/org/apache/maven/shared/utils/io/SelectorUtils.java
index b716e7c..8f920da 100644
--- a/src/main/java/org/apache/maven/shared/utils/io/SelectorUtils.java
+++ b/src/main/java/org/apache/maven/shared/utils/io/SelectorUtils.java
@@ -38,10 +38,7 @@ import javax.annotation.Nonnull;
* <a href="mailto:ajkuiper@wxs.nl">ajkuiper@wxs.nl</a>
* @author Magesh Umasankar
* @author <a href="mailto:bruce@callenish.com">Bruce Atherton</a>
- *
- * @deprecated use {@code java.nio.file.Files.walkFileTree()} and related classes
*/
-@Deprecated
public final class SelectorUtils
{
diff --git a/src/main/java/org/apache/maven/shared/utils/io/conductor/EnforceExcludesOverIncludes.java b/src/main/java/org/apache/maven/shared/utils/io/conductor/EnforceExcludesOverIncludes.java
new file mode 100644
index 0000000..47406bf
--- /dev/null
+++ b/src/main/java/org/apache/maven/shared/utils/io/conductor/EnforceExcludesOverIncludes.java
@@ -0,0 +1,58 @@
+package org.apache.maven.shared.utils.io.conductor;
+
+/*
+ * 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.
+ */
+
+import org.apache.maven.shared.utils.io.DirectoryScanner;
+import org.apache.maven.shared.utils.io.MatchPatterns;
+import org.apache.maven.shared.utils.io.ScanConductor;
+import org.apache.maven.shared.utils.io.ScannerAware;
+
+import java.io.File;
+
+/**
+ * If an exclude is defined on a folder it will bypass the visit of the children
+ * even if some include can match children.
+ */
+public class EnforceExcludesOverIncludes implements ScanConductor, ScannerAware
+{
+ private MatchPatterns excludes;
+
+ @Override
+ public ScanAction visitDirectory( final String name, final File directory )
+ {
+ if ( excludes.matches( name, true ) )
+ {
+ return ScanAction.NO_RECURSE;
+ }
+ return ScanAction.CONTINUE;
+ }
+
+ @Override
+ public ScanAction visitFile( final String name, final File file )
+ {
+ return ScanAction.CONTINUE;
+ }
+
+ @Override
+ public void setDirectoryScanner( final DirectoryScanner scanner )
+ {
+ excludes = scanner.getExcludesPatterns();
+ }
+}
diff --git a/src/test/java/org/apache/maven/shared/utils/io/DirectoryScannerTest.java b/src/test/java/org/apache/maven/shared/utils/io/DirectoryScannerTest.java
index 0111006..4895639 100644
--- a/src/test/java/org/apache/maven/shared/utils/io/DirectoryScannerTest.java
+++ b/src/test/java/org/apache/maven/shared/utils/io/DirectoryScannerTest.java
@@ -162,7 +162,7 @@ public class DirectoryScannerTest
ds.scan();
List<String> included = Arrays.asList( ds.getIncludedFiles() );
assertAlwaysIncluded( included );
- assertEquals( 9, included.size() );
+ assertEquals( included.toString(), 9, included.size() );
List<String> includedDirs = Arrays.asList( ds.getIncludedDirectories() );
assertTrue( includedDirs.contains( "" ) );
assertTrue( includedDirs.contains( "aRegularDir" ) );
@@ -243,14 +243,6 @@ public class DirectoryScannerTest
/* expExclDirs */ NONE );
}
- public void testIsSymbolicLink()
- throws IOException
- {
- File file = new File( "." );
- DirectoryScanner ds = new DirectoryScanner();
- ds.isSymbolicLink( file, "abc" );
- }
-
/**
* Performs a scan and test for the given parameters if not null.
*/
diff --git a/src/test/java/org/apache/maven/shared/utils/io/conductor/EnforceExcludesOverIncludesTest.java b/src/test/java/org/apache/maven/shared/utils/io/conductor/EnforceExcludesOverIncludesTest.java
new file mode 100644
index 0000000..42259e5
--- /dev/null
+++ b/src/test/java/org/apache/maven/shared/utils/io/conductor/EnforceExcludesOverIncludesTest.java
@@ -0,0 +1,92 @@
+package org.apache.maven.shared.utils.io.conductor;
+
+/*
+ * 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.
+ */
+
+import org.apache.maven.shared.utils.io.DirectoryScanner;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import static java.util.Arrays.asList;
+import static java.util.Collections.singletonList;
+import static org.junit.Assert.assertEquals;
+
+public class EnforceExcludesOverIncludesTest
+{
+ @Rule
+ public final TemporaryFolder folder = new TemporaryFolder();
+
+ @Test
+ public void dontVisitChildren() throws IOException
+ {
+ createFakeFiles();
+
+ DirectoryScanner scanner = new DirectoryScanner();
+ scanner.setScanConductor(new EnforceExcludesOverIncludes());
+ scanner.setExcludes( "**/target/**" );
+ scanner.setIncludes( "**" ); // files in target will match include but our conductor will bypass them anyway
+ scanner.setBasedir( folder.getRoot() );
+ scanner.scan();
+ assertResultEquals( asList( "bar", "sub/other" ), scanner.getIncludedFiles() );
+ assertResultEquals( singletonList( "target" ), scanner.getExcludedDirectories() );
+ }
+
+ @Test // we don't set the conductor to ensure we have a "control" test to compare to the other
+ public void controlTest() throws IOException
+ {
+ createFakeFiles();
+
+ DirectoryScanner scanner = new DirectoryScanner();
+ scanner.setExcludes( "**/target/**" );
+ scanner.setIncludes( "**" ); // files in target will match include but our conductor will bypass them anyway
+ scanner.setBasedir( folder.getRoot() );
+ scanner.scan();
+ assertResultEquals( asList( "bar", "sub/other" ), scanner.getIncludedFiles() );
+ assertResultEquals( asList( "target", "target/nested" ), scanner.getExcludedDirectories() );
+ }
+
+ private void createFakeFiles() throws IOException
+ {
+ touch(new File(folder.getRoot(), "bar"));
+ touch(new File(folder.getRoot(), "sub/other"));
+ touch(new File(folder.getRoot(), "target/foo"));
+ touch(new File(folder.getRoot(), "target/nested/dummy"));
+ }
+
+ private void assertResultEquals( final List<String> expected, final String[] actual )
+ {
+ final List<String> actualList = new ArrayList<>( asList( actual ) );
+ Collections.sort( actualList );
+ assertEquals( expected, actualList );
+ }
+
+ private void touch( final File file ) throws IOException
+ {
+ file.getParentFile().mkdirs();
+ new FileWriter( file ).close();
+ }
+}