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

Claudenw pushed a commit to branch create-output-object
in repository https://gitbox.apache.org/repos/asf/creadur-rat.git


The following commit(s) were added to refs/heads/create-output-object by this 
push:
     new d9a70d94 added tests
d9a70d94 is described below

commit d9a70d94950b21b94879b0d953a9da0ea15089ae
Author: Claude Warren <[email protected]>
AuthorDate: Mon Jul 6 15:18:57 2026 +0100

    added tests
---
 apache-rat-core/pom.xml                            |   5 +
 .../java/org/apache/rat/ReportConfiguration.java   |  26 ++-
 .../src/main/java/org/apache/rat/Reporter.java     | 125 +++++++------
 .../rat/config/exclusion/ExclusionProcessor.java   |  54 +++++-
 .../org/apache/rat/utils/StandardXmlFactory.java   |  33 ++++
 .../src/test/java/org/apache/rat/OutputTest.java   | 160 +++++++++++++++++
 .../org/apache/rat/ReportConfigurationTest.java    |  85 ++++++++-
 .../src/test/java/org/apache/rat/ReporterTest.java |  38 ++--
 .../config/exclusion/ExclusionProcessorTest.java   | 200 +++++++++++++++++++++
 .../rat/report/claim/ClaimStatisticTest.java       | 148 +++++++++++++++
 pom.xml                                            |  13 ++
 11 files changed, 801 insertions(+), 86 deletions(-)

diff --git a/apache-rat-core/pom.xml b/apache-rat-core/pom.xml
index 21f0c583..06167004 100644
--- a/apache-rat-core/pom.xml
+++ b/apache-rat-core/pom.xml
@@ -263,5 +263,10 @@
       <artifactId>jimfs</artifactId>
       <scope>test</scope>
     </dependency>
+    <dependency>
+      <groupId>org.xmlunit</groupId>
+      <artifactId>xmlunit-assertj3</artifactId>
+      <scope>test</scope>
+    </dependency>
   </dependencies>
 </project>
diff --git 
a/apache-rat-core/src/main/java/org/apache/rat/ReportConfiguration.java 
b/apache-rat-core/src/main/java/org/apache/rat/ReportConfiguration.java
index ab40686a..4c9f143c 100644
--- a/apache-rat-core/src/main/java/org/apache/rat/ReportConfiguration.java
+++ b/apache-rat-core/src/main/java/org/apache/rat/ReportConfiguration.java
@@ -39,6 +39,7 @@ import java.util.Map;
 import java.util.Objects;
 import java.util.SortedSet;
 import java.util.function.Consumer;
+import java.util.stream.Stream;
 
 import javax.xml.parsers.DocumentBuilder;
 
@@ -152,7 +153,7 @@ public class ReportConfiguration {
     private final List<Reportable> reportables;
 
     /**
-     * A predicate to test if a path should be included in the processing.
+     * The exclusion processor that determines if a file is included or 
excluded.
      */
     private final ExclusionProcessor exclusionProcessor;
 
@@ -258,6 +259,16 @@ public class ReportConfiguration {
         return builder;
     }
 
+    // for testing access
+    Iterable<File> sources() {
+        return sources;
+    }
+
+    // for testing access
+    Stream<DocumentName> reportables() {
+        return reportables.stream().map(Reportable::name);
+    }
+
     /**
      * Gets the matcher that matches generated text.
      * @return the matcher that matches generated text.
@@ -440,6 +451,14 @@ public class ReportConfiguration {
         exclusionProcessor.addIncludedMatcher(new 
DocumentNameMatcher(fileFilter));
     }
 
+    /**
+     * Includes files that match a DocumentNameMatcher.
+     * @param matcher the DocumentNameMatcher to match.
+     */
+    public void addIncludedMatcher(final DocumentNameMatcher matcher) {
+        exclusionProcessor.addIncludedMatcher(matcher);
+    }
+
     /**
      * Add file patterns that are to be included. These patterns override any 
exclusion of
      * the same files.
@@ -458,6 +477,11 @@ public class ReportConfiguration {
         return exclusionProcessor.getNameMatcher(baseDir);
     }
 
+    // visible for testing.
+    ExclusionProcessor getExclusionProcessor() {
+        return exclusionProcessor;
+    }
+
     /**
      * Gets the IOSupplier with the style sheet.
      * @return the Supplier of the InputStream that is the XSLT style sheet to 
style
diff --git a/apache-rat-core/src/main/java/org/apache/rat/Reporter.java 
b/apache-rat-core/src/main/java/org/apache/rat/Reporter.java
index 4ccd3c72..9b037efe 100644
--- a/apache-rat-core/src/main/java/org/apache/rat/Reporter.java
+++ b/apache-rat-core/src/main/java/org/apache/rat/Reporter.java
@@ -111,6 +111,7 @@ public class Reporter {
 
     /**
      * Gets the output from the last {@link #execute} call or {@code null} if 
{@link #execute} has not been called.
+     *
      * @return the output
      */
     public Output getOutput() {
@@ -119,8 +120,9 @@ public class Reporter {
 
     /**
      * Lists the licenses on the configured output stream.
+     *
      * @param configuration The configuration for the system
-     * @param filter the license filter that specifies which licenses to 
output.
+     * @param filter        the license filter that specifies which licenses 
to output.
      * @throws IOException if PrintWriter can not be retrieved from 
configuration.
      */
     public static void listLicenses(final ReportConfiguration configuration, 
final LicenseFilter filter) throws IOException {
@@ -137,7 +139,9 @@ public class Reporter {
      * The output from a report run.
      */
     public static final class Output {
-        /** The XML output document */
+        /**
+         * The XML output document
+         */
         private final Document document;
         /**
          * The claim statics from the execution that generated the document.
@@ -151,6 +155,7 @@ public class Reporter {
 
         /**
          * Create an output with statistics.
+         *
          * @param builder the Builder
          */
         private Output(final Builder builder) {
@@ -161,98 +166,104 @@ public class Reporter {
 
         public static Builder builder() {
             return new Builder();
-    }
+        }
 
         /**
          * Gets the document that was generated during execution.
+         *
          * @return the document that was generated during execution.
          */
         public Document getDocument() {
             return document;
         }
 
-    /**
-     * Get the claim statistics from the run.
-     *
-     * @return the claim statistics.
-     */
-    public ClaimStatistic getStatistic() {
-        return statistic;
-    }
+        /**
+         * Get the claim statistics from the run.
+         *
+         * @return the claim statistics.
+         */
+        public ClaimStatistic getStatistic() {
+            return statistic;
+        }
 
         public ReportConfiguration getConfiguration() {
             return configuration;
         }
-    /**
+
+        /**
          * Formats the report to the output and using the stylesheet found in 
the report configuration.
          *
          * @param config s RAT report configuration.
-     * @throws RatException on error.
-     */
+         * @throws RatException on error.
+         */
         public void format(final ReportConfiguration config) throws 
RatException {
             format(config.getStyleSheet(), config.getOutput());
-    }
+        }
 
-    /**
+        /**
          * Formats the report to the specified output using the stylesheet. It 
is safe to call this method more than once
-     * in order to generate multiple reports from the same run.
-     *
-     * @param stylesheet the style sheet to use for XSLT formatting.
-     * @param output the output stream to write to.
-     * @throws RatException on error.
-     */
+         * in order to generate multiple reports from the same run.
+         *
+         * @param stylesheet the style sheet to use for XSLT formatting.
+         * @param output     the output stream to write to.
+         * @throws RatException on error.
+         */
         public void format(final IOSupplier<InputStream> stylesheet, final 
IOSupplier<OutputStream> output) throws RatException {
 
-        try (OutputStream out = output.get();
-             InputStream styleIn = stylesheet.get()) {
-            StandardXmlFactory.createTransformer(styleIn).transform(new 
DOMSource(document),
-                    new StreamResult(new OutputStreamWriter(out, 
StandardCharsets.UTF_8)));
-        } catch (TransformerException | IOException e) {
-            throw new RatException(e);
+            try (OutputStream out = output.get();
+                 InputStream styleIn = stylesheet.get()) {
+                StandardXmlFactory.createTransformer(styleIn).transform(new 
DOMSource(document),
+                        new StreamResult(new OutputStreamWriter(out, 
StandardCharsets.UTF_8)));
+            } catch (TransformerException | IOException e) {
+                throw new RatException(e);
+            }
         }
-    }
 
-    /**
-     * Lists the licenses on the configured output stream.
-     * @param configuration The configuration for the system
-     * @param filter the license filter that specifies which licenses to 
output.
-     * @throws IOException if PrintWriter can not be retrieved from 
configuration.
-     */
-    public static void listLicenses(final ReportConfiguration configuration, 
final LicenseFilter filter) throws IOException {
-        try (PrintWriter pw = configuration.getWriter().get()) {
-            pw.format("Licenses (%s):%n", filter);
-            configuration.getLicenses(filter)
-                    .forEach(lic -> pw.format(LICENSE_FORMAT, 
lic.getLicenseFamily().getFamilyCategory(),
-                            lic.getLicenseFamily().getFamilyName(), 
lic.getNote()));
-            pw.println();
+        /**
+         * Lists the licenses on the configured output stream.
+         *
+         * @param configuration The configuration for the system
+         * @param filter        the license filter that specifies which 
licenses to output.
+         * @throws IOException if PrintWriter can not be retrieved from 
configuration.
+         */
+        public static void listLicenses(final ReportConfiguration 
configuration, final LicenseFilter filter) throws IOException {
+            try (PrintWriter pw = configuration.getWriter().get()) {
+                pw.format("Licenses (%s):%n", filter);
+                configuration.getLicenses(filter)
+                        .forEach(lic -> pw.format(LICENSE_FORMAT, 
lic.getLicenseFamily().getFamilyCategory(),
+                                lic.getLicenseFamily().getFamilyName(), 
lic.getNote()));
+                pw.println();
+            }
         }
-    }
 
-    /**
-     * Writes a text summary of issues with the run.
-     * @param appendable the appendable to write to.
-     * @throws IOException on error.
-     */
-    public void writeSummary(final Appendable appendable) throws IOException {
-        appendable.append("RAT summary:").append(System.lineSeparator());
-        for (ClaimStatistic.Counter counter : ClaimStatistic.Counter.values()) 
{
-            appendable.append("  ").append(counter.displayName()).append(":  ")
+        /**
+         * Writes a text summary of issues with the run.
+         *
+         * @param appendable the appendable to write to.
+         * @throws IOException on error.
+         */
+        public void writeSummary(final Appendable appendable) throws 
IOException {
+            appendable.append("RAT summary:").append(System.lineSeparator());
+            for (ClaimStatistic.Counter counter : 
ClaimStatistic.Counter.values()) {
+                appendable.append("  
").append(counter.displayName()).append(":  ")
                         
.append(Integer.toString(statistic.getCounter(counter)))
-                    .append(System.lineSeparator());
+                        .append(System.lineSeparator());
+            }
         }
-    }
 
         public static final class Builder {
-            /** The document that was generated */
+            /**
+             * The document that was generated
+             */
             private Document document;
             /**
              * The claim statistic from the execution that generated the 
document.
              * May be empty if the Document was read from disk.
              */
             private ClaimStatistic statistic;
-    /**
+            /**
              * The configuration that generated the document
-     */
+             */
             private ReportConfiguration configuration;
 
             public Builder document(final Document document) {
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 2a608584..b8ade20a 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
@@ -20,6 +20,7 @@ package org.apache.rat.config.exclusion;
 
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
@@ -85,7 +86,47 @@ public class ExclusionProcessor {
         return new Serde();
     }
 
-    /** Reset the {@link #lastMatcher} and {@link #lastMatcherBaseDir} to 
start again */
+    /* the folloing set of methods are here for testing purposes */
+
+    Set<String> getExcludedPatterns() {
+        return new HashSet(excludedPatterns);
+    }
+
+    Collection<DocumentNameMatcher> getExcludedPaths() {
+        return new ArrayList(excludedPaths);
+    }
+
+    Set<String> getIncludedPatterns() {
+        return new HashSet<>(includedPatterns);
+    }
+
+    Collection<DocumentNameMatcher> getIncludedPaths() {
+        return new ArrayList(includedPaths);
+    }
+
+    Collection<StandardCollection> getFileProcessors() {
+        return new HashSet<>(fileProcessors);
+    }
+
+    Set<StandardCollection> getIncludedCollections() {
+        return new HashSet(includedCollections);
+    }
+
+    Set<StandardCollection> getExcludedCollections() {
+        return new HashSet(excludedCollections);
+    }
+
+    DocumentNameMatcher getLastMatcher() {
+        return lastMatcher;
+    }
+
+    DocumentName getLastMatcherBaseDir() {
+        return lastMatcherBaseDir;
+    }
+
+    /**
+     * Reset the {@link #lastMatcher} and {@link #lastMatcherBaseDir} to start 
again
+     */
     private void resetLastMatcher() {
         lastMatcher = null;
         lastMatcherBaseDir = null;
@@ -97,9 +138,11 @@ public class ExclusionProcessor {
      * @return this
      */
     public ExclusionProcessor addIncludedPatterns(final Iterable<String> 
patterns) {
+        if (patterns != null) {
         DefaultLog.getInstance().debug(format("Including patterns: %s", 
String.join(", ", patterns)));
         patterns.forEach(includedPatterns::add);
         resetLastMatcher();
+        }
         return this;
     }
 
@@ -150,9 +193,11 @@ public class ExclusionProcessor {
      * @return this
      */
     public ExclusionProcessor addExcludedPatterns(final Iterable<String> 
patterns) {
+        if (patterns != null) {
         DefaultLog.getInstance().debug(format("Excluding patterns: %s", 
String.join(", ", patterns)));
         patterns.forEach(excludedPatterns::add);
         resetLastMatcher();
+        }
         return this;
     }
 
@@ -188,8 +233,10 @@ public class ExclusionProcessor {
         for (DocumentNameMatcher nameMatcher : excludedPaths) {
             appendable.append(format("Excluding %s.%n", 
nameMatcher.toString()));
         }
+        for (DocumentNameMatcher nameMatcher : includedPaths) {
+            appendable.append(format("Including %s.%n", 
nameMatcher.toString()));
+    }
     }
-
 
     /**
      * Excludes the files/directories specified by a StandardCollection.
@@ -214,7 +261,7 @@ public class ExclusionProcessor {
     public DocumentNameMatcher getNameMatcher(final DocumentName basedir) {
         // if lastMatcher is not set or the basedir is not the same as the 
last one then
         // we have to regenerate the matching document.
-        // Otherwise we can just return the lastMatcher since there is no 
change.
+        // Otherwise, we can just return the lastMatcher since there is no 
change.
         if (lastMatcher == null || !basedir.equals(lastMatcherBaseDir)) {
             lastMatcherBaseDir = basedir;
 
@@ -347,6 +394,7 @@ public class ExclusionProcessor {
             }
         }
     }
+
     /**
      * Serializes and deserializes the ExclusionProcessor to XML document
      */
diff --git 
a/apache-rat-core/src/main/java/org/apache/rat/utils/StandardXmlFactory.java 
b/apache-rat-core/src/main/java/org/apache/rat/utils/StandardXmlFactory.java
index 3ec9849a..c14abc4c 100644
--- a/apache-rat-core/src/main/java/org/apache/rat/utils/StandardXmlFactory.java
+++ b/apache-rat-core/src/main/java/org/apache/rat/utils/StandardXmlFactory.java
@@ -18,7 +18,13 @@
  */
 package org.apache.rat.utils;
 
+import org.w3c.dom.Document;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
 import java.io.InputStream;
+import java.io.StringWriter;
 
 import javax.xml.XMLConstants;
 import javax.xml.parsers.DocumentBuilder;
@@ -27,7 +33,10 @@ import javax.xml.parsers.ParserConfigurationException;
 import javax.xml.transform.OutputKeys;
 import javax.xml.transform.Transformer;
 import javax.xml.transform.TransformerConfigurationException;
+import javax.xml.transform.TransformerException;
 import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
 import javax.xml.transform.stream.StreamSource;
 
 /**
@@ -94,4 +103,28 @@ public final class StandardXmlFactory {
             throw new IllegalStateException("No XML parser defined", e);
         }
     }
+
+    /**
+     * Write an XML document to a file.
+     * @param document the document to write
+     * @param file the file to write to.
+     */
+    public static void writeDocument(final Document document, final File file) 
throws IOException, TransformerException {
+        DOMSource source = new DOMSource(document);
+        FileWriter writer = new FileWriter(file);
+        StreamResult result = new StreamResult(writer);
+        createTransformer().transform(source, result);
+    }
+
+    /**
+     * Write an XML document to a file.
+     * @param document the document to write.
+     */
+    public static String serializeDocument(final Document document) throws 
TransformerException {
+        DOMSource source = new DOMSource(document);
+        StringWriter writer = new StringWriter();
+        StreamResult result = new StreamResult(writer);
+        createTransformer().transform(source, result);
+        return writer.toString();
+    }
 }
diff --git a/apache-rat-core/src/test/java/org/apache/rat/OutputTest.java 
b/apache-rat-core/src/test/java/org/apache/rat/OutputTest.java
new file mode 100644
index 00000000..8e788af5
--- /dev/null
+++ b/apache-rat-core/src/test/java/org/apache/rat/OutputTest.java
@@ -0,0 +1,160 @@
+/*
+ * 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   *
+ *                                                              *
+ *   https://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;
+
+import org.apache.rat.commandline.StyleSheets;
+import org.apache.rat.config.AddLicenseHeaders;
+import org.apache.rat.config.exclusion.StandardCollection;
+import org.apache.rat.config.results.ClaimValidator;
+import org.apache.rat.document.DocumentName;
+import org.apache.rat.document.DocumentNameMatcher;
+import org.apache.rat.license.LicenseSetFactory;
+import org.apache.rat.report.claim.ClaimStatistic;
+import org.apache.rat.report.claim.ClaimStatisticTest;
+import org.apache.rat.testhelpers.FileUtils;
+import org.apache.rat.utils.StandardXmlFactory;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.w3c.dom.Document;
+import org.xml.sax.SAXException;
+import org.xmlunit.assertj3.XmlAssert;
+
+import javax.xml.transform.TransformerException;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StringWriter;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+
+class OutputTest {
+
+    static Path tempPath;
+
+    @BeforeAll
+    static void setup() throws IOException {
+        tempPath = Files.createTempDirectory("outputTest").toAbsolutePath();
+    }
+
+    @AfterAll
+    static void teardown() {
+        FileUtils.delete(tempPath.toFile());
+    }
+
+    @Test
+    void documentReadingTest() throws IOException, SAXException, 
TransformerException {
+        Path testPath = tempPath.resolve("documentReading");
+        File testFile = testPath.toFile();
+        FileUtils.mkDir(testFile);
+        DocumentName workingDirectory = 
DocumentName.builder(testPath.toFile()).setBaseName(testFile).build();
+        Document document;
+        DocumentName documentFile = workingDirectory.resolve("document.xml");
+        try (InputStream inputStream = 
OutputTest.class.getClassLoader().getResourceAsStream("XmlOutputExamples/elements.xml"))
 {
+            document = StandardXmlFactory.documentBuilder().parse(inputStream);
+            StandardXmlFactory.writeDocument(document, documentFile.asFile());
+        }
+
+        Reporter.Output.Builder builder = 
Reporter.Output.builder().document(documentFile.getName(), workingDirectory);
+
+        Reporter.Output output = builder.build();
+        XmlAssert.assertThat(output.getDocument()).and(document)
+                .ignoreWhitespace()
+                .areIdentical();
+    }
+
+    @Test
+    void stasticReadingTest() throws IOException {
+        Path testPath = tempPath.resolve("statisticReading");
+        File testFile = testPath.toFile();
+        FileUtils.mkDir(testFile);
+        DocumentName workingDirectory = 
DocumentName.builder(testPath.toFile()).setBaseName(testFile).build();
+        DocumentName documentFile = workingDirectory.resolve("statistic.xml");
+
+        ClaimStatistic underTest = new ClaimStatistic();
+        underTest.incLicenseCategoryCount("familyCagegory", 1);
+        underTest.incCounter(ClaimStatistic.Counter.APPROVED, 2);
+        underTest.incCounter(org.apache.rat.api.Document.Type.IGNORED, 3);
+        underTest.incLicenseNameCount("licenseName", 4);
+
+        ClaimStatistic.Serde serde = underTest.serde();
+        StringWriter stringWriter = new StringWriter();
+        serde.serialize(stringWriter);
+        try (FileOutputStream fos = new 
FileOutputStream(documentFile.asFile())) {
+            
fos.write(stringWriter.toString().getBytes(StandardCharsets.UTF_8));
+        }
+
+        Reporter.Output.Builder builder = 
Reporter.Output.builder().statistic(documentFile.getName(), workingDirectory);
+
+        Reporter.Output output = builder.build();
+        ClaimStatisticTest.assertSame(output.getStatistic(), underTest);
+    }
+
+    @Test
+    void configurationReadingTest() throws IOException {
+        Path testPath = tempPath.resolve("configurationReading");
+        File testFile = testPath.toFile();
+        FileUtils.mkDir(testFile);
+        DocumentName workingDirectory = 
DocumentName.builder(testPath.toFile()).setBaseName(testFile).build();
+        DocumentName documentFile = 
workingDirectory.resolve("configuration.xml");
+
+        ReportConfiguration underTest = new ReportConfiguration();
+        underTest.setAddLicenseHeaders(AddLicenseHeaders.FORCED);
+        underTest.listFamilies(LicenseSetFactory.LicenseFilter.APPROVED);
+        underTest.listLicenses(LicenseSetFactory.LicenseFilter.ALL);
+        underTest.setDryRun(true);
+        
underTest.setArchiveProcessing(ReportConfiguration.Processing.NOTIFICATION);
+        
underTest.setStandardProcessing(ReportConfiguration.Processing.ABSENCE);
+        underTest.setStyleSheet(StyleSheets.MISSING_HEADERS.getStyleSheet());
+        underTest.setOut(new File("/some/file/somewhere"));
+        underTest.setCopyrightMessage("the copyright message");
+        underTest.addSource(new File("/my/file"));
+        underTest.addSource(new ReportConfigurationTest.TestingReportable());
+        underTest.addExcludedPatterns(List.of("pattern/**", "pattern2/**"));
+        underTest.addExcludedCollection(StandardCollection.BAZAAR);
+        underTest.addExcludedCollection(StandardCollection.MISC);
+        underTest.addExcludedFileProcessor(StandardCollection.HIDDEN_FILE);
+        underTest.addExcludedMatcher(DocumentNameMatcher.MATCHES_ALL);
+        underTest.addIncludedPatterns(List.of("**/pattern3", "**/pattern4"));
+        underTest.addIncludedCollection(StandardCollection.ARCH);
+        underTest.addIncludedCollection(StandardCollection.BITKEEPER);
+        underTest.addIncludedMatcher(DocumentNameMatcher.MATCHES_NONE);
+
+        ClaimValidator claimValidator = underTest.getClaimValidator();
+        claimValidator.setMax(ClaimStatistic.Counter.APPROVED, 5);
+        claimValidator.setMin(ClaimStatistic.Counter.APPROVED, 3);
+        claimValidator.setMax(ClaimStatistic.Counter.ARCHIVES, 10);
+        claimValidator.setMin(ClaimStatistic.Counter.BINARIES, 4);
+
+        StringWriter stringWriter = new StringWriter();
+        underTest.serde().serialize(stringWriter);
+
+        try (FileOutputStream fos = new 
FileOutputStream(documentFile.asFile())) {
+            
fos.write(stringWriter.toString().getBytes(StandardCharsets.UTF_8));
+        }
+
+        Reporter.Output.Builder builder = 
Reporter.Output.builder().configuration(documentFile.getName(), 
workingDirectory);
+
+        Reporter.Output output = builder.build();
+        ReportConfigurationTest.assertSame(output.getConfiguration(), 
underTest);
+    }
+}
diff --git 
a/apache-rat-core/src/test/java/org/apache/rat/ReportConfigurationTest.java 
b/apache-rat-core/src/test/java/org/apache/rat/ReportConfigurationTest.java
index 42f1de0c..caf3b00d 100644
--- a/apache-rat-core/src/test/java/org/apache/rat/ReportConfigurationTest.java
+++ b/apache-rat-core/src/test/java/org/apache/rat/ReportConfigurationTest.java
@@ -24,6 +24,7 @@ import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
 import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileFilter;
@@ -32,9 +33,11 @@ import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.OutputStream;
 import java.io.PrintWriter;
+import java.io.StringWriter;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.net.URL;
+import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -44,15 +47,21 @@ import java.util.function.Function;
 import org.apache.commons.io.filefilter.DirectoryFileFilter;
 import org.apache.commons.io.output.CloseShieldOutputStream;
 import org.apache.rat.analysis.IHeaderMatcher;
+import org.apache.rat.api.RatException;
+import org.apache.rat.commandline.StyleSheets;
 import org.apache.rat.config.AddLicenseHeaders;
+import org.apache.rat.config.exclusion.ExclusionProcessorTest;
 import org.apache.rat.config.exclusion.StandardCollection;
+import org.apache.rat.config.results.ClaimValidator;
 import org.apache.rat.configuration.XMLConfigurationReaderTest;
 import org.apache.rat.document.DocumentName;
 import org.apache.rat.document.DocumentNameMatcher;
 import org.apache.rat.license.ILicense;
 import org.apache.rat.license.ILicenseFamily;
 import org.apache.rat.license.LicenseSetFactory.LicenseFilter;
+import org.apache.rat.report.RatReport;
 import org.apache.rat.report.Reportable;
+import org.apache.rat.report.claim.ClaimStatistic;
 import org.apache.rat.testhelpers.TestingLog;
 import org.apache.rat.testhelpers.TestingLicense;
 import org.apache.rat.testhelpers.TestingMatcher;
@@ -494,7 +503,7 @@ public class ReportConfigurationTest {
         assertThat(underTest.getStyleSheetDescriptor()).isNull();
         assertThat(underTest.getStyleSheet()).isNull();
         InputStream stream = mock(InputStream.class);
-        underTest.setStyleSheet(new 
ReportConfiguration.IODescriptor("stylesheetTest", () -> stream));
+        underTest.setStyleSheet(new 
ReportConfiguration.IODescriptor<>("stylesheetTest", () -> stream));
         
assertThat(underTest.getStyleSheetDescriptor().ioSupplier().get()).isEqualTo(stream);
         assertThat(underTest.getStyleSheet().get()).isEqualTo(stream);
 
@@ -765,6 +774,64 @@ public class ReportConfigurationTest {
         validateDefaultLicenses(config);
     }
 
+    public static void assertSame(ReportConfiguration actual, 
ReportConfiguration expected) {
+        
assertThat(actual.isAddingLicenses()).isEqualTo(expected.isAddingLicenses());
+        
assertThat(actual.isAddingLicensesForced()).isEqualTo(expected.isAddingLicensesForced());
+        assertThat(actual.listFamilies()).isEqualTo(expected.listFamilies());
+        assertThat(actual.listLicenses()).isEqualTo(expected.listLicenses());
+        assertThat(actual.isDryRun()).isEqualTo(expected.isDryRun());
+        
assertThat(actual.getArchiveProcessing()).isEqualTo(expected.getArchiveProcessing());
+        
assertThat(actual.getStandardProcessing()).isEqualTo(expected.getStandardProcessing());
+        
assertThat(actual.getStyleSheetDescriptor().name()).isEqualTo(expected.getStyleSheetDescriptor().name());
+        
assertThat(actual.getOutputDescriptor().name()).isEqualTo(expected.getOutputDescriptor().name());
+
+        
assertThat(actual.getCopyrightMessage()).isEqualTo(expected.getCopyrightMessage());
+
+        
assertThat(actual.sources()).containsExactlyElementsOf(expected.sources());
+        
assertThat(actual.reportables()).containsExactlyElementsOf(expected.reportables().toList());
+        ExclusionProcessorTest.assertSame(actual.getExclusionProcessor(), 
expected.getExclusionProcessor());
+
+    }
+    @Test
+    void serdeTest() throws IOException {
+        underTest.setAddLicenseHeaders(AddLicenseHeaders.FORCED);
+        underTest.listFamilies(LicenseFilter.APPROVED);
+        underTest.listLicenses(LicenseFilter.ALL);
+        underTest.setDryRun(true);
+        
underTest.setArchiveProcessing(ReportConfiguration.Processing.NOTIFICATION);
+        
underTest.setStandardProcessing(ReportConfiguration.Processing.ABSENCE);
+        underTest.setStyleSheet(StyleSheets.MISSING_HEADERS.getStyleSheet());
+        underTest.setOut(new File("/some/file/somewhere"));
+        underTest.setCopyrightMessage("the copyright message");
+        underTest.addSource(new File("/my/file"));
+        underTest.addSource(new TestingReportable());
+
+        underTest.addExcludedPatterns(List.of("pattern/**", "pattern2/**"));
+        underTest.addExcludedCollection(StandardCollection.BAZAAR);
+        underTest.addExcludedCollection(StandardCollection.MISC);
+        underTest.addExcludedFileProcessor(StandardCollection.HIDDEN_FILE);
+        underTest.addExcludedMatcher(DocumentNameMatcher.MATCHES_ALL);
+        underTest.addIncludedPatterns(List.of("**/pattern3", "**/pattern4"));
+        underTest.addIncludedCollection(StandardCollection.ARCH);
+        underTest.addIncludedCollection(StandardCollection.BITKEEPER);
+        underTest.addIncludedMatcher(DocumentNameMatcher.MATCHES_NONE);
+
+        ClaimValidator claimValidator = underTest.getClaimValidator();
+
+        claimValidator.setMax(ClaimStatistic.Counter.APPROVED, 5);
+        claimValidator.setMin(ClaimStatistic.Counter.APPROVED, 3);
+        claimValidator.setMax(ClaimStatistic.Counter.ARCHIVES, 10);
+        claimValidator.setMin(ClaimStatistic.Counter.BINARIES, 4);
+
+        StringWriter stringWriter = new StringWriter();
+        underTest.serde().serialize(stringWriter);
+
+        ReportConfiguration actual = new ReportConfiguration();
+        actual.serde().deserialize(() -> new 
ByteArrayInputStream(stringWriter.toString().getBytes(StandardCharsets.UTF_8)),
+                DocumentName.builder(new File("/rootDir")).build());
+        assertSame(actual, underTest);
+    }
+
     /**
      * A class to act as an output stream and count the number of close 
operations.
      */
@@ -781,4 +848,20 @@ public class ReportConfigurationTest {
             ++closeCount;
         }
     }
+
+    /**
+     * A reportable that only reports its name.  Does no actual work.
+     */
+    static class TestingReportable implements Reportable {
+
+        @Override
+        public void run(RatReport report) throws RatException {
+            // does nothing
+        }
+
+        @Override
+        public DocumentName name() {
+            return 
DocumentName.builder().setBaseName("").setName("TestingReportable").build();
+        }
+    }
 }
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 965239fe..9e2c79de 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
@@ -7,7 +7,7 @@
  * "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                 *
+ *   https://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  *
@@ -81,7 +81,7 @@ public class ReporterTest {
     }
 
     @Test
-    public void testExecute() throws RatException, ParseException {
+    void testExecute() throws RatException, ParseException {
         File output = new File(tempDirectory, "testExecute");
 
         CommandLine cl = new 
DefaultParser().parse(OptionCollection.buildOptions(), new 
String[]{"--output-style", "xml", "--output-file", output.getPath(), basedir});
@@ -136,7 +136,7 @@ public class ReporterTest {
     }
 
     @Test
-    public void testOutputOption() throws Exception {
+    void testOutputOption() throws Exception {
         File output = new File(tempDirectory, "test");
         CommandLine commandLine = new 
DefaultParser().parse(OptionCollection.buildOptions(), new String[]{"-o", 
output.getCanonicalPath(), basedir});
         ArgumentContext ctxt = new ArgumentContext(new File("."), commandLine);
@@ -151,7 +151,7 @@ public class ReporterTest {
     }
 
     @Test
-    public void testDefaultOutput() throws Exception {
+    void testDefaultOutput() throws Exception {
         File output = new File(tempDirectory, "testDefaultOutput");
 
         PrintStream origin = System.out;
@@ -179,7 +179,7 @@ public class ReporterTest {
     }
 
     @Test
-    public void testXMLOutput() throws Exception {
+    void testXMLOutput() throws Exception {
         Map<String, Map<String, String>> expected = new HashMap<>();
         expected.put("/.hiddenDirectory", mapOf("isDirectory", "true", 
"mediaType", "application/octet-stream",
                 "type", "IGNORED"));
@@ -404,14 +404,9 @@ public class ReporterTest {
     }
 
     @Test
-    public void xmlReportTest() throws Exception {
-        ByteArrayOutputStream out = new ByteArrayOutputStream();
-
+    void xmlReportTest() throws Exception {
         ReportConfiguration configuration = initializeConfiguration();
-        configuration.setStyleSheet(StyleSheets.XML.getStyleSheet());
-        configuration.setOut(new 
ReportConfiguration.IODescriptor("xmlReportTest", () -> out));
-        new Reporter(configuration).execute().format(configuration);
-        Document doc = XmlUtils.toDom(new 
ByteArrayInputStream(out.toByteArray()));
+        Document doc = new Reporter(configuration).execute().getDocument();
 
         XPath xPath = XPathFactory.newInstance().newXPath();
 
@@ -448,7 +443,7 @@ public class ReporterTest {
     }
 
     @Test
-    public void plainReportTest() throws Exception {
+    void plainReportTest() throws Exception {
         final String NL = System.lineSeparator();
         final String SEPARATOR = 
"*****************************************************";
         final String HEADER = SEPARATOR + NL + //
@@ -457,10 +452,9 @@ public class ReporterTest {
                 "Generated at: ";
         ByteArrayOutputStream out = new ByteArrayOutputStream();
         ReportConfiguration configuration = initializeConfiguration();
-        configuration.setOut(new 
ReportConfiguration.IODescriptor("plainReportTest", () -> out));
+        configuration.setOut(new 
ReportConfiguration.IODescriptor<>("plainReportTest", () -> out));
         new Reporter(configuration).execute().format(configuration);
 
-        out.flush();
         String document = out.toString();
 
         TextUtils.assertNotContains("<?xml version=\"1.0\" 
encoding=\"UTF-8\"?>", document);
@@ -470,14 +464,13 @@ public class ReporterTest {
     }
 
     @Test
-    public void unapprovedLicensesReportTest() throws Exception {
+    void unapprovedLicensesReportTest() throws Exception {
         ByteArrayOutputStream out = new ByteArrayOutputStream();
         ReportConfiguration configuration = initializeConfiguration();
-        configuration.setOut(new 
ReportConfiguration.IODescriptor("unapprovedLicensesReportTest", () -> out));
+        configuration.setOut(new 
ReportConfiguration.IODescriptor<>("unapprovedLicensesReportTest", () -> out));
         
configuration.setStyleSheet(this.getClass().getResource("/org/apache/rat/unapproved-licenses.xsl"));
         new Reporter(configuration).execute().format(configuration);
 
-        out.flush();
         String document = out.toString();
 
         TextUtils.assertContains("Generated at: ", document );
@@ -489,29 +482,26 @@ public class ReporterTest {
     void listLicensesReportTest() throws Exception {
         ByteArrayOutputStream out = new ByteArrayOutputStream();
         ReportConfiguration configuration = initializeConfiguration();
-        configuration.setOut(new 
ReportConfiguration.IODescriptor("listLicensesReportTest", () -> out));
+        configuration.setOut(new 
ReportConfiguration.IODescriptor<>("listLicensesReportTest", () -> out));
         
configuration.setStyleSheet(StyleSheets.UNAPPROVED_LICENSES.getStyleSheet());
         Reporter.listLicenses(configuration, 
LicenseSetFactory.LicenseFilter.NONE);
 
         out.flush();
         String document = out.toString();
-
         assertThat(document).contains("Licenses (NONE):");
     }
 
     @Test
     void counterMaxTest() throws Exception {
         ReportConfiguration config = initializeConfiguration();
-        Reporter reporter = new Reporter(config);
-        Reporter.Output output = reporter.execute();
+        Reporter.Output output = new Reporter(config).execute();
         assertThat(config.getClaimValidator().hasErrors()).isTrue();
         
assertThat(config.getClaimValidator().isValid(ClaimStatistic.Counter.UNAPPROVED,
 output.getStatistic().getCounter(ClaimStatistic.Counter.UNAPPROVED)))
                 .isFalse();
 
         config = initializeConfiguration();
         config.getClaimValidator().setMax(ClaimStatistic.Counter.UNAPPROVED, 
2);
-        reporter = new Reporter(config);
-        output = reporter.execute();
+        output = new Reporter(config).execute();
         assertThat(config.getClaimValidator().hasErrors()).isFalse();
         
assertThat(config.getClaimValidator().isValid(ClaimStatistic.Counter.UNAPPROVED,
 output.getStatistic().getCounter(ClaimStatistic.Counter.UNAPPROVED)))
                 .isTrue();
diff --git 
a/apache-rat-core/src/test/java/org/apache/rat/config/exclusion/ExclusionProcessorTest.java
 
b/apache-rat-core/src/test/java/org/apache/rat/config/exclusion/ExclusionProcessorTest.java
index 19e2e154..c3057943 100644
--- 
a/apache-rat-core/src/test/java/org/apache/rat/config/exclusion/ExclusionProcessorTest.java
+++ 
b/apache-rat-core/src/test/java/org/apache/rat/config/exclusion/ExclusionProcessorTest.java
@@ -18,6 +18,9 @@
  */
 package org.apache.rat.config.exclusion;
 
+import java.io.ByteArrayInputStream;
+import java.io.StringWriter;
+import java.nio.charset.StandardCharsets;
 import java.nio.file.FileSystems;
 import java.nio.file.Path;
 import java.util.ArrayList;
@@ -26,6 +29,9 @@ import java.util.stream.Stream;
 import org.apache.commons.io.FileUtils;
 import org.apache.rat.document.DocumentNameMatcher;
 import org.apache.rat.document.DocumentName;
+import org.apache.rat.report.xml.writer.XmlWriter;
+import org.apache.rat.utils.StandardXmlFactory;
+import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.io.TempDir;
 
 import java.io.File;
@@ -37,6 +43,8 @@ import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.Arguments;
 import org.junit.jupiter.params.provider.MethodSource;
 import org.mockito.Mockito;
+import org.w3c.dom.Document;
+import org.xml.sax.SAXException;
 
 import static java.lang.String.format;
 import static org.assertj.core.api.Assertions.assertThat;
@@ -264,4 +272,196 @@ public class ExclusionProcessorTest {
 
         return lst.stream();
     }
+
+    @Test
+    void addNullIncludedMatcherTest() {
+        ExclusionProcessor underTest = new ExclusionProcessor();
+        assertThat(underTest.getIncludedPaths()).isEmpty();
+                underTest.addIncludedMatcher(null);
+                assertThat(underTest.getIncludedPaths()).isEmpty();
+                underTest.addIncludedMatcher(DocumentNameMatcher.MATCHES_ALL)
+                        .addIncludedMatcher(null);
+        assertThat(underTest.getIncludedPaths()).hasSize(1);
+        List<String> actualPaths = 
underTest.getIncludedPaths().stream().map(DocumentNameMatcher::toString).toList();
+        
assertThat(actualPaths).containsExactly(DocumentNameMatcher.MATCHES_ALL.toString());
+    }
+
+    @Test
+    void addNullExcludedMatcherTest() {
+        ExclusionProcessor underTest = new ExclusionProcessor();
+        assertThat(underTest.getExcludedPaths()).isEmpty();
+        underTest.addExcludedMatcher(null);
+        assertThat(underTest.getExcludedPaths()).isEmpty();
+        underTest.addExcludedMatcher(DocumentNameMatcher.MATCHES_ALL)
+                .addExcludedMatcher(null);
+        assertThat(underTest.getExcludedPaths()).hasSize(1);
+        List<String> actualPaths = 
underTest.getExcludedPaths().stream().map(DocumentNameMatcher::toString).toList();
+        
assertThat(actualPaths).containsExactly(DocumentNameMatcher.MATCHES_ALL.toString());
+    }
+
+    @Test
+    void addFileProcessor() {
+        ExclusionProcessor underTest = new ExclusionProcessor();
+        assertThat(underTest.getFileProcessors()).isEmpty();
+        underTest.addFileProcessor(null);
+        assertThat(underTest.getFileProcessors()).isEmpty();
+        underTest.addFileProcessor(StandardCollection.HIDDEN_FILE)
+                .addFileProcessor(null);
+        
assertThat(underTest.getFileProcessors()).containsExactly(StandardCollection.HIDDEN_FILE);
+    }
+
+    @Test
+    void addNullIncludedPatternTest() {
+        ExclusionProcessor underTest = new ExclusionProcessor();
+        assertThat(underTest.getIncludedPatterns()).isEmpty();
+        underTest.addIncludedPatterns(null);
+        assertThat(underTest.getIncludedPatterns()).isEmpty();
+        underTest.addIncludedPatterns(Collections.emptyList());
+        assertThat(underTest.getIncludedPatterns()).isEmpty();
+        underTest.addIncludedPatterns(List.of("hello/world"))
+        .addIncludedPatterns(null)
+                .addIncludedPatterns(Collections.emptyList());
+        
assertThat(underTest.getIncludedPatterns()).containsExactly("hello/world");
+    }
+
+    @Test
+    void addNullExcludedPatternTest() {
+        ExclusionProcessor underTest = new ExclusionProcessor();
+        assertThat(underTest.getExcludedPatterns()).isEmpty();
+        underTest.addExcludedPatterns(null);
+        assertThat(underTest.getExcludedPatterns()).isEmpty();
+        underTest.addExcludedPatterns(Collections.emptyList());
+        assertThat(underTest.getExcludedPatterns()).isEmpty();
+        underTest.addExcludedPatterns(List.of("hello/world"))
+                .addExcludedPatterns(null)
+                .addExcludedPatterns(Collections.emptyList());
+        
assertThat(underTest.getExcludedPatterns()).containsExactly("hello/world");
+    }
+
+    public static void assertSame(ExclusionProcessor actual, 
ExclusionProcessor expected) {
+        
assertThat(actual.getExcludedCollections()).containsExactlyElementsOf(expected.getExcludedCollections());
+        
assertThat(actual.getFileProcessors()).containsExactlyElementsOf(expected.getFileProcessors());
+        
assertThat(actual.getIncludedCollections()).containsExactlyElementsOf(expected.getIncludedCollections());
+        
assertThat(actual.getExcludedPatterns()).containsExactlyElementsOf(expected.getExcludedPatterns());
+        
assertThat(actual.getIncludedPatterns()).containsExactlyElementsOf(expected.getIncludedPatterns());
+
+        // paths only match on name.  deserialized paths are not functional.
+        List<String> actualPaths = 
actual.getExcludedPaths().stream().map(DocumentNameMatcher::toString).toList();
+        List<String> expectedPaths = 
expected.getExcludedPaths().stream().map(DocumentNameMatcher::toString).toList();
+        assertThat(actualPaths).as("excluded 
paths").containsExactlyElementsOf(expectedPaths);
+
+        actualPaths = 
actual.getIncludedPaths().stream().map(DocumentNameMatcher::toString).toList();
+        expectedPaths = 
expected.getIncludedPaths().stream().map(DocumentNameMatcher::toString).toList();
+        assertThat(actualPaths).as("included 
paths").containsExactlyElementsOf(expectedPaths);
+    }
+
+    @ParameterizedTest
+    @MethodSource("serdeTestData")
+    void serdeTest(ExclusionProcessor underTest) throws IOException, 
SAXException {
+        StringWriter stringWriter = new StringWriter();
+        try (XmlWriter writer = new XmlWriter(stringWriter)) {
+            underTest.serde().serialize(writer);
+        }
+        Document document = StandardXmlFactory.documentBuilder().parse(new 
ByteArrayInputStream(stringWriter.toString().getBytes(StandardCharsets.UTF_8)));
+        ExclusionProcessor actual = new ExclusionProcessor();
+        
actual.serde().deserialize(document.getElementsByTagName("ExclusionProcessor").item(0));
+        assertSame(actual, underTest);
+    }
+
+    static List<ExclusionProcessor> serdeTestData() {
+        List<ExclusionProcessor> tests = new ArrayList<>();
+        tests.add(new ExclusionProcessor());
+
+        // single exclusions
+        tests.add(new ExclusionProcessor()
+        .addExcludedPatterns(List.of("pattern/**")));
+
+        tests.add(new ExclusionProcessor()
+       .addExcludedCollection(StandardCollection.BAZAAR));
+
+
+        tests.add(new ExclusionProcessor()
+        .addExcludedMatcher(DocumentNameMatcher.MATCHES_ALL));
+
+        // single inclusions
+        tests.add(new ExclusionProcessor()
+        .addIncludedPatterns(List.of("pattern/**")));
+
+        tests.add(new ExclusionProcessor()
+        .addIncludedCollection(StandardCollection.BAZAAR));
+
+        tests.add(new ExclusionProcessor()
+        .addIncludedMatcher(DocumentNameMatcher.MATCHES_NONE));
+
+        // full population
+        tests.add(new ExclusionProcessor()
+        .addExcludedPatterns(List.of("pattern/**", "pattern2/**"))
+        .addExcludedCollection(StandardCollection.BAZAAR)
+        .addExcludedCollection(StandardCollection.MISC)
+        .addExcludedMatcher(DocumentNameMatcher.MATCHES_ALL)
+        .addIncludedPatterns(List.of("**/pattern3", "**/pattern4"))
+                .addIncludedCollection(StandardCollection.ARCH)
+                .addIncludedCollection(StandardCollection.BITKEEPER)
+        .addIncludedMatcher(DocumentNameMatcher.MATCHES_NONE));
+
+        return tests;
+    }
+
+    @Test
+    void reportExclusionsTest() throws IOException {
+        ExclusionProcessor underTest = new ExclusionProcessor()
+                .addExcludedPatterns(List.of("pattern/**", "pattern2/**"))
+                .addExcludedCollection(StandardCollection.BAZAAR)
+                .addExcludedCollection(StandardCollection.MISC)
+                .addFileProcessor(StandardCollection.HIDDEN_FILE)
+                .addExcludedMatcher(DocumentNameMatcher.MATCHES_ALL)
+                .addIncludedPatterns(List.of("**/pattern3", "**/pattern4"))
+                .addIncludedCollection(StandardCollection.ARCH)
+                .addIncludedCollection(StandardCollection.BITKEEPER)
+                .addIncludedMatcher(DocumentNameMatcher.MATCHES_NONE);
+
+        StringWriter writer = new StringWriter();
+        underTest.reportExclusions(writer);
+        String actual = writer.toString();
+
+        assertThat(actual).containsPattern("Excluding 
patterns:[^$]+\\Qpattern/**\\E")
+                .containsPattern("Excluding patterns:[^$]+\\Qpattern2/**\\E")
+                .containsPattern("Including patterns:[^$]+\\Q**/pattern3\\E")
+                .containsPattern("Including patterns:[^$]+\\Q**/pattern4\\E")
+                .contains("Excluding " + StandardCollection.BAZAAR + " 
collection.")
+                .contains("Excluding " + StandardCollection.MISC + " 
collection.")
+                .contains("Including " + StandardCollection.ARCH + " 
collection.")
+                .contains("Including " + StandardCollection.BITKEEPER + " 
collection.")
+                .contains("Processing exclude file from " + 
StandardCollection.HIDDEN_FILE)
+                .contains("Excluding " + DocumentNameMatcher.MATCHES_ALL + ".")
+                .contains("Including " + DocumentNameMatcher.MATCHES_NONE + 
".");
+    }
+
+    @Test
+    void getNameMatcherTest() {
+        ExclusionProcessor underTest = new ExclusionProcessor()
+                .addExcludedPatterns(List.of("pattern/**", "pattern2/**"))
+                .addExcludedCollection(StandardCollection.BAZAAR)
+                .addExcludedCollection(StandardCollection.MISC)
+                .addFileProcessor(StandardCollection.HIDDEN_FILE)
+                .addExcludedMatcher(DocumentNameMatcher.MATCHES_ALL)
+                .addIncludedPatterns(List.of("**/pattern3", "**/pattern4"))
+                .addIncludedCollection(StandardCollection.ARCH)
+                .addIncludedCollection(StandardCollection.BITKEEPER)
+                .addIncludedMatcher(DocumentNameMatcher.MATCHES_NONE);
+
+        assertThat(underTest.getLastMatcherBaseDir()).isNull();
+        assertThat(underTest.getLastMatcher()).isNull();
+        DocumentName base = DocumentName.builder(new File(".")).build();
+
+        DocumentNameMatcher matcher = underTest.getNameMatcher(base);
+        assertThat(underTest.getLastMatcherBaseDir()).isEqualTo(base);
+        assertThat(underTest.getLastMatcher()).isEqualTo(matcher);
+
+        DocumentNameMatcher matcher2 = underTest.getNameMatcher(base);
+        assertThat(underTest.getLastMatcherBaseDir()).isEqualTo(base);
+        assertThat(underTest.getLastMatcher()).isEqualTo(matcher);
+        assertThat(underTest.getLastMatcherBaseDir()).isEqualTo(base);
+        assertThat(matcher2).isEqualTo(matcher);
+    }
 }
diff --git 
a/apache-rat-core/src/test/java/org/apache/rat/report/claim/ClaimStatisticTest.java
 
b/apache-rat-core/src/test/java/org/apache/rat/report/claim/ClaimStatisticTest.java
new file mode 100644
index 00000000..ef8a31e2
--- /dev/null
+++ 
b/apache-rat-core/src/test/java/org/apache/rat/report/claim/ClaimStatisticTest.java
@@ -0,0 +1,148 @@
+/*
+ * 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   *
+ *                                                              *
+ *   https://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.report.claim;
+
+import org.apache.rat.api.Document;
+import org.junit.jupiter.api.Test;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.nio.charset.StandardCharsets;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class ClaimStatisticTest {
+
+    @Test
+    void counterTests() {
+        ClaimStatistic underTest = new ClaimStatistic();
+        
assertThat(underTest.getCounter(ClaimStatistic.Counter.APPROVED)).isZero();
+        underTest.incCounter(ClaimStatistic.Counter.APPROVED, 1);
+        
assertThat(underTest.getCounter(ClaimStatistic.Counter.APPROVED)).isEqualTo(1);
+        underTest.incCounter(ClaimStatistic.Counter.APPROVED, -2);
+        
assertThat(underTest.getCounter(ClaimStatistic.Counter.APPROVED)).isEqualTo(-1);
+        underTest.setCounter(ClaimStatistic.Counter.APPROVED, 
Integer.MAX_VALUE);
+        
assertThat(underTest.getCounter(ClaimStatistic.Counter.APPROVED)).isEqualTo(Integer.MAX_VALUE);
+    }
+
+    @Test
+    void typeTests() {
+        ClaimStatistic underTest = new ClaimStatistic();
+        assertThat(underTest.getCounter(Document.Type.NOTICE)).isZero();
+        underTest.incCounter(Document.Type.NOTICE, 1);
+        assertThat(underTest.getCounter(Document.Type.NOTICE)).isEqualTo(1);
+        underTest.incCounter(Document.Type.NOTICE, -2);
+        assertThat(underTest.getCounter(Document.Type.NOTICE)).isEqualTo(-1);
+    }
+
+    @Test
+    void documentTypeTest() {
+        ClaimStatistic underTest = new ClaimStatistic();
+        assertThat(underTest.getDocumentTypes()).isEmpty();
+        underTest.incCounter(Document.Type.NOTICE, 1);
+        underTest.incCounter(Document.Type.BINARY, -1);
+        
assertThat(underTest.getDocumentTypes()).containsExactly(Document.Type.BINARY, 
Document.Type.NOTICE);
+    }
+
+    @Test
+    void licenseCategoryCountTest() {
+        ClaimStatistic underTest = new ClaimStatistic();
+        assertThat(underTest.getLicenseCategoryCount("fam")).isZero();
+        underTest.incLicenseCategoryCount("fam", 1);
+        assertThat(underTest.getLicenseCategoryCount("fam")).isEqualTo(1);
+        underTest.incLicenseCategoryCount("fam", -2);
+        assertThat(underTest.getLicenseCategoryCount("fam")).isEqualTo(-1);
+        underTest.incLicenseCategoryCount("wayToLongAName", 5);
+        
assertThat(underTest.getLicenseCategoryCount("wayToLongAName")).isEqualTo(5);
+    }
+
+    @Test
+    void licenseNameCountTest() {
+        ClaimStatistic underTest = new ClaimStatistic();
+        assertThat(underTest.getLicenseNameCount("fam")).isZero();
+        underTest.incLicenseNameCount("fam", 1);
+        assertThat(underTest.getLicenseNameCount("fam")).isEqualTo(1);
+        underTest.incLicenseNameCount("fam", -2);
+        assertThat(underTest.getLicenseNameCount("fam")).isEqualTo(-1);
+    }
+
+    @Test
+    void licenseFamilyCategoriesTest() {
+        ClaimStatistic underTest = new ClaimStatistic();
+        assertThat(underTest.getLicenseFamilyCategories()).isEmpty();
+        underTest.incLicenseCategoryCount("one", 1);
+        underTest.incLicenseCategoryCount("neg", -1);
+        
assertThat(underTest.getLicenseFamilyCategories()).containsExactly("neg", 
"one");
+        underTest.incLicenseCategoryCount("wayToLongAName", 5);
+        
assertThat(underTest.getLicenseFamilyCategories()).containsExactly("neg", 
"one", "wayToLongAName");
+    }
+
+    @Test
+    void licenseFamilyNameTest() {
+        ClaimStatistic underTest = new ClaimStatistic();
+        assertThat(underTest.getLicenseNames()).isEmpty();
+        underTest.incLicenseNameCount("one", 1);
+        underTest.incLicenseNameCount("neg", -1);
+        assertThat(underTest.getLicenseNames()).containsExactly("neg", "one");
+        underTest.incLicenseNameCount("wayToLongAName", 5);
+        assertThat(underTest.getLicenseNames()).containsExactly("neg", "one", 
"wayToLongAName");
+    }
+
+    /**
+     * Compares two claim statistics for similarity after 
serialization/deserialization.
+     * @param actual the deserialized verison.
+     * @param expected the original version.
+     */
+    public static void assertSame(final ClaimStatistic actual, final 
ClaimStatistic expected) {
+
+        
assertThat(actual.getLicenseFamilyCategories()).containsExactlyElementsOf(expected.getLicenseFamilyCategories());
+        
assertThat(actual.getLicenseNames()).containsExactlyElementsOf(expected.getLicenseNames());
+        
assertThat(actual.getDocumentTypes()).containsExactlyElementsOf(expected.getDocumentTypes());
+        for (String cat : expected.getLicenseFamilyCategories()) {
+            
assertThat(actual.getLicenseCategoryCount(cat)).isEqualTo(expected.getLicenseCategoryCount(cat));
+        }
+        for (String name : expected.getLicenseNames()) {
+            
assertThat(actual.getLicenseNameCount(name)).isEqualTo(expected.getLicenseNameCount(name));
+        }
+        for (Document.Type type : expected.getDocumentTypes()) {
+            
assertThat(actual.getCounter(type)).isEqualTo(expected.getCounter(type));
+        }
+        for (ClaimStatistic.Counter counter : ClaimStatistic.Counter.values()) 
{
+            
assertThat(actual.getCounter(counter)).isEqualTo(expected.getCounter(counter));
+        }
+    }
+
+    @Test
+    void serdeRoundTrip() throws IOException {
+        ClaimStatistic underTest = new ClaimStatistic();
+        underTest.incLicenseCategoryCount("familyCagegory", 1);
+        underTest.incCounter(ClaimStatistic.Counter.APPROVED, 2);
+        underTest.incCounter(Document.Type.IGNORED, 3);
+        underTest.incLicenseNameCount("licenseName", 4);
+
+        ClaimStatistic.Serde serde = underTest.serde();
+        StringWriter stringWriter = new StringWriter();
+        serde.serialize(stringWriter);
+        ClaimStatistic actual = new ClaimStatistic();
+        ClaimStatistic.Serde serde2 = actual.serde();
+        serde2.deserialize(() -> new 
ByteArrayInputStream(stringWriter.toString().getBytes(StandardCharsets.UTF_8)));
+
+        assertSame(actual, underTest);
+    }
+}
diff --git a/pom.xml b/pom.xml
index cc2dc158..76d9d0ec 100644
--- a/pom.xml
+++ b/pom.xml
@@ -61,6 +61,7 @@ agnostic home for software distribution comprehension and 
audit tools.
     <creadur.jira.id>RAT</creadur.jira.id>
     <velocity.core.version>2.4.1</velocity.core.version>
     <velocity.tools.version>3.1</velocity.tools.version>
+    <xmlunit.version>2.12.0</xmlunit.version>
     <!-- maven plugin versions -->
     <mavenPluginTestingVersion>3.5.1</mavenPluginTestingVersion>
     <mavenPluginPluginVersion>3.15.2</mavenPluginPluginVersion>
@@ -268,6 +269,18 @@ agnostic home for software distribution comprehension and 
audit tools.
         <version>1.3.1</version>
         <scope>test</scope>
       </dependency>
+      <dependency>
+        <groupId>org.xmlunit</groupId>
+        <artifactId>xmlunit-assertj3</artifactId>
+        <version>${xmlunit.version}</version>
+        <scope>test</scope>
+      </dependency>
+      <dependency>
+        <groupId>org.xmlunit</groupId>
+        <artifactId>xmlunit-core</artifactId>
+        <version>${xmlunit.version}</version>
+        <scope>test</scope>
+      </dependency>
     </dependencies>
   </dependencyManagement>
   <reporting>

Reply via email to