View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one   *
3    * or more contributor license agreements.  See the NOTICE file *
4    * distributed with this work for additional information        *
5    * regarding copyright ownership.  The ASF licenses this file   *
6    * to you under the Apache License, Version 2.0 (the            *
7    * "License"); you may not use this file except in compliance   *
8    * with the License.  You may obtain a copy of the License at   *
9    *                                                              *
10   *   http://www.apache.org/licenses/LICENSE-2.0                 *
11   *                                                              *
12   * Unless required by applicable law or agreed to in writing,   *
13   * software distributed under the License is distributed on an  *
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
15   * KIND, either express or implied.  See the License for the    *
16   * specific language governing permissions and limitations      *
17   * under the License.                                           *
18   */
19  package org.apache.rat.license;
20  
21  import java.util.Collection;
22  import java.util.Collections;
23  import java.util.HashSet;
24  import java.util.Optional;
25  import java.util.Set;
26  import java.util.SortedSet;
27  import java.util.TreeSet;
28  import java.util.function.Predicate;
29  import java.util.stream.Collectors;
30  
31  import org.apache.commons.collections4.set.UnmodifiableSortedSet;
32  import org.apache.rat.ConfigurationException;
33  import org.apache.rat.analysis.IHeaderMatcher;
34  import org.apache.rat.analysis.IHeaders;
35  import org.apache.rat.utils.DefaultLog;
36  import org.apache.rat.utils.Log;
37  import org.apache.rat.utils.ReportingSet;
38  
39  /**
40   * Class to take a set of ILicenses and collection of approved license
41   * categories and extract Subsets.
42   */
43  public class LicenseSetFactory {
44  
45      /**
46       * Search a SortedSet of ILicenseFamily instances looking for a matching instance.
47       * @param target the instance to search for.
48       * @param licenseFamilies the license families to search.
49       * @return the matching instance of the target given.
50       */
51      public static ILicenseFamily familySearch(final String target, final SortedSet<ILicenseFamily> licenseFamilies) {
52          ILicenseFamily family = ILicenseFamily.builder().setLicenseFamilyCategory(target).setLicenseFamilyName("Searching family")
53                  .build();
54          return familySearch(family, licenseFamilies);
55      }
56  
57      /**
58       * Search a SortedSet of ILicenseFamily instances looking for a matching instance.
59       * @param target the instance to search for.
60       * @param licenseFamilies the license families to search.
61       * @return the matching instance of the target given.
62       */
63      public static ILicenseFamily familySearch(final ILicenseFamily target, final SortedSet<ILicenseFamily> licenseFamilies) {
64          SortedSet<ILicenseFamily> part = licenseFamilies.tailSet(target);
65          return (!part.isEmpty() && part.first().compareTo(target) == 0) ? part.first() : null;
66      }
67  
68      /**
69       * An enum that defines the types of licenses to extract.
70       */
71      public enum LicenseFilter {
72          /** All defined licenses are returned. */
73          ALL,
74          /** Only approved licenses are returned. */
75          APPROVED,
76          /** No licenses are returned. */
77          NONE
78      }
79  
80      /** The set of defined families. */
81      private final ReportingSet<ILicenseFamily> families;
82      /** The set of defined licenses */
83      private final ReportingSet<ILicense> licenses;
84  
85      /** The set of approved license family categories. If the category is not listed, the family is not approved. */
86      private final SortedSet<String> approvedLicenseCategories;
87      /**
88       * The set of license categories that are to be removed from consideration. These are categories that were
89       * added but should now be removed.
90       */
91      private final SortedSet<String> removedLicenseCategories;
92      /**
93       * The set of approved license ids.  This set contains the set of licenses that are explicitly approved even if
94       * the family is not.
95       */
96      private final SortedSet<String> approvedLicenseIds;
97      /**
98       * The set of license ids that are to be removed from consideration. This set contains licenses that are to be
99       * removed even if the family is approved or if an earlier license approval was granted.
100      */
101     private final SortedSet<String> removedLicenseIds;
102 
103     /**
104      * Constructs a factory with the specified set of Licenses and the approved
105      * license collection.
106      */
107     public LicenseSetFactory() {
108         families = new ReportingSet<>(new TreeSet<ILicenseFamily>())
109                 .setMsgFormat(s -> String.format("Duplicate LicenseFamily category: %s", s.getFamilyCategory()));
110         licenses = new ReportingSet<>(new TreeSet<ILicense>())
111                 .setMsgFormat(s -> String.format("Duplicate License %s (%s) of type %s", s.getName(), s.getId(), s.getLicenseFamily().getFamilyCategory()));
112 
113         approvedLicenseCategories = new TreeSet<>();
114         removedLicenseCategories = new TreeSet<>();
115         approvedLicenseIds = new TreeSet<>();
116         removedLicenseIds = new TreeSet<>();
117     }
118 
119     /**
120      * Constructs a factory with the specified set of Licenses and the approved
121      * license collection.
122      * @param licenses the set of defined licenses. Families will be extracted from the licenses.
123      */
124     public LicenseSetFactory(final SortedSet<ILicense> licenses) {
125         this();
126         this.licenses.addAll(licenses);
127         licenses.forEach(l -> families.addIfNotPresent(l.getLicenseFamily()));
128     }
129 
130     public void validate() {
131         Log log = DefaultLog.getInstance();
132 
133         // verify license definitions exist
134         if (getLicenses(LicenseFilter.ALL).isEmpty()) {
135             String msg = "At least one license must be defined";
136             log.error(msg);
137             throw new ConfigurationException(msg);
138         }
139 
140         // verify that all approved license families exist
141         Set<String> exists = getLicenseFamilies(LicenseFilter.ALL)
142                 .stream().map(ILicenseFamily::getFamilyCategory).collect(Collectors.toSet());
143         Set<String> approved = new HashSet<>(approvedLicenseCategories);
144         approved.removeIf(exists::contains);
145         approved.forEach(name -> log.warn(String.format("License category '%s' was approved but does not exist.", name)));
146 
147         // verify that all approved licenses exist
148         exists = getLicenses(LicenseFilter.ALL)
149                 .stream().map(ILicense::getId).collect(Collectors.toSet());
150         approved = new HashSet<>(approvedLicenseIds);
151         approved.removeIf(exists::contains);
152         approved.forEach(name -> log.warn(String.format("License '%s' was approved but does not exist.", name)));
153     }
154 
155     public void add(final LicenseSetFactory other) {
156         this.families.addAll(other.families);
157         this.licenses.addAll(other.licenses);
158         this.approvedLicenseCategories.addAll(other.approvedLicenseCategories);
159         this.removedLicenseCategories.addAll(other.removedLicenseCategories);
160         this.approvedLicenseIds.addAll(other.approvedLicenseIds);
161         this.removedLicenseIds.addAll(other.removedLicenseIds);
162     }
163 
164     /**
165      * Set the log level for reporting collisions in the set of license families.
166      * <p>NOTE: should be set before licenses or license families are added.</p>
167      * @param level the log level to use.
168      */
169     public void logFamilyCollisions(final Log.Level level) {
170         families.setLogLevel(level);
171     }
172 
173     /**
174      * Sets the reporting option for duplicate license families.
175      * @param state the ReportingSet.Option to use for reporting.
176      */
177     public void familyDuplicateOption(final ReportingSet.Options state) {
178         families.setDuplicateOption(state);
179     }
180 
181     /**
182      * Sets the log level for reporting license collisions.
183      * @param level the log level.
184      */
185     public void logLicenseCollisions(final Log.Level level) {
186         licenses.setLogLevel(level);
187     }
188 
189     /**
190      * Sets the reporting option for duplicate licenses.
191      * @param state the ReportingSt.Option to use for reporting.
192      */
193     public void licenseDuplicateOption(final ReportingSet.Options state) {
194         licenses.setDuplicateOption(state);
195     }
196 
197     /**
198      * Create a sorted set of licenses families from the collection.
199      * @param licenses the collection of all licenses.
200      * @return a SortedSet of license families from the collection.
201      */
202     private static SortedSet<ILicenseFamily> extractFamily(final Collection<ILicense> licenses) {
203         SortedSet<ILicenseFamily> result = new TreeSet<>();
204         licenses.stream().map(ILicense::getLicenseFamily).forEach(result::add);
205         return result;
206     }
207 
208     /**
209      * Adds a license to the list of licenses. Does not add the license to the list
210      * of approved licenses.
211      * @param license the license to add to the list of licenses.
212      */
213     public void addLicense(final ILicense license) {
214         if (license != null) {
215             this.licenses.add(license);
216             this.families.addIfNotPresent(license.getLicenseFamily());
217         }
218     }
219 
220     /**
221      * Adds a license to the list of licenses. Does not add the license to the list
222      * of approved licenses.
223      * @param builder the license builder to build and add to the list of licenses.
224      * @return the ILicense implementation that was added.
225      */
226     public ILicense addLicense(final ILicense.Builder builder) {
227         if (builder != null) {
228             ILicense license = builder.setLicenseFamilies(families).build();
229             this.licenses.add(license);
230             return license;
231         }
232         return null;
233     }
234 
235     /**
236      * Adds multiple licenses to the list of licenses. Does not add the licenses to
237      * the list of approved licenses.
238      * @param licenses the licenses to add.
239      */
240     public void addLicenses(final Collection<ILicense> licenses) {
241         this.licenses.addAll(licenses);
242         licenses.stream().map(ILicense::getLicenseFamily).forEach(families::add);
243     }
244 
245     /**
246      * Adds a license family to the list of families. Does not add the family to the
247      * list of approved licenses.
248      * @param family the license family to add to the list of license families.
249      */
250     public void addFamily(final ILicenseFamily family) {
251         if (family != null) {
252             this.families.add(family);
253         }
254     }
255 
256     /**
257      * Adds a license family to the list of families. Does not add the family to the
258      * list of approved licenses.
259      * @param builder the licenseFamily.Builder to build and add to the list of
260      * licenses.
261      */
262     public void addFamily(final ILicenseFamily.Builder builder) {
263         if (builder != null) {
264             this.families.add(builder.build());
265         }
266     }
267 
268     /**
269      * Adds a license family category (id) to the list of approved licenses.
270      * @param familyCategory the category to add.
271      */
272     public void approveLicenseCategory(final String familyCategory) {
273         approvedLicenseCategories.add(ILicenseFamily.makeCategory(familyCategory));
274     }
275 
276     /**
277      * Removes a license family category (id) from the list of approved licenses.
278      * @param familyCategory the category to remove.
279      */
280     public void removeLicenseCategory(final String familyCategory) {
281         removedLicenseCategories.add(ILicenseFamily.makeCategory(familyCategory));
282     }
283 
284     /**
285      * Adds a license family category (id) to the list of approved licenses
286      * @param licenseId the license ID to add.
287      */
288     public void approveLicenseId(final String licenseId) {
289         approvedLicenseIds.add(licenseId);
290     }
291 
292     /**
293      * Removes a license ID from the list of approved licenses.
294      * @param licenseId the license ID to remove.
295      */
296     public void removeLicenseId(final String licenseId) {
297         removedLicenseIds.add(licenseId);
298     }
299 
300     /**
301      * Test for approved family category.
302      * @param family the license family to test, must be in category format.
303      * @return {@code true} if the category is approved.
304      */
305     private boolean isApprovedCategory(final ILicenseFamily family) {
306         return approvedLicenseCategories.contains(family.getFamilyCategory()) && !removedLicenseCategories.contains(family.getFamilyCategory());
307     }
308 
309     /**
310      * Gets a predicate to filter for approved licenses.
311      * @return a predicate that returns {@code true} if the license is approved.
312      */
313     public Predicate<ILicense> getApprovedLicensePredicate() {
314         return lic -> !removedLicenseIds.contains(lic.getId()) && (approvedLicenseIds.contains(lic.getId()) ||
315                 isApprovedCategory(lic.getLicenseFamily()));
316     }
317 
318     /**
319      * Gets the License objects based on the filter.
320      * @param filter the types of LicenseFamily objects to return.
321      * @return a SortedSet of ILicense objects.
322      */
323     public UnmodifiableSortedSet<ILicense> getLicenses(final LicenseFilter filter) {
324         SortedSet<ILicense> result;
325         switch (filter) {
326         case ALL:
327             result = licenses;
328             break;
329         case APPROVED:
330             result = new TreeSet<>();
331             licenses.stream().filter(getApprovedLicensePredicate()).forEach(result::add);
332             break;
333         case NONE:
334         default:
335             result = Collections.emptySortedSet();
336         }
337         return (UnmodifiableSortedSet<ILicense>) UnmodifiableSortedSet.unmodifiableSortedSet(result);
338     }
339 
340     /**
341      * Gets the LicenseFamily objects based on the filter.
342      * @param filter the types of LicenseFamily objects to return.
343      * @return a SortedSet of ILicenseFamily objects.
344      */
345     public SortedSet<ILicenseFamily> getLicenseFamilies(final LicenseFilter filter) {
346         SortedSet<ILicenseFamily> result;
347         switch (filter) {
348         case ALL:
349             result = extractFamily(licenses);
350             result.addAll(families);
351             return result;
352         case APPROVED:
353             result = new TreeSet<>();
354             licenses.stream().map(ILicense::getLicenseFamily).filter(this::isApprovedCategory).forEach(result::add);
355             return result;
356         case NONE:
357         default:
358             return Collections.emptySortedSet();
359         }
360     }
361 
362     /**
363      * Gets the License ids based on the filter.
364      *
365      * @param filter the types of License Ids to return.
366      * @return a set of all licenses in the category regardless of whether it is used by an ILicense implementation.
367      */
368     public SortedSet<String> getLicenseCategories(final LicenseFilter filter) {
369         SortedSet<String> result = new TreeSet<>();
370         switch (filter) {
371             case ALL:
372                 licenses.forEach(l -> result.add(l.getLicenseFamily().getFamilyCategory()));
373                 families.forEach(f -> result.add(f.getFamilyCategory()));
374                 result.addAll(approvedLicenseCategories);
375                 result.addAll(removedLicenseCategories);
376                 return result;
377             case APPROVED:
378                 approvedLicenseCategories.stream().filter(s -> !removedLicenseCategories.contains(s)).forEach(result::add);
379                 families.stream().filter(this::isApprovedCategory).forEach(f -> result.add(f.getFamilyCategory()));
380                 return result;
381             case NONE:
382             default:
383                 return Collections.emptySortedSet();
384         }
385     }
386 
387     /**
388      * Gets the License ids based on the filter.
389      *
390      * @param filter the types of License Ids to return.
391      * @return a set of all licenses in the category regardless of whether it is used by an ILicense implementation.
392      */
393     public SortedSet<String> getLicenseIds(final LicenseFilter filter) {
394         Predicate<ILicense> approved =  l -> (isApprovedCategory(l.getLicenseFamily()) ||
395                 approvedLicenseIds.contains(l.getId())) && !removedLicenseIds.contains(l.getId());
396         SortedSet<String> result = new TreeSet<>();
397         switch (filter) {
398             case ALL:
399                 licenses.forEach(l -> result.add(l.getId()));
400                 result.addAll(approvedLicenseCategories);
401                 result.addAll(removedLicenseCategories);
402                 result.addAll(approvedLicenseIds);
403                 result.addAll(removedLicenseIds);
404                 return result;
405             case APPROVED:
406                 licenses.stream().filter(approved).forEach(l -> result.add(l.getId()));
407                 families.stream().filter(this::isApprovedCategory).forEach(f -> result.add(f.getFamilyCategory()));
408                 approvedLicenseIds.stream().filter(s -> !removedLicenseIds.contains(s)).forEach(result::add);
409                 return result;
410             case NONE:
411             default:
412                 return Collections.emptySortedSet();
413         }
414     }
415 
416     /**
417      * Search a SortedSet of licenses for the matching license id.
418      *
419      * @param licenseId the id to search for.
420      * @param licenses the SortedSet of licenses to search.
421      * @return the matching license or {@code null} if not found.
422      */
423     public static Optional<ILicense> search(final String familyId, final String licenseId, final SortedSet<ILicense> licenses) {
424         ILicenseFamily searchFamily = ILicenseFamily.builder().setLicenseFamilyCategory(familyId)
425                 .setLicenseFamilyName("searching proxy").build();
426         ILicense target = new ILicense() {
427             @Override
428             public String getId() {
429                 return licenseId;
430             }
431 
432             @Override
433             public void reset() {
434                 // do nothing
435             }
436 
437             @Override
438             public boolean matches(final IHeaders headers) {
439                 return false;
440             }
441 
442             @Override
443             public boolean equals(final Object o) {
444                 return ILicense.equals(this, o);
445             }
446 
447             @Override
448             public int hashCode() {
449                 return ILicense.hash(this);
450             }
451 
452             @Override
453             public ILicenseFamily getLicenseFamily() {
454                 return searchFamily;
455             }
456 
457             @Override
458             public String getNote() {
459                 return null;
460             }
461 
462             @Override
463             public String getName() {
464                 return searchFamily.getFamilyName();
465             }
466 
467             @Override
468             public IHeaderMatcher getMatcher() {
469                 return null;
470             }
471 
472         };
473         return search(target, licenses);
474     }
475 
476     /**
477      * Search a SortedSet of licenses for the matching license.
478      * License must match both family code, and license id.
479      *
480      * @param target the license to search for. Must not be {@code null}.
481      * @param licenses the SortedSet of licenses to search.
482      * @return the matching license or {@code null} if not found.
483      */
484     public static Optional<ILicense> search(final ILicense target, final SortedSet<ILicense> licenses) {
485         if (licenses == null) {
486             return Optional.empty();
487         }
488         SortedSet<ILicense> part = licenses.tailSet(target);
489         return Optional.ofNullable((!part.isEmpty() && part.first().compareTo(target) == 0) ? part.first() : null);
490     }
491 }