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 1f87498a RAT-568: Create IODescriptor (#695)
1f87498a is described below

commit 1f87498a381e8e732ec633732b171c1fc129de52
Author: Claude Warren <[email protected]>
AuthorDate: Wed Jul 8 11:19:35 2026 +0200

    RAT-568: Create IODescriptor (#695)
    
    * Creat IODescriptor to provide better context for input/output reporting.
    
    Update ReportConfiguration to use IODescriptor for output and stylesheet 
parameters.
    Update associated classes to utilize IODescriptor.
    Update tests to account for IODescriptor.
    Update UI generation for IODescriptor
    
    * updated javadoc
    
    * added tests
    
    * Fixed sonarqube issues
    
    * RAT-568: Add changelog entry and fix minor issues during review
    
    ---------
    Co-authored-by: P. Ottlinger <[email protected]>
---
 .../java/org/apache/rat/ReportConfiguration.java   | 116 +++++++++++++++-----
 .../main/java/org/apache/rat/commandline/Arg.java  |   3 +-
 .../org/apache/rat/commandline/StyleSheets.java    |  36 ++++---
 .../org/apache/rat/ReportConfigurationTest.java    | 120 +++++++++++++--------
 .../org/apache/rat/ReporterOptionsProvider.java    |   8 +-
 .../src/test/java/org/apache/rat/ReporterTest.java |  22 ++--
 .../test/AbstractConfigurationOptionsProvider.java |   4 +-
 .../main/java/org/apache/rat/mp/RatCheckMojo.java  |   2 +-
 .../main/java/org/apache/rat/mp/RatReportMojo.java |   7 +-
 .../main/java/org/apache/rat/anttasks/Report.java  |   4 +-
 .../org/apache/rat/tools/xsd/XsdGenerator.java     |   2 +-
 src/changes/changes.xml                            |   3 +
 12 files changed, 212 insertions(+), 115 deletions(-)

diff --git 
a/apache-rat-core/src/main/java/org/apache/rat/ReportConfiguration.java 
b/apache-rat-core/src/main/java/org/apache/rat/ReportConfiguration.java
index 8fc5835d..57909582 100644
--- a/apache-rat-core/src/main/java/org/apache/rat/ReportConfiguration.java
+++ b/apache-rat-core/src/main/java/org/apache/rat/ReportConfiguration.java
@@ -20,6 +20,7 @@ package org.apache.rat;
 
 import java.io.File;
 import java.io.FileFilter;
+import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
@@ -69,15 +70,20 @@ import org.apache.rat.walker.ReportableListWalker;
  */
 public class ReportConfiguration {
 
+    /** The IODescriptor for {@code System.out}. */
+    public static final IODescriptor<OutputStream> SYSTEM_OUT =
+            // SONAR wants to require logging output, which is the wrong 
reporting channel for this case.
+            new IODescriptor<>("System.out", () -> 
CloseShieldOutputStream.wrap(System.out)); // NOSONAR
+
     /**
      * The styles of processing for various categories of documents.
      */
     public enum Processing {
-        /** List file as present only */
+        /** List file as present only. */
         NOTIFICATION("List file as present"),
-        /** List all present licenses */
+        /** List all present licenses. */
         PRESENCE("List any licenses found"),
-        /** List all present licenses and unknown licenses */
+        /** List all present licenses and unknown licenses. */
         ABSENCE("List licenses found and any unknown licences");
 
         /**
@@ -110,18 +116,18 @@ public class ReportConfiguration {
      */
     private boolean addingLicensesForced;
     /**
-     * The copyright message to add if we are adding headers. Will be null if 
we are not
-     * adding copyright messages.
+     * The copyright message to add if we are adding headers. Will be {@code 
null}
+     * if we are not adding copyright messages.
      */
     private String copyrightMessage;
     /**
-     * The IOSupplier that provides the output stream to write the report to.
+     * The IODescriptor that provides the output stream to write the report to.
      */
-    private IOSupplier<OutputStream> out;
+    private IODescriptor<OutputStream> out;
     /**
-     * The IOSupplier that provides the stylesheet to style the XML output.
+     * The IODescriptor that provides the stylesheet to style the XML output.
      */
-    private IOSupplier<InputStream> styleSheet;
+    private IODescriptor<InputStream> styleSheet;
 
     /**
      * A list of files to read file names from.
@@ -129,7 +135,7 @@ public class ReportConfiguration {
     private final List<File> sources;
 
     /**
-     * A list of reportables to process;
+     * A list of reportables to process.
      */
     private final List<Reportable> reportables;
 
@@ -193,7 +199,7 @@ public class ReportConfiguration {
      * Adds a file as a source of files to scan.
      * The file must be a text file that lists files to be included.
      * File within the file must be in linux format with a
-     * "/" file separator.
+     * {@code "/"} file separator.
      * @param file the file to process.
      */
     public void addSource(final File file) {
@@ -442,16 +448,25 @@ public class ReportConfiguration {
      * the report with.
      */
     public IOSupplier<InputStream> getStyleSheet() {
-        return styleSheet;
+        return styleSheet == null ? null : styleSheet.ioSupplier();
     }
 
     /**
-     * Sets the style sheet for custom processing. The IOSupplier may be called
+     * Gets the IODescriptor with the style sheet.
+     * @return the IODescriptor that describes the XSLT style sheet to style
+     * the report with.
+     */
+    public IODescriptor<InputStream> getStyleSheetDescriptor() {
+        return styleSheet == null ? null : styleSheet;
+    }
+
+    /**
+     * Sets the style sheet for custom processing. The IODescriptor may be 
called
      * multiple times, so the input stream must be able to be opened and closed
      * multiple times.
      * @param styleSheet the XSLT style sheet to style the report with.
      */
-    public void setStyleSheet(final IOSupplier<InputStream> styleSheet) {
+    public void setStyleSheet(final IODescriptor<InputStream> styleSheet) {
         this.styleSheet = styleSheet;
     }
 
@@ -499,7 +514,7 @@ public class ReportConfiguration {
      */
     public void setStyleSheet(final URL styleSheet) {
         Objects.requireNonNull(styleSheet, "Stylesheet file must not be null");
-        setStyleSheet(styleSheet::openStream);
+        setStyleSheet(new IODescriptor<>(styleSheet.toString(), 
styleSheet::openStream));
     }
 
     /**
@@ -511,7 +526,7 @@ public class ReportConfiguration {
      * the report to. A null value will use System.out.
      * @see CloseShieldOutputStream
      */
-    public void setOut(final IOSupplier<OutputStream> out) {
+    public void setOut(final IODescriptor<OutputStream> out) {
         this.out = out;
     }
 
@@ -519,8 +534,8 @@ public class ReportConfiguration {
      * Sets the OutputStream supplier to use the specified file. The file may 
be
      * opened and closed several times. File is deleted first and then may be
      * repeatedly opened in append mode.
-     * @see #setOut(IOSupplier)
-     * @param file the file to create the supplier with.
+     * @see #setOut(IODescriptor)
+     * @param file The file to create the supplier with.
      */
     public void setOut(final File file) {
         Objects.requireNonNull(file, "output file should not be null");
@@ -535,16 +550,25 @@ public class ReportConfiguration {
         if (!parent.mkdirs() && !parent.isDirectory()) {
             DefaultLog.getInstance().warn("Unable to create directory: " + 
file.getParentFile());
         }
-        setOut(() -> new FileOutputStream(file, true));
+        setOut(IODescriptor.output(file));
     }
 
     /**
      * Returns the output stream supplier. If no stream has been set returns a
-     * supplier for System.out.
+     * supplier for {@code System.out}.
      * @return the supplier of the output stream to write the report to.
      */
     public IOSupplier<OutputStream> getOutput() {
-        return out == null ? () -> CloseShieldOutputStream.wrap(System.out) : 
out;
+        return getOutputDescriptor().ioSupplier();
+    }
+
+    /**
+     * Returns the output IODescriptor. If no stream has been set returns a
+     * descriptor for {@code System.out}.
+     * @return the IODescriptor of the output stream to write the report to.
+     */
+    public IODescriptor<OutputStream> getOutputDescriptor() {
+        return out == null ? SYSTEM_OUT : out;
     }
 
     /**
@@ -569,7 +593,7 @@ public class ReportConfiguration {
      * Adds a license to the list of licenses. Does not add the license to the 
list
      * of approved licenses.
      * @param builder the license builder to build and add to the list of 
licenses.
-     * @return The ILicense implementation that was added.
+     * @return the ILicense implementation that was added.
      */
     public ILicense addLicense(final ILicense.Builder builder) {
         return licenseSetFactory.addLicense(builder);
@@ -796,12 +820,12 @@ public class ReportConfiguration {
     }
 
     /**
-     * Gets a sorted set of ILicenseFamily objects based on {@code filter}. if
-     * filter is set:
+     * Gets a sorted set of ILicenseFamily objects based on {@code filter}. If
+     * filter is set to:
      * <ul>
      * <li>{@code all} - All licenses families will be returned.</li>
-     * <li>{@code approved} - Only approved license families will be 
returned</li>
-     * <li>{@code none} - No license families will be returned</li>
+     * <li>{@code approved} - Only approved license families will be 
returned.</li>
+     * <li>{@code none} - No license families will be returned.</li>
      * </ul>
      * @param filter The license filter.
      * @return The set of defined licenses.
@@ -843,4 +867,44 @@ public class ReportConfiguration {
             throw new ConfigurationException(msg);
         }
     }
+
+    /**
+     * An IODescriptor comprises a name and an IOSupplier. The name should 
identify the contents of the stream.
+     * @param name the name of the supplier.
+     * @param ioSupplier the IOSupplier that provides either an InputStream or 
an OutputStream
+     * @param <T> either InputStream or OutputStream.
+     */
+    public record IODescriptor<T>(String name, IOSupplier<T> ioSupplier) {
+
+        // OUTPUT CONSTRUCTORS
+        /**
+         * Creates an output IODescriptor for the file name within the working 
directory.
+         * @param name the name of the file to open.
+         * @param workingDirectory the working directory for the file.
+         * @return the Output IODescriptor.
+         */
+        static IODescriptor<OutputStream> output(final String name, final 
DocumentName workingDirectory) {
+            DocumentName docName = workingDirectory.resolve(name);
+            return new IODescriptor<>(name, () -> new 
FileOutputStream(docName.asFile()));
+        }
+
+        /**
+         * Creates an output IODescriptor for the file. Does not modify for 
working directory.
+         * @param file the file to open.
+         * @return the Output IODescriptor.
+         */
+        static IODescriptor<OutputStream> output(final File file) {
+            return new IODescriptor<>(file.toString(), () -> new 
FileOutputStream(file, true));
+        }
+
+        // INPUT CONSTRUCTORS
+        /**
+         * Creates an input IODescriptor for the file. Does not modify for 
working directory.
+         * @param file the file to open.
+         * @return the Input IODescriptor.
+         */
+        static IODescriptor<InputStream> input(final File file) {
+            return new IODescriptor<>(file.toString(), () -> new 
FileInputStream(file));
+        }
+    }
 }
diff --git a/apache-rat-core/src/main/java/org/apache/rat/commandline/Arg.java 
b/apache-rat-core/src/main/java/org/apache/rat/commandline/Arg.java
index 314e9c27..d11e9056 100644
--- a/apache-rat-core/src/main/java/org/apache/rat/commandline/Arg.java
+++ b/apache-rat-core/src/main/java/org/apache/rat/commandline/Arg.java
@@ -38,7 +38,6 @@ import org.apache.commons.cli.OptionGroup;
 import org.apache.commons.cli.Options;
 import org.apache.commons.cli.ParseException;
 import org.apache.commons.io.IOUtils;
-import org.apache.commons.io.output.CloseShieldOutputStream;
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.rat.ConfigurationException;
 import org.apache.rat.Defaults;
@@ -625,7 +624,7 @@ public enum Arg {
                 } catch (ParseException e) {
                     // we write to system out by default.
                     context.logParseException(e, selected, "System.out");
-                    context.getConfiguration().setOut(() -> 
CloseShieldOutputStream.wrap(System.out)); // NOSONAR
+                    
context.getConfiguration().setOut(ReportConfiguration.SYSTEM_OUT);
                 }
             }),
 
diff --git 
a/apache-rat-core/src/main/java/org/apache/rat/commandline/StyleSheets.java 
b/apache-rat-core/src/main/java/org/apache/rat/commandline/StyleSheets.java
index c73a9e4e..b56e0dde 100644
--- a/apache-rat-core/src/main/java/org/apache/rat/commandline/StyleSheets.java
+++ b/apache-rat-core/src/main/java/org/apache/rat/commandline/StyleSheets.java
@@ -25,8 +25,8 @@ import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.Objects;
 
-import org.apache.commons.io.function.IOSupplier;
 import org.apache.rat.ConfigurationException;
+import org.apache.rat.ReportConfiguration;
 
 import static java.lang.String.format;
 
@@ -39,19 +39,20 @@ public enum StyleSheets {
      */
     PLAIN("plain-rat", "The default style."),
     /**
-     * The missing header report style sheet
+     * The missing header report style sheet.
      */
     MISSING_HEADERS("missing-headers", "Produces a report of files that are 
missing headers."),
     /**
-     * The unapproved licenses report
+     * The unapproved licenses report.
      */
     UNAPPROVED_LICENSES("unapproved-licenses", "Produces a report of the files 
with unapproved licenses."),
     /**
-     * The plain style sheet. The current default.
+     * The pretty-printed XML style sheet.
      */
     XML("xml", "Produces output in pretty-printed XML.");
+
     /**
-     * The name of the style sheet. Must map to bundled resource xslt file
+     * The name of the style sheet. Must map to bundled resource XSLT file
      */
     private final String name;
     /**
@@ -61,8 +62,8 @@ public enum StyleSheets {
 
     /**
      * Constructor.
-     * @param name the name of the xslt file.
-     * @param description What this xslt produces.
+     * @param name the name of the XSLT file.
+     * @param description what this XSLT produces.
      */
     StyleSheets(final String name, final String description) {
         this.name = name;
@@ -73,9 +74,10 @@ public enum StyleSheets {
      * Gets the IOSupplier for a style sheet.
      * @return an IOSupplier for the sheet.
      */
-    public IOSupplier<InputStream> getStyleSheet() {
-        return 
Objects.requireNonNull(StyleSheets.class.getClassLoader().getResource(format("org/apache/rat/%s.xsl",
 name)),
-                "missing stylesheet: " + name)::openStream;
+    public ReportConfiguration.IODescriptor<InputStream> getStyleSheet() {
+        URL url = 
StyleSheets.class.getClassLoader().getResource(format("org/apache/rat/%s.xsl", 
name));
+        Objects.requireNonNull(url, "missing stylesheet: " + name);
+        return new ReportConfiguration.IODescriptor<>(name, url::openStream);
     }
 
     /**
@@ -83,29 +85,29 @@ public enum StyleSheets {
      * @param name the short name for or the path to a style sheet.
      * @return the IOSupplier for the style sheet.
      */
-    public static IOSupplier<InputStream> getStyleSheet(final String name) {
+    public static ReportConfiguration.IODescriptor<InputStream> 
getStyleSheet(final String name) {
         URL url = 
StyleSheets.class.getClassLoader().getResource(format("org/apache/rat/%s.xsl", 
name));
         if (url != null) {
-            return url::openStream;
+            return new ReportConfiguration.IODescriptor<>(name, 
url::openStream);
         }
         Path p = Paths.get(name);
         if (p.toFile().exists()) {
-            return () -> Files.newInputStream(p);
+            return new ReportConfiguration.IODescriptor<>(name, () -> 
Files.newInputStream(p));
         }
         throw new ConfigurationException(format("Stylesheet file '%s' not 
found", name));
     }
 
     /**
-     * Gets the name of the xslt file.
-     * @return the name of the xslt file, without the extension.
+     * Gets the name of the XSLT file.
+     * @return the name of the XSLT file, without the extension.
      */
     public String arg() {
         return name;
     }
 
     /**
-     * Gets the description of the xslt file.
-     * @return the description of the xslt file.
+     * Gets the description of the XSLT file.
+     * @return the description of the XSLT file.
      */
     public String desc() {
         return desc;
diff --git 
a/apache-rat-core/src/test/java/org/apache/rat/ReportConfigurationTest.java 
b/apache-rat-core/src/test/java/org/apache/rat/ReportConfigurationTest.java
index df346d2e..7d604043 100644
--- a/apache-rat-core/src/test/java/org/apache/rat/ReportConfigurationTest.java
+++ b/apache-rat-core/src/test/java/org/apache/rat/ReportConfigurationTest.java
@@ -26,6 +26,7 @@ import static org.mockito.Mockito.when;
 import java.io.BufferedReader;
 import java.io.ByteArrayOutputStream;
 import java.io.File;
+import java.io.FileFilter;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
@@ -62,7 +63,6 @@ import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.io.TempDir;
-import org.mockito.Mockito;
 
 public class ReportConfigurationTest {
 
@@ -73,19 +73,19 @@ public class ReportConfigurationTest {
     private File tempDir;
 
     @BeforeEach
-    public void setup() {
+    void setup() {
         log = new TestingLog();
         DefaultLog.setInstance(log);
         underTest = new ReportConfiguration();
     }
 
     @AfterEach
-    public void cleanup() {
+    void cleanup() {
         DefaultLog.setInstance(null);
     }
 
     @Test
-    public void testAddIncludedFilter() {
+    void testAddIncludedFilter() {
         DocumentName dirName = DocumentName.builder(tempDir).build();
         underTest.addExcludedFilter(DirectoryFileFilter.INSTANCE);
         DocumentNameMatcher excluder = underTest.getDocumentExcluder(dirName);
@@ -98,7 +98,7 @@ public class ReportConfigurationTest {
     }
 
     @Test
-    public void testAddFamilies() {
+    void testAddFamilies() {
         ILicenseFamily fam1 = 
ILicenseFamily.builder().setLicenseFamilyCategory("FOO").setLicenseFamilyName("found
 on overview").build();
         ILicenseFamily fam2 = 
ILicenseFamily.builder().setLicenseFamilyCategory("BAR").setLicenseFamilyName("big
 and round").build();
         underTest.addFamilies(Arrays.asList(fam1, fam2));
@@ -109,13 +109,14 @@ public class ReportConfigurationTest {
     }
 
     @Test
-    public void testAddApprovedLicenseId() {
+    void testAddApprovedLicenseId() {
         underTest.addApprovedLicenseId("FOO");
         SortedSet<String> result = 
underTest.getLicenseIds(LicenseFilter.APPROVED);
         assertThat(result).hasSize(1).contains("FOO");
     }
+
     @Test
-    public void testAddAndRemoveApproveLicenseCategories() {
+    void testAddAndRemoveApproveLicenseCategories() {
         List<String> expected = new ArrayList<>();
         underTest.addLicense(new TestingLicense("Unapproved"));
 
@@ -175,7 +176,7 @@ public class ReportConfigurationTest {
     }
 
     @Test
-    public void testRemoveBeforeAddApproveLicenseCategories() {
+    void testRemoveBeforeAddApproveLicenseCategories() {
         underTest.addLicense( new TestingLicense("TheCat"));
         
assertThat(underTest.getLicenseCategories(LicenseFilter.APPROVED)).isEmpty();
         
assertThat(underTest.getLicenseFamilies(LicenseFilter.APPROVED)).isEmpty();
@@ -193,7 +194,7 @@ public class ReportConfigurationTest {
     }
 
     @Test
-    public void testAddAndRemoveApproveLicenseIds() {
+    void testAddAndRemoveApproveLicenseIds() {
         List<String> expected = new ArrayList<>();
         underTest.addLicense(new TestingLicense("Unapproved"));
 
@@ -273,7 +274,7 @@ public class ReportConfigurationTest {
     }
 
     @Test
-    public void removeFamilyAddLicense() {
+    void removeFamilyAddLicense() {
         addCatz();
         underTest.addApprovedLicenseCategory("catz");
         underTest.removeApprovedLicenseCategory("catz");
@@ -283,7 +284,7 @@ public class ReportConfigurationTest {
     }
 
     @Test
-    public void addFamilyRemoveLicense() {
+    void addFamilyRemoveLicense() {
         addCatz();
         underTest.addApprovedLicenseCategory("catz");
         
assertThat(underTest.getLicenses(LicenseFilter.APPROVED).size()).isEqualTo(7);
@@ -292,7 +293,7 @@ public class ReportConfigurationTest {
     }
 
     @Test
-    public void removeFamilyRemoveLicense() {
+    void removeFamilyRemoveLicense() {
         addCatz();
         addDogz();
         underTest.addApprovedLicenseCategory("catz");
@@ -304,7 +305,7 @@ public class ReportConfigurationTest {
     }
 
     @Test
-    public void addFamilyAddLicense() {
+    void addFamilyAddLicense() {
         addCatz();
         addDogz();
         underTest.addApprovedLicenseCategory("catz");
@@ -314,7 +315,7 @@ public class ReportConfigurationTest {
     }
 
     @Test
-    public void testRemoveBeforeAddApproveLicenseIds() {
+    void testRemoveBeforeAddApproveLicenseIds() {
         underTest.addLicense( new TestingLicense("TheCat"));
         assertThat(underTest.getLicenseIds(LicenseFilter.APPROVED)).isEmpty();
         assertThat(underTest.getLicenses(LicenseFilter.APPROVED)).isEmpty();
@@ -335,7 +336,7 @@ public class ReportConfigurationTest {
     }
 
     @Test
-    public void testAddLicense() {
+    void testAddLicense() {
         List<ILicense> expected = new ArrayList<>();
         assertThat(underTest.getLicenses(LicenseFilter.ALL)).isEmpty();
 
@@ -353,17 +354,18 @@ public class ReportConfigurationTest {
     }
 
     @Test
-    public void copyrightMessageTest() {
+    void copyrightMessageTest() {
         assertThat(underTest.getCopyrightMessage()).isNull();
         underTest.setCopyrightMessage("This is the message");
         assertThat(underTest.getCopyrightMessage()).isEqualTo("This is the 
message");
     }
 
-    DocumentName mkDocumentName(File f) {
+    private DocumentName mkDocumentName(File f) {
         return DocumentName.builder(f).setBaseName(tempDir).build();
     }
+
     @Test
-    public void exclusionTest() {
+    void inclusionExclusionTest() {
         DocumentName baseDir = DocumentName.builder(tempDir).build();
         DocumentName foo = mkDocumentName(new File(tempDir,"foo"));
         
assertThat(underTest.getDocumentExcluder(baseDir).matches(foo)).isTrue();
@@ -378,19 +380,24 @@ public class ReportConfigurationTest {
 
         underTest.addIncludedCollection(StandardCollection.HIDDEN_DIR);
         
assertThat(underTest.getDocumentExcluder(baseDir).matches(hiddenDir)).isTrue();
-
+        // explicitly adding again does not change test
         underTest.addExcludedCollection(StandardCollection.HIDDEN_DIR);
         
assertThat(underTest.getDocumentExcluder(baseDir).matches(hiddenDir)).isTrue();
 
-        underTest.addExcludedFilter(DirectoryFileFilter.DIRECTORY);
+        DocumentName percentName = mkDocumentName(new File(tempDir, 
"%hello%"));
+        
assertThat(underTest.getDocumentExcluder(baseDir).matches(percentName)).isFalse();
+        FileFilter percentFilter = file -> file.getName().endsWith("%hello%");
+        underTest.addIncludedFilter(percentFilter);
+        
assertThat(underTest.getDocumentExcluder(baseDir).matches(percentName)).isTrue();
 
+        underTest.addExcludedFilter(DirectoryFileFilter.DIRECTORY);
         File file = new File(tempDir, "newDir");
         assertThat(file.mkdirs()).as(() -> "Could not create directory " + 
file).isTrue();
         
assertThat(underTest.getDocumentExcluder(baseDir).matches(mkDocumentName(file))).isFalse();
     }
 
     @Test
-    public void archiveProcessingTest() {
+    void archiveProcessingTest() {
         
assertThat(underTest.getArchiveProcessing()).isEqualTo(ReportConfiguration.Processing.NOTIFICATION);
 
         underTest.setFrom(Defaults.builder().build());
@@ -407,7 +414,7 @@ public class ReportConfigurationTest {
     }
 
     @Test
-    public void licenseFamiliesTest() {
+    void licenseFamiliesTest() {
         assertThat(underTest.getLicenseFamilies(LicenseFilter.ALL)).isEmpty();
         
assertThat(underTest.getLicenseFamilies(LicenseFilter.APPROVED)).isEmpty();
         assertThat(underTest.getLicenseFamilies(LicenseFilter.NONE)).isEmpty();
@@ -429,7 +436,7 @@ public class ReportConfigurationTest {
     }
 
     @Test
-    public void licensesTest() {
+    void licensesTest() {
         assertThat(underTest.getLicenses(LicenseFilter.ALL)).isEmpty();
         assertThat(underTest.getLicenses(LicenseFilter.APPROVED)).isEmpty();
         assertThat(underTest.getLicenses(LicenseFilter.NONE)).isEmpty();
@@ -451,12 +458,12 @@ public class ReportConfigurationTest {
     }
 
     @Test
-    public void outputTest() throws IOException {
+    void outputTest() throws IOException {
         
assertThat(underTest.getOutput().get()).isExactlyInstanceOf(CloseShieldOutputStream.class);
         assertThat(underTest.getWriter()).isNotNull();
 
         ByteArrayOutputStream stream = new ByteArrayOutputStream();
-        underTest.setOut(() -> stream);
+        underTest.setOut(new ReportConfiguration.IODescriptor("outputTest", () 
-> stream));
         assertThat(underTest.getOutput().get()).isEqualTo(stream);
         PrintWriter writer = underTest.getWriter().get();
         assertThat(writer).isNotNull();
@@ -466,7 +473,7 @@ public class ReportConfigurationTest {
     }
 
     @Test
-    public void reportableTest() {
+    void reportableTest() {
         assertThat(underTest.hasSource()).isFalse();
         Reportable reportable = mock(Reportable.class);
         underTest.addSource(reportable);
@@ -476,13 +483,15 @@ public class ReportConfigurationTest {
     }
 
     @Test
-    public void stylesheetTest() throws IOException, URISyntaxException {
+    void stylesheetTest() throws IOException, URISyntaxException {
         URL url = this.getClass().getResource("ReportConfigurationTestFile");
         assertThat(url).isNotNull();
 
+        assertThat(underTest.getStyleSheetDescriptor()).isNull();
         assertThat(underTest.getStyleSheet()).isNull();
         InputStream stream = mock(InputStream.class);
-        underTest.setStyleSheet(() -> stream);
+        underTest.setStyleSheet(new 
ReportConfiguration.IODescriptor("stylesheetTest", () -> stream));
+        
assertThat(underTest.getStyleSheetDescriptor().ioSupplier().get()).isEqualTo(stream);
         assertThat(underTest.getStyleSheet().get()).isEqualTo(stream);
 
         File file = mock(File.class);
@@ -493,10 +502,13 @@ public class ReportConfigurationTest {
         BufferedReader d = new BufferedReader(new 
InputStreamReader(underTest.getStyleSheet().get()));
         assertThat(d.readLine()).isEqualTo("/*");
         assertThat(d.readLine()).isEqualTo(" * Licensed to the Apache Software 
Foundation (ASF) under one   *");
+        d = new BufferedReader(new 
InputStreamReader(underTest.getStyleSheetDescriptor().ioSupplier().get()));
+        assertThat(d.readLine()).isEqualTo("/*");
+        assertThat(d.readLine()).isEqualTo(" * Licensed to the Apache Software 
Foundation (ASF) under one   *");
     }
 
     @Test
-    public void testFlags() {
+    void testFlags() {
         assertThat(underTest.isAddingLicenses()).isFalse();
         assertThat(underTest.isAddingLicensesForced()).isFalse();
 
@@ -514,14 +526,13 @@ public class ReportConfigurationTest {
     }
 
     @Test
-    public void testValidate() {
+    void testValidate() {
         final StringBuilder sb = new StringBuilder();
         String msg = "At least one source must be specified";
         assertThatThrownBy(() -> 
underTest.validate(sb::append)).isExactlyInstanceOf(ConfigurationException.class)
                 .hasMessageContaining(msg);
         assertThat(sb.toString()).isEqualTo(msg);
 
-
         sb.setLength(0);
         msg = "You must specify at least one license";
         underTest.addSource(mock(Reportable.class));
@@ -537,10 +548,10 @@ public class ReportConfigurationTest {
     }
     
     @Test
-    public void testSetOut() throws IOException {
+    void testSetOut() throws IOException {
         ReportConfiguration config = new ReportConfiguration();
         try (OutputStreamInterceptor osi = new OutputStreamInterceptor()) {
-            config.setOut(() -> osi);
+            config.setOut(new ReportConfiguration.IODescriptor("testSetOut",() 
-> osi));
             assertThat(osi.closeCount).isEqualTo(0);
             try (OutputStream os = config.getOutput().get()) {
                 assertThat(os).isNotNull();
@@ -556,7 +567,7 @@ public class ReportConfigurationTest {
     }
     
     @Test
-    public void logFamilyCollisionTest() {
+    void logFamilyCollisionTest() {
         // setup
         
underTest.addFamily(ILicenseFamily.builder().setLicenseFamilyCategory("CAT").setLicenseFamilyName("name"));
         assertThat(log.getCaptured()).doesNotContain("CAT");
@@ -577,7 +588,7 @@ public class ReportConfigurationTest {
     }
     
     @Test
-    public void familyDuplicateOptionsTest() {
+    void familyDuplicateOptionsTest() {
         
underTest.addFamily(ILicenseFamily.builder().setLicenseFamilyCategory("CAT").setLicenseFamilyName("name"));
         assertThat(log.getCaptured()).doesNotContain("CAT");
         
@@ -605,10 +616,10 @@ public class ReportConfigurationTest {
     }
 
     @Test
-    public void logLicenseCollisionTest() {
+    void logLicenseCollisionTest() {
         // setup
         ILicenseFamily family = 
ILicenseFamily.builder().setLicenseFamilyCategory("CAT").setLicenseFamilyName("family
 name").build();
-        IHeaderMatcher matcher = Mockito.mock(IHeaderMatcher.class);
+        IHeaderMatcher matcher = mock(IHeaderMatcher.class);
         when(matcher.getId()).thenReturn("Macher ID");
         underTest.addFamily(family);
         underTest.addLicense(ILicense.builder().setId("ID").setName("license 
name").setFamily(family.getFamilyCategory())
@@ -633,10 +644,10 @@ public class ReportConfigurationTest {
     }
     
     @Test
-    public void licenseDuplicateOptionsTest() {
+    void licenseDuplicateOptionsTest() {
         // setup
         ILicenseFamily family = 
ILicenseFamily.builder().setLicenseFamilyCategory("CAT").setLicenseFamilyName("family
 name").build();
-        IHeaderMatcher matcher = Mockito.mock(IHeaderMatcher.class);
+        IHeaderMatcher matcher = mock(IHeaderMatcher.class);
         when(matcher.getId()).thenReturn("Macher ID");
         underTest.addFamily(family);
         Function<String,ILicense> makeLicense = s -> 
ILicense.builder().setId("ID").setName(s).setFamily(family.getFamilyCategory())
@@ -662,9 +673,34 @@ public class ReportConfigurationTest {
                 .isExactlyInstanceOf(IllegalArgumentException.class);
     }
 
+    @Test
+    void reportExclusionErrorTest() {
+        Appendable appendable = new Appendable(){
+            @Override
+            public Appendable append(CharSequence csq) throws IOException {
+                throw new IOException("BANG!");
+            }
+
+            @Override
+            public Appendable append(CharSequence csq, int start, int end) 
throws IOException {
+                throw new IOException("BANG!");
+            }
+
+            @Override
+            public Appendable append(char c) throws IOException {
+                throw new IOException("BANG!");
+            }
+        };
+
+        underTest.reportExclusions(appendable);
+        assertThat(log.getCaptured())
+                .contains("WARN: Unable to report exclusions")
+                .contains("java.io.IOException: BANG!");
+    }
+
     /**
      * Validates that the configuration contains the default approved licenses.
-     * @param config The configuration to test.
+     * @param config the configuration to test.
      */
     public static void validateDefaultApprovedLicenses(ReportConfiguration 
config) {
         validateDefaultApprovedLicenses(config, 0);
@@ -672,7 +708,7 @@ public class ReportConfigurationTest {
     
     /**
      * Validates that the configuration contains the default approved licenses.
-     * @param config The configuration to test.
+     * @param config the configuration to test.
      */
     public static void validateDefaultApprovedLicenses(ReportConfiguration 
config, int additionalIdCount) {
         
assertThat(config.getLicenseCategories(LicenseFilter.APPROVED)).hasSize(XMLConfigurationReaderTest.APPROVED_IDS.length
 + additionalIdCount);
@@ -711,7 +747,7 @@ public class ReportConfigurationTest {
     
     /**
      * Validates that the configuration matches the default.
-     * @param config The configuration to test.
+     * @param config the configuration to test.
      */
     public static void validateDefault(ReportConfiguration config) {
         assertThat(config.isAddingLicenses()).isFalse();
diff --git 
a/apache-rat-core/src/test/java/org/apache/rat/ReporterOptionsProvider.java 
b/apache-rat-core/src/test/java/org/apache/rat/ReporterOptionsProvider.java
index 0ef6f019..bd9e4fd6 100644
--- a/apache-rat-core/src/test/java/org/apache/rat/ReporterOptionsProvider.java
+++ b/apache-rat-core/src/test/java/org/apache/rat/ReporterOptionsProvider.java
@@ -75,7 +75,7 @@ class ReporterOptionsProvider extends AbstractOptionsProvider 
implements Argumen
     static File sourceDir;
 
     /**
-     * A flag to determine if help was called
+     * A flag to determine if help was called.
      */
     final AtomicBoolean helpCalled = new AtomicBoolean(false);
 
@@ -93,7 +93,7 @@ class ReporterOptionsProvider extends AbstractOptionsProvider 
implements Argumen
      * Forces the {@code helpCalled} flag to be reset.
      *
      * @param args the arguments.
-     * @return A ReportConfiguration
+     * @return a ReportConfiguration
      * @throws IOException on critical error.
      */
     @Override
@@ -691,7 +691,6 @@ class ReporterOptionsProvider extends 
AbstractOptionsProvider implements Argumen
                 new String[]{"BSD-3"});
     }
 
-
     private void configTest(final Option option) {
         assertDoesNotThrow(() -> {
             configureSourceDir(option);
@@ -725,7 +724,6 @@ class ReporterOptionsProvider extends 
AbstractOptionsProvider implements Argumen
             
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(2);
             
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.APPROVED)).isEqualTo(1);
             
assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(1);
-
         });
     }
 
@@ -939,7 +937,7 @@ class ReporterOptionsProvider extends 
AbstractOptionsProvider implements Argumen
             String actualText = baos.toString(StandardCharsets.UTF_8);
             TextUtils.assertContainsExactly(1, "<resource 
encoding=\"ISO-8859-1\" mediaType=\"text/plain\" name=\"/stylesheet\" 
type=\"STANDARD\">", actualText);
 
-            try (InputStream expected = StyleSheets.getStyleSheet("xml").get();
+            try (InputStream expected = 
StyleSheets.getStyleSheet("xml").ioSupplier().get();
                  InputStream actual = config.getStyleSheet().get()) {
                 assertThat(IOUtils.contentEquals(expected, actual)).as("'xml' 
does not match").isTrue();
             }
diff --git a/apache-rat-core/src/test/java/org/apache/rat/ReporterTest.java 
b/apache-rat-core/src/test/java/org/apache/rat/ReporterTest.java
index 3d9c84e7..d9f11b7a 100644
--- a/apache-rat-core/src/test/java/org/apache/rat/ReporterTest.java
+++ b/apache-rat-core/src/test/java/org/apache/rat/ReporterTest.java
@@ -81,7 +81,7 @@ public class ReporterTest {
     }
 
     @Test
-    public void testExecute() throws RatException, ParseException {
+    void testExecute() throws RatException, ParseException {
         File output = new File(tempDirectory, "testExecute");
 
         CommandLine cl = new 
DefaultParser().parse(OptionCollection.buildOptions(), new 
String[]{"--output-style", "xml", "--output-file", output.getPath(), basedir});
@@ -136,7 +136,7 @@ public class ReporterTest {
     }
 
     @Test
-    public void testOutputOption() throws Exception {
+    void testOutputOption() throws Exception {
         File output = new File(tempDirectory, "test");
         CommandLine commandLine = new 
DefaultParser().parse(OptionCollection.buildOptions(), new String[]{"-o", 
output.getCanonicalPath(), basedir});
         ArgumentContext ctxt = new ArgumentContext(new File("."), commandLine);
@@ -151,7 +151,7 @@ public class ReporterTest {
     }
 
     @Test
-    public void testDefaultOutput() throws Exception {
+    void testDefaultOutput() throws Exception {
         File output = new File(tempDirectory, "testDefaultOutput");
 
         PrintStream origin = System.out;
@@ -179,7 +179,7 @@ public class ReporterTest {
     }
 
     @Test
-    public void testXMLOutput() throws Exception {
+    void testXMLOutput() throws Exception {
         Map<String, Map<String, String>> expected = new HashMap<>();
         expected.put("/.hiddenDirectory", mapOf("isDirectory", "true", 
"mediaType", "application/octet-stream",
                 "type", "IGNORED"));
@@ -404,12 +404,12 @@ public class ReporterTest {
     }
 
     @Test
-    public void xmlReportTest() throws Exception {
+    void xmlReportTest() throws Exception {
         ByteArrayOutputStream out = new ByteArrayOutputStream();
 
         ReportConfiguration configuration = initializeConfiguration();
         configuration.setStyleSheet(StyleSheets.XML.getStyleSheet());
-        configuration.setOut(() -> out);
+        configuration.setOut(new 
ReportConfiguration.IODescriptor("xmlReportTest", () -> out));
         new Reporter(configuration).output();
         Document doc = XmlUtils.toDom(new 
ByteArrayInputStream(out.toByteArray()));
 
@@ -448,7 +448,7 @@ public class ReporterTest {
     }
 
     @Test
-    public void plainReportTest() throws Exception {
+    void plainReportTest() throws Exception {
         final String NL = System.lineSeparator();
         final String SEPARATOR = 
"*****************************************************";
         final String HEADER = SEPARATOR + NL + //
@@ -457,7 +457,7 @@ public class ReporterTest {
                 "Generated at: ";
         ByteArrayOutputStream out = new ByteArrayOutputStream();
         ReportConfiguration configuration = initializeConfiguration();
-        configuration.setOut(() -> out);
+        configuration.setOut(new 
ReportConfiguration.IODescriptor("plainReportTest", () -> out));
         new Reporter(configuration).output();
 
         out.flush();
@@ -470,10 +470,10 @@ public class ReporterTest {
     }
 
     @Test
-    public void unapprovedLicensesReportTest() throws Exception {
+    void unapprovedLicensesReportTest() throws Exception {
         ByteArrayOutputStream out = new ByteArrayOutputStream();
         ReportConfiguration configuration = initializeConfiguration();
-        configuration.setOut(() -> out);
+        configuration.setOut(new 
ReportConfiguration.IODescriptor("unapprovedLicensesReportTest", () -> out));
         
configuration.setStyleSheet(this.getClass().getResource("/org/apache/rat/unapproved-licenses.xsl"));
         new Reporter(configuration).output();
 
@@ -489,7 +489,7 @@ public class ReporterTest {
     void listLicensesReportTest() throws Exception {
         ByteArrayOutputStream out = new ByteArrayOutputStream();
         ReportConfiguration configuration = initializeConfiguration();
-        configuration.setOut(() -> out);
+        configuration.setOut(new 
ReportConfiguration.IODescriptor("listLicensesReportTest", () -> out));
         
configuration.setStyleSheet(StyleSheets.UNAPPROVED_LICENSES.getStyleSheet());
         Reporter.listLicenses(configuration, 
LicenseSetFactory.LicenseFilter.NONE);
 
diff --git 
a/apache-rat-core/src/test/java/org/apache/rat/test/AbstractConfigurationOptionsProvider.java
 
b/apache-rat-core/src/test/java/org/apache/rat/test/AbstractConfigurationOptionsProvider.java
index 5d34d80a..84e30dc5 100644
--- 
a/apache-rat-core/src/test/java/org/apache/rat/test/AbstractConfigurationOptionsProvider.java
+++ 
b/apache-rat-core/src/test/java/org/apache/rat/test/AbstractConfigurationOptionsProvider.java
@@ -818,7 +818,7 @@ public abstract class AbstractConfigurationOptionsProvider 
extends AbstractOptio
             for (String sheet : new String[]{"plain-rat", "missing-headers", 
"unapproved-licenses", file.getAbsolutePath()}) {
                 args[0] = sheet;
                 ReportConfiguration config = 
generateConfig(ImmutablePair.of(option, args));
-                try (InputStream expected = 
StyleSheets.getStyleSheet(sheet).get();
+                try (InputStream expected = 
StyleSheets.getStyleSheet(sheet).ioSupplier().get();
                      InputStream actual = config.getStyleSheet().get()) {
                     assertThat(IOUtils.contentEquals(expected, actual)).as(() 
-> String.format("'%s' does not match", sheet)).isTrue();
                 }
@@ -845,7 +845,7 @@ public abstract class AbstractConfigurationOptionsProvider 
extends AbstractOptio
     protected void xmlTest() {
         assertDoesNotThrow(() -> {
             ReportConfiguration config = 
generateConfig(ImmutablePair.of(Arg.OUTPUT_STYLE.find("xml"), null));
-            try (InputStream expected = StyleSheets.getStyleSheet("xml").get();
+            try (InputStream expected = 
StyleSheets.getStyleSheet("xml").ioSupplier().get();
                  InputStream actual = config.getStyleSheet().get()) {
                 assertThat(IOUtils.contentEquals(expected, actual)).as("'xml' 
does not match").isTrue();
             }
diff --git 
a/apache-rat-plugin/src/main/java/org/apache/rat/mp/RatCheckMojo.java 
b/apache-rat-plugin/src/main/java/org/apache/rat/mp/RatCheckMojo.java
index 7bd6b1da..5a55a061 100644
--- a/apache-rat-plugin/src/main/java/org/apache/rat/mp/RatCheckMojo.java
+++ b/apache-rat-plugin/src/main/java/org/apache/rat/mp/RatCheckMojo.java
@@ -234,7 +234,7 @@ public class RatCheckMojo extends AbstractRatMojo {
                        
!config.getClaimValidator().isValid(ClaimStatistic.Counter.UNAPPROVED, 
statistics.getCounter(ClaimStatistic.Counter.UNAPPROVED))) {
                    try {
                        ByteArrayOutputStream baos = new 
ByteArrayOutputStream();
-                       
reporter.output(StyleSheets.UNAPPROVED_LICENSES.getStyleSheet(), () -> baos);
+                       
reporter.output(StyleSheets.UNAPPROVED_LICENSES.getStyleSheet().ioSupplier(), 
() -> baos);
                        getLog().warn(baos.toString(StandardCharsets.UTF_8));
                    } catch (RuntimeException rte) {
                        throw rte;
diff --git 
a/apache-rat-plugin/src/main/java/org/apache/rat/mp/RatReportMojo.java 
b/apache-rat-plugin/src/main/java/org/apache/rat/mp/RatReportMojo.java
index 330d3c5e..99ac97f5 100644
--- a/apache-rat-plugin/src/main/java/org/apache/rat/mp/RatReportMojo.java
+++ b/apache-rat-plugin/src/main/java/org/apache/rat/mp/RatReportMojo.java
@@ -90,7 +90,6 @@ public class RatReportMojo extends AbstractRatMojo implements 
MavenMultiPageRepo
     @Parameter(property = "outputEncoding", defaultValue = 
"${project.reporting.outputEncoding}", readonly = true)
     private String outputEncoding;
 
-
     /**
      * The local repository.
      */
@@ -153,9 +152,7 @@ public class RatReportMojo extends AbstractRatMojo 
implements MavenMultiPageRepo
         }
 
         File outputDirectory = new File(getOutputDirectory());
-
         String filename = getOutputName() + ".html";
-
         Locale locale = Locale.getDefault();
 
         try {
@@ -182,7 +179,6 @@ public class RatReportMojo extends AbstractRatMojo 
implements MavenMultiPageRepo
                     // render report
                     getSiteRenderer().mergeDocumentIntoSite(writer, sink, 
siteContext);
                 }
-
             }
 
             // copy generated resources also
@@ -264,7 +260,6 @@ public class RatReportMojo extends AbstractRatMojo 
implements MavenMultiPageRepo
             this.sinkFactory = sinkFactory;
 
             if (!(sink instanceof SiteRendererSink)) {
-
                 generateReportManually(locale);
             } else {
                 executeReport(locale);
@@ -446,7 +441,7 @@ public class RatReportMojo extends AbstractRatMojo 
implements MavenMultiPageRepo
                     config.reportExclusions(logWriter);
                 }
                 ByteArrayOutputStream baos = new ByteArrayOutputStream();
-                config.setOut(() -> baos);
+                config.setOut(new ReportConfiguration.IODescriptor("RAT 
output", () -> baos));
                 Reporter reporter = new Reporter(config);
                 reporter.output();
                 if (verbose) {
diff --git a/apache-rat-tasks/src/main/java/org/apache/rat/anttasks/Report.java 
b/apache-rat-tasks/src/main/java/org/apache/rat/anttasks/Report.java
index a069084f..cd91a8ec 100644
--- a/apache-rat-tasks/src/main/java/org/apache/rat/anttasks/Report.java
+++ b/apache-rat-tasks/src/main/java/org/apache/rat/anttasks/Report.java
@@ -416,7 +416,7 @@ public class Report extends BaseAntTask {
                     o -> DefaultLog.getInstance().warn("Help option not 
supported"),
                     true);
             if (getValues(Arg.OUTPUT_FILE).isEmpty()) {
-                configuration.setOut(() -> new LogOutputStream(this, 
Project.MSG_INFO));
+                configuration.setOut(new 
ReportConfiguration.IODescriptor<>("RAT output", () -> new 
LogOutputStream(this, Project.MSG_INFO)));
             }
             DocumentName name = 
DocumentName.builder(getProject().getBaseDir()).build();
             configuration.addSource(new ResourceCollectionContainer(name, 
configuration, nestedResources));
@@ -444,7 +444,7 @@ public class Report extends BaseAntTask {
     public void execute() {
         try {
             Reporter r = new Reporter(validate(getConfiguration()));
-            r.output(StyleSheets.PLAIN.getStyleSheet(), () -> 
CloseShieldOutputStream.wrap(System.out));
+            r.output(StyleSheets.PLAIN.getStyleSheet().ioSupplier(), () -> 
CloseShieldOutputStream.wrap(System.out));
             r.output();
         } catch (BuildException e) {
             throw e;
diff --git 
a/apache-rat-tools/src/main/java/org/apache/rat/tools/xsd/XsdGenerator.java 
b/apache-rat-tools/src/main/java/org/apache/rat/tools/xsd/XsdGenerator.java
index b3860c12..eafcc4e4 100644
--- a/apache-rat-tools/src/main/java/org/apache/rat/tools/xsd/XsdGenerator.java
+++ b/apache-rat-tools/src/main/java/org/apache/rat/tools/xsd/XsdGenerator.java
@@ -70,7 +70,7 @@ public class XsdGenerator {
         XsdGenerator generator = new XsdGenerator();
 
         try (InputStream in = generator.getInputStream();
-             InputStream styleIn = StyleSheets.XML.getStyleSheet().get()) {
+             InputStream styleIn = 
StyleSheets.XML.getStyleSheet().ioSupplier().get()) {
             StandardXmlFactory.createTransformer(styleIn).transform(new 
StreamSource(in),
                     new StreamResult(new OutputStreamWriter(System.out, 
StandardCharsets.UTF_8)));
         }
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 120786a1..6d37b83a 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-568" type="add" dev="claudenw">
+        Internal change to prepare a better UI testing: create and utilize an 
IODescriptor that will have a name associated with the current IOSupplier that 
is in use.
+      </action>
       <action issue="RAT-564" type="add" dev="claudenw">
         Internal change to let LicenseSetFactory return unmodifiable license 
sets.
       </action>


Reply via email to