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.FileFilter;
23 import java.io.FileInputStream;
24 import java.io.FileOutputStream;
25 import java.io.IOException;
26 import java.io.InputStream;
27 import java.io.OutputStream;
28 import java.io.OutputStreamWriter;
29 import java.io.PrintWriter;
30 import java.net.MalformedURLException;
31 import java.net.URI;
32 import java.net.URL;
33 import java.nio.charset.StandardCharsets;
34 import java.nio.file.Files;
35 import java.util.ArrayList;
36 import java.util.Collection;
37 import java.util.List;
38 import java.util.Map;
39 import java.util.Objects;
40 import java.util.SortedSet;
41 import java.util.function.Consumer;
42 import java.util.stream.Stream;
43
44 import org.apache.commons.collections4.set.UnmodifiableSortedSet;
45 import org.apache.commons.io.function.IOSupplier;
46 import org.apache.commons.io.output.CloseShieldOutputStream;
47 import org.apache.commons.lang3.StringUtils;
48 import org.apache.rat.analysis.IHeaderMatcher;
49 import org.apache.rat.api.RatException;
50 import org.apache.rat.commandline.StyleSheets;
51 import org.apache.rat.config.AddLicenseHeaders;
52 import org.apache.rat.config.exclusion.ExclusionProcessor;
53 import org.apache.rat.config.exclusion.StandardCollection;
54 import org.apache.rat.config.results.ClaimValidator;
55 import org.apache.rat.configuration.XMLConfigurationReader;
56 import org.apache.rat.configuration.builders.AnyBuilder;
57 import org.apache.rat.document.DocumentName;
58 import org.apache.rat.document.DocumentNameMatcher;
59 import org.apache.rat.document.FileDocument;
60 import org.apache.rat.license.ILicense;
61 import org.apache.rat.license.ILicenseFamily;
62 import org.apache.rat.license.LicenseSetFactory;
63 import org.apache.rat.license.LicenseSetFactory.LicenseFilter;
64 import org.apache.rat.report.RatReport;
65 import org.apache.rat.report.Reportable;
66 import org.apache.rat.report.claim.ClaimStatistic;
67 import org.apache.rat.report.xml.writer.XmlWriter;
68 import org.apache.rat.utils.DefaultLog;
69 import org.apache.rat.utils.Log.Level;
70 import org.apache.rat.utils.ReportingSet;
71 import org.apache.rat.utils.StandardXmlFactory;
72 import org.apache.rat.walker.FileListWalker;
73 import org.apache.rat.walker.ReportableListWalker;
74 import org.w3c.dom.Node;
75 import org.xml.sax.SAXException;
76
77 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
78
79 /**
80 * A configuration object is used by the front end to invoke the
81 * {@link Reporter}. The sole purpose of the frontends is to create the
82 * configuration and invoke the {@link Reporter}.
83 */
84 public class ReportConfiguration {
85
86 /** The IODescriptor for {@code System.out}. */
87 public static final IODescriptor<OutputStream> SYSTEM_OUT =
88 // SONAR wants to require logging output, which is the wrong reporting channel for this case.
89 new IODescriptor<>("System.out", () -> CloseShieldOutputStream.wrap(System.out)); // NOSONAR
90
91 /**
92 * The styles of processing for various categories of documents.
93 */
94 public enum Processing {
95 /** List file as present only. */
96 NOTIFICATION("List file as present"),
97 /** List all present licenses. */
98 PRESENCE("List any licenses found"),
99 /** List all present licenses and unknown licenses. */
100 ABSENCE("List licenses found and any unknown licences");
101
102 /**
103 * Description of the processing.
104 */
105 private final String description;
106
107 /**
108 * Constructor.
109 * @param description the description for this processing strategy
110 */
111 Processing(final String description) {
112 this.description = description;
113 }
114
115 /**
116 * Gets the description of the processing type.
117 * @return the description of the processing type.
118 */
119 public String desc() {
120 return description;
121 }
122 }
123
124 /** The LicenseSetFactory for the configuration */
125 private final LicenseSetFactory licenseSetFactory;
126
127 /**
128 * {@code true} if we are adding license headers to the files.
129 */
130 private boolean addingLicenses;
131
132 /**
133 * {@code true} if we are adding license headers in place (no *.new files)
134 */
135 private boolean addingLicensesForced;
136
137 /**
138 * The copyright message to add if we are adding headers. Will be {@code null}
139 * if we are not adding copyright messages.
140 */
141 private String copyrightMessage;
142
143 /**
144 * The IODescriptor that provides the output stream to write the report to.
145 */
146 private IODescriptor<OutputStream> outputDescriptor;
147
148 /**
149 * The IODescriptor that provides the stylesheet to style the XML output.
150 */
151 private IODescriptor<InputStream> styleSheet;
152
153 /**
154 * A list of files to read file names from.
155 */
156 private final List<File> sources;
157
158 /**
159 * A list of reportables to process.
160 */
161 private final List<Reportable> reportables;
162
163 /**
164 * The exclusion processor that determines if a file is included or excluded.
165 */
166 private final ExclusionProcessor exclusionProcessor;
167
168 /**
169 * The default filter for displaying families.
170 */
171 private LicenseFilter listFamilies;
172
173 /**
174 * The default filter for displaying licenses.
175 */
176 private LicenseFilter listLicenses;
177
178 /**
179 * {@code true} if this is a dry run and no processing is to take place.
180 */
181 private boolean dryRun;
182
183 /**
184 * How to process ARCHIVE document types.
185 */
186 private Processing archiveProcessing;
187
188 /**
189 * How to process STANDARD document types.
190 */
191 private Processing standardProcessing;
192
193 /**
194 * The ClaimValidator to validate min/max counts and similar claims.
195 */
196 private final ClaimValidator claimValidator;
197
198 /**
199 * Constructor
200 */
201 public ReportConfiguration() {
202 licenseSetFactory = new LicenseSetFactory();
203 listFamilies = Defaults.LIST_FAMILIES;
204 listLicenses = Defaults.LIST_LICENSES;
205 dryRun = false;
206 exclusionProcessor = new ExclusionProcessor();
207 claimValidator = new ClaimValidator();
208 sources = new ArrayList<>();
209 reportables = new ArrayList<>();
210 }
211
212 /**
213 * A serializer/deserializer for a ReportConfiguration.
214 * @return the serializer/deserializer for a ReportConfiguration.
215 */
216 public SerDes serDes() {
217 return new SerDes();
218 }
219
220 /**
221 * Report the excluded files to the appendable object.
222 * @param appendable the appendable object to write to.
223 */
224 public void reportExclusions(final Appendable appendable) {
225 try {
226 exclusionProcessor.reportExclusions(appendable);
227 } catch (IOException e) {
228 DefaultLog.getInstance().warn("Unable to report exclusions", e);
229 }
230 }
231
232 /**
233 * Adds a file as a source of files to scan.
234 * The file must be a text file that lists files to be included.
235 * File within the file must be in linux format with a
236 * {@code "/"} file separator.
237 * @param file the file to process.
238 * @throws ConfigurationException if the file is {@code null}.
239 */
240 public void addSource(final File file) {
241 notNull(file, "File may not be null.");
242 sources.add(file);
243 }
244
245 /**
246 * Throws ConfigurationException if the object is {@code null}.
247 * @param object the object to test.
248 * @param msg the message to create the exception with.
249 * @throws ConfigurationException if the object is {@code null}.
250 */
251 private void notNull(final Object object, final String msg) {
252 if (object == null) {
253 throw new ConfigurationException(msg);
254 }
255 }
256
257 /**
258 * Adds a Reportable as a source of files to scan.
259 * @param reportable the reportable to process.
260 * @throws ConfigurationException if the reportable is {@code null}.
261 */
262 public void addSource(final Reportable reportable) {
263 notNull(reportable, "Reportable may not be null.");
264 reportables.add(reportable);
265 }
266
267 /**
268 * Returns {@code true} if the configuration has any sources defined.
269 * @return {@code true} if the configuration has any sources defined.
270 */
271 public boolean hasSource() {
272 return !reportables.isEmpty() || !sources.isEmpty();
273 }
274
275 /**
276 * Gets a ReportListWalker.Builder initialized with any files specified as sources.
277 * @return a configured ReportListWalker.Builder.
278 */
279 public ReportableListWalker.Builder getSources() {
280 DocumentName name = DocumentName.builder(new File(".")).build();
281 ReportableListWalker.Builder builder = ReportableListWalker.builder(name);
282 sources.forEach(file -> builder.addReportable(new FileListWalker(new FileDocument(file, DocumentNameMatcher.MATCHES_ALL))));
283 reportables.forEach(builder::addReportable);
284 return builder;
285 }
286
287 // for testing access
288 Iterable<File> sources() {
289 return sources;
290 }
291
292 // for testing access
293 Stream<DocumentName> reportables() {
294 return reportables.stream().map(Reportable::name);
295 }
296
297 /**
298 * Gets the matcher that matches generated text.
299 * @return the matcher that matches generated text.
300 */
301 public IHeaderMatcher getGeneratedMatcher() {
302 return new AnyBuilder().setResource("/org/apache/rat/generation-keywords.txt").build();
303 }
304
305 /**
306 * Retrieves the archive processing type.
307 * @return the archive processing type.
308 */
309 public Processing getArchiveProcessing() {
310 return archiveProcessing == null ? Defaults.ARCHIVE_PROCESSING : archiveProcessing;
311 }
312
313 /**
314 * Sets the archive processing type. If not set will default to NOTIFICATION.
315 * @param archiveProcessing the type of processing archives should have.
316 */
317 public void setArchiveProcessing(final Processing archiveProcessing) {
318 this.archiveProcessing = archiveProcessing;
319 }
320
321 /**
322 * Retrieves the standard processing type.
323 * @return the standard processing type.
324 */
325 public Processing getStandardProcessing() {
326 return standardProcessing == null ? Defaults.STANDARD_PROCESSING : standardProcessing;
327 }
328
329 /**
330 * Sets the standard processing type. If not set will default to NOTIFICATION.
331 * @param standardProcessing the type of processing standard files should have.
332 */
333 public void setStandardProcessing(final Processing standardProcessing) {
334 this.standardProcessing = standardProcessing;
335 }
336
337 /**
338 * Set the log level for reporting collisions in the set of license families.
339 * <p>NOTE: should be set before licenses or license families are added.</p>
340 * @param level the log level to use.
341 */
342 public void logFamilyCollisions(final Level level) {
343 licenseSetFactory.logFamilyCollisions(level);
344 }
345
346 /**
347 * Sets the reporting option for duplicate license families.
348 * @param state the ReportingSet.Option to use for reporting.
349 */
350 public void familyDuplicateOption(final ReportingSet.Options state) {
351 licenseSetFactory.familyDuplicateOption(state);
352 }
353
354 /**
355 * Sets the log level for reporting license collisions.
356 * @param level the log level.
357 */
358 public void logLicenseCollisions(final Level level) {
359 licenseSetFactory.logLicenseCollisions(level);
360 }
361
362 /**
363 * Sets the reporting option for duplicate licenses.
364 * @param state the ReportingSet.Option to use for reporting.
365 */
366 public void licenseDuplicateOption(final ReportingSet.Options state) {
367 licenseSetFactory.licenseDuplicateOption(state);
368 }
369
370 /**
371 * Set the level of license families that should be output in the XML document.
372 * @param filter the license families to list.
373 */
374 public void listFamilies(final LicenseFilter filter) {
375 listFamilies = filter;
376 }
377
378 /**
379 * Return the current filter that determines which families will be output in the XML document.
380 * @return the filter that defines the families to list.
381 */
382 public LicenseFilter listFamilies() {
383 return listFamilies;
384 }
385
386 /**
387 * Set the level of licenses that should be output in the XML document.
388 * @param filter the licenses to list.
389 */
390 public void listLicenses(final LicenseFilter filter) {
391 listLicenses = filter;
392 }
393
394 /**
395 * Gets the selected license filter.
396 * @return the filter to limit license display.
397 */
398 public LicenseFilter listLicenses() {
399 return listLicenses;
400 }
401
402 /**
403 * Sets the dry run flag.
404 * @param state the state for the dry run flag.
405 */
406 public void setDryRun(final boolean state) {
407 dryRun = state;
408 }
409
410 /**
411 * Returns the state of the dry run flag.
412 * @return the state of the dry run flag.
413 */
414 public boolean isDryRun() {
415 return dryRun;
416 }
417
418 /**
419 * Excludes a StandardCollection of patterns.
420 * @param collection the StandardCollection to exclude.
421 * @see ExclusionProcessor#addExcludedCollection(StandardCollection)
422 */
423 public void addExcludedCollection(final StandardCollection collection) {
424 exclusionProcessor.addExcludedCollection(collection);
425 }
426
427 /**
428 * Excludes the file processor defined in the StandardCollection.
429 * @param collection the StandardCollection to exclude.
430 * @see ExclusionProcessor#addFileProcessor(StandardCollection)
431 */
432 public void addExcludedFileProcessor(final StandardCollection collection) {
433 exclusionProcessor.addFileProcessor(collection);
434 }
435
436 /**
437 * Excludes files that match a FileFilter.
438 * @param fileFilter the file filter to match.
439 */
440 public void addExcludedFilter(final FileFilter fileFilter) {
441 exclusionProcessor.addExcludedMatcher(new DocumentNameMatcher(fileFilter));
442 }
443
444 /**
445 * Excludes files that match a DocumentNameMatcher.
446 * @param matcher the DocumentNameMatcher to match.
447 */
448 public void addExcludedMatcher(final DocumentNameMatcher matcher) {
449 exclusionProcessor.addExcludedMatcher(matcher);
450 }
451
452 /**
453 * Excludes files that match the pattern.
454 *
455 * @param patterns the collection of patterns to exclude.
456 * @see ExclusionProcessor#addIncludedPatterns(Iterable)
457 */
458 public void addExcludedPatterns(final Iterable<String> patterns) {
459 exclusionProcessor.addExcludedPatterns(patterns);
460 }
461
462 /**
463 * Adds the patterns from the standard collection as included patterns.
464 * @param collection the standard collection to include.
465 */
466 public void addIncludedCollection(final StandardCollection collection) {
467 exclusionProcessor.addIncludedCollection(collection);
468 }
469
470 /**
471 * Adds the fileFilter to filter files that should be included, this overrides any
472 * exclusion of the same files.
473 * @param fileFilter the filter to identify files that should be included.
474 */
475 public void addIncludedFilter(final FileFilter fileFilter) {
476 exclusionProcessor.addIncludedMatcher(new DocumentNameMatcher(fileFilter));
477 }
478
479 /**
480 * Includes files that match a DocumentNameMatcher.
481 * @param matcher the DocumentNameMatcher to match.
482 */
483 public void addIncludedMatcher(final DocumentNameMatcher matcher) {
484 exclusionProcessor.addIncludedMatcher(matcher);
485 }
486
487 /**
488 * Add file patterns that are to be included. These patterns override any exclusion of
489 * the same files.
490 * @param patterns the iterable of Strings containing the patterns.
491 */
492 public void addIncludedPatterns(final Iterable<String> patterns) {
493 exclusionProcessor.addIncludedPatterns(patterns);
494 }
495
496 /**
497 * Get the DocumentNameMatcher that excludes files found in the directory tree.
498 * @param baseDir the DocumentName for the base directory.
499 * @return the DocumentNameMatcher for the base directory.
500 */
501 public DocumentNameMatcher getDocumentExcluder(final DocumentName baseDir) {
502 return exclusionProcessor.getNameMatcher(baseDir);
503 }
504
505 // visible for testing.
506 ExclusionProcessor getExclusionProcessor() {
507 return exclusionProcessor;
508 }
509
510 /**
511 * Gets the IOSupplier with the style sheet.
512 * @return the Supplier of the InputStream that is the XSLT style sheet to style
513 * the report with.
514 */
515 public IOSupplier<InputStream> getStyleSheet() {
516 return styleSheet == null ? null : styleSheet.ioSupplier();
517 }
518
519 /**
520 * Gets the IODescriptor with the style sheet.
521 * @return the IODescriptor that describes the XSLT style sheet to style
522 * the report with.
523 */
524 public IODescriptor<InputStream> getStyleSheetDescriptor() {
525 return styleSheet == null ? null : styleSheet;
526 }
527
528 /**
529 * Sets the style sheet for custom processing. The IODescriptor may be called
530 * multiple times, so the input stream must be able to be opened and closed
531 * multiple times.
532 * @param styleSheet the XSLT style sheet to style the report with.
533 */
534 public void setStyleSheet(final IODescriptor<InputStream> styleSheet) {
535 this.styleSheet = styleSheet;
536 }
537
538 /**
539 * Adds the licenses and approved licenses from the defaults object to the
540 * configuration. <em>Side effect:</em> if the report should be styled and no
541 * style sheet has been set the plain stylesheet from the defaults will be used.
542 * @param defaults the defaults to set.
543 */
544 public void setFrom(final Defaults defaults) {
545 licenseSetFactory.add(defaults.getLicenseSetFactory());
546 if (getStyleSheetDescriptor() == null) {
547 setStyleSheet(StyleSheets.PLAIN.getStyleSheet());
548 }
549 defaults.getStandardExclusion().forEach(this::addExcludedCollection);
550 }
551
552 /**
553 * Sets the style sheet.
554 * @param styleSheet the XSLT style sheet file to style the report with.
555 */
556 public void setStyleSheet(final File styleSheet) {
557 Objects.requireNonNull(styleSheet, "styleSheet file should not be null");
558 setStyleSheet(styleSheet.toURI());
559 }
560
561 /**
562 * Sets the style sheet for custom processing. The stylesheet may be opened
563 * multiple times so the URI must be capable of being opened multiple times.
564 * @param styleSheet the URI of the XSLT style sheet to style the report with.
565 */
566 public void setStyleSheet(final URI styleSheet) {
567 Objects.requireNonNull(styleSheet, "Stylesheet file must not be null");
568 try {
569 setStyleSheet(styleSheet.toURL());
570 } catch (MalformedURLException e) {
571 throw new ConfigurationException("Unable to process stylesheet", e);
572 }
573 }
574
575 /**
576 * Sets the style sheet for custom processing. The stylesheet may be opened
577 * multiple times so the URL must be capable of being opened multiple times.
578 * @param styleSheet the URL of the XSLT style sheet to style the report with.
579 */
580 public void setStyleSheet(final URL styleSheet) {
581 Objects.requireNonNull(styleSheet, "Stylesheet file must not be null");
582 setStyleSheet(new IODescriptor<>(styleSheet.toString(), styleSheet::openStream));
583 }
584
585 /**
586 * Sets the supplier for the output stream. The supplier may be called multiple
587 * times to provide the stream. Suppliers should prepare streams that are
588 * appended to and that can be closed. If an {@code OutputStream} should not be
589 * closed consider wrapping it in a {@code CloseShieldOutputStream}
590 * @param outputDescriptor the OutputStream supplier that provides the output stream to write.
591 * the report to. A {@code null} value will use {@code System.out}.
592 * @see CloseShieldOutputStream
593 */
594 public void setOutput(final IODescriptor<OutputStream> outputDescriptor) {
595 this.outputDescriptor = outputDescriptor;
596 }
597
598 /**
599 * Sets the OutputStream supplier to use the specified file. The file may be
600 * opened and closed several times. File is deleted first and then may be
601 * repeatedly opened in append mode.
602 * @see #setOutput(IODescriptor)
603 * @param file The file to create the supplier with.
604 */
605 public void setOutput(final File file) {
606 Objects.requireNonNull(file, "output file should not be null");
607 if (file.exists()) {
608 try {
609 Files.delete(file.toPath());
610 } catch (IOException e) {
611 DefaultLog.getInstance().warn("Unable to delete file: " + file);
612 }
613 }
614
615 File parent = file.getParentFile();
616 if (!parent.mkdirs() && !parent.isDirectory()) {
617 DefaultLog.getInstance().warn("Unable to create directory: " + file.getParentFile());
618 }
619 setOutput(IODescriptor.output(file));
620 }
621
622 /**
623 * Returns the output stream supplier. If no stream has been set returns a
624 * supplier for {@code System.out}.
625 * @return the supplier of the output stream to write the report to.
626 */
627 public IOSupplier<OutputStream> getOutput() {
628 return getOutputDescriptor().ioSupplier();
629 }
630
631 /**
632 * Returns the output IODescriptor. If no stream has been set returns a
633 * descriptor for {@code System.out}.
634 * @return the IODescriptor of the output stream to write the report to.
635 */
636 public IODescriptor<OutputStream> getOutputDescriptor() {
637 return outputDescriptor == null ? SYSTEM_OUT : outputDescriptor;
638 }
639
640 /**
641 * Gets a PrintWriter that wraps the output stream.
642 * @return a supplier for a PrintWriter that wraps the output stream.
643 * @see #getOutput()
644 */
645 public IOSupplier<PrintWriter> getWriter() {
646 return () -> new PrintWriter(new OutputStreamWriter(getOutput().get(), StandardCharsets.UTF_8));
647 }
648
649 /**
650 * Adds a license to the list of licenses. Does not add the license to the list
651 * of approved licenses.
652 * @param license the license to add to the list of licenses.
653 */
654 public void addLicense(final ILicense license) {
655 licenseSetFactory.addLicense(license);
656 }
657
658 /**
659 * Adds a license to the list of licenses. Does not add the license to the list
660 * of approved licenses.
661 * @param builder the license builder to build and add to the list of licenses.
662 * @return the ILicense implementation that was added.
663 */
664 public ILicense addLicense(final ILicense.Builder builder) {
665 return licenseSetFactory.addLicense(builder);
666 }
667
668 /**
669 * Adds multiple licenses to the list of licenses. Does not add the licenses to
670 * the list of approved licenses.
671 * @param licenses the licenses to add.
672 */
673 public void addLicenses(final Collection<ILicense> licenses) {
674 licenseSetFactory.addLicenses(licenses);
675 }
676
677 /**
678 * Adds a license family to the list of families. Does not add the family to the
679 * list of approved licenses.
680 * @param family the license family to add to the list of license families.
681 */
682 public void addFamily(final ILicenseFamily family) {
683 licenseSetFactory.addFamily(family);
684 }
685
686 /**
687 * Adds a license family to the list of families. Does not add the family to the
688 * list of approved licenses.
689 * @param builder the licenseFamily.Builder to build and add to the list of
690 * licenses.
691 */
692 public void addFamily(final ILicenseFamily.Builder builder) {
693 licenseSetFactory.addFamily(builder);
694 }
695
696 /**
697 * Adds multiple families to the list of license families. Does not add the
698 * licenses to the list of approved licenses.
699 * @param families the license families to add.
700 */
701 public void addFamilies(final Collection<ILicenseFamily> families) {
702 families.forEach(this::addApprovedLicenseCategory);
703 }
704
705 /**
706 * Adds an ILicenseFamily to the list of approved licenses.
707 * @param approvedILicenseFamily the LicenseFamily to add.
708 */
709 public void addApprovedLicenseCategory(final ILicenseFamily approvedILicenseFamily) {
710 addApprovedLicenseCategory(approvedILicenseFamily.getFamilyCategory());
711 }
712
713 /**
714 * Adds a license family category (id) to the list of approved licenses.
715 * @param familyCategory the category to add.
716 */
717 public void addApprovedLicenseCategory(final String familyCategory) {
718 licenseSetFactory.approveLicenseCategory(familyCategory);
719 }
720
721 /**
722 * Adds a collection of license family categories to the set of approved license
723 * names.
724 * @param approvedLicenseCategories set of approved license categories.
725 */
726 public void addApprovedLicenseCategories(final Collection<String> approvedLicenseCategories) {
727 approvedLicenseCategories.forEach(this::addApprovedLicenseCategory);
728 }
729
730 /**
731 * Adds a license family category to the list of approved licenses. <em>Once a
732 * license has been removed from the approved list it cannot be re-added</em>
733 * @param familyCategory the category to add.
734 */
735 public void removeApprovedLicenseCategory(final String familyCategory) {
736 licenseSetFactory.removeLicenseCategory(ILicenseFamily.makeCategory(familyCategory));
737 }
738
739 /**
740 * Removes a license family category from the list of approved licenses.
741 * <em>Once a license has been removed from the approved list it cannot be
742 * re-added</em>
743 * @param familyCategory the family category to remove.
744 */
745 public void removeApprovedLicenseCategories(final Collection<String> familyCategory) {
746 familyCategory.forEach(this::removeApprovedLicenseCategory);
747 }
748
749 /**
750 * Gets the SortedSet of approved license categories. <em>Once a license has
751 * been removed from the approved list it cannot be re-added</em>
752 * @param filter the LicenseFilter to filter the categories by.
753 * @return the Sorted set of approved license categories.
754 */
755 public SortedSet<String> getLicenseCategories(final LicenseFilter filter) {
756 return licenseSetFactory.getLicenseCategories(filter);
757 }
758
759 /**
760 * Gets the SortedSet of approved license categories. <em>Once a license has
761 * been removed from the approved list it cannot be re-added</em>
762 * @param filter the LicenseFilter to filter the licenses by.
763 * @return the Sorted set of approved license categories.
764 */
765 public UnmodifiableSortedSet<ILicense> getLicenses(final LicenseFilter filter) {
766 return licenseSetFactory.getLicenses(filter);
767 }
768
769 /**
770 * Gets the SortedSet of approved license categories. <em>Once a license has
771 * been removed from the approved list it cannot be re-added</em>
772 * @param filter the LicenseFilter to filter the licenses by.
773 * @return the Sorted set of approved license categories.
774 */
775 public SortedSet<String> getLicenseIds(final LicenseFilter filter) {
776 return licenseSetFactory.getLicenseIds(filter);
777 }
778
779 /**
780 * Adds an ILicenseFamily to the list of approved licenses.
781 * @param approvedLicense the License to add.
782 */
783 public void addApprovedLicenseId(final ILicense approvedLicense) {
784 addApprovedLicenseId(approvedLicense.getId());
785 }
786
787 /**
788 * Adds a license family category (id) to the list of approved licenses
789 * @param licenseId the license id to add.
790 */
791 public void addApprovedLicenseId(final String licenseId) {
792 licenseSetFactory.approveLicenseId(licenseId);
793 }
794
795 /**
796 * Adds a collection of license family categories to the set of approved license
797 * names.
798 * @param approvedLicenseIds set of approved license IDs.
799 */
800 public void addApprovedLicenseIds(final Collection<String> approvedLicenseIds) {
801 approvedLicenseIds.forEach(this::addApprovedLicenseId);
802 }
803
804 /**
805 * Adds a license family category to the list of approved licenses. <em>Once a
806 * license has been removed from the approved list it cannot be re-added</em>
807 * @param licenseId the license ID to add.
808 */
809 public void removeApprovedLicenseId(final String licenseId) {
810 licenseSetFactory.removeLicenseId(licenseId);
811 }
812
813 /**
814 * Removes a license family category from the list of approved licenses.
815 * <em>Once a license has been removed from the approved list it cannot be
816 * re-added</em>
817 * @param licenseIds the license IDs to remove.
818 */
819 public void removeApprovedLicenseIds(final Collection<String> licenseIds) {
820 licenseIds.forEach(this::removeApprovedLicenseId);
821 }
822
823 /**
824 * Returns the optional license copyright being added if RAT is adding headers.
825 * This value is ignored, if no license headers are added.
826 * @return the optional copyright message.
827 * @see #isAddingLicenses()
828 */
829 public String getCopyrightMessage() {
830 return copyrightMessage;
831 }
832
833 /**
834 * Sets the optional copyright message used if RAT is adding license headers.
835 * This value is ignored, if no license headers are added.
836 * @param copyrightMessage message to set.
837 * @see #isAddingLicenses()
838 */
839 public void setCopyrightMessage(final String copyrightMessage) {
840 this.copyrightMessage = copyrightMessage;
841 }
842
843 /**
844 * Gets the flag that determines if license headers are "forced" overwriting existing files.
845 * This value is ignored if RAT is not adding licenses.
846 * @return {@code true} if RAT is forcing the adding license headers.
847 * @see #isAddingLicenses()
848 */
849 public boolean isAddingLicensesForced() {
850 return addingLicensesForced;
851 }
852
853 /**
854 * Gets the flag that determines if license headers should be added if missing.
855 * @return whether RAT should add missing license headers.
856 * @see #isAddingLicensesForced()
857 * @see #getCopyrightMessage()
858 */
859 public boolean isAddingLicenses() {
860 return addingLicenses;
861 }
862
863 /**
864 * Sets whether RAT should enable, disable, or force the adding of license
865 * headers.
866 * @param addLicenseHeaders enables/disables or forces adding of licenses
867 * headers.
868 * @see #isAddingLicenses()
869 * @see #setCopyrightMessage(String)
870 */
871 public void setAddLicenseHeaders(final AddLicenseHeaders addLicenseHeaders) {
872 addingLicenses = false;
873 addingLicensesForced = false;
874 switch (addLicenseHeaders) {
875 case FALSE:
876 // do nothing
877 break;
878 case FORCED:
879 addingLicensesForced = true;
880 addingLicenses = true;
881 break;
882 case TRUE:
883 addingLicenses = true;
884 break;
885 }
886 }
887
888 /**
889 * Gets a sorted set of ILicenseFamily objects based on {@code filter}. If
890 * filter is set to:
891 * <ul>
892 * <li>{@code all} - All licenses families will be returned.</li>
893 * <li>{@code approved} - Only approved license families will be returned.</li>
894 * <li>{@code none} - No license families will be returned.</li>
895 * </ul>
896 * @param filter the license filter.
897 * @return the set of defined licenses.
898 */
899 public SortedSet<ILicenseFamily> getLicenseFamilies(final LicenseFilter filter) {
900 return licenseSetFactory.getLicenseFamilies(filter);
901 }
902
903 /**
904 * Gets the ClaimValidator for the configuration.
905 * @return the ClaimValidator.
906 */
907 public ClaimValidator getClaimValidator() {
908 return claimValidator;
909 }
910
911 /**
912 * Gets the enclosed LicenseSetFactory.
913 * @return the license set factory.
914 */
915 public LicenseSetFactory getLicenseSetFactory() {
916 return licenseSetFactory;
917 }
918
919 /**
920 * Validates that the configuration is valid.
921 * @param logger String consumer to log warning messages to.
922 * @throws ConfigurationException on configuration error.
923 */
924 public void validate(final Consumer<String> logger) {
925 if (!hasSource()) {
926 String msg = "At least one source must be specified";
927 logger.accept(msg);
928 throw new ConfigurationException(msg);
929 }
930 if (licenseSetFactory.getLicenses(LicenseFilter.ALL).isEmpty()) {
931 String msg = "You must specify at least one license";
932 logger.accept(msg);
933 throw new ConfigurationException(msg);
934 }
935 }
936
937 /**
938 * An IODescriptor comprises a name and an IOSupplier. The name should identify the contents of the stream.
939 * @param name the name of the supplier.
940 * @param ioSupplier the IOSupplier that provides either an InputStream or an OutputStream.
941 * @param <T> either InputStream or OutputStream.
942 */
943 public record IODescriptor<T>(String name, IOSupplier<T> ioSupplier) {
944
945 // OUTPUT CONSTRUCTORS
946 /**
947 * Creates an output IODescriptor for the file name within the working directory.
948 * @param name the name of the file to open.
949 * @param workingDirectory the working directory for the file.
950 * @return the Output IODescriptor.
951 */
952 static IODescriptor<OutputStream> output(final String name, final DocumentName workingDirectory) {
953 DocumentName docName = workingDirectory.resolve(name);
954 return new IODescriptor<>(name, () -> new FileOutputStream(docName.asFile()));
955 }
956
957 /**
958 * Creates an output IODescriptor for the file. Does not modify for working directory.
959 * @param file the file to open.
960 * @return the Output IODescriptor.
961 */
962 static IODescriptor<OutputStream> output(final File file) {
963 return new IODescriptor<>(file.toString(), () -> new FileOutputStream(file, true));
964 }
965
966 // INPUT CONSTRUCTORS
967 /**
968 * Creates an input IODescriptor for the file. Does not modify for working directory.
969 * @param file the file to open.
970 * @return the Input IODescriptor.
971 */
972 static IODescriptor<InputStream> input(final File file) {
973 return new IODescriptor<>(file.toString(), () -> new FileInputStream(file));
974 }
975 }
976
977 /**
978 * Serializes the ReportConfiguration into an XML document that can be deserialized by the Serde.
979 * Deserialized ReportConfigurations can not be executed as the reportable objects use named placeholders
980 * and do not have access to the original object.
981 */
982 @SuppressFBWarnings("EI_EXPOSE_REP2")
983 public class SerDes {
984 /**
985 * Writes the configuration as an XML document to the appendable.
986 *
987 * @param appendable the Appendable to write to.
988 * @throws IOException on error.
989 */
990 public void serialize(final Appendable appendable) throws IOException {
991 try (XmlWriter writer = new XmlWriter(appendable)) {
992 writer.startElement("ReportConfiguration")
993 .attribute("addingLicenses", Boolean.toString(addingLicenses))
994 .attribute("addingLicensesForced", Boolean.toString(addingLicensesForced))
995 .attribute("listFamilies", listFamilies.name())
996 .attribute("listLicenses", listLicenses.name())
997 .attribute("dryRun", Boolean.toString(dryRun))
998 .attribute("archiveProcessing", getArchiveProcessing().name())
999 .attribute("standardProcessing", getStandardProcessing().name())
1000 .attribute("stylesheet", styleSheet.name())
1001 .attribute("output", outputDescriptor.name());
1002 if (StringUtils.isNotEmpty(copyrightMessage)) {
1003 writer.startElement("copyrightMessage").content(copyrightMessage).closeElement();
1004 }
1005 writer.startElement("sources");
1006 for (File f : sources) {
1007 writer.startElement("source").attribute("name", f.toString()).closeElement();
1008 }
1009 writer.closeElement("sources").startElement("reportables");
1010 for (Reportable reportable : reportables) {
1011 writer.startElement("reportable")
1012 .attribute("baseName", reportable.name().getBaseName())
1013 .attribute("name", reportable.name().toString())
1014 .attribute("class", reportable.getClass().getName()).closeElement();
1015 }
1016 writer.closeElement();
1017
1018 exclusionProcessor.serDes().serialize(writer);
1019
1020 writer.startElement("claimValidator");
1021 for (ClaimStatistic.Counter counter : ClaimStatistic.Counter.values()) {
1022 writer.startElement("claimCounter")
1023 .attribute("name", counter.name()).attribute("min", Integer.toString(claimValidator.getMin(counter)))
1024 .attribute("max", Integer.toString(claimValidator.getMax(counter))).closeElement();
1025 }
1026 writer.closeElement();
1027 } catch (IOException e) {
1028 throw e;
1029 } catch (Exception e) {
1030 throw new IOException(e);
1031 }
1032 }
1033
1034 /**
1035 * Reads an input stream as an XML document and parses the report configuration from that.
1036 * <em>Note:</em> The reportable objects (Files) in a deserialized ReportConfigurations are not executable.
1037 * @param inputStreamSupplier The XML document written by {@link #serialize(Appendable)}
1038 * @param workingDirectory the directory to resolve short XSLT and output names from.
1039 * @throws IOException on parse error.
1040 */
1041 public void deserialize(final IOSupplier<InputStream> inputStreamSupplier, final DocumentName workingDirectory) throws IOException {
1042 org.w3c.dom.Document document;
1043 try (InputStream stream = inputStreamSupplier.get()) {
1044 document = StandardXmlFactory.documentBuilder().parse(stream);
1045 } catch (SAXException e) {
1046 throw new IOException("Unable to read input", e);
1047 }
1048 Node node = document.getDocumentElement();
1049 if (!node.getNodeName().equals("ReportConfiguration")) {
1050 throw new IOException("Invalid ReportConfiguration");
1051 }
1052 Map<String, String> attributes = XMLConfigurationReader.attributes(node);
1053 addingLicenses = Boolean.parseBoolean(attributes.get("addingLicenses"));
1054 addingLicensesForced = Boolean.parseBoolean(attributes.get("addingLicensesForced"));
1055 listFamilies = LicenseFilter.valueOf(attributes.get("listFamilies"));
1056 listLicenses = LicenseFilter.valueOf(attributes.get("listLicenses"));
1057 dryRun = Boolean.parseBoolean(attributes.get("dryRun"));
1058 archiveProcessing = Processing.valueOf(attributes.get("archiveProcessing"));
1059 standardProcessing = Processing.valueOf(attributes.get("standardProcessing"));
1060 String styleName = attributes.get("stylesheet");
1061 if (styleName != null) {
1062 styleSheet = StyleSheets.getStyleSheet(styleName, workingDirectory);
1063 }
1064 String outputName = attributes.get("output");
1065 if (outputName != null) {
1066 if (outputName.equals(ReportConfiguration.SYSTEM_OUT.name())) {
1067 outputDescriptor = ReportConfiguration.SYSTEM_OUT;
1068 } else {
1069 outputDescriptor = IODescriptor.output(outputName, workingDirectory);
1070 }
1071 }
1072
1073 XMLConfigurationReader.nodeListConsumer(document.getElementsByTagName("copyrightMessage"),
1074 lNode -> setCopyrightMessage(lNode.getTextContent()));
1075
1076 XMLConfigurationReader.nodeListConsumer(document.getElementsByTagName("source"), lNode -> {
1077 Map<String, String> nAttributes = XMLConfigurationReader.attributes(lNode);
1078 addSource(new File(nAttributes.get("name")));
1079 });
1080
1081 // Deserialize the reportables.
1082 XMLConfigurationReader.nodeListConsumer(document.getElementsByTagName("reportable"), lNode -> {
1083 Map<String, String> nAttributes = XMLConfigurationReader.attributes(lNode);
1084 DocumentName documentName = DocumentName.builder().setBaseName(nAttributes.get("baseName"))
1085 .setName(nAttributes.get("name")).build();
1086 addSource(new DeserializedReportable(documentName));
1087 });
1088
1089 exclusionProcessor.serDes().deserialize(document.getElementsByTagName("ExclusionProcessor").item(0));
1090
1091 XMLConfigurationReader.nodeListConsumer(document.getElementsByTagName("claimCounter"), lNode -> {
1092 Map<String, String> nAttributes = XMLConfigurationReader.attributes(lNode);
1093 ClaimStatistic.Counter counter = ClaimStatistic.Counter.valueOf(nAttributes.get("name"));
1094 claimValidator.setMin(counter, Integer.parseInt(nAttributes.get("min")));
1095 claimValidator.setMax(counter, Integer.parseInt(nAttributes.get("max")));
1096 });
1097 }
1098 }
1099
1100 /**
1101 * A record that identifies a deserialized reportable. Deserialized reportables are not executable.
1102 * @param name the name of the reportable.
1103 */
1104 private record DeserializedReportable(DocumentName name) implements Reportable {
1105 @Override
1106 public void run(final RatReport report) throws RatException {
1107 throw new RatException("Attempt to run a deserialized reportable");
1108 }
1109 }
1110 }