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