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

commit d21fd704f5b16221ca1bde944cf890730cd24135
Author: Claude Warren <[email protected]>
AuthorDate: Mon Jul 6 13:53:24 2026 +0100

    initial code
---
 .../it/resources/ReportTest/RAT_14/verify.groovy   |   3 +-
 .../src/main/java/org/apache/rat/Report.java       |  10 +-
 .../java/org/apache/rat/ReportConfiguration.java   | 154 +++++++++++-
 .../src/main/java/org/apache/rat/Reporter.java     | 233 ++++++++++++++----
 .../org/apache/rat/commandline/StyleSheets.java    |   9 +-
 .../rat/config/exclusion/ExclusionProcessor.java   | 100 ++++++++
 .../rat/configuration/XMLConfigurationReader.java  |   4 +-
 .../apache/rat/report/claim/ClaimStatistic.java    | 124 +++++++++-
 .../org/apache/rat/ReporterOptionsProvider.java    | 269 ++++++++++-----------
 .../java/org/apache/rat/ReporterOptionsTest.java   |  12 +-
 .../src/test/java/org/apache/rat/ReporterTest.java |  22 +-
 .../main/java/org/apache/rat/mp/RatCheckMojo.java  |  29 ++-
 .../main/java/org/apache/rat/mp/RatReportMojo.java |   5 +-
 .../main/java/org/apache/rat/anttasks/Report.java  |   8 +-
 14 files changed, 738 insertions(+), 244 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 226394df..0a46a22f 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
@@ -68,7 +68,8 @@ ReportConfiguration configuration = 
OptionCollection.parseCommands(src, myArgs,
 assertNotNull(configuration)
 configuration.validate(DefaultLog.getInstance().&error)
 Reporter reporter = new Reporter(configuration)
-ClaimStatistic statistic = reporter.execute()
+Reporter.Output output = reporter.execute()
+ClaimStatistic statistic = output.getStatistic()
 
 assertEquals(3, statistic.getCounter(ClaimStatistic.Counter.APPROVED))
 assertEquals(2, statistic.getCounter(ClaimStatistic.Counter.ARCHIVES))
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 5f5f4cb3..3c366fab 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
@@ -51,15 +51,15 @@ public final class Report {
         ReportConfiguration configuration = OptionCollection.parseCommands(new 
File("."), args, Report::printUsage);
         if (configuration != null) {
             configuration.validate(DefaultLog.getInstance()::error);
-            Reporter reporter = new Reporter(configuration);
-            reporter.output();
-            reporter.writeSummary(DefaultLog.getInstance().asWriter());
+            Reporter.Output output = new Reporter(configuration).execute();
+            output.format(configuration);
+            output.writeSummary(DefaultLog.getInstance().asWriter());
 
             if (configuration.getClaimValidator().hasErrors()) {
-                
configuration.getClaimValidator().logIssues(reporter.getClaimsStatistic());
+                
configuration.getClaimValidator().logIssues(output.getStatistic());
                 throw new RatDocumentAnalysisException(format("Issues with %s",
                         String.join(", ",
-                                
configuration.getClaimValidator().listIssues(reporter.getClaimsStatistic()))));
+                                
configuration.getClaimValidator().listIssues(output.getStatistic()))));
             }
         }
     }
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 18c15d56..ab40686a 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
@@ -35,19 +35,25 @@ import java.nio.file.Files;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.SortedSet;
 import java.util.function.Consumer;
 
+import javax.xml.parsers.DocumentBuilder;
+
 import org.apache.commons.collections4.set.UnmodifiableSortedSet;
 import org.apache.commons.io.function.IOSupplier;
 import org.apache.commons.io.output.CloseShieldOutputStream;
+import org.apache.commons.lang3.StringUtils;
 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.ExclusionProcessor;
 import org.apache.rat.config.exclusion.StandardCollection;
 import org.apache.rat.config.results.ClaimValidator;
+import org.apache.rat.configuration.XMLConfigurationReader;
 import org.apache.rat.configuration.builders.AnyBuilder;
 import org.apache.rat.document.DocumentName;
 import org.apache.rat.document.DocumentNameMatcher;
@@ -56,12 +62,19 @@ import org.apache.rat.license.ILicense;
 import org.apache.rat.license.ILicenseFamily;
 import org.apache.rat.license.LicenseSetFactory;
 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.report.xml.writer.XmlWriter;
 import org.apache.rat.utils.DefaultLog;
 import org.apache.rat.utils.Log.Level;
 import org.apache.rat.utils.ReportingSet;
+import org.apache.rat.utils.StandardXmlFactory;
 import org.apache.rat.walker.FileListWalker;
 import org.apache.rat.walker.ReportableListWalker;
+import org.w3c.dom.Node;
+import org.xml.sax.SAXException;
+
 
 /**
  * A configuration object is used by the front end to invoke the
@@ -182,6 +195,10 @@ public class ReportConfiguration {
         reportables = new ArrayList<>();
     }
 
+    public Serde serde() {
+        return new Serde();
+    }
+
     /**
      * Report the excluded files to the appendable object.
      * @param appendable the appendable object to write to.
@@ -251,7 +268,7 @@ public class ReportConfiguration {
 
     /**
      * Retrieves the archive processing type.
-     * @return the archive processing type.
+     * @return The archive processing type.
      */
     public Processing getArchiveProcessing() {
         return archiveProcessing == null ? Defaults.ARCHIVE_PROCESSING : 
archiveProcessing;
@@ -267,7 +284,7 @@ public class ReportConfiguration {
 
     /**
      * Retrieves the archive processing type.
-     * @return the archive processing type.
+     * @return The archive processing type.
      */
     public Processing getStandardProcessing() {
         return standardProcessing == null ? Defaults.STANDARD_PROCESSING : 
standardProcessing;
@@ -284,7 +301,7 @@ public class ReportConfiguration {
     /**
      * Set the log level for reporting collisions in the set of license 
families.
      * <p>NOTE: should be set before licenses or license families are 
added.</p>
-     * @param level the log level to use.
+     * @param level The log level to use.
      */
     public void logFamilyCollisions(final Level level) {
         licenseSetFactory.logFamilyCollisions(level);
@@ -292,7 +309,7 @@ public class ReportConfiguration {
 
     /**
      * Sets the reporting option for duplicate license families.
-     * @param state the ReportingSet.Option to use for reporting.
+     * @param state The ReportingSet.Option to use for reporting.
      */
     public void familyDuplicateOption(final ReportingSet.Options state) {
         licenseSetFactory.familyDuplicateOption(state);
@@ -300,7 +317,7 @@ public class ReportConfiguration {
 
     /**
      * Sets the log level for reporting license collisions.
-     * @param level the log level.
+     * @param level The log level.
      */
     public void logLicenseCollisions(final Level level) {
         licenseSetFactory.logLicenseCollisions(level);
@@ -908,4 +925,131 @@ public class ReportConfiguration {
             return new IODescriptor<>(file.toString(), () -> new 
FileInputStream(file));
         }
     }
+
+    /**
+     * Serializes the ReportConfiguration into an XML document that can be 
deserialzed by the Serde.
+     * Deserialized ReportConfigurations can not be executed as the reportable 
objects a simply named placeholders
+     * and do not have access to the original object.
+     */
+    public class Serde {
+        /**
+         * Writes the configuration as an XML document to the appendable.
+         *
+         * @param appendable the Appendable to write to.
+         * @throws IOException on error.
+         */
+        public void serialize(final Appendable appendable) throws IOException {
+            try (XmlWriter writer = new XmlWriter(appendable)) {
+                writer.startElement("ReportConfiguration")
+                        .attribute("addingLicenses", 
Boolean.toString(addingLicenses))
+                        .attribute("addingLicensesForced", 
Boolean.toString(addingLicensesForced))
+                        .attribute("listFamilies", listFamilies.name())
+                        .attribute("listLicenses", listLicenses.name())
+                        .attribute("dryRun", Boolean.toString(dryRun))
+                        .attribute("archiveProcessing", 
getArchiveProcessing().name())
+                        .attribute("standardProcessing", 
getStandardProcessing().name())
+                        .attribute("stylesheet", styleSheet.name())
+                        .attribute("output", out.name());
+                if (StringUtils.isNotEmpty(copyrightMessage)) {
+                    
writer.startElement("copyrightMessage").content(copyrightMessage).closeElement();
+                }
+                writer.startElement("sources");
+                for (File f : sources) {
+                    writer.startElement("source").attribute("name", 
f.toString()).closeElement();
+                }
+                writer.closeElement("sources").startElement("reportables");
+                for (Reportable reportable : reportables) {
+                    writer.startElement("reportable")
+                            .attribute("baseName", 
reportable.name().getBaseName())
+                            .attribute("name", reportable.name().toString())
+                            .attribute("class", 
reportable.getClass().getName()).closeElement();
+                }
+                writer.closeElement();
+
+                exclusionProcessor.serde().serialize(writer);
+
+                writer.startElement("claimValidator");
+                for (ClaimStatistic.Counter counter : 
ClaimStatistic.Counter.values()) {
+                    writer.startElement("claimCounter")
+                            .attribute("name", 
counter.name()).attribute("min", 
Integer.toString(claimValidator.getMin(counter)))
+                            .attribute("max", 
Integer.toString(claimValidator.getMax(counter))).closeElement();
+                }
+                writer.closeElement();
+            } catch (IOException e) {
+                throw e;
+            } catch (Exception e) {
+                throw new IOException(e);
+            }
+        }
+
+        public void deserialize(final IOSupplier<InputStream> 
inputStreamSupplier, final DocumentName workingDirectory) throws IOException {
+            DocumentBuilder builder = StandardXmlFactory.documentBuilder();
+            org.w3c.dom.Document document;
+            try (InputStream stream = inputStreamSupplier.get()) {
+                document = builder.parse(stream);
+            } catch (SAXException e) {
+                throw new IOException("Unable to read input", e);
+            }
+            Node node = document.getDocumentElement();
+            if (!node.getNodeName().equals("ReportConfiguration")) {
+                throw new IOException("Invalid ReportConfiguration");
+            }
+            Map<String, String> attributes = 
XMLConfigurationReader.attributes(node);
+            addingLicenses = 
Boolean.parseBoolean(attributes.get("addingLicenses"));
+            addingLicensesForced = 
Boolean.parseBoolean(attributes.get("addingLicensesForced"));
+            listFamilies = 
LicenseFilter.valueOf(attributes.get("listFamilies"));
+            listLicenses = 
LicenseFilter.valueOf(attributes.get("listLicenses"));
+            dryRun = Boolean.parseBoolean(attributes.get("dryRun"));
+            archiveProcessing = 
Processing.valueOf(attributes.get("archiveProcessing"));
+            standardProcessing = 
Processing.valueOf(attributes.get("standardProcessing"));
+            String styleName = attributes.get("stylesheet");
+            if (styleName != null) {
+                styleSheet = StyleSheets.getStyleSheet(styleName);
+            }
+            String outputName = attributes.get("output");
+            if (outputName != null) {
+                if (outputName.equals(ReportConfiguration.SYSTEM_OUT.name())) {
+                    out = ReportConfiguration.SYSTEM_OUT;
+                } else {
+                    out = IODescriptor.output(outputName, workingDirectory);
+                }
+            }
+
+            
XMLConfigurationReader.nodeListConsumer(document.getElementsByTagName("copyrightMessage"),
+                    lNode -> setCopyrightMessage(lNode.getTextContent()));
+
+            
XMLConfigurationReader.nodeListConsumer(document.getElementsByTagName("source"),
 lNode -> {
+                Map<String, String> nAttributes = 
XMLConfigurationReader.attributes(lNode);
+                addSource(new File(nAttributes.get("name")));
+            });
+
+            // Deserialize the reportables.
+            
XMLConfigurationReader.nodeListConsumer(document.getElementsByTagName("reportable"),
 lNode -> {
+                Map<String, String> nAttributes = 
XMLConfigurationReader.attributes(lNode);
+                DocumentName documentName = 
DocumentName.builder().setBaseName(nAttributes.get("baseName"))
+                        .setName(nAttributes.get("name")).build();
+                addSource(new DeserializedReportable(documentName));
+            });
+
+            
exclusionProcessor.serde().deserialize(document.getElementsByTagName("ExclusionProcessor").item(0));
+
+            
XMLConfigurationReader.nodeListConsumer(document.getElementsByTagName("claimCounter"),
 lNode -> {
+                Map<String, String> nAttributes = 
XMLConfigurationReader.attributes(lNode);
+                ClaimStatistic.Counter counter = 
ClaimStatistic.Counter.valueOf(nAttributes.get("name"));
+                claimValidator.setMin(counter, 
Integer.parseInt(nAttributes.get("min")));
+                claimValidator.setMax(counter, 
Integer.parseInt(nAttributes.get("max")));
+            });
+        }
+    }
+
+    /**
+     * A record that identifies a deserialized reportable.  Deserialized 
reportables are not executable.
+     * @param name the name of the reportable.
+     */
+    private record DeserializedReportable(DocumentName name) implements 
Reportable {
+        @Override
+        public void run(final RatReport report) throws RatException {
+            throw new RatException("Attempt to run a deserialized reportable");
+        }
+    }
 }
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 5cf396d9..4ccd3c72 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
@@ -18,22 +18,24 @@
  */
 package org.apache.rat;
 
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.io.OutputStreamWriter;
 import java.io.PrintWriter;
-import java.io.Writer;
+import java.io.StringReader;
 import java.nio.charset.StandardCharsets;
 
+import javax.xml.parsers.DocumentBuilder;
 import javax.xml.transform.TransformerException;
 import javax.xml.transform.dom.DOMSource;
 import javax.xml.transform.stream.StreamResult;
 
 import org.apache.commons.io.function.IOSupplier;
 import org.apache.rat.api.RatException;
+import org.apache.rat.document.DocumentName;
 import org.apache.rat.license.LicenseSetFactory.LicenseFilter;
 import org.apache.rat.report.RatReport;
 import org.apache.rat.report.claim.ClaimStatistic;
@@ -41,6 +43,8 @@ import org.apache.rat.report.xml.XmlReportFactory;
 import org.apache.rat.report.xml.writer.XmlWriter;
 import org.apache.rat.utils.StandardXmlFactory;
 import org.w3c.dom.Document;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
 
 /**
  * Class that executes the report as defined in a {@link ReportConfiguration} 
and stores
@@ -48,18 +52,21 @@ import org.w3c.dom.Document;
  */
 public class Reporter {
 
-    /**  Format used for listing licenses. */
+    /**
+     * Format used for listing licenses.
+     */
     private static final String LICENSE_FORMAT = "%s:\t%s%n\t\t%s%n";
 
-    /** The XML output document */
-    private Document document;
-
-    /** Statistics generated as the report was built */
-    private ClaimStatistic statistic;
-
-    /** The configuration for the report */
+    /**
+     * The configuration for the report
+     */
     private final ReportConfiguration configuration;
 
+    /**
+     * The output from the execution.
+     */
+    private Output output;
+
     /**
      * Create the reporter.
      *
@@ -71,72 +78,135 @@ public class Reporter {
 
     /**
      * Executes the report and builds the output.
-     * This method will build the internal XML document if it does not already 
exist.
-     * If this method or either of the {@link #output()} methods have already 
been called this method will return
-     * the previous results.
-     * @return the claim statistics.
+     *
+     * @return the Output object.
      * @throws RatException on error.
      */
-    public ClaimStatistic execute() throws RatException  {
-        if (document == null || statistic == null) {
-            try {
-                if (configuration.hasSource()) {
-                    ByteArrayOutputStream outputStream = new 
ByteArrayOutputStream();
-                    Writer outputWriter = new OutputStreamWriter(outputStream, 
StandardCharsets.UTF_8);
-                    try (XmlWriter writer = new XmlWriter(outputWriter)) {
-                        statistic = new ClaimStatistic();
-                        RatReport report = 
XmlReportFactory.createStandardReport(writer, statistic, configuration);
-                        report.startReport();
-                        configuration.getSources().build().run(report);
-                        report.endReport();
-                    }
-                    InputStream inputStream = new 
ByteArrayInputStream(outputStream.toByteArray());
-                    document = 
StandardXmlFactory.documentBuilder().parse(inputStream);
-                } else {
-                    document = 
StandardXmlFactory.documentBuilder().newDocument();
-                    statistic = new ClaimStatistic();
+    public Output execute() throws RatException {
+        try {
+            Output.Builder builder = 
Output.builder().configuration(configuration);
+            if (configuration.hasSource()) {
+                StringBuilder sb = new StringBuilder();
+                try (XmlWriter writer = new XmlWriter(sb)) {
+                    writer.startDocument();
+                    ClaimStatistic statistic = new ClaimStatistic();
+                    builder.statistic(statistic);
+                    RatReport report = 
XmlReportFactory.createStandardReport(writer, statistic, configuration);
+                    report.startReport();
+                    configuration.getSources().build().run(report);
+                    report.endReport();
+                    InputSource inputSource = new InputSource(new 
StringReader(sb.toString()));
+                    
builder.document(StandardXmlFactory.documentBuilder().parse(inputSource));
                 }
-            }  catch (Exception e) {
-                throw RatException.makeRatException(e);
+            } else {
+                builder.document = 
StandardXmlFactory.documentBuilder().newDocument();
+                builder.statistic(new ClaimStatistic());
             }
+            this.output = builder.build();
+            return output;
+        } catch (Exception e) {
+            throw RatException.makeRatException(e);
         }
-        return statistic;
     }
 
+    /**
+     * 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() {
+        return output;
+    }
+
+    /**
+     * 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();
+        }
+    }
+
+    /**
+     * The output from a report run.
+     */
+    public static final class Output {
+        /** The XML output document */
+        private final Document document;
+        /**
+         * The claim statics from the execution that generated the document.
+         * May be empty if the Document was read from disk.
+         */
+        private final ClaimStatistic statistic;
+        /**
+         * The configuration that generated the document
+         */
+        private final ReportConfiguration configuration;
+
+        /**
+         * Create an output with statistics.
+         * @param builder the Builder
+         */
+        private Output(final Builder builder) {
+            this.document = builder.document;
+            this.statistic = builder.statistic == null ? new ClaimStatistic() 
: builder.statistic;
+            this.configuration = builder.configuration == null ? new 
ReportConfiguration() : builder.configuration;
+        }
+
+        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 getClaimsStatistic() {
+    public ClaimStatistic getStatistic() {
         return statistic;
     }
 
+        public ReportConfiguration getConfiguration() {
+            return configuration;
+        }
     /**
-     * Outputs the report using the stylesheet and output specified in the 
configuration.
-     * @return the Claim statistic from the run.
+         * 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.
      */
-    public ClaimStatistic output() throws RatException {
-        return output(configuration.getStyleSheet(), 
configuration.getOutput());
+        public void format(final ReportConfiguration config) throws 
RatException {
+            format(config.getStyleSheet(), config.getOutput());
     }
 
     /**
-     * Outputs the report to the specified output using the stylesheet. It is 
safe to call this method more than once
+         * 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.
-     * @return the Claim statistic for the run.
      * @throws RatException on error.
      */
-    public ClaimStatistic output(final IOSupplier<InputStream> stylesheet, 
final IOSupplier<OutputStream> output) throws RatException {
-        ClaimStatistic result = execute();
+        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)));
-            return result;
         } catch (TransformerException | IOException e) {
             throw new RatException(e);
         }
@@ -167,16 +237,77 @@ public class Reporter {
         appendable.append("RAT summary:").append(System.lineSeparator());
         for (ClaimStatistic.Counter counter : ClaimStatistic.Counter.values()) 
{
             appendable.append("  ").append(counter.displayName()).append(":  ")
-                    
.append(Integer.toString(getClaimsStatistic().getCounter(counter)))
+                        
.append(Integer.toString(statistic.getCounter(counter)))
                     .append(System.lineSeparator());
         }
     }
 
+        public static final class Builder {
+            /** 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;
     /**
-     * Gets the document that was generated during execution.
-     * @return the document that was generated during execution.
+             * The configuration that generated the document
      */
-    public Document getDocument() {
-        return document;
+            private ReportConfiguration configuration;
+
+            public Builder document(final Document document) {
+                this.document = document;
+                return this;
+            }
+
+            public Builder document(final String fileName, final DocumentName 
workingDirectory) {
+                DocumentBuilder builder = StandardXmlFactory.documentBuilder();
+                File inputFile = workingDirectory.resolve(fileName).asFile();
+                try (InputStream inputStream = new FileInputStream(inputFile)) 
{
+                    this.document = builder.parse(inputStream);
+                } catch (SAXException | IOException e) {
+                    throw new ConfigurationException("Unable to read file: " + 
inputFile, e);
+                }
+                return this;
+            }
+
+            public Output build() {
+                return new Output(this);
+            }
+
+            public Builder statistic(final ClaimStatistic statistic) {
+                this.statistic = statistic;
+                return this;
+            }
+
+            public Builder statistic(final String fileName, final DocumentName 
workingDirectory) {
+                File sourceFile = workingDirectory.resolve(fileName).asFile();
+                try {
+                    ClaimStatistic newStatistic = new ClaimStatistic();
+                    newStatistic.serde().deserialize(() -> new 
FileInputStream(sourceFile));
+                    this.statistic = newStatistic;
+                    return this;
+                } catch (IOException e) {
+                    throw new ConfigurationException("Unable to read file: " + 
sourceFile, e);
+                }
+            }
+
+            public Builder configuration(final ReportConfiguration 
configuration) {
+                this.configuration = configuration;
+                return this;
+            }
+
+            public Builder configuration(final String fileName, final 
DocumentName workingDirectory) {
+                File configurationFile = 
workingDirectory.resolve(fileName).asFile();
+                try {
+                    ReportConfiguration config = new ReportConfiguration();
+                    config.serde().deserialize(() -> new 
FileInputStream(configurationFile), workingDirectory);
+                    this.configuration = config;
+                    return this;
+                } catch (IOException e) {
+                    throw new ConfigurationException("Unable to read file: " + 
configurationFile, e);
+                }
+            }
+        }
     }
 }
diff --git 
a/apache-rat-core/src/main/java/org/apache/rat/commandline/StyleSheets.java 
b/apache-rat-core/src/main/java/org/apache/rat/commandline/StyleSheets.java
index aef47ded..2332161f 100644
--- a/apache-rat-core/src/main/java/org/apache/rat/commandline/StyleSheets.java
+++ b/apache-rat-core/src/main/java/org/apache/rat/commandline/StyleSheets.java
@@ -70,20 +70,19 @@ public enum StyleSheets {
     }
 
     /**
-     * Gets the IOSupplier for a style sheet.
-     * @return an IOSupplier for the sheet.
+     * Gets the IODescriptor for a style sheet.
+     * @return an IODescriptor for the sheet.
      */
     public ReportConfiguration.IODescriptor<InputStream> getStyleSheet() {
         URL url = 
StyleSheets.class.getClassLoader().getResource(format("org/apache/rat/%s.xsl", 
name));
         Objects.requireNonNull(url, "missing stylesheet: " + name);
         return new ReportConfiguration.IODescriptor<>(name, url::openStream);
-
     }
 
     /**
-     * Gets the IOSupplier for a style sheet.
+     * Gets the IODescriptor for a style sheet.
      * @param name the short name for or the path to a style sheet.
-     * @return the IOSupplier for the style sheet.
+     * @return the IODescriptor for the style sheet.
      */
     public static ReportConfiguration.IODescriptor<InputStream> 
getStyleSheet(final String name) {
         URL url = 
StyleSheets.class.getClassLoader().getResource(format("org/apache/rat/%s.xsl", 
name));
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 d7b5862b..2a608584 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
@@ -22,15 +22,22 @@ import java.io.IOException;
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 import java.util.TreeSet;
+import java.util.function.Predicate;
 import java.util.stream.Collectors;
 
+import org.apache.commons.lang3.NotImplementedException;
+import org.apache.rat.configuration.XMLConfigurationReader;
 import org.apache.rat.document.DocumentName;
 import org.apache.rat.document.DocumentNameMatcher;
+import org.apache.rat.report.xml.writer.XmlWriter;
 import org.apache.rat.utils.DefaultLog;
 import org.apache.rat.utils.ExtendedIterator;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
 
 import static java.lang.String.format;
 
@@ -74,6 +81,10 @@ public class ExclusionProcessor {
         excludedCollections = new HashSet<>();
     }
 
+    public Serde serde() {
+        return new Serde();
+    }
+
     /** Reset the {@link #lastMatcher} and {@link #lastMatcherBaseDir} to 
start again */
     private void resetLastMatcher() {
         lastMatcher = null;
@@ -336,5 +347,94 @@ public class ExclusionProcessor {
             }
         }
     }
+    /**
+     * Serializes and deserializes the ExclusionProcessor to XML document
+     */
+    public class Serde {
+        /** The pattern attribute name */
+        private static final String PATTERN = "pattern";
+        /** THe name attribute name */
+        private static final String NAME = "name";
+
+        /**
+         * Serialize the ExclusionProcessor to XML writer.
+         * @param writer the writer to serialize to.
+         * @throws IOException on Error
+         */
+        public void serialize(final XmlWriter writer) throws IOException {
+            writer.startElement("ExclusionProcessor");
+
+            for (String pattern : excludedPatterns) {
+                writer.startElement("excludedPattern").attribute(PATTERN, 
pattern).closeElement();
+            }
+            for (StandardCollection obj : excludedCollections) {
+                writer.startElement("excludedCollection").attribute(NAME, 
obj.name()).closeElement();
+            }
+            for (DocumentNameMatcher obj : excludedPaths) {
+                writer.startElement("excludedPath").attribute(NAME, 
obj.toString()).closeElement();
+            }
+
+            for (String pattern : includedPatterns) {
+                writer.startElement("includedPattern").attribute(PATTERN, 
pattern).closeElement();
+            }
+            for (StandardCollection obj : includedCollections) {
+                writer.startElement("includedCollection").attribute(NAME, 
obj.name()).closeElement();
+            }
+            for (DocumentNameMatcher obj : includedPaths) {
+                writer.startElement("includedPath").attribute(NAME, 
obj.toString()).closeElement();
+            }
+
+            for (StandardCollection obj : fileProcessors) {
+                writer.startElement("fileProcessor").attribute(NAME, 
obj.name()).closeElement();
+            }
+            writer.closeElement();
 
+        }
+
+        /**
+         * Deserialize from XML Document node to ExclusionProcessor
+         * @param xmlNode the node to deserialize from.
+         */
+        public void deserialize(final Node xmlNode) {
+            final NodeList children = xmlNode.getChildNodes();
+            for (int i = 0; i < children.getLength(); i++) {
+                Node child = children.item(i);
+                Map<String, String> attributes = 
XMLConfigurationReader.attributes(child);
+                StandardCollection collection;
+                switch (child.getNodeName()) {
+                    case "excludedPattern":
+                        excludedPatterns.add(attributes.get(PATTERN));
+                        break;
+                    case "excludedCollection":
+                        collection = 
StandardCollection.valueOf(attributes.get(NAME));
+                        excludedCollections.add(collection);
+                        break;
+                    case "excludedPath":
+                        excludedPaths.add(new 
DocumentNameMatcher(attributes.get(NAME),
+                                (Predicate<DocumentName>) x -> {
+                                    throw new 
NotImplementedException("Deserialized ExclusionProcessor can not evaluate 
paths");
+                                }));
+                        break;
+                    case "includedPattern":
+                        includedPatterns.add(attributes.get(PATTERN));
+                        break;
+                    case "includedCollection":
+                        collection = 
StandardCollection.valueOf(attributes.get(NAME));
+                        includedCollections.add(collection);
+                        break;
+                    case "includedPath":
+                        includedPaths.add(new 
DocumentNameMatcher(attributes.get(NAME),
+                                (Predicate<DocumentName>) x -> {
+                                    throw new 
NotImplementedException("Deserialized ExclusionProcessor can not evaluate 
paths");
+                                }));
+                        break;
+                    case "fileProcessor":
+                        
fileProcessors.add(StandardCollection.valueOf(attributes.get(NAME)));
+                        break;
+                    default:
+                        throw new 
NotImplementedException(String.format("Deserialization for `%s` is not 
implemented", child.getNodeName()));
+                }
+            }
+        }
+    }
 }
diff --git 
a/apache-rat-core/src/main/java/org/apache/rat/configuration/XMLConfigurationReader.java
 
b/apache-rat-core/src/main/java/org/apache/rat/configuration/XMLConfigurationReader.java
index ac11bdd5..04b9fbe4 100644
--- 
a/apache-rat-core/src/main/java/org/apache/rat/configuration/XMLConfigurationReader.java
+++ 
b/apache-rat-core/src/main/java/org/apache/rat/configuration/XMLConfigurationReader.java
@@ -173,7 +173,7 @@ public final class XMLConfigurationReader implements 
LicenseReader, MatcherReade
      * @param list the NodeList to process
      * @param consumer the consumer to apply to each node in the list.
      */
-    private void nodeListConsumer(final NodeList list, final Consumer<Node> 
consumer) {
+    public static void nodeListConsumer(final NodeList list, final 
Consumer<Node> consumer) {
         for (int i = 0; i < list.getLength(); i++) {
             consumer.accept(list.item(i));
         }
@@ -199,7 +199,7 @@ public final class XMLConfigurationReader implements 
LicenseReader, MatcherReade
      * @param node The node to process
      * @return the map of attributes on the node.
      */
-    private Map<String, String> attributes(final Node node) {
+    public static Map<String, String> attributes(final Node node) {
         NamedNodeMap nnm = node.getAttributes();
         Map<String, String> result = new HashMap<>();
         for (int i = 0; i < nnm.getLength(); i++) {
diff --git 
a/apache-rat-core/src/main/java/org/apache/rat/report/claim/ClaimStatistic.java 
b/apache-rat-core/src/main/java/org/apache/rat/report/claim/ClaimStatistic.java
index 62572916..38b538ee 100644
--- 
a/apache-rat-core/src/main/java/org/apache/rat/report/claim/ClaimStatistic.java
+++ 
b/apache-rat-core/src/main/java/org/apache/rat/report/claim/ClaimStatistic.java
@@ -19,14 +19,24 @@
 
 package org.apache.rat.report.claim;
 
+import java.io.IOException;
+import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.Comparator;
 import java.util.List;
 import java.util.Locale;
+import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 
+import javax.xml.parsers.DocumentBuilder;
+
+import org.apache.commons.io.function.IOSupplier;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.rat.api.Document;
+import org.apache.rat.configuration.XMLConfigurationReader;
+import org.apache.rat.report.xml.writer.XmlWriter;
+import org.apache.rat.utils.StandardXmlFactory;
+import org.xml.sax.SAXException;
 
 /**
  * This class provides a numerical overview about
@@ -100,7 +110,7 @@ public class ClaimStatistic {
          * @return displayName of the counter, capitalized and without 
underscores.
          */
         public String displayName() {
-            return StringUtils.capitalize(name().replaceAll("_", " 
").toLowerCase(Locale.ROOT));
+            return StringUtils.capitalize(name().replace("_", " 
").toLowerCase(Locale.ROOT));
         }
     }
 
@@ -113,6 +123,9 @@ public class ClaimStatistic {
     /** Map of counter type to value */
     private final ConcurrentHashMap<ClaimStatistic.Counter, IntCounter> 
counterMap = new ConcurrentHashMap<>();
 
+    public Serde serde() {
+        return new Serde();
+    }
     /**
      * Converts null counter to 0.
      *
@@ -141,6 +154,15 @@ public class ClaimStatistic {
         counterMap.compute(counter, (k, v) -> v == null ? new 
IntCounter().increment(value) : v.increment(value));
     }
 
+    /**
+     * Increments the counts for the counter.
+     * @param counter the counter to increment.
+     * @param value the value to increment the counter by.
+     */
+    public void setCounter(final Counter counter, final int value) {
+        counterMap.put(counter, new IntCounter().increment(value));
+    }
+
     /**
      * Gets the counts for the Document.Type.
      * @param documentType the Document.Type to get the counter for.
@@ -288,5 +310,105 @@ public class ClaimStatistic {
         public int value() {
             return value;
         }
+
+        @Override
+        public String toString() {
+            return String.valueOf(value);
+        }
+    }
+
+    /**
+     * Serialze and deserialze the claim Statistic.
+     */
+    public class Serde {
+        /** The count attribute string */
+        private static final String COUNT = "count";
+        /** the name attribute string */
+        private static final String NAME = "name";
+
+        /**
+         * Serializes the claim statistic into an appendable.
+         * @param appendable the appendable to write to
+         * @throws IOException on error.
+         */
+        public void serialize(final Appendable appendable) throws IOException {
+            try (XmlWriter writer = new XmlWriter(appendable)) {
+                writer.startDocument().startElement("ClaimStatistic")
+                        .startElement("licenseNameMap");
+                for (Map.Entry<String, IntCounter> entry : 
licenseNameMap.entrySet()) {
+                    if (entry.getValue().value > 0) {
+                        writer.startElement("licenseName")
+                                .attribute(COUNT, entry.getValue().toString())
+                                .attribute(NAME, 
entry.getKey()).closeElement();
+                    }
+                }
+                writer.closeElement()
+                        .startElement("licenseFamilyCategoryMap");
+                for (Map.Entry<String, IntCounter> entry : 
licenseFamilyCategoryMap.entrySet()) {
+                    if (entry.getValue().value > 0) {
+                        writer.startElement("familyCategory")
+                                .attribute(COUNT, entry.getValue().toString())
+                                .attribute(NAME, 
entry.getKey()).closeElement();
+                    }
+                }
+                writer.closeElement()
+                        .startElement("documentTypeMap");
+                for (Map.Entry<Document.Type, IntCounter> entry : 
documentTypeMap.entrySet()) {
+                    if (entry.getValue().value > 0) {
+                        writer.startElement("documentType")
+                                .attribute(COUNT, entry.getValue().toString())
+                                .attribute(NAME, 
entry.getKey().name()).closeElement();
+                    }
+                }
+                writer.closeElement()
+                        .startElement("counterMap");
+                for (Map.Entry<ClaimStatistic.Counter, IntCounter> entry : 
counterMap.entrySet()) {
+                    if (entry.getValue().value > 0) {
+                        writer.startElement("counter")
+                                .attribute(COUNT, entry.getValue().toString())
+                                .attribute(NAME, 
entry.getKey().name()).closeElement();
+                    }
+                }
+                writer.closeElement();
+            }
+        }
+
+        /**
+         * Deserializes a ClaimStatistic from an input stream.
+         * @param inputStreamSupplier the supplier of the input stream to 
deserialize from.
+         * @throws IOException on error.
+         */
+        public void deserialize(final IOSupplier<InputStream> 
inputStreamSupplier) throws IOException {
+            DocumentBuilder builder = StandardXmlFactory.documentBuilder();
+            org.w3c.dom.Document document;
+            try (InputStream stream = inputStreamSupplier.get()) {
+                document = builder.parse(stream);
+
+            } catch (SAXException e) {
+                throw new IOException("Unable to read input", e);
+            }
+
+            
XMLConfigurationReader.nodeListConsumer(document.getElementsByTagName("licenseName"),
 node -> {
+                Map<String, String> attributes = 
XMLConfigurationReader.attributes(node);
+                incLicenseNameCount(attributes.get(NAME), 
Integer.parseInt(attributes.get(COUNT)));
+            });
+
+            
XMLConfigurationReader.nodeListConsumer(document.getElementsByTagName("familyCategory"),
 node -> {
+                Map<String, String> attributes = 
XMLConfigurationReader.attributes(node);
+                incLicenseCategoryCount(attributes.get(NAME), 
Integer.parseInt(attributes.get(COUNT)));
+            });
+
+            
XMLConfigurationReader.nodeListConsumer(document.getElementsByTagName("documentType"),
 node -> {
+                Map<String, String> attributes = 
XMLConfigurationReader.attributes(node);
+                Document.Type type = 
Document.Type.valueOf(attributes.get(NAME));
+                incCounter(type, Integer.parseInt(attributes.get(COUNT)));
+            });
+
+            
XMLConfigurationReader.nodeListConsumer(document.getElementsByTagName("counter"),
 node -> {
+                Map<String, String> attributes = 
XMLConfigurationReader.attributes(node);
+                Counter type = Counter.valueOf(attributes.get(NAME));
+                setCounter(type, Integer.parseInt(attributes.get(COUNT)));
+            });
+        }
     }
 }
diff --git 
a/apache-rat-core/src/test/java/org/apache/rat/ReporterOptionsProvider.java 
b/apache-rat-core/src/test/java/org/apache/rat/ReporterOptionsProvider.java
index 0ed214d5..e132e7ee 100644
--- a/apache-rat-core/src/test/java/org/apache/rat/ReporterOptionsProvider.java
+++ b/apache-rat-core/src/test/java/org/apache/rat/ReporterOptionsProvider.java
@@ -65,6 +65,7 @@ import org.w3c.dom.Document;
 
 import static org.apache.rat.commandline.Arg.HELP_LICENSES;
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.assertj.core.api.Fail.fail;
 import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
 
@@ -133,9 +134,9 @@ class ReporterOptionsProvider extends 
AbstractOptionsProvider implements Argumen
         try {
             ReportConfiguration config = 
generateConfig(Collections.emptyList());
             Reporter reporter = new Reporter(config);
-            ClaimStatistic claimStatistic = reporter.execute();
+            Reporter.Output output = reporter.execute();
             ClaimValidator validator = config.getClaimValidator();
-            assertThat(validator.listIssues(claimStatistic)).isEmpty();
+            assertThat(validator.listIssues(output.getStatistic())).isEmpty();
         } finally {
             DefaultLog.setInstance(null);
         }
@@ -160,8 +161,8 @@ class ReporterOptionsProvider extends 
AbstractOptionsProvider implements Argumen
             FileUtils.delete(resultFile);
 
             Reporter reporter = new Reporter(config);
-            ClaimStatistic claimStatistic = reporter.execute();
-            assertThat(claimStatistic).isNotNull();
+            Reporter.Output output = reporter.execute();
+            assertThat(output.getStatistic()).isNotNull();
             String contents = String.join("\n", IOUtils.readLines(new 
FileReader(testFile)));
             assertThat(contents).isEqualTo("class NoLicense {}");
             assertThat(resultFile).exists();
@@ -214,9 +215,9 @@ class ReporterOptionsProvider extends 
AbstractOptionsProvider implements Argumen
 
             ReportConfiguration config = 
generateConfig(ImmutablePair.of(option, args));
             Reporter reporter = new Reporter(config);
-            ClaimStatistic claimStatistic = reporter.execute();
+            Reporter.Output output = reporter.execute();
             ClaimValidator validator = config.getClaimValidator();
-            
assertThat(validator.listIssues(claimStatistic)).containsExactly("UNAPPROVED");
+            
assertThat(validator.listIssues(output.getStatistic())).containsExactly("UNAPPROVED");
         });
     }
 
@@ -243,14 +244,8 @@ class ReporterOptionsProvider extends 
AbstractOptionsProvider implements Argumen
 
             ReportConfiguration config = 
generateConfig(ImmutablePair.of(option, null));
             Reporter reporter = new Reporter(config);
-            try {
-                reporter.execute();
-                fail("Should have thrown exception");
-            } catch (RatException e) {
-                ClaimStatistic claimStatistic = reporter.getClaimsStatistic();
-                ClaimValidator validator = config.getClaimValidator();
-                
assertThat(validator.listIssues(claimStatistic)).containsExactlyInAnyOrder("DOCUMENT_TYPES",
 "LICENSE_CATEGORIES", "LICENSE_NAMES", "STANDARDS");
-            }
+            assertThatThrownBy(reporter::execute)
+                    .isInstanceOf(RatException.class);
         });
     }
 
@@ -276,16 +271,16 @@ class ReporterOptionsProvider extends 
AbstractOptionsProvider implements Argumen
 
             ReportConfiguration config = 
generateConfig(Collections.emptyList());
             Reporter reporter = new Reporter(config);
-            ClaimStatistic claimStatistic = reporter.execute();
+            Reporter.Output output = reporter.execute();
             ClaimValidator validator = config.getClaimValidator();
-            
assertThat(validator.listIssues(claimStatistic)).containsExactly("UNAPPROVED");
+            
assertThat(validator.listIssues(output.getStatistic())).containsExactly("UNAPPROVED");
 
             arg[0] = "Unapproved:1";
             config = generateConfig(ImmutablePair.of(option, arg));
             reporter = new Reporter(config);
-            claimStatistic = reporter.execute();
+            output = reporter.execute();
             validator = config.getClaimValidator();
-            assertThat(validator.listIssues(claimStatistic)).isEmpty();
+            assertThat(validator.listIssues(output.getStatistic())).isEmpty();
         });
     }
 
@@ -301,16 +296,16 @@ class ReporterOptionsProvider extends 
AbstractOptionsProvider implements Argumen
 
             ReportConfiguration config = 
generateConfig(Collections.emptyList());
             Reporter reporter = new Reporter(config);
-            ClaimStatistic claimStatistic = reporter.execute();
+            Reporter.Output output = reporter.execute();
             ClaimValidator validator = config.getClaimValidator();
-            
assertThat(validator.listIssues(claimStatistic)).containsExactly("UNAPPROVED");
+            
assertThat(validator.listIssues(output.getStatistic())).containsExactly("UNAPPROVED");
 
             arg[0] = "Unapproved:1";
             config = generateConfig(ImmutablePair.of(option, arg));
             reporter = new Reporter(config);
-            claimStatistic = reporter.execute();
+            output = reporter.execute();
             validator = config.getClaimValidator();
-            assertThat(validator.listIssues(claimStatistic)).isEmpty();
+            assertThat(validator.listIssues(output.getStatistic())).isEmpty();
         });
     }
 
@@ -329,16 +324,16 @@ class ReporterOptionsProvider extends 
AbstractOptionsProvider implements Argumen
 
             ReportConfiguration config = 
generateConfig(Collections.emptyList());
             Reporter reporter = new Reporter(config);
-            ClaimStatistic claimStatistic = reporter.execute();
-            
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(5);
-            
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(addIgnored
 ? 1 : 0);
+            Reporter.Output output = reporter.execute();
+            
assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(5);
+            
assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(addIgnored
 ? 1 : 0);
 
             // filter out source
             config = generateConfig(ImmutablePair.of(option, args));
             reporter = new Reporter(config);
-            claimStatistic = reporter.execute();
-            
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(2);
-            
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(addIgnored
 ? 4 : 3);
+            output = reporter.execute();
+            
assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(2);
+            
assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(addIgnored
 ? 4 : 3);
         });
     }
 
@@ -381,16 +376,16 @@ class ReporterOptionsProvider extends 
AbstractOptionsProvider implements Argumen
 
             ReportConfiguration config = 
generateConfig(Collections.emptyList());
             Reporter reporter = new Reporter(config);
-            ClaimStatistic claimStatistic = reporter.execute();
-            
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(3);
-            
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(0);
+            Reporter.Output output = reporter.execute();
+            
assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(3);
+            
assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(0);
 
             // filter out source
             config = generateConfig(ImmutablePair.of(option, args));
             reporter = new Reporter(config);
-            claimStatistic = reporter.execute();
-            
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(2);
-            
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(1);
+            output = reporter.execute();
+            
assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(2);
+            
assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(1);
         });
     }
 
@@ -415,15 +410,15 @@ class ReporterOptionsProvider extends 
AbstractOptionsProvider implements Argumen
 
             ReportConfiguration config = 
generateConfig(Collections.emptyList());
             Reporter reporter = new Reporter(config);
-            ClaimStatistic claimStatistic = reporter.execute();
-            
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(5);
-            
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(4);
+            Reporter.Output output = reporter.execute();
+            
assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(5);
+            
assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(4);
 
             config = generateConfig(ImmutablePair.of(option, args));
             reporter = new Reporter(config);
-            claimStatistic = reporter.execute();
-            
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(4);
-            
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(5);
+            output = reporter.execute();
+            
assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(4);
+            
assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(5);
         });
     }
 
@@ -471,16 +466,16 @@ class ReporterOptionsProvider extends 
AbstractOptionsProvider implements Argumen
 
             ReportConfiguration config = 
generateConfig(Collections.emptyList());
             Reporter reporter = new Reporter(config);
-            ClaimStatistic claimStatistic = reporter.execute();
-            
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(11);
+            Reporter.Output output = reporter.execute();
+            
assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(11);
             // .gitignore is ignored by default as it is hidden but not counted
-            
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(0);
+            
assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(0);
 
             config = generateConfig(ImmutablePair.of(option, args));
             reporter = new Reporter(config);
-            claimStatistic = reporter.execute();
-            
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(3);
-            
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(8);
+            output = reporter.execute();
+            
assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(3);
+            
assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(8);
         });
     }
 
@@ -499,25 +494,25 @@ class ReporterOptionsProvider extends 
AbstractOptionsProvider implements Argumen
 
             ReportConfiguration config = 
generateConfig(Collections.emptyList());
             Reporter reporter = new Reporter(config);
-            ClaimStatistic claimStatistic = reporter.execute();
-            
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(4);
-            
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(addIgnored
 ? 1 : 0);
+            Reporter.Output output = reporter.execute();
+            
assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(4);
+            
assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(addIgnored
 ? 1 : 0);
 
             // verify exclude removes most files.
             config = generateConfig(ImmutablePair.of(excludeOption, 
EXCLUDE_ARGS));
             reporter = new Reporter(config);
-            claimStatistic = reporter.execute();
-            
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(1);
+            output = reporter.execute();
+            
assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(1);
             // .gitignore is ignored by default as it is hidden but not counted
-            
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(addIgnored
 ? 4 : 3);
+            
assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(addIgnored
 ? 4 : 3);
 
             // verify include pust them back
             config = generateConfig(ImmutablePair.of(option, args), 
ImmutablePair.of(excludeOption, EXCLUDE_ARGS));
             reporter = new Reporter(config);
-            claimStatistic = reporter.execute();
-            
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(3);
+            output = reporter.execute();
+            
assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(3);
             // .gitignore is ignored by default as it is hidden but not counted
-            
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(addIgnored
 ? 2 : 1);
+            
assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(addIgnored
 ? 2 : 1);
         });
     }
 
@@ -569,15 +564,15 @@ class ReporterOptionsProvider extends 
AbstractOptionsProvider implements Argumen
 
             ReportConfiguration config = 
generateConfig(Collections.singletonList(excludes));
             Reporter reporter = new Reporter(config);
-            ClaimStatistic claimStatistic = reporter.execute();
-            
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(3);
-            
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(5);
+            Reporter.Output output = reporter.execute();
+            
assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(3);
+            
assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(5);
 
             config = generateConfig(excludes, ImmutablePair.of(option, args));
             reporter = new Reporter(config);
-            claimStatistic = reporter.execute();
-            
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(7);
-            
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(1);
+            output = reporter.execute();
+            
assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(7);
+            
assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(1);
         });
     }
 
@@ -594,15 +589,15 @@ class ReporterOptionsProvider extends 
AbstractOptionsProvider implements Argumen
 
             ReportConfiguration config = generateConfig();
             Reporter reporter = new Reporter(config);
-            ClaimStatistic claimStatistic = reporter.execute();
-            
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(3);
-            
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(0);
+            Reporter.Output output = reporter.execute();
+            
assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(3);
+            
assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(0);
 
             config = generateConfig(ImmutablePair.of(option, new 
String[]{inputFile.getAbsolutePath()}));
             reporter = new Reporter(config);
-            claimStatistic = reporter.execute();
-            
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(1);
-            
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(0);
+            output = reporter.execute();
+            
assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(1);
+            
assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(0);
         });
     }
 
@@ -624,18 +619,18 @@ class ReporterOptionsProvider extends 
AbstractOptionsProvider implements Argumen
 
             ReportConfiguration config = addCatzLicense(generateConfig());
             Reporter reporter = new Reporter(config);
-            ClaimStatistic claimStatistic = reporter.execute();
+            Reporter.Output output = reporter.execute();
 
-            
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(1);
-            
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.APPROVED)).isEqualTo(0);
-            
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(1);
+            
assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(1);
+            
assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.APPROVED)).isEqualTo(0);
+            
assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(1);
 
             config = addCatzLicense(generateConfig(arg1));
             reporter = new Reporter(config);
-            claimStatistic = reporter.execute();
-            
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(1);
-            
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.APPROVED)).isEqualTo(1);
-            
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(0);
+            output = reporter.execute();
+            
assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(1);
+            
assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.APPROVED)).isEqualTo(1);
+            
assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(0);
         });
     }
 
@@ -664,17 +659,17 @@ class ReporterOptionsProvider extends 
AbstractOptionsProvider implements Argumen
 
             ReportConfiguration config = generateConfig();
             Reporter reporter = new Reporter(config);
-            ClaimStatistic claimStatistic = reporter.execute();
-            
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(1);
-            
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.APPROVED)).isEqualTo(1);
-            
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(0);
+            Reporter.Output output = reporter.execute();
+            
assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(1);
+            
assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.APPROVED)).isEqualTo(1);
+            
assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(0);
 
             config = generateConfig(arg1);
             reporter = new Reporter(config);
-            claimStatistic = reporter.execute();
-            
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(1);
-            
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.APPROVED)).isEqualTo(0);
-            
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(1);
+            output = reporter.execute();
+            
assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(1);
+            
assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.APPROVED)).isEqualTo(0);
+            
assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(1);
         });
     }
 
@@ -705,26 +700,26 @@ class ReporterOptionsProvider extends 
AbstractOptionsProvider implements Argumen
 
             ReportConfiguration config = generateConfig();
             Reporter reporter = new Reporter(config);
-            ClaimStatistic claimStatistic = reporter.execute();
-            
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(2);
-            
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.APPROVED)).isEqualTo(1);
-            
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(1);
+            Reporter.Output output = reporter.execute();
+            
assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(2);
+            
assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.APPROVED)).isEqualTo(1);
+            
assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(1);
 
             config = generateConfig(arg1);
             reporter = new Reporter(config);
-            claimStatistic = reporter.execute();
-            
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(2);
-            
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.APPROVED)).isEqualTo(2);
-            
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(0);
+            output = reporter.execute();
+            
assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(2);
+            
assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.APPROVED)).isEqualTo(2);
+            
assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(0);
 
             Pair<Option, String[]> arg2 = 
ImmutablePair.of(Arg.CONFIGURATION_NO_DEFAULTS.find("configuration-no-defaults"),
 null);
 
             config = generateConfig(arg1, arg2);
             reporter = new Reporter(config);
-            claimStatistic = reporter.execute();
-            
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(2);
-            
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.APPROVED)).isEqualTo(1);
-            
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(1);
+            output = reporter.execute();
+            
assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(2);
+            
assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.APPROVED)).isEqualTo(1);
+            
assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(1);
 
         });
     }
@@ -751,17 +746,17 @@ class ReporterOptionsProvider extends 
AbstractOptionsProvider implements Argumen
 
             ReportConfiguration config = generateConfig();
             Reporter reporter = new Reporter(config);
-            ClaimStatistic claimStatistic = reporter.execute();
-            
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(2);
-            
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.APPROVED)).isEqualTo(1);
-            
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(1);
+            Reporter.Output output = reporter.execute();
+            
assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(2);
+            
assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.APPROVED)).isEqualTo(1);
+            
assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(1);
 
             config = generateConfig(arg1);
             reporter = new Reporter(config);
-            claimStatistic = reporter.execute();
-            
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(2);
-            
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.APPROVED)).isEqualTo(2);
-            
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(0);
+            output = reporter.execute();
+            
assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(2);
+            
assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.APPROVED)).isEqualTo(2);
+            
assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(0);
         });
     }
 
@@ -795,17 +790,17 @@ class ReporterOptionsProvider extends 
AbstractOptionsProvider implements Argumen
 
             ReportConfiguration config = generateConfig();
             Reporter reporter = new Reporter(config);
-            ClaimStatistic claimStatistic = reporter.execute();
-            
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(1);
-            
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.APPROVED)).isEqualTo(1);
-            
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(0);
+            Reporter.Output output = reporter.execute();
+            
assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(1);
+            
assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.APPROVED)).isEqualTo(1);
+            
assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(0);
 
             config = generateConfig(arg1);
             reporter = new Reporter(config);
-            claimStatistic = reporter.execute();
-            
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(2);
-            
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.APPROVED)).isEqualTo(1);
-            
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(1);
+            output = reporter.execute();
+            
assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(2);
+            
assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.APPROVED)).isEqualTo(1);
+            
assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(1);
         });
     }
 
@@ -819,11 +814,11 @@ class ReporterOptionsProvider extends 
AbstractOptionsProvider implements Argumen
 
             ReportConfiguration config = 
generateConfig(ImmutablePair.of(option, args));
             Reporter reporter = new Reporter(config);
-            ClaimStatistic claimStatistic = reporter.output();
-            
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(1);
-            
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.APPROVED)).isEqualTo(1);
-            
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(0);
-
+            Reporter.Output output = reporter.execute();
+            
assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(1);
+            
assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.APPROVED)).isEqualTo(1);
+            
assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(0);
+            output.format(config);
             String actualText = TextUtils.readFile(outFile);
             TextUtils.assertContainsExactly(1, "Apache License 2.0: 1 ", 
actualText);
             TextUtils.assertContainsExactly(1, "STANDARD: 1 ", actualText);
@@ -859,11 +854,11 @@ class ReporterOptionsProvider extends 
AbstractOptionsProvider implements Argumen
                 args[0] = sheet.arg();
                 ReportConfiguration config = 
generateConfig(ImmutablePair.of(option, args));
                 Reporter reporter = new Reporter(config);
-                ClaimStatistic claimStatistic = reporter.output();
-                
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(1);
-                
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.APPROVED)).isEqualTo(0);
-                
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(1);
-
+                Reporter.Output output = reporter.execute();
+                
assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(1);
+                
assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.APPROVED)).isEqualTo(0);
+                
assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(1);
+                output.format(config);
                 String actualText = baos.toString(StandardCharsets.UTF_8);
                 switch (sheet) {
                     case MISSING_HEADERS:
@@ -889,11 +884,11 @@ class ReporterOptionsProvider extends 
AbstractOptionsProvider implements Argumen
             args[0] = file.getAbsolutePath();
             ReportConfiguration config = 
generateConfig(ImmutablePair.of(option, args));
             Reporter reporter = new Reporter(config);
-            ClaimStatistic claimStatistic = reporter.output();
-            
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(1);
-            
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.APPROVED)).isEqualTo(0);
-            
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(1);
-
+            Reporter.Output output = reporter.execute();
+            
assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(1);
+            
assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.APPROVED)).isEqualTo(0);
+            
assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(1);
+            output.format(config);
             String actualText = baos.toString(StandardCharsets.UTF_8);
             TextUtils.assertContainsExactly(1, "Hello world", actualText);
         } catch (IOException | RatException e) {
@@ -931,11 +926,11 @@ class ReporterOptionsProvider extends 
AbstractOptionsProvider implements Argumen
 
             ReportConfiguration config = 
generateConfig(ImmutablePair.of(option, null));
             Reporter reporter = new Reporter(config);
-            ClaimStatistic claimStatistic = reporter.output();
-            
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(1);
-            
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.APPROVED)).isEqualTo(0);
-            
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(1);
-
+            Reporter.Output output = reporter.execute();
+            
assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(1);
+            
assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.APPROVED)).isEqualTo(0);
+            
assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(1);
+            output.format(config);
             String actualText = baos.toString(StandardCharsets.UTF_8);
             TextUtils.assertContainsExactly(1, "<resource 
encoding=\"ISO-8859-1\" mediaType=\"text/plain\" name=\"/stylesheet\" 
type=\"STANDARD\">", actualText);
 
@@ -962,12 +957,12 @@ class ReporterOptionsProvider extends 
AbstractOptionsProvider implements Argumen
 
             ReportConfiguration config = generateConfig();
             Reporter reporter = new Reporter(config);
-            reporter.output();
+            reporter.execute();
             TextUtils.assertNotContains("DEBUG", 
baos.toString(StandardCharsets.UTF_8));
 
             config = generateConfig(ImmutablePair.of(option, new 
String[]{"debug"}));
             reporter = new Reporter(config);
-            reporter.output();
+            reporter.execute();
             TextUtils.assertContains("DEBUG", 
baos.toString(StandardCharsets.UTF_8));
         } catch (IOException | RatException e) {
             fail(e.getMessage(), e);
@@ -991,7 +986,7 @@ class ReporterOptionsProvider extends 
AbstractOptionsProvider implements Argumen
                 args[0] = filter.name();
                 ReportConfiguration config = generateConfig(outputFile, 
stylesheet, ImmutablePair.of(option, args));
                 Reporter reporter = new Reporter(config);
-                reporter.output();
+                reporter.execute().format(config);
                 Document document = XmlUtils.toDom(new 
FileInputStream(outFile));
                 switch (filter) {
                     case ALL:
@@ -1037,7 +1032,7 @@ class ReporterOptionsProvider extends 
AbstractOptionsProvider implements Argumen
                 args[0] = filter.name();
                 ReportConfiguration config = generateConfig(outputFile, 
stylesheet, ImmutablePair.of(option, args));
                 Reporter reporter = new Reporter(config);
-                reporter.output();
+                reporter.execute().format(config);
                 Document document = 
XmlUtils.toDom(Files.newInputStream(outFile.toPath()));
                 switch (filter) {
                     case ALL:
@@ -1090,7 +1085,7 @@ class ReporterOptionsProvider extends 
AbstractOptionsProvider implements Argumen
                 args[0] = proc.name();
                 ReportConfiguration config = generateConfig(outputFile, 
stylesheet, ImmutablePair.of(option, args));
                 Reporter reporter = new Reporter(config);
-                reporter.output();
+                reporter.execute().format(config);
 
                 Document document = 
XmlUtils.toDom(Files.newInputStream(outFile.toPath()));
                 XmlUtils.assertIsPresent(proc.name(), document, xPath, 
"/rat-report/resource[@name='/dummy.jar']");
@@ -1140,7 +1135,7 @@ class ReporterOptionsProvider extends 
AbstractOptionsProvider implements Argumen
                 args[0] = proc.name();
                 ReportConfiguration config = generateConfig(outputFile, 
stylesheet, ImmutablePair.of(option, args));
                 Reporter reporter = new Reporter(config);
-                reporter.output();
+                reporter.execute().format(config);
 
                 Document document = 
XmlUtils.toDom(Files.newInputStream(outFile.toPath()));
                 XmlUtils.assertIsPresent(proc.name(), document, xPath, 
testDoc);
diff --git 
a/apache-rat-core/src/test/java/org/apache/rat/ReporterOptionsTest.java 
b/apache-rat-core/src/test/java/org/apache/rat/ReporterOptionsTest.java
index ccb82f3c..d98a4646 100644
--- a/apache-rat-core/src/test/java/org/apache/rat/ReporterOptionsTest.java
+++ b/apache-rat-core/src/test/java/org/apache/rat/ReporterOptionsTest.java
@@ -78,14 +78,14 @@ public final class ReporterOptionsTest {
             FileUtils.writeFile(testDir, "foo.md");
             ReportConfiguration config = 
OptionCollection.parseCommands(testDir, args, o -> fail("Help called"), true);
             Reporter reporter = new Reporter(config);
-            ClaimStatistic claimStatistic = reporter.execute();
-            XmlUtils.printDocument(System.out, reporter.getDocument());
+            Reporter.Output output = reporter.execute();
+            XmlUtils.printDocument(System.out, output.getDocument());
             XPath xpath = XPathFactory.newInstance().newXPath();
-            XmlUtils.assertIsPresent(reporter.getDocument(), xpath, 
"/rat-report/resource[@name='/foo.md']");
-            XmlUtils.assertAttributes(reporter.getDocument(), xpath, 
"/rat-report/resource[@name='/foo.md']",
+            XmlUtils.assertIsPresent(output.getDocument(), xpath, 
"/rat-report/resource[@name='/foo.md']");
+            XmlUtils.assertAttributes(output.getDocument(), xpath, 
"/rat-report/resource[@name='/foo.md']",
                     XmlUtils.mapOf("type", "IGNORED"));
-            
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(0);
-            
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(2);
+            
assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(0);
+            
assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(2);
         } catch (IOException | RatException | XPathExpressionException e) {
             fail(e);
         }
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 0a62b157..965239fe 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
@@ -87,7 +87,7 @@ public class ReporterTest {
         CommandLine cl = new 
DefaultParser().parse(OptionCollection.buildOptions(), new 
String[]{"--output-style", "xml", "--output-file", output.getPath(), basedir});
         ArgumentContext ctxt = new ArgumentContext(new File("."), cl);
         ReportConfiguration config = 
OptionCollection.createConfiguration(ctxt);
-        ClaimStatistic statistic = new Reporter(config).execute();
+        ClaimStatistic statistic = new 
Reporter(config).execute().getStatistic();
 
         assertThat(statistic.getCounter(Type.ARCHIVE)).isEqualTo(1);
         assertThat(statistic.getCounter(Type.BINARY)).isEqualTo(2);
@@ -142,7 +142,7 @@ public class ReporterTest {
         ArgumentContext ctxt = new ArgumentContext(new File("."), commandLine);
 
         ReportConfiguration config = 
OptionCollection.createConfiguration(ctxt);
-        new Reporter(config).output();
+        new Reporter(config).execute().format(config);
         assertThat(output.exists()).isTrue();
         String content = FileUtils.readFileToString(output, 
StandardCharsets.UTF_8);
         TextUtils.assertPatternInTarget("^! Unapproved:\\s*2 ", content);
@@ -161,7 +161,7 @@ public class ReporterTest {
             ArgumentContext ctxt = new ArgumentContext(new File("."), 
commandLine);
 
             ReportConfiguration config = 
OptionCollection.createConfiguration(ctxt);
-            new Reporter(config).output();
+            new Reporter(config).execute().format(config);
         } finally {
             System.setOut(origin);
         }
@@ -215,7 +215,7 @@ public class ReporterTest {
         ArgumentContext ctxt = new ArgumentContext(tempDirectory, commandLine);
 
         ReportConfiguration config = 
OptionCollection.createConfiguration(ctxt);
-        new Reporter(config).output();
+        new Reporter(config).execute().format(config);
 
         assertThat(output).exists();
         Document doc = 
XmlUtils.toDom(java.nio.file.Files.newInputStream(output.toPath()));
@@ -410,7 +410,7 @@ public class ReporterTest {
         ReportConfiguration configuration = initializeConfiguration();
         configuration.setStyleSheet(StyleSheets.XML.getStyleSheet());
         configuration.setOut(new 
ReportConfiguration.IODescriptor("xmlReportTest", () -> out));
-        new Reporter(configuration).output();
+        new Reporter(configuration).execute().format(configuration);
         Document doc = XmlUtils.toDom(new 
ByteArrayInputStream(out.toByteArray()));
 
         XPath xPath = XPathFactory.newInstance().newXPath();
@@ -458,7 +458,7 @@ public class ReporterTest {
         ByteArrayOutputStream out = new ByteArrayOutputStream();
         ReportConfiguration configuration = initializeConfiguration();
         configuration.setOut(new 
ReportConfiguration.IODescriptor("plainReportTest", () -> out));
-        new Reporter(configuration).output();
+        new Reporter(configuration).execute().format(configuration);
 
         out.flush();
         String document = out.toString();
@@ -475,7 +475,7 @@ public class ReporterTest {
         ReportConfiguration configuration = initializeConfiguration();
         configuration.setOut(new 
ReportConfiguration.IODescriptor("unapprovedLicensesReportTest", () -> out));
         
configuration.setStyleSheet(this.getClass().getResource("/org/apache/rat/unapproved-licenses.xsl"));
-        new Reporter(configuration).output();
+        new Reporter(configuration).execute().format(configuration);
 
         out.flush();
         String document = out.toString();
@@ -503,17 +503,17 @@ public class ReporterTest {
     void counterMaxTest() throws Exception {
         ReportConfiguration config = initializeConfiguration();
         Reporter reporter = new Reporter(config);
-        reporter.output();
+        Reporter.Output output = reporter.execute();
         assertThat(config.getClaimValidator().hasErrors()).isTrue();
-        
assertThat(config.getClaimValidator().isValid(ClaimStatistic.Counter.UNAPPROVED,
 reporter.getClaimsStatistic().getCounter(ClaimStatistic.Counter.UNAPPROVED)))
+        
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);
-        reporter.output();
+        output = reporter.execute();
         assertThat(config.getClaimValidator().hasErrors()).isFalse();
-        
assertThat(config.getClaimValidator().isValid(ClaimStatistic.Counter.UNAPPROVED,
 reporter.getClaimsStatistic().getCounter(ClaimStatistic.Counter.UNAPPROVED)))
+        
assertThat(config.getClaimValidator().isValid(ClaimStatistic.Counter.UNAPPROVED,
 output.getStatistic().getCounter(ClaimStatistic.Counter.UNAPPROVED)))
                 .isTrue();
     }
 
diff --git 
a/apache-rat-plugin/src/main/java/org/apache/rat/mp/RatCheckMojo.java 
b/apache-rat-plugin/src/main/java/org/apache/rat/mp/RatCheckMojo.java
index 5a55a061..d400b53c 100644
--- a/apache-rat-plugin/src/main/java/org/apache/rat/mp/RatCheckMojo.java
+++ b/apache-rat-plugin/src/main/java/org/apache/rat/mp/RatCheckMojo.java
@@ -36,6 +36,7 @@ import org.apache.rat.Reporter;
 import org.apache.rat.commandline.Arg;
 import org.apache.rat.commandline.StyleSheets;
 import org.apache.rat.config.exclusion.StandardCollection;
+import org.apache.rat.config.results.ClaimValidator;
 import org.apache.rat.license.LicenseSetFactory.LicenseFilter;
 import org.apache.rat.report.claim.ClaimStatistic;
 import org.apache.rat.utils.DefaultLog;
@@ -158,9 +159,6 @@ public class RatCheckMojo extends AbstractRatMojo {
     @Parameter(property = "rat.consoleOutput", defaultValue = "true")
     private boolean consoleOutput;
 
-    /** The reporter that this mojo uses */
-    private Reporter reporter;
-
     @Override
     protected ReportConfiguration getConfiguration() throws 
MojoExecutionException {
         ReportConfiguration result = super.getConfiguration();
@@ -208,12 +206,12 @@ public class RatCheckMojo extends AbstractRatMojo {
                 config.reportExclusions(logWriter);
             }
             try {
-                this.reporter = new Reporter(config);
-                reporter.output();
+                Reporter.Output output = new Reporter(config).execute();
                 if (verbose) {
-                    reporter.writeSummary(logWriter);
+                    output.writeSummary(logWriter);
                 }
-                check(config);
+                output.format(config);
+                check(output);
             } catch (MojoFailureException e) {
                 throw e;
             } catch (Exception e) {
@@ -224,17 +222,18 @@ public class RatCheckMojo extends AbstractRatMojo {
         }
     }
 
-    protected void check(final ReportConfiguration config) throws 
MojoFailureException {
-        ClaimStatistic statistics = reporter.getClaimsStatistic();
+    protected void check(final Reporter.Output output) throws 
MojoFailureException {
+        ClaimStatistic statistics = output.getStatistic();
+        ClaimValidator validator = 
output.getConfiguration().getClaimValidator();
         try {
-           
reporter.writeSummary(DefaultLog.getInstance().asWriter(Log.Level.DEBUG));
-           if (config.getClaimValidator().hasErrors()) {
-               config.getClaimValidator().logIssues(statistics);
+           
output.writeSummary(DefaultLog.getInstance().asWriter(Log.Level.DEBUG));
+           if (validator.hasErrors()) {
+               validator.logIssues(statistics);
                if (consoleOutput &&
-                       
!config.getClaimValidator().isValid(ClaimStatistic.Counter.UNAPPROVED, 
statistics.getCounter(ClaimStatistic.Counter.UNAPPROVED))) {
+                       !validator.isValid(ClaimStatistic.Counter.UNAPPROVED, 
statistics.getCounter(ClaimStatistic.Counter.UNAPPROVED))) {
                    try {
                        ByteArrayOutputStream baos = new 
ByteArrayOutputStream();
-                       
reporter.output(StyleSheets.UNAPPROVED_LICENSES.getStyleSheet().ioSupplier(), 
() -> baos);
+                       
output.format(StyleSheets.UNAPPROVED_LICENSES.getStyleSheet().ioSupplier(), () 
-> baos);
                        getLog().warn(baos.toString(StandardCharsets.UTF_8));
                    } catch (RuntimeException rte) {
                        throw rte;
@@ -244,7 +243,7 @@ public class RatCheckMojo extends AbstractRatMojo {
                }
 
                String msg = format("Counter(s) %s exceeded minimum or maximum 
values. See RAT report in: '%s'.",
-                       String.join(", ", 
config.getClaimValidator().listIssues(statistics)),
+                       String.join(", ", validator.listIssues(statistics)),
                        getRatTxtFile());
 
                if (!ignoreErrors) {
diff --git 
a/apache-rat-plugin/src/main/java/org/apache/rat/mp/RatReportMojo.java 
b/apache-rat-plugin/src/main/java/org/apache/rat/mp/RatReportMojo.java
index 6415641b..98468350 100644
--- a/apache-rat-plugin/src/main/java/org/apache/rat/mp/RatReportMojo.java
+++ b/apache-rat-plugin/src/main/java/org/apache/rat/mp/RatReportMojo.java
@@ -448,10 +448,11 @@ public class RatReportMojo extends AbstractRatMojo 
implements MavenMultiPageRepo
                 ByteArrayOutputStream baos = new ByteArrayOutputStream();
                 config.setOut(new ReportConfiguration.IODescriptor("RAT 
output", () -> baos));
                 Reporter reporter = new Reporter(config);
-                reporter.output();
+                Reporter.Output output = reporter.execute();
                 if (verbose) {
-                    reporter.writeSummary(logWriter);
+                    output.writeSummary(logWriter);
                 }
+                output.format(config);
                 sink.text(baos.toString(StandardCharsets.UTF_8.name()));
             } catch (IOException | MojoExecutionException | RatException e) {
                 throw new MavenReportException(e.getMessage(), e);
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 cd91a8ec..3249452e 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
@@ -443,9 +443,11 @@ public class Report extends BaseAntTask {
     @Override
     public void execute() {
         try {
-            Reporter r = new Reporter(validate(getConfiguration()));
-            r.output(StyleSheets.PLAIN.getStyleSheet().ioSupplier(), () -> 
CloseShieldOutputStream.wrap(System.out));
-            r.output();
+            ReportConfiguration config = validate(getConfiguration());
+            Reporter r = new Reporter(config);
+            Reporter.Output output = r.execute();
+            output.format(StyleSheets.PLAIN.getStyleSheet().ioSupplier(), () 
-> CloseShieldOutputStream.wrap(System.out));
+            output.format(config);
         } catch (BuildException e) {
             throw e;
         } catch (Exception ioex) {


Reply via email to