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

claude pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/creadur-rat.git


The following commit(s) were added to refs/heads/master by this push:
     new 1ab4e3c5 RAT-98: Add working directory to resolve relative document 
names (#424)
1ab4e3c5 is described below

commit 1ab4e3c51eb2f3a9de30f0bfb86cbf19c8b325d5
Author: Claude Warren <[email protected]>
AuthorDate: Mon Jan 27 22:58:33 2025 +0100

    RAT-98: Add working directory to resolve relative document names (#424)
    
    * added working directory
    
    * updated merge issues
    
    * fix for file delete on windows
    
    * Minor cleanups
    
    * Minor fixes
    
    * fixed deprecated messages
    
    ---------
    
    Co-authored-by: P. Ottlinger <[email protected]>
---
 .../it/resources/ReportTest/RAT_14/verify.groovy   |   2 +-
 .../main/java/org/apache/rat/OptionCollection.java |  34 +-
 .../src/main/java/org/apache/rat/Report.java       |   4 +-
 .../apache/rat/commandline/ArgumentContext.java    |  28 +-
 .../rat/config/exclusion/ExclusionProcessor.java   |   4 +-
 .../rat/config/exclusion/plexus/MatchPattern.java  |  10 +-
 .../rat/config/exclusion/plexus/MatchPatterns.java |  42 +--
 .../apache/rat/document/DocumentNameMatcher.java   | 386 +++++++++++++++++++--
 .../java/org/apache/rat/OptionCollectionTest.java  |  48 ++-
 .../src/test/java/org/apache/rat/ReporterTest.java |  19 +-
 .../java/org/apache/rat/commandline/ArgTests.java  |   3 +-
 .../rat/document/DocumentNameMatcherTest.java      |  93 +++++
 .../apache/rat/test/AbstractOptionsProvider.java   | 311 ++++++++++-------
 .../java/org/apache/rat/test/utils/Resources.java  |  29 +-
 .../java/org/apache/rat/testhelpers/XmlUtils.java  |  26 +-
 .../java/org/apache/rat/mp/AbstractRatMojo.java    |  36 +-
 .../java/org/apache/rat/mp/OptionMojoTest.java     | 109 ++----
 .../main/java/org/apache/rat/anttasks/Report.java  |   2 +-
 .../org/apache/rat/anttasks/ReportOptionTest.java  |  35 +-
 .../java/org/apache/rat/tools/Documentation.java   |   3 +-
 20 files changed, 870 insertions(+), 354 deletions(-)

diff --git a/apache-rat-core/src/it/resources/ReportTest/RAT_14/verify.groovy 
b/apache-rat-core/src/it/resources/ReportTest/RAT_14/verify.groovy
index 431b0b04..226394df 100644
--- a/apache-rat-core/src/it/resources/ReportTest/RAT_14/verify.groovy
+++ b/apache-rat-core/src/it/resources/ReportTest/RAT_14/verify.groovy
@@ -64,7 +64,7 @@ myArgs[1] = "UNAPPROVED:-1"
 myArgs[2] = "--"
 myArgs[3] = src.getAbsolutePath()
 
-ReportConfiguration configuration = OptionCollection.parseCommands(myArgs, { 
opts -> })
+ReportConfiguration configuration = OptionCollection.parseCommands(src, 
myArgs, { opts -> })
 assertNotNull(configuration)
 configuration.validate(DefaultLog.getInstance().&error)
 Reporter reporter = new Reporter(configuration)
diff --git a/apache-rat-core/src/main/java/org/apache/rat/OptionCollection.java 
b/apache-rat-core/src/main/java/org/apache/rat/OptionCollection.java
index dca321c9..7dc33fd8 100644
--- a/apache-rat-core/src/main/java/org/apache/rat/OptionCollection.java
+++ b/apache-rat-core/src/main/java/org/apache/rat/OptionCollection.java
@@ -132,25 +132,28 @@ public final class OptionCollection {
     /**
      * Parses the standard options to create a ReportConfiguration.
      *
+     * @param workingDirectory The directory to resolve relative file names 
against.
      * @param args the arguments to parse
      * @param helpCmd the help command to run when necessary.
      * @return a ReportConfiguration or null if Help was printed.
      * @throws IOException on error.
      */
-    public static ReportConfiguration parseCommands(final String[] args, final 
Consumer<Options> helpCmd) throws IOException {
-        return parseCommands(args, helpCmd, false);
+    public static ReportConfiguration parseCommands(final File 
workingDirectory, final String[] args, final Consumer<Options> helpCmd) throws 
IOException {
+        return parseCommands(workingDirectory, args, helpCmd, false);
     }
 
     /**
      * Parses the standard options to create a ReportConfiguration.
      *
+     * @param workingDirectory The directory to resolve relative file names 
against.
      * @param args the arguments to parse
      * @param helpCmd the help command to run when necessary.
      * @param noArgs If true then the commands do not need extra arguments
      * @return a ReportConfiguration or {@code null} if Help was printed.
      * @throws IOException on error.
      */
-    public static ReportConfiguration parseCommands(final String[] args, final 
Consumer<Options> helpCmd, final boolean noArgs) throws IOException {
+    public static ReportConfiguration parseCommands(final File 
workingDirectory, final String[] args,
+                                                    final Consumer<Options> 
helpCmd, final boolean noArgs) throws IOException {
         Options opts = buildOptions();
         CommandLine commandLine;
         try {
@@ -166,27 +169,23 @@ public final class OptionCollection {
 
         Arg.processLogLevel(commandLine);
 
+        ArgumentContext argumentContext = new 
ArgumentContext(workingDirectory, commandLine);
         if (commandLine.hasOption(HELP)) {
             helpCmd.accept(opts);
             return null;
         }
 
         if (commandLine.hasOption(HELP_LICENSES)) {
-            new Licenses(createConfiguration(commandLine), new 
PrintWriter(System.out)).printHelp();
-            return null;
-        }
-
-        if (commandLine.hasOption(HELP_LICENSES)) {
-            new Licenses(createConfiguration(commandLine), new 
PrintWriter(System.out)).printHelp();
+            new Licenses(createConfiguration(argumentContext), new 
PrintWriter(System.out)).printHelp();
             return null;
         }
 
         if (commandLine.hasOption(Arg.HELP_LICENSES.option())) {
-            new Licenses(createConfiguration(commandLine), new 
PrintWriter(System.out)).printHelp();
+            new Licenses(createConfiguration(argumentContext), new 
PrintWriter(System.out)).printHelp();
             return null;
         }
 
-        ReportConfiguration configuration = createConfiguration(commandLine);
+        ReportConfiguration configuration = 
createConfiguration(argumentContext);
         if (!noArgs && !configuration.hasSource()) {
             String msg = "No directories or files specified for scanning. Did 
you forget to close a multi-argument option?";
             DefaultLog.getInstance().error(msg);
@@ -201,14 +200,15 @@ public final class OptionCollection {
      * Create the report configuration.
      * Note: this method is package private for testing.
      * You probably want one of the {@code ParseCommands} methods.
-     * @param commandLine the parsed command line.
+     * @param argumentContext The context to execute in.
      * @return a ReportConfiguration
-     * @see #parseCommands(String[], Consumer)
-     * @see #parseCommands(String[], Consumer, boolean)
+     * @see #parseCommands(File, String[], Consumer)
+     * @see #parseCommands(File, String[], Consumer, boolean)
      */
-    static ReportConfiguration createConfiguration(final CommandLine 
commandLine) {
-        final ReportConfiguration configuration = new ReportConfiguration();
-        new ArgumentContext(configuration, commandLine).processArgs();
+    static ReportConfiguration createConfiguration(final ArgumentContext 
argumentContext) {
+        argumentContext.processArgs();
+        final ReportConfiguration configuration = 
argumentContext.getConfiguration();
+        final CommandLine commandLine = argumentContext.getCommandLine();
         if (Arg.DIR.isSelected()) {
             try {
                 
configuration.addSource(getReportable(commandLine.getParsedOptionValue(Arg.DIR.getSelected()),
 configuration));
diff --git a/apache-rat-core/src/main/java/org/apache/rat/Report.java 
b/apache-rat-core/src/main/java/org/apache/rat/Report.java
index 6a7847e5..ff4103c4 100644
--- a/apache-rat-core/src/main/java/org/apache/rat/Report.java
+++ b/apache-rat-core/src/main/java/org/apache/rat/Report.java
@@ -18,6 +18,8 @@
  */
 package org.apache.rat;
 
+import java.io.File;
+
 import org.apache.commons.cli.Options;
 import org.apache.rat.document.RatDocumentAnalysisException;
 import org.apache.rat.help.Help;
@@ -39,7 +41,7 @@ public final class Report {
      */
     public static void main(final String[] args) throws Exception {
         DefaultLog.getInstance().info(new VersionInfo().toString());
-        ReportConfiguration configuration = 
OptionCollection.parseCommands(args, Report::printUsage);
+        ReportConfiguration configuration = OptionCollection.parseCommands(new 
File("."), args, Report::printUsage);
         if (configuration != null) {
             configuration.validate(DefaultLog.getInstance()::error);
             Reporter reporter = new Reporter(configuration);
diff --git 
a/apache-rat-core/src/main/java/org/apache/rat/commandline/ArgumentContext.java 
b/apache-rat-core/src/main/java/org/apache/rat/commandline/ArgumentContext.java
index 31abfd07..ea672bc8 100644
--- 
a/apache-rat-core/src/main/java/org/apache/rat/commandline/ArgumentContext.java
+++ 
b/apache-rat-core/src/main/java/org/apache/rat/commandline/ArgumentContext.java
@@ -18,10 +18,13 @@
  */
 package org.apache.rat.commandline;
 
+import java.io.File;
+
 import org.apache.commons.cli.CommandLine;
 import org.apache.commons.cli.Option;
 import org.apache.commons.cli.ParseException;
 import org.apache.rat.ReportConfiguration;
+import org.apache.rat.document.DocumentName;
 import org.apache.rat.utils.DefaultLog;
 
 import static java.lang.String.format;
@@ -35,17 +38,30 @@ public class ArgumentContext {
     private final ReportConfiguration configuration;
     /** The command line that is building the configuration */
     private final CommandLine commandLine;
+    /** The directory from which relative file names will be resolved */
+    private final DocumentName workingDirectory;
 
     /**
-     * Constructor.
+     * Creates a context with the specified configuration.
+     * @param workingDirectory the directory from which relative file names 
will be resolved.
      * @param configuration The configuration that is being built.
      * @param commandLine The command line that is building the configuration.
      */
-    public ArgumentContext(final ReportConfiguration configuration, final 
CommandLine commandLine) {
+    public ArgumentContext(final File workingDirectory, final 
ReportConfiguration configuration, final CommandLine commandLine) {
+        this.workingDirectory = DocumentName.builder(workingDirectory).build();
         this.commandLine = commandLine;
         this.configuration = configuration;
     }
 
+    /**
+     * Creates a context with an empty configuration.
+     * @param workingDirectory The directory from which to resolve relative 
file names.
+     * @param commandLine The command line.
+     */
+    public ArgumentContext(final File workingDirectory, final CommandLine 
commandLine) {
+        this(workingDirectory, new ReportConfiguration(), commandLine);
+    }
+
     /**
      * Process the arguments specified in this context.
      */
@@ -69,6 +85,14 @@ public class ArgumentContext {
         return commandLine;
     }
 
+    /**
+     * Gets the directory name from which relative file names will be resolved.
+     * @return The directory name from which relative file names will be 
resolved.
+     */
+    public DocumentName getWorkingDirectory() {
+        return workingDirectory;
+    }
+
     /**
      * Logs a ParseException as a warning.
      * @param exception the parse exception to log
diff --git 
a/apache-rat-core/src/main/java/org/apache/rat/config/exclusion/ExclusionProcessor.java
 
b/apache-rat-core/src/main/java/org/apache/rat/config/exclusion/ExclusionProcessor.java
index e21432e0..c1de611f 100644
--- 
a/apache-rat-core/src/main/java/org/apache/rat/config/exclusion/ExclusionProcessor.java
+++ 
b/apache-rat-core/src/main/java/org/apache/rat/config/exclusion/ExclusionProcessor.java
@@ -255,10 +255,10 @@ public class ExclusionProcessor {
                     .addTo(new ArrayList<>());
 
             if (!incl.isEmpty()) {
-                inclMatchers.add(new DocumentNameMatcher("included patterns", 
MatchPatterns.from(incl), basedir));
+                inclMatchers.add(new DocumentNameMatcher("included patterns", 
MatchPatterns.from(basedir.getDirectorySeparator(), incl), basedir));
             }
             if (!excl.isEmpty()) {
-                exclMatchers.add(new DocumentNameMatcher("excluded patterns", 
MatchPatterns.from(excl), basedir));
+                exclMatchers.add(new DocumentNameMatcher("excluded patterns", 
MatchPatterns.from(basedir.getDirectorySeparator(), excl), basedir));
             }
 
             if (!includedPaths.isEmpty()) {
diff --git 
a/apache-rat-core/src/main/java/org/apache/rat/config/exclusion/plexus/MatchPattern.java
 
b/apache-rat-core/src/main/java/org/apache/rat/config/exclusion/plexus/MatchPattern.java
index c43836ec..b606136a 100644
--- 
a/apache-rat-core/src/main/java/org/apache/rat/config/exclusion/plexus/MatchPattern.java
+++ 
b/apache-rat-core/src/main/java/org/apache/rat/config/exclusion/plexus/MatchPattern.java
@@ -19,6 +19,7 @@ package org.apache.rat.config.exclusion.plexus;
 
 import java.io.File;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.StringTokenizer;
 
@@ -49,7 +50,7 @@ public final class MatchPattern {
 
     private final char[][] tokenizedChar;
 
-    private MatchPattern(final String source, final String separator) {
+    public MatchPattern(final String source, final String separator) {
         regexPattern = SelectorUtils.isRegexPrefixedPattern(source)
                 ? source.substring(
                 SelectorUtils.REGEX_HANDLER_PREFIX.length(),
@@ -83,7 +84,7 @@ public final class MatchPattern {
         } else {
             result = 
SelectorUtils.matchAntPathPattern(getTokenizedPathChars(), strDirs, 
isCaseSensitive);
         }
-        if (result && DefaultLog.getInstance().isEnabled(Log.Level.DEBUG)) {
+        if (DefaultLog.getInstance().isEnabled(Log.Level.DEBUG)) {
             DefaultLog.getInstance().debug(format("%s match %s -> %s", this, 
str, result));
         }
         return result;
@@ -116,7 +117,7 @@ public final class MatchPattern {
 
     @Override
     public String toString() {
-        return source;
+        return Arrays.asList(tokenized).toString();
     }
 
     public String source() {
@@ -141,7 +142,4 @@ public final class MatchPattern {
         return tokenizedNameChar;
     }
 
-    public static MatchPattern fromString(final     String source) {
-        return new MatchPattern(source, File.separator);
-    }
 }
diff --git 
a/apache-rat-core/src/main/java/org/apache/rat/config/exclusion/plexus/MatchPatterns.java
 
b/apache-rat-core/src/main/java/org/apache/rat/config/exclusion/plexus/MatchPatterns.java
index 454ba304..06c4672d 100644
--- 
a/apache-rat-core/src/main/java/org/apache/rat/config/exclusion/plexus/MatchPatterns.java
+++ 
b/apache-rat-core/src/main/java/org/apache/rat/config/exclusion/plexus/MatchPatterns.java
@@ -22,8 +22,9 @@ package org.apache.rat.config.exclusion.plexus;
 
 import java.io.File;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
-import java.util.function.Predicate;
+import java.util.stream.Collectors;
 
 @SuppressWarnings({"checkstyle:RegexpSingleLine", 
"checkstyle:JavadocVariable"})
 /**
@@ -41,15 +42,15 @@ public final class MatchPatterns {
 
     @Override
     public String toString() {
-        return source();
+        return 
Arrays.stream(patterns).map(MatchPattern::toString).collect(Collectors.toList()).toString();
     }
 
     public String source() {
-        List<String> sources = new ArrayList<>();
-        for (MatchPattern pattern : patterns) {
-            sources.add(pattern.source());
-        }
-        return "[" + String.join(", ", sources) + "]";
+        return 
Arrays.stream(patterns).map(MatchPattern::source).collect(Collectors.toList()).toString();
+    }
+
+    public Iterable<MatchPattern> patterns() {
+        return Arrays.asList(patterns);
     }
 
     /**
@@ -58,7 +59,7 @@ public final class MatchPatterns {
      * <p>Uses far less string tokenization than any of the alternatives.</p>
      *
      * @param name The name to look for
-     * @param isCaseSensitive If the comparison is case sensitive
+     * @param isCaseSensitive If the comparison is case-sensitive
      * @return true if any of the supplied patterns match
      */
     public boolean matches(final String name, final boolean isCaseSensitive) {
@@ -83,36 +84,23 @@ public final class MatchPatterns {
         return false;
     }
 
-    public Predicate<String> asPredicate(final boolean isCaseSensitive) {
-        return name -> matches(name, isCaseSensitive);
-    }
-
-    public boolean matchesPatternStart(final String name, final boolean 
isCaseSensitive) {
-        for (MatchPattern includesPattern : patterns) {
-            if (includesPattern.matchPatternStart(name, isCaseSensitive)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    public static MatchPatterns from(final String... sources) {
+    public static MatchPatterns from(final String separator, final String... 
sources) {
         final int length = sources.length;
         MatchPattern[] result = new MatchPattern[length];
         for (int i = 0; i < length; i++) {
-            result[i] = MatchPattern.fromString(sources[i]);
+            result[i] = new MatchPattern(sources[i], separator);
         }
         return new MatchPatterns(result);
     }
 
-    public static MatchPatterns from(final Iterable<String> strings) {
-        return new MatchPatterns(getMatchPatterns(strings));
+    public static MatchPatterns from(final String separator, final 
Iterable<String> strings) {
+        return new MatchPatterns(getMatchPatterns(separator, strings));
     }
 
-    private static MatchPattern[] getMatchPatterns(final Iterable<String> 
items) {
+    private static MatchPattern[] getMatchPatterns(final String separator, 
final Iterable<String> items) {
         List<MatchPattern> result = new ArrayList<>();
         for (String string : items) {
-            result.add(MatchPattern.fromString(string));
+            result.add(new MatchPattern(string, separator));
         }
         return result.toArray(new MatchPattern[0]);
     }
diff --git 
a/apache-rat-core/src/main/java/org/apache/rat/document/DocumentNameMatcher.java
 
b/apache-rat-core/src/main/java/org/apache/rat/document/DocumentNameMatcher.java
index 30bbbbed..80fe275e 100644
--- 
a/apache-rat-core/src/main/java/org/apache/rat/document/DocumentNameMatcher.java
+++ 
b/apache-rat-core/src/main/java/org/apache/rat/document/DocumentNameMatcher.java
@@ -23,9 +23,14 @@ import java.io.FileFilter;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
 import java.util.List;
+import java.util.Optional;
+import java.util.Set;
 import java.util.function.Predicate;
 
+import org.apache.rat.ConfigurationException;
 import org.apache.rat.config.exclusion.plexus.MatchPattern;
 import org.apache.rat.config.exclusion.plexus.MatchPatterns;
 
@@ -40,6 +45,8 @@ public final class DocumentNameMatcher {
     private final Predicate<DocumentName> predicate;
     /** The name of this matcher. */
     private final String name;
+    /** {@code true} if this matcher is a collection of matchers. */
+    private final boolean isCollection;
 
     /**
      * A matcher that matches all documents.
@@ -59,6 +66,7 @@ public final class DocumentNameMatcher {
     public DocumentNameMatcher(final String name, final 
Predicate<DocumentName> predicate) {
         this.name = name;
         this.predicate = predicate;
+        this.isCollection = predicate instanceof CollectionPredicateImpl;
     }
 
     /**
@@ -77,9 +85,22 @@ public final class DocumentNameMatcher {
      * @param basedir the base directory for the scanning.
      */
     public DocumentNameMatcher(final String name, final MatchPatterns 
patterns, final DocumentName basedir) {
-        this(name, (Predicate<DocumentName>) documentName -> 
patterns.matches(documentName.getName(),
-                MatchPattern.tokenizePathToString(documentName.getName(), 
basedir.getDirectorySeparator()),
-                basedir.isCaseSensitive()));
+        this(name, new MatchPatternsPredicate(basedir, patterns));
+    }
+
+    /**
+     * Tokenizes name for faster Matcher processing.
+     * @param name the name to tokenize
+     * @param dirSeparator the directory separator
+     * @return the tokenized name.
+     */
+    private static char[][] tokenize(final String name, final String 
dirSeparator) {
+        String[] tokenizedName = MatchPattern.tokenizePathToString(name, 
dirSeparator);
+        char[][] tokenizedNameChar = new char[tokenizedName.length][];
+        for (int i = 0; i < tokenizedName.length; i++) {
+            tokenizedNameChar[i] = tokenizedName[i].toCharArray();
+        }
+        return tokenizedNameChar;
     }
 
     /**
@@ -88,7 +109,20 @@ public final class DocumentNameMatcher {
      * @param matchers fully specified matchers.
      */
     public DocumentNameMatcher(final String name, final MatchPatterns 
matchers) {
-        this(name, (Predicate<DocumentName>) documentName -> 
matchers.matches(documentName.getName(), documentName.isCaseSensitive()));
+        this(name, new CollectionPredicate() {
+            @Override
+            public Iterable<DocumentNameMatcher> getMatchers() {
+                final List<DocumentNameMatcher> result = new ArrayList<>();
+                matchers.patterns().forEach(p -> result.add(new 
DocumentNameMatcher(p.source(),
+                        (Predicate<DocumentName>) x -> MatchPatterns.from("/", 
p.source()).matches(x.getName(), x.isCaseSensitive()))));
+                return result;
+            }
+
+            @Override
+            public boolean test(final DocumentName documentName) {
+                return matchers.matches(documentName.getName(), 
documentName.isCaseSensitive());
+            }
+        });
     }
 
     /**
@@ -97,7 +131,7 @@ public final class DocumentNameMatcher {
      * @param fileFilter the file filter to execute.
      */
     public DocumentNameMatcher(final String name, final FileFilter fileFilter) 
{
-        this(name, (Predicate<DocumentName>) documentName -> 
fileFilter.accept(new File(documentName.getName())));
+        this(name, new FileFilterPredicate(fileFilter));
     }
 
     /**
@@ -108,11 +142,39 @@ public final class DocumentNameMatcher {
         this(fileFilter.toString(), fileFilter);
     }
 
+    public boolean isCollection() {
+        return isCollection;
+    }
+
+    /**
+     * Returns the predicate that this DocumentNameMatcher is using.
+     * @return The predicate that this DocumentNameMatcher is using.
+     */
+    public Predicate<DocumentName> getPredicate() {
+        return predicate;
+    }
+
     @Override
     public String toString() {
         return name;
     }
 
+    /**
+     * Decomposes the matcher execution against the candidate.
+     * @param candidate the candidate to check.
+     * @return a list of {@link DecomposeData} for each evaluation in the 
matcher.
+     */
+    public List<DecomposeData> decompose(final DocumentName candidate) {
+        final List<DecomposeData> result = new ArrayList<>();
+        decompose(0, this, candidate, result);
+        return result;
+    }
+
+    private void decompose(final int level, final DocumentNameMatcher matcher, 
final DocumentName candidate, final List<DecomposeData> result) {
+        final Predicate<DocumentName> pred = matcher.getPredicate();
+        result.add(new DecomposeData(level, matcher, candidate, 
pred.test(candidate)));
+    }
+
     /**
      * Performs the match against the DocumentName.
      * @param documentName the document name to check.
@@ -135,8 +197,7 @@ public final class DocumentNameMatcher {
             return MATCHES_ALL;
         }
 
-        return new DocumentNameMatcher(format("not(%s)", nameMatcher),
-                (Predicate<DocumentName>) documentName -> 
!nameMatcher.matches(documentName));
+        return new DocumentNameMatcher(format("not(%s)", nameMatcher), new 
NotPredicate(nameMatcher));
     }
 
     /**
@@ -150,30 +211,43 @@ public final class DocumentNameMatcher {
         return String.join(", ", children);
     }
 
+    private static Optional<DocumentNameMatcher> standardCollectionCheck(final 
Collection<DocumentNameMatcher> matchers,
+                                                                         final 
DocumentNameMatcher override) {
+        if (matchers.isEmpty()) {
+            throw new ConfigurationException("Empty matcher collection");
+        }
+        if (matchers.size() == 1) {
+            return Optional.of(matchers.iterator().next());
+        }
+        if (matchers.contains(override)) {
+            return Optional.of(override);
+        }
+        return Optional.empty();
+    }
+
     /**
      * Performs a logical {@code OR} across the collection of matchers.
      * @param matchers the matchers to check.
      * @return a matcher that returns {@code true} if any of the enclosed 
matchers returns {@code true}.
      */
     public static DocumentNameMatcher or(final Collection<DocumentNameMatcher> 
matchers) {
-        if (matchers.isEmpty()) {
-            return MATCHES_NONE;
-        }
-        if (matchers.size() == 1) {
-            return matchers.iterator().next();
-        }
-        if (matchers.contains(MATCHES_ALL)) {
-            return MATCHES_ALL;
+        Optional<DocumentNameMatcher> opt = standardCollectionCheck(matchers, 
MATCHES_ALL);
+        if (opt.isPresent()) {
+            return opt.get();
         }
 
-        return new DocumentNameMatcher(format("or(%s)", join(matchers)), 
(Predicate<DocumentName>) documentName -> {
-                for (DocumentNameMatcher matcher : matchers) {
-                    if (matcher.matches(documentName)) {
-                        return true;
-                    }
-                }
-                return false;
-            });
+        // preserve order
+        Set<DocumentNameMatcher> workingSet = new LinkedHashSet<>();
+        for (DocumentNameMatcher matcher : matchers) {
+            // check for nested or
+            if (matcher.predicate instanceof Or) {
+                ((Or) 
matcher.predicate).getMatchers().forEach(workingSet::add);
+            } else {
+                workingSet.add(matcher);
+            }
+        }
+        return standardCollectionCheck(matchers, MATCHES_ALL)
+                .orElseGet(() -> new DocumentNameMatcher(format("or(%s)", 
join(workingSet)), new Or(workingSet)));
     }
 
     /**
@@ -191,24 +265,45 @@ public final class DocumentNameMatcher {
      * @return a matcher that returns {@code true} if all the enclosed 
matchers return {@code true}.
      */
     public static DocumentNameMatcher and(final 
Collection<DocumentNameMatcher> matchers) {
-        if (matchers.isEmpty()) {
-            return MATCHES_NONE;
-        }
-        if (matchers.size() == 1) {
-            return matchers.iterator().next();
+        Optional<DocumentNameMatcher> opt = standardCollectionCheck(matchers, 
MATCHES_NONE);
+        if (opt.isPresent()) {
+            return opt.get();
         }
-        if (matchers.contains(MATCHES_NONE)) {
-            return MATCHES_NONE;
+
+        // preserve order
+        Set<DocumentNameMatcher> workingSet = new LinkedHashSet<>();
+        for (DocumentNameMatcher matcher : matchers) {
+            //  check for nexted And
+            if (matcher.predicate instanceof And) {
+                ((And) 
matcher.predicate).getMatchers().forEach(workingSet::add);
+            } else {
+                workingSet.add(matcher);
+            }
         }
+        opt = standardCollectionCheck(matchers, MATCHES_NONE);
+        return opt.orElseGet(() -> new DocumentNameMatcher(format("and(%s)", 
join(workingSet)), new And(workingSet)));
+    }
 
-        return new DocumentNameMatcher(format("and(%s)", join(matchers)), 
(Predicate<DocumentName>) documentName -> {
-                for (DocumentNameMatcher matcher : matchers) {
-                if (!matcher.matches(documentName)) {
-                    return false;
-                }
+    /**
+     * A particular matcher that will not match any excluded unless they are 
listed in the includes.
+     * @param includes the DocumentNameMatcher to match the includes.
+     * @param excludes the DocumentNameMatcher to match the excludes.
+     * @return a DocumentNameMatcher with the specified logic.
+     */
+    public static DocumentNameMatcher matcherSet(final DocumentNameMatcher 
includes,
+                                                 final DocumentNameMatcher 
excludes) {
+        if (excludes == MATCHES_NONE) {
+            return MATCHES_ALL;
+        } else {
+            if (includes == MATCHES_NONE) {
+                return not(excludes);
             }
-                return true;
-        });
+        }
+        if (includes == MATCHES_ALL) {
+            return MATCHES_ALL;
+        }
+        List<DocumentNameMatcher> workingSet = Arrays.asList(includes, 
excludes);
+        return new DocumentNameMatcher(format("matcherSet(%s)", 
join(workingSet)), new MatcherPredicate(workingSet));
     }
 
     /**
@@ -219,4 +314,221 @@ public final class DocumentNameMatcher {
     public static DocumentNameMatcher and(final DocumentNameMatcher... 
matchers) {
         return and(Arrays.asList(matchers));
     }
+
+    /**
+     * A DocumentName predicate that uses MatchPatterns.
+     */
+    public static final class MatchPatternsPredicate implements 
Predicate<DocumentName> {
+        /** The base directory for the pattern matches */
+        private final DocumentName basedir;
+        /** The pattern matchers */
+        private final MatchPatterns patterns;
+
+        private MatchPatternsPredicate(final DocumentName basedir, final 
MatchPatterns patterns) {
+            this.basedir = basedir;
+            this.patterns = patterns;
+        }
+
+        @Override
+        public boolean test(final DocumentName documentName) {
+            return patterns.matches(documentName.getName(),
+                    tokenize(documentName.getName(), 
basedir.getDirectorySeparator()),
+                    basedir.isCaseSensitive());
+        }
+
+        @Override
+        public String toString() {
+            return patterns.toString();
+        }
+    }
+
+    /**
+     * A DocumentName predicate that reverses another DocumentNameMatcher.
+     */
+    public static final class NotPredicate implements Predicate<DocumentName> {
+        /** The document name matcher to reverse */
+        private final DocumentNameMatcher nameMatcher;
+
+        private NotPredicate(final DocumentNameMatcher nameMatcher) {
+            this.nameMatcher = nameMatcher;
+        }
+
+        @Override
+        public boolean test(final DocumentName documentName) {
+            return !nameMatcher.matches(documentName);
+        }
+
+        @Override
+        public String toString() {
+            return nameMatcher.predicate.toString();
+        }
+    }
+
+    /**
+     * A DocumentName predicate that uses FileFilter.
+     */
+    public static final class FileFilterPredicate implements 
Predicate<DocumentName> {
+        /** The file filter */
+        private final FileFilter fileFilter;
+
+        private FileFilterPredicate(final FileFilter fileFilter) {
+            this.fileFilter = fileFilter;
+        }
+
+        @Override
+        public boolean test(final DocumentName documentName) {
+            return fileFilter.accept(new File(documentName.getName()));
+        }
+
+        @Override
+        public String toString() {
+            return fileFilter.toString();
+        }
+    }
+
+    interface CollectionPredicate extends Predicate<DocumentName> {
+        Iterable<DocumentNameMatcher> getMatchers();
+    }
+    /**
+     * A marker interface to indicate this predicate contains a collection of 
matchers.
+     */
+    abstract static class CollectionPredicateImpl implements 
CollectionPredicate {
+        /** The collection for matchers that make up this predicate */
+        private final Iterable<DocumentNameMatcher> matchers;
+
+        /**
+         * Constructs a collection predicate from the collection of matchers.
+         * @param matchers the collection of matchers to use.
+         */
+        protected CollectionPredicateImpl(final Iterable<DocumentNameMatcher> 
matchers) {
+            this.matchers = matchers;
+        }
+
+        /**
+         * Gets the internal matchers.
+         * @return an iterable over the internal matchers.
+         */
+        public Iterable<DocumentNameMatcher> getMatchers() {
+            return matchers;
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder builder = new 
StringBuilder(this.getClass().getName()).append(": 
").append(System.lineSeparator());
+            for (DocumentNameMatcher matcher : matchers) {
+                
builder.append(matcher.predicate.toString()).append(System.lineSeparator());
+            }
+            return builder.toString();
+        }
+    }
+
+    /**
+     * An implementation of "and" logic across a collection of 
DocumentNameMatchers.
+     */
+    // package private for testing access
+    static class And extends CollectionPredicateImpl {
+        And(final Iterable<DocumentNameMatcher> matchers) {
+            super(matchers);
+        }
+
+        @Override
+        public boolean test(final DocumentName documentName) {
+            for (DocumentNameMatcher matcher : getMatchers()) {
+                if (!matcher.matches(documentName)) {
+                    return false;
+                }
+            }
+            return true;
+        }
+    }
+
+    /**
+     * An implementation of "or" logic across a collection of 
DocumentNameMatchers.
+     */
+    // package private for testing access
+    static class Or extends CollectionPredicateImpl {
+        Or(final Iterable<DocumentNameMatcher> matchers) {
+            super(matchers);
+        }
+
+        @Override
+        public boolean test(final DocumentName documentName) {
+            for (DocumentNameMatcher matcher : getMatchers()) {
+                if (matcher.matches(documentName)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
+    /**
+     * An implementation of "or" logic across a collection of 
DocumentNameMatchers.
+     */
+    // package private for testing access
+    static class MatcherPredicate extends CollectionPredicateImpl {
+        MatcherPredicate(final Iterable<DocumentNameMatcher> matchers) {
+            super(matchers);
+        }
+
+        @Override
+        public boolean test(final DocumentName documentName) {
+            Iterator<DocumentNameMatcher> iter = getMatchers().iterator();
+            // included
+            if (iter.next().matches(documentName)) {
+                return true;
+            }
+            // excluded
+            return !iter.next().matches(documentName);
+        }
+    }
+
+    /**
+     * Data from a {@link DocumentNameMatcher#decompose(DocumentName)} call.
+     */
+    public static final class DecomposeData {
+        /** The level this data was generated at */
+        private final int level;
+        /** The name of the DocumentNameMatcher that created this result */
+        private final DocumentNameMatcher matcher;
+        /** The result of the check. */
+        private final boolean result;
+        /** The candidate */
+        private final DocumentName candidate;
+
+        private DecomposeData(final int level, final DocumentNameMatcher 
matcher, final DocumentName candidate, final boolean result) {
+            this.level = level;
+            this.matcher = matcher;
+            this.result = result;
+            this.candidate = candidate;
+        }
+
+        @Override
+        public String toString() {
+            final String fill = createFill(level);
+            return format("%s%s: >>%s<< %s%n%s",
+                    fill, matcher.toString(), result,
+                    level == 0 ? candidate.getName() : "",
+                    matcher.predicate instanceof CollectionPredicate ?
+                            decompose(level + 1, (CollectionPredicate) 
matcher.predicate, candidate) :
+                    String.format("%s%s >>%s<<", createFill(level + 1), 
matcher.predicate.toString(), matcher.predicate.test(candidate)));
+        }
+
+        private String createFill(final int level) {
+            final char[] chars = new char[level * 2];
+            Arrays.fill(chars, ' ');
+            return new String(chars);
+        }
+
+        private String decompose(final int level, final CollectionPredicate 
predicate, final DocumentName candidate) {
+            List<DecomposeData> result = new ArrayList<>();
+
+            for (DocumentNameMatcher nameMatcher : predicate.getMatchers()) {
+                nameMatcher.decompose(level, nameMatcher, candidate, result);
+            }
+            StringBuilder sb = new StringBuilder();
+            result.forEach(x -> sb.append(x).append(System.lineSeparator()));
+            return sb.toString();
+        }
+    }
 }
diff --git 
a/apache-rat-core/src/test/java/org/apache/rat/OptionCollectionTest.java 
b/apache-rat-core/src/test/java/org/apache/rat/OptionCollectionTest.java
index ab1ce763..f723e5fd 100644
--- a/apache-rat-core/src/test/java/org/apache/rat/OptionCollectionTest.java
+++ b/apache-rat-core/src/test/java/org/apache/rat/OptionCollectionTest.java
@@ -18,21 +18,25 @@
  */
 package org.apache.rat;
 
+import java.nio.file.Path;
 import org.apache.commons.cli.CommandLine;
 import org.apache.commons.cli.DefaultParser;
 import org.apache.commons.cli.Option;
 import org.apache.commons.cli.ParseException;
 import org.apache.commons.lang3.tuple.Pair;
+import org.apache.rat.commandline.ArgumentContext;
 import org.apache.rat.license.LicenseSetFactory;
 import org.apache.rat.report.IReportable;
 import org.apache.rat.test.AbstractOptionsProvider;
 import org.apache.rat.testhelpers.TestingLog;
 import org.apache.rat.utils.DefaultLog;
 import org.apache.rat.utils.Log;
+import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.condition.EnabledOnOs;
 import org.junit.jupiter.api.condition.OS;
+import org.junit.jupiter.api.io.TempDir;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.ArgumentsProvider;
 import org.junit.jupiter.params.provider.ArgumentsSource;
@@ -47,14 +51,19 @@ import java.util.concurrent.atomic.AtomicBoolean;
 
 import static java.lang.String.format;
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.junit.jupiter.api.Assertions.assertNull;
-import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.junit.jupiter.api.Assertions.fail;
 
 public class OptionCollectionTest {
 
+    @TempDir
+    static Path testPath;
+
+    @AfterAll
+    static void preserveData() {
+        AbstractOptionsProvider.preserveData(testPath.toFile(), "optionTest");
+    }
+
     /**
      * The base directory for the test.
      * We do not use TempFile because we want the evidence of the run
@@ -103,7 +112,7 @@ public class OptionCollectionTest {
         try {
             DefaultLog.setInstance(log);
             String[] args = {"--dir", "target", "-a"};
-            ReportConfiguration config = OptionCollection.parseCommands(args, 
o -> fail("Help printed"), true);
+            ReportConfiguration config = 
OptionCollection.parseCommands(testPath.toFile(), args, o -> fail("Help 
printed"), true);
             assertThat(config).isNotNull();
         } finally {
             DefaultLog.setInstance(null);
@@ -119,7 +128,7 @@ public class OptionCollectionTest {
         try {
             DefaultLog.setInstance(log);
             String[] args = {"--dir", baseDir.getAbsolutePath()};
-            config = OptionCollection.parseCommands(args, (o) -> {
+            config = OptionCollection.parseCommands(testPath.toFile(), args, 
(o) -> {
             }, true);
         } finally {
             DefaultLog.setInstance(null);
@@ -131,7 +140,7 @@ public class OptionCollectionTest {
     @Test
     public void testShortenedOptions() throws IOException {
         String[] args = {"--output-lic", "ALL"};
-        ReportConfiguration config = OptionCollection.parseCommands(args, (o) 
-> {
+        ReportConfiguration config = 
OptionCollection.parseCommands(testPath.toFile(), args, (o) -> {
         }, true);
         assertThat(config).isNotNull();
         
assertThat(config.listLicenses()).isEqualTo(LicenseSetFactory.LicenseFilter.ALL);
@@ -141,7 +150,8 @@ public class OptionCollectionTest {
     public void testDefaultConfiguration() throws ParseException {
         String[] empty = {};
         CommandLine cl = new 
DefaultParser().parse(OptionCollection.buildOptions(), empty);
-        ReportConfiguration config = OptionCollection.createConfiguration(cl);
+        ArgumentContext context = new ArgumentContext(new File("."), cl);
+        ReportConfiguration config = 
OptionCollection.createConfiguration(context);
         ReportConfigurationTest.validateDefault(config);
     }
 
@@ -149,7 +159,7 @@ public class OptionCollectionTest {
     @ValueSource(strings = { ".", "./", "target", "./target" })
     public void getReportableTest(String fName) throws IOException {
         File expected = new File(fName);
-        ReportConfiguration config = OptionCollection.parseCommands(new 
String[]{fName}, o -> fail("Help called"), false);
+        ReportConfiguration config = 
OptionCollection.parseCommands(testPath.toFile(), new String[]{fName}, o -> 
fail("Help called"), false);
         IReportable reportable = OptionCollection.getReportable(expected, 
config);
         assertNotNull(reportable, () -> format("'%s' returned null", fName));
         
assertThat(reportable.getName().getName()).isEqualTo(expected.getAbsolutePath());
@@ -160,8 +170,8 @@ public class OptionCollectionTest {
      * @param name The name of the test.
      * @param test the option test to execute.
      */
-    @ParameterizedTest
-    @ArgumentsSource(OptionsProvider.class)
+    @ParameterizedTest( name = "{index} {0}")
+    @ArgumentsSource(CliOptionsProvider.class)
     public void testOptionsUpdateConfig(String name, OptionTest test) {
         DefaultLog.getInstance().log(Log.Level.INFO, "Running test for: " + 
name);
         test.test();
@@ -170,7 +180,7 @@ public class OptionCollectionTest {
     /**
      * A class to provide the Options and tests to the testOptionsUpdateConfig.
      */
-    static class OptionsProvider extends AbstractOptionsProvider implements 
ArgumentsProvider {
+    static class CliOptionsProvider extends AbstractOptionsProvider implements 
ArgumentsProvider {
 
         /** A flag to determine if help was called */
         final AtomicBoolean helpCalled = new AtomicBoolean(false);
@@ -179,9 +189,9 @@ public class OptionCollectionTest {
         public void helpTest() {
             String[] args = {longOpt(OptionCollection.HELP)};
             try {
-                ReportConfiguration config = 
OptionCollection.parseCommands(args, o -> helpCalled.set(true), true);
-                assertNull(config, "Should not have config");
-                assertTrue(helpCalled.get(), "Help was not called");
+                ReportConfiguration config = 
OptionCollection.parseCommands(testPath.toFile(), args, o -> 
helpCalled.set(true), true);
+                assertThat(config).as("Should not have config").isNull();
+                assertThat(helpCalled.get()).as("Help was not 
called").isTrue();
             } catch (IOException e) {
                 fail(e.getMessage());
             }
@@ -190,8 +200,8 @@ public class OptionCollectionTest {
         /**
          * Constructor. Sets the baseDir and loads the testMap.
          */
-        public OptionsProvider() {
-            super(Collections.emptyList());
+        public CliOptionsProvider() {
+            super(Collections.emptyList(), testPath.toFile());
         }
 
         /**
@@ -201,7 +211,7 @@ public class OptionCollectionTest {
          * @return A ReportConfiguration
          * @throws IOException on critical error.
          */
-        protected ReportConfiguration generateConfig(Pair<Option, String[]>... 
args) throws IOException {
+        protected final ReportConfiguration generateConfig(List<Pair<Option, 
String[]>> args) throws IOException {
             helpCalled.set(false);
             List<String> sArgs = new ArrayList<>();
             for (Pair<Option, String[]> pair : args) {
@@ -213,8 +223,8 @@ public class OptionCollectionTest {
                     }
                 }
             }
-            ReportConfiguration config = 
OptionCollection.parseCommands(sArgs.toArray(new String[0]), o -> 
helpCalled.set(true), true);
-            assertFalse(helpCalled.get(), "Help was called");
+            ReportConfiguration config = 
OptionCollection.parseCommands(testPath.toFile(), sArgs.toArray(new String[0]), 
o -> helpCalled.set(true), true);
+            assertThat(helpCalled.get()).as("Help was called").isFalse();
             return config;
         }
     }
diff --git a/apache-rat-core/src/test/java/org/apache/rat/ReporterTest.java 
b/apache-rat-core/src/test/java/org/apache/rat/ReporterTest.java
index 72517442..4d6b5ab3 100644
--- a/apache-rat-core/src/test/java/org/apache/rat/ReporterTest.java
+++ b/apache-rat-core/src/test/java/org/apache/rat/ReporterTest.java
@@ -18,11 +18,9 @@
  */
 package org.apache.rat;
 
-
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Fail.fail;
 
-
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.File;
@@ -52,6 +50,7 @@ import org.apache.commons.cli.ParseException;
 import org.apache.commons.io.FileUtils;
 import org.apache.rat.api.Document.Type;
 import org.apache.rat.api.RatException;
+import org.apache.rat.commandline.ArgumentContext;
 import org.apache.rat.commandline.StyleSheets;
 import org.apache.rat.document.FileDocument;
 import org.apache.rat.document.DocumentName;
@@ -84,7 +83,8 @@ public class ReporterTest {
         File output = new File(tempDirectory, "testExecute");
 
         CommandLine cl = new 
DefaultParser().parse(OptionCollection.buildOptions(), new 
String[]{"--output-style", "xml", "--output-file", output.getPath(), basedir});
-        ReportConfiguration config = OptionCollection.createConfiguration(cl);
+        ArgumentContext ctxt = new ArgumentContext(new File("."), cl);
+        ReportConfiguration config = 
OptionCollection.createConfiguration(ctxt);
         ClaimStatistic statistic = new Reporter(config).execute();
 
         assertThat(statistic.getCounter(Type.ARCHIVE)).isEqualTo(1);
@@ -137,7 +137,9 @@ public class ReporterTest {
     public void testOutputOption() throws Exception {
         File output = new File(tempDirectory, "test");
         CommandLine commandLine = new 
DefaultParser().parse(OptionCollection.buildOptions(), new String[]{"-o", 
output.getCanonicalPath(), basedir});
-        ReportConfiguration config = 
OptionCollection.createConfiguration(commandLine);
+        ArgumentContext ctxt = new ArgumentContext(new File("."), commandLine);
+
+        ReportConfiguration config = 
OptionCollection.createConfiguration(ctxt);
         new Reporter(config).output();
         assertThat(output.exists()).isTrue();
         String content = FileUtils.readFileToString(output, 
StandardCharsets.UTF_8);
@@ -154,7 +156,9 @@ public class ReporterTest {
         try (PrintStream out = new PrintStream(output)) {
             System.setOut(out);
             CommandLine commandLine = new 
DefaultParser().parse(OptionCollection.buildOptions(), new String[]{basedir});
-            ReportConfiguration config = 
OptionCollection.createConfiguration(commandLine);
+            ArgumentContext ctxt = new ArgumentContext(new File("."), 
commandLine);
+
+            ReportConfiguration config = 
OptionCollection.createConfiguration(ctxt);
             new Reporter(config).output();
         } finally {
             System.setOut(origin);
@@ -204,9 +208,10 @@ public class ReporterTest {
                 "type", "STANDARD"));
 
         File output = new File(tempDirectory, "testXMLOutput");
-
         CommandLine commandLine = new 
DefaultParser().parse(OptionCollection.buildOptions(), new 
String[]{"--output-style", "xml", "--output-file", output.getPath(), basedir});
-        ReportConfiguration config = 
OptionCollection.createConfiguration(commandLine);
+        ArgumentContext ctxt = new ArgumentContext(new File("."), commandLine);
+
+        ReportConfiguration config = 
OptionCollection.createConfiguration(ctxt);
         new Reporter(config).output();
 
         assertThat(output).exists();
diff --git 
a/apache-rat-core/src/test/java/org/apache/rat/commandline/ArgTests.java 
b/apache-rat-core/src/test/java/org/apache/rat/commandline/ArgTests.java
index 88079ec6..7472a0a9 100644
--- a/apache-rat-core/src/test/java/org/apache/rat/commandline/ArgTests.java
+++ b/apache-rat-core/src/test/java/org/apache/rat/commandline/ArgTests.java
@@ -28,7 +28,6 @@ import org.apache.rat.ReportConfiguration;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.ValueSource;
 
-
 import java.io.File;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -56,7 +55,7 @@ public class ArgTests {
 
         CommandLine commandLine = createCommandLine(new String[] 
{"--output-file", name});
         OutputFileConfig configuration = new OutputFileConfig();
-        ArgumentContext ctxt = new ArgumentContext(configuration, commandLine);
+        ArgumentContext ctxt = new ArgumentContext(new File("."), 
configuration, commandLine);
         Arg.processArgs(ctxt);
         assertEquals(expected.getAbsolutePath(), 
configuration.actual.getAbsolutePath());
     }
diff --git 
a/apache-rat-core/src/test/java/org/apache/rat/document/DocumentNameMatcherTest.java
 
b/apache-rat-core/src/test/java/org/apache/rat/document/DocumentNameMatcherTest.java
new file mode 100644
index 00000000..c53fb23d
--- /dev/null
+++ 
b/apache-rat-core/src/test/java/org/apache/rat/document/DocumentNameMatcherTest.java
@@ -0,0 +1,93 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.rat.document;
+
+import java.util.function.Predicate;
+import org.apache.commons.io.filefilter.NameFileFilter;
+import org.apache.rat.config.exclusion.plexus.MatchPatterns;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.apache.rat.document.DocumentNameMatcher.MATCHES_ALL;
+import static org.apache.rat.document.DocumentNameMatcher.MATCHES_NONE;
+
+public class DocumentNameMatcherTest {
+
+    private final static DocumentNameMatcher TRUE = new 
DocumentNameMatcher("T", (Predicate<DocumentName>)name -> true);
+    private final static DocumentNameMatcher FALSE = new 
DocumentNameMatcher("F", (Predicate<DocumentName>)name -> false);
+    private final static DocumentNameMatcher SOME = new 
DocumentNameMatcher("X", (Predicate<DocumentName>)name -> false);
+    private final static DocumentName testName = 
DocumentName.builder().setName("testName").setBaseName("/").build();
+
+    public static String processDecompose(DocumentNameMatcher matcher, 
DocumentName candidate) {
+        StringBuilder sb = new StringBuilder();
+        matcher.decompose(candidate).forEach(s -> sb.append(s).append("\n"));
+        return sb.toString();
+    }
+
+    @Test
+    public void orTest() {
+        assertThat(DocumentNameMatcher.or(TRUE, 
FALSE).matches(testName)).as("T,F").isTrue();
+        assertThat(DocumentNameMatcher.or(FALSE, 
TRUE).matches(testName)).as("F,T").isTrue();
+        assertThat(DocumentNameMatcher.or(TRUE, 
TRUE).matches(testName)).as("T,T").isTrue();
+        assertThat(DocumentNameMatcher.or(FALSE, 
FALSE).matches(testName)).as("F,F").isFalse();
+    }
+
+    @Test
+    public void andTest() {
+        assertThat(DocumentNameMatcher.and(TRUE, 
FALSE).matches(testName)).as("T,F").isFalse();
+        assertThat(DocumentNameMatcher.and(FALSE, 
TRUE).matches(testName)).as("F,T").isFalse();
+        assertThat(DocumentNameMatcher.and(TRUE, 
TRUE).matches(testName)).as("T,T").isTrue();
+        assertThat(DocumentNameMatcher.and(FALSE, 
FALSE).matches(testName)).as("F,F").isFalse();
+    }
+
+    @Test
+    public void matcherSetTest() {
+        assertThat(DocumentNameMatcher.matcherSet(TRUE, 
FALSE).matches(testName)).as("T,F").isTrue();
+        assertThat(DocumentNameMatcher.matcherSet(FALSE, 
TRUE).matches(testName)).as("F,T").isFalse();
+        assertThat(DocumentNameMatcher.matcherSet(TRUE, 
TRUE).matches(testName)).as("T,T").isTrue();
+        assertThat(DocumentNameMatcher.matcherSet(FALSE, 
FALSE).matches(testName)).as("F,F").isTrue();
+
+        assertThat(DocumentNameMatcher.matcherSet(MATCHES_ALL, 
MATCHES_ALL)).as("All,All").isEqualTo(MATCHES_ALL);
+        assertThat(DocumentNameMatcher.matcherSet(MATCHES_ALL, 
MATCHES_NONE)).as("All,None").isEqualTo(MATCHES_ALL);
+        assertThat(DocumentNameMatcher.matcherSet(MATCHES_ALL, 
SOME)).as("All,X").isEqualTo(MATCHES_ALL);
+
+        assertThat(DocumentNameMatcher.matcherSet(MATCHES_NONE, 
MATCHES_ALL)).as("None,All").isEqualTo(MATCHES_NONE);
+        assertThat(DocumentNameMatcher.matcherSet(MATCHES_NONE, 
MATCHES_NONE)).as("None,None").isEqualTo(MATCHES_ALL);
+        assertThat(DocumentNameMatcher.matcherSet(MATCHES_NONE, 
SOME).toString()).as("None,X").isEqualTo("not(X)");
+
+        assertThat(DocumentNameMatcher.matcherSet(SOME, 
MATCHES_ALL).toString()).as("X,All").isEqualTo("matcherSet(X, TRUE)");
+        assertThat(DocumentNameMatcher.matcherSet(SOME, 
MATCHES_NONE)).as("X,None").isEqualTo(MATCHES_ALL);
+        assertThat(DocumentNameMatcher.matcherSet(SOME, 
SOME).toString()).as("X,X").isEqualTo("matcherSet(X, X)");
+    }
+
+    @Test
+    void testDecompose() {
+        DocumentNameMatcher matcher1 = new 
DocumentNameMatcher("FileFilterTest", new NameFileFilter("File.name"));
+        String result = processDecompose(matcher1, testName);
+        assertThat(result).contains("FileFilterTest: >>false<<").contains("  
NameFileFilter(File.name)");
+
+        DocumentNameMatcher matcher2 = new 
DocumentNameMatcher("MatchPatternsTest", MatchPatterns.from("/", "**/test1*", 
"**/*Name"));
+        result = processDecompose(matcher2, testName);
+        assertThat(result).contains("MatchPatternsTest: >>true<<").contains("  
**/test1*: >>false<<").contains("  **/*Name: >>true<<");
+
+        DocumentNameMatcher matcher3 = 
DocumentNameMatcher.matcherSet(matcher1, matcher2);
+        result = processDecompose(matcher3, testName);
+        assertThat(result).contains("MatchPatternsTest: >>true<<").contains("  
**/test1*: >>false<<").contains("  **/*Name: >>true<<");
+    }
+}
diff --git 
a/apache-rat-core/src/test/java/org/apache/rat/test/AbstractOptionsProvider.java
 
b/apache-rat-core/src/test/java/org/apache/rat/test/AbstractOptionsProvider.java
index baf44ead..57c5a051 100644
--- 
a/apache-rat-core/src/test/java/org/apache/rat/test/AbstractOptionsProvider.java
+++ 
b/apache-rat-core/src/test/java/org/apache/rat/test/AbstractOptionsProvider.java
@@ -18,7 +18,10 @@
  */
 package org.apache.rat.test;
 
+import java.nio.file.FileSystems;
+import java.nio.file.Path;
 import org.apache.commons.cli.Option;
+import org.apache.commons.io.FileUtils;
 import org.apache.commons.io.IOUtils;
 import org.apache.commons.lang3.tuple.ImmutablePair;
 import org.apache.commons.lang3.tuple.Pair;
@@ -30,14 +33,15 @@ import org.apache.rat.commandline.StyleSheets;
 import org.apache.rat.config.exclusion.StandardCollection;
 import org.apache.rat.document.DocumentNameMatcher;
 import org.apache.rat.document.DocumentName;
+import org.apache.rat.document.DocumentNameMatcherTest;
 import org.apache.rat.license.ILicense;
 import org.apache.rat.license.ILicenseFamily;
 import org.apache.rat.license.LicenseSetFactory;
 import org.apache.rat.report.claim.ClaimStatistic;
+import org.apache.rat.test.utils.Resources;
 import org.apache.rat.testhelpers.TextUtils;
 import org.apache.rat.utils.DefaultLog;
 import org.apache.rat.utils.Log.Level;
-import org.apache.rat.utils.ExtendedIterator;
 import org.junit.jupiter.api.extension.ExtensionContext;
 import org.junit.jupiter.params.provider.Arguments;
 
@@ -61,14 +65,11 @@ import java.util.Map;
 import java.util.SortedSet;
 import java.util.TreeMap;
 import java.util.stream.Stream;
+import org.junit.jupiter.params.provider.ArgumentsProvider;
 
 import static org.apache.rat.commandline.Arg.HELP_LICENSES;
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertNull;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-import static org.junit.jupiter.api.Assertions.fail;
+import static org.assertj.core.api.Fail.fail;
 
 /**
  * A list of methods that an OptionsProvider in a test case must support.
@@ -76,26 +77,60 @@ import static org.junit.jupiter.api.Assertions.fail;
  * tests an Option from OptionCollection that must be implemented in the UI.
  * Each method in this interface tests an Option in {@link 
org.apache.rat.OptionCollection}.
  */
-public abstract class AbstractOptionsProvider {
-    /** A map of test Options to tests */
+public abstract class AbstractOptionsProvider implements ArgumentsProvider {
+    /**
+     * A map of test Options to tests
+     */
     protected final Map<String, OptionCollectionTest.OptionTest> testMap = new 
TreeMap<>();
-
-    protected static final String[] EXCLUDE_ARGS = { "*.foo", 
"%regex[[A-Z]\\.bar]", "justbaz"};
-    protected static final String[] INCLUDE_ARGS = { "B.bar", "justbaz" };
+    /** The list of exclude args */
+    protected static final String[] EXCLUDE_ARGS = {"*.foo", 
"%regex[[A-Z]\\.bar]", "justbaz"};
+    /** the list of include args */
+    protected static final String[] INCLUDE_ARGS = {"B.bar", "justbaz"};
     /**
      * The directory to place test data in.
-     * We do not use temp file here as we want the evidence to survive failure.
      */
-    protected final File baseDir;
+    protected File baseDir;
+
+    /**
+     * Copy the runtime data to the "target" directory.
+     * @param baseDir the base directory to copy to.
+     * @param targetDir the directory relative to the base directory to copy 
to.
+     */
+    public static void preserveData(File baseDir, String targetDir) {
+        final Path recordPath = FileSystems.getDefault().getPath("target", 
targetDir);
+        recordPath.toFile().mkdirs();
+        try {
+            FileUtils.copyDirectory(baseDir, recordPath.toFile());
+        } catch (IOException e) {
+            System.err.format("Unable to copy data from %s to %s%n", baseDir, 
recordPath);
+        }
+    }
 
+    /**
+     * Gets the document name based on the baseDir.
+     * @return The document name based on the baseDir.
+     */
     protected DocumentName baseName() {
         return DocumentName.builder(baseDir).build();
     }
 
-    protected AbstractOptionsProvider(Collection<String> unsupportedArgs) {
-        baseDir = new File("target/optionTools");
-        baseDir.mkdirs();
+    /**
+     * Copies the test data to the specified directory.
+     * @param baseDir the directory to copy the /src/test/resources to.
+     * @return the {@code baseDir} argument.
+     */
+    public static File setup(File baseDir) {
+        try {
+            final File sourceDir = 
Resources.getResourceDirectory("OptionTools");
+            FileUtils.copyDirectory(sourceDir, new 
File(baseDir,"/src/test/resources/OptionTools"));
+        } catch (IOException e) {
+            DefaultLog.getInstance().error("Can not copy 'OptionTools' to " + 
baseDir, e);
+        }
+        return baseDir;
+    }
 
+    protected AbstractOptionsProvider(Collection<String> unsupportedArgs, File 
baseDir) {
+        this.baseDir = setup(baseDir);
         testMap.put("addLicense", this::addLicenseTest);
         testMap.put("config", this::configTest);
         testMap.put("configuration-no-defaults", 
this::configurationNoDefaultsTest);
@@ -179,21 +214,28 @@ public abstract class AbstractOptionsProvider {
         unsupportedArgs.forEach(testMap::remove);
     }
 
+    @SafeVarargs
+    protected final ReportConfiguration generateConfig(Pair<Option, 
String[]>... args) throws IOException {
+        List<Pair<Option, String[]>> options = Arrays.asList(args);
+        return generateConfig(options);
+    }
+
     /**
      * Create the report configuration from the argument pairs.
      * There must be at least one arg. It may be `ImmutablePair.nullPair()`.
+     *
      * @param args Pairs comprising the argument option and the values for the 
option.
      * @return The generated ReportConfiguration.
      * @throws IOException on error.
      */
-    protected abstract ReportConfiguration generateConfig(Pair<Option, 
String[]>... args) throws IOException;
+    protected abstract ReportConfiguration generateConfig(List<Pair<Option, 
String[]>> args) throws IOException;
 
     protected File writeFile(String name, Iterable<String> lines) {
         File file = new File(baseDir, name);
         try (PrintWriter writer = new PrintWriter(new FileWriter(file))) {
             lines.forEach(writer::println);
         } catch (IOException e) {
-            fail(e.getMessage(), e);
+            fail(e.getMessage());
         }
         return file;
     }
@@ -205,18 +247,30 @@ public abstract class AbstractOptionsProvider {
     /* tests to be implemented */
     protected abstract void helpTest();
 
+    /* Display the option and value under test */
+    private String displayArgAndName(Option option, String fname) {
+        return String.format("%s %s", option.getLongOpt(), fname);
+    }
+
+    private String dump(Option option, String fname, DocumentNameMatcher 
matcher, DocumentName name) {
+        return String.format("Argument and Name: %s%nMatcher 
decomposition:%n%s", displayArgAndName(option, fname),
+                DocumentNameMatcherTest.processDecompose(matcher, name));
+    }
+
     // exclude tests
     private void execExcludeTest(Option option, String[] args) {
-        String[] notExcluded = { "notbaz", "well._afile"  };
-        String[] excluded = { "some.foo", "B.bar", "justbaz"};
+        String[] notExcluded = {"notbaz", "well._afile"};
+        String[] excluded = {"some.foo", "B.bar", "justbaz"};
         try {
             ReportConfiguration config = 
generateConfig(ImmutablePair.of(option, args));
             DocumentNameMatcher excluder = 
config.getDocumentExcluder(baseName());
             for (String fname : notExcluded) {
-                assertTrue(excluder.matches(mkDocName(fname)), () -> 
option.getKey() + " " + fname);
+                DocumentName docName = mkDocName(fname);
+                assertThat(excluder.matches(docName)).as(() -> dump(option, 
fname, excluder, docName)).isTrue();
             }
             for (String fname : excluded) {
-                assertFalse(excluder.matches(mkDocName(fname)), () -> 
option.getKey() + " " + fname);
+                DocumentName docName = mkDocName(fname);
+                assertThat(excluder.matches(docName)).as(() -> dump(option, 
fname, excluder, docName)).isFalse();
             }
         } catch (IOException e) {
             fail(e.getMessage());
@@ -225,7 +279,7 @@ public abstract class AbstractOptionsProvider {
 
     private void excludeFileTest(Option option) {
         File outputFile = writeFile("exclude.txt", 
Arrays.asList(EXCLUDE_ARGS));
-        execExcludeTest(option, new String[] {outputFile.getPath()});
+        execExcludeTest(option, new String[]{outputFile.getAbsolutePath()});
     }
 
     protected void excludeFileTest() {
@@ -246,17 +300,19 @@ public abstract class AbstractOptionsProvider {
 
     protected void inputExcludeStdTest() {
         Option option = Arg.EXCLUDE_STD.find("input-exclude-std");
-        String[] args = { StandardCollection.MISC.name() };
-        String[] excluded = { "afile~", ".#afile", "%afile%", "._afile" };
-        String[] notExcluded = { "afile~more",   "what.#afile", 
"%afile%withMore", "well._afile" };
+        String[] args = {StandardCollection.MISC.name()};
+        String[] excluded = {"afile~", ".#afile", "%afile%", "._afile"};
+        String[] notExcluded = {"afile~more", "what.#afile", 
"%afile%withMore", "well._afile"};
         try {
             ReportConfiguration config = 
generateConfig(ImmutablePair.of(option, args));
             DocumentNameMatcher excluder = 
config.getDocumentExcluder(baseName());
             for (String fname : excluded) {
-                assertFalse(excluder.matches(mkDocName(fname)), () -> 
option.getKey() + " " + fname);
+                DocumentName docName = mkDocName(fname);
+                assertThat(excluder.matches(docName)).as(() -> dump(option, 
fname, excluder, docName)).isFalse();
             }
             for (String fname : notExcluded) {
-                assertTrue(excluder.matches(mkDocName(fname)), () -> 
option.getKey() + " " + fname);
+                DocumentName docName = mkDocName(fname);
+                assertThat(excluder.matches(docName)).as(() -> dump(option, 
fname, excluder, docName)).isTrue();
             }
         } catch (IOException e) {
             fail(e.getMessage());
@@ -265,7 +321,7 @@ public abstract class AbstractOptionsProvider {
 
     protected void inputExcludeParsedScmTest() {
         Option option = Arg.EXCLUDE_PARSE_SCM.find("input-exclude-parsed-scm");
-        String[] args = { "GIT" };
+        String[] args = {"GIT"};
         String[] lines = {
                 "# somethings",
                 "!thingone", "thing*", System.lineSeparator(),
@@ -273,22 +329,28 @@ public abstract class AbstractOptionsProvider {
                 "**/fish", "*_fish",
                 "# some colorful directories",
                 "red/", "blue/*/"};
-        String[] notExcluded = { "thingone", "dir/fish_two"};
-        String[] excluded = { "thingtwo", "dir/fish", "red/fish", "blue/fish" 
};
+
+        String[] notExcluded = {"thingone", "dir/fish_two", "some/thingone", 
"blue/fish/dory" };
+        String[] excluded = {"thingtwo", "some/things", "dir/fish", 
"red/fish", "blue/fish", "some/fish", "another/red_fish"};
 
         writeFile(".gitignore", Arrays.asList(lines));
+        File dir = new File(baseDir, "red");
+        dir.mkdirs();
+        dir = new File(baseDir, "blue");
+        dir = new File(dir, "fish");
+        dir.mkdirs();
+
 
-        List<String> expected = 
ExtendedIterator.create(Arrays.asList("thing*", "**/fish", "*_fish", "red/**", 
"blue/*/**").iterator())
-                .map(s -> new File(baseDir, s).getPath()).addTo(new 
ArrayList<>());
-        expected.add(0, "!" + new File(baseDir, "thingone").getPath());
         try {
             ReportConfiguration config = 
generateConfig(ImmutablePair.of(option, args));
             DocumentNameMatcher excluder = 
config.getDocumentExcluder(baseName());
             for (String fname : excluded) {
-                assertFalse(excluder.matches(mkDocName(fname)), () -> 
option.getKey() + " " + fname);
+                DocumentName docName = mkDocName(fname);
+                assertThat(excluder.matches(docName)).as(() -> dump(option, 
fname, excluder, docName)).isFalse();
             }
             for (String fname : notExcluded) {
-                assertTrue(excluder.matches(mkDocName(fname)), () -> 
option.getKey() + " " + fname);
+                DocumentName docName = mkDocName(fname);
+                assertThat(excluder.matches(docName)).as(() -> dump(option, 
fname, excluder, docName)).isTrue();
             }
         } catch (IOException e) {
             fail(e.getMessage());
@@ -297,22 +359,24 @@ public abstract class AbstractOptionsProvider {
 
     private void inputExcludeSizeTest() {
         Option option = Arg.EXCLUDE_SIZE.option();
-        String[] args = { "5" };
-        writeFile("Hi.txt", Arrays.asList("Hi"));
-        writeFile("Hello.txt", Arrays.asList("Hello"));
-        writeFile("HelloWorld.txt", Arrays.asList("HelloWorld"));
+        String[] args = {"5"};
+        writeFile("Hi.txt", Collections.singletonList("Hi"));
+        writeFile("Hello.txt", Collections.singletonList("Hello"));
+        writeFile("HelloWorld.txt", Collections.singletonList("HelloWorld"));
 
-        String[] notExcluded = { "Hello.txt", "HelloWorld.txt"};
-        String[] excluded = { "Hi.txt" };
+        String[] notExcluded = {"Hello.txt", "HelloWorld.txt"};
+        String[] excluded = {"Hi.txt"};
 
         try {
             ReportConfiguration config = 
generateConfig(ImmutablePair.of(option, args));
             DocumentNameMatcher excluder = 
config.getDocumentExcluder(baseName());
             for (String fname : excluded) {
-                assertFalse(excluder.matches(mkDocName(fname)), () -> 
option.getKey() + " " + fname);
+                DocumentName docName = mkDocName(fname);
+                assertThat(excluder.matches(docName)).as(() -> dump(option, 
fname, excluder, docName)).isFalse();
             }
             for (String fname : notExcluded) {
-                assertTrue(excluder.matches(mkDocName(fname)), () -> 
option.getKey() + " " + fname);
+                DocumentName docName = mkDocName(fname);
+                assertThat(excluder.matches(docName)).as(() -> dump(option, 
fname, excluder, docName)).isTrue();
             }
         } catch (IOException e) {
             fail(e.getMessage());
@@ -329,10 +393,12 @@ public abstract class AbstractOptionsProvider {
                     ImmutablePair.of(excludeOption, EXCLUDE_ARGS));
             DocumentNameMatcher excluder = 
config.getDocumentExcluder(baseName());
             for (String fname : excluded) {
-                assertFalse(excluder.matches(mkDocName(fname)), () -> 
option.getKey() + " " + fname);
+                DocumentName docName = mkDocName(fname);
+                assertThat(excluder.matches(docName)).as(() -> dump(option, 
fname, excluder, docName)).isFalse();
             }
             for (String fname : notExcluded) {
-                assertTrue(excluder.matches(mkDocName(fname)), () -> 
option.getKey() + " " + fname);
+                DocumentName docName = mkDocName(fname);
+                assertThat(excluder.matches(docName)).as(() -> dump(option, 
fname, excluder, docName)).isTrue();
             }
         } catch (IOException e) {
             fail(e.getMessage());
@@ -341,7 +407,7 @@ public abstract class AbstractOptionsProvider {
 
     private void includeFileTest(Option option) {
         File outputFile = writeFile("include.txt", 
Arrays.asList(INCLUDE_ARGS));
-        execIncludeTest(option, new String[] {outputFile.getPath()});
+        execIncludeTest(option, new String[]{outputFile.getAbsolutePath()});
     }
 
     protected void inputIncludeFileTest() {
@@ -362,19 +428,21 @@ public abstract class AbstractOptionsProvider {
 
     protected void inputIncludeStdTest() {
         ImmutablePair<Option, String[]> excludes = 
ImmutablePair.of(Arg.EXCLUDE.find("input-exclude"),
-                new String[] { "*~more", "*~" });
+                new String[]{"*~more", "*~"});
         Option option = Arg.INCLUDE_STD.find("input-include-std");
-        String[] args = { StandardCollection.MISC.name() };
-        String[] excluded = { "afile~more" };
-        String[] notExcluded = { "afile~", ".#afile", "%afile%", "._afile", 
"what.#afile", "%afile%withMore", "well._afile" };
+        String[] args = {StandardCollection.MISC.name()};
+        String[] excluded = {"afile~more"};
+        String[] notExcluded = {"afile~", ".#afile", "%afile%", "._afile", 
"what.#afile", "%afile%withMore", "well._afile"};
         try {
             ReportConfiguration config = generateConfig(excludes, 
ImmutablePair.of(option, args));
             DocumentNameMatcher excluder = 
config.getDocumentExcluder(baseName());
             for (String fname : excluded) {
-                assertFalse(excluder.matches(mkDocName(fname)), () -> 
option.getKey() + " " + fname);
+                DocumentName docName = mkDocName(fname);
+                assertThat(excluder.matches(docName)).as(() -> dump(option, 
fname, excluder, docName)).isFalse();
             }
             for (String fname : notExcluded) {
-                assertTrue(excluder.matches(mkDocName(fname)), () -> 
option.getKey() + " " + fname);
+                DocumentName docName = mkDocName(fname);
+                assertThat(excluder.matches(docName)).as(() -> dump(option, 
fname, excluder, docName)).isTrue();
             }
         } catch (IOException e) {
             fail(e.getMessage());
@@ -385,7 +453,7 @@ public abstract class AbstractOptionsProvider {
         Option option = Arg.SOURCE.find("input-source");
         try {
             ReportConfiguration config = 
generateConfig(ImmutablePair.of(option, new 
String[]{baseDir.getAbsolutePath()}));
-            assertTrue(config.hasSource());
+            assertThat(config.hasSource()).isTrue();
         } catch (IOException e) {
             fail(e.getMessage());
         }
@@ -393,7 +461,7 @@ public abstract class AbstractOptionsProvider {
 
     // LICENSE tests
     protected void execLicensesApprovedTest(Option option, String[] args) {
-        Pair<Option,String[]>  arg1 = ImmutablePair.of(option, args);
+        Pair<Option, String[]> arg1 = ImmutablePair.of(option, args);
         try {
             ReportConfiguration config = generateConfig(arg1);
             SortedSet<String> result = 
config.getLicenseIds(LicenseSetFactory.LicenseFilter.APPROVED);
@@ -402,13 +470,13 @@ public abstract class AbstractOptionsProvider {
             fail(e.getMessage());
         }
 
-        Pair<Option,String[]> arg2 = ImmutablePair.of(
+        Pair<Option, String[]> arg2 = ImmutablePair.of(
                 
Arg.CONFIGURATION_NO_DEFAULTS.find("configuration-no-defaults"),
                 null
         );
 
         try {
-            ReportConfiguration config = generateConfig(arg1, arg2 );
+            ReportConfiguration config = generateConfig(arg1, arg2);
             SortedSet<String> result = 
config.getLicenseIds(LicenseSetFactory.LicenseFilter.APPROVED);
             assertThat(result).containsExactly("one", "two");
         } catch (IOException e) {
@@ -419,7 +487,7 @@ public abstract class AbstractOptionsProvider {
     protected void helpLicenses() {
         ByteArrayOutputStream output = new ByteArrayOutputStream();
         PrintStream origin = System.out;
-        try (PrintStream out = new PrintStream(output)){
+        try (PrintStream out = new PrintStream(output)) {
             System.setOut(out);
             generateConfig(ImmutablePair.of(HELP_LICENSES.option(), null));
         } catch (IOException e) {
@@ -436,12 +504,12 @@ public abstract class AbstractOptionsProvider {
     protected void licensesApprovedFileTest() {
         File outputFile = writeFile("licensesApproved.txt", 
Arrays.asList("one", "two"));
         
execLicensesApprovedTest(Arg.LICENSES_APPROVED_FILE.find("licenses-approved-file"),
-                new String[] { outputFile.getPath()});
+                new String[]{outputFile.getAbsolutePath()});
     }
 
     protected void licensesApprovedTest() {
         
execLicensesApprovedTest(Arg.LICENSES_APPROVED.find("licenses-approved"),
-                new String[] { "one", "two"});
+                new String[]{"one", "two"});
     }
 
     private void execLicensesDeniedTest(Option option, String[] args) {
@@ -456,12 +524,13 @@ public abstract class AbstractOptionsProvider {
     }
 
     protected void licensesDeniedTest() {
-        execLicensesDeniedTest(Arg.LICENSES_DENIED.find("licenses-denied"), 
new String[] {"ILLUMOS"});
+        execLicensesDeniedTest(Arg.LICENSES_DENIED.find("licenses-denied"), 
new String[]{"ILLUMOS"});
     }
 
     protected void licensesDeniedFileTest() {
         File outputFile = writeFile("licensesDenied.txt", 
Collections.singletonList("ILLUMOS"));
-        
execLicensesDeniedTest(Arg.LICENSES_DENIED_FILE.find("licenses-denied-file"), 
new String[] {outputFile.getPath()});
+        
execLicensesDeniedTest(Arg.LICENSES_DENIED_FILE.find("licenses-denied-file"),
+                new String[]{outputFile.getAbsolutePath()});
     }
 
     private void execLicenseFamiliesApprovedTest(Option option, String[] args) 
{
@@ -488,12 +557,12 @@ public abstract class AbstractOptionsProvider {
     protected void licenseFamiliesApprovedFileTest() {
         File outputFile = writeFile("familiesApproved.txt", 
Collections.singletonList("catz"));
         
execLicenseFamiliesApprovedTest(Arg.FAMILIES_APPROVED_FILE.find("license-families-approved-file"),
-                new String[] { outputFile.getPath() });
+                new String[]{outputFile.getAbsolutePath()});
     }
 
     protected void licenseFamiliesApprovedTest() {
         
execLicenseFamiliesApprovedTest(Arg.FAMILIES_APPROVED.find("license-families-approved"),
-                new String[] {"catz"});
+                new String[]{"catz"});
     }
 
     private void execLicenseFamiliesDeniedTest(Option option, String[] args) {
@@ -508,15 +577,15 @@ public abstract class AbstractOptionsProvider {
         }
     }
 
-   protected void licenseFamiliesDeniedFileTest() {
+    protected void licenseFamiliesDeniedFileTest() {
         File outputFile = writeFile("familiesDenied.txt", 
Collections.singletonList("GPL"));
         
execLicenseFamiliesDeniedTest(Arg.FAMILIES_DENIED_FILE.find("license-families-denied-file"),
-                new String[] { outputFile.getPath() });
+                new String[]{outputFile.getAbsolutePath()});
     }
 
     protected void licenseFamiliesDeniedTest() {
         
execLicenseFamiliesDeniedTest(Arg.FAMILIES_DENIED.find("license-families-denied"),
-                new String[] { "GPL" });
+                new String[]{"GPL"});
     }
 
     protected void counterMaxTest() {
@@ -525,17 +594,17 @@ public abstract class AbstractOptionsProvider {
 
         try {
             ReportConfiguration config = 
generateConfig(ImmutablePair.nullPair());
-            assertEquals(0, 
config.getClaimValidator().getMax(ClaimStatistic.Counter.UNAPPROVED));
+            
assertThat(config.getClaimValidator().getMax(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(0);
             args[0] = "Unapproved:-1";
             args[1] = "ignored:1";
             config = generateConfig(ImmutablePair.of(option, args));
-            assertEquals(Integer.MAX_VALUE, 
config.getClaimValidator().getMax(ClaimStatistic.Counter.UNAPPROVED));
-            assertEquals(1, 
config.getClaimValidator().getMax(ClaimStatistic.Counter.IGNORED));
+            
assertThat(config.getClaimValidator().getMax(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(Integer.MAX_VALUE);
+            
assertThat(config.getClaimValidator().getMax(ClaimStatistic.Counter.IGNORED)).isEqualTo(1);
             args[1] = "unapproved:5";
             args[0] = "ignored:0";
             config = generateConfig(ImmutablePair.of(option, args));
-            assertEquals(5, 
config.getClaimValidator().getMax(ClaimStatistic.Counter.UNAPPROVED));
-            assertEquals(0, 
config.getClaimValidator().getMax(ClaimStatistic.Counter.IGNORED));
+            
assertThat(config.getClaimValidator().getMax(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(5);
+            
assertThat(config.getClaimValidator().getMax(ClaimStatistic.Counter.IGNORED)).isEqualTo(0);
         } catch (IOException e) {
             fail(e.getMessage());
         }
@@ -547,17 +616,17 @@ public abstract class AbstractOptionsProvider {
 
         try {
             ReportConfiguration config = 
generateConfig(ImmutablePair.nullPair());
-            assertEquals(0, 
config.getClaimValidator().getMin(ClaimStatistic.Counter.UNAPPROVED));
+            
assertThat(config.getClaimValidator().getMin(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(0);
             args[0] = "Unapproved:1";
             args[1] = "ignored:1";
             config = generateConfig(ImmutablePair.of(option, args));
-            assertEquals(1, 
config.getClaimValidator().getMin(ClaimStatistic.Counter.UNAPPROVED));
-            assertEquals(1, 
config.getClaimValidator().getMin(ClaimStatistic.Counter.IGNORED));
+            
assertThat(config.getClaimValidator().getMin(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(1);
+            
assertThat(config.getClaimValidator().getMin(ClaimStatistic.Counter.IGNORED)).isEqualTo(1);
             args[1] = "unapproved:5";
             args[0] = "ignored:0";
             config = generateConfig(ImmutablePair.of(option, args));
-            assertEquals(5, 
config.getClaimValidator().getMin(ClaimStatistic.Counter.UNAPPROVED));
-            assertEquals(0, 
config.getClaimValidator().getMin(ClaimStatistic.Counter.IGNORED));
+            
assertThat(config.getClaimValidator().getMin(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(5);
+            
assertThat(config.getClaimValidator().getMin(ClaimStatistic.Counter.IGNORED)).isEqualTo(0);
         } catch (IOException e) {
             fail(e.getMessage());
         }
@@ -569,17 +638,17 @@ public abstract class AbstractOptionsProvider {
         try {
             ReportConfiguration config = generateConfig(arg1);
             SortedSet<ILicense> set = 
config.getLicenses(LicenseSetFactory.LicenseFilter.ALL);
-            assertTrue(set.size() > 2);
-            assertTrue(LicenseSetFactory.search("ONE", "ONE", 
set).isPresent());
-            assertTrue(LicenseSetFactory.search("TWO", "TWO", 
set).isPresent());
+            assertThat(set).hasSizeGreaterThan(2);
+            assertThat(LicenseSetFactory.search("ONE", "ONE", 
set)).isPresent();
+            assertThat(LicenseSetFactory.search("TWO", "TWO", 
set)).isPresent();
 
             Pair<Option, String[]> arg2 = 
ImmutablePair.of(Arg.CONFIGURATION_NO_DEFAULTS.find("configuration-no-defaults"),
 null);
 
             config = generateConfig(arg1, arg2);
             set = config.getLicenses(LicenseSetFactory.LicenseFilter.ALL);
-            assertEquals(2, set.size());
-            assertTrue(LicenseSetFactory.search("ONE", "ONE", 
set).isPresent());
-            assertTrue(LicenseSetFactory.search("TWO", "TWO", 
set).isPresent());
+            assertThat(set).hasSize(2);
+            assertThat(LicenseSetFactory.search("ONE", "ONE", 
set)).isPresent();
+            assertThat(LicenseSetFactory.search("TWO", "TWO", 
set)).isPresent();
         } catch (IOException e) {
             fail(e.getMessage());
         }
@@ -596,9 +665,9 @@ public abstract class AbstractOptionsProvider {
     private void noDefaultsTest(Option arg) {
         try {
             ReportConfiguration config = generateConfig(ImmutablePair.of(arg, 
null));
-            
assertTrue(config.getLicenses(LicenseSetFactory.LicenseFilter.ALL).isEmpty());
+            
assertThat(config.getLicenses(LicenseSetFactory.LicenseFilter.ALL)).isEmpty();
             config = generateConfig(ImmutablePair.nullPair());
-            
assertFalse(config.getLicenses(LicenseSetFactory.LicenseFilter.ALL).isEmpty());
+            
assertThat(config.getLicenses(LicenseSetFactory.LicenseFilter.ALL)).isNotEmpty();
         } catch (IOException e) {
             fail(e.getMessage());
         }
@@ -615,9 +684,9 @@ public abstract class AbstractOptionsProvider {
     protected void dryRunTest() {
         try {
             ReportConfiguration config = 
generateConfig(ImmutablePair.of(Arg.DRY_RUN.find("dry-run"), null));
-            assertTrue(config.isDryRun());
+            assertThat(config.isDryRun()).isTrue();
             config = generateConfig(ImmutablePair.nullPair());
-            assertFalse(config.isDryRun());
+            assertThat(config.isDryRun()).isFalse();
         } catch (IOException e) {
             fail(e.getMessage());
         }
@@ -627,10 +696,10 @@ public abstract class AbstractOptionsProvider {
         try {
             Pair<Option, String[]> arg1 = ImmutablePair.of(option, new 
String[]{"MyCopyright"});
             ReportConfiguration config = generateConfig(arg1);
-            assertNull(config.getCopyrightMessage(), "Copyright without 
--edit-license should not work");
+            assertThat(config.getCopyrightMessage()).as("Copyright without 
--edit-license should not work").isNull();
             Pair<Option, String[]> arg2 = 
ImmutablePair.of(Arg.EDIT_ADD.find("edit-license"), null);
             config = generateConfig(arg1, arg2);
-            assertEquals("MyCopyright", config.getCopyrightMessage());
+            assertThat(config.getCopyrightMessage()).isEqualTo("MyCopyright");
         } catch (IOException e) {
             fail(e.getMessage());
         }
@@ -647,9 +716,9 @@ public abstract class AbstractOptionsProvider {
     private void editLicenseTest(Option option) {
         try {
             ReportConfiguration config = 
generateConfig(ImmutablePair.of(option, null));
-            assertTrue(config.isAddingLicenses());
+            assertThat(config.isAddingLicenses()).isTrue();
             config = generateConfig(ImmutablePair.nullPair());
-            assertFalse(config.isAddingLicenses());
+            assertThat(config.isAddingLicenses()).isFalse();
         } catch (IOException e) {
             fail(e.getMessage());
         }
@@ -658,6 +727,7 @@ public abstract class AbstractOptionsProvider {
     protected void addLicenseTest() {
         editLicenseTest(Arg.EDIT_ADD.find("addLicense"));
     }
+
     protected void editLicensesTest() {
         editLicenseTest(Arg.EDIT_ADD.find("edit-license"));
     }
@@ -666,11 +736,11 @@ public abstract class AbstractOptionsProvider {
         Pair<Option, String[]> arg1 = ImmutablePair.of(option, null);
         try {
             ReportConfiguration config = generateConfig(arg1);
-            assertFalse(config.isAddingLicensesForced());
+            assertThat(config.isAddingLicensesForced()).isFalse();
             Pair<Option, String[]> arg2 = 
ImmutablePair.of(Arg.EDIT_ADD.find("edit-license"), null);
 
             config = generateConfig(arg1, arg2);
-            assertTrue(config.isAddingLicensesForced());
+            assertThat(config.isAddingLicensesForced()).isTrue();
         } catch (IOException e) {
             fail(e.getMessage());
         }
@@ -693,7 +763,7 @@ public abstract class AbstractOptionsProvider {
                 try {
                     args[0] = level.name();
                     generateConfig(ImmutablePair.of(option, args));
-                    assertEquals(level, DefaultLog.getInstance().getLevel());
+                    
assertThat(DefaultLog.getInstance().getLevel()).isEqualTo(level);
                 } catch (IOException e) {
                     fail(e.getMessage());
                 }
@@ -709,7 +779,7 @@ public abstract class AbstractOptionsProvider {
             for (ReportConfiguration.Processing proc : 
ReportConfiguration.Processing.values()) {
                 args[0] = proc.name();
                 ReportConfiguration config = 
generateConfig(ImmutablePair.of(option, args));
-                assertEquals(proc, config.getArchiveProcessing());
+                assertThat(config.getArchiveProcessing()).isEqualTo(proc);
             }
         } catch (IOException e) {
             fail(e.getMessage());
@@ -726,7 +796,7 @@ public abstract class AbstractOptionsProvider {
             try {
                 args[0] = filter.name();
                 ReportConfiguration config = 
generateConfig(ImmutablePair.of(option, args));
-                assertEquals(filter, config.listFamilies());
+                assertThat(config.listFamilies()).isEqualTo(filter);
             } catch (IOException e) {
                 fail(e.getMessage());
             }
@@ -752,7 +822,7 @@ public abstract class AbstractOptionsProvider {
                 throw new RuntimeException(e);
             }
             try (BufferedReader reader = new BufferedReader(new 
InputStreamReader(Files.newInputStream(outFile.toPath())))) {
-                assertEquals("Hello world", reader.readLine());
+                assertThat(reader.readLine()).isEqualTo("Hello world");
             } catch (IOException e) {
                 throw new RuntimeException(e);
             }
@@ -775,7 +845,7 @@ public abstract class AbstractOptionsProvider {
             try {
                 args[0] = filter.name();
                 ReportConfiguration config = 
generateConfig(ImmutablePair.of(option, args));
-                assertEquals(filter, config.listLicenses());
+                assertThat(config.listLicenses()).isEqualTo(filter);
             } catch (IOException e) {
                 fail(e.getMessage());
             }
@@ -791,12 +861,12 @@ public abstract class AbstractOptionsProvider {
     }
 
     private void standardTest(Option option) {
-        String[] args = { null};
+        String[] args = {null};
         try {
             for (ReportConfiguration.Processing proc : 
ReportConfiguration.Processing.values()) {
                 args[0] = proc.name();
                 ReportConfiguration config = 
generateConfig(ImmutablePair.of(option, args));
-                assertEquals(proc, config.getStandardProcessing());
+                assertThat(config.getStandardProcessing()).isEqualTo(proc);
             }
         } catch (IOException e) {
             fail(e.getMessage());
@@ -815,7 +885,7 @@ public abstract class AbstractOptionsProvider {
                 OutputStream out = Files.newOutputStream(file.toPath())) {
             IOUtils.copy(in, out);
         } catch (IOException e) {
-            fail("Could not copy MatcherContainerResource.txt: 
"+e.getMessage());
+            fail("Could not copy MatcherContainerResource.txt: " + 
e.getMessage());
         }
         // run the test
         String[] args = {null};
@@ -825,11 +895,11 @@ public abstract class AbstractOptionsProvider {
                 ReportConfiguration config = 
generateConfig(ImmutablePair.of(option, args));
                 try (InputStream expected = 
StyleSheets.getStyleSheet(sheet).get();
                      InputStream actual = config.getStyleSheet().get()) {
-                    assertTrue(IOUtils.contentEquals(expected, actual), () -> 
String.format("'%s' does not match", sheet));
+                    assertThat(IOUtils.contentEquals(expected, actual)).as(() 
-> String.format("'%s' does not match", sheet)).isTrue();
                 }
             }
         } catch (IOException e) {
-            fail(e.getMessage(), e);
+            fail(e.getMessage());
         }
     }
 
@@ -845,7 +915,7 @@ public abstract class AbstractOptionsProvider {
         try {
             ReportConfiguration config = 
generateConfig(ImmutablePair.of(Arg.INCLUDE_STD.find("scan-hidden-directories"),
 null));
             DocumentNameMatcher excluder = 
config.getDocumentExcluder(baseName());
-            assertTrue(excluder.matches(mkDocName(".file")), ".file");
+            
assertThat(excluder.matches(mkDocName(".file"))).as(".file").isTrue();
         } catch (IOException e) {
             fail(e.getMessage());
         }
@@ -856,28 +926,29 @@ public abstract class AbstractOptionsProvider {
             ReportConfiguration config = 
generateConfig(ImmutablePair.of(Arg.OUTPUT_STYLE.find("xml"), null));
             try (InputStream expected = StyleSheets.getStyleSheet("xml").get();
                  InputStream actual = config.getStyleSheet().get()) {
-                assertTrue(IOUtils.contentEquals(expected, actual), "'xml' 
does not match");
+                assertThat(IOUtils.contentEquals(expected, actual)).as("'xml' 
does not match").isTrue();
             }
         } catch (IOException e) {
             fail(e.getMessage());
         }
     }
 
-        final public Stream<? extends Arguments> 
provideArguments(ExtensionContext context) {
-                List<Arguments> lst = new ArrayList<>();
-                List<String> missingTests = new ArrayList<>();
+    @Override
+    final public Stream<? extends Arguments> provideArguments(ExtensionContext 
context) {
+        List<Arguments> lst = new ArrayList<>();
+        List<String> missingTests = new ArrayList<>();
 
-                        for (String key : OptionsList.getKeys()) {
-                        OptionCollectionTest.OptionTest test = 
testMap.get(key);
-                        if (test == null) {
-                                missingTests.add(key);
-                            } else {
-                                lst.add(Arguments.of(key, test));
-                            }
-                    }
-                if (!missingTests.isEmpty()) {
-                        System.out.println("The following tests are excluded: 
'" + String.join( "', '", missingTests ) + "'");
-                    }
-                return lst.stream();
+        for (String key : OptionsList.getKeys()) {
+            OptionCollectionTest.OptionTest test = testMap.get(key);
+            if (test == null) {
+                missingTests.add(key);
+            } else {
+                lst.add(Arguments.of(key, test));
             }
+        }
+        if (!missingTests.isEmpty()) {
+            System.out.println("The following tests are excluded: '" + 
String.join("', '", missingTests) + "'");
+        }
+        return lst.stream();
+    }
 }
diff --git 
a/apache-rat-core/src/test/java/org/apache/rat/test/utils/Resources.java 
b/apache-rat-core/src/test/java/org/apache/rat/test/utils/Resources.java
index fda2b589..8f52f287 100644
--- a/apache-rat-core/src/test/java/org/apache/rat/test/utils/Resources.java
+++ b/apache-rat-core/src/test/java/org/apache/rat/test/utils/Resources.java
@@ -47,12 +47,26 @@ public class Resources {
     private static final File RESOURCE_BASE_PATH = new 
File(SRC_MAIN_RESOURCES);
 
     /**
-     * Locates a test resource file in the class path.
+     * Locates a test resource file in the classpath.
      */
     public static File getResourceFile(String pResource) throws IOException {
-        return getResourceFromBase(TEST_RESOURCE_BASE_PATH, pResource);
+        File f = getResourceFromBase(TEST_RESOURCE_BASE_PATH, pResource);
+        if (!f.isFile()) {
+            throw new FileNotFoundException("Unable to locate resource file: " 
+ pResource);
+        }
+        return f;
     }
 
+    /**
+     * Locates a test resource directory in the classpath.
+     */
+    public static File getResourceDirectory(String pResource) throws 
IOException {
+        File f = getResourceFromBase(TEST_RESOURCE_BASE_PATH, pResource);
+        if (!f.isDirectory()) {
+            throw new FileNotFoundException("Unable to locate resource 
directory: " + pResource);
+        }
+        return f;
+    }
 
     /**
      * Locates a file in the unpacked example data archive.
@@ -66,14 +80,11 @@ public class Resources {
     }
 
     /**
-     * Try to load the given file from baseDir, in case of errors try to add
+     * Try to load the given file from baseDir. In case of errors try to add
      * module names to fix behaviour from within IntelliJ.
      */
     private static File getResourceFromBase(File baseDir, String pResource) 
throws IOException {
         File f = new File(baseDir, pResource);
-        if (!f.isFile()) {
-            throw new FileNotFoundException("Unable to locate resource file: " 
+ pResource);
-        }
         return f.getCanonicalFile();
     }
 
@@ -91,21 +102,21 @@ public class Resources {
     }
 
     /**
-     * Locates a resource file in the class path and returns an {@link 
InputStream}.
+     * Locates a resource file in the classpath and returns an {@link 
InputStream}.
      */
     public static InputStream getResourceStream(String pResource) throws 
IOException {
         return Files.newInputStream(getResourceFile(pResource).toPath());
     }
 
     /**
-     * Locates a resource file in the class path and returns a {@link Reader}.
+     * Locates a resource file in the classpath and returns a {@link Reader}.
      */
     public static Reader getResourceReader(String pResource) throws 
IOException {
         return new InputStreamReader(getResourceStream(pResource), 
StandardCharsets.UTF_8);
     }
 
     /**
-     * Locates a resource file in the class path and returns a
+     * Locates a resource file in the classpath and returns a
      * {@link BufferedReader}.
      */
     public static BufferedReader getBufferedReader(File file) throws 
IOException {
diff --git 
a/apache-rat-core/src/test/java/org/apache/rat/testhelpers/XmlUtils.java 
b/apache-rat-core/src/test/java/org/apache/rat/testhelpers/XmlUtils.java
index 67a9c0c7..e2a1bbe3 100644
--- a/apache-rat-core/src/test/java/org/apache/rat/testhelpers/XmlUtils.java
+++ b/apache-rat-core/src/test/java/org/apache/rat/testhelpers/XmlUtils.java
@@ -30,6 +30,7 @@ import java.io.StringReader;
 import java.lang.reflect.UndeclaredThrowableException;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
 
 import java.util.Map;
@@ -98,7 +99,8 @@ public final class XmlUtils {
     }
 
     public static boolean isPresent(Object source, XPath xPath, String xpath) 
throws XPathExpressionException {
-        return !xPath.compile(xpath).evaluate(source).equals("null");
+        Object node = xPath.compile(xpath).evaluate(source, 
XPathConstants.NODE);
+        return node != null;
     }
 
     public static List<Node> getNodes(Object source, XPath xPath, String 
xpath) throws XPathExpressionException {
@@ -173,10 +175,22 @@ public final class XmlUtils {
         Node node = XmlUtils.getNode(source, xPath, xpath);
         NamedNodeMap attr = node.getAttributes();
         node = attr.getNamedItem(attribute);
-        assertThat(node).as(attribute+" was not found").isNotNull();
+        assertThat(node).as(attribute + " was not found").isNotNull();
         return node.getNodeValue();
     }
 
+    public static Map<String, String> mapOf(String... parts) {
+        Map<String, String> map = new HashMap<>();
+        for (int i = 0; i < parts.length; i += 2) {
+            map.put(parts[i], parts[i+1]);
+        }
+        return map;
+    }
+
+    public static void assertAttributes(Object source, XPath xPath, String 
xpath, String... mapValues) throws XPathExpressionException {
+        assertAttributes(source, xPath, xpath, mapOf(mapValues));
+    }
+
     public static void assertAttributes(Object source, XPath xPath, String 
xpath, Map<String, String> attributes) throws XPathExpressionException {
         Node node = XmlUtils.getNode(source, xPath, xpath);
         NamedNodeMap attr = node.getAttributes();
@@ -186,4 +200,12 @@ public final class XmlUtils {
             assertThat(node.getNodeValue()).as(() -> entry.getKey() + " on " + 
xpath).isEqualTo(entry.getValue());
         }
     }
+
+    public static void assertIsPresent(Object source, XPath xPath, String 
xpath) throws XPathExpressionException {
+        assertThat(isPresent(source, xPath, xpath)).as("Presence of " + 
xpath).isTrue();
+    }
+
+    public static void assertIsNotPresent(Object source, XPath xPath, String 
xpath) throws XPathExpressionException {
+        assertThat(isPresent(source, xPath, xpath)).as("Non-presence of " + 
xpath).isFalse();
+    }
 }
diff --git 
a/apache-rat-plugin/src/main/java/org/apache/rat/mp/AbstractRatMojo.java 
b/apache-rat-plugin/src/main/java/org/apache/rat/mp/AbstractRatMojo.java
index 2603c50a..d5e70ed7 100644
--- a/apache-rat-plugin/src/main/java/org/apache/rat/mp/AbstractRatMojo.java
+++ b/apache-rat-plugin/src/main/java/org/apache/rat/mp/AbstractRatMojo.java
@@ -105,21 +105,21 @@ public abstract class AbstractRatMojo extends BaseRatMojo 
{
 
     /**
      * Whether to add the default list of license matchers.
-     * @deprecated By default, license matchers are added.  Use 
&lt;configurationNoDefaults&gt; to remove them.
+     * @deprecated @deprecated Use specific configuration under 
&lt;configuration&gt;.
      */
     @Deprecated
     @Parameter(property = "rat.addDefaultLicenseMatchers")
     private boolean addDefaultLicenseMatchers;
 
     /** The list of approved licenses
-     * @deprecated Use specific configuration under &lt;configuration&gt;.
+     * @deprecated @deprecated Use specific configuration under 
&lt;configuration&gt;.
      */
     @Deprecated
     @Parameter(required = false)
     private String[] approvedLicenses;
 
     /** The file of approved licenses
-     * @deprecated Use specific configuration under &lt;configuration&gt;.
+     * @deprecated @deprecated Use specific configuration under 
&lt;configuration&gt;.
      */
     @Deprecated
     @Parameter(property = "rat.approvedFile")
@@ -136,14 +136,14 @@ public abstract class AbstractRatMojo extends BaseRatMojo 
{
     private SimpleLicenseFamily[] licenseFamilies;
 
     /** The list of license definitions.
-     * @deprecated Deprecated for removal since 0.17: Use &lt;Config&gt; 
instead. See configuration file documentation.
+     * @deprecated Deprecated for removal since 0.17: Use specific 
configuration under &lt;configuration&gt;. See configuration file documentation.
      */
     @Deprecated
     @Parameter
     private Object[] licenses;
 
     /** The list of family definitions.
-     * @deprecated Use &lt;Configs&gt;.
+     * @deprecated Use specific configuration under &lt;configuration&gt;.
      */
     @Deprecated
     @Parameter
@@ -176,8 +176,9 @@ public abstract class AbstractRatMojo extends BaseRatMojo {
      * <li>configuration files for IDEA, see
      * <a href="#useIdeaDefaultExcludes">useIdeaDefaultExcludes</a></li>
      * </ul>
-     * @deprecated use 
&lt;inputExcludeStd&gt;&lt;exclude&gt;STANDARD_PATTERNS&lt;/exclude&gt;
-     * &lt;exclude&gt;STANDARD_SCM&lt;/exclude&gt;&lt;/inputExcludeStd&gt;
+     * @deprecated When set to true specifies that the STANDARD_PATTERNS are 
excluded, as are
+     * the STANDARD_SCMS patterns. Use the various InputExclude and 
InputInclude elements to
+     * explicitly specify what to include or exclude.
      */
     @Parameter(property = "rat.useDefaultExcludes", defaultValue = "true")
     @Deprecated
@@ -255,6 +256,10 @@ public abstract class AbstractRatMojo extends BaseRatMojo {
     @Parameter(defaultValue = "${project}", required = true, readonly = true)
     protected MavenProject project;
 
+    protected AbstractRatMojo() {
+        DefaultLog.setInstance(makeLog());
+    }
+
     /**
      * @return the Maven project.
      */
@@ -349,10 +354,9 @@ public abstract class AbstractRatMojo extends BaseRatMojo {
 
     private org.apache.rat.utils.Log makeLog() {
         return new org.apache.rat.utils.Log() {
-            private final org.apache.maven.plugin.logging.Log log = getLog();
-
             @Override
             public Level getLevel() {
+                final org.apache.maven.plugin.logging.Log log = getLog();
                 if (log.isDebugEnabled()) {
                     return Level.DEBUG;
                 }
@@ -370,6 +374,7 @@ public abstract class AbstractRatMojo extends BaseRatMojo {
 
             @Override
             public void log(final Level level, final String message, final 
Throwable throwable) {
+                final org.apache.maven.plugin.logging.Log log = getLog();
                 switch (level) {
                     case DEBUG:
                         if (throwable != null) {
@@ -406,6 +411,7 @@ public abstract class AbstractRatMojo extends BaseRatMojo {
 
             @Override
             public void log(final Level level, final String msg) {
+                final org.apache.maven.plugin.logging.Log log = getLog();
                 switch (level) {
                     case DEBUG:
                         log.debug(msg);
@@ -459,10 +465,9 @@ public abstract class AbstractRatMojo extends BaseRatMojo {
     }
 
     protected ReportConfiguration getConfiguration() throws 
MojoExecutionException {
+        Log log = DefaultLog.getInstance();
         if (reportConfiguration == null) {
-            DefaultLog.setInstance(makeLog());
             try {
-                Log log = DefaultLog.getInstance();
                 if (super.getLog().isDebugEnabled()) {
                     log.debug("Start BaseRatMojo Configuration options");
                     for (Map.Entry<String, List<String>> entry : 
args.entrySet()) {
@@ -476,7 +481,8 @@ public abstract class AbstractRatMojo extends BaseRatMojo {
 
                 setIncludeExclude();
 
-                ReportConfiguration config = 
OptionCollection.parseCommands(args().toArray(new String[0]),
+                getLog().warn("Basedir is : " + basedir);
+                ReportConfiguration config = 
OptionCollection.parseCommands(basedir, args().toArray(new String[0]),
                         o -> getLog().warn("Help option not supported"),
                         true);
                 reportDeprecatedProcessing();
@@ -498,7 +504,7 @@ public abstract class AbstractRatMojo extends BaseRatMojo {
                     }
                 }
                 if (families != null || 
getDeprecatedConfigs().findAny().isPresent()) {
-                    if (super.getLog().isDebugEnabled()) {
+                    if (log.isEnabled(Log.Level.DEBUG)) {
                         log.debug(format("%s license families loaded from 
pom", families.length));
                     }
                     Consumer<ILicenseFamily> logger = 
super.getLog().isDebugEnabled() ? l -> log.debug(format("Family: %s", l))
@@ -519,10 +525,10 @@ public abstract class AbstractRatMojo extends BaseRatMojo 
{
                 }
 
                 if (licenses != null) {
-                    if (super.getLog().isDebugEnabled()) {
+                    if (log.isEnabled(Log.Level.DEBUG)) {
                         log.debug(format("%s licenses loaded from pom", 
licenses.length));
                     }
-                    Consumer<ILicense> logger = 
super.getLog().isDebugEnabled() ? l -> log.debug(format("License: %s", l))
+                    Consumer<ILicense> logger = log.isEnabled(Log.Level.DEBUG) 
? l -> log.debug(format("License: %s", l))
                             : l -> {
                     };
                     Consumer<ILicense> addApproved = (approvedLicenses == null 
|| approvedLicenses.length == 0)
diff --git 
a/apache-rat-plugin/src/test/java/org/apache/rat/mp/OptionMojoTest.java 
b/apache-rat-plugin/src/test/java/org/apache/rat/mp/OptionMojoTest.java
index 07c461da..5f5eceb6 100644
--- a/apache-rat-plugin/src/test/java/org/apache/rat/mp/OptionMojoTest.java
+++ b/apache-rat-plugin/src/test/java/org/apache/rat/mp/OptionMojoTest.java
@@ -29,9 +29,15 @@ import org.apache.rat.test.AbstractOptionsProvider;
 import org.apache.rat.OptionCollectionTest;
 import org.apache.rat.ReportConfiguration;
 import org.apache.rat.plugin.BaseRatMojo;
+import org.apache.rat.utils.DefaultLog;
 import 
org.codehaus.plexus.component.configurator.ComponentConfigurationException;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.condition.EnabledOnOs;
+import org.junit.jupiter.api.condition.OS;
+import org.junit.jupiter.api.io.TempDir;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.ArgumentsProvider;
 import org.junit.jupiter.params.provider.ArgumentsSource;
@@ -41,7 +47,6 @@ import java.io.FileWriter;
 import java.io.IOException;
 import java.lang.reflect.Method;
 import java.nio.charset.StandardCharsets;
-import java.nio.file.FileSystems;
 import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.List;
@@ -51,30 +56,51 @@ import static org.junit.jupiter.api.Assertions.fail;
 
 
 public class OptionMojoTest {
-    static final Path testPath = FileSystems.getDefault().getPath("target", 
"optionTest");
+
+    @TempDir
+    static Path testPath;
+
     static String POM_FMT;
 
     @BeforeAll
     public static void makeDirs() throws IOException {
-        testPath.toFile().mkdirs();
         POM_FMT = IOUtils.resourceToString("/optionTest/pom.tpl", 
StandardCharsets.UTF_8);
     }
 
+    @AfterAll
+    static void preserveData() {
+         AbstractOptionsProvider.preserveData(testPath.toFile(), "optionTest");
+    }
+
+    /**
+     * This method is a known workaround for
+     * {@link <a href="https://github.com/junit-team/junit5/issues/2811";>junit 
5 issue #2811</a> }.
+     */
+    @AfterEach
+    @EnabledOnOs(OS.WINDOWS)
+    void cleanUp() {
+        System.gc();
+    }
+
     @ParameterizedTest
-    @ArgumentsSource(OptionsProvider.class)
-    public void testOptionsUpdateConfig(String name, 
OptionCollectionTest.OptionTest test) {
+    @ArgumentsSource(MojoOptionsProvider.class)
+    void testOptionsUpdateConfig(String name, OptionCollectionTest.OptionTest 
test) {
+        DefaultLog.getInstance().info("Running " + name);
         test.test();
     }
 
-    public static class OptionsProvider extends AbstractOptionsProvider 
implements ArgumentsProvider  {
+    static class MojoOptionsProvider extends AbstractOptionsProvider 
implements ArgumentsProvider  {
 
         private RatCheckMojo mojo = null;
-        public OptionsProvider() {
-            super(BaseRatMojo.unsupportedArgs());
+
+        public MojoOptionsProvider() {
+            super(BaseRatMojo.unsupportedArgs(), testPath.toFile());
         }
 
-       private RatCheckMojo generateMojo(Pair<Option,String[]>... args) throws 
IOException {
-           MavenOption keyOption = new MavenOption(args[0].getKey() == null ? 
Option.builder().longOpt("no-option").build() : args[0].getKey());
+       private RatCheckMojo generateMojo(List<Pair<Option, String[]>> args) 
throws IOException {
+           MavenOption keyOption = new MavenOption(args.get(0).getKey() == 
null ?
+                   Option.builder().longOpt("no-option").build() :
+                   args.get(0).getKey());
            List<String> mavenOptions = new ArrayList<>();
            for (Pair<Option, String[]> pair : args) {
                if (pair.getKey() != null) {
@@ -84,7 +110,6 @@ public class OptionMojoTest {
                            mavenOptions.add(new 
MavenOption(pair.getKey()).xmlNode(value));
                        }
                    } else {
-                       MavenOption mavenOption = new 
MavenOption(pair.getKey());
                        mavenOptions.add(new 
MavenOption(pair.getKey()).xmlNode("true"));
                    }
                }
@@ -111,9 +136,10 @@ public class OptionMojoTest {
        }
 
         @Override
-        protected ReportConfiguration generateConfig(Pair<Option, String[]>... 
args) throws IOException {
+        protected final ReportConfiguration generateConfig(List<Pair<Option, 
String[]>> args) throws IOException {
             try {
                 this.mojo = generateMojo(args);
+                
AbstractOptionsProvider.setup(this.mojo.getProject().getBasedir());
                 return mojo.getConfiguration();
             } catch (MojoExecutionException e) {
                 throw new IOException(e.getMessage(), e);
@@ -124,65 +150,6 @@ public class OptionMojoTest {
         protected void helpTest() {
             fail("Should not call help");
         }
-
-/*
-        private void execExcludeTest(Option option, String[] args) {
-
-            try {
-                ReportConfiguration config = 
generateConfig(ImmutablePair.of(option, args));
-                File workingDir = mojo.getProject().getBasedir();
-                for (String fn : new String[] {"some.foo", "B.bar", "justbaz", 
"notbaz"}) {
-                    try (FileOutputStream fos = new FileOutputStream(new 
File(workingDir, fn))) {
-                        fos.write("Hello world".getBytes());
-                    }
-                }
-
-                assertThat(ds.getExcludedList()).contains("some.foo");
-                assertThat(ds.getExcludedList()).contains("B.bar");
-                assertThat(ds.getExcludedList()).contains("justbaz");
-                assertThat(ds.getIncludedList()).contains("notbaz");
-            } catch (IOException | MojoExecutionException e) {
-                fail(e.getMessage(), e);
-            }
-        }
-
-        @Override
-        protected void excludeTest() {
-            String[] args = { "*.foo", "*.bar", "justbaz"};
-            execExcludeTest(Arg.EXCLUDE.find("exclude"), args);
-        }
-
-        @Override
-        protected void inputExcludeTest() {
-            String[] args = { "*.foo", "*.bar", "justbaz"};
-            execExcludeTest(Arg.EXCLUDE.find("input-exclude"), args);
-        }
-
-        private void excludeFileTest(Option option) {
-            File outputFile = new File(baseDir, "exclude.txt");
-            try (FileWriter fw = new FileWriter(outputFile)) {
-                fw.write("*.foo");
-                fw.write(System.lineSeparator());
-                fw.write("*.bar");
-                fw.write(System.lineSeparator());
-                fw.write("justbaz");
-                fw.write(System.lineSeparator());
-            } catch (IOException e) {
-                throw new RuntimeException(e);
-            }
-            execExcludeTest(option, new String[] {outputFile.getPath()});
-        }
-
-        protected void excludeFileTest() {
-            excludeFileTest(Arg.EXCLUDE_FILE.find("exclude-file"));
-        }
-
-
-        protected void inputExcludeFileTest() {
-            excludeFileTest(Arg.EXCLUDE_FILE.find("input-exclude-file"));
-        }
-
- */
     }
 
     public abstract static class SimpleMojoTestcase extends 
BetterAbstractMojoTestCase {
diff --git a/apache-rat-tasks/src/main/java/org/apache/rat/anttasks/Report.java 
b/apache-rat-tasks/src/main/java/org/apache/rat/anttasks/Report.java
index 493b032c..156d7779 100644
--- a/apache-rat-tasks/src/main/java/org/apache/rat/anttasks/Report.java
+++ b/apache-rat-tasks/src/main/java/org/apache/rat/anttasks/Report.java
@@ -352,7 +352,7 @@ public class Report extends BaseAntTask {
             boolean helpLicenses = !getValues(Arg.HELP_LICENSES).isEmpty();
             removeKey(Arg.HELP_LICENSES);
 
-            final ReportConfiguration configuration = 
OptionCollection.parseCommands(args().toArray(new String[0]),
+            final ReportConfiguration configuration = 
OptionCollection.parseCommands(new File("."), args().toArray(new String[0]),
                     o -> DefaultLog.getInstance().warn("Help option not 
supported"),
                     true);
             if (getValues(Arg.OUTPUT_FILE).isEmpty()) {
diff --git 
a/apache-rat-tasks/src/test/java/org/apache/rat/anttasks/ReportOptionTest.java 
b/apache-rat-tasks/src/test/java/org/apache/rat/anttasks/ReportOptionTest.java
index 6b2432c0..cc465da3 100644
--- 
a/apache-rat-tasks/src/test/java/org/apache/rat/anttasks/ReportOptionTest.java
+++ 
b/apache-rat-tasks/src/test/java/org/apache/rat/anttasks/ReportOptionTest.java
@@ -16,6 +16,8 @@
  */
 package org.apache.rat.anttasks;
 
+import java.nio.file.Path;
+import java.util.List;
 import org.apache.commons.cli.Option;
 import org.apache.commons.lang3.tuple.ImmutablePair;
 import org.apache.commons.lang3.tuple.Pair;
@@ -25,7 +27,8 @@ import org.apache.rat.ReportConfiguration;
 import org.apache.rat.testhelpers.TestingLog;
 import org.apache.rat.utils.DefaultLog;
 import org.apache.rat.utils.Log;
-import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.io.TempDir;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.ArgumentsProvider;
 import org.junit.jupiter.params.provider.ArgumentsSource;
@@ -43,18 +46,20 @@ import static org.junit.jupiter.api.Assertions.fail;
  * Tests to ensure the option setting works correctly.
  */
 public class ReportOptionTest  {
-    // devhint: we do want to keep data in case of test failures, thus do not 
use TempDir here
-    static final File baseDir = new File("target/optionTest");
+    @TempDir
+    static Path testPath;
+
     static ReportConfiguration reportConfiguration;
 
-    @BeforeAll
-    public static void makeDirs() {
-        baseDir.mkdirs();
+    @AfterAll
+    static void preserveData() {
+        AbstractOptionsProvider.preserveData(testPath.toFile(), "optionTest");
     }
 
     @ParameterizedTest
-    @ArgumentsSource(OptionsProvider.class)
+    @ArgumentsSource(AntOptionsProvider.class)
     public void testOptionsUpdateConfig(String name, 
OptionCollectionTest.OptionTest test) {
+        DefaultLog.getInstance().info("Running " + name);
         test.test();
     }
 
@@ -68,16 +73,16 @@ public class ReportOptionTest  {
         }
     }
 
-    static class OptionsProvider extends AbstractOptionsProvider implements 
ArgumentsProvider {
+    final static class AntOptionsProvider extends AbstractOptionsProvider 
implements ArgumentsProvider {
 
         final AtomicBoolean helpCalled = new AtomicBoolean(false);
 
-        public OptionsProvider() {
-            super(BaseAntTask.unsupportedArgs());
+        public AntOptionsProvider() {
+            super(BaseAntTask.unsupportedArgs(), testPath.toFile());
         }
 
-        protected ReportConfiguration generateConfig(Pair<Option, String[]>... 
args) {
-            BuildTask task = args[0].getKey() == null ? new BuildTask() : new 
BuildTask(args[0].getKey());
+        protected ReportConfiguration generateConfig(List<Pair<Option, 
String[]>> args) {
+            BuildTask task = args.get(0).getKey() == null ? new BuildTask() : 
new BuildTask(args.get(0).getKey());
             task.setUp(args);
             task.buildRule.executeTarget(task.name);
             return reportConfiguration;
@@ -94,6 +99,8 @@ public class ReportOptionTest  {
             Log oldLog = DefaultLog.setInstance(testLog);
             try {
                 ReportConfiguration config = 
generateConfig(ImmutablePair.of(HELP_LICENSES.option(), null));
+            } catch (IOException e) {
+                throw new RuntimeException(e);
             } finally {
                 DefaultLog.setInstance(oldLog);
             }
@@ -118,10 +125,10 @@ public class ReportOptionTest  {
                 antFile = new File(baseDir, name + ".xml");
             }
 
-            public void setUp(Pair<Option, String[]>... args) {
+            public final void setUp(List<Pair<Option, String[]>> args) {
                 StringBuilder childElements = new StringBuilder();
                 StringBuilder attributes = new StringBuilder();
-                if (args[0].getKey() != null) {
+                if (args.get(0).getKey() != null) {
                     for (Pair<Option, String[]> pair : args) {
                         AntOption argOption = new AntOption(pair.getKey());
 
diff --git 
a/apache-rat-tools/src/main/java/org/apache/rat/tools/Documentation.java 
b/apache-rat-tools/src/main/java/org/apache/rat/tools/Documentation.java
index a96fe9ae..0ebfc3d8 100644
--- a/apache-rat-tools/src/main/java/org/apache/rat/tools/Documentation.java
+++ b/apache-rat-tools/src/main/java/org/apache/rat/tools/Documentation.java
@@ -18,6 +18,7 @@
  */
 package org.apache.rat.tools;
 
+import java.io.File;
 import java.io.IOException;
 import java.io.Writer;
 
@@ -45,7 +46,7 @@ public final class Documentation {
      * @throws IOException on error
      */
     public static void main(final String[] args) throws IOException {
-        ReportConfiguration config = OptionCollection.parseCommands(args, 
Documentation::printUsage, true);
+        ReportConfiguration config = OptionCollection.parseCommands(new 
File("."), args, Documentation::printUsage, true);
         if (config != null) {
             try (Writer writer = config.getWriter().get()) {
                 new Licenses(config, writer).output();

Reply via email to