diff --git a/apache-rat-core/pom.xml b/apache-rat-core/pom.xml index 6958dec9e..1b31b093b 100644 --- a/apache-rat-core/pom.xml +++ b/apache-rat-core/pom.xml @@ -25,8 +25,7 @@ apache-rat-core jar Apache Creadur Rat::Core - The core functionality, shared by the Ant tasks - and the Maven plugin. + The core functionality of Rat that is used by all Clients. diff --git a/apache-rat-core/src/main/java/org/apache/rat/BuilderParams.java b/apache-rat-core/src/main/java/org/apache/rat/BuilderParams.java new file mode 100644 index 000000000..530d43cad --- /dev/null +++ b/apache-rat-core/src/main/java/org/apache/rat/BuilderParams.java @@ -0,0 +1,64 @@ +/* + * 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; + +import java.lang.reflect.Method; +import java.util.Map; +import java.util.SortedSet; + +import org.apache.rat.analysis.IHeaderMatcher; +import org.apache.rat.license.ILicenseFamily; + +/** + * Parameters that can be set by the BUILDER_PARAM ComponentType. The name of the method listed here + * should be the same as the name specified in the ConfigComponent. + */ +public interface BuilderParams { + + /** + * Gets one of the contained methods by name. + * + * @param name the name of the method to get. + * @return the Method. + */ + default Method get(String name) { + try { + return this.getClass().getMethod(name); + } catch (NoSuchMethodException e) { + throw new ImplementationException(String.format("method '%s' is not found in %s", name, this.getClass())); + } catch (IllegalArgumentException e) { + throw new ImplementationException( + String.format("method '%s' in %s can not be retrieved", name, this.getClass()), e); + } + } + + /** + * Gets a mapping of matcher names to matchers. + * + * @return the mapping of matcher names to matchers. + */ + Map matcherMap(); + + /** + * Gets a sorted set of registered license families. + * + * @return the sorted set of license families. + */ + SortedSet licenseFamilies(); +} diff --git a/apache-rat-core/src/main/java/org/apache/rat/Defaults.java b/apache-rat-core/src/main/java/org/apache/rat/Defaults.java index c5aff0748..cb747f03f 100644 --- a/apache-rat-core/src/main/java/org/apache/rat/Defaults.java +++ b/apache-rat-core/src/main/java/org/apache/rat/Defaults.java @@ -36,6 +36,7 @@ import org.apache.rat.license.ILicenseFamily; import org.apache.rat.license.LicenseSetFactory; import org.apache.rat.license.LicenseSetFactory.LicenseFilter; +import org.apache.rat.utils.Log; /** * A class that holds the list of licenses and approved licenses from one or more configuration files. @@ -70,8 +71,8 @@ public static void init() { /** * Builder constructs instances. */ - private Defaults(Set urls) { - this.setFactory = Defaults.readConfigFiles(urls); + private Defaults(Log log, Set urls) { + this.setFactory = Defaults.readConfigFiles(log, urls); } /** @@ -86,7 +87,7 @@ public static Builder builder() { * Reads the configuration files. * @param urls the URLs to read. */ - private static LicenseSetFactory readConfigFiles(Collection urls) { + private static LicenseSetFactory readConfigFiles(Log log, Collection urls) { SortedSet licenses = LicenseSetFactory.emptyLicenseSet(); @@ -102,6 +103,7 @@ private static LicenseSetFactory readConfigFiles(Collection urls) { LicenseReader lReader = fmt.licenseReader(); if (lReader != null) { + lReader.setLog(log); lReader.addLicenses(url); licenses.addAll(lReader.readLicenses()); lReader.approvedLicenseId().stream().map(ILicenseFamily::makeCategory).forEach(approvedLicenseIds::add); @@ -241,10 +243,11 @@ public Builder noDefault() { /** * Builds the defaults object. + * @param log the Log to use to report errors when building the defaults. * @return the current defaults object. */ - public Defaults build() { - return new Defaults(fileNames); + public Defaults build(Log log) { + return new Defaults(log, fileNames); } } } diff --git a/apache-rat-core/src/main/java/org/apache/rat/ImplementationException.java b/apache-rat-core/src/main/java/org/apache/rat/ImplementationException.java new file mode 100644 index 000000000..ed8deaf69 --- /dev/null +++ b/apache-rat-core/src/main/java/org/apache/rat/ImplementationException.java @@ -0,0 +1,47 @@ +/* + * 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; + +/** + * An exception thrown when there is an issue with the Configuration. + */ +public class ImplementationException extends RuntimeException { + + private static final long serialVersionUID = 7257245932787579431L; + + public static ImplementationException makeInstance(Exception e) { + if (e instanceof ImplementationException) { + return (ImplementationException) e; + } + return new ImplementationException(e); + } + + public ImplementationException(String message, Throwable cause) { + super(message, cause); + } + + public ImplementationException(String message) { + super(message); + } + + public ImplementationException(Throwable cause) { + super(cause); + } + +} 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 624c06dfe..c69b3f2f1 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 @@ -31,6 +31,7 @@ import java.util.Arrays; import java.util.Comparator; import java.util.List; +import java.util.function.Consumer; import java.util.regex.PatternSyntaxException; import java.util.stream.Collectors; @@ -115,7 +116,7 @@ public class Report { private static final String LIST_FAMILIES = "list-families"; private static final String LOG_LEVEL = "log-level"; - + private static final String DRY_RUN = "dry-run"; /** * Set unstyled XML output */ @@ -130,6 +131,33 @@ public class Report { * @throws Exception on error. */ public static void main(String[] args) throws Exception { + ReportConfiguration configuration = parseCommands(args, Report::printUsage); + if (configuration != null) { + configuration.validate(DefaultLog.INSTANCE::error); + new Reporter(configuration).output(); + } + } + + /** + * Parses the standard options to create a ReportConfiguraton. + * @param args the arguments to parse + * @param helpCmd the help command to run when necessary. + * @return a ReportConfiguration or null if Help was printed. + * @throws IOException on error. + */ + public static ReportConfiguration parseCommands(String[] args, Consumer helpCmd) throws IOException { + return parseCommands(args, helpCmd, false); + } + + /** + * Parses the standard options to create a ReportConfiguraton. + * @param args the arguments to parse + * @param helpCmd the help command to run when necessary. + * @param noArgs If true then the commands do not need extra arguments + * @return a ReportConfiguration or null if Help was printed. + * @throws IOException on error. + */ + public static ReportConfiguration parseCommands(String[] args, Consumer helpCmd, boolean noArgs) throws IOException { Options opts = buildOptions(); CommandLine cl; try { @@ -138,7 +166,7 @@ public static void main(String[] args) throws Exception { DefaultLog.INSTANCE.error(e.getMessage()); DefaultLog.INSTANCE.error("Please use the \"--help\" option to see a list of valid commands and options"); System.exit(1); - return; // dummy return (won't be reached) to avoid Eclipse complaint about possible NPE + return null; // dummy return (won't be reached) to avoid Eclipse complaint about possible NPE // for "cl" } @@ -152,14 +180,20 @@ public static void main(String[] args) throws Exception { } } if (cl.hasOption(HELP)) { - printUsage(opts); + helpCmd.accept(opts); + return null; } - args = cl.getArgs(); - if (args == null || args.length != 1) { - printUsage(opts); + if (!noArgs) { + args = cl.getArgs(); + if (args == null || args.length != 1) { + helpCmd.accept(opts); + return null; + } } else { - ReportConfiguration configuration = createConfiguration(args[0], cl); + args = new String[] {null}; + } +/* ReportConfiguration configuration = createConfiguration(args[0], cl); configuration.validate(DefaultLog.INSTANCE::error); boolean dryRun = false; @@ -180,14 +214,25 @@ public static void main(String[] args) throws Exception { } if (!dryRun) { - Reporter.report(configuration); + new Reporter(configuration).output(); } } +*/ ReportConfiguration configuration = createConfiguration(args[0], cl); + return configuration; } static ReportConfiguration createConfiguration(String baseDirectory, CommandLine cl) throws IOException { final ReportConfiguration configuration = new ReportConfiguration(DefaultLog.INSTANCE); + configuration.setDryRun(cl.hasOption(DRY_RUN)); + if (cl.hasOption(LIST_FAMILIES)) { + configuration.listFamilies( LicenseFilter.valueOf(cl.getOptionValue(LIST_FAMILIES).toLowerCase())); + } + + if (cl.hasOption(LIST_LICENSES)) { + configuration.listFamilies( LicenseFilter.valueOf(cl.getOptionValue(LIST_LICENSES).toLowerCase())); + } + if (cl.hasOption('o')) { configuration.setOut(new File(cl.getOptionValue('o'))); } @@ -247,9 +292,11 @@ static ReportConfiguration createConfiguration(String baseDirectory, CommandLine defaultBuilder.add(fn); } } - Defaults defaults = defaultBuilder.build(); + Defaults defaults = defaultBuilder.build(DefaultLog.INSTANCE); configuration.setFrom(defaults); - configuration.setReportable(getDirectory(baseDirectory, configuration)); + if (baseDirectory != null) { + configuration.setReportable(getDirectory(baseDirectory, configuration)); + } return configuration; } @@ -288,7 +335,9 @@ static Options buildOptions() { String licFilterValues = Arrays.stream(LicenseFilter.values()).map(LicenseFilter::name).collect(Collectors.joining(", ")); Options opts = new Options() - + .addOption(Option.builder().longOpt(DRY_RUN) + .desc("If set do not update the files but generate the reports.") + .build()) .addOption( Option.builder().hasArg(true).longOpt(LIST_FAMILIES) .desc("List the defined license families (default is none). Valid options are: "+licFilterValues+".") @@ -299,7 +348,6 @@ static Options buildOptions() { .build()) .addOption(new Option(HELP, "help", false, "Print help for the RAT command line interface and exit.")); - Option out = new Option("o", "out", true, "Define the output file where to write a report to (default is System.out)."); @@ -368,13 +416,13 @@ static Options buildOptions() { private static void printUsage(Options opts) { HelpFormatter f = new HelpFormatter(); f.setOptionComparator(new OptionComparator()); - String header = "\nAvailable options"; + String header = System.lineSeparator()+"Available options"; - String footer = "\nNOTE:\n" + "Rat is really little more than a grep ATM\n" - + "Rat is also rather memory hungry ATM\n" + "Rat is very basic ATM\n" - + "Rat highlights possible issues\n" + "Rat reports require interpretation\n" - + "Rat often requires some tuning before it runs well against a project\n" - + "Rat relies on heuristics: it may miss issues\n"; + String footer = String.format("%nNOTE:%nRat is really little more than a grep ATM%n" + + "Rat is also rather memory hungry ATM%n" + "Rat is very basic ATM%n" + + "Rat highlights possible issues%n" + "Rat reports require interpretation%n" + + "Rat often requires some tuning before it runs well against a project%n" + + "Rat relies on heuristics: it may miss issues%n"); f.printHelp("java -jar apache-rat/target/apache-rat-CURRENT-VERSION.jar [options] [DIR|TARBALL]", header, opts, footer, false); @@ -420,7 +468,7 @@ private static IReportable getDirectory(String baseDirectory, ReportConfiguratio /** * This class implements the {@code Comparator} interface for comparing Options. */ - private static class OptionComparator implements Comparator diff --git a/apache-rat-plugin/src/it/it4_RAT-168/verify.groovy b/apache-rat-plugin/src/it/it4_RAT-168/verify.groovy index 3018f96a3..c299a60e1 100644 --- a/apache-rat-plugin/src/it/it4_RAT-168/verify.groovy +++ b/apache-rat-plugin/src/it/it4_RAT-168/verify.groovy @@ -25,4 +25,6 @@ assert ! content.contains( '[WARNING]' ) report = new File( basedir, 'target/site/rat-report.html' ).text -assert report.contains( 'AL pom.xml' ) +assert report.contains( ' S pom.xml' ) +assert report.contains( ' AL AL Apache License Version 2.0' ) + diff --git a/apache-rat-plugin/src/main/java/org/apache/rat/mp/AbstractRatMojo.java b/apache-rat-plugin/src/main/java/org/apache/rat/mp/AbstractRatMojo.java index 9147a51d1..1a39c0c62 100644 --- a/apache-rat-plugin/src/main/java/org/apache/rat/mp/AbstractRatMojo.java +++ b/apache-rat-plugin/src/main/java/org/apache/rat/mp/AbstractRatMojo.java @@ -332,6 +332,8 @@ public void log(Level level, String msg) { case ERROR: log.error(msg); break; + case OFF: + break; } }}; } @@ -340,7 +342,7 @@ protected ReportConfiguration getConfiguration() throws MojoExecutionException { ReportConfiguration config = new ReportConfiguration(makeLog()); reportDeprecatedProcessing(); if (addDefaultLicenses) { - config.setFrom(getDefaultsBuilder().build()); + config.setFrom(getDefaultsBuilder().build(config.getLog())); } else { config.setStyleSheet(Defaults.getPlainStyleSheet()); } @@ -400,9 +402,9 @@ protected ReportConfiguration getConfiguration() throws MojoExecutionException { }; Consumer process = logger.andThen(config::addLicense).andThen(addApproved); - SortedSet families = config.getLicenseFamilies(LicenseFilter.all); + SortedSet families = config.getLicenseFamilies(LicenseFilter.ALL); getDeprecatedConfigs().map(DeprecatedConfig::getLicense).filter(Objects::nonNull) - .map(x -> x.build(families)).forEach(process); + .map(x -> x.setLicenseFamilies(families).build()).forEach(process); getLicenses().map(x -> x.build(families)).forEach(process); } diff --git a/apache-rat-plugin/src/main/java/org/apache/rat/mp/All.java b/apache-rat-plugin/src/main/java/org/apache/rat/mp/All.java index 72700ac12..191bfeb34 100644 --- a/apache-rat-plugin/src/main/java/org/apache/rat/mp/All.java +++ b/apache-rat-plugin/src/main/java/org/apache/rat/mp/All.java @@ -31,7 +31,7 @@ public All() { @Override protected void setMatcher(IHeaderMatcher.Builder builder) { - this.builder.add(builder); + this.builder.addEnclosed(builder); } @Override diff --git a/apache-rat-plugin/src/main/java/org/apache/rat/mp/Any.java b/apache-rat-plugin/src/main/java/org/apache/rat/mp/Any.java index 77cda43e1..f5750d978 100644 --- a/apache-rat-plugin/src/main/java/org/apache/rat/mp/Any.java +++ b/apache-rat-plugin/src/main/java/org/apache/rat/mp/Any.java @@ -31,7 +31,7 @@ public Any() { @Override protected void setMatcher(IHeaderMatcher.Builder builder) { - this.builder.add(builder); + this.builder.addEnclosed(builder); } @Override diff --git a/apache-rat-plugin/src/main/java/org/apache/rat/mp/Copyright.java b/apache-rat-plugin/src/main/java/org/apache/rat/mp/Copyright.java index 034a105e5..f64b2afd7 100644 --- a/apache-rat-plugin/src/main/java/org/apache/rat/mp/Copyright.java +++ b/apache-rat-plugin/src/main/java/org/apache/rat/mp/Copyright.java @@ -19,7 +19,7 @@ package org.apache.rat.mp; import org.apache.maven.plugins.annotations.Parameter; -import org.apache.rat.analysis.IHeaderMatcher; +import org.apache.rat.analysis.matchers.CopyrightMatcher; import org.apache.rat.configuration.builders.CopyrightBuilder; public class Copyright extends CopyrightBuilder { @@ -28,7 +28,7 @@ public class Copyright extends CopyrightBuilder { private String start; @Parameter(required = false) - private String stop; + private String end; @Parameter(required = false) private String owner; @@ -37,8 +37,8 @@ public Copyright() { } @Override - public IHeaderMatcher build() { - setStart(start).setEnd(stop).setOwner(owner); + public CopyrightMatcher build() { + setStart(start).setEnd(end).setOwner(owner); return super.build(); } } diff --git a/apache-rat-plugin/src/main/java/org/apache/rat/mp/FilesReportable.java b/apache-rat-plugin/src/main/java/org/apache/rat/mp/FilesReportable.java index 82c2c8cbe..f8433d6d2 100644 --- a/apache-rat-plugin/src/main/java/org/apache/rat/mp/FilesReportable.java +++ b/apache-rat-plugin/src/main/java/org/apache/rat/mp/FilesReportable.java @@ -1,12 +1,3 @@ -package org.apache.rat.mp; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.Reader; -import java.nio.file.Files; - /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file @@ -25,11 +16,13 @@ * specific language governing permissions and limitations * under the License. */ +package org.apache.rat.mp; + +import java.io.File; +import java.io.IOException; -import org.apache.rat.api.Document; -import org.apache.rat.api.MetaData; import org.apache.rat.api.RatException; -import org.apache.rat.document.impl.DocumentImplUtils; +import org.apache.rat.document.impl.FileDocument; import org.apache.rat.report.IReportable; import org.apache.rat.report.RatReport; @@ -54,51 +47,8 @@ class FilesReportable implements IReportable { @Override public void run(RatReport report) throws RatException { - FileDocument document = new FileDocument(); for (String file : files) { - document.setFile(new File(basedir, file)); - document.getMetaData().clear(); - report.report(document); - } - } - - private static class FileDocument implements Document { - private File file; - private final MetaData metaData = new MetaData(); - - void setFile(File file) { - this.file = file; - } - - @Override - public boolean isComposite() { - return DocumentImplUtils.isZip(file); - } - - @Override - public Reader reader() throws IOException { - final InputStream in = Files.newInputStream(file.toPath()); - return new InputStreamReader(in); - } - - @Override - public String getName() { - return DocumentImplUtils.toName(file); - } - - @Override - public MetaData getMetaData() { - return metaData; - } - - @Override - public InputStream inputStream() throws IOException { - return Files.newInputStream(file.toPath()); - } - - @Override - public String toString() { - return "File:" + file.getAbsolutePath(); + report.report(new FileDocument(new File(basedir, file))); } } } diff --git a/apache-rat-plugin/src/main/java/org/apache/rat/mp/License.java b/apache-rat-plugin/src/main/java/org/apache/rat/mp/License.java index ac17c2b09..195f4bf9c 100644 --- a/apache-rat-plugin/src/main/java/org/apache/rat/mp/License.java +++ b/apache-rat-plugin/src/main/java/org/apache/rat/mp/License.java @@ -32,9 +32,6 @@ public class License extends EnclosingMatcher { @Parameter(required = false) private String notes; - @Parameter(required = false) - private String derivedFrom; - @Parameter(required = true) private String id; @@ -53,8 +50,10 @@ protected void setMatcher(IHeaderMatcher.Builder builder) { } public ILicense build(SortedSet context) { - return builder.setDerivedFrom(derivedFrom).setLicenseFamilyCategory(family).setId(id) - .setName(name).setNotes(notes).build(context); + + return builder.setLicenseFamilies(context) + .setFamily(family).setId(id) + .setName(name).setNote(notes).build(); } @Override diff --git a/apache-rat-plugin/src/main/java/org/apache/rat/mp/Not.java b/apache-rat-plugin/src/main/java/org/apache/rat/mp/Not.java index 2ba51c8d8..9ffad1687 100644 --- a/apache-rat-plugin/src/main/java/org/apache/rat/mp/Not.java +++ b/apache-rat-plugin/src/main/java/org/apache/rat/mp/Not.java @@ -31,7 +31,7 @@ public Not() { @Override protected void setMatcher(IHeaderMatcher.Builder builder) { - this.builder.add(builder); + this.builder.addEnclosed(builder); } @Override diff --git a/apache-rat-plugin/src/main/java/org/apache/rat/mp/RatCheckMojo.java b/apache-rat-plugin/src/main/java/org/apache/rat/mp/RatCheckMojo.java index e2b9cb610..bb842edb7 100644 --- a/apache-rat-plugin/src/main/java/org/apache/rat/mp/RatCheckMojo.java +++ b/apache-rat-plugin/src/main/java/org/apache/rat/mp/RatCheckMojo.java @@ -98,6 +98,8 @@ public class RatCheckMojo extends AbstractRatMojo { @Parameter(property = "rat.consoleOutput", defaultValue = "true") private boolean consoleOutput; + private Reporter reporter; + /** * Invoked by Maven to execute the Mojo. * @@ -113,41 +115,42 @@ public void execute() throws MojoExecutionException, MojoFailureException { return; } ReportConfiguration config = getConfiguration(); - logLicenses(config.getLicenses(LicenseFilter.all)); + logLicenses(config.getLicenses(LicenseFilter.ALL)); final File parent = reportFile.getParentFile(); if (!parent.mkdirs() && !parent.isDirectory()) { throw new MojoExecutionException("Could not create report parent directory " + parent); } try { - final ClaimStatistic report = Reporter.report(config); - check(report, config); - } catch (MojoExecutionException | MojoFailureException e) { + this.reporter = new Reporter(config); + reporter.output(); + check(); + } catch (MojoFailureException e) { throw e; } catch (Exception e) { throw new MojoExecutionException(e.getMessage(), e); } } - protected void check(ClaimStatistic statistics, ReportConfiguration config) throws MojoFailureException { + protected void check() throws MojoFailureException { if (numUnapprovedLicenses > 0) { getLog().info("You requested to accept " + numUnapprovedLicenses + " files with unapproved licenses."); } + ClaimStatistic stats = reporter.getClaimsStatistic(); - int numApproved = statistics.getNumApproved(); - getLog().info("Rat check: Summary over all files. Unapproved: " + statistics.getNumUnApproved() + // - ", unknown: " + statistics.getNumUnknown() + // - ", generated: " + statistics.getNumGenerated() + // - ", approved: " + numApproved + // - (numApproved > 0 ? " licenses." : " license.")); + int numApproved = stats.getCounter(ClaimStatistic.Counter.APPROVED); + StringBuilder statSummary = new StringBuilder("Rat check: Summary over all files. Unapproved: ") + .append(stats.getCounter(ClaimStatistic.Counter.UNAPPROVED)).append(", unknown: ") + .append(stats.getCounter(ClaimStatistic.Counter.UNKNOWN)).append(", generated: ") + .append(stats.getCounter(ClaimStatistic.Counter.GENERATED)).append(", approved: ").append(numApproved) + .append((numApproved > 0 ? " licenses." : " license.")); - if (numUnapprovedLicenses < statistics.getNumUnApproved()) { + getLog().info(statSummary.toString()); + if (numUnapprovedLicenses < stats.getCounter(ClaimStatistic.Counter.UNAPPROVED)) { if (consoleOutput) { try { - config.setStyleSheet(Defaults.getUnapprovedLicensesStyleSheet()); ByteArrayOutputStream baos = new ByteArrayOutputStream(); - config.setOut(()->baos); - Reporter.report(config); + reporter.output(Defaults.getUnapprovedLicensesStyleSheet(), () -> baos); getLog().warn(baos.toString()); } catch (Exception e) { getLog().warn("Unable to print the files with unapproved licenses to the console."); @@ -156,11 +159,11 @@ protected void check(ClaimStatistic statistics, ReportConfiguration config) thro final String seeReport = " See RAT report in: " + reportFile; if (!ignoreErrors) { - throw new RatCheckException( - "Too many files with unapproved license: " + statistics.getNumUnApproved() + seeReport); + throw new RatCheckException("Too many files with unapproved license: " + + stats.getCounter(ClaimStatistic.Counter.UNAPPROVED) + seeReport); } - getLog().warn( - "Rat check: " + statistics.getNumUnApproved() + " files with unapproved licenses." + seeReport); + getLog().warn("Rat check: " + stats.getCounter(ClaimStatistic.Counter.UNAPPROVED) + + " files with unapproved licenses." + seeReport); } } diff --git a/apache-rat-plugin/src/main/java/org/apache/rat/mp/RatReportMojo.java b/apache-rat-plugin/src/main/java/org/apache/rat/mp/RatReportMojo.java index 7ea35af56..c711b59f0 100644 --- a/apache-rat-plugin/src/main/java/org/apache/rat/mp/RatReportMojo.java +++ b/apache-rat-plugin/src/main/java/org/apache/rat/mp/RatReportMojo.java @@ -195,8 +195,7 @@ private SiteRenderingContext createSiteRenderingContext(Locale locale) throws Ma Artifact skinArtifact = siteTool.getSkinArtifactFromRepository(session.getLocalRepository(), remoteRepositories, decorationModel); - getLog().debug( - buffer().a("Rendering content with ").strong(skinArtifact.getId() + " skin").a('.').build()); + getLog().debug(buffer().a("Rendering content with ").strong(skinArtifact.getId() + " skin").a('.').build()); context = siteRenderer.createContextForSkin(skinArtifact, templateProperties, decorationModel, project.getName(), locale); @@ -405,12 +404,11 @@ protected void executeReport(Locale locale) throws MavenReportException { sink.verbatim(SinkEventAttributeSet.BOXED); try { ReportConfiguration config = getConfiguration(); - config.setFrom(getDefaultsBuilder().build()); - //config.setStyleSheet(Defaults.getUnapprovedLicensesStyleSheet()); - logLicenses(config.getLicenses(LicenseFilter.all)); + config.setFrom(getDefaultsBuilder().build(config.getLog())); + logLicenses(config.getLicenses(LicenseFilter.ALL)); ByteArrayOutputStream baos = new ByteArrayOutputStream(); - config.setOut(()->baos); - Reporter.report(config); + config.setOut(() -> baos); + new Reporter(config).output(); sink.text(baos.toString()); } catch (Exception e) { throw new MavenReportException(e.getMessage(), e); diff --git a/apache-rat-plugin/src/main/java/org/apache/rat/mp/Text.java b/apache-rat-plugin/src/main/java/org/apache/rat/mp/Text.java index 1ecf0718a..9fe156e1a 100644 --- a/apache-rat-plugin/src/main/java/org/apache/rat/mp/Text.java +++ b/apache-rat-plugin/src/main/java/org/apache/rat/mp/Text.java @@ -22,6 +22,6 @@ public class Text extends TextBuilder { public void set(String text) { - super.setText(text); + super.setSimpleText(text); } } diff --git a/apache-rat-plugin/src/test/java/org/apache/rat/mp/BetterAbstractMojoTestCase.java b/apache-rat-plugin/src/test/java/org/apache/rat/mp/BetterAbstractMojoTestCase.java index 254da237e..f8ba07cda 100644 --- a/apache-rat-plugin/src/test/java/org/apache/rat/mp/BetterAbstractMojoTestCase.java +++ b/apache-rat-plugin/src/test/java/org/apache/rat/mp/BetterAbstractMojoTestCase.java @@ -42,20 +42,23 @@ import org.eclipse.aether.internal.impl.SimpleLocalRepositoryManagerFactory; import org.eclipse.aether.repository.LocalRepository; -/** Use this as you would {@link AbstractMojoTestCase}, - * where you want more of the standard maven defaults to be set - * (and where the {@link AbstractMojoTestCase} leaves them as null or empty). - * This includes: - *
  • local repo, repo sessions and managers configured - *
  • maven default remote repos installed (NB: this does not use your ~/.m2 local settings) - *
  • system properties are copies +/** + * Use this as you would {@link AbstractMojoTestCase}, where you want more of + * the standard maven defaults to be set (and where the + * {@link AbstractMojoTestCase} leaves them as null or empty). This includes: + *
  • local repo, repo sessions and managers configured + *
  • maven default remote repos installed (NB: this does not use your ~/.m2 + * local settings) + *
  • system properties are copies *

    - * No changes to subclass code is needed; this simply intercepts the {@link #newMavenSession(MavenProject)} method - * used by the various {@link #lookupMojo(String, File)} methods. + * No changes to subclass code is needed; this simply intercepts the + * {@link #newMavenSession(MavenProject)} method used by the various + * {@link #lookupMojo(String, File)} methods. *

    - * This also provides new methods, {@link #newMavenSession()} to conveniently create a maven session, - * and {@link #lookupConfiguredMojo(File, String)} so you don't have to always build the project yourself. - */ + * This also provides new methods, {@link #newMavenSession()} to conveniently + * create a maven session, and {@link #lookupConfiguredMojo(File, String)} so + * you don't have to always build the project yourself. + */ public abstract class BetterAbstractMojoTestCase extends AbstractMojoTestCase { protected MavenSession newMavenSession() { @@ -65,47 +68,53 @@ protected MavenSession newMavenSession() { // populate sensible defaults, including repository basedir and remote repos MavenExecutionRequestPopulator populator; - populator = getContainer().lookup( MavenExecutionRequestPopulator.class ); - populator.populateDefaults( request ); + populator = getContainer().lookup(MavenExecutionRequestPopulator.class); + populator.populateDefaults(request); + + // this is needed to allow java profiles to get resolved; i.e. avoid during + // project builds: + // [ERROR] Failed to determine Java version for profile java-1.5-detected @ + // org.apache.commons:commons-parent:22, + // /Users/alex/.m2/repository/org/apache/commons/commons-parent/22/commons-parent-22.pom, + // line 909, column 14 + request.setSystemProperties(System.getProperties()); - // this is needed to allow java profiles to get resolved; i.e. avoid during project builds: - // [ERROR] Failed to determine Java version for profile java-1.5-detected @ org.apache.commons:commons-parent:22, /Users/alex/.m2/repository/org/apache/commons/commons-parent/22/commons-parent-22.pom, line 909, column 14 - request.setSystemProperties( System.getProperties() ); - - // and this is needed so that the repo session in the maven session + // and this is needed so that the repo session in the maven session // has a repo manager, and it points at the local repo // (cf MavenRepositorySystemUtils.newSession() which is what is otherwise done) - DefaultMaven maven = (DefaultMaven) getContainer().lookup( Maven.class ); - DefaultRepositorySystemSession repoSession = - (DefaultRepositorySystemSession) maven.newRepositorySession( request ); - repoSession.setLocalRepositoryManager( - new SimpleLocalRepositoryManagerFactory().newInstance(repoSession, - new LocalRepository( request.getLocalRepository().getBasedir() ) )); + DefaultMaven maven = (DefaultMaven) getContainer().lookup(Maven.class); + DefaultRepositorySystemSession repoSession = (DefaultRepositorySystemSession) maven + .newRepositorySession(request); + repoSession.setLocalRepositoryManager(new SimpleLocalRepositoryManagerFactory().newInstance(repoSession, + new LocalRepository(request.getLocalRepository().getBasedir()))); - MavenSession session = new MavenSession( getContainer(), - repoSession, - request, result ); + MavenSession session = new MavenSession(getContainer(), repoSession, request, result); return session; } catch (Exception e) { throw new RuntimeException(e); } } - - /** Extends the super to use the new {@link #newMavenSession()} introduced here - * which sets the defaults one expects from maven; the standard test case leaves a lot of things blank */ + + /** + * Extends the super to use the new {@link #newMavenSession()} introduced here + * which sets the defaults one expects from maven; the standard test case leaves + * a lot of things blank + */ @Override protected MavenSession newMavenSession(MavenProject project) { MavenSession session = newMavenSession(); - session.setCurrentProject( project ); - session.setProjects( Arrays.asList( project ) ); - return session; + session.setCurrentProject(project); + session.setProjects(Arrays.asList(project)); + return session; } - /** As {@link #lookupConfiguredMojo(MavenProject, String)} but taking the pom file - * and creating the {@link MavenProject}. */ + /** + * As {@link #lookupConfiguredMojo(MavenProject, String)} but taking the pom + * file and creating the {@link MavenProject}. + */ protected Mojo lookupConfiguredMojo(File pom, String goal) throws Exception { - assertNotNull( pom ); - assertTrue( pom.exists() ); + assertNotNull(pom); + assertTrue(pom.exists()); ProjectBuildingRequest buildingRequest = newMavenSession().getProjectBuildingRequest(); ProjectBuilder projectBuilder = lookup(ProjectBuilder.class); @@ -114,5 +123,4 @@ protected Mojo lookupConfiguredMojo(File pom, String goal) throws Exception { return lookupConfiguredMojo(project, goal); } - } diff --git a/apache-rat-plugin/src/test/java/org/apache/rat/mp/RatCheckMojoTest.java b/apache-rat-plugin/src/test/java/org/apache/rat/mp/RatCheckMojoTest.java index 437735fd9..2cdc942dc 100644 --- a/apache-rat-plugin/src/test/java/org/apache/rat/mp/RatCheckMojoTest.java +++ b/apache-rat-plugin/src/test/java/org/apache/rat/mp/RatCheckMojoTest.java @@ -31,6 +31,7 @@ import org.apache.commons.io.FileUtils; import org.apache.rat.ReportConfiguration; import org.apache.rat.ReportConfigurationTest; +import org.apache.rat.api.Document; import org.apache.rat.license.ILicenseFamily; import org.apache.rat.license.LicenseFamilySetFactory; import org.apache.rat.license.LicenseSetFactory; @@ -110,7 +111,10 @@ public void testIt1() throws Exception { final RatCheckMojo mojo = newRatCheckMojo("it1"); final File ratTxtFile = getRatTxtFile(mojo); - final String[] expected = { " AL +\\Q" + getDir(mojo) + "pom.xml\\E", "Notes: 0", "Binaries: 0", "Archives: 0", + final String[] expected = { + RatTestHelpers.documentOut(true, Document.Type.STANDARD, getDir(mojo) + "pom.xml") + + RatTestHelpers.APACHE_LICENSE, + "Notes: 0", "Binaries: 0", "Archives: 0", "Standards: 1$", "Apache Licensed: 1$", "Generated Documents: 0", "^0 Unknown Licenses" }; ReportConfiguration config = mojo.getConfiguration(); @@ -130,10 +134,16 @@ public void testIt2() throws Exception { final RatCheckMojo mojo = newRatCheckMojo("it2"); final File ratTxtFile = getRatTxtFile(mojo); final String dir = getDir(mojo); - final String[] expected = { "^Files with unapproved licenses:\\s+\\Q" + dir + "src.txt\\E\\s+", "Notes: 0", + final String[] expected = { + "^Files with unapproved licenses:\\s+\\Q" + dir + "src.txt\\E\\s+", + "Notes: 0", "Binaries: 0", "Archives: 0", "Standards: 2$", "Apache Licensed: 1$", "Generated Documents: 0", - "^1 Unknown Licenses", " AL +\\Q" + dir + "pom.xml\\E$", "\\Q!????? " + dir + "src.txt\\E$", - "^== File: \\Q" + dir + "src.txt\\E$" }; + "^1 Unknown Licenses", + RatTestHelpers.documentOut(false, Document.Type.STANDARD, dir + "src.txt") + + RatTestHelpers.UNKNOWN_LICENSE, + RatTestHelpers.documentOut(true, Document.Type.STANDARD, dir + "pom.xml") + + RatTestHelpers.APACHE_LICENSE + }; try { mojo.execute(); fail("Expected RatCheckException"); @@ -154,8 +164,12 @@ public void testIt3() throws Exception { final String dir = getDir(mojo); final String[] expected = { "^Files with unapproved licenses:\\s+\\Q" + dir + "src.apt\\E\\s+", "Notes: 0", "Binaries: 0", "Archives: 0", "Standards: 2$", "Apache Licensed: 1$", "Generated Documents: 0", - "^1 Unknown Licenses", " AL +\\Q" + dir + "pom.xml\\E$", "\\Q!????? " + dir + "src.apt\\E$", - "^== File: \\Q" + dir + "src.apt\\E$" }; + "^1 Unknown Licenses", + RatTestHelpers.documentOut(false, Document.Type.STANDARD, dir + "src.apt") + + RatTestHelpers.UNKNOWN_LICENSE, + RatTestHelpers.documentOut(true, Document.Type.STANDARD, dir + "pom.xml") + + RatTestHelpers.APACHE_LICENSE + }; ReportConfiguration config = mojo.getConfiguration(); assertTrue("should be adding licenses", config.isAddingLicenses()); @@ -182,10 +196,10 @@ public void testIt5() throws Exception { ReportConfigurationTest.validateDefaultApprovedLicenses(config, 1); assertTrue(config.getApprovedLicenseCategories().contains(ILicenseFamily.makeCategory("YAL"))); ReportConfigurationTest.validateDefaultLicenseFamilies(config, "YAL"); - assertNotNull(LicenseFamilySetFactory.search("YAL", config.getLicenseFamilies(LicenseFilter.all))); + assertNotNull(LicenseFamilySetFactory.search("YAL", config.getLicenseFamilies(LicenseFilter.ALL))); ReportConfigurationTest.validateDefaultLicenses(config, "MyLicense", "CpyrT", "RegxT", "SpdxT", "TextT", "Not", "All", "Any"); - assertNotNull(LicenseSetFactory.search("MyLicense", config.getLicenses(LicenseFilter.all))); + assertNotNull(LicenseSetFactory.search("MyLicense", config.getLicenses(LicenseFilter.ALL))); assertNull("Should not have inputFileFilter", config.getInputFileFilter()); mojo.execute(); @@ -200,9 +214,14 @@ public void testIt5() throws Exception { public void testRAT_343() throws Exception { final RatCheckMojo mojo = newRatCheckMojo("RAT-343"); final File ratTxtFile = getRatTxtFile(mojo); - // POM reports as BSD because it has the BSD string in and that gets found before AL match - final String[] expected = { " BSD +\\Q" + getDir(mojo) + "pom.xml\\E", "Notes: 0", "Binaries: 0", "Archives: 0", - "Standards: 1$", "Apache Licensed: 0$", "Generated Documents: 0", "^0 Unknown Licenses" }; + // POM reports AL, BSD and CC BYas BSD because it contains the BSD and CC BY strings + final String[] expected = { + RatTestHelpers.documentOut(false, Document.Type.STANDARD, getDir(mojo) + "pom.xml") + + RatTestHelpers.APACHE_LICENSE + + RatTestHelpers.licenseOut("BSD", "BSD") + + RatTestHelpers.licenseOut("CC BY", "Creative Commons Attribution (Unapproved)"), + "Notes: 0", "Binaries: 0", "Archives: 0", + "Standards: 1$", "Apache Licensed: 1$", "Generated Documents: 0", "^0 Unknown Licenses" }; ReportConfiguration config = mojo.getConfiguration(); // validate configuration @@ -238,13 +257,14 @@ public void testRAT335GitIgnore() throws Exception { "Apache Licensed: 2$", "Generated Documents: 0", "^3 Unknown Licenses", - " AL +\\Q" + dir + "pom.xml\\E$", - "\\Q!????? " + dir + "dir1/dir1.md\\E$", - "\\Q!????? " + dir + "dir2/dir2.txt\\E$", - "\\Q!????? " + dir + "dir3/file3.log\\E$", - "^== File: \\Q" + dir + "dir1/dir1.md\\E$", - "^== File: \\Q" + dir + "dir2/dir2.txt\\E$", - "^== File: \\Q" + dir + "dir3/file3.log\\E$" + RatTestHelpers.documentOut(true, Document.Type.STANDARD, dir + "pom.xml")+ + RatTestHelpers.APACHE_LICENSE, + RatTestHelpers.documentOut(false, Document.Type.STANDARD, dir + "dir1/dir1.md")+ + RatTestHelpers.UNKNOWN_LICENSE, + RatTestHelpers.documentOut(false, Document.Type.STANDARD, dir + "dir2/dir2.txt")+ + RatTestHelpers.UNKNOWN_LICENSE, + RatTestHelpers.documentOut(false, Document.Type.STANDARD, dir + "dir3/file3.log")+ + RatTestHelpers.UNKNOWN_LICENSE, }; try { mojo.execute(); @@ -286,10 +306,6 @@ public void testRAT362GitIgnore() throws Exception { // Create the test file with a content on which it must fail File generatedFile = new File(targetDirectory + "/foo.md"); - BufferedWriter writer = new BufferedWriter(new FileWriter(generatedFile)); - writer.write("File without a valid license\n"); - writer.close(); - final String[] expected = { "Notes: 0", "Binaries: 0", @@ -298,9 +314,14 @@ public void testRAT362GitIgnore() throws Exception { "Apache Licensed: 2$", "Generated Documents: 0", "^1 Unknown Licenses", - "^== File: \\Q" + generatedFile + "\\E$", + RatTestHelpers.documentOut(false, Document.Type.STANDARD, generatedFile.getCanonicalPath()) + + RatTestHelpers.UNKNOWN_LICENSE, }; try { + BufferedWriter writer = new BufferedWriter(new FileWriter(generatedFile)); + writer.write("File without a valid license\n"); + writer.close(); + mojo.execute(); fail("Expected RatCheckException: This check should have failed on the invalid test file"); } catch (RatCheckException e) { @@ -308,9 +329,10 @@ public void testRAT362GitIgnore() throws Exception { assertTrue("report filename was not contained in '" + msg + "'", msg.contains(ratTxtFile.getName())); assertFalse("no null allowed in '" + msg + "'", (msg.toUpperCase().contains("NULL"))); ensureRatReportIsCorrect(ratTxtFile, expected, TextUtils.EMPTY); + } finally { + // Cleanup + assertTrue(generatedFile.delete()); } - // Cleanup - assertTrue(generatedFile.delete()); } } diff --git a/apache-rat-plugin/src/test/java/org/apache/rat/mp/RatTestHelpers.java b/apache-rat-plugin/src/test/java/org/apache/rat/mp/RatTestHelpers.java index e12bafa53..f0054749c 100644 --- a/apache-rat-plugin/src/test/java/org/apache/rat/mp/RatTestHelpers.java +++ b/apache-rat-plugin/src/test/java/org/apache/rat/mp/RatTestHelpers.java @@ -39,6 +39,7 @@ import org.apache.maven.doxia.siterenderer.Renderer; import org.apache.maven.settings.Settings; import org.apache.maven.settings.io.xpp3.SettingsXpp3Reader; +import org.apache.rat.api.Document.Type; import org.apache.rat.testhelpers.TextUtils; import org.codehaus.plexus.PlexusContainer; import org.codehaus.plexus.util.DirectoryScanner; @@ -50,6 +51,10 @@ */ public final class RatTestHelpers { + public static final String APACHE_LICENSE = licenseOut("AL", "Apache License Version 2.0"); + public static final String UNKNOWN_LICENSE = licenseOut("?????", "Unknown license (Unapproved)"); + + /** * @param pDir Removes the given directory recursively. * @throws IOException in case of errors. @@ -208,4 +213,37 @@ public static void ensureRatReportIsCorrect(File pRatTxtFile, String[] in, Strin TextUtils.assertPatternInOutput(pattern, document); } } + + /** + * Defines the expected document string. + * @param approved the approved flag. + * @param type the document type + * @param name the document name. + * @return the string to match the document. + */ + public static String documentOut(boolean approved, Type type, String name) { + return String.format("^\\Q%s%s %s\\E$", approved ? " " : "!", type.name().substring(0, 1), name); + } + + /** + * Defines the expected license string. + * @param family the license family + * @param name the license name + * @return the string to match the license. + */ + public static String licenseOut(String family, String name) { + return licenseOut(family, family, name); + } + + /** + * Defines the expected license string. + * @param family the license family + * @param id the ID for the license. + * @param name the license name + * @return the string to match the license. + */ + public static String licenseOut(String family, String id, String name) { + return String.format("\\s+\\Q%s\\E\\s+\\Q%s\\E\\s+\\Q%s\\E$", family, id, name); + } + } diff --git a/apache-rat-plugin/src/test/resources/unit/RAT-343/pom.xml b/apache-rat-plugin/src/test/resources/unit/RAT-343/pom.xml index aa4b20c34..f1685b5f0 100644 --- a/apache-rat-plugin/src/test/resources/unit/RAT-343/pom.xml +++ b/apache-rat-plugin/src/test/resources/unit/RAT-343/pom.xml @@ -28,6 +28,7 @@ @pom.version@ true + 1 MIT diff --git a/apache-rat-tasks/run-antunit.xml b/apache-rat-tasks/run-antunit.xml index e969c1f37..b60c6ec04 100644 --- a/apache-rat-tasks/run-antunit.xml +++ b/apache-rat-tasks/run-antunit.xml @@ -30,11 +30,12 @@ - - - - - + + + + + + diff --git a/apache-rat-tasks/src/main/java/org/apache/rat/anttasks/All.java b/apache-rat-tasks/src/main/java/org/apache/rat/anttasks/All.java index ff94c3b96..04dec5b1a 100644 --- a/apache-rat-tasks/src/main/java/org/apache/rat/anttasks/All.java +++ b/apache-rat-tasks/src/main/java/org/apache/rat/anttasks/All.java @@ -32,6 +32,6 @@ public IHeaderMatcher build() { } public void add(IHeaderMatcher.Builder builder) { - this.builder.add(builder); + this.builder.addEnclosed(builder); } } diff --git a/apache-rat-tasks/src/main/java/org/apache/rat/anttasks/Any.java b/apache-rat-tasks/src/main/java/org/apache/rat/anttasks/Any.java index 964dcc6e0..3b2bd22f3 100644 --- a/apache-rat-tasks/src/main/java/org/apache/rat/anttasks/Any.java +++ b/apache-rat-tasks/src/main/java/org/apache/rat/anttasks/Any.java @@ -32,6 +32,6 @@ public IHeaderMatcher build() { } public void add(IHeaderMatcher.Builder builder) { - this.builder.add(builder); + this.builder.addEnclosed(builder); } } diff --git a/apache-rat-tasks/src/main/java/org/apache/rat/anttasks/License.java b/apache-rat-tasks/src/main/java/org/apache/rat/anttasks/License.java index 6d42088fc..8a236d8e1 100644 --- a/apache-rat-tasks/src/main/java/org/apache/rat/anttasks/License.java +++ b/apache-rat-tasks/src/main/java/org/apache/rat/anttasks/License.java @@ -34,24 +34,20 @@ ILicense.Builder asBuilder() { } public ILicense build(SortedSet context) { - return builder.build(context); + return builder.setLicenseFamilies(context).build(); } public void setNotes(String notes) { - builder.setNotes(notes); + builder.setNote(notes); } public void addNotes(String notes) { - builder.setNotes(notes); + builder.setNote(notes); } - public void setDerivedFrom(String derivedFrom) { - builder.setDerivedFrom(derivedFrom); - } - public void setFamily(String licenseFamilyCategory) { - builder.setLicenseFamilyCategory(licenseFamilyCategory); + builder.setFamily(licenseFamilyCategory); } public void setId(String id) { diff --git a/apache-rat-tasks/src/main/java/org/apache/rat/anttasks/Not.java b/apache-rat-tasks/src/main/java/org/apache/rat/anttasks/Not.java index 734682c32..50a68433e 100644 --- a/apache-rat-tasks/src/main/java/org/apache/rat/anttasks/Not.java +++ b/apache-rat-tasks/src/main/java/org/apache/rat/anttasks/Not.java @@ -32,6 +32,6 @@ public IHeaderMatcher build() { } public void add(IHeaderMatcher.Builder builder) { - this.builder.add(builder); + this.builder.addEnclosed(builder); } } diff --git a/apache-rat-tasks/src/main/java/org/apache/rat/anttasks/Regex.java b/apache-rat-tasks/src/main/java/org/apache/rat/anttasks/Regex.java index f212c4115..cd7c7e53b 100644 --- a/apache-rat-tasks/src/main/java/org/apache/rat/anttasks/Regex.java +++ b/apache-rat-tasks/src/main/java/org/apache/rat/anttasks/Regex.java @@ -19,22 +19,23 @@ package org.apache.rat.anttasks; import org.apache.rat.analysis.IHeaderMatcher; +import org.apache.rat.analysis.matchers.SimpleRegexMatcher; import org.apache.rat.configuration.builders.RegexBuilder; public class Regex implements IHeaderMatcher.Builder { private final RegexBuilder builder = new RegexBuilder(); - public void setRegex(String pattern) { + public void setExpr(String pattern) { builder.setExpr(pattern); } - + public void addText(String pattern) { builder.setExpr(pattern); } @Override - public IHeaderMatcher build() { + public SimpleRegexMatcher build() { return builder.build(); } } diff --git a/apache-rat-tasks/src/main/java/org/apache/rat/anttasks/Report.java b/apache-rat-tasks/src/main/java/org/apache/rat/anttasks/Report.java index c55d4d9e1..359bc5237 100644 --- a/apache-rat-tasks/src/main/java/org/apache/rat/anttasks/Report.java +++ b/apache-rat-tasks/src/main/java/org/apache/rat/anttasks/Report.java @@ -207,7 +207,7 @@ public void setAddDefaultDefinitions(File fileName) { } public ReportConfiguration getConfiguration() { - Defaults defaults = defaultsBuilder.build(); + Defaults defaults = defaultsBuilder.build(configuration.getLog()); configuration.setFrom(defaults); configuration.setReportable(new ResourceCollectionContainer(nestedResources)); @@ -223,7 +223,9 @@ public ReportConfiguration getConfiguration() { @Override public void execute() { try { - Reporter.report(validate(getConfiguration())); + Reporter r = new Reporter(validate(getConfiguration())); + r.output(null, () -> new ReportConfiguration.NoCloseOutputStream(System.out)); + r.output(); } catch (BuildException e) { throw e; } catch (Exception ioex) { diff --git a/apache-rat-tasks/src/main/java/org/apache/rat/anttasks/ResourceCollectionContainer.java b/apache-rat-tasks/src/main/java/org/apache/rat/anttasks/ResourceCollectionContainer.java index 24b4012f5..ca474b96a 100644 --- a/apache-rat-tasks/src/main/java/org/apache/rat/anttasks/ResourceCollectionContainer.java +++ b/apache-rat-tasks/src/main/java/org/apache/rat/anttasks/ResourceCollectionContainer.java @@ -48,11 +48,10 @@ class ResourceCollectionContainer implements IReportable { @Override public void run(RatReport report) throws RatException { - ResourceDocument document = new ResourceDocument(); + ResourceDocument document = null; for (Resource r : rc) { if (!r.isDirectory()) { - document.setResource(r); - document.getMetaData().clear(); + document = new ResourceDocument(r); report.report(document); } } @@ -60,11 +59,12 @@ public void run(RatReport report) throws RatException { private static class ResourceDocument implements Document { - private Resource resource; - private final MetaData metaData = new MetaData(); + private final Resource resource; + private final MetaData metaData; - private void setResource(Resource resource) { + private ResourceDocument(Resource resource) { this.resource = resource; + this.metaData = new MetaData(); } @Override diff --git a/apache-rat-tasks/src/main/java/org/apache/rat/anttasks/Text.java b/apache-rat-tasks/src/main/java/org/apache/rat/anttasks/Text.java index 6d5857896..e5d544f2a 100644 --- a/apache-rat-tasks/src/main/java/org/apache/rat/anttasks/Text.java +++ b/apache-rat-tasks/src/main/java/org/apache/rat/anttasks/Text.java @@ -26,11 +26,11 @@ public class Text implements IHeaderMatcher.Builder { private final TextBuilder builder = new TextBuilder(); public void addText(String text) { - builder.setText(text); + builder.setSimpleText(text); } public void setText(String text) { - builder.setText(text); + builder.setSimpleText(text); } @Override public IHeaderMatcher build() { diff --git a/apache-rat-tasks/src/test/java/org/apache/rat/anttasks/ReportTest.java b/apache-rat-tasks/src/test/java/org/apache/rat/anttasks/ReportTest.java index 26a29838c..1dcd947de 100644 --- a/apache-rat-tasks/src/test/java/org/apache/rat/anttasks/ReportTest.java +++ b/apache-rat-tasks/src/test/java/org/apache/rat/anttasks/ReportTest.java @@ -53,16 +53,28 @@ protected File getAntFile() { return antFile; } + private String logLine(String id) { + return logLine(true, getAntFileName(), id); + } + + private String logLine(String antFile, String id) { + return logLine(true, antFile, id); + } + + private String logLine(boolean approved, String antFile, String id) { + return String.format( "%sS \\Q%s\\E\\s+\\Q%s\\E ", approved?" ":"!", antFile, id); + } + @Test public void testWithReportSentToAnt() throws Exception { buildRule.executeTarget("testWithReportSentToAnt"); - assertLogMatches("AL +\\Q" + getAntFileName() + "\\E"); + assertLogMatches(logLine("AL")); } @Test public void testWithReportSentToFile() throws Exception { final File reportFile = new File(getTempDir(), "selftest.report"); - final String alLine = "AL +\\Q" + getAntFileName() + "\\E"; + final String alLine = " S \\Q" + getAntFileName() + "\\E"; if (!getTempDir().mkdirs() && !getTempDir().isDirectory()) { throw new IOException("Could not create temporary directory " + getTempDir()); @@ -79,36 +91,36 @@ public void testWithReportSentToFile() throws Exception { @Test public void testWithALUnknown() throws Exception { buildRule.executeTarget("testWithALUnknown"); - assertLogDoesNotMatch("AL +\\Q" + getAntFileName() + "\\E"); - assertLogMatches("\\!\\?\\?\\?\\?\\? +\\Q" + getAntFileName() + "\\E"); + assertLogDoesNotMatch(logLine("AL")); + assertLogMatches(logLine(false, getAntFileName(), "?????")); } @Test public void testCustomLicense() throws Exception { buildRule.executeTarget("testCustomLicense"); - assertLogDoesNotMatch(" AS +\\Q" + getAntFileName() + "\\E"); - assertLogMatches(" newFa +\\Q" + getAntFileName() + "\\E"); + assertLogDoesNotMatch(logLine("AL")); + assertLogMatches(logLine("newFa")); } @Test public void testCustomMatcher() throws Exception { buildRule.executeTarget("testCustomMatcher"); - assertLogDoesNotMatch(" AS +\\Q" + getAntFileName() + "\\E"); - assertLogMatches(" YASL1 +\\Q" + getAntFileName() + "\\E"); + assertLogDoesNotMatch(logLine("AL")); + assertLogMatches(logLine("YASL1")); } @Test public void testInlineCustomMatcher() throws Exception { buildRule.executeTarget("testInlineCustomMatcher"); - assertLogDoesNotMatch(" AS +\\Q" + getAntFileName() + "\\E"); - assertLogMatches(" YASL1 +\\Q" + getAntFileName() + "\\E"); + assertLogDoesNotMatch(logLine("AL")); + assertLogMatches(logLine("YASL1")); } @Test public void testCustomMatcherBuilder() throws Exception { buildRule.executeTarget("testCustomMatcherBuilder"); - assertLogDoesNotMatch(" AS +\\Q" + getAntFileName() + "\\E"); - assertLogMatches(" YASL1 +\\Q" + getAntFileName() + "\\E"); + assertLogDoesNotMatch(logLine("AL")); + assertLogMatches(logLine("YASL1")); } @Test @@ -127,8 +139,8 @@ public void testCopyrightBuild() throws Exception { try { String fileName = new File(getAntFile().getParent(), "index.apt").getPath().replace('\\', '/'); buildRule.executeTarget("testCopyrightBuild"); - assertLogMatches("YASL1 +\\Q" + fileName + "\\E"); - assertLogDoesNotMatch("AL +\\Q" + fileName + "\\E"); + assertLogMatches(logLine(fileName,"YASL1")); + assertLogDoesNotMatch(logLine(fileName,"AL")); } catch (BuildException e) { final String expect = "You must specify at least one file"; assertTrue("Expected " + expect + ", got " + e.getMessage(), e.getMessage().contains(expect)); diff --git a/apache-rat-tasks/src/test/java/org/example/Matcher.java b/apache-rat-tasks/src/test/java/org/example/Matcher.java index 168f1e8f3..58588e19a 100644 --- a/apache-rat-tasks/src/test/java/org/example/Matcher.java +++ b/apache-rat-tasks/src/test/java/org/example/Matcher.java @@ -16,16 +16,19 @@ */ package org.example; -import org.apache.rat.analysis.matchers.AbstractSimpleMatcher; +import org.apache.rat.analysis.IHeaders; +import org.apache.rat.analysis.matchers.AbstractHeaderMatcher; +import org.apache.rat.config.parameters.ComponentType; +import org.apache.rat.config.parameters.ConfigComponent; -public class Matcher extends AbstractSimpleMatcher { +@ConfigComponent(type = ComponentType.MATCHER, name = "myCustomMatcher", desc = "Custom matcher example") +public class Matcher extends AbstractHeaderMatcher { public Matcher() { super("MyCustomMatcher"); } @Override - public boolean doMatch(String line) { + public boolean matches(IHeaders headers) { return true; } - } diff --git a/apache-rat-tasks/src/test/resources/antunit/report-bad-configurations.xml b/apache-rat-tasks/src/test/resources/antunit/report-bad-configurations.xml index 1aee502d0..92faed8ff 100644 --- a/apache-rat-tasks/src/test/resources/antunit/report-bad-configurations.xml +++ b/apache-rat-tasks/src/test/resources/antunit/report-bad-configurations.xml @@ -37,8 +37,7 @@ - + @@ -59,7 +58,7 @@ - + @@ -76,7 +75,7 @@ + expectedMessage="License family 'YASL1' not found"> + + + + + + classpath="${test.classpath}" + loaderref="testloader" /> + classpath="${test.classpath}" + loaderref="testloader" /> @@ -66,10 +73,10 @@ SPDX-License-Identifier: Apache-2.0 - + - + value='<resource name="${file.name}"' /> + @@ -84,11 +91,11 @@ SPDX-License-Identifier: Apache-2.0 - - - - - + @@ -99,7 +106,7 @@ SPDX-License-Identifier: Apache-2.0 - + @@ -107,9 +114,9 @@ SPDX-License-Identifier: Apache-2.0 - + - + @@ -137,8 +144,9 @@ SPDX-License-Identifier: Apache-2.0 - - + + + @@ -155,40 +163,42 @@ SPDX-License-Identifier: Apache-2.0 - - - + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -199,21 +209,22 @@ SPDX-License-Identifier: Apache-2.0 - - - + + + + --> - - - - - - - - - - - + @@ -279,7 +290,7 @@ SPDX-License-Identifier: Apache-2.0 - + @@ -359,8 +370,9 @@ SPDX-License-Identifier: Apache-2.0 - - + + + - - + + + - - + + + - - + + + - + @@ -446,22 +460,26 @@ SPDX-License-Identifier: Apache-2.0 - + diff --git a/apache-rat-tools/pom.xml b/apache-rat-tools/pom.xml new file mode 100644 index 000000000..7a19d511b --- /dev/null +++ b/apache-rat-tools/pom.xml @@ -0,0 +1,94 @@ + + + + 4.0.0 + + org.apache.rat + apache-rat-project + 0.17-SNAPSHOT + + apache-rat-tools + jar + Apache Creadur Rat::Tools + Tools to manage and report on Rat + + + + false + src/main/resources + + + true + src/main/filtered-resources + + + .. + META-INF + + RELEASE_NOTES.txt + + + + + + + org.apache.rat + apache-rat-plugin + + + + src/test/resources/** + + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + test-jar + + + + + + + + + org.apache.rat + apache-rat-core + ${project.parent.version} + + + org.apache.rat + apache-rat-core + ${project.parent.version} + test-jar + tests + test + + + commons-cli + commons-cli + + + diff --git a/apache-rat-tools/pom.xml.new b/apache-rat-tools/pom.xml.new deleted file mode 100644 index 73496c267..000000000 --- a/apache-rat-tools/pom.xml.new +++ /dev/null @@ -1,104 +0,0 @@ - - - - 4.0.0 - org.apache.rat.test - it2 - 1.0 - - - - org.apache.rat - apache-rat-plugin - @pom.version@ - - true - - MIT - - - - - Licensed MIT - Licensed under MIT - Licensed under the MIT license - MIT/GPL2 Licensed - licensed under the MIT and GPL - MIT license - - - BSD - - - - - -BSD-style license - - CC BY - - - - - creativecommons.org/licenses/by/4.0 - - - - MITMIT - BSDBSD - CC BYCC BY - - - - **/*.mp3 - **/localhost.jks - **/node_modules/** - **/package-lock.json - **/target/** - **/.project - **/.classpath - **/.settings/** - .gitattributes - - - - - - - diff --git a/apache-rat-tools/src/main/java/org/apache/rat/Documentation.java b/apache-rat-tools/src/main/java/org/apache/rat/Documentation.java new file mode 100644 index 000000000..320289a97 --- /dev/null +++ b/apache-rat-tools/src/main/java/org/apache/rat/Documentation.java @@ -0,0 +1,148 @@ +/* + * 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; + +import java.io.IOException; +import java.io.Writer; +import java.lang.reflect.InvocationTargetException; +import java.util.Map; + +import org.apache.commons.cli.HelpFormatter; +import org.apache.commons.cli.Options; +import org.apache.rat.config.parameters.Description; +import org.apache.rat.config.parameters.DescriptionBuilder; +import org.apache.rat.configuration.MatcherBuilderTracker; +import org.apache.rat.configuration.builders.AbstractBuilder; +import org.apache.rat.license.ILicense; +import org.apache.rat.license.ILicenseFamily; +import org.apache.rat.license.LicenseSetFactory.LicenseFilter; + +/** + * Generates text based documentation for Licenses, LicenceFamilies, and Matchers. + * + * Utilizes the same command line as the CLI based Report client so that additional licenses, etc. can be added. + * + */ +public class Documentation { + + private static final String INDENT = " "; + + private Documentation() { + } + + /** + * Output the ReportConfiguration to a writer. + * @param cfg The configuration to write. + * @param writer the writer to write to. + * @throws IOException on error. + */ + public static void output(ReportConfiguration cfg, Writer writer) throws IOException { + writer.write(String.format("%n>>>> LICENSES <<<<%n%n")); + for (ILicense l : cfg.getLicenses(LicenseFilter.ALL)) { + printLicense(l, writer); + } + + writer.write(String.format("%n>>>> MATCHERS (Datatype IHeaderMatcher) <<<<%n%n")); + for (Class mClazz : MatcherBuilderTracker.INSTANCE.getClasses()) { + try { + AbstractBuilder builder = mClazz.getConstructor().newInstance(); + printMatcher(DescriptionBuilder.buildMap(builder.builtClass()), writer); + } catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException + | IllegalArgumentException | InvocationTargetException e) { + throw new ConfigurationException( + String.format("Can not instantiate matcher builder %s ", mClazz.getName()), e); + } + } + + writer.write(String.format("%n>>>> FAMILIES (Datatype ILicenseFamily) <<<<%n%n")); + for (ILicenseFamily family : cfg.getLicenseFamilies(LicenseFilter.ALL)) { + printFamily(family, writer); + } + } + + private static void printFamily(ILicenseFamily family, Writer writer) throws IOException { + writer.write(String.format("'%s' - %s%n", family.getFamilyCategory().trim(), family.getFamilyName())); + } + + private static void printMatcher(Description matcher, Writer writer) throws IOException { + writer.write(String.format("'%s' - %s%n", matcher.getCommonName(), matcher.getDescription())); + printChildren(1, matcher.getChildren(), writer); + + } + + private static void printLicense(ILicense license, Writer writer) throws IOException { + Description dLicense = license.getDescription(); + writer.write(String.format("'%s' - %s %n", dLicense.getCommonName(), dLicense.getDescription())); + printChildren(1, dLicense.getChildren(), writer); + } + + private static void writeIndent(int indent, Writer writer) throws IOException { + for (int i = 0; i < indent; i++) { + writer.write(INDENT); + } + } + + private static void printChildren(int indent, Map children, Writer writer) throws IOException { + for (Description d : children.values()) { + writeIndent(indent, writer); + switch (d.getType()) { + case PARAMETER: + case BULID_PARAMETER: + writer.write(String.format("'%s' %s (Datatype: %s%s)%n", d.getCommonName(), d.getDescription(), + d.isCollection() ? "Collection of " : "", d.getChildType().getSimpleName())); + break; + case LICENSE: + // do nothing + break; + case MATCHER: + writeIndent(indent + 1, writer); + writer.write(String.format("%s %s %n", d.isCollection() ? "Collection of " : "A ", + d.getChildType().getSimpleName())); + printChildren(indent + 2, d.getChildren(), writer); + break; + } + } + } + + /** + * Creates the documentation. Writes to the output specified by the -o or --out option. Defaults to System.out. + * @param args the arguments. Try --help for help. + * @throws IOException on error + */ + public static void main(String[] args) throws IOException { + ReportConfiguration config = Report.parseCommands(args, Documentation::printUsage, true); + if (config != null) { + try (Writer writer = config.getWriter().get()) { + Documentation.output(config, writer); + } + } + } + + private static void printUsage(Options opts) { + HelpFormatter f = new HelpFormatter(); + f.setOptionComparator(new Report.OptionComparator()); + f.setWidth(120); + String header = "\nAvailable options"; + String footer = ""; + String cmdLine = String.format("java -jar apache-rat/target/apache-rat-CURRENT-VERSION.jar %s [options]", + Documentation.class.getName()); + f.printHelp(cmdLine, header, opts, footer, false); + System.exit(0); + } +} diff --git a/apache-rat/pom.xml b/apache-rat/pom.xml index 2dcd8e10a..6054f74ce 100644 --- a/apache-rat/pom.xml +++ b/apache-rat/pom.xml @@ -129,6 +129,8 @@ have no license headers --> README-ANT.txt README-CLI.txt + + src/site/examples/** diff --git a/apache-rat/src/site/examples/default_output.txt b/apache-rat/src/site/examples/default_output.txt new file mode 100644 index 000000000..30ac9654b --- /dev/null +++ b/apache-rat/src/site/examples/default_output.txt @@ -0,0 +1,81 @@ + +***************************************************** +Summary +------- +Generated at: 2024-03-30T14:40:39+01:00 + +Notes: 2 +Binaries: 2 +Archives: 1 +Standards: 8 + +Apache Licensed: 5 +Generated Documents: 1 + +JavaDocs are generated, thus a license header is optional. +Generated files do not require license headers. + +2 Unknown Licenses + +***************************************************** + +Files with unapproved licenses: + + src/test/resources/elements/Source.java + src/test/resources/elements/sub/Empty.txt + +***************************************************** + +***************************************************** + Documents with unapproved licenses will start with a '!' + The next character identifies the document type. + + char type + a Archive file + b Binary file + g Generated file + n Notice file + s Standard file + u Unknown file. + + s src/test/resources/elements/ILoggerFactory.java + MIT MIT The MIT License + + b src/test/resources/elements/Image.png + + n src/test/resources/elements/LICENSE + + n src/test/resources/elements/NOTICE + +!s src/test/resources/elements/Source.java + ????? ????? Unknown license (Unapproved) + + s src/test/resources/elements/Text.txt + AL AL Apache License Version 2.0 + + s src/test/resources/elements/TextHttps.txt + AL AL Apache License Version 2.0 + + s src/test/resources/elements/Xml.xml + AL AL Apache License Version 2.0 + + s src/test/resources/elements/buildr.rb + AL AL Apache License Version 2.0 + + a src/test/resources/elements/dummy.jar + + g src/test/resources/elements/generated.txt + GEN GEN Generated Files + + b src/test/resources/elements/plain.json + + s src/test/resources/elements/tri.txt + AL AL Apache License Version 2.0 + BSD-3 BSD-3 BSD 3 clause + BSD-3 TMF The Telemanagement Forum License + +!s src/test/resources/elements/sub/Empty.txt + ????? ????? Unknown license (Unapproved) + + +***************************************************** diff --git a/apache-rat/src/site/examples/rat-report.txt b/apache-rat/src/site/examples/rat-report.txt new file mode 100644 index 000000000..d3e1d7521 --- /dev/null +++ b/apache-rat/src/site/examples/rat-report.txt @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apache-rat/src/site/examples/unapproved-licenses.txt b/apache-rat/src/site/examples/unapproved-licenses.txt new file mode 100644 index 000000000..e69de29bb diff --git a/pom.xml b/pom.xml index 37d3792cb..aa45fefa0 100644 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ org.apache apache - 31 + 32 org.apache.rat apache-rat-project @@ -59,7 +59,7 @@ agnostic home for software distribution comprehension and audit tools. 3.12.0 2.12.1 3.6.3 - 3.21.2 + 3.22.0 0.17 @@ -87,7 +87,7 @@ agnostic home for software distribution comprehension and audit tools. commons-cli commons-cli - 1.6.0 + 1.7.0 org.apache.commons @@ -704,6 +704,7 @@ agnostic home for software distribution comprehension and audit tools. apache-rat-plugin apache-rat-tasks apache-rat + apache-rat-tools diff --git a/src/changes/changes.xml b/src/changes/changes.xml index d0f27270a..71ae294a2 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -72,6 +72,15 @@ https://maven.apache.org/plugins/maven-changes-plugin/xsd/changes-1.0.0.xsd --> + + Fix integration test failure with Maven4 by adding a version property in integration test's pom.xml. Versions above Maven4-alpha13 require Java17 and cannot be used with RAT, as it relies on Java8. + + + Optionally export XML configuration file as part of run. Added framework to inspect available licenses and matchers. + + + Switch to processing header matches in one call rather than line by line. + Fix if --force option is used executable bit is not set properly on newly created/license-augmented file. diff --git a/src/site/apt/index.apt.vm b/src/site/apt/index.apt.vm index 3ace85db6..236cab049 100644 --- a/src/site/apt/index.apt.vm +++ b/src/site/apt/index.apt.vm @@ -63,6 +63,16 @@ Apache Rat {{{./issue-management.html}patch}} or {{{./mailing-lists.html}talk to us}} whenever Rat falls short. + +** How do I extend Rat + + There are several standard ways to extend Rat. + + * Add a {{{./license_def.html}license definition}} via an XML file. + + * Add a new {{{./matcher_def.html}Matcher definition}}. Requires Java expertise. + + * Add a new definition format. Requires Java expertice as well as expertise with the format. ** Who Develops Rat? @@ -102,6 +112,8 @@ java -jar apache-rat-${project.version}.jar --help Use the plugin for {{{https://maven.apache.org}Apache Maven ${mavenVersion}}} or later. + <> Maven4 is based on Java 17 and thus cannot be used with RAT, that relies on Java 8. + +------------------------------------------+ org.apache.rat diff --git a/src/site/apt/license_def.apt.vm b/src/site/apt/license_def.apt.vm new file mode 100644 index 000000000..15c08c813 --- /dev/null +++ b/src/site/apt/license_def.apt.vm @@ -0,0 +1,143 @@ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~ 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. +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + -------------------------- + How to define new licenses + -------------------------- + +How to define licenses in Apache Rat + + All licenses in Apache Rat are defined in configuration files. There is a default + {{{https://gitbox.apache.org/repos/asf/creadur-rat/blob/master/apache-rat-core/src/main/resources/org/apache/rat/default.xml} + XML based configuration file}} that can serve as a good example of various definitions. + + It is possible to create new parsers for different configuration formats. But that task is beyond the + scope of this document. In this document we will address how to define licenses in the default XML format. + + The XML document has a root node named "rat-config" which comprises 4 major sections: "Families", + "Licenses", "Approved" and "Matchers". + +* Families + + Families are groups that define licenses that share similarities. Each family has an id and a name. An example + of an entry in this section is + ++------------------------------------------+ + + + + + ++------------------------------------------+ + +* Matchers + + Matchers define tests that check the contents of files for specific patterns. Matchers are defined in Java + code and are required to have Builder classes that build them. There is {{{./matcher_def.html}additional documentation + explaining how to create new Matchers}}. In the XML document Matchers are identified by the "matcher" element that has + a single "class" property. The value of the "class" property is the fully qualified class name that + identifies a Builder class. An example of a matchers entry is + ++------------------------------------------+ + + + + + + + ++------------------------------------------+ + +* Licenses + + License elements have three properties: "family", "id", and "name". The family property must refer to + the "id" of a family element. The family element can be defined in the current document or in another + included document. It has to exist by the time all the configuration files are read. The "id" element + is optional, if it is not provided the id will be set to the id of the associated family. However, the + "id" property must be unique across all licenses. The "name" property is also optional. If not specified + the name of the associated family will be used. It is recommended that each license have a unique name + as name conflicts make problem determination difficult. + + License elements have two child element types. The first is "notes". There may be multiple "notes" + child elements. They are simple text elements that define notes about the license. The other element + is a Matcher type. A matcher element is defined by a builder in the "matchers" section as described + above. The matchers listed in the example above define the "any", "spdx", and "text" matcher elements. + + When defining the license the matcher is required and only one may be defined. However, there are matchers + that accept multiple matcher child nodes. In the example below the CDDL1 license uses the "any" matcher. + The "any" matcher accepts multiple child matchers and will report a match if any of the enclosed matchers + report a match. In the example below the "any" matcher has "text" and "spdx" child matchers. The "text" + matcher will match the enclosed text and the "spdx" matcher matches {{{https://spdx.org/licenses/}SPDX}} + tags. The second license defined below is the "ILLUMOS" license. It shares the same family as the CDDL1 + license, has its own "id" and "name" properties. It also has a "note" child element that tells the user + that it is a modified CDDL1 license. The license has a single "text" matcher. + ++------------------------------------------+ + + + + + The contents of this file are subject to the terms of the + Common Development and Distribution License("CDDL") (the + "License"). You may not use this file except in compliance + with the License. + + + + + + Modified CDDL1 license + The contents of this file are subject to the terms of + the Common Development and Distribution License (the + "License") You may not use this file except in compliance + with the License. + + + ++------------------------------------------+ + +* Approved + + Approved element lists the approved licenses from this configuration. It is possible to define licenses + that you want to detect but not allow. If the "approved" element is not present all defined families + are considered to be approved. The approved entry has "family" child entries that have "license_ref" + properties that reference the "id" of the family being approved. + ++------------------------------------------+ + + + + + ++------------------------------------------+ + +* Listing components + + All the components (Licenses and Matchers) defined in the system can be displayed using the Documentation + tool. Download the apache-rat-tools-${project.version}.jar and run the tool execute: + ++------------------------------------------+ +java -cp apache-rat-${project.version}.jar:apache-rat-tools-${project.version} org.apache.rat.Documentation ++------------------------------------------+ + + The tool take the same arguments as the standard Rat CLI, so additional license files can be defined. + To display help for the tool execute + ++------------------------------------------+ +java -cp apache-rat-${project.version}.jar:apache-rat-tools-${project.version} org.apache.rat.Documentation --help ++------------------------------------------+ + diff --git a/src/site/apt/matcher_def.apt.vm b/src/site/apt/matcher_def.apt.vm new file mode 100644 index 000000000..fa63881df --- /dev/null +++ b/src/site/apt/matcher_def.apt.vm @@ -0,0 +1,323 @@ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~ 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. +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + -------------------------- + How to define new Matchers + -------------------------- + +How to define matchers in Apache Rat + + Matchers in Apache Rat are paired with builders. A matcher must implement the {{{https://github.com/apache/creadur-rat/blob/master/apache-rat-core/src/main/java/org/apache/rat/analysis/IHeaderMatcher.java}IHeaderMatcher}} + interface and its associated builder must implement the IHeaderMatcher.Builder interface. + +* A simple example + +** The Matcher implementation + + For our example we will implement a Matcher that implements the phrase "Quality, speed and cost, pick any two” by looking for the occurrence of all three words anywhere in the header. + In most cases is it simplest to extend the {{{https://github.com/apache/creadur-rat/blob/master/apache-rat-core/src/main/java/org/apache/rat/analysis/matchers/AbstractHeaderMatcher.java}AbstractHeaderMatcher}} + class as this class will handle setting of the unique id for instances that do not otherwise have a unique id. + + + So lets start by creating our matcher class and implementing the matches method. The matches method takes an + {{{https://github.com/apache/creadur-rat/blob/master/apache-rat-core/src/main/java/org/apache/rat/IHeaders.java}IHeaders}} argument. IHeaders is an object that contains the header text in two formats: + + * raw - just as read from the file. + + * pruned - containing only letters and digits, and with all letters lowercased. + ++------------------------------------------+ +package com.example.ratMatcher; + +import org.apache.rat.analysis.IHeaders; +import org.apache.rat.analysis.matchers.AbstractHeaderMatcher; +import org.apache.rat.config.parameters.Component; +import org.apache.rat.config.parameters.ConfigComponent; + +@ConfigComponent(type = Component.Type.MATCHER, name = "QSC", desc = "Reports if the 'Quality, speed and cost, pick any two' rule is violated") +public class QSCMatcher extends AbstractHeaderMatcher { + + public QSCMatcher(String id) { + super(id); + } + + @Override + public boolean matches(IHeaders headers) { + String text = headers.prune() + return text.contains("quality") && text.contains("speed") && text.contains("cost"); + } +} ++------------------------------------------+ + + In the above example we use the {{{https://github.com/apache/creadur-rat/blob/master/apache-rat-core/src/main/java/org/apache/rat/config/parameters/ConfigComponent.java}ConfigComponent}} annotation to + identify that this is a MATCHER component, that it has the name 'QSC' and a description of what it does. + If the "name" was not specified the name would have been extracted from the class name by removing the "Matcher" from "QSCMatcher" and making the first character lowercase: "qSC". + + The Constructor calls the AbstractHeaderMatcher constructor with an id value. A null argument passed to AbstractHeaderMatcher will generate a UUID based id. + + The matcher uses the pruned text to check for the strings. There is an issue with this matcher in that it would match the string: + "The quality of Dennis Hopper's acting, as Keanu Reeves costar in 'Speed', is outstanding." + + The correction of that is left as an exercise for the reader. Hint: matching the pruned text can be a quick gating check for a set of more expensive regular expression checks against the raw text. + +** The Matcher.Builder implementation + + The builder must implement the IHeaderMatcher.Builder interface. + + The work of handling the id and some other tasks is handled by the + {{{https://github.com/apache/creadur-rat/blob/master/apache-rat-core/src/main/java/org/apache/rat/configuration/builders/AbstractBuilder.java}AbstractBuilder}}. + + So we have: + ++------------------------------------------+ +package com.example.ratMatcher; + +import org.apache.rat.configuration.builders.AbstractBuilder; + +public class QSCBuilder extends AbstractBuilder. { + QSCMatcher build() { + return new QSCMatcher(getId()); + } +} ++------------------------------------------+ + +** Registering the builder for use in XML configuration + + In order to use the matcher in a Rat configuration it has to be registered with the system. This can be done by creating an XML configuration file with the builder specified and passing it + to the command line runner as a license ( "--licenses" option) file. The name of the matcher is "QSC" so "QSC" is also the xml element that the parser will accept in license definitions. + Since this is a joke license we should create a "Joke" family to contain any such licenses and a QSC license that uses the QSC matcher. The new configuration file now looks like: + ++------------------------------------------+ + + + + + + + + + + + + + + + + ++------------------------------------------+ + + If the license entry did not have an "id" attribute its id would be the same as the family. If it did not have a name attribute the name would be the same as the family. + +* A more complex example + + In many cases it is necessary to set properties on the matcher. So let's write a generalized version of the QSC matcher that accepts any 3 strings and triggers if all 3 are found + in the header. + ++------------------------------------------+ +package com.example.ratMatcher; + +import org.apache.commons.lang3.StringUtils; + +import org.apache.rat.analysis.IHeaders; +import org.apache.rat.analysis.matchers.AbstractHeaderMatcher; +import org.apache.rat.config.parameters.Component; +import org.apache.rat.config.parameters.ConfigComponent; + +@ConfigComponent(type = Component.Type.MATCHER, name = "TPM", desc = "Checks that the three string are found in the header") +public class TPMatcher extends AbstractHeaderMatcher { + + @ConfigComponent(type = Component.Type.PARAMETER, desc = "The first parameter" required = "true") + private final String one; + + @ConfigComponent(type = Component.Type.PARAMETER, desc = "The second parameter" required = "true") + private final String two; + + @ConfigComponent(type = Component.Type.PARAMETER, desc = "The third parameter" required = "true") + private final String three; + + public TPMatcher(String id, String one, String two, String three) { + super(id); + if (StringUtils.isEmpthy(one) || StringUtils.isEmpty(two) || StringUtils.isEmpty(three) { + throw new ConfigurationException( "None of the three properties (one, two, or three) may be empty"); + } + this.one = one; + this.two = two; + this.three = three; + } + + public String getOne() { return one; } + + public String getThree() { return two; } + + public String getTwo() { return three; } + + @Override + public boolean matches(IHeaders headers) { + String text = headers.prune() + return text.contains(one) && text.contains(two) && text.contains(three); + } +} ++------------------------------------------+ + + The ConfigComponents with the PARAMETER type indicate that the members specify properties of the component. The matcher must have a "get" method for each parameter and the builder + must have a corresponding "set" method. The names of the methods and the attributes in the XML parser can be changed by adding a 'name' attribute to the ConfigComponent. + + The builder now looks like: + ++------------------------------------------+ +package com.example.ratMatcher; + +import org.apache.rat.configuration.builders.AbstractBuilder; + +public class TPBuilder extends AbstractBuilder { + + private String one; + + private String two; + + private String three; + + TPMatcher build() { + return new TPMatcher(one, two, three); + } + + public TPBuilder setOne(String one) { this.one = one; } + + public TPBuilder setTwo(String two) { this.two = two; } + + public TPBuilder setThree(String three) { this.three = three; } +} ++------------------------------------------+ + + And the new configuration file looks like: + ++------------------------------------------+ + + + + + + + + + + + + + + + + + ++------------------------------------------+ + +* Embedded matchers. + + It is possible to create matchers that embed other matchers. The examples in the codebase are the + {{{https://github.com/apache/creadur-rat/blob/master/apache-rat-core/src/main/java/org/apache/rat/analysis/matchers/AndMatcher.java}All}}, + {{{https://github.com/apache/creadur-rat/blob/master/apache-rat-core/src/main/java/org/apache/rat/analysis/matchers/OrMatcher.java}Any}} and + {{{https://github.com/apache/creadur-rat/blob/master/apache-rat-core/src/main/java/org/apache/rat/analysis/matchers/NotMatcher.java}Not}} matchers and their associated builders. As an + example we will build a "Highlander" matcher that will be true if one and only one enclosed matcher is true; there can be only one. The Highlander matcher will extend + {{{https://github.com/apache/creadur-rat/blob/master/apache-rat-core/src/main/java/org/apache/rat/analysis/matchers/AbstractMatcherContainer}AbstractMatcherContainer}} which will handle the + enclosed resources and the option of reading text matchers from a file. + ++------------------------------------------+ +package com.example.ratMatcher; + +import org.apache.commons.lang3.StringUtils; + +import org.apache.rat.analysis.IHeaders; +import org.apache.rat.analysis.matchers.AbstractHeaderMatcher; +import org.apache.rat.config.parameters.Component; +import org.apache.rat.config.parameters.ConfigComponent; + +@ConfigComponent(type = Component.Type.MATCHER, desc = "Checks that there can be only one matching enclosed matcher") +public class HighlanderMatcher extends AbstractMatcherContainer { + + public HighlanderMatcher(String id, Collection enclosed, String resource) { + super(id, enclosed, resource); + } + + @Override + public boolean matches(IHeaders headers) { + boolean foundOne = false; + for (IHeaderMatcher matcher : getEnclosed()) { + if (matcher.matches(headers)) { + if (foundOne) { + return false; + } + foundOne = true; + } + } + return foundOne; + } +} ++------------------------------------------+ + + We create a simple builder that extends + {{{https://github.com/apache/creadur-rat/blob/master/apache-rat-core/src/main/java/org/apache/rat/configuration/builders/ChildContainerBuilder.java}ChildContainerBuilder}} + which will handle setting the id, enclosed matchers, and the resource. + ++------------------------------------------+ +package com.example.ratMatcher; + +import org.apache.rat.configuration.builders.AbstractBuilder; + +public class HighlanderBuilder extends ChildContainerBuilder { + + @Override + public Highlander build() { + return new Highlander(getId(), getEnclosed(), resource); + } ++------------------------------------------+ + +Add the above to the configuration and we have: + ++------------------------------------------+ + + + + + + + + + + + + + + + + + + + + + + + + ++------------------------------------------+ + + The HighlanderBuilder builds a Highlander object. The Highlander object is annotated with a ConfigComponent that does not specify the name, the system strips the "Matcher" fom the simple class name and lowercases + the first character, so the Highlander matcher has the name "highlander". + The last "license" entry does not have an id or name set so it will have the id of "Joke" and the name of "A joke license" inherited from the family. + + Since there is no "approved" section of the rat-conf all the licenses are assumed to be approved. + +