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;
20  
21  import java.io.File;
22  import java.io.FileOutputStream;
23  import java.io.FilenameFilter;
24  import java.io.IOException;
25  import java.io.InputStream;
26  import java.io.OutputStream;
27  import java.io.OutputStreamWriter;
28  import java.io.PrintWriter;
29  import java.net.MalformedURLException;
30  import java.net.URI;
31  import java.net.URL;
32  import java.nio.charset.StandardCharsets;
33  import java.nio.file.Files;
34  import java.util.Collection;
35  import java.util.Collections;
36  import java.util.Objects;
37  import java.util.SortedSet;
38  import java.util.TreeSet;
39  import java.util.function.Consumer;
40  
41  import org.apache.commons.io.filefilter.FalseFileFilter;
42  import org.apache.commons.io.filefilter.IOFileFilter;
43  import org.apache.commons.io.function.IOSupplier;
44  import org.apache.rat.config.AddLicenseHeaders;
45  import org.apache.rat.license.ILicense;
46  import org.apache.rat.license.ILicenseFamily;
47  import org.apache.rat.license.LicenseFamilySetFactory;
48  import org.apache.rat.license.LicenseSetFactory;
49  import org.apache.rat.license.LicenseSetFactory.LicenseFilter;
50  import org.apache.rat.report.IReportable;
51  import org.apache.rat.utils.Log;
52  import org.apache.rat.utils.ReportingSet;
53  import org.apache.rat.walker.NameBasedHiddenFileFilter;
54  
55  /**
56   * A configuration object is used by the front end to invoke the
57   * {@link Reporter}. The sole purpose of the front ends is to create the
58   * configuration and invoke the {@link Reporter}.
59   */
60  public class ReportConfiguration {
61      private final ReportingSet<ILicenseFamily> families;
62      private final ReportingSet<ILicense> licenses;
63      private final SortedSet<String> approvedLicenseCategories;
64      private final SortedSet<String> removedLicenseCategories;
65      private boolean addingLicenses;
66      private boolean addingLicensesForced;
67      private String copyrightMessage;
68      private IOSupplier<OutputStream> out;
69      private boolean styleReport;
70      private IOSupplier<InputStream> styleSheet;
71      private IReportable reportable;
72      private FilenameFilter inputFileFilter;
73      private IOFileFilter directoryFilter;
74      private Log log;
75  
76     
77      /**
78       * Constructor
79       * @param log The Log implementation that messages will be written to.
80       */
81      public ReportConfiguration(Log log) {
82          this.log = log;
83          families = new ReportingSet<>(LicenseFamilySetFactory.emptyLicenseFamilySet()).setLog(log)
84                  .setMsgFormat( s -> String.format("Duplicate LicenseFamily category: %s",  s.getFamilyCategory()));
85          licenses = new ReportingSet<>(LicenseSetFactory.emptyLicenseSet()).setLog(log)
86                  .setMsgFormat( s -> String.format( "Duplicate License %s (%s) of type %s", s.getName(), s.getId(), s.getLicenseFamily().getFamilyCategory()));
87          approvedLicenseCategories = new TreeSet<>();
88          removedLicenseCategories = new TreeSet<>();
89          directoryFilter = NameBasedHiddenFileFilter.HIDDEN;
90          styleReport = true;
91      }
92      
93      /**
94       * Retrieves the Log that was provided in the constructor.
95       * @return the Log for the system.
96       */
97      public Log getLog() {
98          return log;
99      }
100     /**
101      * Set the log level for reporting collisions in the set of license families.
102      * <p>NOTE: should be set before licenses or license families are added.</p>
103      * @param level The log level to use.
104      */
105     public void logFamilyCollisions(Log.Level level) {
106         families.setLogLevel(level);
107     }
108     
109     /**
110      * Sets the reporting option for duplicate license families.
111      * @param state The ReportingSet.Option to use for reporting.
112      */
113     public void familyDuplicateOption(ReportingSet.Options state) {
114         families.setDuplicateOption(state);
115     }
116 
117     /**
118      * Sets the log level for reporting license collisions.
119      * @param level The log level.
120      */
121     public void logLicenseCollisions(Log.Level level) {
122         licenses.setLogLevel(level);
123     }
124     
125     /**
126      * Sets the reporting option for duplicate licenses.
127      * @param state the ReportingSt.Option to use for reporting.
128      */
129     public void licenseDuplicateOption(ReportingSet.Options state) {
130         licenses.setDuplicateOption(state);
131     }
132     
133     /**
134      * @return The filename filter for the potential input files.
135      */
136     public FilenameFilter getInputFileFilter() {
137         return inputFileFilter;
138     }
139 
140     /**
141      * @param inputFileFilter the filename filter to filter the input files.
142      */
143     public void setInputFileFilter(FilenameFilter inputFileFilter) {
144         this.inputFileFilter = inputFileFilter;
145     }
146 
147     public IOFileFilter getDirectoryFilter() {
148         return directoryFilter;
149     }
150 
151     public void setDirectoryFilter(IOFileFilter directoryFilter) {
152         if (directoryFilter == null) {
153             this.directoryFilter = FalseFileFilter.FALSE;
154         } else {
155             this.directoryFilter = directoryFilter;
156         }
157     }
158 
159     public void addDirectoryFilter(IOFileFilter directoryFilter) {
160         this.directoryFilter = this.directoryFilter.and(directoryFilter);
161     }
162 
163     /**
164      * @return the thing being reported on.
165      */
166     public IReportable getReportable() {
167         return reportable;
168     }
169 
170     /**
171      * @param reportable the thing being reported on.
172      */
173     public void setReportable(IReportable reportable) {
174         this.reportable = reportable;
175     }
176 
177     /**
178      * @return the Supplier of the InputStream that is the XSLT style sheet to style
179      * the report with.
180      */
181     public IOSupplier<InputStream> getStyleSheet() {
182         return styleSheet;
183     }
184 
185     /**
186      * Sets the style sheet for custom processing. The IOSupplier may be called
187      * multiple times, so the input stream must be able to be opened and closed
188      * multiple times.
189      * 
190      * @param styleSheet the XSLT style sheet to style the report with.
191      */
192     public void setStyleSheet(IOSupplier<InputStream> styleSheet) {
193         this.styleSheet = styleSheet;
194     }
195 
196     /**
197      * Adds the licenses and approved licenses from the defaults object to the
198      * configuration. <em>Side effect: </em> if the report should be styled and no
199      * style sheet has been set the plain stylesheet from the defaults will be used.
200      * 
201      * @param defaults The defaults to set.
202      */
203     public void setFrom(Defaults defaults) {
204         addLicensesIfNotPresent(defaults.getLicenses(LicenseFilter.all));
205         addApprovedLicenseCategories(defaults.getLicenseIds(LicenseFilter.approved));
206         if (isStyleReport() && getStyleSheet() == null) {
207             setStyleSheet(Defaults.getPlainStyleSheet());
208         }
209     }
210 
211     /**
212      * 
213      * @param styleSheet the XSLT style sheet to style the report with.
214      */
215     public void setStyleSheet(File styleSheet) {
216         Objects.requireNonNull(styleSheet, "styleSheet file should not be null");
217         setStyleSheet(styleSheet.toURI());
218     }
219 
220     /**
221      * Sets the style sheet for custom processing. The stylesheet may be opened
222      * multiple times so the URI must be capable of being opened multiple times.
223      * 
224      * @param styleSheet the URI of the XSLT style sheet to style the report with.
225      */
226     public void setStyleSheet(URI styleSheet) {
227         Objects.requireNonNull(styleSheet, "styleSheet file should not be null");
228         try {
229             setStyleSheet(styleSheet.toURL());
230         } catch (MalformedURLException e) {
231             throw new ConfigurationException("Unable to process stylesheet", e);
232         }
233     }
234 
235     /**
236      * Sets the style sheet for custom processing. The stylesheet may be opened
237      * multiple times so the URL must be capable of being opened multiple times.
238      * 
239      * @param styleSheet the URL of the XSLT style sheet to style the report with.
240      */
241     public void setStyleSheet(URL styleSheet) {
242         Objects.requireNonNull(styleSheet, "styleSheet file should not be null");
243         setStyleSheet(styleSheet::openStream);
244     }
245 
246     /**
247      * @return {@code true} if the XML report should be styled.
248      */
249     public boolean isStyleReport() {
250         return styleReport;
251     }
252 
253     /**
254      * @param styleReport specifies whether the XML report should be styled.
255      */
256     public void setStyleReport(boolean styleReport) {
257         this.styleReport = styleReport;
258     }
259 
260     /**
261      * Sets the supplier for the output stream. The supplier may be called multiple
262      * times to provide the stream. Suppliers should prepare streams that are
263      * appended to and that can be closed. If an {@code OutputStream} should not be
264      * closed consider wrapping it in a {@code NoCloseOutputStream}
265      * 
266      * @param out The OutputStream supplier that provides the output stream to write
267      * the report to. A null value will use System.out.
268      * @see NoCloseOutputStream
269      */
270     public void setOut(IOSupplier<OutputStream> out) {
271         this.out = out;
272     }
273 
274     /**
275      * Sets the OutputStream supplier to use the specified file. The file may be
276      * opened and closed several times. File is deleted first and then may be
277      * repeatedly opened in append mode.
278      * 
279      * @see #setOut(IOSupplier)
280      * @param file The file to create the supplier with.
281      */
282     public void setOut(File file) {
283         Objects.requireNonNull(file, "output file should not be null");
284         if (file.exists()) {
285             try {
286                 Files.delete(file.toPath());
287             } catch (IOException e) {
288                 log.warn("Unable to delete file:"+file);
289             }
290         }
291         setOut(() -> new FileOutputStream(file, true));
292     }
293 
294     /**
295      * Returns the output stream supplier. If no stream has been set returns a
296      * supplier for System.out.
297      * 
298      * @return The supplier of the output stream to write the report to.
299      */
300     public IOSupplier<OutputStream> getOutput() {
301         return out == null ? () -> new NoCloseOutputStream(System.out) : out;
302     }
303 
304     /**
305      * @return A supplier for a PrintWriter that wraps the output stream.
306      * @see #getOutput()
307      */
308     public IOSupplier<PrintWriter> getWriter() {
309         return () -> new PrintWriter(new OutputStreamWriter(getOutput().get(), StandardCharsets.UTF_8));
310     }
311 
312     /**
313      * Adds a license to the list of licenses. Does not add the license to the list
314      * of approved licenses.
315      * 
316      * @param license The license to add to the list of licenses.
317      */
318     public void addLicense(ILicense license) {
319         if (license != null) {
320             this.licenses.add(license);
321             this.families.addIfNotPresent(license.getLicenseFamily());
322         }
323     }
324 
325     /**
326      * Adds a license to the list of licenses. Does not add the license to the list
327      * of approved licenses.
328      * 
329      * @param builder The license builder to build and add to the list of licenses.
330      */
331     public ILicense addLicense(ILicense.Builder builder) {
332         if (builder != null) {
333             ILicense license = builder.build(families);
334             this.licenses.add(license);
335             return license;
336         }
337         return null;
338     }
339 
340     /**
341      * Adds multiple licenses to the list of licenses. Does not add the licenses to
342      * the list of approved licenses.
343      *
344      * @param licenses The licenses to add.
345      */
346     public void addLicenses(Collection<ILicense> licenses) {
347         this.licenses.addAll(licenses);
348         licenses.stream().map(ILicense::getLicenseFamily).forEach(families::add);
349     }
350 
351     /**
352      * Adds multiple licenses to the list of licenses. Does not add the licenses to
353      * the list of approved licenses.
354      *
355      * @param licenses The licenses to add.
356      */
357     public void addLicensesIfNotPresent(Collection<ILicense> licenses) {
358         this.licenses.addAllIfNotPresent(licenses);
359         licenses.stream().map(ILicense::getLicenseFamily).forEach(families::addIfNotPresent);
360     }
361     
362     /**
363      * Adds a license family to the list of families. Does not add the family to the
364      * list of approved licenses.
365      * 
366      * @param family The license family to add to the list of license families.
367      */
368     public void addFamily(ILicenseFamily family) {
369         if (family != null) {
370             this.families.add(family);
371         }
372     }
373 
374     /**
375      * Adds a license family to the list of families. Does not add the family to the
376      * list of approved licenses.
377      * 
378      * @param builder The licenseFamily.Builder to build and add to the list of
379      * licenses.
380      */
381     public void addFamily(ILicenseFamily.Builder builder) {
382         if (builder != null) {
383             this.families.add(builder.build());
384         }
385     }
386 
387     /**
388      * Adds multiple families to the list of license families. Does not add the
389      * licenses to the list of approved licenses.
390      *
391      * @param families The license families to add.
392      */
393     public void addFamilies(Collection<ILicenseFamily> families) {
394         this.families.addAll(families);
395     }
396 
397     /**
398      * Adds an ILicenseFamily to the list of approved licenses.
399      *
400      * @param approvedILicenseFamily the LicenseFamily to add.
401      */
402     public void addApprovedLicenseCategory(ILicenseFamily approvedILicenseFamily) {
403         approvedLicenseCategories.add(approvedILicenseFamily.getFamilyCategory());
404     }
405 
406     /**
407      * Adds a license family category (id) to the list of approved licenses
408      * 
409      * @param familyCategory the category to add.
410      */
411     public void addApprovedLicenseCategory(String familyCategory) {
412         approvedLicenseCategories.add(ILicenseFamily.makeCategory(familyCategory));
413     }
414 
415     /**
416      * Adds a collection of license family categories to the set of approved license
417      * names.
418      *
419      * @param approvedLicenseCategories set of approved license categories.
420      */
421     public void addApprovedLicenseCategories(Collection<String> approvedLicenseCategories) {
422         approvedLicenseCategories.forEach(this::addApprovedLicenseCategory);
423     }
424 
425     /**
426      * Adds a license family category to the list of approved licenses. <em>Once a
427      * license has been removed from the approved list it cannot be re-added</em>
428      * 
429      * @param familyCategory the category to add.
430      */
431     public void removeApprovedLicenseCategory(String familyCategory) {
432         removedLicenseCategories.add(ILicenseFamily.makeCategory(familyCategory));
433     }
434 
435     /**
436      * Removes a license family category from the list of approved licenses.
437      * <em>Once a license has been removed from the approved list it cannot be
438      * re-added</em>
439      * 
440      * @param familyCategory the family category to remove.
441      */
442     public void removeApprovedLicenseCategories(Collection<String> familyCategory) {
443         familyCategory.forEach(this::removeApprovedLicenseCategory);
444     }
445 
446     /**
447      * Gets the SortedSet of approved license categories. <em>Once a license has
448      * been removed from the approved list it cannot be re-added</em>
449      * 
450      * @return the Sorted set of approved license categories.
451      */
452     public SortedSet<String> getApprovedLicenseCategories() {
453         SortedSet<String> result = new TreeSet<>(approvedLicenseCategories);
454         result.removeAll(removedLicenseCategories);
455         return result;
456     }
457 
458     /**
459      * Returns the optional license copyright being added if RAT is adding headers.
460      * This value is ignored, if no license headers are added.
461      * 
462      * @return the optional copyright message.
463      * @see #isAddingLicenses()
464      */
465     public String getCopyrightMessage() {
466         return copyrightMessage;
467     }
468 
469     /**
470      * Sets the optional copyright message used if RAT is adding license headers.
471      * This value is ignored, if no license headers are added.
472      *
473      * @param copyrightMessage message to set.
474      * @see #isAddingLicenses()
475      */
476     public void setCopyrightMessage(String copyrightMessage) {
477         this.copyrightMessage = copyrightMessage;
478     }
479 
480     /**
481      * This value is ignored if RAT is not adding licenses.
482      * 
483      * @return {@code true} if RAT is forcing the adding license headers.
484      * @see #isAddingLicenses()
485      */
486     public boolean isAddingLicensesForced() {
487         return addingLicensesForced;
488     }
489 
490     /**
491      * @return whether RAT should add missing license headers.
492      * @see #isAddingLicensesForced()
493      * @see #getCopyrightMessage()
494      */
495     public boolean isAddingLicenses() {
496         return addingLicenses;
497     }
498 
499     /**
500      * Sets whether RAT should enable, disable, or force the adding of license
501      * headers.
502      * 
503      * @param addLicenseHeaders enables/disables or forces adding of licenses
504      * headers.
505      * @see #isAddingLicenses()
506      * @see #setCopyrightMessage(String)
507      */
508     public void setAddLicenseHeaders(AddLicenseHeaders addLicenseHeaders) {
509         addingLicenses = false;
510         addingLicensesForced = false;
511         switch (addLicenseHeaders) {
512         case FALSE:
513             // do nothing
514             break;
515         case FORCED:
516             addingLicensesForced = true;
517             // fall through
518         case TRUE:
519             addingLicenses = true;
520             break;
521         }
522     }
523 
524     /**
525      * Gets a set Licenses of depending on the {@code filter} if filter is set:
526      * <ul>
527      * <li>{@code all} - All licenses will be returned.</li>
528      * <li>{@code approved} - Only approved licenses will be returned</li>
529      * <li>{@code none} - No licenses will be returned</li>
530      * </ul>
531      * 
532      * @param filter The license filter.
533      * @return The set of defined licenses.
534      */
535     public SortedSet<ILicense> getLicenses(LicenseFilter filter) {
536         switch (filter) {
537         case all:
538             return Collections.unmodifiableSortedSet(licenses);
539         case approved:
540             return new LicenseSetFactory(licenses, getApprovedLicenseCategories()).getLicenses(filter);
541         case none:
542         default:
543             return LicenseSetFactory.emptyLicenseSet();
544         }
545     }
546 
547     /**
548      * Gets a sorted set of ILicenseFamily objects based on {@code filter}. if
549      * filter is set:
550      * <ul>
551      * <li>{@code all} - All licenses families will be returned.</li>
552      * <li>{@code approved} - Only approved license families will be returned</li>
553      * <li>{@code none} - No license families will be returned</li>
554      * </ul>
555      * 
556      * @param filter The license filter.
557      * @return The set of defined licenses.
558      */
559     public SortedSet<ILicenseFamily> getLicenseFamilies(LicenseFilter filter) {
560         return new LicenseFamilySetFactory(families, getApprovedLicenseCategories()).getFamilies(filter);
561     }
562 
563     /**
564      * Validates that the configuration is valid.
565      * 
566      * @param logger String consumer to log warning messages to.
567      */
568     public void validate(Consumer<String> logger) {
569         if (reportable == null) {
570             throw new ConfigurationException("Reportable may not be null");
571         }
572         if (licenses.isEmpty()) {
573             throw new ConfigurationException("You must specify at least one license");
574         }
575         if (styleSheet != null && !isStyleReport()) {
576             logger.accept("Ignoring stylesheet because styling is not selected");
577         }
578         if (styleSheet == null && isStyleReport()) {
579             throw new ConfigurationException("Stylesheet must be specified if report styling is selected");
580         }
581     }
582 
583     /**
584      * A wrapper around an output stream that does not close the output stream.
585      */
586     public static class NoCloseOutputStream extends OutputStream {
587         private final OutputStream delegate;
588 
589         public NoCloseOutputStream(OutputStream delegate) {
590             this.delegate = delegate;
591         }
592 
593         @Override
594         public void write(int arg0) throws IOException {
595             delegate.write(arg0);
596         }
597 
598         @Override
599         public void close() throws IOException {
600             this.delegate.flush();
601         }
602 
603         @Override
604         public boolean equals(Object obj) {
605             return delegate.equals(obj);
606         }
607 
608         @Override
609         public void flush() throws IOException {
610             delegate.flush();
611         }
612 
613         @Override
614         public int hashCode() {
615             return delegate.hashCode();
616         }
617 
618         @Override
619         public String toString() {
620             return delegate.toString();
621         }
622 
623         @Override
624         public void write(byte[] arg0, int arg1, int arg2) throws IOException {
625             delegate.write(arg0, arg1, arg2);
626         }
627 
628         @Override
629         public void write(byte[] b) throws IOException {
630             delegate.write(b);
631         }
632     }
633 }