diff --git a/apache-rat-core/src/it/java/org/apache/rat/ReportTest.java b/apache-rat-core/src/it/java/org/apache/rat/ReportTest.java index b695134a6..027619288 100644 --- a/apache-rat-core/src/it/java/org/apache/rat/ReportTest.java +++ b/apache-rat-core/src/it/java/org/apache/rat/ReportTest.java @@ -142,7 +142,7 @@ public void integrationTest(String testName, Document commandLineDoc) throws Exc try { Object value = shell.run(groovyScript, new String[]{outputFile.getAbsolutePath(), logFile.getAbsolutePath()}); if (value != null) { - fail(String.format("%s", value)); + fail(String.format("%s: %s", testName, value)); } } catch (AssertionError e) { throw new AssertionError(String.format("%s: %s", testName, e.getMessage()), e); diff --git a/apache-rat-core/src/it/resources/ReportTest/RAT_14/verify.groovy b/apache-rat-core/src/it/resources/ReportTest/RAT_14/verify.groovy index 226394df0..c0027ba13 100644 --- a/apache-rat-core/src/it/resources/ReportTest/RAT_14/verify.groovy +++ b/apache-rat-core/src/it/resources/ReportTest/RAT_14/verify.groovy @@ -66,9 +66,9 @@ myArgs[3] = src.getAbsolutePath() ReportConfiguration configuration = OptionCollection.parseCommands(src, myArgs, { opts -> }) assertNotNull(configuration) -configuration.validate(DefaultLog.getInstance().&error) +configuration.validate() Reporter reporter = new Reporter(configuration) -ClaimStatistic statistic = reporter.execute() +ClaimStatistic statistic = reporter.execute().getStatistic() assertEquals(3, statistic.getCounter(ClaimStatistic.Counter.APPROVED)) assertEquals(2, statistic.getCounter(ClaimStatistic.Counter.ARCHIVES)) diff --git a/apache-rat-core/src/main/java/org/apache/rat/OptionCollection.java b/apache-rat-core/src/main/java/org/apache/rat/OptionCollection.java index d04398080..65706862e 100644 --- a/apache-rat-core/src/main/java/org/apache/rat/OptionCollection.java +++ b/apache-rat-core/src/main/java/org/apache/rat/OptionCollection.java @@ -34,7 +34,6 @@ import java.util.stream.Collectors; import org.apache.commons.cli.CommandLine; -import org.apache.commons.cli.DefaultParser; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; @@ -131,27 +130,22 @@ public static ReportConfiguration parseCommands(final File workingDirectory, fin final Consumer helpCmd, final boolean noArgs) throws IOException { Options opts = buildOptions(); - CommandLine commandLine; + ArgumentContext argumentContext; try { - commandLine = DefaultParser.builder().setDeprecatedHandler(DeprecationReporter.getLogReporter()) - .setAllowPartialMatching(true).build().parse(opts, args); + argumentContext = new ArgumentContext(workingDirectory, opts, args); } catch (ParseException e) { - DefaultLog.getInstance().error(e.getMessage()); - DefaultLog.getInstance().error("Please use the \"--help\" option to see a list of valid commands and options.", e); System.exit(1); return null; // dummy return (won't be reached) to avoid Eclipse complaint about possible NPE // for "commandLine" } - - ArgumentContext argumentContext = new ArgumentContext(workingDirectory, commandLine); Arg.processLogLevel(argumentContext, CLIOptionCollection.INSTANCE); - if (commandLine.hasOption(HELP)) { + if (argumentContext.getCommandLine().hasOption(HELP)) { helpCmd.accept(opts); return null; } - if (commandLine.hasOption(Arg.HELP_LICENSES.option())) { + if (argumentContext.getCommandLine().hasOption(Arg.HELP_LICENSES.option())) { new Licenses(createConfiguration(argumentContext), new PrintWriter(System.out, false, StandardCharsets.UTF_8)).printHelp(); return null; } diff --git a/apache-rat-core/src/main/java/org/apache/rat/OptionCollectionParser.java b/apache-rat-core/src/main/java/org/apache/rat/OptionCollectionParser.java index d40686c60..db75e3eec 100644 --- a/apache-rat-core/src/main/java/org/apache/rat/OptionCollectionParser.java +++ b/apache-rat-core/src/main/java/org/apache/rat/OptionCollectionParser.java @@ -76,8 +76,7 @@ public ArgumentContext parseCommands(final File workingDirectory, final String[] * @return the CommandLine * @throws ParseException on option parsing error. */ - //@VisibleForTesting - CommandLine parseCommandLine(final Options opts, final String[] args) throws ParseException { + public static CommandLine parseCommandLine(final Options opts, final String[] args) throws ParseException { try { return DefaultParser.builder().setDeprecatedHandler(DeprecationReporter.getLogReporter()) .setAllowPartialMatching(true).build().parse(opts, args); @@ -100,8 +99,7 @@ CommandLine parseCommandLine(final Options opts, final String[] args) throws Par */ private ArgumentContext parseCommands(final File workingDirectory, final String[] args, final Options options) throws IOException, ParseException { - CommandLine commandLine = parseCommandLine(options, args); - ArgumentContext argumentContext = new ArgumentContext(workingDirectory, commandLine); + ArgumentContext argumentContext = new ArgumentContext(workingDirectory, options, args); Arg.processLogLevel(argumentContext, uiOptionCollection); populateConfiguration(argumentContext); if (uiOptionCollection.isSelected(Arg.HELP_LICENSES)) { diff --git a/apache-rat-core/src/main/java/org/apache/rat/Report.java b/apache-rat-core/src/main/java/org/apache/rat/Report.java index 5f5f4cb35..ce019b782 100644 --- a/apache-rat-core/src/main/java/org/apache/rat/Report.java +++ b/apache-rat-core/src/main/java/org/apache/rat/Report.java @@ -19,8 +19,14 @@ package org.apache.rat; import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.nio.charset.StandardCharsets; import org.apache.commons.cli.Options; +import org.apache.commons.io.function.IOSupplier; +import org.apache.rat.commandline.ArgumentContext; import org.apache.rat.document.RatDocumentAnalysisException; import org.apache.rat.help.Help; import org.apache.rat.utils.DefaultLog; @@ -42,34 +48,66 @@ public final class Report { public static void main(final String[] args) throws Exception { DefaultLog.getInstance().info(new VersionInfo().toString()); + if (args == null || args.length == 0) { DefaultLog.getInstance().info("Please use the \"--help\" option to see a " + "list of valid commands and options, as you did not provide any arguments."); System.exit(0); } - ReportConfiguration configuration = OptionCollection.parseCommands(new File("."), args, Report::printUsage); - if (configuration != null) { - configuration.validate(DefaultLog.getInstance()::error); - Reporter reporter = new Reporter(configuration); - reporter.output(); - reporter.writeSummary(DefaultLog.getInstance().asWriter()); + Reporter.Output result = generateReport(CLIOptionCollection.INSTANCE, new File("."), args); + if (result != null) { + result.writeSummary(DefaultLog.getInstance().asWriter()); - if (configuration.getClaimValidator().hasErrors()) { - configuration.getClaimValidator().logIssues(reporter.getClaimsStatistic()); + if (result.getConfiguration().getClaimValidator().hasErrors()) { + result.getConfiguration().getClaimValidator().logIssues(result.getStatistic()); throw new RatDocumentAnalysisException(format("Issues with %s", String.join(", ", - configuration.getClaimValidator().listIssues(reporter.getClaimsStatistic())))); + result.getConfiguration().getClaimValidator().listIssues(result.getStatistic())))); } } } /** - * Prints the usage message on {@code System.out}. - * @param opts The defined options. + * Prints the usage message on the specified output stream. + * @param out The OutputStream supplier */ - private static void printUsage(final Options opts) { - new Help(System.out).printUsage(opts); + private static void printUsage(final Options options, final IOSupplier out) { + try (OutputStream stream = out.get(); + PrintWriter writer = new PrintWriter(stream, false, StandardCharsets.UTF_8)) { + new Help(writer).printUsage(options); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + /** + * Generates the report + * @param workingDirectory the directory that we are executing in + * @param args the arguments from the command line. + * @return The Client output. + * @throws Exception on error. + */ + static Reporter.Output generateReport(final CLIOptionCollection optionCollection, final File workingDirectory, final String[] args) throws Exception { + Reporter.Output output = null; + OptionCollectionParser optionParser = new OptionCollectionParser(optionCollection); + ArgumentContext argumentContext = optionParser.parseCommands(workingDirectory, args); + ReportConfiguration configuration = argumentContext.getConfiguration(); + if (configuration != null) { + if (argumentContext.getCommandLine().hasOption(CLIOptionCollection.HELP)) { + printUsage(optionCollection.getOptions(), argumentContext.getConfiguration().getOutput()); + } else if (!configuration.hasSource()) { + String msg = "No directories or files specified for scanning. Did you forget to close a multi-argument option?"; + DefaultLog.getInstance().error(msg); + printUsage(optionCollection.getOptions(), argumentContext.getConfiguration().getOutput()); + } else { + configuration.validate(); + Reporter reporter = new Reporter(configuration); + output = reporter.execute(); + output.format(configuration); + } + } + return output; } private Report() { diff --git a/apache-rat-core/src/main/java/org/apache/rat/ReportConfiguration.java b/apache-rat-core/src/main/java/org/apache/rat/ReportConfiguration.java index 799dfaf53..f6b2ccf7c 100644 --- a/apache-rat-core/src/main/java/org/apache/rat/ReportConfiguration.java +++ b/apache-rat-core/src/main/java/org/apache/rat/ReportConfiguration.java @@ -34,18 +34,26 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.SortedSet; -import java.util.function.Consumer; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.apache.commons.collections4.set.UnmodifiableSortedSet; import org.apache.commons.io.function.IOSupplier; import org.apache.commons.io.output.CloseShieldOutputStream; +import org.apache.commons.lang3.StringUtils; import org.apache.rat.analysis.IHeaderMatcher; +import org.apache.rat.api.RatException; import org.apache.rat.commandline.StyleSheets; import org.apache.rat.config.AddLicenseHeaders; import org.apache.rat.config.exclusion.ExclusionProcessor; import org.apache.rat.config.exclusion.StandardCollection; import org.apache.rat.config.results.ClaimValidator; +import org.apache.rat.configuration.XMLConfigurationReader; import org.apache.rat.configuration.builders.AnyBuilder; import org.apache.rat.document.DocumentName; import org.apache.rat.document.DocumentNameMatcher; @@ -55,11 +63,19 @@ import org.apache.rat.license.LicenseSetFactory; import org.apache.rat.license.LicenseSetFactory.LicenseFilter; import org.apache.rat.report.IReportable; +import org.apache.rat.report.RatReport; +import org.apache.rat.report.claim.ClaimStatistic; +import org.apache.rat.report.xml.writer.IXmlWriter; +import org.apache.rat.report.xml.writer.XmlWriter; import org.apache.rat.utils.DefaultLog; import org.apache.rat.utils.Log.Level; import org.apache.rat.utils.ReportingSet; import org.apache.rat.walker.FileListWalker; import org.apache.rat.walker.IReportableListWalker; +import org.w3c.dom.Node; +import org.xml.sax.SAXException; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; /** * A configuration object is used by the front end to invoke the @@ -68,6 +84,8 @@ */ public class ReportConfiguration { + /** The IODescriptor for System.out */ + public static final IODescriptor SYSTEM_OUT = new IODescriptor<>("System.out", () -> CloseShieldOutputStream.wrap(System.out)); /** * The styles of processing for various categories of documents. */ @@ -117,11 +135,11 @@ public String desc() { /** * The IOSupplier that provides the output stream to write the report to. */ - private IOSupplier out; + private IODescriptor out; /** * The IOSupplier that provides the stylesheet to style the XML output. */ - private IOSupplier styleSheet; + private IODescriptor styleSheet; /** * A list of files to read file names from. @@ -176,6 +194,10 @@ public ReportConfiguration() { reportables = new ArrayList<>(); } + public Serde serde() { + return new Serde(); + } + /** * Report the excluded files to the appendable object. * @param appendable the appendable object to write to. @@ -427,7 +449,7 @@ public void addIncludedPatterns(final Iterable patterns) { } /** - * Get the DocumentNameMatcher that excludes files found in the directory tree.. + * Get the DocumentNameMatcher that excludes files found in the directory tree. * @param baseDir the DocumentName for the base directory. * @return the DocumentNameMatcher for the base directory. */ @@ -441,6 +463,15 @@ public DocumentNameMatcher getDocumentExcluder(final DocumentName baseDir) { * the report with. */ public IOSupplier getStyleSheet() { + return styleSheet.ioSupplier(); + } + + /** + * Gets the IODescriptor of the style sheet. + * @return the Supplier of the InputStream that is the XSLT style sheet to style + * the report with. + */ + public IODescriptor getStyleSheetDescriptor() { return styleSheet; } @@ -450,7 +481,7 @@ public IOSupplier getStyleSheet() { * multiple times. * @param styleSheet the XSLT style sheet to style the report with. */ - public void setStyleSheet(final IOSupplier styleSheet) { + public void setStyleSheet(final IODescriptor styleSheet) { this.styleSheet = styleSheet; } @@ -462,7 +493,7 @@ public void setStyleSheet(final IOSupplier styleSheet) { */ public void setFrom(final Defaults defaults) { licenseSetFactory.add(defaults.getLicenseSetFactory()); - if (getStyleSheet() == null) { + if (getStyleSheetDescriptor() == null) { setStyleSheet(StyleSheets.PLAIN.getStyleSheet()); } defaults.getStandardExclusion().forEach(this::addExcludedCollection); @@ -498,19 +529,18 @@ public void setStyleSheet(final URI styleSheet) { */ public void setStyleSheet(final URL styleSheet) { Objects.requireNonNull(styleSheet, "Stylesheet file must not be null"); - setStyleSheet(styleSheet::openStream); + setStyleSheet(new IODescriptor<>(styleSheet.toString(), styleSheet::openStream)); } /** * Sets the supplier for the output stream. The supplier may be called multiple * times to provide the stream. Suppliers should prepare streams that are * appended to and that can be closed. If an {@code OutputStream} should not be - * closed consider wrapping it in a {@code CloseShieldOutputStream} + * closed consider wrapping it in a {@code NoCloseOutputStream} * @param out The OutputStream supplier that provides the output stream to write * the report to. A null value will use System.out. - * @see CloseShieldOutputStream */ - public void setOut(final IOSupplier out) { + public void setOut(final IODescriptor out) { this.out = out; } @@ -518,7 +548,7 @@ public void setOut(final IOSupplier out) { * Sets the OutputStream supplier to use the specified file. The file may be * opened and closed several times. File is deleted first and then may be * repeatedly opened in append mode. - * @see #setOut(IOSupplier) + * @see #setOut(IODescriptor) * @param file The file to create the supplier with. */ public void setOut(final File file) { @@ -534,7 +564,7 @@ public void setOut(final File file) { if (!parent.mkdirs() && !parent.isDirectory()) { DefaultLog.getInstance().warn("Unable to create directory: " + file.getParentFile()); } - setOut(() -> new FileOutputStream(file, true)); + setOut(new IODescriptor<>(file.toString(), () -> new FileOutputStream(file, true))); } /** @@ -543,7 +573,15 @@ public void setOut(final File file) { * @return The supplier of the output stream to write the report to. */ public IOSupplier getOutput() { - return out == null ? () -> CloseShieldOutputStream.wrap(System.out) : out; + return getOutputDescriptor().ioSupplier(); + } + + /** + * Returns the output IODescriptor. + * @return The IODescriptor of the output stream to write the report to. + */ + public IODescriptor getOutputDescriptor() { + return out == null ? SYSTEM_OUT : out; } /** @@ -671,7 +709,7 @@ public SortedSet getLicenseCategories(final LicenseFilter filter) { * @param filter The LicenseFilter to filter the licenses by. * @return the Sorted set of approved license categories. */ - public SortedSet getLicenses(final LicenseFilter filter) { + public UnmodifiableSortedSet getLicenses(final LicenseFilter filter) { return licenseSetFactory.getLicenses(filter); } @@ -827,19 +865,162 @@ public LicenseSetFactory getLicenseSetFactory() { /** * Validates that the configuration is valid. - * @param logger String consumer to log warning messages to. * @throws ConfigurationException on configuration error. */ - public void validate(final Consumer logger) { + public void validate() { if (!hasSource()) { String msg = "At least one source must be specified"; - logger.accept(msg); + DefaultLog.getInstance().error(msg); throw new ConfigurationException(msg); } - if (licenseSetFactory.getLicenses(LicenseFilter.ALL).isEmpty()) { - String msg = "You must specify at least one license"; - logger.accept(msg); - throw new ConfigurationException(msg); + licenseSetFactory.validate(); + } + + /** + * Serializes the ReportConfiguration into an XML document that can be deserialzed by the Serde. + * Deserialized ReportConfigurations can not be executed as the reportable objects a simply named placeholders + * and do not have access to the original object. + */ + @SuppressFBWarnings({"MALICIOUS_XSLT", "XXE_DOCUMENT"}) + public class Serde { + /** + * Writes the configuration as an XML document to the appendable. + * + * @param appendable the Appendable to write to. + * @throws IOException on error. + */ + public void serialize(final Appendable appendable) throws IOException { + try (IXmlWriter writer = new XmlWriter(appendable)) { + writer.openElement("ReportConfiguration") + .attribute("addingLicenses", Boolean.toString(addingLicenses)) + .attribute("addingLicensesForced", Boolean.toString(addingLicensesForced)) + .attribute("listFamilies", listFamilies.name()) + .attribute("listLicenses", listLicenses.name()) + .attribute("dryRun", Boolean.toString(dryRun)) + .attribute("archiveProcessing", getArchiveProcessing().name()) + .attribute("standardProcessing", getStandardProcessing().name()) + .attribute("stylesheet", styleSheet.name()) + .attribute("output", out.name()); + if (StringUtils.isNotEmpty(copyrightMessage)) { + writer.openElement("copyrightMessage").content(copyrightMessage).closeElement(); + } + writer.openElement("sources"); + for (File f : sources) { + writer.openElement("source").attribute("name", f.getName()).closeElement(); + } + writer.closeElement("sources").openElement("reportables"); + for (IReportable reportable : reportables) { + writer.openElement("reportable") + .attribute("baseName", reportable.name().getBaseName()) + .attribute("name", reportable.name().toString()) + .attribute("class", reportable.getClass().getName()).closeElement(); + } + writer.closeElement(); + + exclusionProcessor.serde().serialize(writer); + + writer.openElement("claimValidator"); + for (ClaimStatistic.Counter counter : ClaimStatistic.Counter.values()) { + writer.openElement("claimCounter") + .attribute("name", counter.name()).attribute("min", Integer.toString(claimValidator.getMin(counter))) + .attribute("max", Integer.toString(claimValidator.getMax(counter))).closeElement(); + } + writer.closeElement(); + } catch (IOException e) { + throw e; + } catch (Exception e) { + throw new IOException(e); + } + } + + public void deserialize(final IOSupplier inputStreamSupplier, final DocumentName workingDirectory) throws IOException { + DocumentBuilder builder; + try { + builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + } catch (ParserConfigurationException e) { + throw new ConfigurationException("Unable to create DOM builder", e); + } + org.w3c.dom.Document document; + try (InputStream stream = inputStreamSupplier.get()) { + document = builder.parse(stream); + } catch (SAXException e) { + throw new IOException("Unable to read input", e); + } + Node node = document.getDocumentElement(); + if (!node.getNodeName().equals("ReportConfiguration")) { + throw new IOException("Invalid ReportConfiguration"); + } + Map attributes = XMLConfigurationReader.attributes(node); + addingLicensesForced = Boolean.parseBoolean(attributes.get("addingLicensesForced")); + listFamilies = LicenseFilter.valueOf(attributes.get("listFamilies")); + listLicenses = LicenseFilter.valueOf(attributes.get("listLicenses")); + dryRun = Boolean.parseBoolean(attributes.get("dryRun")); + archiveProcessing = Processing.valueOf(attributes.get("archiveProcessing")); + standardProcessing = Processing.valueOf(attributes.get("standardProcessing")); + String styleName = attributes.get("stylesheet"); + if (styleName != null) { + styleSheet = StyleSheets.getStyleSheet(styleName, workingDirectory); + } + String outputName = attributes.get("output"); + if (outputName != null) { + if (outputName.equals(ReportConfiguration.SYSTEM_OUT.name())) { + out = ReportConfiguration.SYSTEM_OUT; + } else { + out = IODescriptor.output(outputName, workingDirectory); + } + } + + XMLConfigurationReader.nodeListConsumer(document.getElementsByTagName("source"), lNode -> { + Map nAttributes = XMLConfigurationReader.attributes(lNode); + addSource(new File(nAttributes.get("name"))); + }); + + // Deserialize the reportables. + XMLConfigurationReader.nodeListConsumer(document.getElementsByTagName("reportable"), lNode -> { + Map nAttributes = XMLConfigurationReader.attributes(lNode); + DocumentName documentName = DocumentName.builder().setBaseName(nAttributes.get("baseName")) + .setName(nAttributes.get("name")).build(); + addSource(new DeserializedReportable(documentName)); + }); + + exclusionProcessor.serde().deserialize(document.getElementsByTagName("ExclusionProcessor").item(0)); + + XMLConfigurationReader.nodeListConsumer(document.getElementsByTagName("claimCounter"), lNode -> { + Map nAttributes = XMLConfigurationReader.attributes(lNode); + ClaimStatistic.Counter counter = ClaimStatistic.Counter.valueOf(nAttributes.get("name")); + claimValidator.setMin(counter, Integer.parseInt(nAttributes.get("min"))); + claimValidator.setMax(counter, Integer.parseInt(nAttributes.get("max"))); + }); } } + + /** + * A record that identifies a deserialized reportable. Deserialized reportables are not executable. + * @param name the name of the reportable. + */ + private record DeserializedReportable(DocumentName name) implements IReportable { + @Override + public void run(final RatReport report) throws RatException { + throw new RatException("Attempt to run a deserialized reportable"); + } + } + + /** + * An IODescriptor comprises a name and an IOSupplier. The name should identify the contents of the stream. + * @param name the name of the supplier. + * @param ioSupplier the IOSupplier that provides either an InputStream or an OutputStream + * @param either InputStream or OutputStream. + */ + public record IODescriptor(String name, IOSupplier ioSupplier) { + /** + * Creates an output IODescriptor for the file name within the working directory. + * @param name the name of the file to open. + * @param workingDirectory the working directory for the file. + * @return the Output IODescriptor. + */ + static IODescriptor output(final String name, final DocumentName workingDirectory) { + DocumentName docName = workingDirectory.resolve(name); + return new IODescriptor(name, () -> new FileOutputStream(docName.asFile())); + } + }; } diff --git a/apache-rat-core/src/main/java/org/apache/rat/Reporter.java b/apache-rat-core/src/main/java/org/apache/rat/Reporter.java index 2e58c5829..0bc6d6d8b 100644 --- a/apache-rat-core/src/main/java/org/apache/rat/Reporter.java +++ b/apache-rat-core/src/main/java/org/apache/rat/Reporter.java @@ -18,17 +18,19 @@ */ package org.apache.rat; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; -import java.io.Writer; +import java.io.StringReader; import java.nio.charset.StandardCharsets; +import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerException; @@ -39,6 +41,7 @@ import org.apache.commons.io.function.IOSupplier; import org.apache.rat.api.RatException; +import org.apache.rat.document.DocumentName; import org.apache.rat.license.LicenseSetFactory.LicenseFilter; import org.apache.rat.report.RatReport; import org.apache.rat.report.claim.ClaimStatistic; @@ -46,6 +49,10 @@ import org.apache.rat.report.xml.writer.IXmlWriter; import org.apache.rat.report.xml.writer.XmlWriter; import org.w3c.dom.Document; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; /** * Class that executes the report as defined in a {@link ReportConfiguration} and stores @@ -56,15 +63,14 @@ public class Reporter { /** Format used for listing licenses. */ private static final String LICENSE_FORMAT = "%s:\t%s%n\t\t%s%n"; - /** The XML output document */ - private Document document; - - /** Statistics generated as the report was built */ - private ClaimStatistic statistic; - /** The configuration for the report */ private final ReportConfiguration configuration; + /** + * The output from the execution. + */ + private Output output; + /** * Create the reporter. * @@ -76,83 +82,39 @@ public Reporter(final ReportConfiguration configuration) { /** * Executes the report and builds the output. - * This method will build the internal XML document if it does not already exist. - * If this method or either of the {@link #output()} methods have already been called this method will return - * the previous results. - * @return the claim statistics. + * @return the Output object. * @throws RatException on error. */ - public ClaimStatistic execute() throws RatException { - if (document == null || statistic == null) { - try { - if (configuration.hasSource()) { - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - Writer outputWriter = new OutputStreamWriter(outputStream, StandardCharsets.UTF_8); - try (IXmlWriter writer = new XmlWriter(outputWriter)) { - statistic = new ClaimStatistic(); - RatReport report = XmlReportFactory.createStandardReport(writer, statistic, configuration); - report.startReport(); - configuration.getSources().build().run(report); - report.endReport(); - } - InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray()); - document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(inputStream); - } else { - document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); - statistic = new ClaimStatistic(); + public Output execute() throws RatException { + try { + Output.Builder builder = Output.builder().configuration(configuration); + if (configuration.hasSource()) { + StringBuilder sb = new StringBuilder(); + try (IXmlWriter writer = new XmlWriter(sb)) { + writer.startDocument(); + ClaimStatistic statistic = new ClaimStatistic(); + builder.statistic(statistic); + RatReport report = XmlReportFactory.createStandardReport(writer, statistic, configuration); + report.startReport(); + configuration.getSources().build().run(report); + report.endReport(); + InputSource inputSource = new InputSource(new StringReader(sb.toString())); + builder.document(DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(inputSource)); } - } catch (Exception e) { - throw RatException.makeRatException(e); } + this.output = builder.build(); + return output; + } catch (Exception e) { + throw RatException.makeRatException(e); } - return statistic; } /** - * Get the claim statistics from the run. - * - * @return the claim statistics. + * Gets the output from the last {@link #execute} call or {@code null} if {@link #execute} has not been called. + * @return the output */ - public ClaimStatistic getClaimsStatistic() { - return statistic; - } - - /** - * Outputs the report using the stylesheet and output specified in the configuration. - * @return the Claim statistic from the run. - * @throws RatException on error. - */ - public ClaimStatistic output() throws RatException { - return output(configuration.getStyleSheet(), configuration.getOutput()); - } - - /** - * Outputs the report to the specified output using the stylesheet. It is safe to call this method more than once - * in order to generate multiple reports from the same run. - * - * @param stylesheet the style sheet to use for XSLT formatting. - * @param output the output stream to write to. - * @return the Claim statistic for the run. - * @throws RatException on error. - */ - public ClaimStatistic output(final IOSupplier stylesheet, final IOSupplier output) throws RatException { - ClaimStatistic result = execute(); - TransformerFactory tf = TransformerFactory.newInstance(); - Transformer transformer; - try (OutputStream out = output.get(); - InputStream styleIn = stylesheet.get()) { - transformer = tf.newTransformer(new StreamSource(styleIn)); - transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); - transformer.setOutputProperty(OutputKeys.METHOD, "xml"); - transformer.setOutputProperty(OutputKeys.INDENT, "yes"); - transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); - transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4"); - transformer.transform(new DOMSource(document), - new StreamResult(new OutputStreamWriter(out, StandardCharsets.UTF_8))); - return result; - } catch (TransformerException | IOException e) { - throw new RatException(e); - } + public Output getOutput() { + return output; } /** @@ -172,24 +134,174 @@ public static void listLicenses(final ReportConfiguration configuration, final L } /** - * Writes a text summary of issues with the run. - * @param appendable the appendable to write to. - * @throws IOException on error. + * The output from a report run. */ - public void writeSummary(final Appendable appendable) throws IOException { - appendable.append("RAT summary:").append(System.lineSeparator()); - for (ClaimStatistic.Counter counter : ClaimStatistic.Counter.values()) { - appendable.append(" ").append(counter.displayName()).append(": ") - .append(Integer.toString(getClaimsStatistic().getCounter(counter))) - .append(System.lineSeparator()); + public static final class Output { + /** The XML output document */ + private final Document document; + /** + * The claim statics from the execution that generated the document. + * May be empty if the Document was read from disk. + */ + private final ClaimStatistic statistic; + /** + * The configuration that generated the document + */ + private final ReportConfiguration configuration; + + /** + * Create an output with statistics. + * @param builder the Builder + */ + private Output(final Builder builder) { + this.document = builder.document; + this.statistic = builder.statistic == null ? new ClaimStatistic() : builder.statistic; + this.configuration = builder.configuration == null ? new ReportConfiguration() : builder.configuration; } - } - /** - * Gets the document that was generated during execution. - * @return the document that was generated during execution. - */ - public Document getDocument() { - return document; + public static Builder builder() { + return new Builder(); + } + + /** + * Gets the document that was generated during execution. + * @return the document that was generated during execution. + */ + public Document getDocument() { + return document; + } + + public ClaimStatistic getStatistic() { + return statistic; + } + + public ReportConfiguration getConfiguration() { + return configuration; + } + /** + * Formats the report to the output and using the stylesheet found in the report configuration. + * + * @param config s RAT report configuration. + * @throws RatException on error. + */ + public void format(final ReportConfiguration config) throws RatException { + format(config.getStyleSheet(), config.getOutput()); + } + + /** + * Formats the report to the specified output using the stylesheet. It is safe to call this method more than once + * in order to generate multiple reports from the same run. + * + * @param stylesheet the style sheet to use for XSLT formatting. + * @param output the output stream to write to. + * @throws RatException on error. + */ + @SuppressFBWarnings({"MALICIOUS_XSLT", "XXE_DTD_TRANSFORM_FACTORY", " XXE_XSLT_TRANSFORM_FACTORY"}) + public void format(final IOSupplier stylesheet, final IOSupplier output) throws RatException { + TransformerFactory tf = TransformerFactory.newInstance(); + Transformer transformer; + try (OutputStream out = output.get(); + InputStream styleIn = stylesheet.get()) { + transformer = tf.newTransformer(new StreamSource(styleIn)); + transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); + transformer.setOutputProperty(OutputKeys.METHOD, "xml"); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); + transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4"); + transformer.transform(new DOMSource(document), + new StreamResult(new OutputStreamWriter(out, StandardCharsets.UTF_8))); + } catch (TransformerException | IOException e) { + throw new RatException(e); + } + } + + /** + * Writes a text summary of issues with the run. + * @param appendable the appendable to write to. + * @throws IOException on error. + */ + public void writeSummary(final Appendable appendable) throws IOException { + appendable.append("RAT summary:").append(System.lineSeparator()); + for (ClaimStatistic.Counter counter : ClaimStatistic.Counter.values()) { + appendable.append(" ").append(counter.displayName()).append(": ") + .append(Integer.toString(statistic.getCounter(counter))) + .append(System.lineSeparator()); + } + } + + @SuppressFBWarnings("EI_EXPOSE_REP2") + public static final class Builder { + /** The document that was generated */ + private Document document; + /** + * The claim statistic from the execution that generated the document. + * May be empty if the Document was read from disk. + */ + private ClaimStatistic statistic; + /** + * The configuration that generated the document + */ + private ReportConfiguration configuration; + + public Builder document(final Document document) { + this.document = document; + return this; + } + @SuppressFBWarnings("XXE_DOCUMENT") + public Builder document(final String fileName, final DocumentName workingDirectory) { + DocumentBuilder builder; + try { + builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + } catch (ParserConfigurationException e) { + throw new ConfigurationException("Unable to create DOM builder", e); + } + + File inputFile = workingDirectory.resolve(fileName).asFile(); + try (InputStream inputStream = new FileInputStream(inputFile)) { + this.document = builder.parse(inputStream); + } catch (SAXException | IOException e) { + throw new ConfigurationException("Unable to read file: " + inputFile, e); + } + return this; + } + + public Output build() { + return new Output(this); + } + + public Builder statistic(final ClaimStatistic statistic) { + this.statistic = statistic; + return this; + } + + public Builder statistic(final String fileName, final DocumentName workingDirectory) { + File sourceFile = workingDirectory.resolve(fileName).asFile(); + try { + ClaimStatistic statistic = new ClaimStatistic(); + statistic.serde().deserialize(() -> new FileInputStream(sourceFile)); + this.statistic = statistic; + return this; + } catch (IOException e) { + throw new ConfigurationException("Unable to read file: " + sourceFile, e); + } + } + + public Builder configuration(final ReportConfiguration configuration) { + this.configuration = configuration; + return this; + } + + public Builder configuration(final String fileName, final DocumentName workingDirectory) { + File configurationFile = workingDirectory.resolve(fileName).asFile(); + try { + ReportConfiguration config = new ReportConfiguration(); + config.serde().deserialize(() -> new FileInputStream(configurationFile), workingDirectory); + this.configuration = config; + return this; + } catch (IOException e) { + throw new ConfigurationException("Unable to read file: " + configurationFile, e); + } + } + } } } diff --git a/apache-rat-core/src/main/java/org/apache/rat/analysis/AnalyserFactory.java b/apache-rat-core/src/main/java/org/apache/rat/analysis/AnalyserFactory.java index f2eca190d..14a7dc84e 100644 --- a/apache-rat-core/src/main/java/org/apache/rat/analysis/AnalyserFactory.java +++ b/apache-rat-core/src/main/java/org/apache/rat/analysis/AnalyserFactory.java @@ -18,10 +18,9 @@ */ package org.apache.rat.analysis; -import java.util.Collection; -import java.util.Set; import java.util.function.Predicate; +import org.apache.commons.collections4.set.UnmodifiableSortedSet; import org.apache.rat.ConfigurationException; import org.apache.rat.Defaults; import org.apache.rat.ReportConfiguration; @@ -80,7 +79,7 @@ public static DocumentAnalyser createMultiplexer(final DocumentAnalyser... analy */ public static DocumentAnalyser createConfiguredAnalyser(final ReportConfiguration configuration) { LicenseSetFactory licenseSetFactory = configuration.getLicenseSetFactory(); - Set licenses = licenseSetFactory.getLicenses(LicenseSetFactory.LicenseFilter.ALL); + UnmodifiableSortedSet licenses = licenseSetFactory.getLicenses(LicenseSetFactory.LicenseFilter.ALL); if (licenses.isEmpty()) { throw new ConfigurationException("At least one license must be defined"); } @@ -98,7 +97,7 @@ public static DocumentAnalyser createConfiguredAnalyser(final ReportConfiguratio private static final class DefaultAnalyser implements DocumentAnalyser { /** The licenses to analyze */ - private final Collection licenses; + private final UnmodifiableSortedSet licenses; /** the Report Configuration */ private final ReportConfiguration configuration; /** The matcher for generated files */ @@ -109,7 +108,7 @@ private static final class DefaultAnalyser implements DocumentAnalyser { * @param config the ReportConfiguration * @param licenses The licenses to analyse */ - DefaultAnalyser(final ReportConfiguration config, final Collection licenses) { + DefaultAnalyser(final ReportConfiguration config, final UnmodifiableSortedSet licenses) { this.licenses = licenses; this.configuration = config; this.generatedMatcher = configuration.getGeneratedMatcher(); diff --git a/apache-rat-core/src/main/java/org/apache/rat/analysis/DocumentHeaderAnalyser.java b/apache-rat-core/src/main/java/org/apache/rat/analysis/DocumentHeaderAnalyser.java index cec174f6f..3f99b7f87 100644 --- a/apache-rat-core/src/main/java/org/apache/rat/analysis/DocumentHeaderAnalyser.java +++ b/apache-rat-core/src/main/java/org/apache/rat/analysis/DocumentHeaderAnalyser.java @@ -20,8 +20,8 @@ import java.io.IOException; import java.io.Reader; -import java.util.Collection; +import org.apache.commons.collections4.set.UnmodifiableSortedSet; import org.apache.rat.api.Document; import org.apache.rat.document.DocumentAnalyser; import org.apache.rat.license.ILicense; @@ -35,7 +35,7 @@ class DocumentHeaderAnalyser implements DocumentAnalyser { /** The license to analyse */ - private final Collection licenses; + private final UnmodifiableSortedSet licenses; /** The matcher for generated headers */ private final IHeaderMatcher generatedMatcher; @@ -43,7 +43,7 @@ class DocumentHeaderAnalyser implements DocumentAnalyser { * Constructs the HeaderAnalyser for the specific license. * @param licenses The licenses to analyse */ - DocumentHeaderAnalyser(final IHeaderMatcher generatedMatcher, final Collection licenses) { + DocumentHeaderAnalyser(final IHeaderMatcher generatedMatcher, final UnmodifiableSortedSet licenses) { super(); this.generatedMatcher = generatedMatcher; this.licenses = licenses; diff --git a/apache-rat-core/src/main/java/org/apache/rat/analysis/HeaderCheckWorker.java b/apache-rat-core/src/main/java/org/apache/rat/analysis/HeaderCheckWorker.java index 30243bb07..122a5f512 100644 --- a/apache-rat-core/src/main/java/org/apache/rat/analysis/HeaderCheckWorker.java +++ b/apache-rat-core/src/main/java/org/apache/rat/analysis/HeaderCheckWorker.java @@ -21,10 +21,10 @@ import java.io.BufferedReader; import java.io.IOException; import java.io.Reader; -import java.util.Collection; import java.util.Locale; import java.util.Objects; +import org.apache.commons.collections4.set.UnmodifiableSortedSet; import org.apache.rat.ConfigurationException; import org.apache.rat.analysis.matchers.FullTextMatcher; import org.apache.rat.api.Document; @@ -54,7 +54,7 @@ public final class HeaderCheckWorker { /** The BufferedReader used to read the lines */ private final BufferedReader reader; /** The licenses to check for match */ - private final Collection licenses; + private final UnmodifiableSortedSet licenses; /** The document being processed */ private final Document document; /** The matcher for generated headers */ @@ -108,7 +108,7 @@ public String toString() { * @param licenses The licenses to check against. Not null. * @param name The document that is being checked. Possibly null. */ - public HeaderCheckWorker(final IHeaderMatcher generatedMatcher, final Reader reader, final Collection licenses, final Document name) { + public HeaderCheckWorker(final IHeaderMatcher generatedMatcher, final Reader reader, final UnmodifiableSortedSet licenses, final Document name) { this(generatedMatcher, reader, DEFAULT_NUMBER_OF_RETAINED_HEADER_LINES, licenses, name); } @@ -123,7 +123,7 @@ public HeaderCheckWorker(final IHeaderMatcher generatedMatcher, final Reader rea * @param document The document that is being checked. Possibly null. */ public HeaderCheckWorker(final IHeaderMatcher generatedMatcher, final Reader reader, - final int numberOfRetainedHeaderLine, final Collection licenses, + final int numberOfRetainedHeaderLine, final UnmodifiableSortedSet licenses, final Document document) { Objects.requireNonNull(reader, "Reader may not be null"); Objects.requireNonNull(licenses, "Licenses may not be null"); diff --git a/apache-rat-core/src/main/java/org/apache/rat/analysis/license/SimplePatternBasedLicense.java b/apache-rat-core/src/main/java/org/apache/rat/analysis/license/SimplePatternBasedLicense.java index 21a3230ea..4032d01fb 100644 --- a/apache-rat-core/src/main/java/org/apache/rat/analysis/license/SimplePatternBasedLicense.java +++ b/apache-rat-core/src/main/java/org/apache/rat/analysis/license/SimplePatternBasedLicense.java @@ -20,6 +20,7 @@ import java.util.Arrays; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import org.apache.rat.DeprecationReporter; import org.apache.rat.configuration.builders.AbstractBuilder; import org.apache.rat.configuration.builders.AnyBuilder; @@ -31,6 +32,7 @@ * @since Rat 0.8 * @deprecated Use new configuration options */ +@SuppressFBWarnings("EI_EXPOSE_REP2") @Deprecated // Since 0.16 @DeprecationReporter.Info(since = "0.16", forRemoval = true, use = "new configuration options") public class SimplePatternBasedLicense extends BaseLicense { diff --git a/apache-rat-core/src/main/java/org/apache/rat/analysis/matchers/SimpleTextMatcher.java b/apache-rat-core/src/main/java/org/apache/rat/analysis/matchers/SimpleTextMatcher.java index c77ddf096..2a5d46b51 100644 --- a/apache-rat-core/src/main/java/org/apache/rat/analysis/matchers/SimpleTextMatcher.java +++ b/apache-rat-core/src/main/java/org/apache/rat/analysis/matchers/SimpleTextMatcher.java @@ -23,6 +23,8 @@ import org.apache.rat.config.parameters.ComponentType; import org.apache.rat.config.parameters.ConfigComponent; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + /** * A simple text matching IHeaderMatcher implementation. */ @@ -44,11 +46,12 @@ public SimpleTextMatcher(final String simpleText) { this(null, simpleText); } + // no sonar and supress FI_USELESS because this is how we ensure that the finalize bug does not bite us + @SuppressFBWarnings("FI_USELESS") @Override protected final void finalize() throws Throwable { // NOSONAR - // no sonar because this is how we ensure that the finalize bug does not bite us + // finalizer attack remediation. super.finalize(); // NOSONAR - // finalizer attack remediation. } /** diff --git a/apache-rat-core/src/main/java/org/apache/rat/annotation/AbstractLicenseAppender.java b/apache-rat-core/src/main/java/org/apache/rat/annotation/AbstractLicenseAppender.java index 3444676ad..c865bd54d 100644 --- a/apache-rat-core/src/main/java/org/apache/rat/annotation/AbstractLicenseAppender.java +++ b/apache-rat-core/src/main/java/org/apache/rat/annotation/AbstractLicenseAppender.java @@ -308,7 +308,7 @@ public void append(final File document) throws IOException { // for Java just place the license at the front, for XML add // an XML decl first - don't know how to handle PHP if (expectsPackage || expectsXMLDecl) { - try (FileWriter writer2 = new FileWriter(newDocument)) { + try (FileWriter writer2 = new FileWriter(newDocument, StandardCharsets.UTF_8)) { if (expectsXMLDecl) { writer2.write(""); writer2.write(LINE_SEP); diff --git a/apache-rat-core/src/main/java/org/apache/rat/commandline/Arg.java b/apache-rat-core/src/main/java/org/apache/rat/commandline/Arg.java index 069009aa9..91c9c0d0d 100644 --- a/apache-rat-core/src/main/java/org/apache/rat/commandline/Arg.java +++ b/apache-rat-core/src/main/java/org/apache/rat/commandline/Arg.java @@ -38,7 +38,6 @@ import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; import org.apache.commons.io.IOUtils; -import org.apache.commons.io.output.CloseShieldOutputStream; import org.apache.commons.lang3.tuple.Pair; import org.apache.rat.ConfigurationException; import org.apache.rat.Defaults; @@ -518,14 +517,14 @@ public enum Arg { if ("x".equals(key)) { // display deprecated message. context.getCommandLine().hasOption("x"); - context.getConfiguration().setStyleSheet(StyleSheets.getStyleSheet("xml")); + context.getConfiguration().setStyleSheet(StyleSheets.getStyleSheet("xml", context.getWorkingDirectory())); } else { String[] style = context.getCommandLine().getOptionValues(selected); if (style.length != 1) { DefaultLog.getInstance().error("Please specify a single stylesheet"); throw new ConfigurationException("Please specify a single stylesheet"); } - context.getConfiguration().setStyleSheet(StyleSheets.getStyleSheet(style[0])); + context.getConfiguration().setStyleSheet(StyleSheets.getStyleSheet(style[0], context.getWorkingDirectory())); } }), @@ -624,7 +623,7 @@ public enum Arg { } catch (ParseException e) { // we write to system out by default. context.logParseException(e, selected, "System.out"); - context.getConfiguration().setOut(() -> CloseShieldOutputStream.wrap(System.out)); // NOSONAR + context.getConfiguration().setOut(ReportConfiguration.SYSTEM_OUT); } }), diff --git a/apache-rat-core/src/main/java/org/apache/rat/commandline/ArgumentContext.java b/apache-rat-core/src/main/java/org/apache/rat/commandline/ArgumentContext.java index d4374a57b..49917397d 100644 --- a/apache-rat-core/src/main/java/org/apache/rat/commandline/ArgumentContext.java +++ b/apache-rat-core/src/main/java/org/apache/rat/commandline/ArgumentContext.java @@ -22,7 +22,9 @@ import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.Option; +import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; +import org.apache.rat.OptionCollectionParser; import org.apache.rat.ReportConfiguration; import org.apache.rat.document.DocumentName; import org.apache.rat.ui.UIOptionCollection; @@ -46,21 +48,26 @@ public final class ArgumentContext { * Creates a context with the specified configuration. * @param workingDirectory the directory from which relative file names will be resolved. * @param configuration The configuration that is being built. - * @param commandLine The command line that is building the configuration. + * @param opts the Options for the command line. + * @param args the arguments for the options. + * @throws ParseException if the options can not parse the arguments. */ - public ArgumentContext(final File workingDirectory, final ReportConfiguration configuration, final CommandLine commandLine) { + public ArgumentContext(final File workingDirectory, final ReportConfiguration configuration, final Options opts, final String[] args) + throws ParseException { this.workingDirectory = DocumentName.builder(workingDirectory).build(); - this.commandLine = commandLine; + this.commandLine = OptionCollectionParser.parseCommandLine(opts, args); this.configuration = configuration; } /** * Creates a context with an empty configuration. * @param workingDirectory The directory from which to resolve relative file names. - * @param commandLine The command line. + * @param opts the Options for the command line. + * @param args the arguments for the options. + * @throws ParseException if the options can not parse the arguments. */ - public ArgumentContext(final File workingDirectory, final CommandLine commandLine) { - this(workingDirectory, new ReportConfiguration(), commandLine); + public ArgumentContext(final File workingDirectory, final Options opts, final String[] args) throws ParseException { + this(workingDirectory, new ReportConfiguration(), opts, args); } /** diff --git a/apache-rat-core/src/main/java/org/apache/rat/commandline/StyleSheets.java b/apache-rat-core/src/main/java/org/apache/rat/commandline/StyleSheets.java index c73a9e4ef..2e110a7db 100644 --- a/apache-rat-core/src/main/java/org/apache/rat/commandline/StyleSheets.java +++ b/apache-rat-core/src/main/java/org/apache/rat/commandline/StyleSheets.java @@ -21,12 +21,11 @@ import java.io.InputStream; import java.net.URL; import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; import java.util.Objects; -import org.apache.commons.io.function.IOSupplier; import org.apache.rat.ConfigurationException; +import org.apache.rat.ReportConfiguration; +import org.apache.rat.document.DocumentName; import static java.lang.String.format; @@ -49,7 +48,11 @@ public enum StyleSheets { /** * The plain style sheet. The current default. */ - XML("xml", "Produces output in pretty-printed XML."); + XML("xml", "Produces output in pretty-printed XML."), + /** + * Official HTML5 stylesheet. + */ + XHTML5("xhtml5", "Produces a HTML5 report"); /** * The name of the style sheet. Must map to bundled resource xslt file */ @@ -70,29 +73,31 @@ public enum StyleSheets { } /** - * Gets the IOSupplier for a style sheet. - * @return an IOSupplier for the sheet. + * Gets the IODescriptor for a style sheet. + * @return an IODescriptor for the sheet. */ - public IOSupplier getStyleSheet() { - return Objects.requireNonNull(StyleSheets.class.getClassLoader().getResource(format("org/apache/rat/%s.xsl", name)), - "missing stylesheet: " + name)::openStream; + public ReportConfiguration.IODescriptor getStyleSheet() { + URL url = StyleSheets.class.getClassLoader().getResource(format("org/apache/rat/%s.xsl", name)); + Objects.requireNonNull(url, "missing stylesheet: " + name); + return new ReportConfiguration.IODescriptor<>(name, url::openStream); } /** - * Gets the IOSupplier for a style sheet. + * Gets the IODescriptor for a style sheet. * @param name the short name for or the path to a style sheet. - * @return the IOSupplier for the style sheet. + * @param workingDirectory the working directory to resolve the name against. + * @return the IODescriptor for the style sheet. */ - public static IOSupplier getStyleSheet(final String name) { + public static ReportConfiguration.IODescriptor getStyleSheet(final String name, final DocumentName workingDirectory) { URL url = StyleSheets.class.getClassLoader().getResource(format("org/apache/rat/%s.xsl", name)); if (url != null) { - return url::openStream; + return new ReportConfiguration.IODescriptor<>(name, url::openStream); } - Path p = Paths.get(name); - if (p.toFile().exists()) { - return () -> Files.newInputStream(p); + DocumentName xslt = workingDirectory.resolve(name); + if (xslt.asFile().exists()) { + return new ReportConfiguration.IODescriptor<>(xslt.toString(), () -> Files.newInputStream(xslt.asFile().toPath())); } - throw new ConfigurationException(format("Stylesheet file '%s' not found", name)); + throw new ConfigurationException(format("Stylesheet file '%s' not found: %s", name, xslt.getName())); } /** diff --git a/apache-rat-core/src/main/java/org/apache/rat/config/exclusion/ExclusionProcessor.java b/apache-rat-core/src/main/java/org/apache/rat/config/exclusion/ExclusionProcessor.java index 7e7474b00..1ddce2e8a 100644 --- a/apache-rat-core/src/main/java/org/apache/rat/config/exclusion/ExclusionProcessor.java +++ b/apache-rat-core/src/main/java/org/apache/rat/config/exclusion/ExclusionProcessor.java @@ -22,15 +22,22 @@ import java.util.ArrayList; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.TreeSet; +import java.util.function.Predicate; import java.util.stream.Collectors; +import org.apache.commons.lang3.NotImplementedException; +import org.apache.rat.configuration.XMLConfigurationReader; import org.apache.rat.document.DocumentName; import org.apache.rat.document.DocumentNameMatcher; +import org.apache.rat.report.xml.writer.IXmlWriter; import org.apache.rat.utils.DefaultLog; import org.apache.rat.utils.ExtendedIterator; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; import static java.lang.String.format; @@ -74,7 +81,13 @@ public ExclusionProcessor() { excludedCollections = new HashSet<>(); } - /** Reset the {@link #lastMatcher} and {@link #lastMatcherBaseDir} to start again */ + public Serde serde() { + return new Serde(); + } + + /** + * Reset the {@link #lastMatcher} and {@link #lastMatcherBaseDir} to start again + */ private void resetLastMatcher() { lastMatcher = null; lastMatcherBaseDir = null; @@ -203,7 +216,7 @@ public ExclusionProcessor addExcludedCollection(final StandardCollection collect public DocumentNameMatcher getNameMatcher(final DocumentName basedir) { // if lastMatcher is not set or the basedir is not the same as the last one then // we have to regenerate the matching document. - // Otherwise we can just return the lastMatcher since there is no change. + // Otherwise, we can just return the lastMatcher since there is no change. if (lastMatcher == null || !basedir.equals(lastMatcherBaseDir)) { lastMatcherBaseDir = basedir; @@ -337,4 +350,75 @@ private void extractPaths(final MatcherSet.Builder matcherBuilder) { } } + public class Serde { + public void serialize(final IXmlWriter writer) throws IOException { + writer.openElement("ExclusionProcessor"); + + for (String pattern : excludedPatterns) { + writer.openElement("excludedPattern").attribute("pattern", pattern).closeElement(); + } + for (StandardCollection obj : excludedCollections) { + writer.openElement("excludedCollection").attribute("name", obj.name()).closeElement(); + } + for (DocumentNameMatcher obj : excludedPaths) { + writer.openElement("excludedPath").attribute("name", obj.toString()).closeElement(); + } + + for (String pattern : includedPatterns) { + writer.openElement("includedPattern").attribute("pattern", pattern).closeElement(); + } + for (StandardCollection obj : includedCollections) { + writer.openElement("includedCollection").attribute("name", obj.name()).closeElement(); + } + for (DocumentNameMatcher obj : includedPaths) { + writer.openElement("includedPath").attribute("name", obj.toString()).closeElement(); + } + + for (StandardCollection obj : fileProcessors) { + writer.openElement("fileProcessor").attribute("name", obj.name()).closeElement(); + } + writer.closeElement(); + + } + + public void deserialize(final Node n) { + final NodeList children = n.getChildNodes(); + for (int i = 0; i < children.getLength(); i++) { + Node child = children.item(i); + Map attributes = XMLConfigurationReader.attributes(child); + StandardCollection collection; + switch (child.getNodeName()) { + case "excludedPattern": + excludedPatterns.add(attributes.get("pattern")); + break; + case "excludedCollection": + collection = StandardCollection.valueOf(attributes.get("name")); + excludedCollections.add(collection); + break; + case "excludedPath": + excludedPaths.add(new DocumentNameMatcher(attributes.get("name"), + (Predicate) x -> { + throw new NotImplementedException("Deserialized ExclusionProcessor can not evaluate paths"); + })); + break; + case "includedPattern": + includedPatterns.add(attributes.get("pattern")); + break; + case "includedCollection": + collection = StandardCollection.valueOf(attributes.get("name")); + includedCollections.add(collection); + break; + case "includedPath": + includedPaths.add(new DocumentNameMatcher(attributes.get("name"), + (Predicate) x -> { + throw new NotImplementedException("Deserialized ExclusionProcessor can not evaluate paths"); + })); + break; + default: + throw new NotImplementedException("Deserialization for %s is not implemented", child.getNodeName()); + } + + } + } + } } diff --git a/apache-rat-core/src/main/java/org/apache/rat/config/parameters/Description.java b/apache-rat-core/src/main/java/org/apache/rat/config/parameters/Description.java index 6aa20bd38..a4e579dab 100644 --- a/apache-rat-core/src/main/java/org/apache/rat/config/parameters/Description.java +++ b/apache-rat-core/src/main/java/org/apache/rat/config/parameters/Description.java @@ -37,7 +37,7 @@ /** * A description of a component. */ -public class Description { +public final class Description { /** The type of component this describes */ private final ComponentType type; /** diff --git a/apache-rat-core/src/main/java/org/apache/rat/config/results/ClaimValidator.java b/apache-rat-core/src/main/java/org/apache/rat/config/results/ClaimValidator.java index f295f276b..b4f81da32 100644 --- a/apache-rat-core/src/main/java/org/apache/rat/config/results/ClaimValidator.java +++ b/apache-rat-core/src/main/java/org/apache/rat/config/results/ClaimValidator.java @@ -23,6 +23,7 @@ import java.util.concurrent.ConcurrentHashMap; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.mutable.MutableInt; import org.apache.rat.report.claim.ClaimStatistic; import org.apache.rat.utils.DefaultLog; @@ -35,11 +36,11 @@ public final class ClaimValidator { /** * The map of max counter limits. */ - private final ConcurrentHashMap max = new ConcurrentHashMap<>(); + private final ConcurrentHashMap max = new ConcurrentHashMap<>(); /** * The map of min counter limits. */ - private final ConcurrentHashMap min = new ConcurrentHashMap<>(); + private final ConcurrentHashMap min = new ConcurrentHashMap<>(); /** * {@code true} if errors were detected in the claim. */ @@ -51,8 +52,8 @@ public final class ClaimValidator { public ClaimValidator() { for (ClaimStatistic.Counter counter : ClaimStatistic.Counter.values()) { max.put(counter, - counter.getDefaultMaxValue() < 0 ? Integer.MAX_VALUE : counter.getDefaultMaxValue()); - min.put(counter, counter.getDefaultMinValue()); + new MutableInt(counter.getDefaultMaxValue() < 0 ? Integer.MAX_VALUE : counter.getDefaultMaxValue())); + min.put(counter, new MutableInt(counter.getDefaultMinValue())); } } @@ -70,22 +71,28 @@ public boolean hasErrors() { * @param value the value to set. A negative value specifies no maximum value. */ public void setMax(final ClaimStatistic.Counter counter, final int value) { - if (value < 0) { - max.put(counter, Integer.MAX_VALUE); - } else { - max.put(counter, value); - } - min.compute(counter, (k, v) -> v != null && v > max.get(k) ? max.get(k) : v); + MutableInt maxValue = max.compute(counter, (k, v) -> { + v.setValue(value < 0 ? Integer.MAX_VALUE : value); + return v; }); + min.compute(counter, (k, v) -> { + if (v.intValue() > maxValue.intValue()) { + v.setValue(maxValue.intValue()); + } + return v; }); } /** - * Sets the max value for the specified counter. + * Sets the min value for the specified counter. * @param counter the counter to set the limit for. * @param value the value to set. A negative value specifies no maximum value. */ public void setMin(final ClaimStatistic.Counter counter, final int value) { - min.put(counter, value); - max.compute(counter, (k, v) -> v == null || v < value ? value : v); + min.put(counter, new MutableInt(value)); + max.compute(counter, (k, v) -> { + if (v.intValue() < value) { + v.setValue(value); + } + return v; }); } /** @@ -94,8 +101,7 @@ public void setMin(final ClaimStatistic.Counter counter, final int value) { * @return the limit for the counter or 0 if not set. */ public int getMax(final ClaimStatistic.Counter counter) { - Integer result = max.get(counter); - return result == null ? 0 : result; + return max.get(counter).intValue(); } /** @@ -104,8 +110,7 @@ public int getMax(final ClaimStatistic.Counter counter) { * @return the limit for the counter or 0 if not set. */ public int getMin(final ClaimStatistic.Counter counter) { - Integer result = min.get(counter); - return result == null ? 0 : result; + return min.get(counter).intValue(); } /** @@ -115,7 +120,7 @@ public int getMin(final ClaimStatistic.Counter counter) { * @return {@code true} if the count is within the limits, {@code false} otherwise. */ public boolean isValid(final ClaimStatistic.Counter counter, final int count) { - boolean result = max.get(counter) >= count && min.get(counter) <= count; + boolean result = max.get(counter).intValue() >= count && min.get(counter).intValue() <= count; hasErrors |= !result; return result; } diff --git a/apache-rat-core/src/main/java/org/apache/rat/configuration/XMLConfigurationReader.java b/apache-rat-core/src/main/java/org/apache/rat/configuration/XMLConfigurationReader.java index 4d3e1a882..630558007 100644 --- a/apache-rat-core/src/main/java/org/apache/rat/configuration/XMLConfigurationReader.java +++ b/apache-rat-core/src/main/java/org/apache/rat/configuration/XMLConfigurationReader.java @@ -63,6 +63,8 @@ import org.xml.sax.InputSource; import org.xml.sax.SAXException; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + /** * A class that reads the XML configuration file format. */ @@ -191,7 +193,7 @@ public void read(final URI... uris) { * @param list the NodeList to process * @param consumer the consumer to apply to each node in the list. */ - private void nodeListConsumer(final NodeList list, final Consumer consumer) { + public static void nodeListConsumer(final NodeList list, final Consumer consumer) { for (int i = 0; i < list.getLength(); i++) { consumer.accept(list.item(i)); } @@ -217,7 +219,7 @@ public void add(final Document newDoc) { * @param node The node to process * @return the map of attributes on the node. */ - private Map attributes(final Node node) { + public static Map attributes(final Node node) { NamedNodeMap nnm = node.getAttributes(); Map result = new HashMap<>(); for (int i = 0; i < nnm.getLength(); i++) { @@ -624,6 +626,7 @@ public void readMatcherBuilders() { nodeListConsumer(document.getElementsByTagName(XMLConfig.MATCHER), this::parseMatcherBuilder); } + @SuppressFBWarnings("URLCONNECTION_SSRF_FD") @Override public void addMatchers(final URI uri) { read(uri); diff --git a/apache-rat-core/src/main/java/org/apache/rat/configuration/XMLConfigurationWriter.java b/apache-rat-core/src/main/java/org/apache/rat/configuration/XMLConfigurationWriter.java index 79e7da998..fb9ec018e 100644 --- a/apache-rat-core/src/main/java/org/apache/rat/configuration/XMLConfigurationWriter.java +++ b/apache-rat-core/src/main/java/org/apache/rat/configuration/XMLConfigurationWriter.java @@ -44,6 +44,8 @@ import org.apache.rat.report.xml.writer.IXmlWriter; import org.apache.rat.report.xml.writer.XmlWriter; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + /** * Writes the XML configuration file format. */ @@ -59,6 +61,7 @@ public class XMLConfigurationWriter { * Constructor * @param configuration the configuration information to write. */ + @SuppressFBWarnings("EI_EXPOSE_REP2") public XMLConfigurationWriter(final ReportConfiguration configuration) { this.configuration = configuration; this.matchers = new HashSet<>(); @@ -126,9 +129,8 @@ public void write(final IXmlWriter writer) throws RatException { writer.closeElement(); // APPROVED // matchers section - MatcherBuilderTracker tracker = MatcherBuilderTracker.instance(); writer.openElement(XMLConfig.MATCHERS); - for (Class clazz : tracker.getClasses()) { + for (Class clazz : MatcherBuilderTracker.instance().getClasses()) { writer.openElement(XMLConfig.MATCHER).attribute(XMLConfig.ATT_CLASS_NAME, clazz.getCanonicalName()) .closeElement(); } diff --git a/apache-rat-core/src/main/java/org/apache/rat/configuration/builders/ChildContainerBuilder.java b/apache-rat-core/src/main/java/org/apache/rat/configuration/builders/ChildContainerBuilder.java index d40a657d2..172314fbe 100644 --- a/apache-rat-core/src/main/java/org/apache/rat/configuration/builders/ChildContainerBuilder.java +++ b/apache-rat-core/src/main/java/org/apache/rat/configuration/builders/ChildContainerBuilder.java @@ -59,7 +59,7 @@ protected ChildContainerBuilder() { */ public AbstractBuilder setResource(final String resourceName) { // this method is called by reflection - URL url = this.getClass().getResource(resourceName); + URL url = AbstractBuilder.class.getResource(resourceName); if (url == null) { throw new ConfigurationException("Unable to read matching text file: " + resourceName); } diff --git a/apache-rat-core/src/main/java/org/apache/rat/configuration/builders/MatcherRefBuilder.java b/apache-rat-core/src/main/java/org/apache/rat/configuration/builders/MatcherRefBuilder.java index 68598d4c7..1662220f3 100644 --- a/apache-rat-core/src/main/java/org/apache/rat/configuration/builders/MatcherRefBuilder.java +++ b/apache-rat-core/src/main/java/org/apache/rat/configuration/builders/MatcherRefBuilder.java @@ -27,6 +27,8 @@ import org.apache.rat.config.parameters.ConfigComponent; import org.apache.rat.config.parameters.MatcherBuilder; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + /** * A reference matching Matcher builder. *

@@ -62,6 +64,7 @@ public MatcherRefBuilder setRefId(final String refId) { * @param matchers the Map of ids to instances. * @return this builder for chaining. */ + @SuppressFBWarnings(value = "EI_EXPOSE_REP2", justification = "Expected external update of matchers.") public MatcherRefBuilder setMatcherMap(final Map matchers) { // this method is called by reflection this.matchers = matchers; @@ -109,6 +112,7 @@ public static class IHeaderMatcherProxy implements IHeaderMatcher { * @param proxyId the id of the matcher to find. * @param matchers a mapping of matchers that have been found. */ + @SuppressFBWarnings(value = "EI_EXPOSE_REP2", justification = "Expected external update of matchers.") public IHeaderMatcherProxy(final String proxyId, final Map matchers) { this.proxyId = proxyId; this.matchers = matchers; diff --git a/apache-rat-core/src/main/java/org/apache/rat/document/ArchiveEntryDocument.java b/apache-rat-core/src/main/java/org/apache/rat/document/ArchiveEntryDocument.java deleted file mode 100644 index dabc848bc..000000000 --- a/apache-rat-core/src/main/java/org/apache/rat/document/ArchiveEntryDocument.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one * - * or more contributor license agreements. See the NOTICE file * - * distributed with this work for additional information * - * regarding copyright ownership. The ASF licenses this file * - * to you under the Apache License, Version 2.0 (the * - * "License"); you may not use this file except in compliance * - * with the License. You may obtain a copy of the License at * - * * - * http://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, * - * software distributed under the License is distributed on an * - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * - * KIND, either express or implied. See the License for the * - * specific language governing permissions and limitations * - * under the License. * - */ - -package org.apache.rat.document; - -import java.io.ByteArrayInputStream; -import java.io.InputStream; -import java.util.Collections; -import java.util.SortedSet; - -import org.apache.rat.api.Document; - -/** - * A Document that wraps an Archive entry. - */ -public class ArchiveEntryDocument extends Document { - - /** The contents of the entry */ - private final byte[] contents; - - /** - * Creates an Archive entry. - * @param entryName the name of this entry from outside the archive. - * @param contents the contents of the entry. - * @param nameMatcher the name matcher to filter contents with. - */ - public ArchiveEntryDocument(final ArchiveEntryName entryName, final byte[] contents, final DocumentNameMatcher nameMatcher) { - super(entryName, nameMatcher); - this.contents = contents; - } - - @Override - public InputStream inputStream() { - return new ByteArrayInputStream(contents); - } - - @Override - public boolean isDirectory() { - return false; - } - - @Override - public SortedSet listChildren() { - return Collections.emptySortedSet(); - } -} diff --git a/apache-rat-core/src/main/java/org/apache/rat/document/ArchiveEntryName.java b/apache-rat-core/src/main/java/org/apache/rat/document/ArchiveEntryName.java index 13b4668e6..25bc2c041 100644 --- a/apache-rat-core/src/main/java/org/apache/rat/document/ArchiveEntryName.java +++ b/apache-rat-core/src/main/java/org/apache/rat/document/ArchiveEntryName.java @@ -28,11 +28,11 @@ public class ArchiveEntryName extends DocumentName { private final DocumentName archiveFileName; private static DocumentName.Builder prepareBuilder(final DocumentName archiveFileName, final String archiveEntryName) { - String root = archiveFileName.getName() + "#"; + String root = archiveFileName.getName() + "#/"; FSInfo fsInfo = new FSInfo("archiveEntry", "/", true, Collections.singletonList(root)); return DocumentName.builder(fsInfo) .setRoot(root) - .setBaseName(root + "/") + .setBaseName("/") .setName(archiveEntryName); } public ArchiveEntryName(final DocumentName archiveFileName, final String archiveEntryName) { @@ -71,14 +71,4 @@ public String localized(final String dirSeparator) { superLocal = superLocal.substring(superLocal.lastIndexOf("#") + 1); return archiveFileName.localized(dirSeparator) + "#" + superLocal; } - - @Override - public boolean equals(final Object other) { - return super.equals(other); - } - - @Override - public int hashCode() { - return super.hashCode(); - } } diff --git a/apache-rat-core/src/main/java/org/apache/rat/document/DocumentName.java b/apache-rat-core/src/main/java/org/apache/rat/document/DocumentName.java index c2c0510e9..d8ddfa2d1 100644 --- a/apache-rat-core/src/main/java/org/apache/rat/document/DocumentName.java +++ b/apache-rat-core/src/main/java/org/apache/rat/document/DocumentName.java @@ -45,8 +45,8 @@ * The name for a document. The {@code DocumentName} is an immutable structure that handles all the intricacies of file * naming on various operating systems. DocumentNames have several components: *

    - *
  • {@code root} - where in the file system the name starts (e.g C: on windows). May be empty but not null.
  • - *
  • {@code dirSeparator} - the separator between name segments (e.g. "\\" on windows, "/" on linux). May not be + *
  • {@code root} - where in the file system the name starts (e.g C:\ on Microsoft Windows). May be empty but not null.
  • + *
  • {@code dirSeparator} - the separator between name segments (e.g. "\" on MicroSoft Windows, "/" on linux). May not be * empty or null.
  • *
  • {@code name} - the name of the file relative to the {@code root}. May not be null. Does NOT begin with a {@code dirSeparator}
  • *
  • {@code baseName} - the name of a directory or file from which this file is reported. A DocumentName with a @@ -63,9 +63,10 @@ public class DocumentName implements Comparable { private final String name; /** The name of the base directory for the document. */ private final DocumentName baseName; + // visible for testing /** The file system info for this document. */ private final FSInfo fsInfo; - /** The root for the DocumentName. May be empty but not null. */ + /** The root for the DocumentName. May be empty but not null. Must be one of the roots in fsInfo*/ private final String root; /** @@ -126,7 +127,7 @@ public static Builder builder(final DocumentName documentName) { } /** - * Creates a file from the document name. + * Creates a file from the fully qualified document name. * @return a new File object. */ public File asFile() { @@ -134,7 +135,8 @@ public File asFile() { } /** - * Creates a path from the document name. + * Creates a path from the document name. This method uses the fullyqualified name without the root. + * this results in a relative file name from the root. * @return a new Path object. */ public Path asPath() { @@ -144,9 +146,23 @@ public Path asPath() { /** * Creates a new DocumentName by adding the child to the current name. * Resulting documentName will have the same base name. + * Directory separator is normalized to the directory separator for this file system. + * If the child string: + *
    + *
    Is blank
    + *
    This DocumentName is returned.
    + *
    Starts with the file system root
    + *
    The root The root must match the root of this DocumentName and the directory structure + * must start with the directory structure of the basename for this DocuemntName.
    + *
    Starts with the directory separator character
    + *
    Result will be a tree starting at the directory specified by the basename.
    + *
    Does not start with a directory separator character
    + *
    Result will be a tree starting at the directory specified by this DocumentName
    + *
    * @param child the child to add (must use directory separator from this document name). * @return the new document name with the same {@link #baseName}, directory sensitivity and case sensitivity as * this one. + * @throws IllegalArgumentException if the child specifies a different root from this document name. */ public DocumentName resolve(final String child) { if (StringUtils.isBlank(child)) { @@ -156,8 +172,25 @@ public DocumentName resolve(final String child) { String pattern = separator.equals("/") ? child.replace('\\', '/') : child.replace('/', '\\'); + Optional root = fsInfo.rootFor(child); + if (root.isPresent()) { + if (!root.get().equals(getRoot())) { + throw new IllegalArgumentException(String.format("%s does not start with %s", pattern, getName())); + } + if (!getRoot().equals(separator)) { + // we have something like C:\ as the root so convert the pattern to start with the separator. + pattern = separator + pattern.substring(getRoot().length()); + if (pattern.startsWith(baseName.name)) { + pattern = pattern.substring(baseName.name.length()); + } + } + } + + // patterns with separators either start with the name of this document plus a relative + // name, or are just directory off the baseName. In either case the name is correct. + // so just handle the relative case. if (!pattern.startsWith(separator)) { - pattern = name + separator + pattern; + pattern = name + separator + pattern; } return new Builder(this).setName(fsInfo.normalize(pattern)).build(); @@ -168,7 +201,7 @@ public DocumentName resolve(final String child) { * @return the fully qualified name of the document. */ public String getName() { - return root + fsInfo.dirSeparator() + name; + return root + name; } /** @@ -203,6 +236,10 @@ public String getDirectorySeparator() { return fsInfo.dirSeparator(); } + public FSInfo fsInfo() { + return fsInfo; + } + /** * Determines if the candidate starts with the root or separator strings. * @param candidate the candidate to check. If blank method will return {@code false}. @@ -287,17 +324,23 @@ public String toString() { @Override public int compareTo(final DocumentName other) { - return CompareToBuilder.reflectionCompare(this, other); + return new CompareToBuilder() + .append(this.root, other.root) + .append(this.getBaseName(), other.getBaseName()) + .append(this.getName(), other.getName()).build(); } @Override - public boolean equals(final Object other) { - return EqualsBuilder.reflectionEquals(this, other); + public final boolean equals(final Object other) { + if (other instanceof DocumentName otherDocumentName) { + return compareTo(otherDocumentName) == 0; + } + return false; } @Override - public int hashCode() { - return HashCodeBuilder.reflectionHashCode(this); + public final int hashCode() { + return new HashCodeBuilder().append(root).append(getBaseName()).append(getName()).toHashCode(); } private static final class FSInfoData { @@ -468,6 +511,14 @@ public Optional rootFor(final String name) { return Optional.empty(); } + /** + * Gets the array of roots for this file system. + * @return an array of roots for this file system. + */ + public String[] roots() { + return data.roots.toArray(new String[0]); + } + /** * Tokenizes the string based on the directory separator of this DocumentName. * @param source the source to tokenize. @@ -502,6 +553,16 @@ public String normalize(final String pattern) { return parts.stream().filter(Objects::nonNull).collect(Collectors.joining(dirSeparator())); } + /** + * Creates a path separated by the directory separator. + * Starting with an empty string will cause the directory separateor to appear at the beginning. + * @param segments the segments that make up the path. + * @return the path string. + */ + public String mkPath(final String... segments) { + return String.join(dirSeparator(), segments); + } + @Override public int compareTo(final FSInfo other) { return CompareToBuilder.reflectionCompare(this, other); @@ -538,7 +599,7 @@ public static final class Builder { */ private Builder(final FSInfo fsInfo) { this.fsInfo = fsInfo; - root = ""; + root = fsInfo.data.roots.get(0); } /** @@ -625,12 +686,6 @@ public Builder setName(final String name) { this.root = pair.getLeft(); } this.name = fsInfo.normalize(pair.getRight()); - if (this.baseName != null && !baseName.name.isEmpty()) { - if (!this.name.startsWith(baseName.name)) { - this.name = this.name.isEmpty() ? baseName.name : - baseName.name + fsInfo.dirSeparator() + this.name; - } - } return this; } @@ -644,16 +699,15 @@ public Builder setName(final String name) { */ Pair splitRoot(final String name) { String workingName = name; - Optional maybeRoot = fsInfo.rootFor(name); - String root = maybeRoot.orElse(""); + String root = fsInfo.rootFor(name).orElse(""); if (!root.isEmpty()) { if (workingName.startsWith(root)) { workingName = workingName.substring(root.length()); - if (!workingName.startsWith(fsInfo.dirSeparator())) { - if (root.endsWith(fsInfo.dirSeparator())) { - root = root.substring(0, root.length() - fsInfo.dirSeparator().length()); - } - } +// if (!workingName.startsWith(fsInfo.dirSeparator())) { +// if (root.endsWith(fsInfo.dirSeparator())) { +// root = root.substring(0, root.length() - fsInfo.dirSeparator().length()); +// } +// } } } return ImmutablePair.of(root, workingName); @@ -751,6 +805,26 @@ public Builder setBaseName(final File file) { */ public DocumentName build() { verify(); + if (this.baseName != null) { + if (!this.name.startsWith(baseName.name)) { + this.name = this.name.isEmpty() ? baseName.name : + baseName.name + fsInfo.dirSeparator() + this.name; + } + if (!this.baseName.getRoot().equals(root)) { + Builder builder = new Builder(baseName).setRoot(root); + if (baseName.baseName != null && baseName.baseName != baseName) { + builder.setBaseName(baseName.baseName); + } else { + builder.baseName = null; + builder.sameNameFlag = true; + } + this.baseName = builder.build(); + } + } else { + if (this.name.startsWith(root)) { + this.name = this.name.substring(root.length()); + } + } return new DocumentName(this); } } diff --git a/apache-rat-core/src/main/java/org/apache/rat/header/HeaderMatcher.java b/apache-rat-core/src/main/java/org/apache/rat/header/HeaderMatcher.java index 89b71b0a2..7f05afa3a 100644 --- a/apache-rat-core/src/main/java/org/apache/rat/header/HeaderMatcher.java +++ b/apache-rat-core/src/main/java/org/apache/rat/header/HeaderMatcher.java @@ -18,6 +18,7 @@ */ package org.apache.rat.header; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import org.apache.rat.DeprecationReporter; import java.io.IOException; @@ -35,6 +36,7 @@ *

    Note: use only from a single thread.

    * */ +@SuppressFBWarnings("EI_EXPOSE_REP2") @Deprecated // since 0.17 @DeprecationReporter.Info(since = "0.17", forRemoval = true) public class HeaderMatcher { diff --git a/apache-rat-core/src/main/java/org/apache/rat/help/Licenses.java b/apache-rat-core/src/main/java/org/apache/rat/help/Licenses.java index 0277186b0..bac55af54 100644 --- a/apache-rat-core/src/main/java/org/apache/rat/help/Licenses.java +++ b/apache-rat-core/src/main/java/org/apache/rat/help/Licenses.java @@ -43,6 +43,8 @@ import org.apache.rat.license.ILicenseFamily; import org.apache.rat.license.LicenseSetFactory.LicenseFilter; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + import static java.lang.String.format; /** @@ -64,6 +66,7 @@ public final class Licenses extends AbstractHelp { * @param config The configuration that contains the license information. * @param writer the writer to write the report to. */ + @SuppressFBWarnings("EI_EXPOSE_REP2") public Licenses(final ReportConfiguration config, final Writer writer) { this.config = config; this.licenses = config.getLicenses(LicenseFilter.ALL); diff --git a/apache-rat-core/src/main/java/org/apache/rat/license/LicenseSetFactory.java b/apache-rat-core/src/main/java/org/apache/rat/license/LicenseSetFactory.java index 6fe00db5a..8da3b193e 100644 --- a/apache-rat-core/src/main/java/org/apache/rat/license/LicenseSetFactory.java +++ b/apache-rat-core/src/main/java/org/apache/rat/license/LicenseSetFactory.java @@ -20,13 +20,19 @@ import java.util.Collection; import java.util.Collections; +import java.util.HashSet; import java.util.Optional; +import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import java.util.function.Predicate; +import java.util.stream.Collectors; +import org.apache.commons.collections4.set.UnmodifiableSortedSet; +import org.apache.rat.ConfigurationException; import org.apache.rat.analysis.IHeaderMatcher; import org.apache.rat.analysis.IHeaders; +import org.apache.rat.utils.DefaultLog; import org.apache.rat.utils.Log; import org.apache.rat.utils.ReportingSet; @@ -121,6 +127,31 @@ public LicenseSetFactory(final SortedSet licenses) { licenses.forEach(l -> families.addIfNotPresent(l.getLicenseFamily())); } + public void validate() { + Log log = DefaultLog.getInstance(); + + // verify license definitions exist + if (getLicenses(LicenseFilter.ALL).isEmpty()) { + String msg = "At least one license must be defined"; + log.error(msg); + throw new ConfigurationException(msg); + } + + // verify that all approved license families exist + Set exists = getLicenseFamilies(LicenseFilter.ALL) + .stream().map(ILicenseFamily::getFamilyCategory).collect(Collectors.toSet()); + Set approved = new HashSet<>(approvedLicenseCategories); + approved.removeIf(exists::contains); + approved.forEach(name -> log.warn(String.format("License category '%s' was approved but does not exist.", name))); + + // verify that all approved licenses exist + exists = getLicenses(LicenseFilter.ALL) + .stream().map(ILicense::getId).collect(Collectors.toSet()); + approved = new HashSet<>(approvedLicenseIds); + approved.removeIf(exists::contains); + approved.forEach(name -> log.warn(String.format("License '%s' was approved but does not exist.", name))); + } + public void add(final LicenseSetFactory other) { this.families.addAll(other.families); this.licenses.addAll(other.licenses); @@ -289,18 +320,21 @@ public Predicate getApprovedLicensePredicate() { * @param filter the types of LicenseFamily objects to return. * @return a SortedSet of ILicense objects. */ - public SortedSet getLicenses(final LicenseFilter filter) { + public UnmodifiableSortedSet getLicenses(final LicenseFilter filter) { + SortedSet result = new TreeSet<>(); switch (filter) { case ALL: - return Collections.unmodifiableSortedSet(licenses); + result = licenses; + break; case APPROVED: - SortedSet result = new TreeSet<>(); + result = new TreeSet<>(); licenses.stream().filter(getApprovedLicensePredicate()).forEach(result::add); - return result; + break; case NONE: default: - return Collections.emptySortedSet(); + result = Collections.emptySortedSet(); } + return (UnmodifiableSortedSet) UnmodifiableSortedSet.unmodifiableSortedSet(result); } /** diff --git a/apache-rat-core/src/main/java/org/apache/rat/report/IReportable.java b/apache-rat-core/src/main/java/org/apache/rat/report/IReportable.java index 2c108af6c..a92ba0599 100644 --- a/apache-rat-core/src/main/java/org/apache/rat/report/IReportable.java +++ b/apache-rat-core/src/main/java/org/apache/rat/report/IReportable.java @@ -33,5 +33,5 @@ public interface IReportable { * Returns the DocumentName for the reportable. * @return the DocumentName for the reportable. */ - DocumentName getName(); + DocumentName name(); } diff --git a/apache-rat-core/src/main/java/org/apache/rat/report/claim/ClaimStatistic.java b/apache-rat-core/src/main/java/org/apache/rat/report/claim/ClaimStatistic.java index 62572916c..d47f2e5b7 100644 --- a/apache-rat-core/src/main/java/org/apache/rat/report/claim/ClaimStatistic.java +++ b/apache-rat-core/src/main/java/org/apache/rat/report/claim/ClaimStatistic.java @@ -19,14 +19,26 @@ package org.apache.rat.report.claim; +import java.io.IOException; +import java.io.InputStream; import java.util.ArrayList; import java.util.Comparator; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.apache.commons.io.function.IOSupplier; import org.apache.commons.lang3.StringUtils; +import org.apache.rat.ConfigurationException; import org.apache.rat.api.Document; +import org.apache.rat.configuration.XMLConfigurationReader; +import org.apache.rat.report.xml.writer.XmlWriter; +import org.xml.sax.SAXException; /** * This class provides a numerical overview about @@ -113,6 +125,9 @@ public String displayName() { /** Map of counter type to value */ private final ConcurrentHashMap counterMap = new ConcurrentHashMap<>(); + public Serde serde() { + return new Serde(); + } /** * Converts null counter to 0. * @@ -141,6 +156,15 @@ public void incCounter(final Counter counter, final int value) { counterMap.compute(counter, (k, v) -> v == null ? new IntCounter().increment(value) : v.increment(value)); } + /** + * Increments the counts for the counter. + * @param counter the counter to increment. + * @param value the value to increment the counter by. + */ + public void setCounter(final Counter counter, final int value) { + counterMap.put(counter, new IntCounter().increment(value)); + } + /** * Gets the counts for the Document.Type. * @param documentType the Document.Type to get the counter for. @@ -288,5 +312,96 @@ public IntCounter increment(final int count) { public int value() { return value; } + + @Override + public String toString() { + return String.valueOf(value); + } + } + + /** + * Serialze and deserialze the claim Statistic. + */ + public class Serde { + + public void serialize(final Appendable appendable) throws IOException { + try (XmlWriter writer = new XmlWriter(appendable)) { + writer.startDocument().openElement("ClaimStatistic") + .openElement("licenseNameMap"); + for (Map.Entry entry : licenseNameMap.entrySet()) { + if (entry.getValue().value > 0) { + writer.openElement("licenseName") + .attribute("count", entry.getValue().toString()) + .attribute("name", entry.getKey()).closeElement(); + } + } + writer.closeElement() + .openElement("licenseFamilyCategoryMap"); + for (Map.Entry entry : licenseFamilyCategoryMap.entrySet()) { + if (entry.getValue().value > 0) { + writer.openElement("familyCategory") + .attribute("count", entry.getValue().toString()) + .attribute("name", entry.getKey()).closeElement(); + } + } + writer.closeElement() + .openElement("documentTypeMap"); + for (Map.Entry entry : documentTypeMap.entrySet()) { + if (entry.getValue().value > 0) { + writer.openElement("documentType") + .attribute("count", entry.getValue().toString()) + .attribute("name", entry.getKey().name()).closeElement(); + } + } + writer.closeElement() + .openElement("counterMap"); + for (Map.Entry entry : counterMap.entrySet()) { + if (entry.getValue().value > 0) { + writer.openElement("counter") + .attribute("count", entry.getValue().toString()) + .attribute("name", entry.getKey().name()).closeElement(); + } + } + writer.closeElement(); + } + } + + public void deserialize(final IOSupplier inputStreamSupplier) throws IOException { + DocumentBuilder builder; + try { + builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + } catch (ParserConfigurationException e) { + throw new ConfigurationException("Unable to create DOM builder", e); + } + org.w3c.dom.Document document; + try (InputStream stream = inputStreamSupplier.get()) { + document = builder.parse(stream); + + } catch (SAXException e) { + throw new IOException("Unable to read input", e); + } + + XMLConfigurationReader.nodeListConsumer(document.getElementsByTagName("licenseName"), node -> { + Map attributes = XMLConfigurationReader.attributes(node); + incLicenseNameCount(attributes.get("name"), Integer.parseInt(attributes.get("count"))); + }); + + XMLConfigurationReader.nodeListConsumer(document.getElementsByTagName("familyCategory"), node -> { + Map attributes = XMLConfigurationReader.attributes(node); + incLicenseCategoryCount(attributes.get("name"), Integer.parseInt(attributes.get("count"))); + }); + + XMLConfigurationReader.nodeListConsumer(document.getElementsByTagName("documentType"), node -> { + Map attributes = XMLConfigurationReader.attributes(node); + Document.Type type = Document.Type.valueOf(attributes.get("name")); + incCounter(type, Integer.parseInt(attributes.get("count"))); + }); + + XMLConfigurationReader.nodeListConsumer(document.getElementsByTagName("counter"), node -> { + Map attributes = XMLConfigurationReader.attributes(node); + Counter type = Counter.valueOf(attributes.get("name")); + setCounter(type, Integer.parseInt(attributes.get("count"))); + }); + } } } diff --git a/apache-rat-core/src/test/java/org/apache/rat/testhelpers/FileUtils.java b/apache-rat-core/src/main/java/org/apache/rat/utils/FileUtils.java similarity index 81% rename from apache-rat-core/src/test/java/org/apache/rat/testhelpers/FileUtils.java rename to apache-rat-core/src/main/java/org/apache/rat/utils/FileUtils.java index 5681e7972..50da3eccd 100644 --- a/apache-rat-core/src/test/java/org/apache/rat/testhelpers/FileUtils.java +++ b/apache-rat-core/src/main/java/org/apache/rat/utils/FileUtils.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * * under the License. * */ -package org.apache.rat.testhelpers; +package org.apache.rat.utils; import java.io.File; import java.io.FileWriter; @@ -25,15 +25,17 @@ import java.util.Arrays; import java.util.Collections; -import static org.assertj.core.api.Fail.fail; -public class FileUtils { +public final class FileUtils { + private FileUtils() { + // do not instantiate + } /** * Creates a directory if it does not exist. * @param dir the directory to make. */ - public static void mkDir(File dir) { + public static void mkDir(final File dir) { boolean ignored = dir.mkdirs(); } @@ -41,7 +43,7 @@ public static void mkDir(File dir) { * Deletes a file if it exists. * @param file the file to delete. */ - public static void delete(File file) { + public static void delete(final File file) { if (file.exists()) { if (file.isDirectory()) { try { @@ -62,15 +64,16 @@ public static void delete(File file) { * @param lines the lines to write into the file. * @return the new File. */ - static public File writeFile(File dir, final String name, final Iterable lines) { + public static File writeFile(final File dir, final String name, final Iterable lines) { if (dir == null) { - fail("base directory not specified"); + throw new IllegalArgumentException("base directory not specified"); } + mkDir(dir); File file = new File(dir, name); try (PrintWriter writer = new PrintWriter(new FileWriter(file))) { lines.forEach(writer::println); } catch (IOException e) { - fail(e.getMessage()); + throw new RuntimeException(e.getMessage(), e); } return file; } @@ -82,7 +85,7 @@ static public File writeFile(File dir, final String name, final Iterable * @param lines the lines to write into the file. * @return the new File. */ - static public File writeFile(File dir, final String name, final String... lines) { + public static File writeFile(final File dir, final String name, final String... lines) { return writeFile(dir, name, Arrays.asList(lines)); } @@ -92,7 +95,7 @@ static public File writeFile(File dir, final String name, final String... lines) * @param name the name of the file. * @return the new file. */ - public static File writeFile(File dir, String name) { + public static File writeFile(final File dir, final String name) { return writeFile(dir, name, Collections.singletonList(name)); } } diff --git a/apache-rat-core/src/main/java/org/apache/rat/walker/ArchiveWalker.java b/apache-rat-core/src/main/java/org/apache/rat/walker/ArchiveWalker.java index a50aa5ef8..4ad410449 100644 --- a/apache-rat-core/src/main/java/org/apache/rat/walker/ArchiveWalker.java +++ b/apache-rat-core/src/main/java/org/apache/rat/walker/ArchiveWalker.java @@ -20,12 +20,15 @@ package org.apache.rat.walker; import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.List; +import java.util.SortedSet; import org.apache.commons.compress.archivers.ArchiveEntry; import org.apache.commons.compress.archivers.ArchiveException; @@ -34,9 +37,9 @@ import org.apache.commons.io.IOUtils; import org.apache.rat.api.Document; import org.apache.rat.api.RatException; -import org.apache.rat.document.ArchiveEntryDocument; import org.apache.rat.document.ArchiveEntryName; import org.apache.rat.document.DocumentName; +import org.apache.rat.document.DocumentNameMatcher; import org.apache.rat.report.RatReport; import org.apache.rat.utils.DefaultLog; @@ -87,13 +90,29 @@ public Collection getDocuments() throws RatException { ArchiveEntry entry; while ((entry = input.getNextEntry()) != null) { if (!entry.isDirectory() && input.canReadEntryData(entry)) { - DocumentName innerName = DocumentName.builder().setName(entry.getName()) + final DocumentName innerName = DocumentName.builder().setName(entry.getName()) .setBaseName(".").build(); - if (this.getDocument().getNameMatcher().matches(innerName)) { + final DocumentNameMatcher documentNameMatcher = getDocument().getNameMatcher(); + if (documentNameMatcher.matches(innerName)) { + ArchiveEntryName entryName = new ArchiveEntryName(getDocument().getName(), entry.getName()); ByteArrayOutputStream baos = new ByteArrayOutputStream(); IOUtils.copy(input, baos); - ArchiveEntryName entryName = new ArchiveEntryName(getDocument().getName(), entry.getName()); - result.add(new ArchiveEntryDocument(entryName, baos.toByteArray(), getDocument().getNameMatcher())); + result.add(new Document(entryName, documentNameMatcher) { + @Override + public InputStream inputStream() { + return new ByteArrayInputStream(baos.toByteArray()); + } + + @Override + public boolean isDirectory() { + return false; + } + + @Override + public SortedSet listChildren() { + return Collections.emptySortedSet(); + } + }); } } } diff --git a/apache-rat-core/src/main/java/org/apache/rat/walker/FileListWalker.java b/apache-rat-core/src/main/java/org/apache/rat/walker/FileListWalker.java index 056090858..1898c4e84 100644 --- a/apache-rat-core/src/main/java/org/apache/rat/walker/FileListWalker.java +++ b/apache-rat-core/src/main/java/org/apache/rat/walker/FileListWalker.java @@ -72,7 +72,7 @@ private FileDocument createDocument(final String unixFileName) { @Override public void run(final RatReport report) throws RatException { DefaultLog.getInstance().debug(String.format("Reading file name: %s due to option %s", source, Arg.SOURCE.option())); - DocumentName sourceName = getName(); + DocumentName sourceName = name(); try (Reader reader = source.reader()) { for (String docName : IOUtils.readLines(reader)) { try { @@ -93,7 +93,7 @@ public void run(final RatReport report) throws RatException { } @Override - public DocumentName getName() { + public DocumentName name() { return source.getName(); } } diff --git a/apache-rat-core/src/main/java/org/apache/rat/walker/IReportableListWalker.java b/apache-rat-core/src/main/java/org/apache/rat/walker/IReportableListWalker.java index b028b9c68..8df62905e 100644 --- a/apache-rat-core/src/main/java/org/apache/rat/walker/IReportableListWalker.java +++ b/apache-rat-core/src/main/java/org/apache/rat/walker/IReportableListWalker.java @@ -61,13 +61,13 @@ public void run(final RatReport report) { try { reportable.run(report); } catch (RatException e) { - DefaultLog.getInstance().error("Error processing " + reportable.getName(), e); + DefaultLog.getInstance().error("Error processing " + reportable.name(), e); } } } @Override - public DocumentName getName() { + public DocumentName name() { return documentName; } diff --git a/apache-rat-core/src/main/java/org/apache/rat/walker/Walker.java b/apache-rat-core/src/main/java/org/apache/rat/walker/Walker.java index 5651f7ab9..f0e6d7bd4 100644 --- a/apache-rat-core/src/main/java/org/apache/rat/walker/Walker.java +++ b/apache-rat-core/src/main/java/org/apache/rat/walker/Walker.java @@ -48,7 +48,7 @@ protected Document getDocument() { } @Override - public DocumentName getName() { + public DocumentName name() { return document.getName(); } } diff --git a/apache-rat-core/src/main/resources/org/apache/rat/xhtml5.xsl b/apache-rat-core/src/main/resources/org/apache/rat/xhtml5.xsl new file mode 100644 index 000000000..b15224605 --- /dev/null +++ b/apache-rat-core/src/main/resources/org/apache/rat/xhtml5.xsl @@ -0,0 +1,354 @@ + + + + + + + + + + 🗜 + 🔠 + 🚫 + + + + 📂 + + + + + + + + + + + + + + + +

    Rat Report

    + + + + + + + + + + + + +

    Detail

    + +

    + Documents with unapproved licenses will start with a + The first character on the next line identifies the document type. +

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + Symbols used in this RAT report +
    SymbolType
    Archive file
    Binary file
    Ignored file
    Notice file
    Standard file
    Unknown file
    Directory
    + + + + + + + + + +
    + Resources discovered in this RAT report +
    + + + + + + + + + + + +
    + +
    + + +
    +
    + +
    +
    + + + + +
    + ID: + + UUID + + + + Family: + + Unknown + + + +
    +
    +
    +
    + + +

    Summary

    + +
    +

    Generated at by + +

    + + + + + + + + + + + + + + + +
    + Table 1: A summary of statistics from this RAT report. +
    Name:Count:Description:
    + + + + + Unknown + + +
    + +

    License Statistics

    +
    +

    Categories

    +
    + + + + + + + + + + + + + +
    + Table 2: License categories found in this RAT report. +
    Name:Count:
    + + + + + Unknown + + +
    +
    + +

    Licenses

    +
    + + + + + + + + + + + + + +
    + Table 3: Licenses found in this RAT report. +
    Name:Count:
    + + + + + Unknown + + +
    +
    +
    + +

    Document types

    +
    + + + + + + + + + + + + + +
    + Table 4: Document types found in this RAT report. +
    Name:Count:
    +
    +
    +
    + + +

    Files with unapproved licenses

    + +
      + +
    • +
      +
    +
    + + +

    Archives

    +
      + +
    • +
      +
    +
    + + + + + + + + + + + + + + + + + + +
    + +
    + + +
    +
    + +
    + + +
    +
    diff --git a/apache-rat-core/src/test/java/org/apache/rat/DefaultsTest.java b/apache-rat-core/src/test/java/org/apache/rat/DefaultsTest.java index bfd5f31e7..db9edb03f 100644 --- a/apache-rat-core/src/test/java/org/apache/rat/DefaultsTest.java +++ b/apache-rat-core/src/test/java/org/apache/rat/DefaultsTest.java @@ -22,6 +22,7 @@ import java.util.Set; import java.util.TreeSet; +import org.apache.commons.collections4.set.UnmodifiableSortedSet; import org.apache.rat.license.ILicense; import org.apache.rat.license.LicenseSetFactory.LicenseFilter; import org.junit.jupiter.api.Test; @@ -36,7 +37,7 @@ public class DefaultsTest { public void defaultConfigTest() { Defaults defaults = Defaults.builder().build(); - Set licenses = defaults.getLicenseSetFactory().getLicenses(LicenseFilter.ALL); + UnmodifiableSortedSet licenses = defaults.getLicenseSetFactory().getLicenses(LicenseFilter.ALL); Set names = new TreeSet<>(); licenses.forEach(x -> names.add(x.getLicenseFamily().getFamilyCategory())); diff --git a/apache-rat-core/src/test/java/org/apache/rat/OptionCollectionTest.java b/apache-rat-core/src/test/java/org/apache/rat/OptionCollectionTest.java index 64c11257a..51dad4d80 100644 --- a/apache-rat-core/src/test/java/org/apache/rat/OptionCollectionTest.java +++ b/apache-rat-core/src/test/java/org/apache/rat/OptionCollectionTest.java @@ -211,8 +211,7 @@ public void testShortenedOptions() throws IOException { @Test public void testDefaultConfiguration() throws ParseException { String[] empty = {}; - CommandLine cl = new DefaultParser().parse(OptionCollection.buildOptions(), empty); - ArgumentContext context = new ArgumentContext(new File("."), cl); + ArgumentContext context = new ArgumentContext(new File("."), OptionCollection.buildOptions(), empty); ReportConfiguration config = OptionCollection.createConfiguration(context); ReportConfigurationTest.validateDefault(config); } @@ -225,7 +224,7 @@ public void getReportableTest(String fName) throws IOException { ReportConfiguration config = OptionCollection.parseCommands(testPath.toFile(), new String[]{fName}, o -> fail("Help called"), false); IReportable reportable = OptionCollection.getReportable(base, config); assertThat(reportable).as(() -> format("'%s' returned null", fName)).isNotNull(); - assertThat(reportable.getName().getName()).isEqualTo(expected); + assertThat(reportable.name().getName()).isEqualTo(expected); } @Test diff --git a/apache-rat-core/src/test/java/org/apache/rat/ReportConfigurationTest.java b/apache-rat-core/src/test/java/org/apache/rat/ReportConfigurationTest.java index 69af88b2b..f29c622ea 100644 --- a/apache-rat-core/src/test/java/org/apache/rat/ReportConfigurationTest.java +++ b/apache-rat-core/src/test/java/org/apache/rat/ReportConfigurationTest.java @@ -39,6 +39,7 @@ import java.util.List; import java.util.SortedSet; import java.util.function.Function; +import java.util.stream.Collectors; import org.apache.commons.io.filefilter.DirectoryFileFilter; import org.apache.commons.io.output.CloseShieldOutputStream; @@ -56,6 +57,7 @@ import org.apache.rat.testhelpers.TestingLicense; import org.apache.rat.testhelpers.TestingMatcher; import org.apache.rat.utils.DefaultLog; +import org.apache.rat.utils.Log; import org.apache.rat.utils.Log.Level; import org.apache.rat.utils.ReportingSet.Options; import org.junit.jupiter.api.AfterEach; @@ -456,7 +458,7 @@ public void outputTest() throws IOException { assertThat(underTest.getWriter()).isNotNull(); ByteArrayOutputStream stream = new ByteArrayOutputStream(); - underTest.setOut(() -> stream); + underTest.setOut(new ReportConfiguration.IODescriptor<>("outputTest", () -> stream)); assertThat(underTest.getOutput().get()).isEqualTo(stream); PrintWriter writer = underTest.getWriter().get(); assertThat(writer).isNotNull(); @@ -480,9 +482,9 @@ public void stylesheetTest() throws IOException, URISyntaxException { URL url = this.getClass().getResource("ReportConfigurationTestFile"); assertThat(url).isNotNull(); - assertThat(underTest.getStyleSheet()).isNull(); + assertThat(underTest.getStyleSheetDescriptor()).isNull(); InputStream stream = mock(InputStream.class); - underTest.setStyleSheet(() -> stream); + underTest.setStyleSheet(new ReportConfiguration.IODescriptor<>("stylesheetTest", () -> stream)); assertThat(underTest.getStyleSheet().get()).isEqualTo(stream); File file = mock(File.class); @@ -515,32 +517,37 @@ public void testFlags() { @Test public void testValidate() { - final StringBuilder sb = new StringBuilder(); + TestingLog testLog = new TestingLog(); + Log oldLog = DefaultLog.getInstance(); + try { + DefaultLog.setInstance(testLog); + String msg = "At least one source must be specified"; - assertThatThrownBy(() -> underTest.validate(sb::append)).isExactlyInstanceOf(ConfigurationException.class) + assertThatThrownBy(underTest::validate).isExactlyInstanceOf(ConfigurationException.class) .hasMessageContaining(msg); - assertThat(sb.toString()).isEqualTo(msg); + testLog.assertContains(msg); + testLog.clear(); - sb.setLength(0); - msg = "You must specify at least one license"; + msg = "At least one license must be defined"; underTest.addSource(mock(IReportable.class)); - assertThatThrownBy(() -> underTest.validate(sb::append)).isExactlyInstanceOf(ConfigurationException.class) + assertThatThrownBy(underTest::validate).isExactlyInstanceOf(ConfigurationException.class) .hasMessageContaining(msg); - assertThat(sb.toString()).isEqualTo(msg); + testLog.assertContains(msg); - sb.setLength(0); underTest.addLicense(testingLicense("valid", "Validation testing license")); - underTest.validate(sb::append); - assertThat(sb.length()).isEqualTo(0); + underTest.validate(); + } finally { + DefaultLog.setInstance(oldLog); + } } @Test public void testSetOut() throws IOException { ReportConfiguration config = new ReportConfiguration(); try (OutputStreamInterceptor osi = new OutputStreamInterceptor()) { - config.setOut(() -> osi); + config.setOut(new ReportConfiguration.IODescriptor<>("testSetOut", () -> osi)); assertThat(osi.closeCount).isEqualTo(0); try (OutputStream os = config.getOutput().get()) { assertThat(os).isNotNull(); @@ -563,16 +570,18 @@ public void logFamilyCollisionTest() { // verify default collision logs WARNING underTest.addFamily(ILicenseFamily.builder().setLicenseFamilyCategory("CAT").setLicenseFamilyName("name2")); - assertThat(log.getCaptured().contains("WARN")).as("default value not WARN").isTrue(); - assertThat(log.getCaptured().contains("CAT")).as("'CAT' not found").isTrue(); + assertThat(log.getCaptured()).contains("WARN").contains("CAT"); // verify level setting works. for (Level l : Level.values()) { log.clear(); underTest.logFamilyCollisions(l); underTest.addFamily(ILicenseFamily.builder().setLicenseFamilyCategory("CAT").setLicenseFamilyName("name2")); - assertThat(log.getCaptured().contains("CAT")).as("'CAT' not found").isTrue(); - assertThat(log.getCaptured().contains(l.name())).as("logging not set to "+l).isTrue(); + if (DefaultLog.getInstance().isEnabled(l)) { + assertThat(log.getCaptured()).contains("CAT").contains(l.name()); + } else { + assertThat(log.getCaptured()).doesNotContain("CAT").doesNotContain(l.name()); + } } } @@ -662,51 +671,52 @@ public void licenseDuplicateOptionsTest() { .isExactlyInstanceOf(IllegalArgumentException.class); } - /** - * Validates that the configuration contains the default approved licenses. - * @param config The configuration to test. - */ - public static void validateDefaultApprovedLicenses(ReportConfiguration config) { - validateDefaultApprovedLicenses(config, 0); - } /** * Validates that the configuration contains the default approved licenses. * @param config The configuration to test. */ - public static void validateDefaultApprovedLicenses(ReportConfiguration config, int additionalIdCount) { - assertThat(config.getLicenseCategories(LicenseFilter.APPROVED)).hasSize(XMLConfigurationReaderTest.APPROVED_IDS.length + additionalIdCount); - for (String s : XMLConfigurationReaderTest.APPROVED_IDS) { - assertThat(config.getLicenseCategories(LicenseFilter.APPROVED)).contains(ILicenseFamily.makeCategory(s)); + public static void validateDefaultApprovedLicenses(ReportConfiguration config, String... additionalIds) { + validateLicenses(config, Arrays.asList(additionalIds), LicenseFilter.APPROVED, XMLConfigurationReaderTest.APPROVED_LICENSES); } + + /** + * Validates that the configuration contains all the default licenses along with any addiitonal licenses + * @param config the configuration to test. + * @param additionalLicenses Additional licence IDs that are expected. + */ + public static void validateDefaultLicenses(ReportConfiguration config, String...additionalLicenses) { + validateLicenses(config, Arrays.asList(additionalLicenses), LicenseFilter.ALL, XMLConfigurationReaderTest.EXPECTED_LICENSES); } + private static void validateLicenses(ReportConfiguration config, List additionalIds, LicenseFilter filter, String[] approvedIds) { + List expected = new ArrayList<>(Arrays.asList(approvedIds)); + expected.addAll(additionalIds); + assertThat(config.getLicenses(filter).stream().map(ILicense::getId).collect(Collectors.toSet())).containsExactlyInAnyOrderElementsOf(expected); + } + + /** * Validates that the configuration contains the default license families. * @param config the configuration to test. */ public static void validateDefaultLicenseFamilies(ReportConfiguration config, String...additionalIds) { - assertThat(config.getLicenseFamilies(LicenseFilter.ALL)).hasSize(XMLConfigurationReaderTest.EXPECTED_IDS.length + additionalIds.length); - List expected = new ArrayList<>(); - expected.addAll(Arrays.asList(XMLConfigurationReaderTest.EXPECTED_IDS)); - expected.addAll(Arrays.asList(additionalIds)); - for (ILicenseFamily family : config.getLicenseFamilies(LicenseFilter.ALL)) { - assertThat(expected).contains(family.getFamilyCategory().trim()); - } + validateLicenseFamilies(config, Arrays.asList(additionalIds), LicenseFilter.ALL, XMLConfigurationReaderTest.EXPECTED_IDS); } /** - * Validates that the configuration contains the default licenses. + * Validates that the configuration contains the default license families. * @param config the configuration to test. */ - public static void validateDefaultLicenses(ReportConfiguration config, String...additionalLicenses) { - assertThat(config.getLicenses(LicenseFilter.ALL)).hasSize(XMLConfigurationReaderTest.EXPECTED_LICENSES.length + additionalLicenses.length); - List expected = new ArrayList<>(); - expected.addAll(Arrays.asList(XMLConfigurationReaderTest.EXPECTED_LICENSES)); - expected.addAll(Arrays.asList(additionalLicenses)); - for (ILicense license : config.getLicenses(LicenseFilter.ALL)) { - assertThat(expected).contains(license.getId()); + public static void validateDefaultApprovedLicenseFamilies(ReportConfiguration config, String...additionalIds) { + validateLicenseFamilies(config, Arrays.asList(additionalIds), LicenseFilter.APPROVED, XMLConfigurationReaderTest.APPROVED_IDS); } + + private static void validateLicenseFamilies(ReportConfiguration config, List additionalIds, LicenseFilter filter, String[] approvedIds) { + List expected = new ArrayList<>(Arrays.asList(approvedIds)); + expected.addAll(additionalIds); + assertThat(config.getLicenseFamilies(filter).stream().map(lf -> lf.getFamilyCategory().trim()) + .collect(Collectors.toSet())).containsExactlyInAnyOrderElementsOf(expected); } /** @@ -719,9 +729,10 @@ public static void validateDefault(ReportConfiguration config) { assertThat(config.getCopyrightMessage()).isNull(); assertThat(config.getStyleSheet()).withFailMessage("Stylesheet should not be null").isNotNull(); - validateDefaultApprovedLicenses(config); validateDefaultLicenseFamilies(config); + validateDefaultApprovedLicenseFamilies(config); validateDefaultLicenses(config); + validateDefaultApprovedLicenses(config); } /** diff --git a/apache-rat-core/src/test/java/org/apache/rat/ReporterOptionsProvider.java b/apache-rat-core/src/test/java/org/apache/rat/ReporterOptionsProvider.java index 2baa5e6ff..2bf1c8dce 100644 --- a/apache-rat-core/src/test/java/org/apache/rat/ReporterOptionsProvider.java +++ b/apache-rat-core/src/test/java/org/apache/rat/ReporterOptionsProvider.java @@ -56,7 +56,7 @@ import org.apache.rat.test.AbstractOptionsProvider; import org.apache.rat.test.utils.OptionFormatter; import org.apache.rat.test.utils.Resources; -import org.apache.rat.testhelpers.FileUtils; +import org.apache.rat.utils.FileUtils; import org.apache.rat.testhelpers.TestingLog; import org.apache.rat.testhelpers.TextUtils; import org.apache.rat.testhelpers.XmlUtils; @@ -128,9 +128,9 @@ private void validateNoArgSetup() throws IOException, RatException { try { ReportConfiguration config = generateConfig(Collections.emptyList()); Reporter reporter = new Reporter(config); - ClaimStatistic claimStatistic = reporter.execute(); + Reporter.Output output = reporter.execute(); ClaimValidator validator = config.getClaimValidator(); - assertThat(validator.listIssues(claimStatistic)).isEmpty(); + assertThat(validator.listIssues(output.getStatistic())).isEmpty(); } finally { DefaultLog.setInstance(null); } @@ -156,8 +156,8 @@ private void editLicenseTest(final Option option) { FileUtils.delete(resultFile); Reporter reporter = new Reporter(config); - ClaimStatistic claimStatistic = reporter.execute(); - assertThat(claimStatistic).isNotNull(); + Reporter.Output output = reporter.execute(); + assertThat(output.getStatistic()).isNotNull(); String contents = String.join("\n", IOUtils.readLines(new FileReader(testFile))); assertThat(contents).isEqualTo("class NoLicense {}"); assertThat(resultFile).exists(); @@ -212,9 +212,9 @@ private void execLicensesDeniedTest(final Option option, final String[] args) { ReportConfiguration config = generateConfig(ImmutablePair.of(option, args)); Reporter reporter = new Reporter(config); - ClaimStatistic claimStatistic = reporter.execute(); + Reporter.Output output = reporter.execute(); ClaimValidator validator = config.getClaimValidator(); - assertThat(validator.listIssues(claimStatistic)).containsExactly("UNAPPROVED"); + assertThat(validator.listIssues(output.getStatistic())).containsExactly("UNAPPROVED"); } catch (IOException | RatException e) { fail(e.getMessage(), e); } @@ -246,9 +246,7 @@ private void noDefaultsTest(final Option option) { reporter.execute(); fail("Should have thrown exception"); } catch (RatException e) { - ClaimStatistic claimStatistic = reporter.getClaimsStatistic(); ClaimValidator validator = config.getClaimValidator(); - assertThat(validator.listIssues(claimStatistic)).containsExactlyInAnyOrder("DOCUMENT_TYPES", "LICENSE_CATEGORIES", "LICENSE_NAMES", "STANDARDS"); } } catch (IOException | RatException e) { fail(e.getMessage(), e); @@ -276,16 +274,16 @@ protected void counterMaxTest() { ReportConfiguration config = generateConfig(Collections.emptyList()); Reporter reporter = new Reporter(config); - ClaimStatistic claimStatistic = reporter.execute(); + Reporter.Output output = reporter.execute(); ClaimValidator validator = config.getClaimValidator(); - assertThat(validator.listIssues(claimStatistic)).containsExactly("UNAPPROVED"); + assertThat(validator.listIssues(output.getStatistic())).containsExactly("UNAPPROVED"); arg[0] = "Unapproved:1"; config = generateConfig(ImmutablePair.of(option, arg)); reporter = new Reporter(config); - claimStatistic = reporter.execute(); + output = reporter.execute(); validator = config.getClaimValidator(); - assertThat(validator.listIssues(claimStatistic)).isEmpty(); + assertThat(validator.listIssues(output.getStatistic())).isEmpty(); } catch (IOException | RatException e) { fail(e.getMessage(), e); } @@ -303,16 +301,16 @@ protected void counterMinTest() { ReportConfiguration config = generateConfig(Collections.emptyList()); Reporter reporter = new Reporter(config); - ClaimStatistic claimStatistic = reporter.execute(); + Reporter.Output output = reporter.execute(); ClaimValidator validator = config.getClaimValidator(); - assertThat(validator.listIssues(claimStatistic)).containsExactly("UNAPPROVED"); + assertThat(validator.listIssues(output.getStatistic())).containsExactly("UNAPPROVED"); arg[0] = "Unapproved:1"; config = generateConfig(ImmutablePair.of(option, arg)); reporter = new Reporter(config); - claimStatistic = reporter.execute(); + output = reporter.execute(); validator = config.getClaimValidator(); - assertThat(validator.listIssues(claimStatistic)).isEmpty(); + assertThat(validator.listIssues(output.getStatistic())).isEmpty(); } catch (IOException | RatException e) { fail(e.getMessage(), e); } @@ -332,16 +330,16 @@ private void execExcludeTest(final Option option, final String[] args) { ReportConfiguration config = generateConfig(Collections.emptyList()); Reporter reporter = new Reporter(config); - ClaimStatistic claimStatistic = reporter.execute(); - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(5); - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(0); + Reporter.Output output = reporter.execute(); + assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(5); + assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(0); // filter out source config = generateConfig(ImmutablePair.of(option, args)); reporter = new Reporter(config); - claimStatistic = reporter.execute(); - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(2); - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(3); + output = reporter.execute(); + assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(2); + assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(3); } catch (IOException | RatException e) { fail(e.getMessage(), e); } @@ -386,16 +384,16 @@ protected void inputExcludeSizeTest() { ReportConfiguration config = generateConfig(Collections.emptyList()); Reporter reporter = new Reporter(config); - ClaimStatistic claimStatistic = reporter.execute(); - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(3); - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(0); + Reporter.Output output = reporter.execute(); + assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(3); + assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(0); // filter out source config = generateConfig(ImmutablePair.of(option, args)); reporter = new Reporter(config); - claimStatistic = reporter.execute(); - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(2); - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(1); + output = reporter.execute(); + assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(2); + assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(1); } catch (IOException | RatException e) { fail(e.getMessage(), e); } @@ -422,15 +420,15 @@ protected void inputExcludeStdTest() { ReportConfiguration config = generateConfig(Collections.emptyList()); Reporter reporter = new Reporter(config); - ClaimStatistic claimStatistic = reporter.execute(); - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(5); - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(4); + Reporter.Output output = reporter.execute(); + assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(5); + assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(4); config = generateConfig(ImmutablePair.of(option, args)); reporter = new Reporter(config); - claimStatistic = reporter.execute(); - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(4); - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(5); + output = reporter.execute(); + assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(4); + assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(5); } catch (IOException | RatException e) { fail(e.getMessage(), e); } @@ -480,16 +478,16 @@ protected void inputExcludeParsedScmTest() { ReportConfiguration config = generateConfig(Collections.emptyList()); Reporter reporter = new Reporter(config); - ClaimStatistic claimStatistic = reporter.execute(); - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(11); + Reporter.Output output = reporter.execute(); + assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(11); // .gitignore is ignored by default as it is hidden but not counted - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(0); + assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(0); config = generateConfig(ImmutablePair.of(option, args)); reporter = new Reporter(config); - claimStatistic = reporter.execute(); - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(3); - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(8); + output = reporter.execute(); + assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(3); + assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(8); } catch (IOException | RatException e) { fail(e.getMessage(), e); } @@ -509,25 +507,25 @@ private void execIncludeTest(final Option option, final String[] args) { ReportConfiguration config = generateConfig(Collections.emptyList()); Reporter reporter = new Reporter(config); - ClaimStatistic claimStatistic = reporter.execute(); - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(4); - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(0); + Reporter.Output output = reporter.execute(); + assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(4); + assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(0); // verify exclude removes most files. config = generateConfig(ImmutablePair.of(excludeOption, EXCLUDE_ARGS)); reporter = new Reporter(config); - claimStatistic = reporter.execute(); - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(1); + output = reporter.execute(); + assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(1); // .gitignore is ignored by default as it is hidden but not counted - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(3); + assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(3); // verify include pust them back config = generateConfig(ImmutablePair.of(option, args), ImmutablePair.of(excludeOption, EXCLUDE_ARGS)); reporter = new Reporter(config); - claimStatistic = reporter.execute(); - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(3); + output = reporter.execute(); + assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(3); // .gitignore is ignored by default as it is hidden but not counted - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(1); + assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(1); } catch (IOException | RatException e) { fail(e.getMessage(), e); } @@ -579,15 +577,15 @@ protected void inputIncludeStdTest() { ReportConfiguration config = generateConfig(Collections.singletonList(excludes)); Reporter reporter = new Reporter(config); - ClaimStatistic claimStatistic = reporter.execute(); - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(3); - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(5); + Reporter.Output output = reporter.execute(); + assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(3); + assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(5); config = generateConfig(excludes, ImmutablePair.of(option, args)); reporter = new Reporter(config); - claimStatistic = reporter.execute(); - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(7); - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(1); + output = reporter.execute(); + assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(7); + assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(1); } catch (IOException | RatException e) { fail(e.getMessage(), e); } @@ -605,15 +603,15 @@ protected void inputSourceTest() { ReportConfiguration config = generateConfig(); Reporter reporter = new Reporter(config); - ClaimStatistic claimStatistic = reporter.execute(); - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(3); - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(0); + Reporter.Output output = reporter.execute(); + assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(3); + assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(0); config = generateConfig(ImmutablePair.of(option, new String[]{inputFile.getAbsolutePath()})); reporter = new Reporter(config); - claimStatistic = reporter.execute(); - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(1); - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(0); + output = reporter.execute(); + assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(1); + assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(0); } catch (IOException | RatException e) { fail(e.getMessage(), e); } @@ -636,18 +634,18 @@ private void execLicenseFamiliesApprovedTest(final Option option, final String[] ReportConfiguration config = addCatzLicense(generateConfig()); Reporter reporter = new Reporter(config); - ClaimStatistic claimStatistic = reporter.execute(); + Reporter.Output output = reporter.execute(); - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(1); - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.APPROVED)).isEqualTo(0); - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(1); + assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(1); + assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.APPROVED)).isEqualTo(0); + assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(1); config = addCatzLicense(generateConfig(arg1)); reporter = new Reporter(config); - claimStatistic = reporter.execute(); - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(1); - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.APPROVED)).isEqualTo(1); - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(0); + output = reporter.execute(); + assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(1); + assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.APPROVED)).isEqualTo(1); + assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(0); } catch (IOException | RatException e) { fail(e.getMessage(), e); } @@ -677,17 +675,17 @@ private void execLicenseFamiliesDeniedTest(final Option option, final String[] a ReportConfiguration config = generateConfig(); Reporter reporter = new Reporter(config); - ClaimStatistic claimStatistic = reporter.execute(); - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(1); - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.APPROVED)).isEqualTo(1); - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(0); + Reporter.Output output = reporter.execute(); + assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(1); + assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.APPROVED)).isEqualTo(1); + assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(0); config = generateConfig(arg1); reporter = new Reporter(config); - claimStatistic = reporter.execute(); - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(1); - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.APPROVED)).isEqualTo(0); - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(1); + output = reporter.execute(); + assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(1); + assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.APPROVED)).isEqualTo(0); + assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(1); } catch (IOException | RatException e) { fail(e.getMessage(), e); } @@ -721,26 +719,26 @@ private void configTest(final Option option) { ReportConfiguration config = generateConfig(); Reporter reporter = new Reporter(config); - ClaimStatistic claimStatistic = reporter.execute(); - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(2); - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.APPROVED)).isEqualTo(1); - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(1); + Reporter.Output output = reporter.execute(); + assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(2); + assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.APPROVED)).isEqualTo(1); + assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(1); config = generateConfig(arg1); reporter = new Reporter(config); - claimStatistic = reporter.execute(); - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(2); - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.APPROVED)).isEqualTo(2); - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(0); + output = reporter.execute(); + assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(2); + assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.APPROVED)).isEqualTo(2); + assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(0); Pair arg2 = ImmutablePair.of(Arg.CONFIGURATION_NO_DEFAULTS.find("configuration-no-defaults"), null); config = generateConfig(arg1, arg2); reporter = new Reporter(config); - claimStatistic = reporter.execute(); - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(2); - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.APPROVED)).isEqualTo(1); - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(1); + output = reporter.execute(); + assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(2); + assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.APPROVED)).isEqualTo(1); + assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(1); } catch (IOException | RatException e) { fail(e.getMessage(), e); @@ -769,17 +767,17 @@ protected void execLicensesApprovedTest(final Option option, String[] args) { ReportConfiguration config = generateConfig(); Reporter reporter = new Reporter(config); - ClaimStatistic claimStatistic = reporter.execute(); - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(2); - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.APPROVED)).isEqualTo(1); - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(1); + Reporter.Output output = reporter.execute(); + assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(2); + assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.APPROVED)).isEqualTo(1); + assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(1); config = generateConfig(arg1); reporter = new Reporter(config); - claimStatistic = reporter.execute(); - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(2); - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.APPROVED)).isEqualTo(2); - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(0); + output = reporter.execute(); + assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(2); + assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.APPROVED)).isEqualTo(2); + assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(0); } catch (IOException | RatException e) { @@ -816,17 +814,17 @@ protected void scanHiddenDirectoriesTest() { ReportConfiguration config = generateConfig(); Reporter reporter = new Reporter(config); - ClaimStatistic claimStatistic = reporter.execute(); - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(1); - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.APPROVED)).isEqualTo(1); - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(0); + Reporter.Output output = reporter.execute(); + assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(1); + assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.APPROVED)).isEqualTo(1); + assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(0); config = generateConfig(arg1); reporter = new Reporter(config); - claimStatistic = reporter.execute(); - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(2); - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.APPROVED)).isEqualTo(1); - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(1); + output = reporter.execute(); + assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(2); + assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.APPROVED)).isEqualTo(1); + assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(1); } catch (IOException | RatException e) { fail(e.getMessage(), e); } @@ -842,16 +840,17 @@ private void outTest(final Option option) { ReportConfiguration config = generateConfig(ImmutablePair.of(option, args)); Reporter reporter = new Reporter(config); - ClaimStatistic claimStatistic = reporter.output(); - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(1); - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.APPROVED)).isEqualTo(1); - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(0); + Reporter.Output output = reporter.execute(); + output.format(config); + assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(1); + assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.APPROVED)).isEqualTo(1); + assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(0); String actualText = TextUtils.readFile(outFile); TextUtils.assertContainsExactly(1, "Apache License 2.0: 1 ", actualText); TextUtils.assertContainsExactly(1, "STANDARD: 1 ", actualText); - } catch (IOException | RatException e) { + } catch (IOException | RatException e ) { fail(e.getMessage(), e); } } @@ -885,10 +884,11 @@ private void styleSheetTest(final Option option) { args[0] = sheet.arg(); ReportConfiguration config = generateConfig(ImmutablePair.of(option, args)); Reporter reporter = new Reporter(config); - ClaimStatistic claimStatistic = reporter.output(); - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(1); - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.APPROVED)).isEqualTo(0); - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(1); + Reporter.Output output = reporter.execute(); + output.format(config); + assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(1); + assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.APPROVED)).isEqualTo(0); + assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(1); String actualText = baos.toString(StandardCharsets.UTF_8); switch (sheet) { @@ -906,6 +906,9 @@ private void styleSheetTest(final Option option) { case UNAPPROVED_LICENSES: TextUtils.assertContainsExactly(1, "Files with unapproved licenses:" + System.lineSeparator() + " /stylesheet", actualText); break; + case XHTML5: + TextUtils.assertPatternInTarget("Approved<\\/td>\\s+\\d+<\\/td>\\s+A count of approved licenses.<\\/td>", actualText); + break; default: fail("No test for stylesheet " + sheet); break; @@ -915,10 +918,11 @@ private void styleSheetTest(final Option option) { args[0] = file.getAbsolutePath(); ReportConfiguration config = generateConfig(ImmutablePair.of(option, args)); Reporter reporter = new Reporter(config); - ClaimStatistic claimStatistic = reporter.output(); - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(1); - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.APPROVED)).isEqualTo(0); - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(1); + Reporter.Output output = reporter.execute(); + output.format(config); + assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(1); + assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.APPROVED)).isEqualTo(0); + assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(1); String actualText = baos.toString(StandardCharsets.UTF_8); TextUtils.assertContainsExactly(1, "Hello world", actualText); @@ -957,15 +961,16 @@ protected void xmlTest() { ReportConfiguration config = generateConfig(ImmutablePair.of(option, null)); Reporter reporter = new Reporter(config); - ClaimStatistic claimStatistic = reporter.output(); - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(1); - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.APPROVED)).isEqualTo(0); - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(1); + Reporter.Output output = reporter.execute(); + output.format(config); + assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(1); + assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.APPROVED)).isEqualTo(0); + assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.UNAPPROVED)).isEqualTo(1); String actualText = baos.toString(StandardCharsets.UTF_8); TextUtils.assertContainsExactly(1, "", actualText); - try (InputStream expected = StyleSheets.getStyleSheet("xml").get(); + try (InputStream expected = StyleSheets.getStyleSheet("xml", null).ioSupplier().get(); InputStream actual = config.getStyleSheet().get()) { assertThat(IOUtils.contentEquals(expected, actual)).as("'xml' does not match").isTrue(); } @@ -988,13 +993,13 @@ protected void logLevelTest() { ReportConfiguration config = generateConfig(); Reporter reporter = new Reporter(config); - reporter.output(); - TextUtils.assertNotContains("DEBUG", baos.toString(StandardCharsets.UTF_8)); + reporter.execute(); + TextUtils.assertNotContains("DEBUG", baos.toString(StandardCharsets.UTF_8.name())); config = generateConfig(ImmutablePair.of(option, new String[]{"debug"})); reporter = new Reporter(config); - reporter.output(); - TextUtils.assertContains("DEBUG", baos.toString(StandardCharsets.UTF_8)); + reporter.execute(); + TextUtils.assertContains("DEBUG", baos.toString(StandardCharsets.UTF_8.name())); } catch (IOException | RatException e) { fail(e.getMessage(), e); } finally { @@ -1009,16 +1014,11 @@ private void listLicenses(final Option option) { try { configureSourceDir(option); - File outFile = new File(sourceDir, "out.xml"); - FileUtils.delete(outFile); - ImmutablePair outputFile = ImmutablePair.of(Arg.OUTPUT_FILE.option(), new String[]{outFile.getAbsolutePath()}); - ImmutablePair stylesheet = ImmutablePair.of(Arg.OUTPUT_STYLE.option(), new String[]{StyleSheets.XML.arg()}); for (LicenseSetFactory.LicenseFilter filter : LicenseSetFactory.LicenseFilter.values()) { args[0] = filter.name(); - ReportConfiguration config = generateConfig(outputFile, stylesheet, ImmutablePair.of(option, args)); + ReportConfiguration config = generateConfig(ImmutablePair.of(option, args)); Reporter reporter = new Reporter(config); - reporter.output(); - Document document = XmlUtils.toDom(new FileInputStream(outFile)); + Document document = reporter.execute().getDocument(); switch (filter) { case ALL: XmlUtils.assertIsPresent(filter.name(), document, xPath, "/rat-report/rat-config/licenses/license[@id='AL2.0']"); @@ -1036,8 +1036,7 @@ private void listLicenses(final Option option) { throw new IllegalArgumentException("Unexpected filter: " + filter); } } - } catch (IOException | RatException | SAXException | ParserConfigurationException | - XPathExpressionException e) { + } catch (IOException | RatException | XPathExpressionException e) { fail(e.getMessage(), e); } } @@ -1058,16 +1057,12 @@ private void listFamilies(final Option option) { try { configureSourceDir(option); - File outFile = new File(sourceDir, "out.xml"); - FileUtils.delete(outFile); - ImmutablePair outputFile = ImmutablePair.of(Arg.OUTPUT_FILE.option(), new String[]{outFile.getAbsolutePath()}); - ImmutablePair stylesheet = ImmutablePair.of(Arg.OUTPUT_STYLE.option(), new String[]{StyleSheets.XML.arg()}); for (LicenseSetFactory.LicenseFilter filter : LicenseSetFactory.LicenseFilter.values()) { args[0] = filter.name(); - ReportConfiguration config = generateConfig(outputFile, stylesheet, ImmutablePair.of(option, args)); + ReportConfiguration config = generateConfig(ImmutablePair.of(option, args)); Reporter reporter = new Reporter(config); - reporter.output(); - Document document = XmlUtils.toDom(Files.newInputStream(outFile.toPath())); + Reporter.Output output = reporter.execute(); + Document document = output.getDocument(); switch (filter) { case ALL: XmlUtils.assertIsPresent(filter.name(), document, xPath, "/rat-report/rat-config/families/family[@id='AL']"); @@ -1085,8 +1080,7 @@ private void listFamilies(final Option option) { throw new IllegalArgumentException("Unexpected filter: " + filter); } } - } catch (IOException | RatException | SAXException | ParserConfigurationException | - XPathExpressionException e) { + } catch (IOException | RatException | XPathExpressionException e) { fail(e.getMessage(), e); } } @@ -1122,9 +1116,8 @@ private void archiveTest(final Option option) { args[0] = proc.name(); ReportConfiguration config = generateConfig(outputFile, stylesheet, ImmutablePair.of(option, args)); Reporter reporter = new Reporter(config); - reporter.output(); - Document document = XmlUtils.toDom(Files.newInputStream(outFile.toPath())); + Document document = reporter.execute().getDocument(); XmlUtils.assertIsPresent(proc.name(), document, xPath, "/rat-report/resource[@name='/dummy.jar']"); switch (proc) { case ABSENCE: @@ -1143,8 +1136,7 @@ private void archiveTest(final Option option) { throw new IllegalArgumentException("Unexpected processing " + proc); } } - } catch (IOException | RatException | SAXException | ParserConfigurationException | - XPathExpressionException e) { + } catch (IOException | RatException | XPathExpressionException e) { fail(e.getMessage(), e); } } @@ -1175,9 +1167,9 @@ private void standardTest(final Option option) { args[0] = proc.name(); ReportConfiguration config = generateConfig(outputFile, stylesheet, ImmutablePair.of(option, args)); Reporter reporter = new Reporter(config); - reporter.output(); + Reporter.Output output = reporter.execute(); - Document document = XmlUtils.toDom(Files.newInputStream(outFile.toPath())); + Document document = output.getDocument(); XmlUtils.assertIsPresent(proc.name(), document, xPath, testDoc); XmlUtils.assertIsPresent(proc.name(), document, xPath, missingDoc); @@ -1198,8 +1190,7 @@ private void standardTest(final Option option) { throw new IllegalArgumentException("Unexpected processing " + proc); } } - } catch (IOException | RatException | SAXException | ParserConfigurationException | - XPathExpressionException e) { + } catch (IOException | RatException | XPathExpressionException e) { fail(e.getMessage(), e); } } diff --git a/apache-rat-core/src/test/java/org/apache/rat/ReporterOptionsTest.java b/apache-rat-core/src/test/java/org/apache/rat/ReporterOptionsTest.java index ccb82f3c6..4f52a31a5 100644 --- a/apache-rat-core/src/test/java/org/apache/rat/ReporterOptionsTest.java +++ b/apache-rat-core/src/test/java/org/apache/rat/ReporterOptionsTest.java @@ -27,7 +27,7 @@ import org.apache.rat.api.RatException; import org.apache.rat.report.claim.ClaimStatistic; import org.apache.rat.test.AbstractConfigurationOptionsProvider; -import org.apache.rat.testhelpers.FileUtils; +import org.apache.rat.utils.FileUtils; import org.apache.rat.testhelpers.XmlUtils; import org.apache.rat.utils.DefaultLog; import org.apache.rat.utils.Log; @@ -78,14 +78,14 @@ void testRat362() { FileUtils.writeFile(testDir, "foo.md"); ReportConfiguration config = OptionCollection.parseCommands(testDir, args, o -> fail("Help called"), true); Reporter reporter = new Reporter(config); - ClaimStatistic claimStatistic = reporter.execute(); - XmlUtils.printDocument(System.out, reporter.getDocument()); + Reporter.Output output = reporter.execute(); + XmlUtils.printDocument(System.out, output.getDocument()); XPath xpath = XPathFactory.newInstance().newXPath(); - XmlUtils.assertIsPresent(reporter.getDocument(), xpath, "/rat-report/resource[@name='/foo.md']"); - XmlUtils.assertAttributes(reporter.getDocument(), xpath, "/rat-report/resource[@name='/foo.md']", + XmlUtils.assertIsPresent(output.getDocument(), xpath, "/rat-report/resource[@name='/foo.md']"); + XmlUtils.assertAttributes(output.getDocument(), xpath, "/rat-report/resource[@name='/foo.md']", XmlUtils.mapOf("type", "IGNORED")); - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(0); - assertThat(claimStatistic.getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(2); + assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.STANDARDS)).isEqualTo(0); + assertThat(output.getStatistic().getCounter(ClaimStatistic.Counter.IGNORED)).isEqualTo(2); } catch (IOException | RatException | XPathExpressionException e) { fail(e); } diff --git a/apache-rat-core/src/test/java/org/apache/rat/ReporterTest.java b/apache-rat-core/src/test/java/org/apache/rat/ReporterTest.java index 3c2a9a725..9c3205391 100644 --- a/apache-rat-core/src/test/java/org/apache/rat/ReporterTest.java +++ b/apache-rat-core/src/test/java/org/apache/rat/ReporterTest.java @@ -19,21 +19,28 @@ package org.apache.rat; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Fail.fail; -import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; +import java.io.IOException; import java.io.PrintStream; +import java.lang.reflect.Method; import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.TreeMap; +import java.util.UUID; +import java.util.stream.Stream; import javax.xml.XMLConstants; import javax.xml.transform.Source; import javax.xml.transform.dom.DOMSource; @@ -45,24 +52,33 @@ import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathFactory; -import org.apache.commons.cli.CommandLine; -import org.apache.commons.cli.DefaultParser; import org.apache.commons.cli.ParseException; import org.apache.commons.io.FileUtils; import org.apache.rat.api.Document.Type; import org.apache.rat.api.RatException; +import org.apache.rat.commandline.Arg; import org.apache.rat.commandline.ArgumentContext; -import org.apache.rat.commandline.StyleSheets; import org.apache.rat.document.FileDocument; import org.apache.rat.document.DocumentName; import org.apache.rat.license.ILicenseFamily; import org.apache.rat.report.claim.ClaimStatistic; import org.apache.rat.test.utils.Resources; +import org.apache.rat.testhelpers.BaseOptionCollection; import org.apache.rat.testhelpers.TextUtils; import org.apache.rat.testhelpers.XmlUtils; +import org.apache.rat.testhelpers.data.ReportTestDataProvider; +import org.apache.rat.testhelpers.data.TestData; +import org.apache.rat.testhelpers.data.ValidatorData; import org.apache.rat.walker.DirectoryWalker; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.w3c.dom.Document; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; @@ -71,22 +87,68 @@ * Tests the output of the Reporter. */ public class ReporterTest { - @TempDir - File tempDirectory; + /** + * temporary file. Not using tempDir because it does not + * always work. + */ + private static Path tempPath; + /** + * The TestInfo for the current test. + */ + private TestInfo testInfo; + + /** + * The directory for the test. + */ + private Path testPath; + + /** + * Directory for the test data. + */ final String basedir; + private final OptionCollectionParser collectionParser; + ReporterTest() throws URISyntaxException { basedir = Resources.getExampleResource("exampleData").getPath(); + collectionParser = new OptionCollectionParser(BaseOptionCollection.builder().build()); } - @Test - public void testExecute() throws RatException, ParseException { - File output = new File(tempDirectory, "testExecute"); + @BeforeAll + static void setUp() throws IOException { + tempPath = Files.createTempDirectory("ReporterTest"); + } - CommandLine cl = new DefaultParser().parse(OptionCollection.buildOptions(), new String[]{"--output-style", "xml", "--output-file", output.getPath(), basedir}); - ArgumentContext ctxt = new ArgumentContext(new File("."), cl); - ReportConfiguration config = OptionCollection.createConfiguration(ctxt); - ClaimStatistic statistic = new Reporter(config).execute(); + @AfterAll + static void cleanup() throws IOException { + FileUtils.deleteDirectory(tempPath.toFile()); + } + + @BeforeEach + void setUpTest(TestInfo testInfo) { + this.testInfo = testInfo; + Optional testMethod = testInfo.getTestMethod(); + this.testPath = tempPath.resolve(testMethod.isPresent() ? + testMethod.get().getName() : + UUID.randomUUID().toString()); + File file = testPath.toFile(); + if (!file.exists()) { + if (!file.mkdirs()) { + throw new RuntimeException("Unable to create temp directory: " + file.getName()); + } + } else { + if (!file.isDirectory()) { + throw new RuntimeException(file.getName() + " is NOT a directory."); + } + } + } + + @Test + void testExecute() throws RatException, ParseException, IOException { + File output = testPath.resolve("output.xml").toFile(); + BaseOptionCollection optionCollection = BaseOptionCollection.builder().build(); + ArgumentContext ctxt = collectionParser.parseCommands(new File("."), new String[]{"--output-style", "xml", "--output-file", output.getPath(), basedir}); + ClaimStatistic statistic = new Reporter(ctxt.getConfiguration()).execute().getStatistic(); assertThat(statistic.getCounter(Type.ARCHIVE)).isEqualTo(1); assertThat(statistic.getCounter(Type.BINARY)).isEqualTo(2); @@ -135,13 +197,10 @@ public void testExecute() throws RatException, ParseException { } @Test - public void testOutputOption() throws Exception { - File output = new File(tempDirectory, "test"); - CommandLine commandLine = new DefaultParser().parse(OptionCollection.buildOptions(), new String[]{"-o", output.getCanonicalPath(), basedir}); - ArgumentContext ctxt = new ArgumentContext(new File("."), commandLine); - - ReportConfiguration config = OptionCollection.createConfiguration(ctxt); - new Reporter(config).output(); + void testOutputOption() throws Exception { + File output = testPath.resolve("output.txt").toFile(); + ArgumentContext ctxt = collectionParser.parseCommands(new File("."), new String[]{"--output-file", output.getCanonicalPath(), basedir}); + new Reporter(ctxt.getConfiguration()).execute().format(ctxt.getConfiguration()); assertThat(output.exists()).isTrue(); String content = FileUtils.readFileToString(output, StandardCharsets.UTF_8); TextUtils.assertPatternInTarget("^! Unapproved:\\s*2 ", content); @@ -151,16 +210,14 @@ public void testOutputOption() throws Exception { @Test public void testDefaultOutput() throws Exception { - File output = new File(tempDirectory, "testDefaultOutput"); + File output = testPath.resolve("captured.txt").toFile(); + BaseOptionCollection optionCollection = BaseOptionCollection.builder().build(); PrintStream origin = System.out; try (PrintStream out = new PrintStream(output)) { System.setOut(out); - CommandLine commandLine = new DefaultParser().parse(OptionCollection.buildOptions(), new String[]{basedir}); - ArgumentContext ctxt = new ArgumentContext(new File("."), commandLine); - - ReportConfiguration config = OptionCollection.createConfiguration(ctxt); - new Reporter(config).output(); + ArgumentContext ctxt = collectionParser.parseCommands(new File("."), new String[]{basedir}); + new Reporter(ctxt.getConfiguration()).execute().format(ctxt.getConfiguration()); } finally { System.setOut(origin); } @@ -178,7 +235,7 @@ private static Map mapOf(String... parts) { } @Test - public void testXMLOutput() throws Exception { + void testXMLOutput() throws Exception { Map> expected = new HashMap<>(); expected.put("/.hiddenDirectory", mapOf("isDirectory", "true", "mediaType", "application/octet-stream", "type", "IGNORED")); @@ -208,12 +265,9 @@ public void testXMLOutput() throws Exception { expected.put("/tri.txt", mapOf("encoding", "ISO-8859-1", "mediaType", "text/plain", "type", "STANDARD")); - File output = new File(tempDirectory, "testXMLOutput"); - CommandLine commandLine = new DefaultParser().parse(OptionCollection.buildOptions(), new String[]{"--output-style", "xml", "--output-file", output.getPath(), basedir}); - ArgumentContext ctxt = new ArgumentContext(new File("."), commandLine); - - ReportConfiguration config = OptionCollection.createConfiguration(ctxt); - new Reporter(config).output(); + File output = testPath.resolve("output.xml").toFile(); + ArgumentContext ctxt = collectionParser.parseCommands(new File("."), new String[]{"--output-style", "xml", "--output-file", output.getPath(), basedir}); + new Reporter(ctxt.getConfiguration()).execute().format(ctxt.getConfiguration()); assertThat(output).exists(); Document doc = XmlUtils.toDom(java.nio.file.Files.newInputStream(output.toPath())); @@ -402,14 +456,9 @@ private Validator initValidator() throws SAXException { } @Test - public void xmlReportTest() throws Exception { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - + void xmlReportTest() throws Exception { ReportConfiguration configuration = initializeConfiguration(); - configuration.setStyleSheet(StyleSheets.XML.getStyleSheet()); - configuration.setOut(() -> out); - new Reporter(configuration).output(); - Document doc = XmlUtils.toDom(new ByteArrayInputStream(out.toByteArray())); + Document doc = new Reporter(configuration).execute().getDocument(); XPath xPath = XPathFactory.newInstance().newXPath(); @@ -446,7 +495,7 @@ public void xmlReportTest() throws Exception { } @Test - public void plainReportTest() throws Exception { + void plainReportTest() throws Exception { final String NL = System.lineSeparator(); final String SEPARATOR = "*****************************************************"; final String HEADER = SEPARATOR + NL + // @@ -455,10 +504,9 @@ public void plainReportTest() throws Exception { "Generated at: "; ByteArrayOutputStream out = new ByteArrayOutputStream(); ReportConfiguration configuration = initializeConfiguration(); - configuration.setOut(() -> out); - new Reporter(configuration).output(); + configuration.setOut(new ReportConfiguration.IODescriptor<>("plainReportTest", () -> out)); + new Reporter(configuration).execute().format(configuration); - out.flush(); String document = out.toString(); TextUtils.assertNotContains("", document); @@ -468,14 +516,13 @@ public void plainReportTest() throws Exception { } @Test - public void unapprovedLicensesReportTest() throws Exception { + void unapprovedLicensesReportTest() throws Exception { ByteArrayOutputStream out = new ByteArrayOutputStream(); ReportConfiguration configuration = initializeConfiguration(); - configuration.setOut(() -> out); + configuration.setOut(new ReportConfiguration.IODescriptor<>("unapprovedLicensesReportTest", () -> out)); configuration.setStyleSheet(this.getClass().getResource("/org/apache/rat/unapproved-licenses.xsl")); - new Reporter(configuration).output(); + new Reporter(configuration).execute().format(configuration); - out.flush(); String document = out.toString(); TextUtils.assertContains("Generated at: ", document ); @@ -484,23 +531,52 @@ public void unapprovedLicensesReportTest() throws Exception { } @Test - public void counterMaxTest() throws Exception { + void counterMaxTest() throws Exception { ReportConfiguration config = initializeConfiguration(); - Reporter reporter = new Reporter(config); - reporter.output(); + Reporter.Output output = new Reporter(config).execute(); assertThat(config.getClaimValidator().hasErrors()).isTrue(); - assertThat(config.getClaimValidator().isValid(ClaimStatistic.Counter.UNAPPROVED, reporter.getClaimsStatistic().getCounter(ClaimStatistic.Counter.UNAPPROVED))) + assertThat(config.getClaimValidator().isValid(ClaimStatistic.Counter.UNAPPROVED, output.getStatistic().getCounter(ClaimStatistic.Counter.UNAPPROVED))) .isFalse(); config = initializeConfiguration(); config.getClaimValidator().setMax(ClaimStatistic.Counter.UNAPPROVED, 2); - reporter = new Reporter(config); - reporter.output(); + output = new Reporter(config).execute(); assertThat(config.getClaimValidator().hasErrors()).isFalse(); - assertThat(config.getClaimValidator().isValid(ClaimStatistic.Counter.UNAPPROVED, reporter.getClaimsStatistic().getCounter(ClaimStatistic.Counter.UNAPPROVED))) + assertThat(config.getClaimValidator().isValid(ClaimStatistic.Counter.UNAPPROVED, output.getStatistic().getCounter(ClaimStatistic.Counter.UNAPPROVED))) .isTrue(); } + static Stream getTestData() { + BaseOptionCollection.Builder builder = BaseOptionCollection.builder() + .unsupported(Arg.OUTPUT_FILE); + return new ReportTestDataProvider().getOptionTests(builder.build()).stream().map(testData -> + Arguments.of(testData.getTestName(), testData)); + } + + @ParameterizedTest( name = "{index} {0}") + @MethodSource("getTestData") + void testReportData(String name, TestData test) throws Exception { + Path invokePath = testPath.resolve(test.getTestName()); + org.apache.rat.utils.FileUtils.mkDir(invokePath.toFile()); + + test.setupFiles(invokePath); + ArgumentContext ctxt = collectionParser.parseCommands(invokePath.toFile(), + test.getCommandLine(invokePath.toString())); + if (test.expectingException()) { + assertThatThrownBy(() -> new Reporter(ctxt.getConfiguration()).execute()).as("Expected throws from " + name) + .hasMessageContaining(test.getExpectedException().getMessage()); + ValidatorData data = new ValidatorData(Reporter.Output.builder().configuration(ctxt.getConfiguration()).build(), + invokePath.toString()); + test.getValidator().accept(data); + } else { + Reporter.Output output = ctxt.getConfiguration() != null ? new Reporter(ctxt.getConfiguration()).execute() : + Reporter.Output.builder().build(); + ValidatorData data = new ValidatorData(output, invokePath.toString()); + data.getOutput().format(data.getConfiguration()); + test.getValidator().accept(data); + } + } + private record LicenseInfo(String id, String family, boolean approval, boolean hasNotes) { LicenseInfo(String id, boolean approval, boolean hasNotes) { this(id, id, approval, hasNotes); diff --git a/apache-rat-core/src/test/java/org/apache/rat/analysis/HeaderCheckWorkerTest.java b/apache-rat-core/src/test/java/org/apache/rat/analysis/HeaderCheckWorkerTest.java index b00aec082..ec6420aed 100644 --- a/apache-rat-core/src/test/java/org/apache/rat/analysis/HeaderCheckWorkerTest.java +++ b/apache-rat-core/src/test/java/org/apache/rat/analysis/HeaderCheckWorkerTest.java @@ -22,7 +22,11 @@ import java.io.StringReader; import java.util.Collections; +import java.util.List; +import java.util.SortedSet; +import java.util.TreeSet; +import org.apache.commons.collections4.set.UnmodifiableSortedSet; import org.apache.rat.Defaults; import org.apache.rat.api.Document; import org.apache.rat.configuration.builders.AnyBuilder; @@ -38,12 +42,19 @@ public class HeaderCheckWorkerTest { + /** Create an unmodifiable sorted set from members */ + private UnmodifiableSortedSet asLicenses(ILicense... licenses) { + SortedSet inner = new TreeSet<>(); + inner.addAll(List.of(licenses)); + return (UnmodifiableSortedSet) UnmodifiableSortedSet.unmodifiableSortedSet(inner); + } + @Test public void emptyInputIsUnknownTest() throws RatHeaderAnalysisException { final Document subject = new TestingDocument("subject"); subject.getMetaData().setApprovalPredicate(Defaults.builder().build().getLicenseSetFactory().getApprovedLicensePredicate()); ILicense matcher = new TestingLicense("test", "test"); - HeaderCheckWorker worker = new HeaderCheckWorker(new TestingMatcher(), new StringReader(""), Lists.list(matcher), subject); + HeaderCheckWorker worker = new HeaderCheckWorker(new TestingMatcher(), new StringReader(""), asLicenses(matcher), subject); worker.read(); assertThat(subject.getMetaData().unapprovedLicenses().count()).isEqualTo(1); assertThat(subject.getMetaData().unapprovedLicenses().toList().get(0).getLicenseFamily()).isEqualTo(ILicenseFamily.UNKNOWN); @@ -53,7 +64,7 @@ public void emptyInputIsUnknownTest() throws RatHeaderAnalysisException { public void generatedFileDetectionTest() throws Exception { final Document subject = new TestingDocument(new StringReader("Generated from configure.ac by autoheader"), "subject"); IHeaderMatcher matcher = new AnyBuilder().setResource("/org/apache/rat/generation-keywords.txt").build(); - HeaderCheckWorker worker = new HeaderCheckWorker(matcher, subject.reader(), Collections.emptyList(), subject); + HeaderCheckWorker worker = new HeaderCheckWorker(matcher, subject.reader(), asLicenses(), subject); worker.read(); assertThat(subject.getMetaData().getDocumentType()).isEqualTo(Document.Type.IGNORED); } diff --git a/apache-rat-core/src/test/java/org/apache/rat/commandline/ArgTests.java b/apache-rat-core/src/test/java/org/apache/rat/commandline/ArgTests.java index 1d51a5f29..b50073487 100644 --- a/apache-rat-core/src/test/java/org/apache/rat/commandline/ArgTests.java +++ b/apache-rat-core/src/test/java/org/apache/rat/commandline/ArgTests.java @@ -56,9 +56,9 @@ public void setOut(File file) { String fileName = name.replace("/", DocumentName.FSInfo.getDefault().dirSeparator()); File expected = new File(fileName); - CommandLine commandLine = createCommandLine(new String[] {"--output-file", fileName}); + String[] args = new String[] {"--output-file", fileName}; OutputFileConfig configuration = new OutputFileConfig(); - ArgumentContext ctxt = new ArgumentContext(new File("."), configuration, commandLine); + ArgumentContext ctxt = new ArgumentContext(new File("."), configuration, OptionCollection.buildOptions(), args); Arg.processArgs(ctxt, CLIOptionCollection.INSTANCE); assertThat(configuration.actual.getAbsolutePath()).isEqualTo(expected.getCanonicalPath()); } diff --git a/apache-rat-core/src/test/java/org/apache/rat/configuration/XMLConfigurationReaderTest.java b/apache-rat-core/src/test/java/org/apache/rat/configuration/XMLConfigurationReaderTest.java index a78632733..949dd1f18 100644 --- a/apache-rat-core/src/test/java/org/apache/rat/configuration/XMLConfigurationReaderTest.java +++ b/apache-rat-core/src/test/java/org/apache/rat/configuration/XMLConfigurationReaderTest.java @@ -29,6 +29,7 @@ import java.net.URL; import java.util.Collection; +import java.util.stream.Collectors; import org.apache.rat.analysis.IHeaderMatcher; import org.apache.rat.config.parameters.ComponentType; import org.apache.rat.config.parameters.Description; @@ -55,6 +56,10 @@ public class XMLConfigurationReaderTest { public static final String[] EXPECTED_LICENSES = { "AL1.0", "AL1.1", "AL2.0", "BSD-3", "DOJO", "TMF", "CDDL1", "ILLUMOS", "GPL1", "GPL2", "GPL3", "MIT", "OASIS", "W3C", "W3CD" }; + public static final String[] APPROVED_LICENSES = { "AL1.0", "AL1.1", "AL2.0", "BSD-3", "DOJO", "TMF", "CDDL1", "ILLUMOS", + "MIT", "OASIS", "W3C", "W3CD" }; + + @Test public void approvedLicenseIdTest() throws URISyntaxException { XMLConfigurationReader reader = new XMLConfigurationReader(); @@ -62,9 +67,8 @@ public void approvedLicenseIdTest() throws URISyntaxException { assertThat(url).isNotNull(); reader.read(url.toURI()); - Collection readCategories = reader.approvedLicenseId(); - - assertArrayEquals(APPROVED_IDS, readCategories.toArray(new String[readCategories.size()])); + Collection actual = reader.approvedLicenseId(); + assertThat(actual).containsExactlyInAnyOrder(APPROVED_IDS); } @Test @@ -80,9 +84,12 @@ public void LicensesTest() throws URISyntaxException { public void LicenseFamiliesTest() throws URISyntaxException { XMLConfigurationReader reader = new XMLConfigurationReader(); URL url = XMLConfigurationReaderTest.class.getResource("/org/apache/rat/default.xml"); + assertNotNull(url); reader.read(url.toURI()); - assertArrayEquals(EXPECTED_IDS, reader.readFamilies().stream().map(x -> x.getFamilyCategory().trim()).toArray(String[]::new)); + Collection actual = reader.readFamilies().stream().map(lf -> lf.getFamilyCategory().trim()) + .collect(Collectors.toList()); + assertThat(actual).containsExactlyInAnyOrder(EXPECTED_IDS); } private void checkMatcher(String name, Class clazz) { @@ -118,7 +125,7 @@ public void descriptionTest() throws SecurityException, URISyntaxException { IHeaderMatcher.Builder builder = MatcherBuilderTracker.getMatcherBuilder("copyright"); Description desc = DescriptionBuilder.buildMap(builder.getClass()); - assertNotNull(desc, () -> "did not build description for 'copyright'"); + assertNotNull(desc, "did not build description for 'copyright'"); assertEquals("copyright", desc.getCommonName()); assertEquals(ComponentType.MATCHER, desc.getType()); assertFalse(desc.isCollection()); diff --git a/apache-rat-core/src/test/java/org/apache/rat/document/DocumentNameBuilderTest.java b/apache-rat-core/src/test/java/org/apache/rat/document/DocumentNameBuilderTest.java index 0e0f0d1f7..122cf72af 100644 --- a/apache-rat-core/src/test/java/org/apache/rat/document/DocumentNameBuilderTest.java +++ b/apache-rat-core/src/test/java/org/apache/rat/document/DocumentNameBuilderTest.java @@ -19,23 +19,23 @@ package org.apache.rat.document; import java.io.File; -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Stream; + +import org.apache.commons.lang3.tuple.Pair; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.FieldSource; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.apache.rat.document.FSInfoTest.WINDOWS; public class DocumentNameBuilderTest { - @ParameterizedTest(name="{0}") - @MethodSource("buildTestData") - void buildTest(String testName, DocumentName documentName, String name, String shortName, String baseName, String root, + private static final DocumentName.FSInfo[] TEST_SUITE = FSInfoTest.TEST_SUITE; + + void assertDocumentName(DocumentName documentName, String name, String shortName, String baseName, String root, String directorySeparator, Boolean isCaseSensitive, String localized, String localizedArg) { assertThat(documentName.getName()).as("Invalid name").isEqualTo(name); assertThat(documentName.getShortName()).as("Invalid short name").isEqualTo(shortName); @@ -48,120 +48,182 @@ void buildTest(String testName, DocumentName documentName, String name, String s assertThat(documentName.isCaseSensitive()).as("Invalid case sensitivity").isFalse(); } assertThat(documentName.localized()).as("Invalid localized ").isEqualTo(localized); - final String sep = documentName.getDirectorySeparator().equals("/") ? "\\" : "/"; - assertThat(documentName.localized(sep)).as(() -> String.format("Invalid localized('%s')", sep)).isEqualTo(localizedArg); + assertThat(documentName.localized("+")).as("Invalid localized('+')").isEqualTo(localizedArg); } - static Stream buildTestData() { - List lst = new ArrayList<>(); - - // - String testName = "windows\\foo direct"; - DocumentName documentName = DocumentName.builder(WINDOWS).setName("C:\\windows\\foo").setBaseName("C:\\windows").build(); - lst.add(Arguments.of( testName, documentName, "C:\\windows\\foo", "foo", "C:\\windows", "C:", "\\", false, - "\\foo", "/foo")); - DocumentName baseName = documentName; - - // - testName = "builder(docName)"; - documentName = DocumentName.builder(baseName).build(); - lst.add(Arguments.of( testName, documentName, "C:\\windows\\foo", "foo", "C:\\windows", "C:", "\\", false, - "\\foo", "/foo")); - - // - testName = "windows\\foo\\bar by resolve"; - documentName = baseName.resolve("bar"); - lst.add(Arguments.of(testName, documentName, "C:\\windows\\foo\\bar", "bar", "C:\\windows", "C:", "\\", false, - "\\foo\\bar", "/foo/bar")); - - // - testName = "windows\\foo\\direct by basename"; - documentName = DocumentName.builder(baseName).setName("windows\\foo\\direct").build(); - lst.add(Arguments.of(testName, documentName, "C:\\windows\\foo\\direct", "direct", "C:\\windows", "C:", "\\", false, - "\\foo\\direct", "/foo/direct")); - - // - testName = "windows\\foo\\bar by file"; - File file = mock(File.class); - File parent = mock(File.class); - when(file.getAbsolutePath()).thenReturn("C:\\windows\\foo\\bar"); - when(file.getParentFile()).thenReturn(parent); - when(file.isDirectory()).thenReturn(false); - when(parent.getAbsolutePath()).thenReturn("C:\\windows\\foo"); - when(parent.isDirectory()).thenReturn(true); - documentName = new DocumentName.Builder(WINDOWS, file).build(); - lst.add(Arguments.of(testName, documentName, "C:\\windows\\foo\\bar", "bar", "C:\\windows\\foo", "C:", "\\", false, - "\\bar", "/bar")); - - // - testName = "windows\\foo\\bar by directory"; - file = mock(File.class); - parent = mock(File.class); - when(file.getAbsolutePath()).thenReturn("C:\\windows\\foo\\bar"); - when(file.getParentFile()).thenReturn(parent); - when(file.isDirectory()).thenReturn(true); - when(parent.getAbsolutePath()).thenReturn("C:\\windows\\foo"); - when(parent.isDirectory()).thenReturn(true); - documentName = new DocumentName.Builder(WINDOWS, file).build(); - lst.add(Arguments.of(testName, documentName, "C:\\windows\\foo\\bar", "bar", "C:\\windows\\foo\\bar", "C:", "\\", false, - "\\", "/")); - - // - testName = "windows setRoot"; - documentName = DocumentName.builder(baseName).setRoot("D:").build(); - lst.add(Arguments.of(testName, documentName, "D:\\windows\\foo", "foo", "C:\\windows", "D:", "\\", false, - "D:\\windows\\foo", "D:/windows/foo")); - - testName = "windows setRoot(null)"; - documentName = DocumentName.builder(baseName).setRoot(null).build(); - lst.add(Arguments.of(testName, documentName, "\\windows\\foo", "foo", "C:\\windows", "", "\\", false, - "\\windows\\foo", "/windows/foo")); - - testName = "windows setRoot('')"; - documentName = DocumentName.builder(baseName).setRoot("").build(); - lst.add(Arguments.of(testName, documentName, "\\windows\\foo", "foo", "C:\\windows", "", "\\", false, - "\\windows\\foo", "/windows/foo")); - - // - testName = "windows setName('baz')"; - documentName = DocumentName.builder(baseName).setName("baz").build(); - lst.add(Arguments.of(testName, documentName, "C:\\windows\\baz", "baz", "C:\\windows", "C:", "\\", false, - "\\baz", "/baz")); - - testName = "windows setName((String)null)"; - documentName = DocumentName.builder(baseName).setName((String)null).build(); - lst.add(Arguments.of(testName, documentName, "C:\\windows", "windows", "C:\\windows", "C:", "\\", false, - "\\", "/")); - - testName = "windows setName('')"; - documentName = DocumentName.builder(baseName).setName("").build(); - lst.add(Arguments.of(testName, documentName, "C:\\windows", "windows", "C:\\windows", "C:", "\\", false, - "\\", "/")); - - file = mock(File.class); - parent = mock(File.class); - when(file.getAbsolutePath()).thenReturn("C:\\windows\\foo\\bar"); + @ParameterizedTest + @FieldSource("TEST_SUITE") + void baseNamePreserved(DocumentName.FSInfo fsInfo) { + final String root = fsInfo.roots()[0]; + final String baseNameStr = root + fsInfo; + // create a document os/bar with base name of os. + final DocumentName siblingName = DocumentName.builder(fsInfo).setName("bar").setBaseName(fsInfo.toString()).build(); + + // check foo/baz + String nameStr = fsInfo.mkPath("foo", "baz"); + DocumentName documentName = DocumentName.builder(siblingName).setName(nameStr).build(); + String expected = root + fsInfo.mkPath(fsInfo.toString(), "foo", "baz"); + assertThat(documentName.getName()).as("relative value").isEqualTo(expected); + assertDocumentName(documentName, expected, "baz", baseNameStr, root, fsInfo.dirSeparator(), fsInfo.isCaseSensitive(), + fsInfo.dirSeparator() + nameStr, "+foo+baz"); + + //String fqName = fsInfo.mkPath("", fsInfo.toString(), "foo", "baz"); + documentName = DocumentName.builder(siblingName).setName(expected).build(); + assertThat(documentName.getName()).as("absolute value").isEqualTo(expected); + assertDocumentName(documentName, expected, "baz", baseNameStr, root, fsInfo.dirSeparator(), fsInfo.isCaseSensitive(), + fsInfo.dirSeparator() + nameStr, "+foo+baz"); + + } + + @ParameterizedTest + @FieldSource("TEST_SUITE") + void documentNameFromFQNameWithBaseName(DocumentName.FSInfo fsInfo) { + final String root = fsInfo.roots()[0]; + final String baseNameStr = root + fsInfo; + String fqName = root + fsInfo.mkPath(fsInfo.toString(), "foo"); + DocumentName documentName = DocumentName.builder(fsInfo).setName(fqName).setBaseName(baseNameStr).build(); + assertDocumentName(documentName, fqName, "foo", baseNameStr, root, fsInfo.dirSeparator(), fsInfo.isCaseSensitive(), + fsInfo.dirSeparator() + "foo", "+foo"); + } + + @ParameterizedTest + @FieldSource("TEST_SUITE") + void noBaseNameThowsException(DocumentName.FSInfo fsInfo) { + final String root = fsInfo.roots()[0]; + String fqName = root + fsInfo.mkPath(fsInfo.toString(), "foo"); + assertThatThrownBy(() -> DocumentName.builder(fsInfo).setName(fqName).build()) + .isInstanceOf(NullPointerException.class) + .hasMessage("Basename must not be null"); + } + + @ParameterizedTest + @FieldSource("TEST_SUITE") + void noNameThowsException(DocumentName.FSInfo fsInfo) { + final String root = fsInfo.roots()[0]; + String fqName = root + fsInfo.mkPath(fsInfo.toString(), "foo"); + assertThatThrownBy(() -> DocumentName.builder(fsInfo).setBaseName(fqName).build()) + .isInstanceOf(NullPointerException.class) + .hasMessage("Name must not be null"); + } + + + @ParameterizedTest + @FieldSource("TEST_SUITE") + void DocumentNameFromDocumentName(DocumentName.FSInfo fsInfo) { + final String root = fsInfo.roots()[0]; + final String baseNameStr = root + fsInfo; + String fqName = root + fsInfo.mkPath(fsInfo.toString(), "foo"); + DocumentName expected = DocumentName.builder(fsInfo).setName(fqName).setBaseName(baseNameStr).build(); + + DocumentName actual = DocumentName.builder(expected).build(); + assertThat(actual).isEqualTo(expected); + + assertDocumentName(actual, fqName, "foo", baseNameStr, root, fsInfo.dirSeparator(), fsInfo.isCaseSensitive(), + fsInfo.dirSeparator() + "foo", "+foo"); + + } + + @ParameterizedTest + @FieldSource("TEST_SUITE") + void builderOnDocumentNameWithNameSharesBaseName(DocumentName.FSInfo fsInfo) { + final String root = fsInfo.roots()[0]; + final String baseNameStr = root + fsInfo; + + String fqName = root + fsInfo.mkPath(fsInfo.toString(), "foo"); + DocumentName firstName = DocumentName.builder(fsInfo).setName(fqName).setBaseName(baseNameStr).build(); + + fqName = root + fsInfo.mkPath(fsInfo.toString(), "bar"); + DocumentName actual = DocumentName.builder(firstName).setName(fqName).setBaseName(baseNameStr).build(); + + assertDocumentName(actual, fqName, "bar", baseNameStr, root, fsInfo.dirSeparator(), fsInfo.isCaseSensitive(), + fsInfo.dirSeparator() + "bar", "+bar"); + } + + @ParameterizedTest + @FieldSource("TEST_SUITE") + void builderOnFile(DocumentName.FSInfo fsInfo) { + final String root = fsInfo.roots()[0]; + final String baseNameStr = root + fsInfo.mkPath(fsInfo.toString(), "foo"); + final String fqName = root + fsInfo.mkPath(fsInfo.toString(), "foo", "bar"); + File file = mock(File.class); + File parent = mock(File.class); + when(file.getAbsolutePath()).thenReturn(fqName); when(file.getParentFile()).thenReturn(parent); when(file.isDirectory()).thenReturn(false); - when(parent.getAbsolutePath()).thenReturn("C:\\windows\\foo"); + when(parent.getAbsolutePath()).thenReturn(baseNameStr); when(parent.isDirectory()).thenReturn(true); - testName = "windows setName(file)"; - documentName = DocumentName.builder(baseName).setName(file).build(); - lst.add(Arguments.of(testName, documentName, "C:\\windows\\foo\\bar", "bar", "C:\\windows\\foo", "C:", "\\", false, - "\\bar", "/bar")); - - file = mock(File.class); - parent = mock(File.class); - when(file.getAbsolutePath()).thenReturn("C:\\windows\\foo\\bar"); - when(file.getParentFile()).thenReturn(parent); - when(file.isDirectory()).thenReturn(true); - when(parent.getAbsolutePath()).thenReturn("C:\\windows\\foo"); - when(parent.isDirectory()).thenReturn(true); - testName = "windows setName(directory)"; - documentName = DocumentName.builder(baseName).setName(file).build(); - lst.add(Arguments.of(testName, documentName, "C:\\windows\\foo\\bar", "bar", "C:\\windows\\foo\\bar", "C:", "\\", false, - "\\", "/")); - return lst.stream(); + DocumentName actual = new DocumentName.Builder(fsInfo, file).build(); + + assertDocumentName(actual, fqName, "bar", baseNameStr, root, fsInfo.dirSeparator(), fsInfo.isCaseSensitive(), + fsInfo.dirSeparator() + "bar", "+bar"); + + } + + @Test + void windowRootDifference() { + DocumentName.FSInfo fsInfo = WINDOWS; + String root = fsInfo.roots()[0]; + String baseNameStr = root + fsInfo.mkPath(fsInfo.toString(), "foo"); + String fqName = root + fsInfo.mkPath(fsInfo.toString(), "foo", "bar"); + DocumentName firstName = DocumentName.builder(fsInfo).setName(fqName).setBaseName(baseNameStr).build(); + + root = "D:\\"; + DocumentName actual = DocumentName.builder(firstName).setRoot(root).build(); + + baseNameStr = root + fsInfo.mkPath(fsInfo.toString(), "foo"); + fqName = root + fsInfo.mkPath(fsInfo.toString(), "foo", "bar"); + + assertDocumentName(actual, fqName, "bar", baseNameStr, root, fsInfo.dirSeparator(), fsInfo.isCaseSensitive(), + fsInfo.dirSeparator() + "bar", "+bar"); + + } + + @ParameterizedTest + @FieldSource("TEST_SUITE") + void setRootNull(DocumentName.FSInfo fsInfo) { + String root = fsInfo.roots()[0]; + String baseNameStr = root + fsInfo.mkPath(fsInfo.toString(), "foo"); + String fqName = root + fsInfo.mkPath(fsInfo.toString(), "foo", "bar"); + DocumentName firstName = DocumentName.builder(fsInfo).setName(fqName).setBaseName(baseNameStr).build(); + + root = null; + DocumentName actual = DocumentName.builder(firstName).setRoot(root).build(); + + baseNameStr = fsInfo.mkPath(fsInfo.toString(), "foo"); + fqName = fsInfo.mkPath(fsInfo.toString(), "foo", "bar"); + + assertDocumentName(actual, fqName, "bar", baseNameStr, "", fsInfo.dirSeparator(), fsInfo.isCaseSensitive(), + fsInfo.dirSeparator() + "bar", "+bar"); + + } + + @ParameterizedTest + @FieldSource("TEST_SUITE") + void setRootEmpty(DocumentName.FSInfo fsInfo) { + String root = fsInfo.roots()[0]; + String baseNameStr = root + fsInfo.mkPath(fsInfo.toString(), "foo"); + String fqName = root + fsInfo.mkPath(fsInfo.toString(), "foo", "bar"); + DocumentName firstName = DocumentName.builder(fsInfo).setName(fqName).setBaseName(baseNameStr).build(); + + root = ""; + DocumentName actual = DocumentName.builder(firstName).setRoot(root).build(); + + baseNameStr = root + fsInfo.mkPath(fsInfo.toString(), "foo"); + fqName = root + fsInfo.mkPath(fsInfo.toString(), "foo", "bar"); + + assertDocumentName(actual, fqName, "bar", baseNameStr, root, fsInfo.dirSeparator(), fsInfo.isCaseSensitive(), + fsInfo.dirSeparator() + "bar", "+bar"); + + } + + @ParameterizedTest + @FieldSource("TEST_SUITE") + void splitRootsTest(DocumentName.FSInfo fsInfo) { + String root = fsInfo.roots()[0]; + String path = fsInfo.mkPath("My", "path", "to", "a", "file.txt"); + Pair result = DocumentName.builder(fsInfo).splitRoot(root + path); + assertThat(result.getLeft()).isEqualTo(root); + assertThat(result.getRight()).isEqualTo(path); } } diff --git a/apache-rat-core/src/test/java/org/apache/rat/document/DocumentNameTest.java b/apache-rat-core/src/test/java/org/apache/rat/document/DocumentNameTest.java index beeeeca94..64982082f 100644 --- a/apache-rat-core/src/test/java/org/apache/rat/document/DocumentNameTest.java +++ b/apache-rat-core/src/test/java/org/apache/rat/document/DocumentNameTest.java @@ -18,77 +18,50 @@ */ package org.apache.rat.document; +import java.io.BufferedReader; import java.io.File; import java.io.FileFilter; +import java.io.FileReader; +import java.io.FileWriter; import java.io.FilenameFilter; import java.io.IOException; -import java.nio.file.FileSystems; +import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; -import java.util.HashSet; import java.util.List; -import java.util.Set; import java.util.stream.Stream; -import org.apache.commons.io.FileUtils; -import org.apache.commons.lang3.tuple.Pair; import org.apache.rat.config.exclusion.ExclusionUtils; import org.apache.rat.document.DocumentName.FSInfo; -import org.assertj.core.util.Files; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.FieldSource; import org.junit.jupiter.params.provider.MethodSource; -import org.mockito.Mockito; import static org.assertj.core.api.Assertions.assertThat; -import static org.apache.rat.document.FSInfoTest.OSX; -import static org.apache.rat.document.FSInfoTest.UNIX; -import static org.apache.rat.document.FSInfoTest.WINDOWS; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.mockito.ArgumentMatchers.any; public class DocumentNameTest { - - public static DocumentName mkName(Path tempDir, FSInfo fsInfo) { - File docFile = mkFile(tempDir.toFile(), fsInfo); - DocumentName result = DocumentName.builder(fsInfo).setName(docFile).build(); - DocumentName mocked = Mockito.spy(result); - - String fn = result.localized(FileSystems.getDefault().getSeparator()); - File file = tempDir.resolve(fn.substring(1)).toFile(); - File mockedFile = mkFile(file, fsInfo); - when(mocked.asFile()).thenReturn(mockedFile); - - assertThat(mocked.asFile()).isEqualTo(mockedFile); - return mocked; - } + private static final FSInfo[] TEST_SUITE = FSInfoTest.TEST_SUITE; private static File[] listFiles(File file, FSInfo fsInfo) { File[] fileList = file.listFiles(); - if (fileList == null) { - return fileList; - } - return Arrays.stream(fileList).map(f -> mkFile(f, fsInfo)).toArray(File[]::new); + return fileList == null ? null : Arrays.stream(fileList).map(f -> mkFile(f, fsInfo)).toArray(File[]::new); } private static File[] listFiles(File file, FSInfo fsInfo, FileFilter filter) { File[] fileList = file.listFiles(); - if (fileList == null) { - return fileList; - } - return Arrays.stream(fileList).map(f -> mkFile(f, fsInfo)).filter(filter::accept).toArray(File[]::new); + return fileList == null ? null : Arrays.stream(fileList).map(f -> mkFile(f, fsInfo)).filter(filter::accept).toArray(File[]::new); } private static File[] listFiles(File file, FSInfo fsInfo, FilenameFilter filter) { File[] fileList = file.listFiles(); - if (fileList == null) { - return fileList; - } - return Arrays.stream(fileList).map(f -> mkFile(f, fsInfo)).filter(x -> filter.accept(x, x.getName())).toArray(File[]::new); + return fileList == null ? null : Arrays.stream(fileList).map(f -> mkFile(f, fsInfo)).filter(x -> filter.accept(x, x.getName())).toArray(File[]::new); } public static File mkFile(final File file, final FSInfo fsInfo) { @@ -105,222 +78,124 @@ public static File mkFile(final File file, final FSInfo fsInfo) { return mockedFile; } - public static DocumentName mkName(Path tempDir, DocumentName baseDir, String pth) throws IOException { - DocumentName result = baseDir.resolve(ExclusionUtils.convertSeparator(pth, "/", baseDir.getDirectorySeparator())); - DocumentName mocked = Mockito.spy(result); - - String fn = result.localized(FileSystems.getDefault().getSeparator()); - File file = tempDir.resolve(fn.substring(1)).toFile(); - File parent = file.getParentFile(); - if (parent.exists() && !parent.isDirectory()) { - parent.delete(); - } - parent.mkdirs(); - if (file.exists()) { - if (file.isDirectory()) { - FileUtils.deleteDirectory(file); - } else { - FileUtils.delete(file); - } - } - file.createNewFile(); - when(mocked.asFile()).thenReturn(file); - return mocked; - } - @ParameterizedTest(name = "{index} {0} {2}") @MethodSource("resolveTestData") - void resolveTest(String testName, DocumentName base, String toResolve, DocumentName expected) { + void resolveTest(DocumentName.FSInfo fsInfo, DocumentName base, String toResolve, DocumentName expected) { DocumentName actual = base.resolve(toResolve); assertThat(actual).isEqualTo(expected); } private static Stream resolveTestData() { List lst = new ArrayList<>(); + DocumentName base; + DocumentName expected; + for (DocumentName.FSInfo fsInfo : TEST_SUITE) { + String root = fsInfo.roots()[0]; + for (String baseName : List.of(root, root + fsInfo.mkPath("from", "base"))) { + String name = fsInfo.mkPath("", "dir", fsInfo.toString()); - DocumentName base = DocumentName.builder(UNIX).setName("/dir/unix").setBaseName("/").build(); - - DocumentName expected = DocumentName.builder(UNIX).setName("/dir/unix/relative").setBaseName("/").build(); - lst.add(Arguments.of("unix", base, "relative", expected)); - - expected = DocumentName.builder(UNIX).setName("/from/root").setBaseName("/").build(); - lst.add(Arguments.of("unix", base, "/from/root", expected)); - - expected = DocumentName.builder(UNIX).setName("dir/up/and/down").setBaseName("/").build(); - lst.add(Arguments.of("unix", base, "../up/and/down", expected)); + base = DocumentName.builder(fsInfo).setName(name).setBaseName(baseName).build(); - expected = DocumentName.builder(UNIX).setName("/from/root").setBaseName("/").build(); - lst.add(Arguments.of("unix", base, "\\from\\root", expected)); + expected = DocumentName.builder(fsInfo).setName(fsInfo.mkPath("", "dir", fsInfo.toString(), "relative")).setBaseName(baseName).build(); + lst.add(Arguments.of(fsInfo, base, "relative", expected)); - expected = DocumentName.builder(UNIX).setName("dir/up/and/down").setBaseName("/").build(); - lst.add(Arguments.of("unix", base, "..\\up\\and\\down", expected)); + expected = DocumentName.builder(fsInfo).setName(fsInfo.mkPath("", "from", "root")).setBaseName(baseName).build(); + lst.add(Arguments.of(fsInfo, base, fsInfo.mkPath("", "from", "root"), expected)); - // WINDOWS - base = DocumentName.builder(WINDOWS).setName("\\dir\\windows").setBaseName("C:\\").build(); + expected = DocumentName.builder(fsInfo).setName(fsInfo.mkPath("dir", "up", "and", "down")).setBaseName(baseName).build(); + lst.add(Arguments.of(fsInfo, base, fsInfo.mkPath("..", "up", "and", "down"), expected)); - expected = DocumentName.builder(WINDOWS).setName("\\dir\\windows\\relative").setBaseName("C:\\").build(); - lst.add(Arguments.of("windows", base, "relative", expected)); - - expected = DocumentName.builder(WINDOWS).setName("\\from\\root").setBaseName("C:\\").build(); - lst.add(Arguments.of("windows", base, "/from/root", expected)); - - expected = DocumentName.builder(WINDOWS).setName("dir\\up\\and\\down").setBaseName("C:\\").build(); - lst.add(Arguments.of("windows", base, "../up/and/down", expected)); - - expected = DocumentName.builder(WINDOWS).setName("\\from\\root").setBaseName("C:\\").build(); - lst.add(Arguments.of("windows", base, "\\from\\root", expected)); - - expected = DocumentName.builder(WINDOWS).setName("dir\\up\\and\\down").setBaseName("C:\\").build(); - lst.add(Arguments.of("windows", base, "..\\up\\and\\down", expected)); - - // OSX - base = DocumentName.builder(OSX).setName("/dir/osx").setBaseName("/").build(); - - expected = DocumentName.builder(OSX).setName("/dir/osx/relative").setBaseName("/").build(); - lst.add(Arguments.of("osx", base, "relative", expected)); - - expected = DocumentName.builder(OSX).setName("/from/root").setBaseName("/").build(); - lst.add(Arguments.of("osx", base, "/from/root", expected)); - - expected = DocumentName.builder(OSX).setName("dir/up/and/down").setBaseName("/").build(); - lst.add(Arguments.of("osx", base, "../up/and/down", expected)); - - expected = DocumentName.builder(OSX).setName("/from/root").setBaseName("/").build(); - lst.add(Arguments.of("osx", base, "\\from\\root", expected)); - - expected = DocumentName.builder(OSX).setName("dir/up/and/down").setBaseName("/").build(); - lst.add(Arguments.of("osx", base, "..\\up\\and\\down", expected)); + expected = DocumentName.builder(fsInfo).setName(fsInfo.mkPath("", "from", "root")).setBaseName(baseName).build(); + String wrongSeparator = fsInfo.dirSeparator().equals("/") ? "\\" : "/"; + lst.add(Arguments.of(fsInfo, base, String.join(wrongSeparator, "", "from", "root"), expected)); + expected = DocumentName.builder(fsInfo).setName(fsInfo.mkPath("dir", "up", "and", "down")).setBaseName(baseName).build(); + lst.add(Arguments.of(fsInfo, base, String.join(wrongSeparator, "..", "up", "and", "down"), expected)); + } + } return lst.stream(); } - @Test - void localizeTest() { - DocumentName documentName = DocumentName.builder(UNIX).setName("/a/b/c") - .setBaseName("/a").build(); - assertThat(documentName.localized()).isEqualTo("/b/c"); - assertThat(documentName.localized("-")).isEqualTo("-b-c"); - - documentName = DocumentName.builder(WINDOWS).setName("\\a\\b\\c") - .setBaseName("\\a").build(); - assertThat(documentName.localized()).isEqualTo("\\b\\c"); + @ParameterizedTest + @FieldSource("TEST_SUITE") + void localizeTest(FSInfo fsInfo) { + DocumentName documentName = DocumentName.builder(fsInfo).setName( + fsInfo.mkPath("", "a", "b", "c")) + .setBaseName(fsInfo.mkPath("", "a")).build(); + assertThat(documentName.localized()).isEqualTo(fsInfo.mkPath("", "b", "c")); assertThat(documentName.localized("-")).isEqualTo("-b-c"); - - documentName = DocumentName.builder(OSX).setName("/a/b/c") - .setBaseName("/a").build(); - assertThat(documentName.localized()).isEqualTo("/b/c"); - assertThat(documentName.localized("-")).isEqualTo("-b-c"); - } - - @ParameterizedTest(name = "{index} {0}") - @MethodSource("validBuilderData") - void validBuilderTest(String testName, DocumentName.Builder builder, String root, String name, String baseName, String dirSeparator) { - DocumentName underTest = builder.build(); - assertThat(underTest.getRoot()).as(testName).isEqualTo(root); - assertThat(underTest.getDirectorySeparator()).as(testName).isEqualTo(dirSeparator); - assertThat(underTest.getName()).as(testName).isEqualTo(root + dirSeparator + name); - assertThat(underTest.getBaseName()).as(testName).isEqualTo(root + dirSeparator + baseName); } - private static Stream validBuilderData() { - List lst = new ArrayList<>(); - File f = Files.newTemporaryFile(); - - Set roots = new HashSet<>(); - File[] rootary = File.listRoots(); - if (rootary != null) { - for (File root : rootary) { - String name = root.getPath(); - roots.add(name); - } - } - - String name = f.getAbsolutePath(); - String root = ""; - for (String sysRoot : roots) { - if (name.startsWith(sysRoot)) { - name = name.substring(sysRoot.length()); - if (sysRoot.endsWith(File.separator)) { - root = sysRoot.substring(0, sysRoot.length() - File.separator.length()); - } - break; - } + @Test + void asFileTest() throws IOException { + File expected = File.createTempFile("docNameTest", ".txt"); + try (FileWriter fw = new FileWriter(expected, StandardCharsets.UTF_8)) { + fw.write("Hello world"); } - - File p = f.getParentFile(); - String baseName = p.getAbsolutePath().substring(root.length()); - if (baseName.startsWith(File.separator)) { - baseName = baseName.substring(File.separator.length()); + DocumentName underTest = DocumentName.builder(expected).build(); + File actual = underTest.asFile(); + try (FileReader fr = new FileReader(actual, StandardCharsets.UTF_8); + BufferedReader br = new BufferedReader(fr)) { + assertThat(br.readLine()).isEqualTo("Hello world"); } - lst.add(Arguments.of("setName(file)", DocumentName.builder().setName(f), root, name, baseName, File.separator)); - lst.add(Arguments.of("Builder(file)", DocumentName.builder(f), root, name, baseName, File.separator)); - - lst.add(Arguments.of("setName(dir)", DocumentName.builder().setName(p), root, baseName, baseName, File.separator)); - lst.add(Arguments.of("Builder(dir)", DocumentName.builder(p), root, baseName, baseName, File.separator)); - - File r = new File(root.isEmpty() ? File.separator : root); - lst.add(Arguments.of("setName(root)", DocumentName.builder().setName(r), root, "", "", File.separator)); - lst.add(Arguments.of("Builder(root)", DocumentName.builder(r), root, "", "", File.separator)); - - - lst.add(Arguments.of("foo/bar foo", DocumentName.builder(UNIX) - .setName("/foo/bar").setBaseName("foo"), "", "foo/bar", "foo", "/")); - - DocumentName.Builder builder = DocumentName.builder(WINDOWS).setName("\\foo\\bar").setBaseName("C:\\foo") - .setRoot("C:"); - lst.add(Arguments.of("\\foo\\bar foo", builder, "C:", "foo\\bar", "foo", "\\")); - - lst.add(Arguments.of("foo/bar foo", DocumentName.builder(OSX) - .setName("/foo/bar").setBaseName("foo"), "", "foo/bar", "foo", "/")); - - return lst.stream(); } @Test - void splitRootsTest() { - Pair result = DocumentName.builder(WINDOWS).splitRoot("C:\\My\\path\\to\\a\\file.txt"); - assertThat(result.getLeft()).isEqualTo("C:"); - assertThat(result.getRight()).isEqualTo("My\\path\\to\\a\\file.txt"); - - result = DocumentName.builder(UNIX).splitRoot("/My/path/to/a/file.txt"); - assertThat(result.getLeft()).isEqualTo(""); - assertThat(result.getRight()).isEqualTo("My/path/to/a/file.txt"); - - result = DocumentName.builder(OSX).splitRoot("/My/path/to/a/file.txt"); - assertThat(result.getLeft()).isEqualTo(""); - assertThat(result.getRight()).isEqualTo("My/path/to/a/file.txt"); + void asPathTest() throws IOException { + File expected = File.createTempFile("docNameTest", ".txt"); + try (FileWriter fw = new FileWriter(expected, StandardCharsets.UTF_8)) { + fw.write("Hello world"); + } + DocumentName underTest = DocumentName.builder(expected).build(); + Path actual = underTest.asPath(); + Path root = Path.of(underTest.getRoot()); + File file = root.resolve(actual).toFile(); + try (FileReader fr = new FileReader(file, StandardCharsets.UTF_8); + BufferedReader br = new BufferedReader(fr)) { + assertThat(br.readLine()).isEqualTo("Hello world"); + } } - @Test - void archiveEntryNameTest() { - String entryName = "./anArchiveEntry.txt"; - DocumentName archiveName = DocumentName.builder(WINDOWS) - .setName("C:\\archives\\anArchive.zip").setBaseName("C:\\archives").build(); - - assertThat(archiveName.getRoot()).isEqualTo("C:"); - assertThat(archiveName.getDirectorySeparator()).isEqualTo("\\"); - assertThat(archiveName.getBaseName()).isEqualTo("C:\\archives"); - assertThat(archiveName.getName()).isEqualTo("C:\\archives\\anArchive.zip"); - assertThat(archiveName.localized()).isEqualTo("\\anArchive.zip"); - ArchiveEntryName archiveEntryName = new ArchiveEntryName(archiveName, entryName); - - assertThat(archiveEntryName.getRoot()).isEqualTo(archiveName.getName()+"#"); - assertThat(archiveEntryName.getDirectorySeparator()).isEqualTo("/"); - assertThat(archiveEntryName.getBaseName()).isEqualTo("C:\\archives\\anArchive.zip#"); - assertThat(archiveEntryName.getName()).isEqualTo("C:\\archives\\anArchive.zip#/anArchiveEntry.txt"); - assertThat(archiveEntryName.localized()).isEqualTo("/anArchiveEntry.txt"); - assertThat(archiveEntryName.localized("/")).isEqualTo("/anArchive.zip#/anArchiveEntry.txt"); + @ParameterizedTest(name = "{index} {0} {1}") + @MethodSource("archiveEntryTestData") + void archiveEntryNameTest(String os, String testName, DocumentName archiveName, String root, String separator, String baseName, + String localizedName) { + assertThat(archiveName.getRoot()).as("root").isEqualTo(root); + assertThat(archiveName.getDirectorySeparator()).as("separator").isEqualTo(separator); + assertThat(archiveName.getBaseName()).as("baseName").isEqualTo(baseName); + assertThat(archiveName.localized()).as("localized").isEqualTo(localizedName); + assertThat(archiveName.getName()).as("name").isEqualTo(baseName + localizedName); + if (!separator.equals(archiveName.fsInfo().dirSeparator())) + { + String newBaseName = separator.equals("/") ? baseName.replace('\\', '/') : baseName.replace('/', '\\'); + assertThat(archiveName.localized(separator)).as("localized(x)").isEqualTo(newBaseName + localizedName); + } + } - // test with directory - entryName = "./someDir/anArchiveEntry.txt"; - archiveEntryName = new ArchiveEntryName(archiveName, entryName); + static List archiveEntryTestData() { + List lst = new ArrayList<>(); - assertThat(archiveEntryName.getRoot()).isEqualTo(archiveName.getName()+"#"); - assertThat(archiveEntryName.getDirectorySeparator()).isEqualTo("/"); - assertThat(archiveEntryName.getBaseName()).isEqualTo("C:\\archives\\anArchive.zip#"); - assertThat(archiveEntryName.getName()).isEqualTo("C:\\archives\\anArchive.zip#/someDir/anArchiveEntry.txt"); - assertThat(archiveEntryName.localized()).isEqualTo("/someDir/anArchiveEntry.txt"); - assertThat(archiveEntryName.localized("/")).isEqualTo("/anArchive.zip#/someDir/anArchiveEntry.txt"); + for (FSInfo fsInfo : FSInfoTest.TEST_SUITE) { + String os = fsInfo.toString(); + String root = fsInfo.roots()[0]; + String baseName = String.format(String.format("%sarchives", root)); + String simpleName = String.format("%sanArchive.zip", fsInfo.dirSeparator()); + String entryName = "./anArchiveEntry.txt"; + DocumentName archiveName = DocumentName.builder(fsInfo).setName(baseName + simpleName).setBaseName(baseName).build(); + lst.add(Arguments.of(os, "archive name", archiveName, root, fsInfo.dirSeparator(), baseName, simpleName)); + + ArchiveEntryName archiveEntryName = new ArchiveEntryName(archiveName, entryName); + baseName = archiveName.getName() + "#"; + root = baseName + "/"; + lst.add(Arguments.of(os, "archive entry name", archiveEntryName, root, "/", baseName, "/anArchiveEntry.txt")); + + // test with directory + entryName = "./someDir/anArchiveEntry.txt"; + archiveEntryName = new ArchiveEntryName(archiveName, entryName); + + lst.add(Arguments.of(os, "archive entry with directory", archiveEntryName, root, "/", baseName, "/someDir/anArchiveEntry.txt")); + } + return lst; } } diff --git a/apache-rat-core/src/test/java/org/apache/rat/test/AbstractConfigurationOptionsProvider.java b/apache-rat-core/src/test/java/org/apache/rat/test/AbstractConfigurationOptionsProvider.java index 6c4aa2ab5..ef3bdc463 100644 --- a/apache-rat-core/src/test/java/org/apache/rat/test/AbstractConfigurationOptionsProvider.java +++ b/apache-rat-core/src/test/java/org/apache/rat/test/AbstractConfigurationOptionsProvider.java @@ -787,16 +787,23 @@ private void styleSheetTest(final Option option) { // run the test String[] args = {null}; try { - for (String sheet : new String[]{"plain-rat", "missing-headers", "unapproved-licenses", file.getAbsolutePath()}) { + for (String sheet : new String[]{"plain-rat", "missing-headers", "unapproved-licenses", "stylesheet-" + option.getLongOpt()}) { args[0] = sheet; ReportConfiguration config = generateConfig(ImmutablePair.of(option, args)); - try (InputStream expected = StyleSheets.getStyleSheet(sheet).get(); + DocumentName base = DocumentName.builder(baseDir).build(); + DocumentName expectedName = base.resolve(sheet); + try (InputStream expected = StyleSheets.getStyleSheet(sheet, base).ioSupplier().get(); InputStream actual = config.getStyleSheet().get()) { - assertThat(IOUtils.contentEquals(expected, actual)).as(() -> String.format("'%s' does not match", sheet)).isTrue(); + String expectedStr = IOUtils.toString(expected); + String actualStr = IOUtils.toString(actual); + assertThat(actualStr).isEqualTo(expectedStr).as(() -> String.format("'%s' does not match '%s': %s != %s", + config.getStyleSheetDescriptor().name(), + expectedName.getName(), + actualStr, expectedStr)); } } } catch (IOException e) { - fail(e.getMessage()); + fail(e.getMessage(), e); } } @@ -821,7 +828,7 @@ protected void scanHiddenDirectoriesTest() { protected void xmlTest() { try { ReportConfiguration config = generateConfig(ImmutablePair.of(Arg.OUTPUT_STYLE.find("xml"), null)); - try (InputStream expected = StyleSheets.getStyleSheet("xml").get(); + try (InputStream expected = StyleSheets.getStyleSheet("xml", null).ioSupplier().get(); InputStream actual = config.getStyleSheet().get()) { assertThat(IOUtils.contentEquals(expected, actual)).as("'xml' does not match").isTrue(); } diff --git a/apache-rat-core/src/test/java/org/apache/rat/test/AbstractOptionsProvider.java b/apache-rat-core/src/test/java/org/apache/rat/test/AbstractOptionsProvider.java index f4a188099..1f55d2ba0 100644 --- a/apache-rat-core/src/test/java/org/apache/rat/test/AbstractOptionsProvider.java +++ b/apache-rat-core/src/test/java/org/apache/rat/test/AbstractOptionsProvider.java @@ -170,7 +170,7 @@ public static String[] extractArgs(List> args) { } protected File writeFile(final String name, final Iterable lines) { - return org.apache.rat.testhelpers.FileUtils.writeFile(baseDir, name, lines); + return org.apache.rat.utils.FileUtils.writeFile(baseDir, name, lines); } final protected DocumentName mkDocName(final String name) { diff --git a/apache-rat-core/src/test/java/org/apache/rat/testhelpers/BaseOption.java b/apache-rat-core/src/test/java/org/apache/rat/testhelpers/BaseOption.java new file mode 100644 index 000000000..4a22d9d7e --- /dev/null +++ b/apache-rat-core/src/test/java/org/apache/rat/testhelpers/BaseOption.java @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * https://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + */ +package org.apache.rat.testhelpers; + +import org.apache.commons.cli.Option; +import org.apache.rat.ui.ArgumentTracker; +import org.apache.rat.ui.UIOption; +import org.apache.rat.ui.UIOptionCollection; +import org.apache.rat.utils.CasedString; + +public final class BaseOption extends UIOption { + BaseOption(final UIOptionCollection collection, Option option) { + super(collection, option, new CasedString(CasedString.StringCase.KEBAB, ArgumentTracker.extractKey(option))); + } + protected String cleanupName(Option option) { + return ArgumentTracker.extractKey(option); + } + + public String getExample() { + return ""; + } + + public String getText() { + return ""; + } +} diff --git a/apache-rat-core/src/test/java/org/apache/rat/testhelpers/BaseOptionCollection.java b/apache-rat-core/src/test/java/org/apache/rat/testhelpers/BaseOptionCollection.java new file mode 100644 index 000000000..18b507103 --- /dev/null +++ b/apache-rat-core/src/test/java/org/apache/rat/testhelpers/BaseOptionCollection.java @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * https://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + */ +package org.apache.rat.testhelpers; + +import org.apache.commons.cli.Option; +import org.apache.rat.ui.UIOptionCollection; + +public final class BaseOptionCollection extends UIOptionCollection { + private BaseOptionCollection(Builder builder) { + super(builder); + } + + public static Builder builder() { + return new Builder(); + } + + public static final class Builder extends UIOptionCollection.Builder { + private Builder() { + super(BaseOption::new); + } + + public BaseOptionCollection build() { + return new BaseOptionCollection(this); + } + } +} diff --git a/apache-rat-core/src/test/java/org/apache/rat/testhelpers/TestingLog.java b/apache-rat-core/src/test/java/org/apache/rat/testhelpers/TestingLog.java index 171cacacd..0298eb52c 100644 --- a/apache-rat-core/src/test/java/org/apache/rat/testhelpers/TestingLog.java +++ b/apache-rat-core/src/test/java/org/apache/rat/testhelpers/TestingLog.java @@ -26,6 +26,7 @@ public class TestingLog implements Log { private StringBuilder captured = new StringBuilder(); + private Log.Level level = Log.Level.INFO; /** * Clears the captured buffer @@ -86,12 +87,18 @@ public void assertNotContainsPattern(String pattern) { @Override public Level getLevel() { - return Level.DEBUG; + return level; + } + + @Override + public void setLevel(Level level) { + this.level = level; } @Override public void log(Level level, String msg) { - captured.append(String.format("%s: %s%n", level, msg)); + if (isEnabled(level)) + captured.append(String.format("%s: %s%n", level, msg)); } /** diff --git a/apache-rat-core/src/test/java/org/apache/rat/testhelpers/data/AbstractTestDataProvider.java b/apache-rat-core/src/test/java/org/apache/rat/testhelpers/data/AbstractTestDataProvider.java new file mode 100644 index 000000000..6bc5682f8 --- /dev/null +++ b/apache-rat-core/src/test/java/org/apache/rat/testhelpers/data/AbstractTestDataProvider.java @@ -0,0 +1,198 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + */ +package org.apache.rat.testhelpers.data; + + +import com.google.common.collect.ImmutableList; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; +import java.util.stream.Stream; +import org.apache.commons.cli.Option; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.apache.rat.OptionCollectionParser; +import org.apache.rat.commandline.Arg; +import org.apache.rat.ui.UIOptionCollection; +import org.apache.rat.ui.ArgumentTracker; +import org.apache.rat.utils.DefaultLog; + +/** + * Generates a list of TestData for executing the Report. + * Use of this interface ensures consistent testing across the UIs. Each method + * tests an Option from {@link OptionCollectionParser} that must be implemented in the UI. + */ +public abstract class AbstractTestDataProvider { + + /** The list of exclude args */ + static final String[] EXCLUDE_ARGS = {"*.foo", "%regex[[A-Z]\\.bar]", "justbaz"}; + /** the list of include args */ + static final String[] INCLUDE_ARGS = {"B.bar", "justbaz"}; + public final ImmutableList> NO_OPTIONS = ImmutableList.of(ImmutablePair.nullPair()); + + /** + * Generates a map of TestData indexed by the testName + * @param optionCollection the collection of options for the UI under test. + * @return the map of testName to Test Data. + */ + public final Map getOptionTestMap(final UIOptionCollection optionCollection) { + Map map = new TreeMap<>(); + for (TestData test : getOptionTests(optionCollection)) { + map.put(test.getTestName(), test); + } + return map; + } + + /** + * Generates a list of test data for Option testing. + * This is different from UI testing as this is to test + * that the command line is properly parsed into a configuration. + * @param optionCollection the collection of options for the UI under test. + * @return a set of TestData for the tests. + */ + public final Set getOptionTests(final UIOptionCollection optionCollection) { + // the optionCollection establishes any changes to the Arg values. + List