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

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


The following commit(s) were added to refs/heads/master by this push:
     new 0c08080b RAT-561: Cleanup XmlWriter resource issues (#683)
0c08080b is described below

commit 0c08080b9d0abe4c699a4261bbe7603cef29bdd0
Author: Claude Warren <[email protected]>
AuthorDate: Wed Jun 24 16:30:45 2026 +0200

    RAT-561: Cleanup XmlWriter resource issues (#683)
    
    * Cleanup XmlWriter resource issues
    Remove IXmlWriter
    Replace simple RatReport implementations with anonymous implementations.
    Rework tests to ensure that XmlWriter is closed properly.
    
    * update javadoc
    
    ---------
    
    Co-authored-by: P. Ottlinger <[email protected]>
---
 .../src/main/java/org/apache/rat/Reporter.java     |   3 +-
 .../rat/configuration/XMLConfigurationWriter.java  | 295 +++++-----
 .../org/apache/rat/report/ConfigurationReport.java |  53 --
 .../rat/report/claim/ClaimReporterMultiplexer.java |  90 ---
 .../rat/report/claim/ClaimValidatorReport.java     |  77 ---
 .../rat/report/claim/SimpleXmlClaimReporter.java   |  58 --
 .../org/apache/rat/report/xml/XmlElements.java     | 472 ++++++++--------
 .../apache/rat/report/xml/XmlReportFactory.java    | 130 ++++-
 .../apache/rat/report/xml/writer/IXmlWriter.java   | 159 ------
 .../xml/writer/OperationNotAllowedException.java   |   2 +-
 .../apache/rat/report/xml/writer/XmlWriter.java    |  94 +++-
 .../apache/rat/analysis/AnalyserFactoryTest.java   |  20 +-
 .../apache/rat/report/ConfigurationReportTest.java |  27 +-
 .../rat/report/xml/XmlReportFactoryTest.java       |   3 +-
 .../rat/report/xml/writer/XmlWriterUtilsTest.java  |  27 +-
 .../report/xml/writer/impl/base/XmlWriterTest.java | 621 ++++++++++-----------
 .../java/org/apache/rat/testhelpers/XmlUtils.java  |   4 +-
 .../java/org/apache/rat/tools/xsd/XsdWriter.java   |   6 +-
 src/changes/changes.xml                            |   3 +
 19 files changed, 924 insertions(+), 1220 deletions(-)

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 d387dd44..6ca704ca 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
@@ -42,7 +42,6 @@ import org.apache.rat.license.LicenseSetFactory.LicenseFilter;
 import org.apache.rat.report.RatReport;
 import org.apache.rat.report.claim.ClaimStatistic;
 import org.apache.rat.report.xml.XmlReportFactory;
-import org.apache.rat.report.xml.writer.IXmlWriter;
 import org.apache.rat.report.xml.writer.XmlWriter;
 import org.apache.rat.utils.StandardXmlFactory;
 import org.w3c.dom.Document;
@@ -88,7 +87,7 @@ public class Reporter {
                 if (configuration.hasSource()) {
                     ByteArrayOutputStream outputStream = new 
ByteArrayOutputStream();
                     Writer outputWriter = new OutputStreamWriter(outputStream, 
StandardCharsets.UTF_8);
-                    try (IXmlWriter writer = new XmlWriter(outputWriter)) {
+                    try (XmlWriter writer = new XmlWriter(outputWriter)) {
                         statistic = new ClaimStatistic();
                         RatReport report = 
XmlReportFactory.createStandardReport(writer, statistic, configuration);
                         report.startReport();
diff --git 
a/apache-rat-core/src/main/java/org/apache/rat/configuration/XMLConfigurationWriter.java
 
b/apache-rat-core/src/main/java/org/apache/rat/configuration/XMLConfigurationWriter.java
index 79e7da99..11ee840f 100644
--- 
a/apache-rat-core/src/main/java/org/apache/rat/configuration/XMLConfigurationWriter.java
+++ 
b/apache-rat-core/src/main/java/org/apache/rat/configuration/XMLConfigurationWriter.java
@@ -41,18 +41,17 @@ import 
org.apache.rat.configuration.builders.MatcherRefBuilder;
 import org.apache.rat.license.ILicense;
 import org.apache.rat.license.ILicenseFamily;
 import org.apache.rat.license.LicenseSetFactory.LicenseFilter;
-import org.apache.rat.report.xml.writer.IXmlWriter;
 import org.apache.rat.report.xml.writer.XmlWriter;
 
 /**
  * Writes the XML configuration file format.
  */
 public class XMLConfigurationWriter {
-    /** The configuration that is being written */
+    /** The configuration that is being written. */
     private final ReportConfiguration configuration;
-    /** The set of defined matcher IDs */
+    /** The set of defined matcher IDs. */
     private final Set<String> matchers;
-    /** The set of defined license IDs */
+    /** The set of defined license IDs. */
     private final Set<String> licenseChildren;
 
     /**
@@ -87,52 +86,59 @@ public class XMLConfigurationWriter {
         write(new XmlWriter(plainWriter));
     }
 
+    private void writeFamilies(final XmlWriter writer, final 
SortedSet<ILicenseFamily> families) throws IOException, RatException {
+        if (!families.isEmpty()) {
+            writer.startElement(XMLConfig.FAMILIES);
+            for (ILicenseFamily family : families) {
+                writeFamily(writer, family);
+            }
+            writer.closeElement(); // FAMILIES
+        }
+    }
+
+    private void writeLicenses(final XmlWriter writer, final 
SortedSet<ILicense> licenses) throws IOException, RatException {
+        if (!licenses.isEmpty()) {
+            writer.startElement(XMLConfig.LICENSES);
+            for (ILicense license : licenses) {
+                writeDescription(writer, license.getDescription(), license);
+            }
+            writer.closeElement(); // LICENSES
+        }
+    }
+
+    private void writeApproved(final XmlWriter writer) throws IOException {
+        writer.startElement(XMLConfig.APPROVED);
+        for (String family : 
configuration.getLicenseCategories(LicenseFilter.APPROVED)) {
+            
writer.startElement(XMLConfig.APPROVED).attribute(XMLConfig.ATT_LICENSE_REF, 
family.trim())
+                    .closeElement();
+        }
+        writer.closeElement(); // APPROVED
+    }
+
+    private void writeMatchers(final XmlWriter writer) throws IOException {
+        MatcherBuilderTracker tracker = MatcherBuilderTracker.instance();
+        writer.startElement(XMLConfig.MATCHERS);
+        for (Class<?> clazz : tracker.getClasses()) {
+            
writer.startElement(XMLConfig.MATCHER).attribute(XMLConfig.ATT_CLASS_NAME, 
clazz.getCanonicalName())
+                    .closeElement();
+        }
+        writer.closeElement(); // MATCHERS
+    }
+
     /**
-     * Writes the configuration to an IXmlWriter instance.
-     * @param writer the IXmlWriter to write to.
+     * Writes the configuration to an XmlWriter instance.
+     * @param writer the XmlWriter to write to.
      * @throws RatException on error.
      */
-    public void write(final IXmlWriter writer) throws RatException {
+    public void write(final XmlWriter writer) throws RatException {
         if (configuration.listFamilies() != LicenseFilter.NONE || 
configuration.listLicenses() != LicenseFilter.NONE) {
             try {
-                writer.openElement(XMLConfig.ROOT);
+                writer.startElement(XMLConfig.ROOT);
 
-                // Families section
-                SortedSet<ILicenseFamily> families = 
configuration.getLicenseFamilies(configuration.listFamilies());
-                if (!families.isEmpty()) {
-                    writer.openElement(XMLConfig.FAMILIES);
-                    for (ILicenseFamily family : families) {
-                        writeFamily(writer, family);
-                    }
-                    writer.closeElement(); // FAMILIES
-                }
-
-                // licenses section
-                SortedSet<ILicense> licenses = 
configuration.getLicenses(configuration.listLicenses());
-                if (!licenses.isEmpty()) {
-                    writer.openElement(XMLConfig.LICENSES);
-                    for (ILicense license : licenses) {
-                        writeDescription(writer, license.getDescription(), 
license);
-                    }
-                    writer.closeElement(); // LICENSES
-                }
-
-                // approved section
-                writer.openElement(XMLConfig.APPROVED);
-                for (String family : 
configuration.getLicenseCategories(LicenseFilter.APPROVED)) {
-                    
writer.openElement(XMLConfig.APPROVED).attribute(XMLConfig.ATT_LICENSE_REF, 
family.trim())
-                            .closeElement();
-                }
-                writer.closeElement(); // APPROVED
-
-                // matchers section
-                MatcherBuilderTracker tracker = 
MatcherBuilderTracker.instance();
-                writer.openElement(XMLConfig.MATCHERS);
-                for (Class<?> clazz : tracker.getClasses()) {
-                    
writer.openElement(XMLConfig.MATCHER).attribute(XMLConfig.ATT_CLASS_NAME, 
clazz.getCanonicalName())
-                            .closeElement();
-                }
-                writer.closeElement(); // MATCHERS
+                writeFamilies(writer, 
configuration.getLicenseFamilies(configuration.listFamilies()));
+                writeLicenses(writer, 
configuration.getLicenses(configuration.listLicenses()));
+                writeApproved(writer);
+                writeMatchers(writer);
 
                 writer.closeElement(); // ROOT
             } catch (IOException e) {
@@ -141,9 +147,9 @@ public class XMLConfigurationWriter {
         }
     }
 
-    private void writeFamily(final IXmlWriter writer, final ILicenseFamily 
family) throws RatException {
+    private void writeFamily(final XmlWriter writer, final ILicenseFamily 
family) throws RatException {
         try {
-            writer.openElement(XMLConfig.FAMILY).attribute(XMLConfig.ATT_ID, 
family.getFamilyCategory().trim())
+            writer.startElement(XMLConfig.FAMILY).attribute(XMLConfig.ATT_ID, 
family.getFamilyCategory().trim())
                     .attribute(XMLConfig.ATT_NAME, family.getFamilyName());
             writer.closeElement();
         } catch (IOException e) {
@@ -151,21 +157,21 @@ public class XMLConfigurationWriter {
         }
     }
 
-    private void writeDescriptions(final IXmlWriter writer, final 
Collection<Description> descriptions, final IHeaderMatcher component)
+    private void writeDescriptions(final XmlWriter writer, final 
Collection<Description> descriptions, final IHeaderMatcher component)
             throws RatException {
         for (Description description : descriptions) {
             writeDescription(writer, description, component);
         }
     }
 
-    private void writeChildren(final IXmlWriter writer, final Description 
description, final IHeaderMatcher component)
+    private void writeChildren(final XmlWriter writer, final Description 
description, final IHeaderMatcher component)
             throws RatException {
         writeAttributes(writer, 
description.filterChildren(attributeFilter(component.getDescription())), 
component);
         writeDescriptions(writer, 
description.filterChildren(attributeFilter(component.getDescription()).negate()),
                 component);
     }
 
-    private void writeAttributes(final IXmlWriter writer, final 
Collection<Description> descriptions, final IHeaderMatcher component)
+    private void writeAttributes(final XmlWriter writer, final 
Collection<Description> descriptions, final IHeaderMatcher component)
             throws RatException {
         for (Description d : descriptions) {
             try {
@@ -176,13 +182,13 @@ public class XMLConfigurationWriter {
         }
     }
 
-    private void writeComment(final IXmlWriter writer, final Description 
description) throws IOException {
+    private void writeComment(final XmlWriter writer, final Description 
description) throws IOException {
         if (StringUtils.isNotBlank(description.getDescription())) {
             writer.comment(description.getDescription().replace("-->", 
"-&ndash;>"));
         }
     }
 
-    private void writeAttribute(final IXmlWriter writer, final Description 
description, final IHeaderMatcher component)
+    private void writeAttribute(final XmlWriter writer, final Description 
description, final IHeaderMatcher component)
             throws IOException {
         String paramValue = description.getParamValue(component);
         if (paramValue != null) {
@@ -190,102 +196,121 @@ public class XMLConfigurationWriter {
         }
     }
 
-    /* package private for testing */
+    private boolean hasUUIDId(final Description desc, final IHeaderMatcher 
comp) {
+        if ("id".equals(desc.getCommonName())) {
+            try {
+                String paramId = desc.getParamValue(comp);
+                // if a UUID skip it.
+                if (paramId != null) {
+                    UUID.fromString(paramId);
+                    return true;
+                }
+            } catch (IllegalArgumentException expected) {
+                // do nothing.
+            }
+        }
+        return false;
+    }
+
+    private void writeChildParameter(final XmlWriter writer, final Description 
description, final IHeaderMatcher component) throws IOException {
+        boolean inline = 
XMLConfig.isInlineNode(component.getDescription().getCommonName(),
+                description.getCommonName());
+        String s = description.getParamValue(component);
+        if (StringUtils.isNotBlank(s)) {
+            if (!inline) {
+                writer.startElement(description.getCommonName());
+            }
+            writer.content(description.getParamValue(component));
+            if (!inline) {
+                writer.closeElement();
+            }
+        }
+
+    }
     @SuppressWarnings("unchecked")
-    void writeDescription(final IXmlWriter writer, final Description desc, 
final IHeaderMatcher comp) throws RatException {
-        Description description = desc;
-        IHeaderMatcher component = comp;
-        try {
-            switch (description.getType()) {
-            case MATCHER:
-                // see if id was registered
-                Optional<Description> id = 
description.childrenOfType(ComponentType.PARAMETER).stream()
-                        .filter(i -> 
XMLConfig.ATT_ID.equals(i.getCommonName())).findFirst();
+    private void writeParameterDescription(final XmlWriter writer, final 
Description description, final IHeaderMatcher component) throws IOException {
+        if (hasUUIDId(description, component)) {
+            return;
+        }
 
-                // id will not be present in matcherRef
-                if (id.isPresent()) {
-                    String matcherId = id.get().getParamValue(component);
-                    // if we have seen the ID before, put a reference to the 
other one.
-                    if (matchers.contains(matcherId)) {
-                        component = new 
MatcherRefBuilder.IHeaderMatcherProxy(matcherId, null);
-                        description = component.getDescription();
-                    } else {
-                        matchers.add(matcherId);
-                    }
-                    // remove the matcher id if it is a UUID
-                    try {
-                        UUID.fromString(matcherId);
-                        description.getChildren().remove(XMLConfig.ATT_ID);
-                    } catch (IllegalArgumentException expected) {
-                        if (description.getCommonName().equals("spdx")) {
-                            description.getChildren().remove(XMLConfig.ATT_ID);
-                        }
+        if (description.getChildType() == String.class) {
+            writeChildParameter(writer, description, component);
+        } else {
+            try {
+                if (description.isCollection()) {
+                    for (IHeaderMatcher matcher : (Collection<IHeaderMatcher>) 
description
+                            .getter(component.getClass()).invoke(component)) {
+                        writeDescription(writer, matcher.getDescription(), 
matcher);
                     }
+                } else {
+                    IHeaderMatcher matcher = (IHeaderMatcher) 
description.getter(component.getClass())
+                            .invoke(component);
+                    writeDescription(writer, matcher.getDescription(), 
matcher);
                 }
+            } catch (IllegalAccessException | IllegalArgumentException | 
InvocationTargetException
+                     | NoSuchMethodException | SecurityException | 
RatException e) {
+                throw new ImplementationException(e);
+            }
+        }
+    }
 
-                // if only a resource, list the resource not the contents of 
the matcher
-                Optional<Description> resource = 
description.childrenOfType(ComponentType.PARAMETER).stream()
-                        .filter(i -> 
XMLConfig.ATT_RESOURCE.equals(i.getCommonName())).findFirst();
-                if (resource.isPresent()) {
-                    String resourceStr = 
resource.get().getParamValue(component);
-                    if (StringUtils.isNotBlank(resourceStr)) {
-                        description.getChildren().remove("enclosed");
-                    }
+    private void writeMatcherDescription(final XmlWriter writer, final 
Description desc, final IHeaderMatcher comp) throws IOException, RatException {
+        Description description = desc;
+        IHeaderMatcher component = comp;
+        // see if id was registered
+        Optional<Description> id = 
description.childrenOfType(ComponentType.PARAMETER).stream()
+                .filter(i -> 
XMLConfig.ATT_ID.equals(i.getCommonName())).findFirst();
+
+        // id will not be present in matcherRef
+        if (id.isPresent()) {
+            String matcherId = id.get().getParamValue(component);
+            // if we have seen the id before, put a reference to the other one.
+            if (matchers.contains(matcherId)) {
+                component = new 
MatcherRefBuilder.IHeaderMatcherProxy(matcherId, null);
+                description = component.getDescription();
+            } else {
+                matchers.add(matcherId);
+            }
+            // remove the matcher id if it is a UUID
+            try {
+                UUID.fromString(matcherId);
+                description.getChildren().remove(XMLConfig.ATT_ID);
+            } catch (IllegalArgumentException expected) {
+                if (description.getCommonName().equals("spdx")) {
+                    description.getChildren().remove(XMLConfig.ATT_ID);
                 }
-                writeComment(writer, description);
-                writer.openElement(description.getCommonName());
-                writeChildren(writer, description, component);
-                writer.closeElement();
+            }
+        }
+
+        // if only a resource, list the resource not the contents of the 
matcher
+        Optional<Description> resource = 
description.childrenOfType(ComponentType.PARAMETER).stream()
+                .filter(i -> 
XMLConfig.ATT_RESOURCE.equals(i.getCommonName())).findFirst();
+        if (resource.isPresent()) {
+            String resourceStr = resource.get().getParamValue(component);
+            if (StringUtils.isNotBlank(resourceStr)) {
+                description.getChildren().remove("enclosed");
+            }
+        }
+        writeComment(writer, description);
+        writer.startElement(description.getCommonName());
+        writeChildren(writer, description, component);
+        writer.closeElement();
+    }
+
+    /* package private for testing */
+    void writeDescription(final XmlWriter writer, final Description 
description, final IHeaderMatcher component) throws RatException {
+        try {
+            switch (description.getType()) {
+            case MATCHER:
+                writeMatcherDescription(writer, description, component);
                 break;
             case LICENSE:
-                writer.openElement(XMLConfig.LICENSE);
+                writer.startElement(XMLConfig.LICENSE);
                 writeChildren(writer, description, component);
                 writer.closeElement();
                 break;
             case PARAMETER:
-                if ("id".equals(description.getCommonName())) {
-                    try {
-                        String paramId = description.getParamValue(component);
-                        // if a UUID skip it.
-                        if (paramId != null) {
-                            UUID.fromString(paramId);
-                            return;
-                        }
-                    } catch (IllegalArgumentException expected) {
-                        // do nothing.
-                    }
-                }
-                if (description.getChildType() == String.class) {
-
-                    boolean inline = 
XMLConfig.isInlineNode(component.getDescription().getCommonName(),
-                            description.getCommonName());
-                    String s = description.getParamValue(component);
-                    if (StringUtils.isNotBlank(s)) {
-                        if (!inline) {
-                            writer.openElement(description.getCommonName());
-                        }
-                        writer.content(description.getParamValue(component));
-                        if (!inline) {
-                            writer.closeElement();
-                        }
-                    }
-                } else {
-                    try {
-                        if (description.isCollection()) {
-                            for (IHeaderMatcher matcher : 
(Collection<IHeaderMatcher>) description
-                                    
.getter(component.getClass()).invoke(component)) {
-                                writeDescription(writer, 
matcher.getDescription(), matcher);
-                            }
-                        } else {
-                            IHeaderMatcher matcher = (IHeaderMatcher) 
description.getter(component.getClass())
-                                    .invoke(component);
-                            writeDescription(writer, matcher.getDescription(), 
matcher);
-                        }
-                    } catch (IllegalAccessException | IllegalArgumentException 
| InvocationTargetException
-                            | NoSuchMethodException | SecurityException | 
RatException e) {
-                        throw new ImplementationException(e);
-                    }
-                }
+                writeParameterDescription(writer, description, component);
                 break;
             case BUILD_PARAMETER:
                 // ignore;
diff --git 
a/apache-rat-core/src/main/java/org/apache/rat/report/ConfigurationReport.java 
b/apache-rat-core/src/main/java/org/apache/rat/report/ConfigurationReport.java
deleted file mode 100644
index 66ed7629..00000000
--- 
a/apache-rat-core/src/main/java/org/apache/rat/report/ConfigurationReport.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one   *
- * or more contributor license agreements.  See the NOTICE file *
- * distributed with this work for additional information        *
- * regarding copyright ownership.  The ASF licenses this file   *
- * to you under the Apache License, Version 2.0 (the            *
- * "License"); you may not use this file except in compliance   *
- * with the License.  You may obtain a copy of the License at   *
- *                                                              *
- *   http://www.apache.org/licenses/LICENSE-2.0                 *
- *                                                              *
- * Unless required by applicable law or agreed to in writing,   *
- * software distributed under the License is distributed on an  *
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
- * KIND, either express or implied.  See the License for the    *
- * specific language governing permissions and limitations      *
- * under the License.                                           *
- */
-package org.apache.rat.report;
-
-import org.apache.rat.ReportConfiguration;
-import org.apache.rat.api.RatException;
-import org.apache.rat.configuration.XMLConfigurationWriter;
-import org.apache.rat.license.LicenseSetFactory.LicenseFilter;
-import org.apache.rat.report.xml.writer.IXmlWriter;
-
-/**
- * A report that dumps the ReportConfiguration into the XML output.
- */
-public class ConfigurationReport implements RatReport {
-    /** The report configuration to report on. */
-    private final ReportConfiguration configuration;
-    /** The XML writer to write the report with. */
-    private final IXmlWriter writer;
-
-    /**
-     * Constructor.
-     *
-     * @param writer The writer to write the XML data to.
-     * @param configuration the configuration to dump
-     */
-    public ConfigurationReport(final IXmlWriter writer, final 
ReportConfiguration configuration) {
-        this.configuration = configuration;
-        this.writer = writer;
-    }
-
-    @Override
-    public void startReport() throws RatException {
-        if (configuration.listFamilies() != LicenseFilter.NONE || 
configuration.listLicenses() != LicenseFilter.NONE) {
-            new XMLConfigurationWriter(configuration).write(writer);
-        }
-    }
-}
diff --git 
a/apache-rat-core/src/main/java/org/apache/rat/report/claim/ClaimReporterMultiplexer.java
 
b/apache-rat-core/src/main/java/org/apache/rat/report/claim/ClaimReporterMultiplexer.java
deleted file mode 100644
index 5f60284a..00000000
--- 
a/apache-rat-core/src/main/java/org/apache/rat/report/claim/ClaimReporterMultiplexer.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one   *
- * or more contributor license agreements.  See the NOTICE file *
- * distributed with this work for additional information        *
- * regarding copyright ownership.  The ASF licenses this file   *
- * to you under the Apache License, Version 2.0 (the            *
- * "License"); you may not use this file except in compliance   *
- * with the License.  You may obtain a copy of the License at   *
- *                                                              *
- *   http://www.apache.org/licenses/LICENSE-2.0                 *
- *                                                              *
- * Unless required by applicable law or agreed to in writing,   *
- * software distributed under the License is distributed on an  *
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
- * KIND, either express or implied.  See the License for the    *
- * specific language governing permissions and limitations      *
- * under the License.                                           *
- */
-package org.apache.rat.report.claim;
-
-
-import java.util.List;
-
-import org.apache.rat.api.Document;
-import org.apache.rat.api.RatException;
-import org.apache.rat.document.DocumentAnalyser;
-import org.apache.rat.document.RatDocumentAnalysisException;
-import org.apache.rat.report.RatReport;
-import org.apache.rat.report.xml.XmlElements;
-import org.apache.rat.report.xml.writer.IXmlWriter;
-
-/**
- * Executes a RatReport that multiplexes the running of multiple RatReports
- */
-public class ClaimReporterMultiplexer implements RatReport {
-    /** The document analyser to use */
-    private final DocumentAnalyser analyser;
-    /** A list of reports that are being updated */
-    private final List<? extends RatReport> reporters;
-    /** If {@code true} this is a dry run do not generate report */
-    private final boolean dryRun;
-    /** The XML Writer this multiplexer writes to */
-    private final IXmlWriter writer;
-
-    /**
-     * A multiplexer to run multiple claim reports.
-     * @param dryRun true if this is a dry run.
-     * @param analyser the analyser to use.
-     * @param reporters the reports to execute.
-     */
-    public ClaimReporterMultiplexer(final IXmlWriter writer, final boolean 
dryRun, final DocumentAnalyser analyser,
-                                    final List<? extends RatReport> reporters) 
{
-        this.analyser  = analyser;
-        this.reporters = reporters;
-        this.dryRun = dryRun;
-        this.writer = writer;
-    }
-
-    @Override
-    public void report(final Document document) throws RatException {
-        if (!dryRun) {
-            if (analyser != null) {
-                try {
-                    analyser.analyse(document);
-                } catch (RatDocumentAnalysisException e) {
-                    throw new RatException(e.getMessage(), e);
-                }
-            }
-            for (RatReport report : reporters) {
-                report.report(document);
-            }
-        }
-    }
-
-    @Override
-    public void startReport() throws RatException {
-        new XmlElements(writer).ratReport();
-        for (RatReport report : reporters) {
-            report.startReport();
-        }
-    }
-
-    @Override
-    public void endReport() throws RatException {
-        for (RatReport report : reporters) {
-            report.endReport();
-        }
-        new XmlElements(writer).closeElement();
-    }
-}
diff --git 
a/apache-rat-core/src/main/java/org/apache/rat/report/claim/ClaimValidatorReport.java
 
b/apache-rat-core/src/main/java/org/apache/rat/report/claim/ClaimValidatorReport.java
deleted file mode 100644
index ad955a61..00000000
--- 
a/apache-rat-core/src/main/java/org/apache/rat/report/claim/ClaimValidatorReport.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.rat.report.claim;
-
-import org.apache.rat.ReportConfiguration;
-import org.apache.rat.api.Document;
-import org.apache.rat.api.RatException;
-import org.apache.rat.config.results.ClaimValidator;
-import org.apache.rat.report.RatReport;
-import org.apache.rat.report.xml.XmlElements;
-import org.apache.rat.report.xml.writer.IXmlWriter;
-
-/**
- * A RatReport that publishes the results of the ClaimValidator.
- */
-public class ClaimValidatorReport implements RatReport {
-    /**
-     * The XmlElements tool to work with.
-     */
-    private final XmlElements elements;
-    /**
-     * The claim statistics to report.
-     */
-    private final ClaimStatistic statistic;
-    /**
-     * The validator for the statistics.
-     */
-    private final ClaimValidator validator;
-
-    /**
-     * Constructor.
-     * @param writer the XMLWriter to write with.
-     * @param statistic the Claim statistics to report.
-     * @param configuration the configuration for the run.
-     */
-    public ClaimValidatorReport(final IXmlWriter writer, final ClaimStatistic 
statistic, final ReportConfiguration configuration) {
-        this.elements = new XmlElements(writer);
-        this.statistic = statistic;
-        this.validator = configuration.getClaimValidator();
-    }
-
-    @Override
-    public void endReport() throws RatException {
-        elements.statistics();
-        for (ClaimStatistic.Counter counter : ClaimStatistic.Counter.values()) 
{
-            int count = statistic.getCounter(counter);
-            elements.statistic(counter.displayName(), count, 
counter.getDescription(), validator.isValid(counter, count));
-        }
-        for (String category : statistic.getLicenseFamilyCategories()) {
-            elements.licenseCategory(category, 
statistic.getLicenseCategoryCount(category));
-        }
-        for (String category : statistic.getLicenseNames()) {
-            elements.licenseName(category, 
statistic.getLicenseNameCount(category));
-        }
-        for (Document.Type type : statistic.getDocumentTypes()) {
-            elements.documentType(type.name(), statistic.getCounter(type));
-        }
-
-        elements.closeElement();
-    }
-}
diff --git 
a/apache-rat-core/src/main/java/org/apache/rat/report/claim/SimpleXmlClaimReporter.java
 
b/apache-rat-core/src/main/java/org/apache/rat/report/claim/SimpleXmlClaimReporter.java
deleted file mode 100644
index cf9228f6..00000000
--- 
a/apache-rat-core/src/main/java/org/apache/rat/report/claim/SimpleXmlClaimReporter.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one   *
- * or more contributor license agreements.  See the NOTICE file *
- * distributed with this work for additional information        *
- * regarding copyright ownership.  The ASF licenses this file   *
- * to you under the Apache License, Version 2.0 (the            *
- * "License"); you may not use this file except in compliance   *
- * with the License.  You may obtain a copy of the License at   *
- *                                                              *
- *   http://www.apache.org/licenses/LICENSE-2.0                 *
- *                                                              *
- * Unless required by applicable law or agreed to in writing,   *
- * software distributed under the License is distributed on an  *
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
- * KIND, either express or implied.  See the License for the    *
- * specific language governing permissions and limitations      *
- * under the License.                                           *
- */
-package org.apache.rat.report.claim;
-
-import java.util.Iterator;
-
-import org.apache.rat.api.Document;
-import org.apache.rat.api.MetaData;
-import org.apache.rat.api.RatException;
-import org.apache.rat.license.ILicense;
-import org.apache.rat.report.RatReport;
-import org.apache.rat.report.xml.XmlElements;
-import org.apache.rat.report.xml.writer.IXmlWriter;
-
-/**
- * A claim reporter to write the XML document.
- */
-public class SimpleXmlClaimReporter implements RatReport {
-
-
-    /** the writer to write to */
-    private final XmlElements xmlElements;
-
-    /**
-     * Constructor.
-     * @param writer The writer to write the report to.
-     */
-    public SimpleXmlClaimReporter(final IXmlWriter writer) {
-        this.xmlElements = new XmlElements(writer);
-    }
-
-    @Override
-    public void report(final Document document) throws RatException {
-        final MetaData metaData = document.getMetaData();
-        xmlElements.document(document);
-        for (Iterator<ILicense> iter = metaData.licenses().iterator(); 
iter.hasNext();) {
-            final ILicense license = iter.next();
-            xmlElements.license(license, metaData.isApproved(license));
-        }
-        xmlElements.closeElement();
-    }
-}
diff --git 
a/apache-rat-core/src/main/java/org/apache/rat/report/xml/XmlElements.java 
b/apache-rat-core/src/main/java/org/apache/rat/report/xml/XmlElements.java
index 35d14b10..922b1d92 100644
--- a/apache-rat-core/src/main/java/org/apache/rat/report/xml/XmlElements.java
+++ b/apache-rat-core/src/main/java/org/apache/rat/report/xml/XmlElements.java
@@ -28,309 +28,345 @@ import org.apache.rat.VersionInfo;
 import org.apache.rat.api.Document;
 import org.apache.rat.api.MetaData;
 import org.apache.rat.api.RatException;
+import org.apache.rat.config.results.ClaimValidator;
 import org.apache.rat.license.ILicense;
-import org.apache.rat.report.xml.writer.IXmlWriter;
+import org.apache.rat.report.claim.ClaimStatistic;
+import org.apache.rat.report.xml.writer.XmlWriter;
 import org.apache.rat.utils.CasedString;
 
 /**
  * Creates the elements in the XML report.
  */
-public class XmlElements {
-    /**
-     * Converts an enum name to snake case.
-     * @param name the enum name to convert.
-     * @return a camel cased name.
-     */
-    private static String normalizeName(final String name) {
-        CasedString casedName = new CasedString(CasedString.StringCase.SNAKE, 
name.toLowerCase(Locale.ROOT));
-       return casedName.toCase(CasedString.StringCase.PASCAL);
-    }
-
-    /**
-     * The elements in the report.
-     */
-    public enum Elements {
-        /** The start of the RAT report */
-        RAT_REPORT("rat-report"),
-        /** The version of RAT being run */
-        VERSION(),
-        /** A resource element */
-        RESOURCE(),
-        /** A license element */
-        LICENSE(),
-        /** A notes element */
-        NOTES(),
-        /** A statistics element */
-        STATISTICS(),
-        /** A statistic entry */
-        STATISTIC(),
-        /** A license name entry */
-        LICENSE_NAME(),
-        /** A license category entry */
-        LICENSE_CATEGORY(),
-        /** A document type entry */
-        DOCUMENT_TYPE();
-
-        /** The XML name for the element */
-        private final String elementName;
-
-        /**
-         * Constructor.
-         * @param elementName the XML name for the element.
-         */
-        Elements(final String elementName) {
-            this.elementName = elementName;
-        }
-
-        Elements() {
-            this.elementName = normalizeName(name());
-        }
-
-        /**
-         * Gets the XML element name.
-         * @return the XML element name.
-         */
-        public String getElementName() {
-            return elementName;
-        }
-    }
-
-    /**
-     * The attributes of elements in the report.
-     */
-    public enum Attributes {
-        /** A timestamp */
-        TIMESTAMP,
-        /** A version string */
-        VERSION,
-        /** The product identifier */
-        PRODUCT,
-        /** The vendor identifier */
-        VENDOR,
-        /** The approval flag */
-        APPROVAL,
-        /** The family category */
-        FAMILY,
-        /** The document type */
-        TYPE,
-        /** The id */
-        ID,
-        /** The name */
-        NAME,
-        /** A counter */
-        COUNT,
-        /** A description */
-        DESCRIPTION,
-        /** The media type for a document */
-        MEDIA_TYPE,
-        /** The encoding for a text document */
-        ENCODING,
-        /** Denotes a skipped directory */
-        IS_DIRECTORY
+public final class XmlElements {
+    private XmlElements() {
+        // do not instantiate
     }
 
-    /** The XMLWriter that we write to */
-    private final IXmlWriter writer;
-
     /**
-     * Constructor.
-     * @param xmlWriter The writer to use.
+     * Converts an enum name to pascal case.
+     *
+     * @param name the attribute to normalize
+     * @return a pascal cased name.
      */
-    public XmlElements(final IXmlWriter xmlWriter) {
-        this.writer = xmlWriter;
+    private static String normalizeName(final String name) {
+        CasedString casedName = new CasedString(CasedString.StringCase.SNAKE, 
name.toLowerCase(Locale.ROOT));
+        return casedName.toCase(CasedString.StringCase.PASCAL);
     }
 
     /**
      * Create the RAT report element. Includes the timestamp and the version 
element.
-     * @return this.
+     * Does not close the report.
+     *
      * @throws RatException on error
      */
-    public XmlElements ratReport() throws RatException {
-        return write(Elements.RAT_REPORT).
-                write(Attributes.TIMESTAMP, 
DateFormatUtils.ISO_8601_EXTENDED_DATETIME_TIME_ZONE_FORMAT.format(Calendar.getInstance()))
-                .version();
+    public static void ratReport(final XmlWriter writer) throws RatException {
+        try {
+            writer.startElement(Elements.RAT_REPORT.elementName)
+                    .attribute(Attributes.TIMESTAMP.attributeName(),
+                            
DateFormatUtils.ISO_8601_EXTENDED_DATETIME_TIME_ZONE_FORMAT.format(Calendar.getInstance()));
+            version(writer);
+        } catch (IOException e) {
+            throw new RatException(e);
+        }
     }
 
     /**
      * Creates the version element with all version attributes populated. 
Closes the version element.
-     * @return this.
+     *
      * @throws RatException on error
      */
-    public XmlElements version() throws RatException {
+    public static void version(final XmlWriter writer) throws RatException {
         VersionInfo versionInfo = new VersionInfo();
-        return write(Elements.VERSION)
-                .write(Attributes.PRODUCT, versionInfo.getTitle())
-                .write(Attributes.VENDOR, versionInfo.getVendor())
-                .write(Attributes.VERSION, versionInfo.getVersion())
-                .closeElement();
+        try {
+            writer.startElement(Elements.VERSION.elementName)
+                    .attribute(Attributes.PRODUCT.attributeName(), 
versionInfo.getTitle())
+                    .attribute(Attributes.VENDOR.attributeName(), 
versionInfo.getVendor())
+                    .attribute(Attributes.VERSION.attributeName(), 
versionInfo.getVersion())
+                    .closeElement();
+        } catch (IOException e) {
+            throw new RatException(e);
+        }
     }
 
     /**
      * Creates a license element. Closes the element before exit.
-     * @param license the license for the element.
+     *
+     * @param license  the license for the element.
      * @param approved {@code true} if the license is approved.
-     * @return this
      * @throws RatException on error.
      */
-    public XmlElements license(final ILicense license, final boolean approved) 
throws RatException {
-        write(Elements.LICENSE).write(Attributes.ID, license.getId())
-                .write(Attributes.NAME, license.getName())
-                .write(Attributes.APPROVAL, 
Boolean.valueOf(approved).toString())
-                .write(Attributes.FAMILY, 
license.getLicenseFamily().getFamilyCategory());
-        if (StringUtils.isNotBlank(license.getNote())) {
-            try {
-                write(Elements.NOTES).cdata(license.getNote()).closeElement();
-            } catch (IOException e) {
-                throw new RatException("Can not write CDATA for 'notes' 
element", e);
+    public static void license(final XmlWriter writer, final ILicense license, 
final boolean approved) throws RatException {
+        try {
+            writer.startElement(Elements.LICENSE.elementName)
+                    .attribute(Attributes.ID.attributeName(), license.getId())
+                    .attribute(Attributes.NAME.attributeName(), 
license.getName())
+                    .attribute(Attributes.APPROVAL.attributeName(), 
Boolean.toString(approved))
+                    .attribute(Attributes.FAMILY.attributeName(), 
license.getLicenseFamily().getFamilyCategory());
+
+            if (StringUtils.isNotBlank(license.getNote())) {
+                
writer.startElement(Elements.NOTES.elementName).cdata(license.getNote()).closeElement();
             }
+            writer.closeElement();
+        } catch (IOException e) {
+            throw new RatException(e);
         }
-        return closeElement();
     }
 
     /**
-     * Writes a CDATA block
-     * @param data the data to write.
-     * @return this
-     * @throws IOException on error.
-     */
-    private XmlElements cdata(final String data) throws IOException {
-        writer.cdata(data);
-        return this;
-    }
-
-    /**
-     * Creates a document element with attributes. Does NOT close the document 
element.
+     * Creates a document element with attributes. Does <strong>NOT</strong> 
close the document element.
+     *
      * @param document the document to write.
-     * @return this
      * @throws RatException on error.
      */
-    public XmlElements document(final Document document) throws RatException {
+    public static void document(final XmlWriter writer, final Document 
document) throws RatException {
         final MetaData metaData = document.getMetaData();
-        XmlElements result = write(Elements.RESOURCE)
-                .write(Attributes.NAME, document.getName().localized("/"))
-                .write(Attributes.TYPE, metaData.getDocumentType().toString())
-                .write(Attributes.MEDIA_TYPE, 
metaData.getMediaType().toString());
-        if (Document.Type.STANDARD == metaData.getDocumentType() || 
metaData.hasCharset()) {
-            result = result.write(Attributes.ENCODING, 
metaData.getCharset().displayName());
-        }
-        if (document.isIgnored()) {
-            result = result.write(Attributes.IS_DIRECTORY, 
Boolean.toString(document.isDirectory()));
+        try {
+            writer.startElement(Elements.RESOURCE.elementName)
+                    .attribute(Attributes.NAME.attributeName(), 
document.getName().localized("/"))
+                    .attribute(Attributes.TYPE.attributeName(), 
metaData.getDocumentType().toString())
+                    .attribute(Attributes.MEDIA_TYPE.attributeName(), 
metaData.getMediaType().toString());
+            if (Document.Type.STANDARD == metaData.getDocumentType() || 
metaData.hasCharset()) {
+                writer.attribute(Attributes.ENCODING.attributeName(), 
metaData.getCharset().displayName());
+            }
+            if (document.isIgnored()) {
+                writer.attribute(Attributes.IS_DIRECTORY.attributeName(), 
Boolean.toString(document.isDirectory()));
+            }
+        } catch (IOException e) {
+            throw new RatException(e);
         }
-        return result;
     }
 
     /**
      * Creates a statistics element.
-     * @return this
+     *
      * @throws RatException on error.
      */
-    public XmlElements statistics() throws RatException {
-        return write(Elements.STATISTICS);
+    public static void statistics(final XmlWriter writer, final ClaimStatistic 
statistic, final ClaimValidator validator) throws RatException {
+        try {
+            writer.startElement(Elements.STATISTICS.elementName);
+            for (ClaimStatistic.Counter counter : 
ClaimStatistic.Counter.values()) {
+                int count = statistic.getCounter(counter);
+                XmlElements.statistic(writer, counter.displayName(), count, 
counter.getDescription(), validator.isValid(counter, count));
+            }
+            for (String category : statistic.getLicenseFamilyCategories()) {
+                XmlElements.licenseCategory(writer, category, 
statistic.getLicenseCategoryCount(category));
+            }
+            for (String category : statistic.getLicenseNames()) {
+                XmlElements.licenseName(writer, category, 
statistic.getLicenseNameCount(category));
+            }
+            for (Document.Type type : statistic.getDocumentTypes()) {
+                XmlElements.documentType(writer, type.name(), 
statistic.getCounter(type));
+            }
+
+            writer.closeElement();
+        } catch (IOException e) {
+            throw new RatException(e);
+        }
     }
 
     /**
      * Creates a statistic element. Closes the element before returning.
-     * @param name the name of the statistics element.
-     * @param count the count for the element.
+     *
+     * @param name        the name of the statistics element.
+     * @param count       the count for the element.
      * @param description description of this statistic.
-     * @param isOk if {@code true} the count is within limits.
-     * @return this
+     * @param isOk        if {@code true} the count is within limits.
      * @throws RatException on error.
      */
-    public XmlElements statistic(final String name, final int count, final 
String description, final boolean isOk) throws RatException {
-        return write(Elements.STATISTIC)
-                .write(Attributes.NAME, name)
-                .write(Attributes.COUNT, Integer.toString(count))
-                .write(Attributes.APPROVAL, Boolean.toString(isOk))
-                .write(Attributes.DESCRIPTION, description)
-                .closeElement();
+    public static void statistic(final XmlWriter writer, final String name, 
final int count, final String description, final boolean isOk) throws 
RatException {
+        try {
+            writer.startElement(Elements.STATISTIC.elementName)
+                    .attribute(Attributes.NAME.attributeName(), name)
+                    .attribute(Attributes.COUNT.attributeName(), 
Integer.toString(count))
+                    .attribute(Attributes.APPROVAL.attributeName(), 
Boolean.toString(isOk))
+                    .attribute(Attributes.DESCRIPTION.attributeName(), 
description)
+                    .closeElement();
+        } catch (IOException e) {
+            throw new RatException(e);
+        }
     }
 
     /**
      * Creates a statistic element. Closes the element before returning.
-     * @param name the name of the statistics element.
+     *
+     * @param name  the name of the statistics element.
      * @param count the count for the element.
-     * @return this
      * @throws RatException on error.
      */
-    public XmlElements licenseCategory(final String name, final int count) 
throws RatException {
-        return write(Elements.LICENSE_CATEGORY)
-                .write(Attributes.NAME, name)
-                .write(Attributes.COUNT, Integer.toString(count))
-                .closeElement();
+    public static void licenseCategory(final XmlWriter writer, final String 
name, final int count) throws RatException {
+        try {
+            writer.startElement(Elements.LICENSE_CATEGORY.elementName)
+                    .attribute(Attributes.NAME.attributeName(), name)
+                    .attribute(Attributes.COUNT.attributeName(), 
Integer.toString(count))
+                    .closeElement();
+        } catch (IOException e) {
+            throw new RatException(e);
+        }
     }
 
     /**
      * Creates a statistic element. Closes the element before returning.
-     * @param name the name of the statistics element.
+     *
+     * @param name  the name of the statistics element.
      * @param count the count for the element.
-     * @return this
      * @throws RatException on error.
      */
-    public XmlElements licenseName(final String name, final int count) throws 
RatException {
-        return write(Elements.LICENSE_NAME)
-                .write(Attributes.NAME, name)
-                .write(Attributes.COUNT, Integer.toString(count))
-                .closeElement();
+    public static void licenseName(final XmlWriter writer, final String name, 
final int count) throws RatException {
+        try {
+            writer.startElement(Elements.LICENSE_NAME.elementName)
+                    .attribute(Attributes.NAME.attributeName(), name)
+                    .attribute(Attributes.COUNT.attributeName(), 
Integer.toString(count))
+                    .closeElement();
+        } catch (IOException e) {
+            throw new RatException(e);
+        }
     }
 
     /**
-     * Creates a statistic element. Closes the element before returning.
-     * @param name the name of the statistics element.
+     * Creates a document type element. Closes the element before returning.
+     *
+     * @param name  the name of the document type element.
      * @param count the count for the element.
-     * @return this
      * @throws RatException on error.
      */
-    public XmlElements documentType(final String name, final int count) throws 
RatException {
-        return write(Elements.DOCUMENT_TYPE)
-                .write(Attributes.NAME, name)
-                .write(Attributes.COUNT, Integer.toString(count))
-                .closeElement();
-    }
-
-    /**
-     * Closes the currently open element.
-     * @return this
-     * @throws RatException on error.
-     */
-    public XmlElements closeElement() throws RatException {
+    public static void documentType(final XmlWriter writer, final String name, 
final int count) throws RatException {
         try {
-            writer.closeElement();
-            return this;
+            writer.startElement(Elements.DOCUMENT_TYPE.elementName)
+                    .attribute(Attributes.NAME.attributeName(), name)
+                    .attribute(Attributes.COUNT.attributeName(), 
Integer.toString(count))
+                    .closeElement();
         } catch (IOException e) {
-            throw new RatException("Cannot close currently open element", e);
+            throw new RatException(e);
         }
-
     }
 
     /**
-     * Write an element. The element is not closed.
-     * @param element the element to write.
-     * @return this
-     * @throws RatException on error.
+     * The elements in the report.
      */
-    private XmlElements write(final Elements element) throws RatException {
-        try {
-            writer.openElement(element.getElementName());
-            return this;
-        } catch (IOException e) {
-            throw new RatException("Cannot open start element: " + 
element.elementName, e);
+    public enum Elements {
+        /**
+         * The start of the RAT report.
+         */
+        RAT_REPORT("rat-report"),
+        /**
+         * The version of RAT being run.
+         */
+        VERSION(),
+        /**
+         * A resource element.
+         */
+        RESOURCE(),
+        /**
+         * A license element.
+         */
+        LICENSE(),
+        /**
+         * A notes element.
+         */
+        NOTES(),
+        /**
+         * A statistics element.
+         */
+        STATISTICS(),
+        /**
+         * A statistic entry.
+         */
+        STATISTIC(),
+        /**
+         * A license name entry.
+         */
+        LICENSE_NAME(),
+        /**
+         * A license category entry.
+         */
+        LICENSE_CATEGORY(),
+        /**
+         * A document type entry.
+         */
+        DOCUMENT_TYPE();
+
+        /**
+         * The XML name for the element
+         */
+        private final String elementName;
+
+        /**
+         * Constructor.
+         *
+         * @param elementName the XML name for the element.
+         */
+        Elements(final String elementName) {
+            this.elementName = elementName;
+        }
+
+        Elements() {
+            this.elementName = normalizeName(name());
         }
     }
 
     /**
-     * Write an attribute.
-     * @param attribute the attribute name.
-     * @param value the attribute value.
-     * @return this
-     * @throws RatException on error.
+     * The attributes of elements in the report.
      */
-    public XmlElements write(final Attributes attribute, final String value) 
throws RatException {
-        try {
-            writer.attribute(normalizeName(attribute.name()), value);
-            return this;
-        } catch (IOException e) {
-            throw new RatException("Cannot open add attribute: " + attribute, 
e);
+    public enum Attributes {
+        /**
+         * A timestamp.
+         */
+        TIMESTAMP,
+        /**
+         * A version string.
+         */
+        VERSION,
+        /**
+         * The product identifier.
+         */
+        PRODUCT,
+        /**
+         * The vendor identifier.
+         */
+        VENDOR,
+        /**
+         * The approval flag.
+         */
+        APPROVAL,
+        /**
+         * The family category.
+         */
+        FAMILY,
+        /**
+         * The document type.
+         */
+        TYPE,
+        /**
+         * The id.
+         */
+        ID,
+        /**
+         * The name.
+         */
+        NAME,
+        /**
+         * A counter.
+         */
+        COUNT,
+        /**
+         * A description.
+         */
+        DESCRIPTION,
+        /**
+         * The media type for a document.
+         */
+        MEDIA_TYPE,
+        /**
+         * The encoding for a text document.
+         */
+        ENCODING,
+        /**
+         * Denotes a skipped directory.
+         */
+        IS_DIRECTORY;
+
+        String attributeName() {
+            return normalizeName(this.name());
         }
     }
 }
diff --git 
a/apache-rat-core/src/main/java/org/apache/rat/report/xml/XmlReportFactory.java 
b/apache-rat-core/src/main/java/org/apache/rat/report/xml/XmlReportFactory.java
index 2bdce3b0..4214b4fb 100644
--- 
a/apache-rat-core/src/main/java/org/apache/rat/report/xml/XmlReportFactory.java
+++ 
b/apache-rat-core/src/main/java/org/apache/rat/report/xml/XmlReportFactory.java
@@ -18,21 +18,26 @@
  */
 package org.apache.rat.report.xml;
 
+import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Iterator;
 import java.util.List;
 
 import org.apache.rat.ReportConfiguration;
 import org.apache.rat.analysis.AnalyserFactory;
+import org.apache.rat.api.Document;
+import org.apache.rat.api.MetaData;
+import org.apache.rat.api.RatException;
+import org.apache.rat.configuration.XMLConfigurationWriter;
+import org.apache.rat.document.DocumentAnalyser;
+import org.apache.rat.document.RatDocumentAnalysisException;
+import org.apache.rat.license.ILicense;
 import org.apache.rat.license.LicenseSetFactory.LicenseFilter;
-import org.apache.rat.report.ConfigurationReport;
 import org.apache.rat.report.RatReport;
 import org.apache.rat.report.claim.ClaimAggregator;
-import org.apache.rat.report.claim.ClaimReporterMultiplexer;
 import org.apache.rat.report.claim.ClaimStatistic;
-import org.apache.rat.report.claim.ClaimValidatorReport;
 import org.apache.rat.report.claim.LicenseAddingReport;
-import org.apache.rat.report.claim.SimpleXmlClaimReporter;
-import org.apache.rat.report.xml.writer.IXmlWriter;
+import org.apache.rat.report.xml.writer.XmlWriter;
 
 /**
  * A factory to create reports from a writer and a configuration.
@@ -53,7 +58,7 @@ public final class XmlReportFactory {
      * @param configuration The report configuration.
      * @return a RatReport instance.
      */
-    public static RatReport createStandardReport(final IXmlWriter writer, 
final ClaimStatistic statistic, final ReportConfiguration configuration) {
+    public static RatReport createStandardReport(final XmlWriter writer, final 
ClaimStatistic statistic, final ReportConfiguration configuration) {
         final List<RatReport> reporters = new ArrayList<>();
         if (statistic != null) {
             reporters.add(new ClaimAggregator(statistic));
@@ -63,13 +68,116 @@ public final class XmlReportFactory {
         }
 
         if (configuration.listFamilies() != LicenseFilter.NONE || 
configuration.listLicenses() != LicenseFilter.NONE) {
-
-            reporters.add(new ConfigurationReport(writer, configuration));
+            reporters.add(configuration(writer, configuration));
         }
 
-        reporters.add(new SimpleXmlClaimReporter(writer));
-        reporters.add(new ClaimValidatorReport(writer, statistic, 
configuration));
+        reporters.add(simple(writer));
+        reporters.add(validator(writer, statistic, configuration));
+
+        return multiplexer(writer, configuration.isDryRun(), 
AnalyserFactory.createConfiguredAnalyser(configuration), reporters);
+    }
+
+    /**
+     * Creates a simple claim reporter.
+     * @param writer The XmlWriter to use
+     * @return the RatReport that will write to the writer.
+     */
+    public static RatReport simple(final XmlWriter writer) {
+        return new RatReport() {
+            @Override
+            public void report(final Document document) throws RatException {
+                final MetaData metaData = document.getMetaData();
+                XmlElements.document(writer, document);
+                for (Iterator<ILicense> iter = metaData.licenses().iterator(); 
iter.hasNext();) {
+                    final ILicense license = iter.next();
+                    XmlElements.license(writer, license, 
metaData.isApproved(license));
+                }
+                try {
+                    writer.closeElement();
+                } catch (IOException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+        };
+    }
+
+    /**
+     * Creates a simple claim validator reporter
+     * @param writer The XmlWriter to use
+     * @return the RatReport that will write to the writer.
+     */
+    public static RatReport validator(final XmlWriter writer, final 
ClaimStatistic statistic, final ReportConfiguration configuration) {
+        return new RatReport() {
+            @Override
+            public void endReport() throws RatException {
+                XmlElements.statistics(writer, statistic, 
configuration.getClaimValidator());
+            }
+        };
+    }
+
+    /**
+     * Creates a configuration report
+     *
+     * @param writer The writer to write the XML data to.
+     * @param configuration the configuration to write
+     */
+    public static RatReport configuration(final XmlWriter writer, final 
ReportConfiguration configuration) {
+        return new RatReport() {
+            @Override
+            public void startReport() throws RatException {
+                if (configuration.listFamilies() != LicenseFilter.NONE || 
configuration.listLicenses() != LicenseFilter.NONE) {
+                    new XMLConfigurationWriter(configuration).write(writer);
+                }
+            }
+        };
+    }
+
 
-        return new ClaimReporterMultiplexer(writer, configuration.isDryRun(), 
AnalyserFactory.createConfiguredAnalyser(configuration), reporters);
+    /**
+     * Creates a RatReport that multiplexes the running of multiple RatReports
+     */
+    public static RatReport multiplexer(final XmlWriter writer, final boolean 
dryRun, final DocumentAnalyser analyser,
+                                        final List<? extends RatReport> 
reporters) {
+        return new MultiplexerReport(writer, dryRun, analyser, reporters);
     }
+
+    private record MultiplexerReport(XmlWriter writer, boolean dryRun, 
DocumentAnalyser analyser,
+                                     List<? extends RatReport> reporters) 
implements RatReport {
+
+        @Override
+            public void report(final Document document) throws RatException {
+                if (!dryRun) {
+                    if (analyser != null) {
+                        try {
+                            analyser.analyse(document);
+                        } catch (RatDocumentAnalysisException e) {
+                            throw new RatException(e.getMessage(), e);
+                        }
+                    }
+                    for (RatReport report : reporters) {
+                        report.report(document);
+                    }
+                }
+            }
+
+            @Override
+            public void startReport() throws RatException {
+                XmlElements.ratReport(writer);
+                for (RatReport report : reporters) {
+                    report.startReport();
+                }
+            }
+
+            @Override
+            public void endReport() throws RatException {
+                for (RatReport report : reporters) {
+                    report.endReport();
+                }
+                try {
+                    writer.closeElement();
+                } catch (IOException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+        }
 }
diff --git 
a/apache-rat-core/src/main/java/org/apache/rat/report/xml/writer/IXmlWriter.java
 
b/apache-rat-core/src/main/java/org/apache/rat/report/xml/writer/IXmlWriter.java
deleted file mode 100644
index a227b9c5..00000000
--- 
a/apache-rat-core/src/main/java/org/apache/rat/report/xml/writer/IXmlWriter.java
+++ /dev/null
@@ -1,159 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one   *
- * or more contributor license agreements.  See the NOTICE file *
- * distributed with this work for additional information        *
- * regarding copyright ownership.  The ASF licenses this file   *
- * to you under the Apache License, Version 2.0 (the            *
- * "License"); you may not use this file except in compliance   *
- * with the License.  You may obtain a copy of the License at   *
- *                                                              *
- *   http://www.apache.org/licenses/LICENSE-2.0                 *
- *                                                              *
- * Unless required by applicable law or agreed to in writing,   *
- * software distributed under the License is distributed on an  *
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
- * KIND, either express or implied.  See the License for the    *
- * specific language governing permissions and limitations      *
- * under the License.                                           *
- */
-package org.apache.rat.report.xml.writer;
-
-import java.io.IOException;
-
-import org.apache.rat.report.xml.XmlElements;
-import org.w3c.dom.Document;
-
-/**
- * Simple interface for creating basic XML documents.
- * Performs basic validation and escaping.
- * Not namespace aware.
- */
-public interface IXmlWriter extends AutoCloseable {
-
-    /**
-     * Starts a document by writing a prologue.
-     * Calling this method is optional.
-     * When writing a document fragment, it should <em>not</em> be called.
-     * @return this object
-     * @throws OperationNotAllowedException
-     * if called after the first element has been written
-     * or once a prolog has already been written
-     */
-    IXmlWriter startDocument() throws IOException;
-
-    /**
-     * Writes the start of an element.
-     *
-     * @param elementName the name of the element, not null
-     * @return this object
-     * @throws InvalidXmlException if the name is not valid for a xml element
-     * @throws OperationNotAllowedException
-     * if called after the first element has been closed
-     */
-    IXmlWriter openElement(CharSequence elementName) throws IOException;
-
-    /**
-     * Writes the start of an element.
-     *
-     * @param element the element, not null
-     * @return this object
-     * @throws InvalidXmlException if the name is not valid for a xml element
-     * @throws OperationNotAllowedException
-     * if called after the first element has been closed
-     */
-    default IXmlWriter openElement(XmlElements.Elements element) throws 
IOException {
-        return openElement(element.getElementName());
-    }
-
-    /**
-     * Writes a comment.
-     *
-     * @param text the comment text
-     * @return this object
-     * @throws OperationNotAllowedException
-     * if called after the first element has been closed
-     */
-    IXmlWriter comment(CharSequence text) throws IOException;
-
-    /**
-     * Writes an attribute of an element.
-     * Note that this is only allowed directly after {@link 
#openElement(CharSequence)}
-     * or this method.
-     *
-     * @param name the attribute name, not null
-     * @param value the attribute value, not null
-     * @return this object
-     * @throws InvalidXmlException if the name is not valid for a xml attribute
-     * or if a value for the attribute has already been written
-     * @throws OperationNotAllowedException if called after {@link 
#content(CharSequence)}
-     * or {@link #closeElement()} or before any call to {@link 
#openElement(CharSequence)}
-     */
-    IXmlWriter attribute(CharSequence name, CharSequence value) throws 
IOException;
-
-    /**
-     * Writes content.
-     * Note that this method does not support CDATA.
-     * This method automatically escapes characters.
-     *
-     * @param content the content to write
-     * @return this object
-     * @throws OperationNotAllowedException
-     * if called before any call to {@link #openElement(CharSequence)}
-     * or after the first element has been closed
-     */
-    IXmlWriter content(CharSequence content) throws IOException;
-
-    /**
-     * Writes CDATA content.
-     * This method DOES NOT automatically escape characters.
-     * It will remove enclosed CDATA closing strings (e.g. {@code ]]>})
-     *
-     * @param content the content to write
-     * @return this object
-     * @throws OperationNotAllowedException
-     * if called before any call to {@link #openElement(CharSequence)}
-     * or after the first element has been closed
-     */
-    IXmlWriter cdata(CharSequence content) throws IOException;
-
-    /**
-     * Closes the last element written.
-     *
-     * @return this object
-     * @throws OperationNotAllowedException
-     * if called before any call to {@link #openElement(CharSequence)}
-     * or after the first element has been closed
-     */
-    IXmlWriter closeElement() throws IOException;
-
-    /**
-     * Closes all open elements back to and including the named element.
-     *
-     * @param name  the last element to close
-     * @return this object
-     * @throws OperationNotAllowedException
-     * if called before any call to {@link #openElement(CharSequence)}
-     * or after the first element has been closed
-     */
-    IXmlWriter closeElement(CharSequence name) throws IOException;
-
-    /**
-     * Closes all pending elements.
-     * When appropriate, resources are also flushed and closed.
-     * No exception is raised when called upon a document whose
-     * root element has already been closed.
-     *
-     * @return this object
-     * @throws OperationNotAllowedException
-     * if called before any call to {@link #openElement(CharSequence)}
-     */
-    IXmlWriter closeDocument() throws IOException;
-
-    /**
-     * Append an XML document into this one.
-     * @param document the document to append
-     * @return this object
-     * @throws IOException on error.
-     */
-    IXmlWriter append(Document document) throws IOException;
-}
diff --git 
a/apache-rat-core/src/main/java/org/apache/rat/report/xml/writer/OperationNotAllowedException.java
 
b/apache-rat-core/src/main/java/org/apache/rat/report/xml/writer/OperationNotAllowedException.java
index df248bac..943abe6a 100644
--- 
a/apache-rat-core/src/main/java/org/apache/rat/report/xml/writer/OperationNotAllowedException.java
+++ 
b/apache-rat-core/src/main/java/org/apache/rat/report/xml/writer/OperationNotAllowedException.java
@@ -21,7 +21,7 @@ package org.apache.rat.report.xml.writer;
 import java.io.IOException;
 
 /**
- * Thrown by {@link IXmlWriter} implementations when the current
+ * Thrown by {@link XmlWriter} implementations when the current
  * state does not allow the requested operation.
  */
 public class OperationNotAllowedException extends IOException {
diff --git 
a/apache-rat-core/src/main/java/org/apache/rat/report/xml/writer/XmlWriter.java 
b/apache-rat-core/src/main/java/org/apache/rat/report/xml/writer/XmlWriter.java
index 9a49fbdd..c9c0c16d 100644
--- 
a/apache-rat-core/src/main/java/org/apache/rat/report/xml/writer/XmlWriter.java
+++ 
b/apache-rat-core/src/main/java/org/apache/rat/report/xml/writer/XmlWriter.java
@@ -36,9 +36,6 @@ import org.apache.rat.utils.StandardXmlFactory;
 import org.w3c.dom.Document;
 
 /**
- * <p>
- * Lightweight {@link IXmlWriter} implementation.
- * </p>
  * <p>
  * Requires a wrapper to be used safely in a multithreaded environment.
  * </p>
@@ -47,7 +44,7 @@ import org.w3c.dom.Document;
  * </p>
  */
 @SuppressWarnings({"checkstyle:MagicNumber", "checkstyle:JavadocVariable"})
-public final class XmlWriter implements IXmlWriter {
+public final class XmlWriter implements AutoCloseable {
     private final Appendable appendable;
     private final ArrayDeque<CharSequence> elementNames;
     private final Set<CharSequence> currentAttributes = new HashSet<>();
@@ -86,8 +83,7 @@ public final class XmlWriter implements IXmlWriter {
      * @throws OperationNotAllowedException if called after the first element 
has
      * been written or once a prolog has already been written
      */
-    @Override
-    public IXmlWriter startDocument() throws IOException {
+    public XmlWriter startDocument() throws IOException {
         if (elementsWritten) {
             throw new OperationNotAllowedException("Document already started");
         }
@@ -108,8 +104,7 @@ public final class XmlWriter implements IXmlWriter {
      * @throws OperationNotAllowedException if called after the first element 
has
      * been closed
      */
-    @Override
-    public IXmlWriter openElement(final CharSequence elementName) throws 
IOException {
+    public XmlWriter startElement(final CharSequence elementName) throws 
IOException {
         validateRootOpen();
         if (!XMLChar.isValidName(elementName.toString())) {
             throw new InvalidXmlException("'" + elementName + "' is not a 
valid element name");
@@ -124,8 +119,15 @@ public final class XmlWriter implements IXmlWriter {
         return this;
     }
 
-    @Override
-    public IXmlWriter comment(final CharSequence text) throws IOException {
+    /**
+     * Writes a comment.
+     *
+     * @param text the comment text
+     * @return this object
+     * @throws OperationNotAllowedException
+     * if called after the first element has been closed
+     */
+    public XmlWriter comment(final CharSequence text) throws IOException {
         maybeCloseElement();
         appendable.append("<!-- ");
         writeEscaped(text, false);
@@ -135,7 +137,7 @@ public final class XmlWriter implements IXmlWriter {
 
     /**
      * Writes an attribute of an element. Note that this is only allowed 
directly
-     * after {@link #openElement(CharSequence)} or a previous {@code 
attribute} call.
+     * after {@link #startElement(CharSequence)} or a previous {@code 
attribute} call.
      *
      * @param name the attribute name, not null
      * @param value the attribute value, not null
@@ -144,10 +146,9 @@ public final class XmlWriter implements IXmlWriter {
      * if a value for the attribute has already been written
      * @throws OperationNotAllowedException if called after
      * {@link #content(CharSequence)} or {@link #closeElement()} or before any 
call
-     * to {@link #openElement(CharSequence)}
+     * to {@link #startElement(CharSequence)}
      */
-    @Override
-    public IXmlWriter attribute(final CharSequence name, final CharSequence 
value) throws IOException {
+    public XmlWriter attribute(final CharSequence name, final CharSequence 
value) throws IOException {
         if (elementNames.isEmpty()) {
             validateRootOpen();
             throw new OperationNotAllowedException("Close called before an 
element has been opened.");
@@ -182,15 +183,34 @@ public final class XmlWriter implements IXmlWriter {
         maybeCloseElement();
     }
 
-    @Override
-    public IXmlWriter content(final CharSequence content) throws IOException {
+    /**
+     * Writes content.
+     * Note that this method does not support CDATA.
+     * This method automatically escapes characters.
+     *
+     * @param content the content to write
+     * @return this object
+     * @throws OperationNotAllowedException
+     * if called before any call to {@link #startElement(CharSequence)}
+     * or after the first element has been closed
+     */public XmlWriter content(final CharSequence content) throws IOException 
{
         prepareForData();
         writeEscaped(content, false);
         return this;
     }
 
-    @Override
-    public IXmlWriter cdata(final CharSequence content) throws IOException {
+    /**
+     * Writes CDATA content.
+     * This method DOES NOT automatically escape characters.
+     * It will remove enclosed CDATA closing strings (e.g. {@code ]]>})
+     *
+     * @param content the content to write
+     * @return this object
+     * @throws OperationNotAllowedException
+     * if called before any call to {@link #startElement(CharSequence)}
+     * or after the first element has been closed
+     */
+    public XmlWriter cdata(final CharSequence content) throws IOException {
         prepareForData();
         StringBuilder sb = new StringBuilder(content);
         int found;
@@ -240,10 +260,9 @@ public final class XmlWriter implements IXmlWriter {
      *
      * @return this object
      * @throws OperationNotAllowedException if called before any call to
-     * {@link #openElement} or after the first element has been closed
+     * {@link #startElement} or after the first element has been closed
      */
-    @Override
-    public IXmlWriter closeElement() throws IOException {
+    public XmlWriter closeElement() throws IOException {
         if (elementNames.isEmpty()) {
             validateRootOpen();
             throw new OperationNotAllowedException("Close called before an 
element has been opened.");
@@ -265,10 +284,9 @@ public final class XmlWriter implements IXmlWriter {
      * @param name The name of the element to close.  Must not be {@code null}.
      * @return this object
      * @throws OperationNotAllowedException if called before any call to
-     * {@link #openElement} or after the first element has been closed
+     * {@link #startElement} or after the first element has been closed
      */
-    @Override
-    public IXmlWriter closeElement(final CharSequence name) throws IOException 
{
+    public XmlWriter closeElement(final CharSequence name) throws IOException {
         Objects.requireNonNull(name);
         if (elementNames.isEmpty()) {
             validateRootOpen();
@@ -296,10 +314,9 @@ public final class XmlWriter implements IXmlWriter {
      *
      * @return this object
      * @throws OperationNotAllowedException if called before any call to
-     * {@link #openElement}
+     * {@link #startElement}
      */
-    @Override
-    public IXmlWriter closeDocument() throws IOException {
+    public XmlWriter closeDocument() throws IOException {
         if (elementNames.isEmpty() && !elementsWritten) {
             throw new OperationNotAllowedException("Close called before an 
element has been opened.");
         }
@@ -311,13 +328,28 @@ public final class XmlWriter implements IXmlWriter {
 
     @Override
     public void close() throws IOException {
-        closeDocument();
-        if (appendable instanceof Closeable closeable) {
-            closeable.close();
+        IOException thrown = null;
+        try {
+            closeDocument();
+        } catch (IOException e) {
+            thrown = e;
+        } finally {
+            if (appendable instanceof Closeable closeable) {
+                try {
+                    closeable.close();
+                } catch (IOException e) {
+                    if (thrown == null) {
+                        thrown = e;
+                    }
+                }
+            }
+        }
+        if (thrown != null) {
+            throw thrown;
         }
     }
 
-    public IXmlWriter append(final Document document) throws IOException {
+    public XmlWriter append(final Document document) throws IOException {
         validateRootOpen();
         elementsWritten = true;
         maybeCloseElement();
diff --git 
a/apache-rat-core/src/test/java/org/apache/rat/analysis/AnalyserFactoryTest.java
 
b/apache-rat-core/src/test/java/org/apache/rat/analysis/AnalyserFactoryTest.java
index 13319f35..22a4945f 100644
--- 
a/apache-rat-core/src/test/java/org/apache/rat/analysis/AnalyserFactoryTest.java
+++ 
b/apache-rat-core/src/test/java/org/apache/rat/analysis/AnalyserFactoryTest.java
@@ -36,7 +36,7 @@ import org.apache.rat.document.DocumentAnalyser;
 import org.apache.rat.document.DocumentName;
 import org.apache.rat.document.FileDocument;
 import org.apache.rat.document.RatDocumentAnalysisException;
-import org.apache.rat.report.claim.SimpleXmlClaimReporter;
+import org.apache.rat.report.xml.XmlReportFactory;
 import org.apache.rat.report.xml.writer.XmlWriter;
 import org.apache.rat.test.utils.Resources;
 import org.apache.rat.testhelpers.TestingDocument;
@@ -53,8 +53,6 @@ public class AnalyserFactoryTest {
 
     private final DocumentName basedir;
 
-    private StringWriter out;
-    private SimpleXmlClaimReporter reporter;
     private DocumentAnalyser analyser;
 
     AnalyserFactoryTest() {
@@ -63,8 +61,6 @@ public class AnalyserFactoryTest {
 
     @BeforeEach
     public void setUp() {
-        out = new StringWriter();
-        reporter = new SimpleXmlClaimReporter(new XmlWriter(out));
         ReportConfiguration config = new ReportConfiguration();
         config.addLicense(UnknownLicense.INSTANCE);
         analyser = AnalyserFactory.createConfiguredAnalyser(config);
@@ -175,13 +171,20 @@ public class AnalyserFactoryTest {
         
assertThat(document.getMetaData().getMediaType().toString()).isEqualTo("application/gzip");
     }
 
+    private String buildReport(final Document document) throws Exception {
+        StringWriter out = new StringWriter();
+        try (XmlWriter writer = new XmlWriter(out)) {
+            XmlReportFactory.simple(writer).report(document);
+        }
+        return out.toString();
+    }
+
     @Test
     public void RAT147_unix_Test() throws Exception {
         final Document document = new FileDocument(basedir,
                 
Resources.getResourceFile("/jira/RAT147/unix-newlines.txt.bin"), 
DocumentNameMatcher.MATCHES_ALL);
         analyser.analyse(document);
-        reporter.report(document);
-        String result = out.toString();
+        String result = buildReport(document);
         TextUtils.assertPatternInTarget(
                 "<resource name='/jira/RAT147/unix-newlines.txt.bin' 
type='STANDARD'",
                 result);
@@ -192,8 +195,7 @@ public class AnalyserFactoryTest {
         final Document document = new FileDocument(basedir,
                 
Resources.getResourceFile("/jira/RAT147/windows-newlines.txt.bin"), 
DocumentNameMatcher.MATCHES_ALL);
         analyser.analyse(document);
-        reporter.report(document);
-        String result = out.toString();
+        String result = buildReport(document);
         TextUtils.assertPatternInTarget(
                 "<resource name='/jira/RAT147/windows-newlines.txt.bin' 
type='STANDARD'",
                 result);
diff --git 
a/apache-rat-core/src/test/java/org/apache/rat/report/ConfigurationReportTest.java
 
b/apache-rat-core/src/test/java/org/apache/rat/report/ConfigurationReportTest.java
index af0d1333..cc964ffe 100644
--- 
a/apache-rat-core/src/test/java/org/apache/rat/report/ConfigurationReportTest.java
+++ 
b/apache-rat-core/src/test/java/org/apache/rat/report/ConfigurationReportTest.java
@@ -35,7 +35,7 @@ import org.apache.rat.ReportConfiguration;
 import org.apache.rat.configuration.MatcherBuilderTracker;
 import org.apache.rat.license.ILicenseFamily;
 import org.apache.rat.license.LicenseSetFactory.LicenseFilter;
-import org.apache.rat.report.xml.writer.IXmlWriter;
+import org.apache.rat.report.xml.XmlReportFactory;
 import org.apache.rat.report.xml.writer.XmlWriter;
 import org.apache.rat.testhelpers.XmlUtils;
 import org.junit.jupiter.api.BeforeEach;
@@ -45,30 +45,31 @@ import org.w3c.dom.Node;
 
 public class ConfigurationReportTest {
 
-    private ConfigurationReport report;
-    private StringWriter sw;
-    private IXmlWriter writer;
-
+    private ReportConfiguration reportConfiguration;
     private final String[] FAMILY_IDS = { "AL", "BSD-3", "CDDL1", "GPL", 
"MIT", "OASIS", "W3C", "W3CD", };
 
     @BeforeEach
     public void setup() {
-        ReportConfiguration reportConfiguration = new ReportConfiguration();
+        reportConfiguration = new ReportConfiguration();
         reportConfiguration.listFamilies(LicenseFilter.ALL);
         reportConfiguration.listLicenses(LicenseFilter.ALL);
         reportConfiguration.setFrom(Defaults.builder().build());
+    }
 
-        sw = new StringWriter();
-        writer = new XmlWriter(sw);
-        report = new ConfigurationReport(writer, reportConfiguration);
+    private String executeReport() throws Exception {
+        StringWriter out = new StringWriter();
+        try (XmlWriter writer = new XmlWriter(out)) {
+            writer.startDocument();
+            RatReport report = XmlReportFactory.configuration(writer, 
reportConfiguration);
+            report.startReport();
+            report.endReport();
+        }
+        return out.toString();
     }
 
     @Test
     public void testAllFamiliesAndMatchers() throws Exception {
-        report.startReport();
-        report.endReport();
-        writer.closeDocument();
-        String result = sw.toString();
+        String result = executeReport();
         assertTrue(XmlUtils.isWellFormedXml(result), "Is well formed");
 
         XPath xPath = XPathFactory.newInstance().newXPath();
diff --git 
a/apache-rat-core/src/test/java/org/apache/rat/report/xml/XmlReportFactoryTest.java
 
b/apache-rat-core/src/test/java/org/apache/rat/report/xml/XmlReportFactoryTest.java
index 3de15b01..fb041648 100644
--- 
a/apache-rat-core/src/test/java/org/apache/rat/report/xml/XmlReportFactoryTest.java
+++ 
b/apache-rat-core/src/test/java/org/apache/rat/report/xml/XmlReportFactoryTest.java
@@ -38,7 +38,6 @@ import org.apache.rat.license.ILicense;
 import org.apache.rat.license.ILicenseFamily;
 import org.apache.rat.report.RatReport;
 import org.apache.rat.report.claim.ClaimStatistic;
-import org.apache.rat.report.xml.writer.IXmlWriter;
 import org.apache.rat.report.xml.writer.XmlWriter;
 import org.apache.rat.test.utils.Resources;
 import org.apache.rat.testhelpers.TestingLicense;
@@ -54,7 +53,7 @@ public class XmlReportFactoryTest {
             .setLicenseFamilyName("Testing family").build();
 
     private StringWriter out;
-    private IXmlWriter writer;
+    private XmlWriter writer;
 
     @BeforeEach
     public void setUp() throws Exception {
diff --git 
a/apache-rat-core/src/test/java/org/apache/rat/report/xml/writer/XmlWriterUtilsTest.java
 
b/apache-rat-core/src/test/java/org/apache/rat/report/xml/writer/XmlWriterUtilsTest.java
index f0c8c921..27eab5fb 100644
--- 
a/apache-rat-core/src/test/java/org/apache/rat/report/xml/writer/XmlWriterUtilsTest.java
+++ 
b/apache-rat-core/src/test/java/org/apache/rat/report/xml/writer/XmlWriterUtilsTest.java
@@ -19,7 +19,6 @@
 package org.apache.rat.report.xml.writer;
 
 import org.apache.rat.testhelpers.XmlUtils;
-import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
 import java.io.StringWriter;
@@ -28,25 +27,23 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
 
 public class XmlWriterUtilsTest {
 
-    private StringWriter out;
-    private IXmlWriter writer;
-
-    @BeforeEach
-    public void setUp() throws Exception {
-        out = new StringWriter();
-        writer = new XmlWriter(out);
-        writer.openElement("alpha");
-    }
-
     @Test
     public void writeTrue() throws Exception {
-        XmlUtils.writeAttribute(writer, "name", true);
-        assertEquals("<alpha name='true'", out.toString(), "Attribute written 
as True");
+        StringWriter out = new StringWriter();
+        try (XmlWriter writer = new XmlWriter(out)) {
+            writer.startElement("alpha");
+            XmlUtils.writeAttribute(writer, "name", true);
+            assertEquals("<alpha name='true'", out.toString(), "Attribute 
written as True");
+        }
     }
 
     @Test
     public void writeFalse() throws Exception {
-        XmlUtils.writeAttribute(writer, "name", false);
-        assertEquals("<alpha name='false'", out.toString(), "Attribute written 
as False");
+        StringWriter out = new StringWriter();
+        try (XmlWriter writer = new XmlWriter(out)) {
+            writer.startElement("alpha");
+            XmlUtils.writeAttribute(writer, "name", false);
+            assertEquals("<alpha name='false'", out.toString(), "Attribute 
written as False");
+        }
     }
 }
diff --git 
a/apache-rat-core/src/test/java/org/apache/rat/report/xml/writer/impl/base/XmlWriterTest.java
 
b/apache-rat-core/src/test/java/org/apache/rat/report/xml/writer/impl/base/XmlWriterTest.java
index 4f5ea5a0..287db767 100644
--- 
a/apache-rat-core/src/test/java/org/apache/rat/report/xml/writer/impl/base/XmlWriterTest.java
+++ 
b/apache-rat-core/src/test/java/org/apache/rat/report/xml/writer/impl/base/XmlWriterTest.java
@@ -7,7 +7,7 @@
  * "License"); you may not use this file except in compliance   *
  * with the License.  You may obtain a copy of the License at   *
  *                                                              *
- *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *   https://www.apache.org/licenses/LICENSE-2.0                 *
  *                                                              *
  * Unless required by applicable law or agreed to in writing,   *
  * software distributed under the License is distributed on an  *
@@ -18,12 +18,10 @@
  */ 
 package org.apache.rat.report.xml.writer.impl.base;
 
-import org.apache.rat.report.xml.writer.IXmlWriter;
 import org.apache.rat.report.xml.writer.InvalidXmlException;
 import org.apache.rat.report.xml.writer.OperationNotAllowedException;
 import org.apache.rat.report.xml.writer.XmlWriter;
 import org.apache.rat.testhelpers.XmlUtils;
-import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.w3c.dom.Document;
 
@@ -33,73 +31,61 @@ import java.io.StringWriter;
 import java.nio.charset.StandardCharsets;
 import java.util.NoSuchElementException;
 
-import static org.assertj.core.api.Assertions.fail;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatNoException;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
 class XmlWriterTest {
 
-    private XmlWriter writer;
-    private StringWriter out;
-    
-    @BeforeEach
-    public void setUp() {
-        out = new StringWriter();
-        writer = new XmlWriter(out);
-    }
-
     @Test
     void returnValues() throws Exception {
-        assertEquals( 
-                writer, writer.openElement("alpha"), "XmlWriters should always 
return themselves");
-        assertEquals(
-                writer, writer.attribute("beta", "b"), "XmlWriters should 
always return themselves");
-        assertEquals(
-                writer, writer.content("gamma"), "XmlWriters should always 
return themselves");
-        assertEquals(
-                writer, writer.closeElement(), "XmlWriters should always 
return themselves");
+        StringWriter out = new StringWriter();
+        try (XmlWriter writer = new XmlWriter(out)) {
+        assertThat(writer.startElement("alpha")).as("XmlWriters should always 
return themselves").isEqualTo(writer);
+            assertThat(writer.attribute("beta", "b")).as("XmlWriters should 
always return themselves").isEqualTo(writer);
+            assertThat(writer.content("gamma")).as("XmlWriters should always 
return themselves").isEqualTo(writer);
+            assertThat(writer.closeElement()).as("XmlWriters should always 
return themselves").isEqualTo(writer);
+        }
     }
 
     @Test
-    void openElement() throws Exception {
-        assertEquals(
-                writer, writer.openElement("alpha"), "XmlWriters should always 
return themselves");
-         assertEquals("<alpha", out.toString(), "Alpha element started");
-        assertEquals(
-                writer, writer.openElement("beta"), "XmlWriters should always 
return themselves");
-        assertEquals("<alpha><beta", out.toString(), "Alpha element tag closed 
and beta started");
-        assertEquals(
-                writer, writer.closeElement(), "XmlWriters should always 
return themselves");
-        assertEquals("<alpha><beta/>", out.toString(), "Beta tag ended");
-        assertEquals(
-                writer, writer.openElement("gamma"), "XmlWriters should always 
return themselves");
-        assertEquals("<alpha><beta/><gamma", out.toString(), "Gamma tag 
started");
+    void startElement() throws Exception {
+        StringWriter out = new StringWriter();
+        try (XmlWriter writer = new XmlWriter(out)) {
+            writer.startElement("alpha");
+            assertThat(out).hasToString("<alpha");
+            writer.startElement("beta");
+            assertThat(out).hasToString("<alpha><beta");
+            writer.closeElement();
+            assertThat(out).hasToString("<alpha><beta/>");
+            writer.startElement("gamma");
+            assertThat(out).hasToString("<alpha><beta/><gamma");
+        }
     }
     
     @Test
     void invalidElementName() throws Exception {
-        assertTrue( isValidElementName("alpha"), "All strings ok");
-        assertTrue(isValidElementName("alpha77"), "Strings and digits ok");
-        assertFalse(isValidElementName("5alpha77"), "Must no start with 
digit");
-        assertFalse(isValidElementName("alph<a77"), "Greater than not ok");
-        assertFalse(isValidElementName("alph<a77"), "Less than not ok");
-        assertFalse(isValidElementName("alph'a77"), "Quote not ok");
-        assertTrue(isValidElementName("alph-a77"), "Dash ok");
-        assertTrue(isValidElementName("alph_a77"), "Underscore ok");
-        assertTrue(isValidElementName("alph.a77"), "Dot ok");
-        assertTrue(isValidElementName("alpha:77"), "Colon ok");
-        assertFalse(isValidElementName("-a77"), "Start with dash not ok");
-        assertTrue(isValidElementName("_a77"), "Start with underscore ok");
-        assertFalse(isValidElementName(".a77"), "Start with dot not ok");
-        assertTrue(isValidElementName(":a77"), "Start with colon ok");
+        assertThat(isValidElementName("alpha")).as("All strings ok").isTrue();
+        assertThat(isValidElementName("alpha77")).as("Strings and digits 
ok").isTrue();
+        assertThat(isValidElementName("5alpha77")).as("Must no start with 
digit").isFalse();
+        assertThat(isValidElementName("alph<a77")).as("Greater than not 
ok").isFalse();
+        assertThat(isValidElementName("alph<a77")).as("Less than not 
ok").isFalse();
+        assertThat(isValidElementName("alph'a77")).as("Quote not 
ok").isFalse();
+        assertThat(isValidElementName("alph-a77")).as("Dash ok").isTrue();
+        assertThat(isValidElementName("alph_a77")).as("Underscore 
ok").isTrue();
+        assertThat(isValidElementName("alph.a77")).as("Dot ok").isTrue();
+        assertThat(isValidElementName("alpha:77")).as("Colon ok").isTrue();
+        assertThat(isValidElementName("-a77")).as("Start with dash not 
ok").isFalse();
+        assertThat(isValidElementName("_a77")).as("Start with underscore 
ok").isTrue();
+        assertThat(isValidElementName(".a77")).as("Start with dot not 
ok").isFalse();
+        assertThat(isValidElementName(":a77")).as("Start with colon 
ok").isTrue();
     }
     
     private boolean isValidElementName(String elementName) throws Exception {
         boolean result = true;
-        try {
-            writer.openElement(elementName);
+        StringWriter out = new StringWriter();
+        try (XmlWriter writer = new XmlWriter(out)) {
+            writer.startElement(elementName);
         } catch (InvalidXmlException e) {
             result = false;
         }
@@ -107,191 +93,177 @@ class XmlWriterTest {
     }
 
     @Test
-    void callOpenElementAfterLastElementClosed() throws Exception {
-        assertEquals(
-                writer, writer.openElement("alpha"), "XmlWriters should always 
return themselves");
-         assertEquals("<alpha", out.toString(), "Alpha element started");
-        assertEquals(
-                writer, writer.closeElement(), "XmlWriters should always 
return themselves");
-        assertEquals("<alpha/>", out.toString(), "Element alpha is closed");
-        try {
-            writer.openElement("delta");
-            fail("Cannot open new elements once the first element has been 
closed");
-        } catch (OperationNotAllowedException e) {
-            // Cannot open new elements once the first element has been closed
+    void callStartElementAfterLastElementClosed() throws Exception {
+        StringWriter out = new StringWriter();
+        try (XmlWriter writer = new XmlWriter(out)) {
+            writer.startElement("alpha");
+            assertThat(out).hasToString("<alpha");
+            writer.closeElement();
+            assertThat(out).hasToString("<alpha/>");
+            assertThatThrownBy(() -> writer.startElement("delta"))
+                    .hasMessageContaining("Root element already closed. Cannot 
open new element.")
+                            .isInstanceOf(OperationNotAllowedException.class);
         }
     }    
 
     @Test
     void callCloseElementAfterLastElementClosed() throws Exception {
-        assertEquals(
-                writer, writer.openElement("alpha"), "XmlWriters should always 
return themselves");
-         assertEquals("<alpha", out.toString(), "Alpha element started");
-        assertEquals(
-                writer, writer.closeElement(), "XmlWriters should always 
return themselves");
-        assertEquals("<alpha/>", out.toString(), "Element alpha is closed");
-        try {
+        StringWriter out = new StringWriter();
+        try (XmlWriter writer = new XmlWriter(out)) {
+            writer.startElement("alpha");
+            assertThat(out).hasToString("<alpha");
             writer.closeElement();
-            fail("Cannot close elements once the first element has been 
closed");
-        } catch (OperationNotAllowedException e) {
-            // Cannot open new elements once the first element has been closed
+            assertThat(out).hasToString("<alpha/>");
+            assertThatThrownBy(writer::closeElement)
+                    .hasMessageContaining("Root element already closed. Cannot 
open new element.")
+                    .isInstanceOf(OperationNotAllowedException.class);
         }
     }
 
     @Test
     void closeFirstElement() throws Exception {
-        assertEquals(
-                writer, writer.openElement("alpha"), "XmlWriters should always 
return themselves");
-        assertEquals("<alpha", out.toString(), "Alpha element started");
-        assertEquals(
-                writer, writer.closeElement(), "XmlWriters should always 
return themselves");
-        assertEquals("<alpha/>", out.toString(), "Element alpha is closed");
+        StringWriter out = new StringWriter();
+        try (XmlWriter writer = new XmlWriter(out)) {
+            writer.startElement("alpha");
+            assertThat(out).hasToString("<alpha");
+            writer.closeElement();
+            assertThat(out).hasToString("<alpha/>");
+        }
     }
     
     @Test
     void closeElementWithContent() throws Exception {
-        assertEquals(
-                writer, writer.openElement("alpha"), "XmlWriters should always 
return themselves");
-        assertEquals("<alpha", out.toString(), "Alpha element started");
-        assertEquals(
-                writer, writer.openElement("beta"), "XmlWriters should always 
return themselves");
-        assertEquals("<alpha><beta", out.toString(), "Beta element started");
-        assertEquals(
-                writer, writer.closeElement(), "XmlWriters should always 
return themselves");
-        assertEquals("<alpha><beta/>", out.toString(), "Element beta is 
closed");
-        assertEquals(
-                writer, writer.closeElement(), "XmlWriters should always 
return themselves");
-        assertEquals("<alpha><beta/></alpha>", out.toString(), "Element beta 
is closed");
-        try {
+        StringWriter out = new StringWriter();
+        try (XmlWriter writer = new XmlWriter(out)) {
+            writer.startElement("alpha").startElement("beta");
+            assertThat(out).hasToString("<alpha><beta");
+
+                   writer.closeElement();
+            assertThat(out).hasToString("<alpha><beta/>");
             writer.closeElement();
-            fail("Cannot close elements once the first element has been 
closed");
-        } catch (OperationNotAllowedException e) {
-            // Cannot open new elements once the first element has been closed
+            assertThat(out).hasToString("<alpha><beta/></alpha>");
+            assertThatThrownBy(writer::closeElement)
+                    .hasMessageContaining("Root element already closed. Cannot 
open new element.")
+                    .isInstanceOf(OperationNotAllowedException.class);
         }
     }
     
     @Test
     void closeElementBeforeFirstElement() throws Exception {
-        try {
-            writer.closeElement();
-            fail("Cannot close elements before the first element has been 
closed");
-        } catch (OperationNotAllowedException e) {
-            // Cannot open new elements before the first element has been 
closed
+        StringWriter out = new StringWriter();
+        try (XmlWriter writer = new XmlWriter(out)) {
+            assertThatThrownBy(writer::closeElement)
+                    .hasMessageContaining("Close called before an element has 
been opened.")
+                    .isInstanceOf(OperationNotAllowedException.class);
+        } catch (OperationNotAllowedException expected) {
+            assertThat(expected).hasMessageContaining("Close called before an 
element has been opened.");
         }
+
     }
     
     @Test
     void contentAfterElement() throws Exception {
-        assertEquals(
-                writer, writer.openElement("alpha"), "XmlWriters should always 
return themselves");
-         assertEquals("<alpha", out.toString(), "Alpha element started");
-        assertEquals(
-                writer, writer.content("foo bar"), "XmlWriters should always 
return themselves");
-        assertEquals("<alpha>foo bar", out.toString(), "Alpha tag closed. 
Content written");
-        assertEquals(
-                writer, writer.content(" and more foo bar"), "XmlWriters 
should always return themselves");
-        assertEquals("<alpha>foo bar and more foo bar", out.toString(), "Alpha 
tag closed. Content written");
-        assertEquals(
-                writer, writer.openElement("beta"), "XmlWriters should always 
return themselves");
-        assertEquals("<alpha>foo bar and more foo bar<beta", out.toString());
-        assertEquals(
-                writer, writer.closeElement(), "XmlWriters should always 
return themselves");
-        assertEquals("<alpha>foo bar and more foo bar<beta/>", out.toString(), 
"Element beta is closed");
-        assertEquals(
-                writer, writer.closeElement(), "XmlWriters should always 
return themselves");
-        assertEquals("<alpha>foo bar and more foo bar<beta/></alpha>", 
out.toString(), "Element beta is closed");
-        try {
-            writer.content("A Sentence Too far");
-            fail("Cannot write content once the first element has been 
closed");
-        } catch (OperationNotAllowedException e) {
-            // Cannot open new elements once the first element has been closed
+        StringWriter out = new StringWriter();
+        try (XmlWriter writer = new XmlWriter(out)) {
+            writer.startElement("alpha");
+            assertThat(out).hasToString("<alpha");
+            writer.content("foo bar");
+            assertThat(out).hasToString("<alpha>foo bar");
+            writer.content(" and more foo bar");
+            assertThat(out).hasToString("<alpha>foo bar and more foo bar");
+            writer.startElement("beta");
+            assertThat(out).hasToString("<alpha>foo bar and more foo 
bar<beta");
+            writer.closeElement();
+            assertThat(out).hasToString("<alpha>foo bar and more foo 
bar<beta/>");
+            writer.closeElement();
+            assertThat(out).hasToString("<alpha>foo bar and more foo 
bar<beta/></alpha>");
+            assertThatThrownBy(() -> writer.content("A Sentence Too far"))
+                    .hasMessageContaining("Root element already closed. Cannot 
open new element.")
+                            .isInstanceOf(OperationNotAllowedException.class);
         }
     }
 
     @Test
     void contentAfterLastElement() throws Exception {
-        assertEquals(
-                writer, writer.openElement("alpha"), "XmlWriters should always 
return themselves");
-         assertEquals("<alpha", out.toString(), "Alpha element started");
-        assertEquals(
-                writer, writer.closeElement(), "XmlWriters should always 
return themselves");
-        assertEquals("<alpha/>", out.toString(), "Element alpha is closed");
-        try {
-            writer.content("A Sentence Too far");
-            fail("Cannot write content once the first element has been 
closed");
-        } catch (OperationNotAllowedException e) {
-            // Cannot open new elements once the first element has been closed
+        StringWriter out = new StringWriter();
+        try (XmlWriter writer = new XmlWriter(out)) {
+            writer.startElement("alpha");
+            assertThat(out).hasToString("<alpha");
+            writer.closeElement();
+            assertThat(out).hasToString("<alpha/>");
+            assertThatThrownBy(() -> writer.content("A Sentence Too far"))
+                    .hasMessageContaining("Root element already closed. Cannot 
open new element.")
+                            .isInstanceOf(OperationNotAllowedException.class);
         }
     }
     
     @Test
     void writeContentBeforeFirstElement() throws Exception {
-        try {
-            writer.content("Too early");
-            fail("Cannot close elements before the first element has been 
closed");
-        } catch (OperationNotAllowedException e) {
-            // Cannot open new elements before the first element has been 
closed
+        StringWriter out = new StringWriter();
+        try (XmlWriter writer = new XmlWriter(out)) {
+            assertThatThrownBy(() -> writer.content("Too early"))
+                    .hasMessageContaining("An element must be opened before 
content can be written.")
+                            .isInstanceOf(OperationNotAllowedException.class);
+        } catch (OperationNotAllowedException expected) {
+            assertThat(expected).hasMessageContaining("Close called before an 
element has been opened.");
         }
     }
     
     @Test
     void contentEscaping() throws Exception {
-        assertEquals(
-                writer, writer.openElement("alpha"), "XmlWriters should always 
return themselves");
-         assertEquals("<alpha", out.toString(), "Alpha element started");
-        assertEquals(
-                writer, writer.content("this&that"), "XmlWriters should always 
return themselves");
-        assertEquals("<alpha>this&amp;that", out.toString(), "Amphersands must 
be escaped");
-        assertEquals(
-                writer, writer.content("small<large"), "XmlWriters should 
always return themselves");
-        assertEquals("<alpha>this&amp;thatsmall&lt;large", out.toString(), 
"Left angle brackets must be escaped");
-        assertEquals(
-                writer, writer.content("12>1"), "XmlWriters should always 
return themselves");
-        assertEquals("<alpha>this&amp;thatsmall&lt;large12&gt;1", 
out.toString(), "Choose to escape right angle brackets");
-
+        StringWriter out = new StringWriter();
+        try (XmlWriter writer = new XmlWriter(out)) {
+            writer.startElement("alpha");
+            assertThat(out).hasToString("<alpha");
+            writer.content("this&that");
+            assertThat(out).hasToString("<alpha>this&amp;that");
+            writer.content("small<large");
+            assertThat(out).hasToString("<alpha>this&amp;thatsmall&lt;large");
+            writer.content("12>1");
+            
assertThat(out).hasToString("<alpha>this&amp;thatsmall&lt;large12&gt;1");
+        }
     }
 
     @Test
     void attributeAfterLastElement() throws Exception {
-        assertEquals(
-                writer, writer.openElement("alpha"), "XmlWriters should always 
return themselves");
-         assertEquals("<alpha", out.toString(), "Alpha element started");
-        assertEquals(
-                writer, writer.closeElement(), "XmlWriters should always 
return themselves");
-        assertEquals("<alpha/>", out.toString(), "Element alpha is closed");
-        try {
-            writer.attribute("foo", "bar");
-            fail("Cannot write content once the first element has been 
closed");
-        } catch (OperationNotAllowedException e) {
-            // Cannot open new elements once the first element has been closed
+        StringWriter out = new StringWriter();
+        try (XmlWriter writer = new XmlWriter(out)) {
+            writer.startElement("alpha").closeElement();
+            assertThat(out).hasToString("<alpha/>");
+            assertThatThrownBy(() -> writer.attribute("foo", "bar"))
+                    .hasMessageContaining("Root element already closed. Cannot 
open new element.")
+                            .isInstanceOf(OperationNotAllowedException.class);
         }
     }
     
     @Test
     void attributeContentBeforeFirstElement() throws Exception {
-        try {
-            writer.attribute("foo", "bar");
-            fail("Cannot close elements before the first element has been 
closed");
-        } catch (OperationNotAllowedException e) {
-            // Cannot open new elements before the first element has been 
closed
+        StringWriter out = new StringWriter();
+        try (XmlWriter writer = new XmlWriter(out)) {
+            assertThatThrownBy(() -> writer.attribute("foo", "bar"))
+                    .hasMessageContaining("Close called before an element has 
been opened.")
+                    .isInstanceOf(OperationNotAllowedException.class);
+        } catch (OperationNotAllowedException expected) {
+            assertThat(expected).hasMessageContaining("Close called before an 
element has been opened.");
         }
     }
     
     @Test
     void invalidAttributeName() throws Exception {
-        writer.openElement("alpha");
-        assertTrue(isValidAttributeName("alpha"), "All string ok");
-        assertTrue(isValidAttributeName("alpha77"), "Strings and digits ok");
-        assertFalse(isValidAttributeName("5alpha77"), "Must not start with 
digit");
-        assertTrue(isValidAttributeName("alpha:77"), "Colon ok");
-        assertFalse(isValidAttributeName("alph<a77"),"Greater than not ok");
-        assertFalse(isValidAttributeName("alph<a77"), "Less than not ok");
-        assertFalse(isValidAttributeName("alph'a77"), "Quote not ok");
+        assertThat(isValidAttributeName("alpha")).as("All string ok").isTrue();
+        assertThat(isValidAttributeName("alpha77")).as("Strings and digits 
ok").isTrue();
+        assertThat(isValidAttributeName("5alpha77")).as("Must not start with 
digit").isFalse();
+        assertThat(isValidAttributeName("alpha:77")).as("Colon ok").isTrue();
+        assertThat(isValidAttributeName("alph<a77")).as("Greater than not 
ok").isFalse();
+        assertThat(isValidAttributeName("alph<a77")).as("Less than not 
ok").isFalse();
+        assertThat(isValidAttributeName("alph'a77")).as("Quote not 
ok").isFalse();
     }
     
     private boolean isValidAttributeName(String name) throws Exception {
         boolean result = true;
-        try {
+        StringWriter out = new StringWriter();
+        try (XmlWriter writer = new XmlWriter(out)) {
+            writer.startElement("alpha");
             writer.attribute(name, "");
         } catch (InvalidXmlException e) {
             result = false;
@@ -301,235 +273,199 @@ class XmlWriterTest {
     
     @Test
     void escapeAttributeContent() throws Exception {
-        assertEquals(
-                writer, writer.openElement("alpha"), "XmlWriters should always 
return themselves");
-         assertEquals("<alpha", out.toString(), "Alpha element started");
-        assertEquals(
-                writer, writer.attribute("one", "this&that"), "XmlWriters 
should always return themselves");
-        assertEquals("<alpha one='this&amp;that'", out.toString(), 
"Amphersands must be escaped");
-        assertEquals(
-                writer, writer.attribute("two", "small<large"), "XmlWriters 
should always return themselves");
-        assertEquals("<alpha one='this&amp;that' two='small&lt;large'", 
out.toString(), "Left angle brackets must be escaped");
-        assertEquals(
-                writer, writer.attribute("three", "12>1"), "XmlWriters should 
always return themselves");
-        assertEquals("<alpha one='this&amp;that' two='small&lt;large' 
three='12&gt;1'", out.toString(), "Choose to escape right angle brackets");
-        assertEquals(
-                writer, writer.attribute("four", "'quote'"), "XmlWriters 
should always return themselves");
-        assertEquals("<alpha one='this&amp;that' two='small&lt;large' 
three='12&gt;1' four='&apos;quote&apos;'", out.toString(), "Apostrophes must be 
escape");
-        assertEquals(
-                writer, writer.attribute("five", "\"quote\""), "XmlWriters 
should always return themselves");
-        assertEquals("<alpha one='this&amp;that' two='small&lt;large' 
three='12&gt;1' four='&apos;quote&apos;' five='&quot;quote&quot;'", 
out.toString(), "Double quotes must be escape");
-
+        StringWriter out = new StringWriter();
+        try (XmlWriter writer = new XmlWriter(out)) {
+            writer.startElement("alpha").attribute("one", 
"this&that").attribute("two", "small<large")
+                    .attribute("three", "12>1").attribute("four", 
"'quote'").attribute("five", "\"quote\"");
+            assertThat(out).hasToString("<alpha one='this&amp;that' 
two='small&lt;large' three='12&gt;1' four='&apos;quote&apos;' 
five='&quot;quote&quot;'");
+        }
     }
     
     @Test
     void attributeInContent() throws Exception {
-        assertEquals(
-                writer, writer.openElement("alpha"), "XmlWriters should always 
return themselves");
-         assertEquals("<alpha", out.toString(), "Alpha element started");
-        assertEquals(
-                writer, writer.content("foo bar"), "XmlWriters should always 
return themselves");
-        try {
-            writer.attribute("name", "value");
-            fail("attributes after body content are not allowed");
-        } catch (InvalidXmlException e) {
-            // attributes after body content are not allowed
+        StringWriter out = new StringWriter();
+        try (XmlWriter writer = new XmlWriter(out)) {
+            writer.startElement("alpha").content("foo bar");
+            assertThatThrownBy(() -> writer.attribute("name", "value"))
+                    .hasMessageContaining("Attributes can only be written in 
elements")
+                            .isInstanceOf(InvalidXmlException.class);
         }
     }
   
     @Test
     void outOfRangeCharacter() throws Exception {
-        assertEquals(
-                writer, writer.openElement("alpha"), "XmlWriters should always 
return themselves");
-         assertEquals("<alpha", out.toString(), "Alpha element started");
-         CharSequence cs = new CharSequence() {
-             @Override
-             public int length() {
-                 return 1;
-             }
+        StringWriter out = new StringWriter();
+        try (XmlWriter writer = new XmlWriter(out)) {
+            CharSequence cs = new CharSequence() {
+                @Override
+                public int length() {
+                    return 1;
+                }
 
-             @Override
-             public char charAt(int index) {
-                 return Character.highSurrogate(0x110000);
-             }
+                @Override
+                public char charAt(int index) {
+                    return Character.highSurrogate(0x110000);
+                }
 
-             @Override
-             public CharSequence subSequence(int start, int end) {
-                 return null;
-             }
-         };
-
-        assertEquals(writer, writer.content(cs), "XmlWriters should always 
return themselves");
-        assertEquals("<alpha>\\uDC00", this.out.toString(), "Replace illegal 
characters with \\u encoding");
+                @Override
+                public CharSequence subSequence(int start, int end) {
+                    return null;
+                }
+            };
+            writer.startElement("alpha").content(cs);
+            assertThat(out).hasToString("<alpha>\\uDC00");
+        }
     }
     
     @Test
     void attributeAfterElementClosed() throws Exception {
-        assertEquals(
-                writer, writer.openElement("alpha"), "XmlWriters should always 
return themselves");
-         assertEquals("<alpha", out.toString(), "Alpha element started");
-        assertEquals(
-                writer, writer.openElement("beta"), "XmlWriters should always 
return themselves");
-        assertEquals("<alpha><beta", out.toString(), "Beta element started");
-        assertEquals(
-                writer, writer.closeElement(), "XmlWriters should always 
return themselves");
-        assertEquals("<alpha><beta/>", out.toString(), "Beta element closed");
-        try {
-            writer.attribute("name", "value");
-            fail("attributes after closed element are not allowed");
-        } catch (InvalidXmlException e) {
-            // attributes after body content are not allowed
+        StringWriter out = new StringWriter();
+        try (XmlWriter writer = new XmlWriter(out)) {
+            writer.startElement("alpha").startElement("beta").closeElement();
+            assertThat(out).hasToString("<alpha><beta/>");
+            assertThatThrownBy(() -> writer.attribute("name", "value"))
+                    .hasMessageContaining("Attributes can only be written in 
elements")
+                            .isInstanceOf(InvalidXmlException.class);
         }
     }
     
     @Test
     void closeDocumentBeforeOpen() throws Exception {
-        try {
-            writer.closeDocument();
-            fail("Cannot close document before the first element has been 
opened");
-        } catch (OperationNotAllowedException e) {
-            // Cannot open new elements before the first element has been 
opened
+        StringWriter out = new StringWriter();
+        try (XmlWriter writer = new XmlWriter(out)) {
+            assertThatThrownBy(writer::closeDocument)
+                    .hasMessageContaining("Close called before an element has 
been opened.")
+                    .isInstanceOf(OperationNotAllowedException.class);
+        } catch (OperationNotAllowedException expected) {
+            assertThat(expected).hasMessageContaining("Close called before an 
element has been opened.");
         }
+
     }
     
     @Test
     void closeDocumentAfterRootElementClosed() throws Exception {
-        assertEquals(
-                writer, writer.openElement("alpha"), "XmlWriters should always 
return themselves");
-         assertEquals("<alpha", out.toString(), "Alpha element started");
-        assertEquals(
-                writer, writer.closeElement(), "XmlWriters should always 
return themselves");
-        assertEquals("<alpha/>", out.toString());
-        try {
-            writer.closeDocument();
-        } catch (OperationNotAllowedException e) {
-            fail("No exception should be thrown when called after the root 
element is closed.");
-        }
-    }   
+        StringWriter out = new StringWriter();
+        try (XmlWriter writer = new XmlWriter(out)) {
+            writer.startElement("alpha").closeElement();
+            assertThatNoException().isThrownBy(writer::closeDocument);
+        }
+    }
     
     @Test
     void closeSimpleDocument() throws Exception {
-        assertEquals(
-                writer, writer.openElement("alpha"), "XmlWriters should always 
return themselves");
-         assertEquals("<alpha", out.toString(), "Alpha element started");
-        assertEquals(
-                writer, writer.openElement("beta"), "XmlWriters should always 
return themselves");
-        assertEquals("<alpha><beta", out.toString(), "Beta element started");
-        assertEquals(
-                writer, writer.closeDocument(), "XmlWriters should always 
return themselves");
-        assertEquals("<alpha><beta/></alpha>", out.toString(), "Beta element 
started");
+        StringWriter out = new StringWriter();
+        try (XmlWriter writer = new XmlWriter(out)) {
+            writer.startElement("alpha").startElement("beta").closeDocument();
+            assertThat(out).hasToString("<alpha><beta/></alpha>");
+        }
     }
     
     @Test
     void closeComplexDocument() throws Exception {
-        assertEquals(
-                writer, writer.openElement("alpha"), "XmlWriters should always 
return themselves");
-         assertEquals("<alpha", out.toString(), "Alpha element started");
-        assertEquals(
-                writer, writer.openElement("beta"), "XmlWriters should always 
return themselves");
-        assertEquals("<alpha><beta", out.toString(), "Beta element started");
-        assertEquals(
-                writer, writer.attribute("name", "value"), "XmlWriters should 
always return themselves");
-        assertEquals("<alpha><beta name='value'", out.toString(), "Beta 
element started");
-        assertEquals(
-                writer, writer.closeElement(), "XmlWriters should always 
return themselves");
-        assertEquals("<alpha><beta name='value'/>", out.toString(), "Beta 
element started");
-        assertEquals(
-                writer, writer.openElement("beta"), "XmlWriters should always 
return themselves");
-        assertEquals("<alpha><beta name='value'/><beta", out.toString(), "Beta 
element started");
-        assertEquals(
-                writer, writer.attribute("name", "value"), "XmlWriters should 
always return themselves");
-        assertEquals("<alpha><beta name='value'/><beta name='value'", 
out.toString(), "Beta element started");
-        assertEquals(
-                writer, writer.openElement("gamma"), "XmlWriters should always 
return themselves");
-        assertEquals("<alpha><beta name='value'/><beta name='value'><gamma", 
out.toString(), "Beta element started");
-        assertEquals(
-                writer, writer.closeDocument(), "XmlWriters should always 
return themselves");
-        assertEquals("<alpha><beta name='value'/><beta 
name='value'><gamma/></beta></alpha>", out.toString(), "Beta element started");
+        StringWriter out = new StringWriter();
+        try (XmlWriter writer = new XmlWriter(out)) {
+            
writer.startElement("alpha").startElement("beta").attribute("name", 
"value").closeElement()
+                    .startElement("beta")
+            .attribute("name", "value")
+                    .startElement("gamma").closeDocument();
+            assertThat(out).hasToString("<alpha><beta name='value'/><beta 
name='value'><gamma/></beta></alpha>");
+        }
     }
     
     @Test
     void writeProlog() throws Exception {
-        assertEquals(
-                writer, writer.startDocument(), "XmlWriters should always 
return themselves");
-        assertEquals("<?xml version='1.0'?>", out.toString(), "Prolog 
written");
+        StringWriter out = new StringWriter();
+        try (XmlWriter writer = new XmlWriter(out)) {
+            writer.startDocument();
+            assertThat(out).hasToString("<?xml version='1.0'?>");
+        } catch (OperationNotAllowedException expected) {
+            assertThat(expected).hasMessageContaining("Close called before an 
element has been opened.");
+        }
     }
     
     @Test
     void writeAfterElement() throws Exception {
-        assertEquals(
-                writer, writer.openElement("alpha"), "XmlWriters should always 
return themselves");
-         assertEquals("<alpha", out.toString(), "Alpha element started");
-        try {
-            writer.startDocument();
-            fail("Operation not allowed once an element has been written");
-        } catch (OperationNotAllowedException e) {
-            // Operation not allowed once an element has been written
+        StringWriter out = new StringWriter();
+        try (XmlWriter writer = new XmlWriter(out)) {
+            writer.startElement("alpha");
+            
assertThatThrownBy(writer::startDocument).hasMessageContaining("Document 
already started")
+                    .isInstanceOf(OperationNotAllowedException.class);
         }
     }
     
     @Test
-    void writePrologTwo() throws Exception {
-        assertEquals(
-                writer, writer.startDocument(), "XmlWriters should always 
return themselves");
-        assertEquals("<?xml version='1.0'?>", out.toString(), "Prolog 
written");
-        try {
+    void writePrologTwice() throws Exception {
+        StringWriter out = new StringWriter();
+        try (XmlWriter writer = new XmlWriter(out)) {
             writer.startDocument();
-            fail("Operation not allow once a prolog has been written");
-        } catch (OperationNotAllowedException e) {
-            // Operation not allowed once a prolog has been written
+            assertThat(out).hasToString("<?xml version='1.0'?>");
+            
assertThatThrownBy(writer::startDocument).hasMessageContaining("Only one prolog 
allowed")
+                    .isInstanceOf(OperationNotAllowedException.class);
+        } catch (OperationNotAllowedException expected) {
+            assertThat(expected).hasMessageContaining("Close called before an 
element has been opened.");
         }
     }
     
     @Test
     void duplicateAttributes() throws Exception {
-        assertEquals(
-                writer, writer.openElement("alpha"), "XmlWriters should always 
return themselves");
-         assertEquals("<alpha", out.toString(), "Alpha element started");
-        assertEquals(
-                writer, writer.attribute("one", "1"), "XmlWriters should 
always return themselves");
-        assertEquals("<alpha one='1'", out.toString(), "Attribute written");
-        assertEquals(
-                writer, writer.openElement("beta"), "XmlWriters should always 
return themselves");
-        assertEquals("<alpha one='1'><beta", out.toString(), "Beta element 
started");
-        assertEquals(
-                writer, writer.attribute("one", "1"), "XmlWriters should 
always return themselves");
-        assertEquals("<alpha one='1'><beta one='1'", out.toString(), "Beta 
element started");
-        try {
-            writer.attribute("one", "2");
-            fail("Each attribute may only be written once");
-        } catch (InvalidXmlException e) {
-            // Each attribute may only be written once
+        StringWriter out = new StringWriter();
+        try (XmlWriter writer = new XmlWriter(out)) {
+            writer.startElement("alpha").attribute("one", 
"1").startElement("beta").attribute("one", "1");
+            assertThat(out).hasToString("<alpha one='1'><beta one='1'");
+            assertThatThrownBy(() -> writer.attribute("one", "2"))
+                    .hasMessageContaining("Each attribute can only be written 
once")
+                    .isInstanceOf(InvalidXmlException.class);
         }
     }
 
     @Test
-    void writeCDataBeforeElement() {
-        assertThrows(OperationNotAllowedException.class, () -> 
writer.startDocument().cdata("Just cdata").closeDocument());
+    void writeCDataBeforeElement() throws IOException {
+        StringWriter out = new StringWriter();
+        try (XmlWriter writer = new XmlWriter(out)) {
+            writer.startDocument();
+            assertThatThrownBy(() -> writer.cdata("Just cdata"))
+                    .hasMessageContaining("An element must be opened before 
content can be written.")
+                    .isInstanceOf(OperationNotAllowedException.class);
+        } catch (OperationNotAllowedException expected) {
+            assertThat(expected).hasMessageContaining("Close called before an 
element has been opened.");
+        }
     }
 
     @Test
     void writeCData() throws Exception {
-        writer.startDocument().openElement("test").cdata("Just 
cdata").closeDocument();
-        assertEquals("<?xml version='1.0'?><test><![CDATA[ Just cdata 
]]></test>", out.toString());
+        StringWriter out = new StringWriter();
+        try (XmlWriter writer = new XmlWriter(out)) {
+            writer.startDocument().startElement("test").cdata("Just 
cdata").closeDocument();
+            assertThat(out).hasToString("<?xml version='1.0'?><test><![CDATA[ 
Just cdata ]]></test>");
+        }
     }
 
     @Test
     void writeCDataEmbeddedCData() throws Exception {
-        writer.startDocument().openElement("test").cdata("Some <![CDATA[ cdata 
]]> text").closeDocument();
-        assertEquals("<?xml version='1.0'?><test><![CDATA[ Some \\u3C![CDATA[ 
cdata {rat:CDATA close} text ]]></test>", out.toString());
+        StringWriter out = new StringWriter();
+        try (XmlWriter writer = new XmlWriter(out)) {
+            writer.startDocument().startElement("test").cdata("Some <![CDATA[ 
cdata ]]> text").closeDocument();
+            assertThat(out).hasToString("<?xml version='1.0'?><test><![CDATA[ 
Some \\u3C![CDATA[ cdata {rat:CDATA close} text ]]></test>");
+        }
     }
 
     @Test
     void closeElementBeforeOpened() throws IOException {
-        IXmlWriter underTest = writer.startDocument().openElement("test");
-        assertThrows(NoSuchElementException.class, () -> 
underTest.closeElement("missing"));
+        StringWriter out = new StringWriter();
+        try (XmlWriter writer = new XmlWriter(out)) {
+            XmlWriter underTest = writer.startDocument().startElement("test");
+            assertThatThrownBy(() -> underTest.closeElement("missing"))
+                            .isInstanceOf(NoSuchElementException.class);
+        }
     }
 
     @Test
     void closeElement() throws Exception {
-        
writer.startDocument().openElement("root").openElement("hello").openElement("world").content("hello
 world").closeElement("hello").openElement("test").closeDocument();
-        assertEquals("<?xml version='1.0'?><root><hello><world>hello 
world</world></hello><test/></root>", out.toString());
+        StringWriter out = new StringWriter();
+        try (XmlWriter writer = new XmlWriter(out)) {
+            
writer.startDocument().startElement("root").startElement("hello").startElement("world").content("hello
 world").closeElement("hello").startElement("test").closeDocument();
+            assertThat(out).hasToString("<?xml 
version='1.0'?><root><hello><world>hello world</world></hello><test/></root>");
+        }
     }
 
     @Test
@@ -545,7 +481,10 @@ class XmlWriterTest {
                 "</base>");
         byte[] rawDocument = "<?xml version='1.0'?><root><hello><world>hello 
world</world></hello><test/></root>".getBytes(StandardCharsets.UTF_8);
         Document document = XmlUtils.toDom(new 
ByteArrayInputStream(rawDocument));
-        
writer.startDocument().openElement("base").append(document).closeDocument();
-        assertEquals(expected, out.toString());
+        StringWriter out = new StringWriter();
+        try (XmlWriter writer = new XmlWriter(out)) {
+            
writer.startDocument().startElement("base").append(document).closeDocument();
+        }
+        assertThat(out).hasToString(expected);
     }
 }
diff --git 
a/apache-rat-core/src/test/java/org/apache/rat/testhelpers/XmlUtils.java 
b/apache-rat-core/src/test/java/org/apache/rat/testhelpers/XmlUtils.java
index c7e3cfcc..7f53c9a6 100644
--- a/apache-rat-core/src/test/java/org/apache/rat/testhelpers/XmlUtils.java
+++ b/apache-rat-core/src/test/java/org/apache/rat/testhelpers/XmlUtils.java
@@ -46,7 +46,7 @@ import javax.xml.xpath.XPath;
 import javax.xml.xpath.XPathConstants;
 import javax.xml.xpath.XPathExpressionException;
 
-import org.apache.rat.report.xml.writer.IXmlWriter;
+import org.apache.rat.report.xml.writer.XmlWriter;
 import org.apache.rat.utils.DefaultLog;
 import org.apache.rat.utils.StandardXmlFactory;
 import org.w3c.dom.Document;
@@ -143,7 +143,7 @@ public final class XmlUtils {
         return StandardXmlFactory.documentBuilder().parse(inputStream);
     }
 
-    public static void writeAttribute(final IXmlWriter writer, final String 
name, final boolean booleanValue)
+    public static void writeAttribute(final XmlWriter writer, final String 
name, final boolean booleanValue)
             throws IOException {
         final String value = Boolean.toString(booleanValue);
         writer.attribute(name, value);
diff --git 
a/apache-rat-tools/src/main/java/org/apache/rat/tools/xsd/XsdWriter.java 
b/apache-rat-tools/src/main/java/org/apache/rat/tools/xsd/XsdWriter.java
index 56b97b02..ccbc583b 100644
--- a/apache-rat-tools/src/main/java/org/apache/rat/tools/xsd/XsdWriter.java
+++ b/apache-rat-tools/src/main/java/org/apache/rat/tools/xsd/XsdWriter.java
@@ -76,7 +76,7 @@ public class XsdWriter {
      */
     public XsdWriter init() throws IOException {
         writer.startDocument()
-        .openElement("xs:schema")
+        .startElement("xs:schema")
         .attribute("attributeFormDefault", "unqualified")
                 .attribute("xmlns:xs", "http://www.w3.org/2001/XMLSchema";);
         return this;
@@ -111,7 +111,7 @@ public class XsdWriter {
      * @throws IOException on error.
      */
     public XsdWriter open(final Type type, final String... attributeMap) 
throws IOException {
-        writer.openElement(type.elementName);
+        writer.startElement(type.elementName);
         writeAttributes(attributeMap);
         return this;
     }
@@ -124,7 +124,7 @@ public class XsdWriter {
      * @throws IOException on error.
      */
     public XsdWriter attribute(final String name, final String... 
attributeMap) throws IOException {
-        writer.openElement("xs:attribute").attribute("name", name);
+        writer.startElement("xs:attribute").attribute("name", name);
         writeAttributes(attributeMap);
         writer.closeElement();
         return this;
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index cd8a80e5..33009c05 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -68,6 +68,9 @@ in order to be properly linked in site reports.
     </release>
     -->
     <release version="1.0.0-SNAPSHOT" date="xxxx-yy-zz" description="Current 
SNAPSHOT - release to be done">
+      <action issue="RAT-561" type="add" dev="claudenw">
+        Remove IXmlWriter and simplify RatReport XML writer implementations 
while ensuring all resources are closed in tests.
+      </action>
       <action issue="RAT-560" type="add" dev="claudenw">
         Disallowed DOCTYPE declarations in the Xerces2 XML parser to mitigate 
XXE attacks, and centralised XML parser initialisation within RAT.
       </action>

Reply via email to