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 3e6b50cc RAT-564: make LicenseSetFactory return unmodifiable licenses 
sets (#687)
3e6b50cc is described below

commit 3e6b50ccf2c4edb571873669220f2aa5a3839258
Author: Claude Warren <[email protected]>
AuthorDate: Sat Jul 4 23:32:42 2026 +0200

    RAT-564: make LicenseSetFactory return unmodifiable licenses sets (#687)
    
    * make LicenseSetFactory return unmodifiable licenses sets
    
    * added factory tests
    
    
    ---------
    
    Co-authored-by: P. Ottlinger <[email protected]>
    Co-authored-by: P. Ottlinger <[email protected]>
---
 .../java/org/apache/rat/ReportConfiguration.java   | 45 ++++++------
 .../org/apache/rat/analysis/AnalyserFactory.java   | 33 ++++-----
 .../rat/analysis/DocumentHeaderAnalyser.java       |  8 +--
 .../org/apache/rat/analysis/HeaderCheckWorker.java | 27 ++++---
 .../org/apache/rat/license/LicenseSetFactory.java  | 84 +++++++++++++++-------
 .../java/org/apache/rat/license/SimpleLicense.java | 10 +--
 .../src/test/java/org/apache/rat/DefaultsTest.java | 13 ++--
 .../apache/rat/analysis/HeaderCheckWorkerTest.java | 24 +++++--
 .../apache/rat/license/LicenseSetFactoryTest.java  | 77 ++++++++++++++++++++
 .../apache/rat/documentation/velocity/RatTool.java |  9 ++-
 src/changes/changes.xml                            |  3 +
 11 files changed, 227 insertions(+), 106 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 787bb3dc..8fc5835d 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
@@ -38,6 +38,7 @@ import java.util.Objects;
 import java.util.SortedSet;
 import java.util.function.Consumer;
 
+import org.apache.commons.collections4.set.UnmodifiableSortedSet;
 import org.apache.commons.io.function.IOSupplier;
 import org.apache.commons.io.output.CloseShieldOutputStream;
 import org.apache.rat.analysis.IHeaderMatcher;
@@ -84,7 +85,6 @@ public class ReportConfiguration {
          */
         private final String description;
 
-
         Processing(final String description) {
             this.description = description;
         }
@@ -162,6 +162,7 @@ public class ReportConfiguration {
      * The ClaimValidator to validate min/max counts and similar claims.
      */
     private final ClaimValidator claimValidator;
+
     /**
      * Constructor
      */
@@ -245,7 +246,7 @@ public class ReportConfiguration {
 
     /**
      * Retrieves the archive processing type.
-     * @return The archive processing type.
+     * @return the archive processing type.
      */
     public Processing getArchiveProcessing() {
         return archiveProcessing == null ? Defaults.ARCHIVE_PROCESSING : 
archiveProcessing;
@@ -261,7 +262,7 @@ public class ReportConfiguration {
 
     /**
      * Retrieves the archive processing type.
-     * @return The archive processing type.
+     * @return the archive processing type.
      */
     public Processing getStandardProcessing() {
         return standardProcessing == null ? Defaults.STANDARD_PROCESSING : 
standardProcessing;
@@ -278,7 +279,7 @@ public class ReportConfiguration {
     /**
      * Set the log level for reporting collisions in the set of license 
families.
      * <p>NOTE: should be set before licenses or license families are 
added.</p>
-     * @param level The log level to use.
+     * @param level the log level to use.
      */
     public void logFamilyCollisions(final Level level) {
         licenseSetFactory.logFamilyCollisions(level);
@@ -286,7 +287,7 @@ public class ReportConfiguration {
 
     /**
      * Sets the reporting option for duplicate license families.
-     * @param state The ReportingSet.Option to use for reporting.
+     * @param state the ReportingSet.Option to use for reporting.
      */
     public void familyDuplicateOption(final ReportingSet.Options state) {
         licenseSetFactory.familyDuplicateOption(state);
@@ -294,7 +295,7 @@ public class ReportConfiguration {
 
     /**
      * Sets the log level for reporting license collisions.
-     * @param level The log level.
+     * @param level the log level.
      */
     public void logLicenseCollisions(final Level level) {
         licenseSetFactory.logLicenseCollisions(level);
@@ -420,7 +421,7 @@ public class ReportConfiguration {
     /**
      * Add file patterns that are to be included. These patterns override any 
exclusion of
      * the same files.
-     * @param patterns The iterable of Strings containing the patterns.
+     * @param patterns the iterable of Strings containing the patterns.
      */
     public void addIncludedPatterns(final Iterable<String> patterns) {
         exclusionProcessor.addIncludedPatterns(patterns);
@@ -458,7 +459,7 @@ public class ReportConfiguration {
      * Adds the licenses and approved licenses from the defaults object to the
      * configuration. <em>Side effect:</em> if the report should be styled and 
no
      * style sheet has been set the plain stylesheet from the defaults will be 
used.
-     * @param defaults The defaults to set.
+     * @param defaults the defaults to set.
      */
     public void setFrom(final Defaults defaults) {
         licenseSetFactory.add(defaults.getLicenseSetFactory());
@@ -506,7 +507,7 @@ public class ReportConfiguration {
      * times to provide the stream. Suppliers should prepare streams that are
      * appended to and that can be closed. If an {@code OutputStream} should 
not be
      * closed consider wrapping it in a {@code CloseShieldOutputStream}
-     * @param out The OutputStream supplier that provides the output stream to 
write
+     * @param out the OutputStream supplier that provides the output stream to 
write
      * the report to. A null value will use System.out.
      * @see CloseShieldOutputStream
      */
@@ -519,7 +520,7 @@ public class ReportConfiguration {
      * 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.
+     * @param file the file to create the supplier with.
      */
     public void setOut(final File file) {
         Objects.requireNonNull(file, "output file should not be null");
@@ -540,7 +541,7 @@ public class ReportConfiguration {
     /**
      * Returns the output stream supplier. If no stream has been set returns a
      * supplier for System.out.
-     * @return The supplier of the output stream to write the report to.
+     * @return the supplier of the output stream to write the report to.
      */
     public IOSupplier<OutputStream> getOutput() {
         return out == null ? () -> CloseShieldOutputStream.wrap(System.out) : 
out;
@@ -548,7 +549,7 @@ public class ReportConfiguration {
 
     /**
      * Gets a PrintWriter that wraps the output stream.
-     * @return A supplier for a PrintWriter that wraps the output stream.
+     * @return a supplier for a PrintWriter that wraps the output stream.
      * @see #getOutput()
      */
     public IOSupplier<PrintWriter> getWriter() {
@@ -558,7 +559,7 @@ public class ReportConfiguration {
     /**
      * Adds a license to the list of licenses. Does not add the license to the 
list
      * of approved licenses.
-     * @param license The license to add to the list of licenses.
+     * @param license the license to add to the list of licenses.
      */
     public void addLicense(final ILicense license) {
         licenseSetFactory.addLicense(license);
@@ -567,7 +568,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.
+     * @param builder the license builder to build and add to the list of 
licenses.
      * @return The ILicense implementation that was added.
      */
     public ILicense addLicense(final ILicense.Builder builder) {
@@ -577,7 +578,7 @@ public class ReportConfiguration {
     /**
      * Adds multiple licenses to the list of licenses. Does not add the 
licenses to
      * the list of approved licenses.
-     * @param licenses The licenses to add.
+     * @param licenses the licenses to add.
      */
     public void addLicenses(final Collection<ILicense> licenses) {
         licenseSetFactory.addLicenses(licenses);
@@ -586,7 +587,7 @@ public class ReportConfiguration {
     /**
      * Adds a license family to the list of families. Does not add the family 
to the
      * list of approved licenses.
-     * @param family The license family to add to the list of license families.
+     * @param family the license family to add to the list of license families.
      */
     public void addFamily(final ILicenseFamily family) {
        licenseSetFactory.addFamily(family);
@@ -595,7 +596,7 @@ public class ReportConfiguration {
     /**
      * Adds a license family to the list of families. Does not add the family 
to the
      * list of approved licenses.
-     * @param builder The licenseFamily.Builder to build and add to the list of
+     * @param builder the licenseFamily.Builder to build and add to the list of
      * licenses.
      */
     public void addFamily(final ILicenseFamily.Builder builder) {
@@ -605,7 +606,7 @@ public class ReportConfiguration {
     /**
      * Adds multiple families to the list of license families. Does not add the
      * licenses to the list of approved licenses.
-     * @param families The license families to add.
+     * @param families the license families to add.
      */
     public void addFamilies(final Collection<ILicenseFamily> families) {
         families.forEach(this::addApprovedLicenseCategory);
@@ -658,7 +659,7 @@ public class ReportConfiguration {
     /**
      * Gets the SortedSet of approved license categories. <em>Once a license 
has
      * been removed from the approved list it cannot be re-added</em>
-     * @param filter The LicenseFilter to filter the categories by.
+     * @param filter the LicenseFilter to filter the categories by.
      * @return the Sorted set of approved license categories.
      */
     public SortedSet<String> getLicenseCategories(final LicenseFilter filter) {
@@ -668,17 +669,17 @@ public class ReportConfiguration {
     /**
      * Gets the SortedSet of approved license categories. <em>Once a license 
has
      * been removed from the approved list it cannot be re-added</em>
-     * @param filter The LicenseFilter to filter the licenses by.
+     * @param filter the LicenseFilter to filter the licenses by.
      * @return the Sorted set of approved license categories.
      */
-    public SortedSet<ILicense> getLicenses(final LicenseFilter filter) {
+    public UnmodifiableSortedSet<ILicense> getLicenses(final LicenseFilter 
filter) {
         return licenseSetFactory.getLicenses(filter);
     }
 
     /**
      * Gets the SortedSet of approved license categories. <em>Once a license 
has
      * been removed from the approved list it cannot be re-added</em>
-     * @param filter The LicenseFilter to filter the licenses by.
+     * @param filter the LicenseFilter to filter the licenses by.
      * @return the Sorted set of approved license categories.
      */
     public SortedSet<String> getLicenseIds(final LicenseFilter filter) {
diff --git 
a/apache-rat-core/src/main/java/org/apache/rat/analysis/AnalyserFactory.java 
b/apache-rat-core/src/main/java/org/apache/rat/analysis/AnalyserFactory.java
index f2eca190..0d4abc09 100644
--- a/apache-rat-core/src/main/java/org/apache/rat/analysis/AnalyserFactory.java
+++ b/apache-rat-core/src/main/java/org/apache/rat/analysis/AnalyserFactory.java
@@ -18,10 +18,9 @@
  */
 package org.apache.rat.analysis;
 
-import java.util.Collection;
-import java.util.Set;
 import java.util.function.Predicate;
 
+import org.apache.commons.collections4.set.UnmodifiableSortedSet;
 import org.apache.rat.ConfigurationException;
 import org.apache.rat.Defaults;
 import org.apache.rat.ReportConfiguration;
@@ -50,7 +49,7 @@ public final class AnalyserFactory {
      *     Note you probably do not want this as it is automatically added to 
{@link #createConfiguredAnalyser}.
      * </p>
      * @param approvalPredicate the predicate to approve licenses.
-     * @return A document analyser that sets the approvalPredicate in document 
metadata.
+     * @return a document analyser that sets the approvalPredicate in document 
metadata.
      */
     public static DocumentAnalyser createPolicy(final Predicate<ILicense> 
approvalPredicate) {
         return document -> {
@@ -75,12 +74,12 @@ public final class AnalyserFactory {
 
     /**
      * Creates a DocumentAnalyser from the report configuration.
-     * @param configuration the ReportConfiguration
-     * @return A document analyser that uses the provided licenses.
+     * @param configuration the ReportConfiguration.
+     * @return a document analyser that uses the provided licenses.
      */
     public static DocumentAnalyser createConfiguredAnalyser(final 
ReportConfiguration configuration) {
         LicenseSetFactory licenseSetFactory = 
configuration.getLicenseSetFactory();
-        Set<ILicense> licenses = 
licenseSetFactory.getLicenses(LicenseSetFactory.LicenseFilter.ALL);
+        UnmodifiableSortedSet<ILicense> licenses = 
licenseSetFactory.getLicenses(LicenseSetFactory.LicenseFilter.ALL);
         if (licenses.isEmpty()) {
             throw new ConfigurationException("At least one license must be 
defined");
         }
@@ -98,18 +97,18 @@ public final class AnalyserFactory {
     private static final class DefaultAnalyser implements DocumentAnalyser {
 
         /** The licenses to analyze */
-        private final Collection<ILicense> licenses;
-        /** the Report Configuration */
+        private final UnmodifiableSortedSet<ILicense> licenses;
+        /** The report configuration */
         private final ReportConfiguration configuration;
         /** The matcher for generated files */
         private final IHeaderMatcher generatedMatcher;
 
         /**
          * Constructs a DocumentAnalyser for the specified license.
-         * @param config the ReportConfiguration
-         * @param licenses The licenses to analyse
+         * @param config the ReportConfiguration.
+         * @param licenses the licenses to analyse.
          */
-        DefaultAnalyser(final ReportConfiguration config, final 
Collection<ILicense> licenses) {
+        DefaultAnalyser(final ReportConfiguration config, final 
UnmodifiableSortedSet<ILicense> licenses) {
             this.licenses = licenses;
             this.configuration = config;
             this.generatedMatcher = configuration.getGeneratedMatcher();
@@ -118,15 +117,13 @@ public final class AnalyserFactory {
         /**
          * Generates a predicate to filter out licenses that should not be 
reported.
          * @param proc the processing status to filter.
-         * @return a Predicate to do the filtering.
+         * @return a predicate to do the filtering.
          */
         private Predicate<ILicense> licenseFilter(final 
ReportConfiguration.Processing proc)  {
-            return license -> {
-                return switch (proc) {
-                    case PRESENCE -> 
!license.getLicenseFamily().equals(UnknownLicense.INSTANCE.getLicenseFamily());
-                    case ABSENCE -> true;
-                    default -> false;
-                };
+            return license -> switch (proc) {
+                case PRESENCE -> 
!license.getLicenseFamily().equals(UnknownLicense.INSTANCE.getLicenseFamily());
+                case ABSENCE -> true;
+                default -> false;
             };
         }
 
diff --git 
a/apache-rat-core/src/main/java/org/apache/rat/analysis/DocumentHeaderAnalyser.java
 
b/apache-rat-core/src/main/java/org/apache/rat/analysis/DocumentHeaderAnalyser.java
index cec174f6..a87d18fe 100644
--- 
a/apache-rat-core/src/main/java/org/apache/rat/analysis/DocumentHeaderAnalyser.java
+++ 
b/apache-rat-core/src/main/java/org/apache/rat/analysis/DocumentHeaderAnalyser.java
@@ -20,8 +20,8 @@ package org.apache.rat.analysis;
 
 import java.io.IOException;
 import java.io.Reader;
-import java.util.Collection;
 
+import org.apache.commons.collections4.set.UnmodifiableSortedSet;
 import org.apache.rat.api.Document;
 import org.apache.rat.document.DocumentAnalyser;
 import org.apache.rat.license.ILicense;
@@ -35,15 +35,15 @@ import static java.lang.String.format;
 class DocumentHeaderAnalyser implements DocumentAnalyser {
 
     /** The license to analyse */
-    private final Collection<ILicense> licenses;
+    private final UnmodifiableSortedSet<ILicense> licenses;
     /** The matcher for generated headers */
     private final IHeaderMatcher generatedMatcher;
 
     /**
      * Constructs the HeaderAnalyser for the specific license.
-     * @param licenses The licenses to analyse
+     * @param licenses the licenses to analyse.
      */
-    DocumentHeaderAnalyser(final IHeaderMatcher generatedMatcher, final 
Collection<ILicense> licenses) {
+    DocumentHeaderAnalyser(final IHeaderMatcher generatedMatcher, final 
UnmodifiableSortedSet<ILicense> licenses) {
         super();
         this.generatedMatcher = generatedMatcher;
         this.licenses = licenses;
diff --git 
a/apache-rat-core/src/main/java/org/apache/rat/analysis/HeaderCheckWorker.java 
b/apache-rat-core/src/main/java/org/apache/rat/analysis/HeaderCheckWorker.java
index 30243bb0..b7aed491 100644
--- 
a/apache-rat-core/src/main/java/org/apache/rat/analysis/HeaderCheckWorker.java
+++ 
b/apache-rat-core/src/main/java/org/apache/rat/analysis/HeaderCheckWorker.java
@@ -21,17 +21,17 @@ package org.apache.rat.analysis;
 import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.Reader;
-import java.util.Collection;
 import java.util.Locale;
 import java.util.Objects;
 
+import org.apache.commons.collections4.set.UnmodifiableSortedSet;
 import org.apache.rat.ConfigurationException;
 import org.apache.rat.analysis.matchers.FullTextMatcher;
 import org.apache.rat.api.Document;
 import org.apache.rat.license.ILicense;
 
 /**
- * Reads from a stream to check license.
+ * Reads from a stream to check for a license.
  * <p>
  * <strong>Note</strong> that this class is not thread safe.
  * </p>
@@ -54,21 +54,20 @@ public final class HeaderCheckWorker {
     /** The BufferedReader used to read the lines */
     private final BufferedReader reader;
     /** The licenses to check for match */
-    private final Collection<ILicense> licenses;
+    private final UnmodifiableSortedSet<ILicense> licenses;
     /** The document being processed */
     private final Document document;
     /**  The matcher for generated headers */
     private final IHeaderMatcher generatedMatcher;
 
-
     /**
      * Read the input and perform the header check.
      * <p>
-     * The number of lines indicates how many lines from the top of the file 
will be read for processing
+     * The number of lines indicates how many lines from the top of the file 
will be read for processing.
      *
-     * @param reader The reader for the document.
+     * @param reader the reader for the document.
      * @param numberOfLines the number of lines to read from the header.
-     * @return The IHeaders instance for the header.
+     * @return the IHeaders instance for the header.
      * @throws IOException on input failure
      */
     public static IHeaders readHeader(final BufferedReader reader, final int 
numberOfLines) throws IOException {
@@ -103,12 +102,12 @@ public final class HeaderCheckWorker {
      * Convenience constructor wraps given <code>Reader</code> in a
      * <code>BufferedReader</code>.
      *
-     * @param generatedMatcher The matcher for generated headers.
-     * @param reader The reader on the document. Not null.
-     * @param licenses The licenses to check against. Not null.
-     * @param name The document that is being checked. Possibly null.
+     * @param generatedMatcher the matcher for generated headers.
+     * @param reader the reader on the document. Not null.
+     * @param licenses the licenses to check against. Not null.
+     * @param name the document that is being checked. Possibly null.
      */
-    public HeaderCheckWorker(final IHeaderMatcher generatedMatcher, final 
Reader reader, final Collection<ILicense> licenses, final Document name) {
+    public HeaderCheckWorker(final IHeaderMatcher generatedMatcher, final 
Reader reader, final UnmodifiableSortedSet<ILicense> licenses, final Document 
name) {
         this(generatedMatcher, reader, 
DEFAULT_NUMBER_OF_RETAINED_HEADER_LINES, licenses, name);
     }
 
@@ -123,7 +122,7 @@ public final class HeaderCheckWorker {
      * @param document The document that is being checked. Possibly null.
      */
     public HeaderCheckWorker(final IHeaderMatcher generatedMatcher, final 
Reader reader,
-                             final int numberOfRetainedHeaderLine, final 
Collection<ILicense> licenses,
+                             final int numberOfRetainedHeaderLine, final 
UnmodifiableSortedSet<ILicense> licenses,
                              final Document document) {
         Objects.requireNonNull(reader, "Reader may not be null");
         Objects.requireNonNull(licenses, "Licenses may not be null");
@@ -140,7 +139,7 @@ public final class HeaderCheckWorker {
     /**
      * Read the input and perform the header check.
      *
-     * @throws RatHeaderAnalysisException on IO exception.
+     * @throws RatHeaderAnalysisException in case of I/O exceptions.
      */
     public void read() throws RatHeaderAnalysisException {
         try {
diff --git 
a/apache-rat-core/src/main/java/org/apache/rat/license/LicenseSetFactory.java 
b/apache-rat-core/src/main/java/org/apache/rat/license/LicenseSetFactory.java
index 6fe00db5..18ca3b41 100644
--- 
a/apache-rat-core/src/main/java/org/apache/rat/license/LicenseSetFactory.java
+++ 
b/apache-rat-core/src/main/java/org/apache/rat/license/LicenseSetFactory.java
@@ -20,13 +20,19 @@ package org.apache.rat.license;
 
 import java.util.Collection;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.Optional;
+import java.util.Set;
 import java.util.SortedSet;
 import java.util.TreeSet;
 import java.util.function.Predicate;
+import java.util.stream.Collectors;
 
+import org.apache.commons.collections4.set.UnmodifiableSortedSet;
+import org.apache.rat.ConfigurationException;
 import org.apache.rat.analysis.IHeaderMatcher;
 import org.apache.rat.analysis.IHeaders;
+import org.apache.rat.utils.DefaultLog;
 import org.apache.rat.utils.Log;
 import org.apache.rat.utils.ReportingSet;
 
@@ -38,7 +44,7 @@ public class LicenseSetFactory {
 
     /**
      * Search a SortedSet of ILicenseFamily instances looking for a matching 
instance.
-     * @param target The instance to search for.
+     * @param target the instance to search for.
      * @param licenseFamilies the license families to search.
      * @return the matching instance of the target given.
      */
@@ -50,7 +56,7 @@ public class LicenseSetFactory {
 
     /**
      * Search a SortedSet of ILicenseFamily instances looking for a matching 
instance.
-     * @param target The instance to search for.
+     * @param target the instance to search for.
      * @param licenseFamilies the license families to search.
      * @return the matching instance of the target given.
      */
@@ -60,7 +66,7 @@ public class LicenseSetFactory {
     }
 
     /**
-     * An enum that defines the types of Licenses to extract.
+     * An enum that defines the types of licenses to extract.
      */
     public enum LicenseFilter {
         /** All defined licenses are returned. */
@@ -121,6 +127,31 @@ public class LicenseSetFactory {
         licenses.forEach(l -> families.addIfNotPresent(l.getLicenseFamily()));
     }
 
+    public void validate() {
+        Log log = DefaultLog.getInstance();
+
+        // verify license definitions exist
+        if (getLicenses(LicenseFilter.ALL).isEmpty()) {
+            String msg = "At least one license must be defined";
+            log.error(msg);
+            throw new ConfigurationException(msg);
+        }
+
+        // verify that all approved license families exist
+        Set<String> exists = getLicenseFamilies(LicenseFilter.ALL)
+                
.stream().map(ILicenseFamily::getFamilyCategory).collect(Collectors.toSet());
+        Set<String> approved = new HashSet<>(approvedLicenseCategories);
+        approved.removeIf(exists::contains);
+        approved.forEach(name -> log.warn(String.format("License category '%s' 
was approved but does not exist.", name)));
+
+        // verify that all approved licenses exist
+        exists = getLicenses(LicenseFilter.ALL)
+                .stream().map(ILicense::getId).collect(Collectors.toSet());
+        approved = new HashSet<>(approvedLicenseIds);
+        approved.removeIf(exists::contains);
+        approved.forEach(name -> log.warn(String.format("License '%s' was 
approved but does not exist.", name)));
+    }
+
     public void add(final LicenseSetFactory other) {
         this.families.addAll(other.families);
         this.licenses.addAll(other.licenses);
@@ -133,7 +164,7 @@ public class LicenseSetFactory {
     /**
      * Set the log level for reporting collisions in the set of license 
families.
      * <p>NOTE: should be set before licenses or license families are 
added.</p>
-     * @param level The log level to use.
+     * @param level the log level to use.
      */
     public void logFamilyCollisions(final Log.Level level) {
         families.setLogLevel(level);
@@ -141,7 +172,7 @@ public class LicenseSetFactory {
 
     /**
      * Sets the reporting option for duplicate license families.
-     * @param state The ReportingSet.Option to use for reporting.
+     * @param state the ReportingSet.Option to use for reporting.
      */
     public void familyDuplicateOption(final ReportingSet.Options state) {
         families.setDuplicateOption(state);
@@ -149,7 +180,7 @@ public class LicenseSetFactory {
 
     /**
      * Sets the log level for reporting license collisions.
-     * @param level The log level.
+     * @param level the log level.
      */
     public void logLicenseCollisions(final Log.Level level) {
         licenses.setLogLevel(level);
@@ -177,7 +208,7 @@ public class LicenseSetFactory {
     /**
      * Adds a license to the list of licenses. Does not add the license to the 
list
      * of approved licenses.
-     * @param license The license to add to the list of licenses.
+     * @param license the license to add to the list of licenses.
      */
     public void addLicense(final ILicense license) {
         if (license != null) {
@@ -189,8 +220,8 @@ public class LicenseSetFactory {
     /**
      * 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.
+     * @param builder the license builder to build and add to the list of 
licenses.
+     * @return the ILicense implementation that was added.
      */
     public ILicense addLicense(final ILicense.Builder builder) {
         if (builder != null) {
@@ -204,7 +235,7 @@ public class LicenseSetFactory {
     /**
      * Adds multiple licenses to the list of licenses. Does not add the 
licenses to
      * the list of approved licenses.
-     * @param licenses The licenses to add.
+     * @param licenses the licenses to add.
      */
     public void addLicenses(final Collection<ILicense> licenses) {
         this.licenses.addAll(licenses);
@@ -214,7 +245,7 @@ public class LicenseSetFactory {
     /**
      * Adds a license family to the list of families. Does not add the family 
to the
      * list of approved licenses.
-     * @param family The license family to add to the list of license families.
+     * @param family the license family to add to the list of license families.
      */
     public void addFamily(final ILicenseFamily family) {
         if (family != null) {
@@ -225,7 +256,7 @@ public class LicenseSetFactory {
     /**
      * Adds a license family to the list of families. Does not add the family 
to the
      * list of approved licenses.
-     * @param builder The licenseFamily.Builder to build and add to the list of
+     * @param builder the licenseFamily.Builder to build and add to the list of
      * licenses.
      */
     public void addFamily(final ILicenseFamily.Builder builder) {
@@ -235,7 +266,7 @@ public class LicenseSetFactory {
     }
 
     /**
-     * Adds a license family category (id) to the list of approved licenses
+     * Adds a license family category (id) to the list of approved licenses.
      * @param familyCategory the category to add.
      */
     public void approveLicenseCategory(final String familyCategory) {
@@ -243,8 +274,8 @@ public class LicenseSetFactory {
     }
 
     /**
-     * Adds a license family category (id) to the list of approved licenses
-     * @param familyCategory the category to add.
+     * Removes a license family category (id) from the list of approved 
licenses.
+     * @param familyCategory the category to remove.
      */
     public void removeLicenseCategory(final String familyCategory) {
         
removedLicenseCategories.add(ILicenseFamily.makeCategory(familyCategory));
@@ -269,7 +300,7 @@ public class LicenseSetFactory {
     /**
      * Test for approved family category.
      * @param family the license family to test, must be in category format.
-     * @return return {@code true} if the category is approved.
+     * @return {@code true} if the category is approved.
      */
     private boolean isApprovedCategory(final ILicenseFamily family) {
         return approvedLicenseCategories.contains(family.getFamilyCategory()) 
&& !removedLicenseCategories.contains(family.getFamilyCategory());
@@ -289,18 +320,21 @@ public class LicenseSetFactory {
      * @param filter the types of LicenseFamily objects to return.
      * @return a SortedSet of ILicense objects.
      */
-    public SortedSet<ILicense> getLicenses(final LicenseFilter filter) {
+    public UnmodifiableSortedSet<ILicense> getLicenses(final LicenseFilter 
filter) {
+        SortedSet<ILicense> result;
         switch (filter) {
         case ALL:
-            return Collections.unmodifiableSortedSet(licenses);
+            result = licenses;
+            break;
         case APPROVED:
-            SortedSet<ILicense> result = new TreeSet<>();
+            result = new TreeSet<>();
             
licenses.stream().filter(getApprovedLicensePredicate()).forEach(result::add);
-            return result;
+            break;
         case NONE:
         default:
-            return Collections.emptySortedSet();
+            result = Collections.emptySortedSet();
         }
+        return (UnmodifiableSortedSet<ILicense>) 
UnmodifiableSortedSet.unmodifiableSortedSet(result);
     }
 
     /**
@@ -329,7 +363,7 @@ public class LicenseSetFactory {
      * Gets the License ids based on the filter.
      *
      * @param filter the types of License Ids to return.
-     * @return The list of all licenses in the category regardless of whether 
or not it is used by an ILicense implementation.
+     * @return a set of all licenses in the category regardless of whether it 
is used by an ILicense implementation.
      */
     public SortedSet<String> getLicenseCategories(final LicenseFilter filter) {
         SortedSet<String> result = new TreeSet<>();
@@ -354,7 +388,7 @@ public class LicenseSetFactory {
      * Gets the License ids based on the filter.
      *
      * @param filter the types of License Ids to return.
-     * @return The list of all licenses in the category regardless of whether 
or not it is used by an ILicense implementation.
+     * @return a set of all licenses in the category regardless of whether it 
is used by an ILicense implementation.
      */
     public SortedSet<String> getLicenseIds(final LicenseFilter filter) {
         Predicate<ILicense> approved =  l -> 
(isApprovedCategory(l.getLicenseFamily()) ||
@@ -390,7 +424,6 @@ public class LicenseSetFactory {
         ILicenseFamily searchFamily = 
ILicenseFamily.builder().setLicenseFamilyCategory(familyId)
                 .setLicenseFamilyName("searching proxy").build();
         ILicense target = new ILicense() {
-
             @Override
             public String getId() {
                 return licenseId;
@@ -449,6 +482,9 @@ public class LicenseSetFactory {
      * @return the matching license or {@code null} if not found.
      */
     public static Optional<ILicense> search(final ILicense target, final 
SortedSet<ILicense> licenses) {
+        if (licenses == null) {
+            return Optional.empty();
+        }
         SortedSet<ILicense> part = licenses.tailSet(target);
         return Optional.ofNullable((!part.isEmpty() && 
part.first().compareTo(target) == 0) ? part.first() : null);
     }
diff --git 
a/apache-rat-core/src/main/java/org/apache/rat/license/SimpleLicense.java 
b/apache-rat-core/src/main/java/org/apache/rat/license/SimpleLicense.java
index e4286ab9..3e7d699b 100644
--- a/apache-rat-core/src/main/java/org/apache/rat/license/SimpleLicense.java
+++ b/apache-rat-core/src/main/java/org/apache/rat/license/SimpleLicense.java
@@ -49,7 +49,7 @@ public class SimpleLicense implements ILicense {
     /** The name of this license */
     @ConfigComponent(type = ComponentType.PARAMETER, desc = "The name of this 
license.")
     private final String name;
-    /** The ID for this license.  Must be unique */
+    /** The ID for this license. Must be unique. */
     @ConfigComponent(type = ComponentType.PARAMETER, desc = "The ID for this 
license.")
     private final String id;
 
@@ -171,7 +171,7 @@ public class SimpleLicense implements ILicense {
         /**
          * Sets the ID of the license. If the ID is not set then the ID of the 
license
          * family is used.
-         * @param id the ID for the license
+         * @param id the ID for the license.
          * @return this builder for chaining.
          */
         @Override
@@ -197,7 +197,7 @@ public class SimpleLicense implements ILicense {
         /**
          * Sets the name of the license. If the name is not set then the name 
of the
          * license family is used.
-         * @param name the name for the license
+         * @param name the name for the license.
          * @return this builder for chaining.
          */
         @Override
@@ -218,7 +218,7 @@ public class SimpleLicense implements ILicense {
                 throw new ConfigurationException("'matcher' must not be null");
             }
             if (licenseFamilies == null) {
-                throw new ImplementationException("'licenseFamilies' must not 
be null");
+                throw new ImplementationException("License builder 
'licenseFamilies' must not be null");
             }
             if (StringUtils.isBlank(familyCategory)) {
                 throw new ImplementationException("License 'family' must be 
specified");
@@ -226,7 +226,7 @@ public class SimpleLicense implements ILicense {
 
             String familyCat = ILicenseFamily.makeCategory(familyCategory);
             Optional<ILicenseFamily> family = 
licenseFamilies.stream().filter(f ->  
f.getFamilyCategory().equals(familyCat)).findFirst();
-            if (!family.isPresent()) {
+            if (family.isEmpty()) {
                 throw new ConfigurationException(String.format("License family 
'%s' not found.", familyCategory));
             }
 
diff --git a/apache-rat-core/src/test/java/org/apache/rat/DefaultsTest.java 
b/apache-rat-core/src/test/java/org/apache/rat/DefaultsTest.java
index bfd5f31e..884e9d76 100644
--- a/apache-rat-core/src/test/java/org/apache/rat/DefaultsTest.java
+++ b/apache-rat-core/src/test/java/org/apache/rat/DefaultsTest.java
@@ -19,29 +19,28 @@
 package org.apache.rat;
 
 import java.util.Arrays;
-import java.util.Set;
 import java.util.TreeSet;
 
+import org.apache.commons.collections4.set.UnmodifiableSortedSet;
 import org.apache.rat.license.ILicense;
 import org.apache.rat.license.LicenseSetFactory.LicenseFilter;
 import org.junit.jupiter.api.Test;
 
 import static org.assertj.core.api.Assertions.assertThat;
 
-
-public class DefaultsTest {
+class DefaultsTest {
     private static final String[] FAMILIES = { "BSD-3", "GPL  ", "AL   ", 
"OASIS", "W3CD ", "W3C  ", "MIT  ", "CDDL1" };
 
     @Test
-    public void defaultConfigTest() {
+    void defaultConfigTest() {
         Defaults defaults = Defaults.builder().build();
 
-        Set<ILicense> licenses = 
defaults.getLicenseSetFactory().getLicenses(LicenseFilter.ALL);
+        UnmodifiableSortedSet<ILicense> licenses = 
defaults.getLicenseSetFactory().getLicenses(LicenseFilter.ALL);
 
-        Set<String> names = new TreeSet<>();
+        TreeSet<String> names = new TreeSet<>();
         licenses.forEach(x -> 
names.add(x.getLicenseFamily().getFamilyCategory()));
         assertThat(names).hasSize(FAMILIES.length);
-        names.removeAll(Arrays.asList(FAMILIES));
+        Arrays.asList(FAMILIES).forEach(names::remove);
         assertThat(names).isEmpty();
     }
 }
diff --git 
a/apache-rat-core/src/test/java/org/apache/rat/analysis/HeaderCheckWorkerTest.java
 
b/apache-rat-core/src/test/java/org/apache/rat/analysis/HeaderCheckWorkerTest.java
index b00aec08..9583f47e 100644
--- 
a/apache-rat-core/src/test/java/org/apache/rat/analysis/HeaderCheckWorkerTest.java
+++ 
b/apache-rat-core/src/test/java/org/apache/rat/analysis/HeaderCheckWorkerTest.java
@@ -21,8 +21,11 @@ package org.apache.rat.analysis;
 
 import java.io.StringReader;
 
-import java.util.Collections;
+import java.util.List;
+import java.util.SortedSet;
+import java.util.TreeSet;
 
+import org.apache.commons.collections4.set.UnmodifiableSortedSet;
 import org.apache.rat.Defaults;
 import org.apache.rat.api.Document;
 import org.apache.rat.configuration.builders.AnyBuilder;
@@ -31,29 +34,36 @@ import org.apache.rat.testhelpers.TestingDocument;
 import org.apache.rat.license.ILicense;
 import org.apache.rat.testhelpers.TestingLicense;
 import org.apache.rat.testhelpers.TestingMatcher;
-import org.assertj.core.util.Lists;
 import org.junit.jupiter.api.Test;
 
 import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
 
-public class HeaderCheckWorkerTest {
+class HeaderCheckWorkerTest {
+
+    /**
+     * Create an unmodifiable sorted set from members
+     */
+    private UnmodifiableSortedSet<ILicense> asLicenses(ILicense... licenses) {
+        SortedSet<ILicense> inner = new TreeSet<>(List.of(licenses));
+        return (UnmodifiableSortedSet<ILicense>) 
UnmodifiableSortedSet.unmodifiableSortedSet(inner);
+    }
 
     @Test
-    public void emptyInputIsUnknownTest() throws RatHeaderAnalysisException {
+    void emptyInputIsUnknownTest() throws RatHeaderAnalysisException {
         final Document subject = new TestingDocument("subject");
         
subject.getMetaData().setApprovalPredicate(Defaults.builder().build().getLicenseSetFactory().getApprovedLicensePredicate());
         ILicense matcher = new TestingLicense("test", "test");
-        HeaderCheckWorker worker = new HeaderCheckWorker(new TestingMatcher(), 
new StringReader(""), Lists.list(matcher), subject);
+        HeaderCheckWorker worker = new HeaderCheckWorker(new TestingMatcher(), 
new StringReader(""), asLicenses(matcher), subject);
         worker.read();
         
assertThat(subject.getMetaData().unapprovedLicenses().count()).isEqualTo(1);
         
assertThat(subject.getMetaData().unapprovedLicenses().toList().get(0).getLicenseFamily()).isEqualTo(ILicenseFamily.UNKNOWN);
     }
 
     @Test
-    public void generatedFileDetectionTest() throws Exception {
+    void generatedFileDetectionTest() throws Exception {
         final Document subject = new TestingDocument(new 
StringReader("Generated from configure.ac by autoheader"), "subject");
         IHeaderMatcher matcher = new 
AnyBuilder().setResource("/org/apache/rat/generation-keywords.txt").build();
-        HeaderCheckWorker worker = new HeaderCheckWorker(matcher, 
subject.reader(), Collections.emptyList(), subject);
+        HeaderCheckWorker worker = new HeaderCheckWorker(matcher, 
subject.reader(), asLicenses(), subject);
         worker.read();
         
assertThat(subject.getMetaData().getDocumentType()).isEqualTo(Document.Type.IGNORED);
     }
diff --git 
a/apache-rat-core/src/test/java/org/apache/rat/license/LicenseSetFactoryTest.java
 
b/apache-rat-core/src/test/java/org/apache/rat/license/LicenseSetFactoryTest.java
index 13bf945b..ba0bf80c 100644
--- 
a/apache-rat-core/src/test/java/org/apache/rat/license/LicenseSetFactoryTest.java
+++ 
b/apache-rat-core/src/test/java/org/apache/rat/license/LicenseSetFactoryTest.java
@@ -18,7 +18,12 @@
      */
 package org.apache.rat.license;
 
+import java.util.Arrays;
+import java.util.Optional;
 import java.util.SortedSet;
+import java.util.TreeSet;
+
+import org.apache.rat.ConfigurationException;
 import org.apache.rat.Defaults;
 import org.apache.rat.analysis.UnknownLicense;
 import org.apache.rat.testhelpers.TestingLicense;
@@ -27,6 +32,8 @@ import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatNoException;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
 public class LicenseSetFactoryTest {
 
@@ -165,4 +172,74 @@ public class LicenseSetFactoryTest {
                 .as("Found unapproved id " + licenseId)
                 .isFalse();
     }
+
+    @Test
+    void familySearchTest() {
+        SortedSet<ILicenseFamily> families = new 
TreeSet<>(Arrays.asList(APPROVED_FAMILIES));
+        ILicenseFamily actual = 
LicenseSetFactory.familySearch(APPROVED_FAMILIES[0], families);
+        assertThat(actual).isEqualTo(APPROVED_FAMILIES[0]);
+        actual = 
LicenseSetFactory.familySearch(APPROVED_FAMILIES[2].getFamilyCategory(), 
families);
+        assertThat(actual).isEqualTo(APPROVED_FAMILIES[2]);
+        actual = LicenseSetFactory.familySearch("not a real category", 
families);
+        assertThat(actual).isNull();
+        ILicenseFamily family = 
ILicenseFamily.builder().setLicenseFamilyCategory("***").setLicenseFamilyName("testing").build();
+        actual = LicenseSetFactory.familySearch(family, families);
+        assertThat(actual).isNull();
+    }
+
+    @Test
+    void validateTest() {
+        LicenseSetFactory underTest = new LicenseSetFactory();
+
+        assertThatThrownBy(underTest::validate)
+                .hasMessageContaining("At least one license must be defined")
+                .isInstanceOf(ConfigurationException.class);
+
+        // set up builder and add a license
+        SortedSet<ILicenseFamily> families = new TreeSet<>();
+        
families.add(ILicenseFamily.builder().setLicenseFamilyCategory("test").setLicenseFamilyName("testing
 family").build());
+        ILicense.Builder builder = 
ILicense.builder().setLicenseFamilies(families);
+        underTest.addLicense(builder.setFamily("test").setMatcher(new 
TestingMatcher())
+                .setName("test1").build());
+
+        assertThatNoException().isThrownBy(underTest::validate);
+    }
+
+    @Test
+    void addNullLicense() {
+        LicenseSetFactory underTest = new LicenseSetFactory();
+        
assertThat(underTest.getLicenses(LicenseSetFactory.LicenseFilter.ALL)).isEmpty();
+        underTest.addLicense((ILicense) null);
+        
assertThat(underTest.getLicenses(LicenseSetFactory.LicenseFilter.ALL)).isEmpty();
+        ILicense result = underTest.addLicense((ILicense.Builder) null);
+        
assertThat(underTest.getLicenses(LicenseSetFactory.LicenseFilter.ALL)).isEmpty();
+        assertThat(result).isNull();
+    }
+
+    @Test
+    void addNullFamily() {
+        LicenseSetFactory underTest = new LicenseSetFactory();
+        
assertThat(underTest.getLicenseFamilies(LicenseSetFactory.LicenseFilter.ALL)).isEmpty();
+        underTest.addFamily((ILicenseFamily) null);
+        
assertThat(underTest.getLicenseFamilies(LicenseSetFactory.LicenseFilter.ALL)).isEmpty();
+        underTest.addFamily((ILicenseFamily.Builder) null);
+        
assertThat(underTest.getLicenseFamilies(LicenseSetFactory.LicenseFilter.ALL)).isEmpty();
+    }
+
+    @Test
+    void searchTest() {
+        assertThat(LicenseSetFactory.search("theFamily", "TheLicense", 
null)).isEmpty();
+
+        assertThat(LicenseSetFactory.search("theFamily", "TheLicense", new 
TreeSet<>())).isEmpty();
+
+        SortedSet<ILicense> set = 
licenseSetFactory.getLicenses(LicenseSetFactory.LicenseFilter.ALL);
+        
assertThat(LicenseSetFactory.search(APPROVED_FAMILIES[0].getFamilyCategory(), 
"TheLicense", set)).isEmpty();
+
+        Optional<ILicense> optLicense = LicenseSetFactory.search("AL", 
"AL2.0", set);
+        assertThat(optLicense).isNotEmpty();
+        ILicense license = optLicense.get();
+        
assertThat(license.getLicenseFamily().getFamilyCategory()).isEqualTo(ILicenseFamily.makeCategory("AL"));
+        assertThat(license.getId()).isEqualTo("AL2.0");
+        assertThat(license.getName()).isEqualTo("Apache License 2.0");
+    }
 }
diff --git 
a/apache-rat-tools/src/main/java/org/apache/rat/documentation/velocity/RatTool.java
 
b/apache-rat-tools/src/main/java/org/apache/rat/documentation/velocity/RatTool.java
index 820e246e..ca1adadf 100644
--- 
a/apache-rat-tools/src/main/java/org/apache/rat/documentation/velocity/RatTool.java
+++ 
b/apache-rat-tools/src/main/java/org/apache/rat/documentation/velocity/RatTool.java
@@ -24,11 +24,11 @@ import java.util.Comparator;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.SortedSet;
 import java.util.TreeSet;
 import java.util.stream.Collectors;
 
 import org.apache.commons.cli.Option;
+import org.apache.commons.collections4.set.UnmodifiableSortedSet;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.rat.CLIOption;
 import org.apache.rat.CLIOptionCollection;
@@ -187,9 +187,8 @@ public class RatTool {
      * @return the set of Matchers.
      */
     public Set<Matcher> matchers() {
-        MatcherBuilderTracker tracker = MatcherBuilderTracker.instance();
         Set<Matcher> documentationSet = new 
TreeSet<>(Comparator.comparing(Matcher::getName));
-        for (Class<?> clazz : tracker.getClasses()) {
+        for (Class<?> clazz : MatcherBuilderTracker.instance().getClasses()) {
             Description desc = DescriptionBuilder.buildMap(clazz);
             documentationSet.add(new Matcher(desc, null));
         }
@@ -246,7 +245,7 @@ public class RatTool {
      * @return a list of license property descriptions.
      */
     public List<Description> licenseProperties() {
-        SortedSet<ILicense> licenses = 
licenseSetFactory.getLicenses(LicenseSetFactory.LicenseFilter.ALL);
+        UnmodifiableSortedSet<ILicense> licenses = 
licenseSetFactory.getLicenses(LicenseSetFactory.LicenseFilter.ALL);
         Description licenseDescription = 
DescriptionBuilder.build(licenses.first());
         List<Description> descriptions = new 
ArrayList<>(licenseDescription.filterChildren(d -> d.getType() == 
ComponentType.PARAMETER));
         descriptions.sort(Comparator.comparing(Description::getCommonName));
@@ -258,7 +257,7 @@ public class RatTool {
      * @return the list of defined licenses.
      */
     public List<License> licenses() {
-        Set<ILicense> licenses = 
licenseSetFactory.getLicenses(LicenseSetFactory.LicenseFilter.ALL);
+        UnmodifiableSortedSet<ILicense> licenses = 
licenseSetFactory.getLicenses(LicenseSetFactory.LicenseFilter.ALL);
         return 
licenses.stream().map(License::new).collect(Collectors.toList());
     }
 
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index bd96c2fc..120786a1 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-564" type="add" dev="claudenw">
+        Internal change to let LicenseSetFactory return unmodifiable license 
sets.
+      </action>
       <action issue="RAT-563" type="add" dev="claudenw">
         Internal change: rename IReportable and IReportableListWalker to 
clarify interface structure, when doing RAT scans.
       </action>


Reply via email to