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

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

commit 2b0a37897efecf3053fd432f56d9c20582bd267d
Author: Claude Warren <[email protected]>
AuthorDate: Sun Jun 28 09:03:21 2026 +0100

    make LicenseSetFactory return unmodifiable licenses sets
---
 .../java/org/apache/rat/ReportConfiguration.java   |  3 +-
 .../org/apache/rat/analysis/AnalyserFactory.java   |  9 ++---
 .../rat/analysis/DocumentHeaderAnalyser.java       |  6 +--
 .../org/apache/rat/analysis/HeaderCheckWorker.java |  8 ++--
 .../org/apache/rat/license/LicenseSetFactory.java  | 44 +++++++++++++++++++---
 .../src/test/java/org/apache/rat/DefaultsTest.java |  3 +-
 .../apache/rat/analysis/HeaderCheckWorkerTest.java | 17 +++++++--
 .../apache/rat/documentation/velocity/RatTool.java |  9 ++---
 8 files changed, 71 insertions(+), 28 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 799dfaf5..43c26b0f 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;
@@ -671,7 +672,7 @@ public class ReportConfiguration {
      * @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);
     }
 
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..14a7dc84 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;
@@ -80,7 +79,7 @@ public final class AnalyserFactory {
      */
     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,7 +97,7 @@ public final class AnalyserFactory {
     private static final class DefaultAnalyser implements DocumentAnalyser {
 
         /** The licenses to analyze */
-        private final Collection<ILicense> licenses;
+        private final UnmodifiableSortedSet<ILicense> licenses;
         /** the Report Configuration */
         private final ReportConfiguration configuration;
         /** The matcher for generated files */
@@ -109,7 +108,7 @@ public final class AnalyserFactory {
          * @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();
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..3f99b7f8 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,7 +35,7 @@ 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;
 
@@ -43,7 +43,7 @@ class DocumentHeaderAnalyser implements DocumentAnalyser {
      * Constructs the HeaderAnalyser for the specific license.
      * @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..122a5f51 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,10 +21,10 @@ 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;
@@ -54,7 +54,7 @@ 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 */
@@ -108,7 +108,7 @@ public final class HeaderCheckWorker {
      * @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 +123,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");
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..5474501f 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;
 
@@ -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);
@@ -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);
     }
 
     /**
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..db9edb03 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
@@ -22,6 +22,7 @@ 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;
@@ -36,7 +37,7 @@ public class DefaultsTest {
     public 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<>();
         licenses.forEach(x -> 
names.add(x.getLicenseFamily().getFamilyCategory()));
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..02a51085 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,19 +34,25 @@ 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 {
 
+    /** Create an unmodifiable sorted set from members */
+    private UnmodifiableSortedSet<ILicense> asLicenses(ILicense... licenses) {
+        SortedSet<ILicense> inner = new  TreeSet<>();
+        inner.addAll(List.of(licenses));
+        return (UnmodifiableSortedSet<ILicense>) 
UnmodifiableSortedSet.unmodifiableSortedSet(inner);
+    }
+
     @Test
     public 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);
@@ -53,7 +62,7 @@ public class HeaderCheckWorkerTest {
     public 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-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());
     }
 

Reply via email to