- * Each Arg contains an OptionGroup that contains the individual options that all resolve to the same option.
- * This allows us to deprecate options as we move forward in development.
+ * An enumeration of options that are recommended across all UIs. A UI may not implement some options if they are unsupportable
+ * within the UI.
+ * Each Arg contains:
+ *
+ *
An OptionGroup that contains the individual options that all resolve to the same option.
+ * This allows us to deprecate options as we move forward in development.
+ *
A {@code BiConsumer} that defines the process to configure the option in
+ * the {@code ArgumentContext.configuration}.
+ *
+ *
*/
public enum Arg {
-
///////////////////////// EDIT OPTIONS
/**
* Defines options to add copyright to files
@@ -79,7 +83,9 @@ public enum Arg {
.addOption(Option.builder().longOpt("edit-copyright").hasArg()
.desc("The copyright message to use in the license headers. Usually in the form of \"Copyright 2008 Foo\". "
+ "Only valid with --edit-license")
- .build())),
+ .build()),
+ Arg::doNotExecute
+ ),
/**
* Causes file updates to overwrite existing files.
@@ -93,7 +99,9 @@ public enum Arg {
.addOption(Option.builder().longOpt("edit-overwrite")
.desc("Forces any changes in files to be written directly to the source files so that new files are not created. "
+ "Only valid with --edit-license.")
- .build())),
+ .build()),
+ Arg::doNotExecute
+ ),
/**
* Defines options to add licenses to files
@@ -112,7 +120,9 @@ public enum Arg {
"Add the Apache-2.0 license header to any file with an unknown license that is not in the exclusion list. "
+ "By default new files will be created with the license header, "
+ "to force the modification of existing files use the --edit-overwrite option.").build()
- )),
+ ),
+ Arg::doNotExecute
+ ),
//////////////////////////// CONFIGURATION OPTIONS
/**
@@ -129,7 +139,9 @@ public enum Arg {
.deprecated(DeprecatedAttributes.builder().setSince("0.17").setForRemoval(true).setDescription(StdMsgs.useMsg("--config")).get())
.converter(Converters.FILE_CONVERTER)
.type(File.class)
- .build())),
+ .build()),
+ Arg::doNotExecute
+ ),
/**
* Group of options that skip the default configuration file
@@ -143,16 +155,21 @@ public enum Arg {
.setForRemoval(true)
.setDescription(StdMsgs.useMsg("--configuration-no-defaults")).get())
.desc("Ignore default configuration.")
- .build())),
+ .build()),
+ Arg::doNotExecute
+ ),
/**
* Option that adds approved licenses to the list
*/
LICENSES_APPROVED(new OptionGroup().addOption(Option.builder().longOpt("licenses-approved").hasArg().argName("LicenseID")
.desc("A comma separated list of approved License IDs. These licenses will be added to the list of approved licenses.")
- .converter(Converters.TEXT_LIST_CONVERTER)
- .type(String[].class)
- .build())),
+ .converter(Converters.TEXT_LIST_CONVERTER)
+ .type(String[].class)
+ .build()),
+ (context, selected) ->
+ context.getConfiguration().addApprovedLicenseIds(processArrayArg(context, selected))
+ ),
/**
* Option that adds approved licenses from a file
@@ -161,7 +178,9 @@ public enum Arg {
.desc("Name of file containing comma separated lists of approved License IDs.")
.converter(Converters.FILE_CONVERTER)
.type(File.class)
- .build())),
+ .build()),
+ (context, selected) ->
+ context.getConfiguration().addApprovedLicenseIds(processArrayFile(context, selected))),
/**
* Option that specifies approved license families
@@ -170,7 +189,8 @@ public enum Arg {
.desc("A comma separated list of approved license family IDs. These license families will be added to the list of approved license families.")
.converter(Converters.TEXT_LIST_CONVERTER)
.type(String[].class)
- .build())),
+ .build()),
+ (context, selected) -> context.getConfiguration().addApprovedLicenseCategories(processArrayArg(context, selected))),
/**
* Option that specifies approved license families from a file
@@ -179,7 +199,9 @@ public enum Arg {
.desc("Name of file containing comma separated lists of approved family IDs.")
.converter(Converters.FILE_CONVERTER)
.type(File.class)
- .build())),
+ .build()),
+ (context, selected) -> context.getConfiguration().addApprovedLicenseCategories(processArrayFile(context, selected))
+ ),
/**
* Option to remove licenses from the approved list
@@ -190,7 +212,8 @@ public enum Arg {
"Once licenses are removed they can not be added back.")
.converter(Converters.TEXT_LIST_CONVERTER)
.type(String[].class)
- .build())),
+ .build()),
+ (context, selected) -> context.getConfiguration().removeApprovedLicenseIds(processArrayArg(context, selected))),
/**
* Option to read a file licenses to be removed from the approved list.
@@ -201,7 +224,8 @@ public enum Arg {
.desc("Name of file containing comma separated lists of the denied license IDs. " +
"These licenses will be removed from the list of approved licenses. " +
"Once licenses are removed they can not be added back.")
- .build())),
+ .build()),
+ (context, selected) -> context.getConfiguration().removeApprovedLicenseIds(processArrayFile(context, selected))),
/**
* Option to list license families to remove from the approved list.
@@ -213,7 +237,8 @@ public enum Arg {
"Once license families are removed they can not be added back.")
.converter(Converters.TEXT_LIST_CONVERTER)
.type(String[].class)
- .build())),
+ .build()),
+ (context, selected) -> context.getConfiguration().removeApprovedLicenseCategories(processArrayArg(context, selected))),
/**
* Option to read a list of license families to remove from the approved list.
@@ -224,7 +249,8 @@ public enum Arg {
"Once license families are removed they can not be added back.")
.type(File.class)
.converter(Converters.FILE_CONVERTER)
- .build())),
+ .build()),
+ (context, selected) -> context.getConfiguration().removeApprovedLicenseCategories(processArrayFile(context, selected))),
/**
* Option to specify an acceptable number of various counters.
@@ -233,7 +259,14 @@ public enum Arg {
.desc("The acceptable maximum number for the specified counter. A value of '-1' specifies an unlimited number.")
.converter(Converters.COUNTER_CONVERTER)
.type(Pair.class)
- .build())),
+ .build()),
+ (context, selected) -> {
+ for (String arg : context.getCommandLine().getOptionValues(selected)) {
+ Pair pair = Converters.COUNTER_CONVERTER.apply(arg);
+ int limit = pair.getValue();
+ context.getConfiguration().getClaimValidator().setMax(pair.getKey(), limit < 0 ? Integer.MAX_VALUE : limit);
+ }
+ }),
/**
* Option to specify an acceptable number of various counters.
@@ -242,7 +275,13 @@ public enum Arg {
.desc("The minimum number for the specified counter.")
.converter(Converters.COUNTER_CONVERTER)
.type(Pair.class)
- .build())),
+ .build()),
+ (context, selected) -> {
+ for (String arg : context.getCommandLine().getOptionValues(selected)) {
+ Pair pair = Converters.COUNTER_CONVERTER.apply(arg);
+ context.getConfiguration().getClaimValidator().setMin(pair.getKey(), pair.getValue());
+ }
+ }),
////////////////// INPUT OPTIONS
/**
@@ -256,7 +295,13 @@ public enum Arg {
"argument is located.")
.converter(Converters.FILE_CONVERTER)
.type(File.class)
- .build())),
+ .build()),
+ (context, selected) -> {
+ File[] files = getParsedOptionValues(selected, context.getCommandLine());
+ for (File f : files) {
+ context.getConfiguration().addSource(f);
+ }
+ }),
/**
* Excludes files by expression
@@ -269,7 +314,13 @@ public enum Arg {
.build())
.addOption(Option.builder().longOpt("input-exclude").hasArgs().argName("Expression")
.desc("Excludes files matching .")
- .build())),
+ .build()),
+ (context, selected) -> {
+ String[] excludes = context.getCommandLine().getOptionValues(selected);
+ if (excludes != null) {
+ context.getConfiguration().addExcludedPatterns(Arrays.asList(excludes));
+ }
+ }),
/**
* Excludes files based on the contents of a file.
@@ -286,7 +337,17 @@ public enum Arg {
.argName("File").hasArg().type(File.class)
.converter(Converters.FILE_CONVERTER)
.desc("Reads entries from a file. Entries will be excluded from processing.")
- .build())),
+ .build()),
+ (context, selected) -> {
+ try {
+ File excludeFileName = context.getCommandLine().getParsedOptionValue(selected);
+ if (excludeFileName != null) {
+ context.getConfiguration().addExcludedPatterns(ExclusionUtils.asIterable(excludeFileName, "#"));
+ }
+ } catch (Exception e) {
+ throw ConfigurationException.from(e);
+ }
+ }),
/**
* Excludes files based on standard groupings.
*/
@@ -296,8 +357,12 @@ public enum Arg {
.desc("Excludes files defined in standard collections based on commonly occurring groups. " +
"Excludes any path matcher actions but DOES NOT exclude any file processor actions.")
.type(StandardCollection.class)
- .build())
- ),
+ .build()),
+ (context, selected) -> {
+ for (String s : context.getCommandLine().getOptionValues(selected)) {
+ context.getConfiguration().addExcludedCollection(StandardCollection.valueOf(s));
+ }
+ }),
/**
* Excludes files if they are smaller than the given threshold.
@@ -306,8 +371,20 @@ public enum Arg {
.addOption(Option.builder().longOpt("input-exclude-size").argName("Integer")
.hasArg().type(Integer.class)
.desc("Excludes files with sizes less than the number of bytes specified.")
- .build())
- ),
+ .build()),
+ (context, selected) -> {
+ try {
+ final int maxSize = context.getCommandLine().getParsedOptionValue(selected);
+ DocumentNameMatcher matcher = new DocumentNameMatcher(String.format("File size < %s bytes", maxSize),
+ (Predicate) documentName -> {
+ File f = new File(documentName.getName());
+ return f.isFile() && f.length() < maxSize;
+ });
+ context.getConfiguration().addExcludedMatcher(matcher);
+ } catch (Exception e) {
+ throw ConfigurationException.from(e);
+ }
+ }),
/**
* Excludes files by expression.
*/
@@ -319,8 +396,13 @@ public enum Arg {
.desc("Includes files matching . Will override excluded files.")
.deprecated(DeprecatedAttributes.builder().setForRemoval(true).setSince("0.17")
.setDescription(StdMsgs.useMsg("--input-include")).get())
- .build())
- ),
+ .build()),
+ (context, selected) -> {
+ String[] includes = context.getCommandLine().getOptionValues(selected);
+ if (includes != null) {
+ context.getConfiguration().addIncludedPatterns(Arrays.asList(includes));
+ }
+ }),
/**
* Includes files based on the contents of a file.
@@ -337,7 +419,17 @@ public enum Arg {
.desc("Reads entries from a file. Entries will be excluded from processing.")
.deprecated(DeprecatedAttributes.builder().setForRemoval(true).setSince("0.17")
.setDescription(StdMsgs.useMsg("--input-include-file")).get())
- .build())),
+ .build()),
+ (context, selected) -> {
+ try {
+ File includeFileName = context.getCommandLine().getParsedOptionValue(selected);
+ if (includeFileName != null) {
+ context.getConfiguration().addIncludedPatterns(ExclusionUtils.asIterable(includeFileName, "#"));
+ }
+ } catch (Exception e) {
+ throw ConfigurationException.from(e);
+ }
+ }),
/**
* Includes files based on standard groups.
@@ -353,8 +445,17 @@ public enum Arg {
.desc("Scans hidden directories.")
.deprecated(DeprecatedAttributes.builder().setForRemoval(true).setSince("0.17")
.setDescription(StdMsgs.useMsg("--input-include-std with 'HIDDEN_DIR' argument")).get()).build()
- )
- ),
+ ),
+ (context, selected) -> {
+ // display deprecation log if needed.
+ if (context.getCommandLine().hasOption("scan-hidden-directories")) {
+ context.getConfiguration().addIncludedCollection(StandardCollection.HIDDEN_DIR);
+ } else {
+ for (String s : context.getCommandLine().getOptionValues(selected)) {
+ context.getConfiguration().addIncludedCollection(StandardCollection.valueOf(s));
+ }
+ }
+ }),
/**
* Excludes files based on SCM exclusion file processing.
@@ -366,17 +467,31 @@ public enum Arg {
.desc("Parse SCM based exclusion files to exclude specified files and directories. " +
"This action can apply to any standard collection that implements a file processor.")
.type(StandardCollection.class)
- .build())
- ),
+ .build()),
+ (context, selected) -> {
+ StandardCollection[] collections = getParsedOptionValues(selected, context.getCommandLine());
+ final ReportConfiguration configuration = context.getConfiguration();
+ for (StandardCollection collection : collections) {
+ if (collection == StandardCollection.ALL) {
+ Arrays.asList(StandardCollection.values()).forEach(configuration::addExcludedFileProcessor);
+ Arrays.asList(StandardCollection.values()).forEach(configuration::addExcludedCollection);
+ } else {
+ configuration.addExcludedFileProcessor(collection);
+ configuration.addExcludedCollection(collection);
+ }
+ }
+ }),
/**
* Stop processing an input stream and declare an input file.
*/
DIR(new OptionGroup().addOption(Option.builder().option("d").longOpt("dir").hasArg()
- .type(File.class)
+ .type(File.class)
.desc("Used to indicate end of list when using options that take multiple arguments.").argName("DirOrArchive")
.deprecated(DeprecatedAttributes.builder().setForRemoval(true).setSince("0.17")
- .setDescription("Use the standard '--' to signal the end of arguments.").get()).build())),
+ .setDescription("Use the standard '--' to signal the end of arguments.").get()).build()),
+ Arg::doNotExecute
+ ),
/////////////// OUTPUT OPTIONS
/**
@@ -397,7 +512,22 @@ public enum Arg {
.setForRemoval(true)
.setDescription(StdMsgs.useMsg("--output-style with the 'xml' argument")).get())
.desc("forces XML output rather than the textual report.")
- .build())),
+ .build()),
+ (context, selected) -> {
+ String key = selected.getKey(); // is not null due to above isSelected()-call
+ if ("x".equals(key)) {
+ // display deprecated message.
+ context.getCommandLine().hasOption("x");
+ 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.getWorkingDirectory()));
+ }
+ }),
/**
* Specifies the license definitions that should be included in the output.
@@ -411,7 +541,14 @@ public enum Arg {
.desc("List the defined licenses.")
.converter(s -> LicenseSetFactory.LicenseFilter.valueOf(s.toUpperCase()))
.deprecated(DeprecatedAttributes.builder().setSince("0.17").setForRemoval(true).setDescription(StdMsgs.useMsg("--output-licenses")).get())
- .build())),
+ .build()),
+ (context, selected) -> {
+ try {
+ context.getConfiguration().listLicenses(context.getCommandLine().getParsedOptionValue(selected));
+ } catch (ParseException e) {
+ context.logParseException(e, selected, Defaults.LIST_LICENSES);
+ }
+ }),
/**
* Specifies the license families that should be included in the output.
@@ -425,7 +562,14 @@ public enum Arg {
.desc("List the defined license families.")
.converter(s -> LicenseSetFactory.LicenseFilter.valueOf(s.toUpperCase()))
.deprecated(DeprecatedAttributes.builder().setSince("0.17").setForRemoval(true).setDescription(StdMsgs.useMsg("--output-families")).get())
- .build())),
+ .build()),
+ (context, selected) -> {
+ try {
+ context.getConfiguration().listFamilies(context.getCommandLine().getParsedOptionValue(selected));
+ } catch (ParseException e) {
+ context.logParseException(e, selected, Defaults.LIST_FAMILIES);
+ }
+ }),
/**
* Specifies the log level to log messages at.
@@ -434,14 +578,25 @@ public enum Arg {
.hasArg().argName("LogLevel")
.desc("Sets the log level.")
.converter(s -> Log.Level.valueOf(s.toUpperCase()))
- .build())),
+ .build()),
+ (context, selected) -> {
+ Log dLog = DefaultLog.getInstance();
+ try {
+ dLog.setLevel(context.getCommandLine().getParsedOptionValue(selected));
+ } catch (ParseException e) {
+ logParseException(DefaultLog.getInstance(), e, selected, context.getCommandLine(), dLog.getLevel());
+ }
+ }),
/**
* Specifies that the run should not perform any updates to files.
*/
DRY_RUN(new OptionGroup().addOption(Option.builder().longOpt("dry-run")
.desc("If set do not update the files but generate the reports.")
- .build())),
+ .build()),
+ (context, selected) ->
+ context.getConfiguration().setDryRun(true)
+ ),
/**
* Specifies where the output should be written.
@@ -457,7 +612,21 @@ public enum Arg {
.desc("Define the output file where to write a report to.")
.type(File.class)
.converter(Converters.FILE_CONVERTER)
- .build())),
+ .build()),
+ (context, selected) -> {
+ try {
+ File file = context.getCommandLine().getParsedOptionValue(selected);
+ File parent = file.getParentFile();
+ if (!parent.mkdirs() && !parent.isDirectory()) {
+ DefaultLog.getInstance().error("Could not create report parent directory " + file);
+ }
+ context.getConfiguration().setOut(file);
+ } catch (ParseException e) {
+ // we write to system out by default.
+ context.logParseException(e, selected, "System.out");
+ context.getConfiguration().setOut(ReportConfiguration.SYSTEM_OUT);
+ }
+ }),
/**
* Specifies the level of reporting detail for archive files.
@@ -466,7 +635,15 @@ public enum Arg {
.addOption(Option.builder().longOpt("output-archive").hasArg().argName("ProcessingType")
.desc("Specifies the level of detail in ARCHIVE file reporting.")
.converter(s -> ReportConfiguration.Processing.valueOf(s.toUpperCase()))
- .build())),
+ .build()),
+ (context, selected) -> {
+ try {
+ context.getConfiguration().setArchiveProcessing(context.getCommandLine().getParsedOptionValue(selected));
+ } catch (ParseException e) {
+ context.logParseException(e, selected, Defaults.ARCHIVE_PROCESSING);
+ }
+ }
+ ),
/**
* Specifies the level of reporting detail for standard files.
@@ -475,51 +652,66 @@ public enum Arg {
.addOption(Option.builder().longOpt("output-standard").hasArg().argName("ProcessingType")
.desc("Specifies the level of detail in STANDARD file reporting.")
.converter(s -> ReportConfiguration.Processing.valueOf(s.toUpperCase()))
- .build())),
+ .build()),
+ (context, selected) -> {
+ try {
+ context.getConfiguration().setStandardProcessing(context.getCommandLine().getParsedOptionValue(selected));
+ } catch (ParseException e) {
+ context.logParseException(e, selected, Defaults.STANDARD_PROCESSING);
+ }
+ }),
/**
* Provide license definition listing of registered licenses.
*/
HELP_LICENSES(new OptionGroup()
.addOption(Option.builder().longOpt("help-licenses") //
- .desc("Print information about registered licenses.").build()));
+ .desc("Print information about registered licenses.").build()),
+ Arg::doNotExecute
+ );
- /** The option group for the argument */
+ /**
+ * The option group for the argument
+ */
private final OptionGroup group;
+ /**
+ * The BiConsumer to apply the option to update the state of the context.configuration.
+ */
+ private final BiConsumer process;
+
+
+ private static void doNotExecute(final ArgumentContext context, final Option selected) {
+ throw new ImplementationException(String.format("'%s' should not be executed directly", selected));
+ }
+
/**
* Creates an Arg from an Option group.
*
* @param group The option group.
*/
- Arg(final OptionGroup group) {
+ Arg(final OptionGroup group, final BiConsumer process) {
this.group = group;
+ this.process = process;
}
/**
- * Determines if the group has a selected element.
- *
- * @return {@code true} if the group has a selected element.
+ * Executes the process associated with this Arg if the collection has an Option from this group selected.
+ * @param context the ArgumentContext that is being processed.
+ * @param optionCollection the OptionCollection that is available.
*/
- public boolean isSelected() {
- return group.getSelected() != null;
+ private void execute(final ArgumentContext context, final UIOptionCollection> optionCollection) {
+ optionCollection.getSelected(this)
+ .ifPresent(selected -> this.process.accept(context, selected));
}
/**
- * Gets the select element from the group.
+ * Determines if all the options have been removed from this argument.
*
- * @return the selected element or {@code null} if no element is selected.
- */
- public Option getSelected() {
- String s = group.getSelected();
- if (s != null) {
- for (Option result : group.getOptions()) {
- if (result.getKey().equals(s)) {
- return result;
- }
- }
- }
- return null;
+ * @return {@code true} if all the options have been removed from this argument.
+ */
+ public boolean isEmpty() {
+ return this.group().getOptions().isEmpty();
}
/**
@@ -538,14 +730,6 @@ public Option find(final String key) {
throw new IllegalArgumentException("Can not find " + key);
}
- /**
- * Gets the default value for this arg.
- * @return default value of this arg.
- */
- public String defaultValue() {
- return DEFAULT_VALUES.get(this);
- }
-
/**
* Gets the group for this arg.
*
@@ -558,20 +742,25 @@ public OptionGroup group() {
/**
* Returns the first non-deprecated option from the group.
*
- * @return the first non-deprecated option or {@code null} if no non-deprecated option is available.
+ * @return the first non-deprecated option or, if no non-deprecated option is available, the first option.
*/
public Option option() {
+ Option first = null;
for (Option result : group.getOptions()) {
+ if (first == null) {
+ first = result;
+ }
if (!result.isDeprecated()) {
return result;
}
}
- return null;
+ return first;
}
/**
* Gets the full set of options.
- * @return the full set of options for this Arg.
+ *
+ * @return the full set of options for this Arg.
*/
public static Options getOptions() {
Options options = new Options();
@@ -586,29 +775,55 @@ public static Options getOptions() {
*
* @param context the context to work with.
*/
- private static void processEditArgs(final ArgumentContext context) {
- if (EDIT_ADD.isSelected()) {
- context.getCommandLine().hasOption(Arg.EDIT_ADD.getSelected());
- boolean force = EDIT_OVERWRITE.isSelected();
+ private static void processEditArgs(final ArgumentContext context, final UIOptionCollection> optionCollection) {
+ optionCollection.getSelected(EDIT_ADD).ifPresent(option -> {
+ // prints deprecation
+ context.getCommandLine().hasOption(option);
+ boolean force = optionCollection.isSelected(EDIT_OVERWRITE);
if (force) {
- context.getCommandLine().hasOption(EDIT_OVERWRITE.getSelected());
+ // prints deprecation
+ optionCollection.getSelected(EDIT_OVERWRITE).ifPresent(context.getCommandLine()::hasOption);
}
context.getConfiguration().setAddLicenseHeaders(force ? AddLicenseHeaders.FORCED : AddLicenseHeaders.TRUE);
- if (EDIT_COPYRIGHT.isSelected()) {
- context.getConfiguration().setCopyrightMessage(context.getCommandLine().getOptionValue(EDIT_COPYRIGHT.getSelected()));
- }
+ optionCollection.getSelected(EDIT_COPYRIGHT).
+ ifPresent(editOption -> context.getConfiguration().setCopyrightMessage(context.getCommandLine().getOptionValue(editOption)));
+ });
+ }
+
+ /**
+ * Gets the list of Strings that are arguments for the option.
+ * @param context the ArgumentContext containing the command line.
+ * @param selected the selected option.
+ * @return the list of Strings that are aguments.
+ */
+ private static List processArrayArg(final ArgumentContext context, final Option selected) {
+ try {
+ return Arrays.asList(context.getCommandLine().getParsedOptionValue(selected));
+ } catch (ParseException e) {
+ throw new ConfigurationException(e);
}
}
- private static List processArrayArg(final ArgumentContext context, final Arg arg) throws ParseException {
- String[] ids = context.getCommandLine().getParsedOptionValue(arg.getSelected());
- return Arrays.asList(ids);
+ /**
+ * parses the option as a file.
+ * @param context the Argument context that provides the command line.
+ * @param selected the selected option.
+ * @return Option as a file.
+ */
+ private static File commandLineFile(final ArgumentContext context, final Option selected) throws ParseException {
+ return context.getCommandLine().getParsedOptionValue(selected);
}
- private static List processArrayFile(final ArgumentContext context, final Arg arg) throws ParseException {
+ /**
+ * parses lines with comma separated tokens from a file and returns the entire collection of tokens as a list of strings.
+ * @param context the Argument context that provides the command line.
+ * @param selected the selected option.
+ * @return the list of strings parsed from the file.
+ */
+ private static List processArrayFile(final ArgumentContext context, final Option selected) {
List result = new ArrayList<>();
- File file = context.getCommandLine().getParsedOptionValue(arg.getSelected());
- try (InputStream in = Files.newInputStream(file.toPath())) {
+ try (InputStream in = Files.newInputStream(commandLineFile(context, selected)
+ .toPath())) {
for (String line : IOUtils.readLines(in, StandardCharsets.UTF_8)) {
String[] ids = Converters.TEXT_LIST_CONVERTER.apply(line);
result.addAll(Arrays.asList(ids));
@@ -616,6 +831,9 @@ private static List processArrayFile(final ArgumentContext context, fina
return result;
} catch (IOException e) {
throw new ConfigurationException(e);
+
+ } catch (ParseException e) {
+ throw ConfigurationException.from(e);
}
}
@@ -625,64 +843,28 @@ private static List processArrayFile(final ArgumentContext context, fina
* @param context the context to process.
* @throws ConfigurationException if configuration files can not be read.
*/
- private static void processConfigurationArgs(final ArgumentContext context) throws ConfigurationException {
- try {
- Defaults.Builder defaultBuilder = Defaults.builder();
- if (CONFIGURATION.isSelected()) {
- File[] files = CONFIGURATION.getParsedOptionValues(context.getCommandLine());
- for (File file : files) {
- defaultBuilder.add(file);
- }
- }
- if (CONFIGURATION_NO_DEFAULTS.isSelected()) {
- // display deprecation log if needed.
- context.getCommandLine().hasOption(CONFIGURATION_NO_DEFAULTS.getSelected());
- defaultBuilder.noDefault();
- }
- context.getConfiguration().setFrom(defaultBuilder.build());
+ private static void processConfigurationArgs(final ArgumentContext context, final UIOptionCollection> optionCollection) throws ConfigurationException {
- if (FAMILIES_APPROVED.isSelected()) {
- context.getConfiguration().addApprovedLicenseCategories(processArrayArg(context, FAMILIES_APPROVED));
- }
- if (FAMILIES_APPROVED_FILE.isSelected()) {
- context.getConfiguration().addApprovedLicenseCategories(processArrayFile(context, FAMILIES_APPROVED_FILE));
- }
- if (FAMILIES_DENIED.isSelected()) {
- context.getConfiguration().removeApprovedLicenseCategories(processArrayArg(context, FAMILIES_DENIED));
- }
- if (FAMILIES_DENIED_FILE.isSelected()) {
- context.getConfiguration().removeApprovedLicenseCategories(processArrayFile(context, FAMILIES_DENIED_FILE));
- }
-
- if (LICENSES_APPROVED.isSelected()) {
- context.getConfiguration().addApprovedLicenseIds(processArrayArg(context, LICENSES_APPROVED));
- }
+ Defaults.Builder defaultBuilder = Defaults.builder();
- if (LICENSES_APPROVED_FILE.isSelected()) {
- context.getConfiguration().addApprovedLicenseIds(processArrayFile(context, LICENSES_APPROVED_FILE));
- }
- if (LICENSES_DENIED.isSelected()) {
- context.getConfiguration().removeApprovedLicenseIds(processArrayArg(context, LICENSES_DENIED));
- }
-
- if (LICENSES_DENIED_FILE.isSelected()) {
- context.getConfiguration().removeApprovedLicenseIds(processArrayFile(context, LICENSES_DENIED_FILE));
- }
- if (COUNTER_MAX.isSelected()) {
- for (String arg : context.getCommandLine().getOptionValues(COUNTER_MAX.getSelected())) {
- Pair pair = Converters.COUNTER_CONVERTER.apply(arg);
- int limit = pair.getValue();
- context.getConfiguration().getClaimValidator().setMax(pair.getKey(), limit < 0 ? Integer.MAX_VALUE : limit);
- }
- }
- if (COUNTER_MIN.isSelected()) {
- for (String arg : context.getCommandLine().getOptionValues(COUNTER_MIN.getSelected())) {
- Pair pair = Converters.COUNTER_CONVERTER.apply(arg);
- context.getConfiguration().getClaimValidator().setMin(pair.getKey(), pair.getValue());
- }
- }
- } catch (Exception e) {
- throw ConfigurationException.from(e);
+ optionCollection.getSelected(CONFIGURATION).ifPresent(
+ selected -> {
+ File[] files = getParsedOptionValues(selected, context.getCommandLine());
+ for (File file : files) {
+ defaultBuilder.add(file);
+ }
+ });
+ optionCollection.getSelected(CONFIGURATION_NO_DEFAULTS).ifPresent(selected -> {
+ // display deprecation log if needed.
+ context.getCommandLine().hasOption(selected);
+ defaultBuilder.noDefault();
+ });
+ context.getConfiguration().setFrom(defaultBuilder.build());
+
+ for (Arg arg : new Arg[]{FAMILIES_APPROVED, FAMILIES_APPROVED_FILE, FAMILIES_DENIED, FAMILIES_DENIED_FILE,
+ LICENSES_APPROVED, LICENSES_APPROVED_FILE, LICENSES_DENIED, LICENSES_DENIED_FILE,
+ COUNTER_MAX, COUNTER_MIN}) {
+ arg.execute(context, optionCollection);
}
}
@@ -692,91 +874,21 @@ private static void processConfigurationArgs(final ArgumentContext context) thro
* @param context the context to work in.
* @throws ConfigurationException if an exclude file can not be read.
*/
- private static void processInputArgs(final ArgumentContext context) throws ConfigurationException {
- try {
- if (SOURCE.isSelected()) {
- File[] files = SOURCE.getParsedOptionValues(context.getCommandLine());
- for (File f : files) {
- context.getConfiguration().addSource(f);
- }
- }
- // TODO when include/exclude processing is updated check calling methods to ensure that all specified
- // directories are handled in the list of directories.
- if (EXCLUDE.isSelected()) {
- String[] excludes = context.getCommandLine().getOptionValues(EXCLUDE.getSelected());
- if (excludes != null) {
- context.getConfiguration().addExcludedPatterns(Arrays.asList(excludes));
- }
- }
- if (EXCLUDE_FILE.isSelected()) {
- File excludeFileName = context.getCommandLine().getParsedOptionValue(EXCLUDE_FILE.getSelected());
- if (excludeFileName != null) {
- context.getConfiguration().addExcludedPatterns(ExclusionUtils.asIterable(excludeFileName, "#"));
- }
- }
- if (EXCLUDE_STD.isSelected()) {
- for (String s : context.getCommandLine().getOptionValues(EXCLUDE_STD.getSelected())) {
- context.getConfiguration().addExcludedCollection(StandardCollection.valueOf(s));
- }
- }
- if (EXCLUDE_PARSE_SCM.isSelected()) {
- StandardCollection[] collections = EXCLUDE_PARSE_SCM.getParsedOptionValues(context.getCommandLine());
- final ReportConfiguration configuration = context.getConfiguration();
- for (StandardCollection collection : collections) {
- if (collection == StandardCollection.ALL) {
- Arrays.asList(StandardCollection.values()).forEach(configuration::addExcludedFileProcessor);
- Arrays.asList(StandardCollection.values()).forEach(configuration::addExcludedCollection);
- } else {
- configuration.addExcludedFileProcessor(collection);
- configuration.addExcludedCollection(collection);
- }
- }
- }
- if (EXCLUDE_SIZE.isSelected()) {
- final int maxSize = EXCLUDE_SIZE.getParsedOptionValue(context.getCommandLine());
- DocumentNameMatcher matcher = new DocumentNameMatcher(String.format("File size < %s bytes", maxSize),
- (Predicate) documentName -> {
- File f = new File(documentName.getName());
- return f.isFile() && f.length() < maxSize;
- });
- context.getConfiguration().addExcludedMatcher(matcher);
- }
- if (INCLUDE.isSelected()) {
- String[] includes = context.getCommandLine().getOptionValues(INCLUDE.getSelected());
- if (includes != null) {
- context.getConfiguration().addIncludedPatterns(Arrays.asList(includes));
- }
- }
- if (INCLUDE_FILE.isSelected()) {
- File includeFileName = context.getCommandLine().getParsedOptionValue(INCLUDE_FILE.getSelected());
- if (includeFileName != null) {
- context.getConfiguration().addIncludedPatterns(ExclusionUtils.asIterable(includeFileName, "#"));
- }
- }
- if (INCLUDE_STD.isSelected()) {
- Option selected = INCLUDE_STD.getSelected();
- // display deprecation log if needed.
- if (context.getCommandLine().hasOption("scan-hidden-directories")) {
- context.getConfiguration().addIncludedCollection(StandardCollection.HIDDEN_DIR);
- } else {
- for (String s : context.getCommandLine().getOptionValues(selected)) {
- context.getConfiguration().addIncludedCollection(StandardCollection.valueOf(s));
- }
- }
- }
- } catch (Exception e) {
- throw ConfigurationException.from(e);
+ private static void processInputArgs(final ArgumentContext context, final UIOptionCollection> optionCollection) throws ConfigurationException {
+ for (Arg arg : new Arg[]{SOURCE, EXCLUDE, EXCLUDE_FILE, EXCLUDE_STD, EXCLUDE_PARSE_SCM, EXCLUDE_SIZE,
+ INCLUDE, INCLUDE_FILE, INCLUDE_STD}) {
+ arg.execute(context, optionCollection);
}
}
/**
* Logs a ParseException as a warning.
*
- * @param log the Log to write to
+ * @param log the Log to write to
* @param exception the parse exception to log
- * @param opt the option being processed
- * @param cl the command line being processed
- * @param defaultValue The default value the option is being set to.
+ * @param opt the option being processed
+ * @param cl the command line being processed
+ * @param defaultValue The default value the option is being set to.
*/
private static void logParseException(final Log log, final ParseException exception, final Option opt, final CommandLine cl, final Object defaultValue) {
log.warn(format("Invalid %s specified: %s ", opt.getOpt(), cl.getOptionValue(opt)));
@@ -787,17 +899,10 @@ private static void logParseException(final Log log, final ParseException except
/**
* Process the log level setting.
*
- * @param commandLine The command line to process.
+ * @param context The argument context
*/
- public static void processLogLevel(final CommandLine commandLine) {
- if (LOG_LEVEL.getSelected() != null) {
- Log dLog = DefaultLog.getInstance();
- try {
- dLog.setLevel(commandLine.getParsedOptionValue(LOG_LEVEL.getSelected()));
- } catch (ParseException e) {
- logParseException(DefaultLog.getInstance(), e, LOG_LEVEL.getSelected(), commandLine, dLog.getLevel());
- }
- }
+ public static void processLogLevel(final ArgumentContext context, final UIOptionCollection> optionCollection) throws ConfigurationException {
+ LOG_LEVEL.execute(context, optionCollection);
}
/**
@@ -806,12 +911,12 @@ public static void processLogLevel(final CommandLine commandLine) {
* @param context the context in which to process the args.
* @throws ConfigurationException on error
*/
- public static void processArgs(final ArgumentContext context) throws ConfigurationException {
+ public static void processArgs(final ArgumentContext context, final UIOptionCollection> optionCollection) throws ConfigurationException {
Converters.FILE_CONVERTER.setWorkingDirectory(context.getWorkingDirectory());
- processOutputArgs(context);
- processEditArgs(context);
- processInputArgs(context);
- processConfigurationArgs(context);
+ processOutputArgs(context, optionCollection);
+ processEditArgs(context, optionCollection);
+ processInputArgs(context, optionCollection);
+ processConfigurationArgs(context, optionCollection);
}
/**
@@ -819,69 +924,9 @@ public static void processArgs(final ArgumentContext context) throws Configurati
*
* @param context the context in which to process the args.
*/
- private static void processOutputArgs(final ArgumentContext context) {
- context.getConfiguration().setDryRun(DRY_RUN.isSelected());
-
- if (OUTPUT_FAMILIES.isSelected()) {
- try {
- context.getConfiguration().listFamilies(context.getCommandLine().getParsedOptionValue(OUTPUT_FAMILIES.getSelected()));
- } catch (ParseException e) {
- context.logParseException(e, OUTPUT_FAMILIES.getSelected(), Defaults.LIST_FAMILIES);
- }
- }
-
- if (OUTPUT_LICENSES.isSelected()) {
- try {
- context.getConfiguration().listLicenses(context.getCommandLine().getParsedOptionValue(OUTPUT_LICENSES.getSelected()));
- } catch (ParseException e) {
- context.logParseException(e, OUTPUT_LICENSES.getSelected(), Defaults.LIST_LICENSES);
- }
- }
-
- if (OUTPUT_ARCHIVE.isSelected()) {
- try {
- context.getConfiguration().setArchiveProcessing(context.getCommandLine().getParsedOptionValue(OUTPUT_ARCHIVE.getSelected()));
- } catch (ParseException e) {
- context.logParseException(e, OUTPUT_ARCHIVE.getSelected(), Defaults.ARCHIVE_PROCESSING);
- }
- }
-
- if (OUTPUT_STANDARD.isSelected()) {
- try {
- context.getConfiguration().setStandardProcessing(context.getCommandLine().getParsedOptionValue(OUTPUT_STANDARD.getSelected()));
- } catch (ParseException e) {
- context.logParseException(e, OUTPUT_STANDARD.getSelected(), Defaults.STANDARD_PROCESSING);
- }
- }
-
- if (OUTPUT_FILE.isSelected()) {
- try {
- File file = context.getCommandLine().getParsedOptionValue(OUTPUT_FILE.getSelected());
- File parent = file.getParentFile();
- if (!parent.mkdirs() && !parent.isDirectory()) {
- DefaultLog.getInstance().error("Could not create report parent directory " + file);
- }
- context.getConfiguration().setOut(file);
- } catch (ParseException e) {
- context.logParseException(e, OUTPUT_FILE.getSelected(), "System.out");
- context.getConfiguration().setOut((IOSupplier) null);
- }
- }
-
- if (OUTPUT_STYLE.isSelected()) {
- String selected = OUTPUT_STYLE.getSelected().getKey(); // is not null due to above isSelected()-call
- if ("x".equals(selected)) {
- // display deprecated message.
- context.getCommandLine().hasOption("x");
- context.getConfiguration().setStyleSheet(StyleSheets.getStyleSheet("xml"));
- } else {
- String[] style = context.getCommandLine().getOptionValues(OUTPUT_STYLE.getSelected());
- 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]));
- }
+ private static void processOutputArgs(final ArgumentContext context, final UIOptionCollection> optionCollection) throws ConfigurationException {
+ for (Arg arg : new Arg[]{DRY_RUN, OUTPUT_FAMILIES, OUTPUT_LICENSES, OUTPUT_ARCHIVE, OUTPUT_STANDARD, OUTPUT_FILE, OUTPUT_STYLE}) {
+ arg.execute(context, optionCollection);
}
}
@@ -898,27 +943,9 @@ public static void reset() {
}
}
- /**
- * Finds the Arg that an Option is in.
- *
- * @param optionToFind the Option to locate.
- * @return The Arg or {@code null} if no Arg is found.
- */
- public static Arg findArg(final Option optionToFind) {
- if (optionToFind != null) {
- for (Arg arg : Arg.values()) {
- for (Option candidate : arg.group.getOptions()) {
- if (optionToFind.equals(candidate)) {
- return arg;
- }
- }
- }
- }
- return null;
- }
-
/**
* Finds the Arg that contains an Option with the specified key.
+ *
* @param key the key for the Option to locate.
* @return The Arg or {@code null} if no Arg is found.
*/
@@ -935,31 +962,18 @@ public static Arg findArg(final String key) {
return null;
}
- private T getParsedOptionValue(final CommandLine commandLine) throws ParseException {
- return commandLine.getParsedOptionValue(this.getSelected());
- }
-
- private String getOptionValue(final CommandLine commandLine) {
- return commandLine.getOptionValue(this.getSelected());
- }
-
- private String[] getOptionValues(final CommandLine commandLine) {
- return commandLine.getOptionValues(this.getSelected());
- }
-
- private T[] getParsedOptionValues(final CommandLine commandLine) {
- Option option = getSelected();
+ private static T[] getParsedOptionValues(final Option selected, final CommandLine commandLine) {
try {
- Class extends T> clazz = (Class extends T>) option.getType();
- String[] values = commandLine.getOptionValues(option);
+ Class extends T> clazz = (Class extends T>) selected.getType();
+ String[] values = commandLine.getOptionValues(selected);
T[] result = (T[]) Array.newInstance(clazz, values.length);
for (int i = 0; i < values.length; i++) {
- result[i] = clazz.cast(option.getConverter().apply(values[i]));
+ result[i] = clazz.cast(selected.getConverter().apply(values[i]));
}
return result;
} catch (Throwable t) {
- throw new ConfigurationException(format("'%s' converter for %s '%s' does not produce a class of type %s", this,
- option.getKey(), option.getConverter().getClass().getName(), option.getType()), t);
+ throw new ConfigurationException(format("'%s' converter for %s '%s' does not produce a class of type %s", selected,
+ selected.getKey(), selected.getConverter().getClass().getName(), selected.getType()), t);
}
}
@@ -981,18 +995,4 @@ public static String useMsg(final String name) {
return format("Use %s instead.", name);
}
}
-
- /**
- * The default values description map
- */
- private static final Map DEFAULT_VALUES = new HashMap<>();
-
- static {
- DEFAULT_VALUES.put(OUTPUT_FILE, "System.out");
- DEFAULT_VALUES.put(LOG_LEVEL, Log.Level.WARN.name());
- DEFAULT_VALUES.put(OUTPUT_ARCHIVE, Defaults.ARCHIVE_PROCESSING.name());
- DEFAULT_VALUES.put(OUTPUT_STANDARD, Defaults.STANDARD_PROCESSING.name());
- DEFAULT_VALUES.put(OUTPUT_LICENSES, Defaults.LIST_LICENSES.name());
- DEFAULT_VALUES.put(OUTPUT_FAMILIES, Defaults.LIST_FAMILIES.name());
- }
}
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 ea672bc8d..d4374a57b 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
@@ -25,6 +25,7 @@
import org.apache.commons.cli.ParseException;
import org.apache.rat.ReportConfiguration;
import org.apache.rat.document.DocumentName;
+import org.apache.rat.ui.UIOptionCollection;
import org.apache.rat.utils.DefaultLog;
import static java.lang.String.format;
@@ -33,7 +34,7 @@
* Provides the context necessary to process various arguments.
* @since 0.17
*/
-public class ArgumentContext {
+public final class ArgumentContext {
/** The report configuration that is being built */
private final ReportConfiguration configuration;
/** The command line that is building the configuration */
@@ -65,8 +66,8 @@ public ArgumentContext(final File workingDirectory, final CommandLine commandLin
/**
* Process the arguments specified in this context.
*/
- public void processArgs() {
- Arg.processArgs(this);
+ public void processArgs(final UIOptionCollection> uiOptionCollection) {
+ Arg.processArgs(this, uiOptionCollection);
}
/**
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 dc661b6f7..6cfeffc0d 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;
@@ -174,6 +187,9 @@ public void reportExclusions(final Appendable appendable) throws IOException {
for (StandardCollection sc : fileProcessors) {
appendable.append(format("Processing exclude file from %s.%n", sc.name()));
}
+ for (DocumentNameMatcher nameMatcher : excludedPaths) {
+ appendable.append(format("Excluding %s.%n", nameMatcher.toString()));
+ }
}
@@ -334,4 +350,73 @@ 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":
+ excludedPaths.add(new DocumentNameMatcher(attributes.get("name"),
+ (Predicate) x -> {
+ throw new NotImplementedException("Deserialized ExclusionProcessor can not evaluate paths");
+ }));
+ break;
+ }
+
+ }
+ }
+ }
}
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..51ef1c1a8 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
@@ -191,7 +191,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 +217,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++) {
diff --git a/apache-rat-core/src/main/java/org/apache/rat/help/AbstractHelp.java b/apache-rat-core/src/main/java/org/apache/rat/help/AbstractHelp.java
index eff2b49a8..e7a8e3363 100644
--- a/apache-rat-core/src/main/java/org/apache/rat/help/AbstractHelp.java
+++ b/apache-rat-core/src/main/java/org/apache/rat/help/AbstractHelp.java
@@ -30,9 +30,9 @@
import org.apache.commons.cli.Options;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.text.WordUtils;
+import org.apache.rat.CLIOptionCollection;
import org.apache.rat.OptionCollection;
import org.apache.rat.VersionInfo;
-import org.apache.rat.commandline.Arg;
import static java.lang.String.format;
@@ -185,8 +185,7 @@ protected StringBuffer renderOptions(final StringBuffer sb, final int width, fin
optBuf.append(END_OF_OPTION_MSG);
}
// check for default value
- Arg arg = Arg.findArg(option);
- String defaultValue = arg == null ? null : arg.defaultValue();
+ String defaultValue = CLIOptionCollection.INSTANCE.defaultValue(option);
if (defaultValue != null) {
optBuf.append(format(" (Default value = %s)", defaultValue));
}
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..07f278918 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,18 @@
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.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 +126,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);
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/main/java/org/apache/rat/ui/ArgumentTracker.java b/apache-rat-core/src/main/java/org/apache/rat/ui/ArgumentTracker.java
new file mode 100644
index 000000000..3ecab294d
--- /dev/null
+++ b/apache-rat-core/src/main/java/org/apache/rat/ui/ArgumentTracker.java
@@ -0,0 +1,231 @@
+/*
+ * 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.ui;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.function.BiConsumer;
+
+import org.apache.commons.cli.Option;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.rat.commandline.Arg;
+import org.apache.rat.utils.CasedString;
+import org.apache.rat.utils.DefaultLog;
+import org.apache.rat.utils.Log;
+
+/**
+ * Tracks Arg values that are set and their values for conversion from native UI to
+ * Apache Commons command line values.
+ */
+public final class ArgumentTracker {
+
+ /**
+ * List of deprecated arguments and their deprecation notice.
+ */
+ private final Map deprecatedArgs = new HashMap<>();
+
+ /**
+ * A map of CLI-based arguments to values.
+ */
+ private final Map> args = new HashMap<>();
+
+ /**
+ * The arguments understood by the UI for the current report execution.
+ * @param optionCollection The AbstractOptionCollection for this UI.
+ */
+ public ArgumentTracker(final UIOptionCollection> optionCollection) {
+ for (UIOption> abstractOption : optionCollection.getMappedOptions().toList()) {
+ if (abstractOption.isDeprecated()) {
+ deprecatedArgs.put(abstractOption.getName(),
+ String.format("Use of deprecated option '%s'. %s", abstractOption.getName(), abstractOption.getDeprecated()));
+ }
+ }
+ }
+
+ /**
+ * Extract the core name from the option. This is the {@link Option#getLongOpt()} if defined, otherwise
+ * the {@link Option#getOpt()}.
+ * @param option the commons cli option.
+ * @return the common cli based name.
+ */
+ public static String extractKey(final Option option) {
+ return StringUtils.defaultIfBlank(option.getLongOpt(), option.getOpt());
+ }
+
+ /**
+ * Generates the CasedString for the specified option.
+ * @param option the option to extract the name for.
+ * @return the CasedString in KEBAB format.
+ */
+ public static CasedString extractName(final Option option) {
+ return new CasedString(CasedString.StringCase.KEBAB, ArgumentTracker.extractKey(option));
+ }
+
+ /**
+ * Gets the list of arguments prepared for the CLI code to parse.
+ * @return the List of arguments for the CLI command line.
+ */
+ public List args() {
+ final List result = new ArrayList<>();
+ for (Map.Entry> entry : args.entrySet()) {
+ result.add("--" + entry.getKey());
+ result.addAll(entry.getValue().stream().filter(Objects::nonNull).toList());
+ }
+ return result;
+ }
+
+ /**
+ * Applies the consumer to each arg and list in turn.
+ */
+ public void apply(final BiConsumer> consumer) {
+ args.forEach((key, value) -> consumer.accept(key, new ArrayList<>(value)));
+ }
+
+ /**
+ * Validate that the option is defined in Args and has not already been set.
+ * This check will verify tha only one of the keys in the group can be set.
+ * @param key the key to check
+ * @return true if the key may be set.
+ */
+ private boolean validateSet(final String key) {
+ final Arg arg = Arg.findArg(key);
+ if (arg != null) {
+ final Option opt = arg.find(key);
+ final Option main = arg.option();
+ if (opt.isDeprecated()) {
+ args.remove(extractKey(main));
+ // deprecated options must be explicitly set so let it go.
+ return true;
+ }
+ // non-deprecated options may have default so ignore it if another option has already been set.
+ for (Option o : arg.group().getOptions()) {
+ if (!o.equals(main) && args.containsKey(extractKey(o))) {
+ return false;
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Set a key and value into the argument list.
+ * Replaces any existing value.
+ * @param key the key for the map.
+ * @param value the value to set.
+ */
+ public void setArg(final UIOption> key, final String value) {
+ setArg(key.keyValue(), value);
+ }
+
+ /**
+ * Set a key and value into the argument list.
+ * Replaces any existing value.
+ * @param trackerKey the key for the map.
+ * @param value the value to set.
+ */
+ public void setArg(final String trackerKey, final String value) {
+ if (value == null || StringUtils.isNotBlank(value)) {
+ if (validateSet(trackerKey)) {
+ Option ratOption = Arg.findArg(trackerKey).find(trackerKey);
+ if (ratOption.hasArg()) {
+ List values = new ArrayList<>();
+ if (DefaultLog.getInstance().isEnabled(Log.Level.DEBUG)) {
+ DefaultLog.getInstance().debug(String.format("Setting %s to '%s'", trackerKey, value));
+ }
+ values.add(value);
+ args.put(trackerKey, values);
+ } else {
+ DefaultLog.getInstance().warn(String.format("Key '%s' does not accept arguments.", trackerKey));
+ }
+ } else {
+ DefaultLog.getInstance().warn(String.format("Key '%s' is unknown", trackerKey));
+ }
+ }
+ }
+
+ /**
+ * Get the list of values for a key.
+ * @param key the key for the map.
+ * @return the list of values for the key or {@code null} if not set.
+ */
+ public Optional> getArg(final String key) {
+ return Optional.ofNullable(args.get(key));
+ }
+
+ /**
+ * Add values to the key in the argument list.
+ * empty values are ignored. If no non-empty values are present no change is made.
+ * If the key does not exist, adds it.
+ * @param option the option to add values for.
+ * @param value the array of values to set.
+ */
+ public void addArg(final UIOption> option, final String... value) {
+ addArg(option.keyValue(), value);
+ }
+
+ /**
+ * Add values to the key in the argument list.
+ * empty values are ignored. If no non-empty values are present no change is made.
+ * If the key does not exist, adds it.
+ * @param trackerKey the key add values for.
+ * @param value the array of values to set.
+ */
+ public void addArg(final String trackerKey, final String... value) {
+ List newValues = Arrays.stream(value).filter(StringUtils::isNotBlank).toList();
+ if (newValues.isEmpty()) {
+ return;
+ }
+ if (!validateSet(trackerKey)) {
+ DefaultLog.getInstance().warn(String.format("Key '%s' is unknown", trackerKey));
+ return;
+ }
+ Option ratOption = Arg.findArg(trackerKey).find(trackerKey);
+ if (!ratOption.hasArgs()) {
+ DefaultLog.getInstance().warn(String.format("Key '%s' does not accept %sarguments.", trackerKey,
+ ratOption.hasArg() ? "more that one " : ""));
+ }
+ if (DefaultLog.getInstance().isEnabled(Log.Level.DEBUG)) {
+ DefaultLog.getInstance().debug(String.format("Adding [%s] to %s", String.join(", ", Arrays.asList(value)), trackerKey));
+ }
+ List values = args.computeIfAbsent(trackerKey, k -> new ArrayList<>());
+ values.addAll(newValues);
+ }
+
+ /**
+ * Remove a key from the argument list.
+ * @param option the option to remove the key for.
+ */
+ public void removeArg(final UIOption> option) {
+ args.remove(option.keyValue());
+ }
+
+ /**
+ * Remove a key from the argument list.
+ * @param trackerKey the key remove.
+ */
+ public void removeArg(final String trackerKey) {
+ args.remove(trackerKey);
+ }
+}
diff --git a/apache-rat-core/src/main/java/org/apache/rat/ui/UI.java b/apache-rat-core/src/main/java/org/apache/rat/ui/UI.java
new file mode 100644
index 000000000..db1df6a5e
--- /dev/null
+++ b/apache-rat-core/src/main/java/org/apache/rat/ui/UI.java
@@ -0,0 +1,36 @@
+/*
+ * 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.ui;
+
+public interface UI> {
+ /**
+ * Gets the common name of this UI.
+ * @return the common name of this UI.
+ */
+ default String name() {
+ return getClass().getSimpleName();
+ }
+
+ /**
+ * Gets the OptionFactory configuration for this UI.
+ *
+ * @return the OptionFactory configuration for this UI.
+ */
+ UIOptionCollection getOptionCollection();
+}
diff --git a/apache-rat-tools/src/main/java/org/apache/rat/documentation/options/AbstractOption.java b/apache-rat-core/src/main/java/org/apache/rat/ui/UIOption.java
similarity index 70%
rename from apache-rat-tools/src/main/java/org/apache/rat/documentation/options/AbstractOption.java
rename to apache-rat-core/src/main/java/org/apache/rat/ui/UIOption.java
index 84335831c..8f6e3812e 100644
--- a/apache-rat-tools/src/main/java/org/apache/rat/documentation/options/AbstractOption.java
+++ b/apache-rat-core/src/main/java/org/apache/rat/ui/UIOption.java
@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.rat.documentation.options;
+package org.apache.rat.ui;
import java.util.HashMap;
import java.util.Locale;
@@ -28,47 +28,68 @@
import org.apache.commons.cli.Option;
import org.apache.commons.lang3.StringUtils;
import org.apache.rat.OptionCollection;
-import org.apache.rat.commandline.Arg;
+import org.apache.rat.utils.CasedString;
import static java.lang.String.format;
/**
* Abstract class that provides the framework for UI-specific RAT options.
- * In this context UI option means an option expressed in the specific UI, such as:
- * @see AntOption
- * @see MavenOption
- * @see CLIOption
+ * In this context UI option means an option expressed in the specific UI.
+ * @param the concrete implementation of AbstractOption.
*/
-public abstract class AbstractOption {
+public abstract class UIOption> {
/** The pattern to match CLI options in text */
- protected static final Pattern PATTERN = Pattern.compile("-(-[a-z0-9]+)+");
+ protected static final Pattern PATTERN = Pattern.compile("-?-([A-Za-z0-9]+-?)+"); // NOSONAR
/** The actual UI-specific name for the option */
protected final Option option;
/** The name for the option */
- protected final String name;
+ protected final CasedString name;
/** The argument type for this option */
protected final OptionCollection.ArgumentType argumentType;
+ /** The AbstractOptionCollection associated with this AbstractOption */
+ protected final UIOptionCollection optionCollection;
/**
* Constructor.
*
* @param option The CLI option
- * @param name the UI-specific name for the option.
+ * @param name the UI-specific name for the option
*/
- AbstractOption(final Option option, final String name) {
+ protected > UIOption(final C optionCollection, final Option option, final CasedString name) {
+ this.optionCollection = optionCollection;
this.option = option;
this.name = name;
- argumentType = option.hasArg() ?
- option.getArgName() == null ? OptionCollection.ArgumentType.ARG :
- OptionCollection.ArgumentType.valueOf(option.getArgName().toUpperCase(Locale.ROOT)) :
- OptionCollection.ArgumentType.NONE;
+ OptionCollection.ArgumentType argType;
+ if (option.hasArg()) {
+ if (option.getArgName() == null) {
+ argType = OptionCollection.ArgumentType.ARG;
+ } else {
+ // extract the name of the argument type.
+ try {
+ argType = OptionCollection.ArgumentType.valueOf(option.getArgName().toUpperCase(Locale.ROOT));
+ } catch (IllegalArgumentException e) {
+ argType = OptionCollection.ArgumentType.ARG;
+ }
+ }
+ } else {
+ argType = OptionCollection.ArgumentType.NONE;
+ }
+ this.argumentType = argType;
+ }
+
+ /**
+ * Gets the AbstractOptionCollection that this option is a member of.
+ * @return the AbstractOptionCollection that this option is a member of.
+ */
+ public final > X getOptionCollection() {
+ return (X) optionCollection;
}
/**
* Gets the option this abstract option is wrapping.
* @return the original Option.
*/
- public Option getOption() {
+ public final Option getOption() {
return option;
}
@@ -76,9 +97,8 @@ public Option getOption() {
* Return default value.
* @return default value or {@code null} if no argument given.
*/
- public String getDefaultValue() {
- Arg arg = Arg.findArg(option);
- return arg == null ? null : arg.defaultValue();
+ public final String getDefaultValue() {
+ return optionCollection.defaultValue(option);
}
/**
@@ -94,14 +114,6 @@ public String getDefaultValue() {
*/
public abstract String getExample();
- /**
- * Gets this option's cleaned up name.
- * @return This option's cleaned up name.
- */
- public String cleanupName() {
- return cleanupName(option);
- }
-
/**
* Replaces CLI pattern options with implementation specific pattern options.
* @param str the string to clean.
@@ -114,9 +126,9 @@ public String cleanup(final String str) {
Matcher matcher = PATTERN.matcher(workingStr);
while (matcher.find()) {
String key = matcher.group();
- String optKey = key.substring(2);
- Optional
maybeResult = getOptionCollection().getOptions().getOptions().stream()
+ .filter(o -> optKey.equals(o.getOpt()) || optKey.equals(o.getLongOpt())).findFirst();
maybeResult.ifPresent(value -> maps.put(key, cleanupName(value)));
}
for (Map.Entry entry : maps.entrySet()) {
@@ -127,10 +139,18 @@ public String cleanup(final String str) {
}
/**
- * Gets the implementation specific name for the CLI option.
- * @return The implementation specific name for the CLI option.
+ * Gets the implementation specific name for the native UI.
+ * @return The implementation specific name for the native UI.
*/
public final String getName() {
+ return name.toString();
+ }
+
+ /**
+ * Gets the CasedString version of the name for the native UI..
+ * @return the CasedString version of the name for the native UI..
+ */
+ public final CasedString getCasedName() {
return name;
}
@@ -207,20 +227,12 @@ public final boolean hasArgs() {
return option.hasArgs();
}
- /**
- * Returns the number of arguments.
- * @return The number of arguments.
- */
- public final int argCount() {
- return option.getArgs();
- }
-
/**
* The key value for the option.
* @return the key value for the CLI argument map.
*/
public final String keyValue() {
- return format("\"%s\"", StringUtils.defaultIfEmpty(option.getLongOpt(), option.getOpt()));
+ return StringUtils.defaultIfEmpty(option.getLongOpt(), option.getOpt());
}
/**
diff --git a/apache-rat-core/src/main/java/org/apache/rat/ui/UIOptionCollection.java b/apache-rat-core/src/main/java/org/apache/rat/ui/UIOptionCollection.java
new file mode 100644
index 000000000..79d2cc5ba
--- /dev/null
+++ b/apache-rat-core/src/main/java/org/apache/rat/ui/UIOptionCollection.java
@@ -0,0 +1,290 @@
+/*
+ * 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.ui;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.TreeMap;
+import java.util.function.BiFunction;
+import java.util.stream.Stream;
+
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.Options;
+import org.apache.rat.Defaults;
+import org.apache.rat.commandline.Arg;
+import org.apache.rat.utils.Log;
+
+/**
+ * A collection of options supported by the UI. This includes RAT options and UI specific options.
+ * @param the AbstractOption implementation.
+ */
+public class UIOptionCollection> {
+ /** map of ARG to the associated UpdatableOptionGroup */
+ private final Map argMap;
+ /** set of RAT OptionGroups with unsupported options for this UI removed */
+ private final UpdatableOptionGroupCollection supportedRatOptions;
+ /** set of UI specific options */
+ private final Map
uiOptions;
+ /**
+ * Map of option to overridden default value. Generally applies to supported rat options but may be ui
+ * specific options as well
+ */
+ private final Map
defaultValues;
+
+ /**
+ * The function to generate a concrete BaseOption instance.
+ */
+ private final BiFunction, Option, T> mapper;
+
+ /**
+ * Construct the UIOptionCollection from the builder.
+ * @param builder the builder to build from.
+ */
+ protected UIOptionCollection(final Builder builder) {
+ Objects.requireNonNull(builder.mapper, "Builder.mapper");
+ argMap = new TreeMap<>();
+ mapper = builder.mapper;
+ supportedRatOptions = new UpdatableOptionGroupCollection();
+
+ for (Arg arg : Arg.values()) {
+ argMap.put(arg, supportedRatOptions.add(arg.group()));
+ }
+
+ for (Option opt : builder.unsupportedRatOptions) {
+ supportedRatOptions.findGroups(opt).forEach(group -> group.disableOption(opt));
+ }
+ uiOptions = new HashMap<>();
+ supportedRatOptions.options().getOptions()
+ .forEach(option -> uiOptions.put(option, mapper.apply(this, option)));
+ builder.uiOptions.stream().filter(option -> !uiOptions.containsKey(option))
+ .forEach(option -> uiOptions.put(option, mapper.apply(this, option)));
+ defaultValues = new HashMap<>(builder.defaultValues);
+ }
+
+ /**
+ * Checks if an Arg is selected.
+ * @param arg the Arg to check.
+ * @return {@code true} if the arg is selected.
+ */
+ public final boolean isSelected(final Arg arg) {
+ UpdatableOptionGroup group = argMap.get(arg);
+ return group != null && group.getSelected() != null;
+ }
+
+ /**
+ * Gets the selected Option for the arg.
+ * @param arg the arg to check.
+ * @return an Optional containing the selected option, or an empty Optional if none was selected.
+ */
+ public final Optional
getSelected(final Arg arg) {
+ UpdatableOptionGroup group = argMap.get(arg);
+ String s = group == null ? null : group.getSelected();
+ if (s != null) {
+ for (Option result : group.getOptions()) {
+ if (result.getKey().equals(s)) {
+ return Optional.of(result);
+ }
+ }
+ }
+ return Optional.empty();
+ }
+
+ /**
+ * Gets the collection of unsupported Options.
+ * @return the Options comprised for the unsupported options.
+ */
+ public final Options getUnsupportedOptions() {
+ return supportedRatOptions.unsupportedOptions();
+ }
+
+ /**
+ * Gets the UiOption instance for the Option.
+ * @param option the option to find the instance of.
+ * @return an UIOption instance that wraps the option.
+ */
+ public final Optional getMappedOption(final Option option) {
+ return Optional.ofNullable(uiOptions.get(option));
+ }
+
+ /**
+ * Gets an Options that contains the RAT Arg defined Option instances that are understood by this collection.
+ * OptionGroups are registered in the resulting Options object.
+ * @return an Options that contains the RAT Arg defined Option instances that are understood by this collection.
+ */
+ public final Options getOptions() {
+ return supportedRatOptions.options().addOptions(additionalOptions());
+ }
+
+ /**
+ * Gets the Stream of AbstractOption implementations understood by this collection.
+ * @return the Stream of AbstractOption implementations understood by this collection.
+ */
+ public final Stream getMappedOptions() {
+ return uiOptions.values().stream();
+ }
+
+ /**
+ * Gets a map client option name to specified AbstractOption implementation.
+ * @return a map client option name to specified AbstractOption implementation
+ */
+ public final Map getOptionMap() {
+ Map result = new TreeMap<>();
+ getMappedOptions().forEach(mappedOption -> result.put(ArgumentTracker.extractKey(mappedOption.getOption()), mappedOption));
+ return result;
+ }
+
+ /**
+ * Gets the additional options understood by this collection.
+ * @return the additional options understood by this collection.
+ */
+ public final Options additionalOptions() {
+ Options options = new Options();
+ uiOptions.keySet().stream()
+ .filter(option -> !supportedRatOptions.contains(option))
+ .forEach(options::addOption);
+ return options;
+ }
+
+ /**
+ * Gets the default value for the option.
+ * @param option the option to lookup.
+ * @return the default value or {@code null} if not set.
+ */
+ public final String defaultValue(final Option option) {
+ return defaultValues.get(option);
+ }
+
+ /**
+ * Builder for a BaseOptionCollection.
+ * @param the concreate type of the BaseOption.
+ * @param the concrete type being built.
+ */
+ protected static class Builder, S extends Builder> {
+ /** set of additional UI specific options */
+ private final List
uiOptions;
+ /**
+ * Map of option to overridden default value. Generally applies to supported rat options but may be ui
+ * specific options as well
+ */
+ private final Map
defaultValues;
+ /** The list of unsupported Rat options */
+ protected final List
unsupportedRatOptions;
+ /** The function to convert an option into a UIOption. */
+ private final BiFunction, Option, T> mapper;
+
+ /**
+ * Constructor for the builder.
+ */
+ protected Builder(final BiFunction, Option, T> mapper) {
+ this.mapper = mapper;
+ uiOptions = new ArrayList<>();
+ defaultValues = new HashMap<>();
+ unsupportedRatOptions = new ArrayList<>();
+ defaultValue(Arg.LOG_LEVEL, Log.Level.WARN.name());
+ defaultValue(Arg.OUTPUT_ARCHIVE, Defaults.ARCHIVE_PROCESSING.name());
+ defaultValue(Arg.OUTPUT_STANDARD, Defaults.STANDARD_PROCESSING.name());
+ defaultValue(Arg.OUTPUT_LICENSES, Defaults.LIST_LICENSES.name());
+ defaultValue(Arg.OUTPUT_FAMILIES, Defaults.LIST_FAMILIES.name());
+ }
+
+ /**
+ * build the UIOptionCollection.
+ * @return the UIOptionCollection.
+ */
+ public UIOptionCollection build() {
+ return new UIOptionCollection<>(this);
+ }
+
+ /**
+ * Returns this cast to {@code } class.
+ * @return this as {@code } class.
+ */
+ protected final S self() {
+ return (S) this;
+ }
+
+ /**
+ * Add a UI option to the collection.
+ * @param uiOption the UI Option to add.
+ * @return this
+ */
+ public S uiOption(final Option uiOption) {
+ uiOptions.add(uiOption);
+ return self();
+ }
+
+ /**
+ * Add a UI options to the collection.
+ * @param uiOption the UIOptions ({@code } objects) to add.
+ * @return this
+ */
+ public S uiOptions(final Option... uiOption) {
+ uiOptions.addAll(Arrays.asList(uiOption));
+ return self();
+ }
+
+ /**
+ * Register an option as unsupported.
+ * @param option the option that is not be supported. This should be an option in the
+ * {@link Arg} collection.
+ * @return this
+ */
+ public S unsupported(final Option option) {
+ unsupportedRatOptions.add(option);
+ return self();
+ }
+
+ /**
+ * Register multiple options as unsupported.
+ * Will ignore all the options associated with the specified Arg.
+ * @param arg The Arg to ignore.
+ * @return this
+ */
+ public S unsupported(final Arg arg) {
+ unsupportedRatOptions.addAll(arg.group().getOptions());
+ return self();
+ }
+
+ /**
+ * Specify the default values for an option.
+ * @param option the option to specify the default value for.
+ * @param value the value for the option.
+ * @return this
+ */
+ public S defaultValue(final Option option, final String value) {
+ defaultValues.put(option, value);
+ return self();
+ }
+
+ /**
+ * Specify the default values for an Arg.
+ * @param arg the Arg to specify the default value for.
+ * @param value the value for the option.
+ * @return this
+ */
+ public S defaultValue(final Arg arg, final String value) {
+ return defaultValue(arg.option(), value);
+ }
+ }
+}
diff --git a/apache-rat-core/src/main/java/org/apache/rat/ui/UpdatableOptionGroup.java b/apache-rat-core/src/main/java/org/apache/rat/ui/UpdatableOptionGroup.java
new file mode 100644
index 000000000..42181e995
--- /dev/null
+++ b/apache-rat-core/src/main/java/org/apache/rat/ui/UpdatableOptionGroup.java
@@ -0,0 +1,85 @@
+/*
+ * 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.ui;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.stream.Stream;
+
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.OptionGroup;
+
+/**
+ * An implementation of Apache Commons CLI OptionGroup that allows options to be removed (disabled).
+ */
+public final class UpdatableOptionGroup extends OptionGroup {
+ /** The set of options to remove */
+ private final Set
disabledOptions = new HashSet<>();
+
+ /**
+ * Converts the group into an UpdatableOptionGroup if it is not already an instance
+ * @param group the group to convert.
+ * @return an UpdatableOptionGroup.
+ */
+ public static UpdatableOptionGroup create(final OptionGroup group) {
+ return group instanceof UpdatableOptionGroup updatableOptionGroup ? updatableOptionGroup : new UpdatableOptionGroup(group);
+ }
+
+ private UpdatableOptionGroup(final OptionGroup group) {
+ group.getOptions().forEach(super::addOption);
+ }
+
+ /**
+ * Disable an option in the group.
+ * @param option The option to disable.
+ */
+ public void disableOption(final Option option) {
+ disabledOptions.add(option);
+ }
+
+ public boolean isEmpty() {
+ return getOptions().isEmpty();
+ }
+
+ /**
+ * Gets the disabled options for this group.
+ * @return the set of disabled options for this group.
+ */
+ public Stream
getDisableOptions() {
+ return disabledOptions.stream();
+ }
+ /**
+ * Reset the group so that all disabled options are re-enabled.
+ */
+ public void reset() {
+ disabledOptions.clear();
+ }
+
+ @Override
+ public Collection
getOptions() {
+ return super.getOptions().stream().filter(opt -> !disabledOptions.contains(opt)).toList();
+ }
+
+ @Override
+ public UpdatableOptionGroup addOption(final Option option) {
+ super.addOption(option);
+ return this;
+ }
+}
diff --git a/apache-rat-core/src/main/java/org/apache/rat/ui/UpdatableOptionGroupCollection.java b/apache-rat-core/src/main/java/org/apache/rat/ui/UpdatableOptionGroupCollection.java
new file mode 100644
index 000000000..893db66a0
--- /dev/null
+++ b/apache-rat-core/src/main/java/org/apache/rat/ui/UpdatableOptionGroupCollection.java
@@ -0,0 +1,110 @@
+/*
+ * 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.ui;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Stream;
+
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.OptionGroup;
+import org.apache.commons.cli.Options;
+
+/**
+ * A collection of UpdatableOptionGroups.
+ */
+public class UpdatableOptionGroupCollection {
+ /** the contained UpdatableOptionGroups */
+ private final List updatableOptionGroups;
+
+ /**
+ * Creates an empty collection.
+ */
+ public UpdatableOptionGroupCollection() {
+ updatableOptionGroups = new ArrayList<>();
+ }
+
+ /**
+ * Adds an OptionGroup to the collection. If the OptionGroup is not an UpdatableOptionGroup
+ * it is converted first.
+ * @param optionGroup an OptionGroup to add.
+ * @return the UpdatableOptionGroup that was added.
+ */
+ public UpdatableOptionGroup add(final OptionGroup optionGroup) {
+ UpdatableOptionGroup uog = UpdatableOptionGroup.create(optionGroup);
+ updatableOptionGroups.add(uog);
+ return uog;
+ }
+
+ /**
+ * Gets an Options object from this collection.
+ * @return an Options object.
+ */
+ public Options options() {
+ Options result = new Options();
+ updatableOptionGroups.forEach(result::addOptionGroup);
+ return result;
+ }
+
+ /**
+ * Gets ll the UpdatableOptionGroups that the option is in.
+ * @param option the option to searhc for.
+ * @return the stream of UpdatableOptionGroups the option is in.
+ */
+ public Stream findGroups(final Option option) {
+ return updatableOptionGroups.stream().filter(og -> og.getOptions().contains(option));
+ }
+
+ /**
+ * Gets the set of removed Options from the collection.
+ * @return the set of removed options.
+ */
+ public Set
removedOptions() {
+ Set
result = new HashSet<>();
+ updatableOptionGroups.forEach(uog -> uog.getDisableOptions().forEach(result::add));
+ return result;
+ }
+
+ /**
+ * Gets the unsupported options
+ * If multiple options from the a group are disabled they will be added to the
+ * options in a group together.
+ * @return the Options object containing all the unsupported options.
+ */
+ public Options unsupportedOptions() {
+ Options result = new Options();
+ for (UpdatableOptionGroup uog : updatableOptionGroups) {
+ OptionGroup group = new OptionGroup();
+ uog.getDisableOptions().forEach(group::addOption);
+ result.addOptionGroup(group);
+ }
+ return result;
+ }
+
+ /**
+ * Returns true if the option is in any of the groups.
+ * @param option the option.
+ * @return {@code true} if the option is in any of the groups.
+ */
+ public boolean contains(final Option option) {
+ return updatableOptionGroups.stream().anyMatch(og -> og.getOptions().contains(option));
+ }
+}
diff --git a/apache-rat-core/src/main/java/org/apache/rat/ui/package-info.java b/apache-rat-core/src/main/java/org/apache/rat/ui/package-info.java
new file mode 100644
index 000000000..765d92c1d
--- /dev/null
+++ b/apache-rat-core/src/main/java/org/apache/rat/ui/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * 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. *
+ */
+
+/**
+ * Classes that support UI generation and interoperability.
+ */
+package org.apache.rat.ui;
diff --git a/apache-rat-core/src/main/java/org/apache/rat/ui/spi/UIProvider.java b/apache-rat-core/src/main/java/org/apache/rat/ui/spi/UIProvider.java
new file mode 100644
index 000000000..bcdad6f54
--- /dev/null
+++ b/apache-rat-core/src/main/java/org/apache/rat/ui/spi/UIProvider.java
@@ -0,0 +1,26 @@
+/*
+ * 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.ui.spi;
+
+import org.apache.rat.ui.UI;
+import org.apache.rat.ui.UIOption;
+
+public interface UIProvider {
+ > UI create();
+}
diff --git a/apache-rat-core/src/main/java/org/apache/rat/ui/spi/package-info.java b/apache-rat-core/src/main/java/org/apache/rat/ui/spi/package-info.java
new file mode 100644
index 000000000..9d7246752
--- /dev/null
+++ b/apache-rat-core/src/main/java/org/apache/rat/ui/spi/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+/**
+ * The SPI implementation for the UIs
+ */
+package org.apache.rat.ui.spi;
diff --git a/apache-rat-core/src/main/java/org/apache/rat/utils/CasedString.java b/apache-rat-core/src/main/java/org/apache/rat/utils/CasedString.java
index 6751ad173..4215803a4 100644
--- a/apache-rat-core/src/main/java/org/apache/rat/utils/CasedString.java
+++ b/apache-rat-core/src/main/java/org/apache/rat/utils/CasedString.java
@@ -57,7 +57,7 @@ public CasedString(final StringCase stringCase, final String string) {
}
/**
- * Creates a cased string of the specified case and segments.
+ * Creates a cased string of the specified case and segments
* @param stringCase the case of the string.
* @param segments the segments of the string.
*/
@@ -112,12 +112,12 @@ public int hashCode() {
}
/**
- * The definition of a string case.
+ * The definition of a String case.
*/
public static final class StringCase {
- /** The camel case. Example: "HelloWorld"*/
+ /** The camel case. Example: "HelloWorld"*/
public static final StringCase CAMEL;
- /** The pascal case. Example: "helloWorld" */
+ /** The pascal case. Example: "helloWorld" */
public static final StringCase PASCAL;
/** The Snake case. Example: "hello_world" */
public static final StringCase SNAKE;
@@ -125,7 +125,7 @@ public static final class StringCase {
public static final StringCase KEBAB;
/** The phrase case. Example: "hello world" */
public static final StringCase PHRASE;
- /** The dot case. Example: "hello.world" */
+ /** The dot case. Example: "hello.world" */
public static final StringCase DOT;
/** The slash case. Example: "hello/world" */
public static final StringCase SLASH;
@@ -135,13 +135,13 @@ public static final class StringCase {
static final String[] EMPTY_SEGMENT;
/** The name of this case */
private final String name;
- /** The predicate that determines if a character is a splitter character. A splitter character
+ /** The predicate that determines if a character is a spliter character. A splitter character
* is the character that signals the start of a new segment.
*/
private final Predicate splitter;
/**
- * If {@code true}, the splitter character is preserved as part of the subsequent section otherwise,
- * the splitter character is discarded.
+ * If {@code true} the spliter character is preserved as part of the subsequent section otherwise,
+ * the spliter character is discarded.
*/
private final boolean preserveSplit;
/** The function that converts segments into the String representation */
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/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.
+
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']");
@@ -1086,8 +1081,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);
}
}
@@ -1123,9 +1117,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:
@@ -1144,8 +1137,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);
}
}
@@ -1176,9 +1168,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);
@@ -1199,8 +1191,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);
}
}
@@ -1275,7 +1266,6 @@ protected void editOverwriteTest() {
}
@OptionCollectionTest.TestFunction
- @Override
public void helpTest() {
PrintStream origin = System.out;
Options options = OptionCollection.buildOptions();
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..d8f85e0d5 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,24 @@
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.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
+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.TreeMap;
+import java.util.stream.Stream;
import javax.xml.XMLConstants;
import javax.xml.transform.Source;
import javax.xml.transform.dom.DOMSource;
@@ -45,24 +48,29 @@
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.Test;
import org.junit.jupiter.api.io.TempDir;
+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;
@@ -74,19 +82,19 @@ public class ReporterTest {
@TempDir
File tempDirectory;
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 {
+ public void testExecute() throws RatException, ParseException, IOException {
File output = new File(tempDirectory, "testExecute");
-
- 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();
+ 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);
@@ -137,11 +145,8 @@ 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();
+ 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);
@@ -152,15 +157,13 @@ public void testOutputOption() throws Exception {
@Test
public void testDefaultOutput() throws Exception {
File output = new File(tempDirectory, "testDefaultOutput");
+ 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);
}
@@ -209,11 +212,8 @@ public void testXMLOutput() throws Exception {
"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();
+ 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()));
@@ -403,13 +403,8 @@ private Validator initValidator() throws SAXException {
@Test
public void xmlReportTest() throws Exception {
- ByteArrayOutputStream out = new ByteArrayOutputStream();
-
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();
@@ -455,10 +450,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);
@@ -471,11 +465,10 @@ public void plainReportTest() throws Exception {
public 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 );
@@ -486,21 +479,50 @@ public void unapprovedLicensesReportTest() throws Exception {
@Test
public 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 tempPath = tempDirectory.toPath();
+ Path basePath = tempPath.resolve(test.getTestName());
+ org.apache.rat.utils.FileUtils.mkDir(basePath.toFile());
+ test.setupFiles(basePath);
+ ArgumentContext ctxt = collectionParser.parseCommands(basePath.toFile(),
+ test.getCommandLine(basePath.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(),
+ basePath.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, basePath.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/commandline/ArgTests.java b/apache-rat-core/src/test/java/org/apache/rat/commandline/ArgTests.java
index 7f27cb8d1..28e856b05 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
@@ -23,6 +23,7 @@
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
+import org.apache.rat.CLIOptionCollection;
import org.apache.rat.DeprecationReporter;
import org.apache.rat.OptionCollection;
import org.apache.rat.ReportConfiguration;
@@ -58,7 +59,7 @@ public void setOut(File file) {
CommandLine commandLine = createCommandLine(new String[] {"--output-file", fileName});
OutputFileConfig configuration = new OutputFileConfig();
ArgumentContext ctxt = new ArgumentContext(new File("."), configuration, commandLine);
- Arg.processArgs(ctxt);
+ 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 extends AbstractBuilder> 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/test/AbstractConfigurationOptionsProvider.java b/apache-rat-core/src/test/java/org/apache/rat/test/AbstractConfigurationOptionsProvider.java
index 622878471..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
@@ -114,7 +114,6 @@ protected AbstractConfigurationOptionsProvider(final Collection unsuppor
addTest(OptionCollectionTest.OptionTest.namedTest("exclude", this::excludeTest));
addTest(OptionCollectionTest.OptionTest.namedTest("exclude-file", this::excludeFileTest));
addTest(OptionCollectionTest.OptionTest.namedTest("force", this::forceTest));
- addTest(OptionCollectionTest.OptionTest.namedTest("help", this::helpTest));
addTest(OptionCollectionTest.OptionTest.namedTest("help-licenses", this::helpLicenses));
addTest(OptionCollectionTest.OptionTest.namedTest("include", this::includeTest));
addTest(OptionCollectionTest.OptionTest.namedTest("includes-file", this::includesFileTest));
@@ -153,9 +152,6 @@ protected AbstractConfigurationOptionsProvider(final Collection unsuppor
super.validate(unsupportedArgs);
}
- /** Help test */
- protected abstract void helpTest();
-
// exclude tests
private void execExcludeTest(final Option option, final String[] args) {
String[] notExcluded = {"notbaz", "well._afile"};
@@ -791,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);
}
}
@@ -825,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 dac4b4150..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,16 +170,13 @@ 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) {
return DocumentName.builder(new File(baseDir, name)).build();
}
- /** Help test */
- protected abstract void helpTest();
-
/** Display the option and value under test */
final protected String displayArgAndName(final Option option, final String fname) {
return String.format("%s %s", option.getLongOpt(), fname);
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
unsupportedOptions = new ArrayList<>(optionCollection.getUnsupportedOptions().getOptions());
+ Set result = new TreeSet<>();
+ for (Arg arg : Arg.values()) {
+ if (!arg.isEmpty()) {
+ Stream
options = arg.group().getOptions().stream().filter(opt -> !unsupportedOptions.contains(opt));
+ switch (arg) {
+ case CONFIGURATION -> options.forEach(opt -> configTest(result, opt));
+ case CONFIGURATION_NO_DEFAULTS -> options.forEach(opt -> configurationNoDefaultsTest(result, opt));
+ case COUNTER_MIN -> options.forEach(opt -> counterMinTest(result, opt));
+ case COUNTER_MAX -> options.forEach(opt -> counterMaxTest(result, opt));
+ case DRY_RUN -> options.forEach(opt -> dryRunTest(result, opt));
+ case EDIT_COPYRIGHT -> options.forEach(opt -> editCopyrightTest(result, opt));
+ case EDIT_ADD -> options.forEach(opt -> editLicenseTest(result, opt));
+ case EDIT_OVERWRITE -> options.forEach(opt -> editOverwriteTest(result, opt));
+ case HELP_LICENSES -> options.forEach(opt -> helpLicenses(result, opt));
+ case EXCLUDE -> options.forEach(opt -> inputExcludeTest(result, opt));
+ case EXCLUDE_FILE -> options.forEach(opt -> inputExcludeFileTest(result, opt));
+ case EXCLUDE_PARSE_SCM -> options.forEach(opt -> inputExcludeParsedScmTest(result, opt));
+ case EXCLUDE_STD -> options.forEach(opt -> inputExcludeStdTest(result, opt));
+ case EXCLUDE_SIZE -> options.forEach(opt -> inputExcludeSizeTest(result, opt));
+ case INCLUDE -> options.forEach(opt -> inputIncludeTest(result, opt));
+ case INCLUDE_FILE -> options.forEach(opt -> inputIncludeFileTest(result, opt));
+ case INCLUDE_STD -> options.forEach(opt -> inputIncludeStdTest(result, opt));
+ case SOURCE -> options.forEach(opt -> inputSourceTest(result, opt));
+ case FAMILIES_APPROVED -> options.forEach(opt -> licenseFamiliesApprovedTest(result, opt));
+ case FAMILIES_APPROVED_FILE -> options.forEach(opt -> licenseFamiliesApprovedFileTest(result, opt));
+ case FAMILIES_DENIED -> options.forEach(opt -> licenseFamiliesDeniedTest(result, opt));
+ case FAMILIES_DENIED_FILE -> options.forEach(opt -> licenseFamiliesDeniedFileTest(result, opt));
+ case LICENSES_APPROVED -> options.forEach(opt -> licensesApprovedTest(result, opt));
+ case LICENSES_APPROVED_FILE -> options.forEach(opt -> licensesApprovedFileTest(result, opt));
+ case LICENSES_DENIED -> options.forEach(opt -> licensesDeniedTest(result, opt));
+ case LICENSES_DENIED_FILE -> options.forEach(opt -> licensesDeniedFileTest(result, opt));
+ case LOG_LEVEL -> options.forEach(opt -> logLevelTest(result, opt));
+ case OUTPUT_ARCHIVE -> options.forEach(opt -> outputArchiveTest(result, opt));
+ case OUTPUT_FAMILIES -> options.forEach(opt -> outputFamiliesTest(result, opt));
+ case OUTPUT_FILE -> options.forEach(opt -> outputFileTest(result, opt));
+ case OUTPUT_LICENSES -> options.forEach(opt -> outputLicensesTest(result, opt));
+ case OUTPUT_STANDARD -> options.forEach(opt -> outputStandardTest(result, opt));
+ case OUTPUT_STYLE -> options.forEach(opt -> outputStyleTest(result, opt));
+ }
+ }
+ }
+ validate(result);
+ return result;
+ }
+
+ private void validate(Set result) {
+ Set
options = new HashSet<>(Arg.getOptions().getOptions());
+ result.forEach(testData -> options.remove(testData.getOption()));
+ // TODO fix this once deprecated options are removed
+ options.forEach(opt -> DefaultLog.getInstance().warn("Option " + ArgumentTracker.extractKey(opt) + " was not tested."));
+ //assertThat(options).describedAs("All options are not accounted for.").isEmpty();
+ }
+
+ protected abstract void inputExcludeFileTest(final Set result, final Option option);
+
+ protected abstract void inputExcludeTest(final Set result, final Option option);
+
+ protected abstract void inputExcludeStdTest(final Set result, final Option option);
+
+ protected abstract void inputExcludeParsedScmTest(final Set result, final Option option);
+
+ protected abstract void inputExcludeSizeTest(final Set result, final Option option);
+
+ protected abstract void inputIncludeFileTest(final Set result, final Option option);
+
+ protected abstract void inputIncludeTest(final Set result, final Option option);
+
+ protected abstract void inputIncludeStdTest(final Set result, final Option option);
+
+ protected abstract void inputSourceTest(final Set result, final Option option);
+
+ protected abstract void helpLicenses(final Set result, final Option option);
+
+ protected abstract void licensesApprovedFileTest(final Set result, final Option option);
+
+ protected abstract void licensesApprovedTest(final Set result, final Option option);
+
+ protected abstract void licensesDeniedTest(final Set result, final Option option);
+
+ protected abstract void licensesDeniedFileTest(final Set result, final Option option);
+
+ protected abstract void licenseFamiliesApprovedFileTest(final Set result, final Option option);
+
+ protected abstract void licenseFamiliesApprovedTest(final Set result, final Option option);
+
+ protected abstract void licenseFamiliesDeniedFileTest(final Set result, final Option option);
+
+ protected abstract void licenseFamiliesDeniedTest(final Set result, final Option option);
+
+ protected abstract void counterMaxTest(final Set result, final Option option);
+
+ protected abstract void counterMinTest(final Set result, final Option option);
+
+ /**
+ * Add results to the result list.
+ * @param result the result list.
+ * @param option configuration option we are testing.
+ */
+ protected abstract void configTest(final Set result, final Option option);
+
+ protected abstract void configurationNoDefaultsTest(final Set result, final Option option);
+
+ protected abstract void dryRunTest(final Set result, final Option option);
+
+ protected abstract void editCopyrightTest(final Set result, final Option option);
+
+ protected abstract void editLicenseTest(final Set result, final Option option);
+
+ protected abstract void editOverwriteTest(final Set result, final Option option);
+
+ protected abstract void logLevelTest(final Set result, final Option option);
+
+ protected abstract void outputArchiveTest(final Set result, final Option option);
+
+ protected abstract void outputFamiliesTest(final Set result, final Option option);
+
+ protected abstract void outputFileTest(final Set result, final Option option);
+
+ protected abstract void outputLicensesTest(final Set result, final Option option);
+
+ protected abstract void outputStandardTest(final Set result, final Option option);
+
+ protected abstract void outputStyleTest(final Set result, final Option option);
+}
diff --git a/apache-rat-core/src/test/java/org/apache/rat/testhelpers/data/DataUtils.java b/apache-rat-core/src/test/java/org/apache/rat/testhelpers/data/DataUtils.java
new file mode 100644
index 000000000..11ad8ec87
--- /dev/null
+++ b/apache-rat-core/src/test/java/org/apache/rat/testhelpers/data/DataUtils.java
@@ -0,0 +1,142 @@
+/*
+ * 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 java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Locale;
+import java.util.function.Consumer;
+import org.apache.commons.cli.Option;
+import org.apache.rat.report.xml.writer.XmlWriter;
+import org.apache.rat.utils.CasedString;
+import org.apache.rat.utils.DefaultLog;
+
+public class DataUtils {
+ /**
+ * The text for the current ASF license.
+ */
+ public static final String[] ASF_TEXT_LINES = {
+ "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."
+ };
+
+ /**
+ * The text for the current ASF license as a block of text with new lines.
+ */
+ public static final String ASF_TEXT = String.join("\n" ,ASF_TEXT_LINES);
+
+ /**
+ * A setup that does nothing.
+ */
+ public static final Consumer NO_SETUP = basePath -> {
+ DefaultLog.getInstance().warn("no setup for " + basePath);
+ };
+
+ public static final Consumer NO_VALIDATOR = validatorData -> {};
+
+ private DataUtils() {
+ // do not instantiate
+ }
+
+ /**
+ * Create a directory name from the option. Directory names are camel.
+ * @param option the option to create a directory name from.
+ * @return the directory name for the option.
+ */
+ static String asDirName(Option option) {
+ if (option.hasLongOpt()) {
+ return CasedString.StringCase.CAMEL.assemble(CasedString.StringCase.KEBAB.getSegments(option.getLongOpt()));
+ }
+ return option.getOpt().toLowerCase(Locale.ROOT);
+ }
+
+ /**
+ * Generate a simple configuration file that defines one license based on the name of the file and
+ * containing as single text matcher that mateches the {@code text} parameter.
+ * @param fileName The name of the file to create.
+ * @param id the ID for the family.
+ * @param text the text for the matcher.
+ */
+ static void generateTextConfig(Path fileName, String id, String text) {
+ try (XmlWriter writer = new XmlWriter(new OutputStreamWriter(Files.newOutputStream(fileName.toFile().toPath()),
+ StandardCharsets.UTF_8))) {
+ writer.startDocument()
+ .comment(ASF_TEXT)
+ .openElement("rat-config")
+ .openElement("families")
+ .openElement("family")
+ .attribute("id", id)
+ .attribute("name", "from " + fileName.getFileName())
+ .closeElement() // family
+ .closeElement() // families
+ .openElement("licenses")
+ .openElement("license")
+ .attribute("family", id)
+ .openElement("text")
+ .content(text)
+ .closeDocument(); // close all open elements
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ /**
+ * Generate a simple configuration file that defines one license based on the name of the file and
+ * containing as single text matcher that mateches the {@code text} parameter.
+ * @param fileName The name of the file to create.
+ * @param id the ID for the family.
+ * @param spdxId the SPDX id for the matcher.
+ */
+ static void generateSpdxConfig(Path fileName, String id, String spdxId) {
+ try (XmlWriter writer = new XmlWriter(new OutputStreamWriter(Files.newOutputStream(fileName.toFile().toPath()),
+ StandardCharsets.UTF_8))) {
+ writer.startDocument()
+ .comment(ASF_TEXT)
+ .openElement("rat-config")
+ .openElement("families")
+ .openElement("family")
+ .attribute("id", id)
+ .attribute("name", "from " + fileName.getFileName())
+ .closeElement() // family
+ .closeElement() // families
+ .openElement("licenses")
+ .openElement("license")
+ .attribute("family", id)
+ .openElement("spdx")
+ .attribute("name", spdxId)
+ .closeDocument(); // close all open elements
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/apache-rat-core/src/test/java/org/apache/rat/testhelpers/data/ReportTestDataProvider.java b/apache-rat-core/src/test/java/org/apache/rat/testhelpers/data/ReportTestDataProvider.java
new file mode 100644
index 000000000..4b50a8d9d
--- /dev/null
+++ b/apache-rat-core/src/test/java/org/apache/rat/testhelpers/data/ReportTestDataProvider.java
@@ -0,0 +1,1281 @@
+/*
+ * 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.data;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFactory;
+import org.apache.commons.cli.Option;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.tuple.ImmutablePair;
+import org.apache.rat.OptionCollectionParser;
+import org.apache.rat.ReportConfiguration;
+import org.apache.rat.api.RatException;
+import org.apache.rat.commandline.Arg;
+import org.apache.rat.commandline.StyleSheets;
+import org.apache.rat.config.exclusion.StandardCollection;
+import org.apache.rat.config.results.ClaimValidator;
+import org.apache.rat.document.DocumentName;
+import org.apache.rat.license.LicenseSetFactory;
+import org.apache.rat.report.claim.ClaimStatistic;
+import org.apache.rat.report.xml.writer.XmlWriter;
+import org.apache.rat.utils.FileUtils;
+import org.apache.rat.testhelpers.TestingLog;
+import org.apache.rat.testhelpers.TextUtils;
+import org.apache.rat.testhelpers.XmlUtils;
+import org.apache.rat.utils.DefaultLog;
+import org.apache.rat.utils.Log;
+import org.w3c.dom.Document;
+
+import static org.apache.rat.utils.FileUtils.writeFile;
+import static org.apache.rat.testhelpers.data.DataUtils.NO_SETUP;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Fail.fail;
+
+/**
+ * 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.
+ * These tests generally validate the results in the generated XML are as expected.
+ */
+public class ReportTestDataProvider extends AbstractTestDataProvider {
+
+ public static final RatException NO_LICENSES_EXCEPTION = new RatException("At least one license must be defined");
+
+ private final XPath xpath = XPathFactory.newInstance().newXPath();
+
+ private final Consumer mkRat = basePath -> {
+ DefaultLog.getInstance().warn("mkRat setup for " + basePath);
+ File baseDir = basePath.toFile();
+ File ratDir = new File(baseDir, ".rat");
+ FileUtils.mkDir(ratDir);
+ };
+
+ private void assertStandardFile(Document document, String fname) {
+ try {
+ XmlUtils.assertIsPresent(document, xpath,
+ String.format("/rat-report/resource[@name='/%s'][@type='STANDARD']", fname));
+ } catch (XPathExpressionException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private void assertIgnoredFile(Document document, String fname) {
+ try {
+ XmlUtils.assertIsPresent(document, xpath,
+ String.format("/rat-report/resource[@name='/%s'][@type='IGNORED']", fname));
+ } catch (XPathExpressionException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private void assertCounter(ValidatorData data, ClaimStatistic.Counter counter, int count) {
+ assertThat(data.getStatistic().getCounter(counter)).as(counter.name()).isEqualTo(count);
+ }
+
+ // exclude tests
+ private List execExcludeTest(final Option option, final Supplier args, Consumer setupFiles) {
+ Consumer setup = setupFiles.andThen(mkRat).andThen(basePath -> {
+ DefaultLog.getInstance().warn("execExcludeTest setup for " + basePath);
+ File baseDir = basePath.toFile();
+ writeFile(baseDir, "notbaz");
+ writeFile(baseDir, "well._afile");
+ writeFile(baseDir, "some.foo");
+ writeFile(baseDir, "B.bar");
+ writeFile(baseDir, "justbaz");
+ });
+
+ TestData test1 = new TestData(DataUtils.asDirName(option), NO_OPTIONS,
+ setup,
+ validatorData -> {
+ DefaultLog.getInstance().warn("validating execExcludeTest for " + validatorData.getBaseDir());
+ assertCounter(validatorData, ClaimStatistic.Counter.STANDARDS, 5);
+ assertCounter(validatorData, ClaimStatistic.Counter.IGNORED, 1);
+ });
+
+ String[] ignored = {"B.bar", "justbaz", "some.foo", ".rat"};
+ String[] standard = {"well._afile", "notbaz"};
+ TestData test2 = new TestData("", Collections.singletonList(ImmutablePair.of(option, args.get())),
+ setup,
+ validatorData -> {
+ DefaultLog.getInstance().warn("validating execExcludeTest for " + validatorData.getBaseDir());
+ ClaimStatistic claimStatistic = validatorData.getStatistic();
+ assertCounter(validatorData, ClaimStatistic.Counter.STANDARDS, standard.length);
+ assertCounter(validatorData, ClaimStatistic.Counter.IGNORED, ignored.length);
+ for (String fileName : ignored) {
+ assertIgnoredFile(validatorData.getDocument(), fileName);
+ }
+ for (String fileName : standard) {
+ assertStandardFile(validatorData.getDocument(), fileName);
+ }
+ });
+ return Arrays.asList(test1, test2);
+ }
+
+ protected void inputExcludeFileTest(final Set result, final Option option) {
+ Consumer setup = mkRat.andThen(baseDir -> {
+ File dir = baseDir.resolve(".rat").toFile();
+ writeFile(dir, "exclude.txt", Arrays.asList(AbstractTestDataProvider.EXCLUDE_ARGS));
+ });
+ Supplier args = () -> new String[]{".rat/exclude.txt"};
+ result.addAll(execExcludeTest(option, args, setup));
+ }
+
+
+ protected void inputExcludeTest(final Set result, final Option option) {
+ result.addAll(execExcludeTest(option, () -> AbstractTestDataProvider.EXCLUDE_ARGS, x -> {
+ }));
+ }
+
+ protected void inputExcludeStdTest(final Set result, final Option option) {
+ String[] args = {StandardCollection.MAVEN.name()};
+ String[] defaultExcluded = {"afile~", ".#afile", "%afile%", "._afile"};
+ String[] defaultIncluded = {"afile~more", "what.#afile", "%afile%withMore", "well._afile"};
+ String mavenFile = "build.log";
+ Consumer setup = basePath -> {
+ DefaultLog.getInstance().warn("inputExcludeStdTest setup for " + basePath);
+ File baseDir = basePath.toFile();
+ for (String fileName : defaultExcluded) {
+ writeFile(baseDir, fileName);
+ }
+ for (String fileName : defaultIncluded) {
+ writeFile(baseDir, fileName);
+ }
+ writeFile(baseDir, mavenFile);
+ };
+ result.add(new TestData(DataUtils.asDirName(option), Collections.singletonList(ImmutablePair.nullPair()),
+ setup,
+ validatorData -> {
+ DefaultLog.getInstance().warn("validating inputExcludeStdTest for " + validatorData.getBaseDir());
+ assertCounter(validatorData, ClaimStatistic.Counter.STANDARDS, defaultIncluded.length + 1);
+ assertCounter(validatorData, ClaimStatistic.Counter.IGNORED, defaultExcluded.length);
+ for (String fileName : defaultIncluded) {
+ assertStandardFile(validatorData.getDocument(), fileName);
+ }
+ for (String fileName : defaultExcluded) {
+ assertIgnoredFile(validatorData.getDocument(), fileName);
+ }
+ }));
+
+
+ result.add(new TestData("", Collections.singletonList(ImmutablePair.of(option, args)),
+ setup,
+ validatorData -> {
+ DefaultLog.getInstance().warn("validating inputExcludeStdTest for " + validatorData.getBaseDir());
+ assertCounter(validatorData, ClaimStatistic.Counter.STANDARDS, defaultIncluded.length);
+ assertCounter(validatorData, ClaimStatistic.Counter.IGNORED, defaultExcluded.length + 1);
+ for (String fileName : defaultIncluded) {
+ assertStandardFile(validatorData.getDocument(), fileName);
+ }
+ for (String fileName : defaultExcluded) {
+ assertIgnoredFile(validatorData.getDocument(), fileName);
+ }
+ assertIgnoredFile(validatorData.getDocument(), mavenFile);
+ }));
+ }
+
+ protected void inputExcludeParsedScmTest(final Set result, final Option option) {
+ Consumer setup = basePath -> {
+ DefaultLog.getInstance().warn("inputExcludeParsedScmTest setup for " + basePath);
+ File baseDir = basePath.toFile();
+ String[] lines = {
+ "# somethings",
+ "!thingone", "thing*", System.lineSeparator(),
+ "# some fish",
+ "**/fish", "*_fish",
+ "# some colorful directories",
+ "red/", "blue/*/"};
+ writeFile(baseDir, ".gitignore", Arrays.asList(lines));
+ writeFile(baseDir, "thingone");
+ writeFile(baseDir, "thingtwo");
+ File dir = new File(baseDir, "dir");
+ FileUtils.mkDir(dir);
+ FileUtils.writeFile(dir, "fish_two");
+ FileUtils.writeFile(dir, "fish");
+
+ dir = new File(baseDir, "red");
+ FileUtils.mkDir(dir);
+ FileUtils.writeFile(dir, "fish");
+
+ dir = new File(baseDir, "blue/fish");
+ FileUtils.mkDir(dir);
+ FileUtils.writeFile(dir, "dory");
+
+ dir = new File(baseDir, "some");
+ FileUtils.mkDir(dir);
+ FileUtils.writeFile(dir, "fish");
+ FileUtils.writeFile(dir, "things");
+ FileUtils.writeFile(dir, "thingone");
+
+ dir = new File(baseDir, "another");
+ FileUtils.mkDir(dir);
+ FileUtils.writeFile(dir, "red_fish");
+ };
+
+ result.add(new TestData(DataUtils.asDirName(option), Collections.singletonList(ImmutablePair.nullPair()),
+ setup,
+ validatorData -> {
+ DefaultLog.getInstance().warn("validating inputExcludeParsedScmTest for " + validatorData.getBaseDir());
+ assertCounter(validatorData, ClaimStatistic.Counter.STANDARDS, 11);
+ // .gitignore is ignored by default as it is hidden
+ assertCounter(validatorData, ClaimStatistic.Counter.IGNORED, 0);
+ }));
+
+ result.add(new TestData("GIT", Collections.singletonList(ImmutablePair.of(option, new String[]{"GIT"})),
+ setup,
+ validatorData -> {
+ DefaultLog.getInstance().warn("validating inputExcludeParsedScmTest for " + validatorData.getBaseDir());
+ assertCounter(validatorData, ClaimStatistic.Counter.STANDARDS, 3);
+ // .gitignore is ignored by default as it is hidden
+ assertCounter(validatorData, ClaimStatistic.Counter.IGNORED, 8);
+ }));
+ }
+
+ protected void inputExcludeSizeTest(final Set result, final Option option) {
+ String[] notExcluded = {"Hello.txt", "HelloWorld.txt"};
+ String[] excluded = {"Hi.txt"};
+
+ Consumer setup = basePath -> {
+ DefaultLog.getInstance().warn("inputExcludeSizeTest setup for " + basePath);
+ File baseDir = basePath.toFile();
+ writeFile(baseDir, "Hi.txt", Collections.singletonList("Hi"));
+ writeFile(baseDir, "Hello.txt", Collections.singletonList("Hello"));
+ writeFile(baseDir, "HelloWorld.txt", Collections.singletonList("HelloWorld"));
+ };
+
+ result.add(new TestData(DataUtils.asDirName(option), Collections.singletonList(ImmutablePair.of(null, null)),
+ setup,
+ validatorData -> {
+ DefaultLog.getInstance().warn("validating inputExcludeSizeTest for " + validatorData.getBaseDir());
+ assertCounter(validatorData, ClaimStatistic.Counter.STANDARDS, 3);
+ assertCounter(validatorData, ClaimStatistic.Counter.IGNORED, 0);
+ for (String fname : excluded) {
+ assertStandardFile(validatorData.getDocument(), fname);
+ }
+ for (String fname : notExcluded) {
+ assertStandardFile(validatorData.getDocument(), fname);
+ }
+ }));
+
+ result.add(new TestData("", Collections.singletonList(ImmutablePair.of(option, new String[]{"5"})),
+ setup,
+ validatorData -> {
+ DefaultLog.getInstance().warn("validating inputExcludeSizeTest for " + validatorData.getBaseDir());
+ assertCounter(validatorData, ClaimStatistic.Counter.STANDARDS, 2);
+ assertCounter(validatorData, ClaimStatistic.Counter.IGNORED, 1);
+ for (String fname : excluded) {
+ assertIgnoredFile(validatorData.getDocument(), fname);
+ }
+ for (String fname : notExcluded) {
+ assertStandardFile(validatorData.getDocument(), fname);
+ }
+ }));
+ }
+
+ // include tests
+ private List execIncludeTest(final Option option, String[] args, Consumer setupFiles) {
+ Option excludeOption = Arg.EXCLUDE.option();
+ Consumer setup = setupFiles.andThen(basePath -> {
+ DefaultLog.getInstance().warn("execIncludeTest setup for " + basePath);
+ File baseDir = basePath.toFile();
+ writeFile(baseDir, "notbaz");
+ writeFile(baseDir, "some.foo");
+ writeFile(baseDir, "B.bar");
+ writeFile(baseDir, "justbaz");
+ });
+
+ // standard without options.
+ TestData test1 = new TestData(DataUtils.asDirName(option), NO_OPTIONS,
+ setup,
+ validatorData -> {
+ DefaultLog.getInstance().warn("validating execIncludeTest for " + validatorData.getBaseDir());
+ assertCounter(validatorData, ClaimStatistic.Counter.STANDARDS, 4);
+ assertCounter(validatorData, ClaimStatistic.Counter.IGNORED, 1);
+ });
+
+ // verify exclude removes the files
+ TestData test2 = new TestData(DataUtils.asDirName(option), Collections.singletonList(ImmutablePair.of(excludeOption, AbstractTestDataProvider.EXCLUDE_ARGS)),
+ setup,
+ validatorData -> {
+ DefaultLog.getInstance().warn("validating execIncludeTest for " + validatorData.getBaseDir());
+ assertCounter(validatorData, ClaimStatistic.Counter.STANDARDS, 1);
+ // .gitignore is ignored by default as it is hidden but not counted
+ assertCounter(validatorData, ClaimStatistic.Counter.IGNORED, 4);
+ });
+
+ TestData test3 = new TestData("", Arrays.asList(ImmutablePair.of(option, args), ImmutablePair.of(excludeOption, AbstractTestDataProvider.EXCLUDE_ARGS)),
+ setup,
+ validatorData -> {
+ DefaultLog.getInstance().warn("validating execIncludeTest for " + validatorData.getBaseDir());
+ assertCounter(validatorData, ClaimStatistic.Counter.STANDARDS, 3);
+ // .gitignore is ignored by default as it is hidden but not counted
+ assertCounter(validatorData, ClaimStatistic.Counter.IGNORED, 2);
+ });
+ return Arrays.asList(test1, test2, test3);
+ }
+
+ protected void inputIncludeFileTest(final Set result, final Option option) {
+ Consumer setup = mkRat.andThen(basePath -> {
+ DefaultLog.getInstance().warn("inputIncludeFileTest setup for " + basePath);
+ File dir = basePath.resolve(".rat").toFile();
+ writeFile(dir, "include.txt", Arrays.asList(AbstractTestDataProvider.INCLUDE_ARGS));
+ });
+ result.addAll(execIncludeTest(option, new String[]{".rat/include.txt"}, setup));
+ }
+
+
+ protected void inputIncludeTest(final Set result, final Option option) {
+ result.addAll(execIncludeTest(option, AbstractTestDataProvider.INCLUDE_ARGS, mkRat));
+ }
+
+ protected void inputIncludeStdTest(final Set result, final Option option) {
+ Consumer setup = basePath -> {
+ DefaultLog.getInstance().warn("inputIncludeStdTest setup for " + basePath);
+ File baseDir = basePath.toFile();
+ writeFile(baseDir, "afile~more");
+ writeFile(baseDir, "afile~");
+ writeFile(baseDir, ".#afile");
+ writeFile(baseDir, "%afile%");
+ writeFile(baseDir, "._afile");
+ writeFile(baseDir, "what.#afile");
+ writeFile(baseDir, "%afile%withMore");
+ writeFile(baseDir, "well._afile");
+ writeFile(baseDir, ".hiddenFile", "The hidden file");
+ File hiddenDir = new File(baseDir, ".hiddenDir");
+ FileUtils.mkDir(hiddenDir);
+ writeFile(hiddenDir, "aFile", "File in hidden directory");
+ };
+ if (Arg.EXCLUDE.isEmpty()) {
+ throw new RuntimeException(String.format("Can not test %s if there are no exclude file options supported", option.getKey()));
+ }
+ ImmutablePair
copyright = ImmutablePair.of(option, new String[]{"MyCopyright"});
+ if (Arg.EDIT_ADD.isEmpty()) {
+ throw new RuntimeException("Can not execute copyright tests without an EDIT_ADD option avialable");
+ }
+ ImmutablePair
editLicense = ImmutablePair.of(Arg.EDIT_ADD.option(), null);
+
+ result.add(new TestData("noEditLicense", Collections.singletonList(copyright),
+ setup,
+ validatorData -> {
+ DefaultLog.getInstance().warn("validating editCopyrightTest for " + validatorData.getBaseDir());
+ try {
+ String actualText = TextUtils.readFile(validatorData.getBaseDir().resolve("Missing.java").toFile());
+ TextUtils.assertNotContains("MyCopyright", actualText);
+ } catch (IOException ex) {
+ throw new RuntimeException(ex);
+ }
+ }));
+
+ result.add(new TestData(DataUtils.asDirName(editLicense.getLeft()), Arrays.asList(copyright, editLicense),
+ setup,
+ validatorData -> {
+ DefaultLog.getInstance().warn("validating editCopyrightTest for " + validatorData.getBaseDir());
+ try {
+ String actualText = TextUtils.readFile(validatorData.getBaseDir().resolve("Missing.java").toFile());
+ TextUtils.assertNotContains("MyCopyright", actualText);
+ assertThat(validatorData.getBaseDir().resolve("Missing.java.new")).exists();
+ actualText = TextUtils.readFile(validatorData.getBaseDir().resolve("Missing.java.new").toFile());
+ TextUtils.assertContains("MyCopyright", actualText);
+ } catch (IOException ex) {
+ throw new RuntimeException(ex);
+ }
+ }));
+
+ if (!Arg.DRY_RUN.isEmpty()) {
+ Option dryRun = Arg.DRY_RUN.option();
+ result.add(new TestData(DataUtils.asDirName(dryRun),
+ Arrays.asList(copyright, ImmutablePair.of(dryRun, null), editLicense),
+ setup,
+ validatorData -> {
+ DefaultLog.getInstance().warn("validating editCopyrightTest for " + validatorData.getBaseDir());
+ try {
+ String actualText = TextUtils.readFile(validatorData.getBaseDir().resolve("Missing.java").toFile());
+ TextUtils.assertNotContains("MyCopyright", actualText);
+ } catch (IOException ex) {
+ throw new RuntimeException(ex);
+ }
+ assertThat(validatorData.getBaseDir().resolve("Missing.java.new")).doesNotExist();
+ }));
+ }
+
+ if (!Arg.EDIT_OVERWRITE.isEmpty()) {
+ Option overwrite = Arg.EDIT_OVERWRITE.option();
+ result.add(new TestData(DataUtils.asDirName(overwrite), Arrays.asList(copyright, editLicense, ImmutablePair.of(overwrite, null)),
+ setup,
+ validatorData -> {
+ DefaultLog.getInstance().warn("validating editCopyrightTest for " + validatorData.getBaseDir());
+ try {
+ String actualText = TextUtils.readFile(validatorData.getBaseDir().resolve("Missing.java").toFile());
+ TextUtils.assertContains("MyCopyright", actualText);
+ assertThat(validatorData.getBaseDir().resolve("Missing.java.new")).doesNotExist();
+ } catch (IOException ex) {
+ throw new RuntimeException(ex);
+ }
+ }));
+ }
+ }
+
+ protected void editLicenseTest(final Set result, final Option option) {
+ result.add(new TestData("", Collections.singletonList(ImmutablePair.of(option, null)),
+ basePath -> {
+ DefaultLog.getInstance().warn("editLicenseTest setup for " + basePath);
+ File baseDir = basePath.toFile();
+ writeFile(baseDir, "NoLicense.java", "class NoLicense {}");
+ },
+ validatorData -> {
+ DefaultLog.getInstance().warn("validating editLicenseTest for " + validatorData.getBaseDir());
+ try {
+ assertThat(validatorData.getStatistic()).isNotNull();
+ File javaFile = validatorData.getBaseDir().resolve("NoLicense.java").toFile();
+ String contents = String.join("\n", IOUtils.readLines(new FileReader(javaFile)));
+ assertThat(contents).isEqualTo("class NoLicense {}");
+ File resultFile = validatorData.getBaseDir().resolve("NoLicense.java.new").toFile();
+ assertThat(resultFile).exists();
+ contents = String.join("\n", IOUtils.readLines(new FileReader(resultFile)));
+ assertThat(contents).isEqualTo("""
+ /*
+ * 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
+ *\s
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *\s
+ * 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.
+ */
+
+ class NoLicense {}""");
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }));
+ }
+
+ protected void editOverwriteTest(final Set result, final Option option) {
+ result.add(new TestData("noEditLicense", Collections.singletonList(ImmutablePair.of(option, null)),
+ NO_SETUP,
+ validatorData -> {
+ DefaultLog.getInstance().warn("validating editOverwriteTest for " + validatorData.getBaseDir());
+ assertThat(validatorData.getConfiguration().isAddingLicensesForced())
+ .describedAs("Without edit-license should be false").isFalse();
+ }));
+
+ if (!Arg.EDIT_ADD.isEmpty()) {
+ result.add(new TestData("", Arrays.asList(ImmutablePair.of(option, null),
+ ImmutablePair.of(Arg.EDIT_ADD.find("edit-license"), null)),
+ NO_SETUP,
+ validatorData -> {
+ DefaultLog.getInstance().warn("validating editOverwriteTest for " + validatorData.getBaseDir());
+ assertThat(validatorData.getConfiguration().isAddingLicensesForced())
+ .as("testing getConfiguration().isAddingLicensesForced() flag").isTrue();
+ }));
+ }
+ }
+
+ protected void logLevelTest(final Set result, final Option option) {
+ final TestingLog testingLog = new TestingLog();
+
+ Consumer setup = basePath -> {
+ DefaultLog.getInstance().warn("logLevelTest setup for " + basePath);
+ DefaultLog.setInstance(testingLog);
+ testingLog.clear();
+ };
+
+ result.add(new TestData(Log.Level.INFO.name(),
+ Collections.singletonList(ImmutablePair.of(option, new String[]{Log.Level.INFO.name()})),
+ setup,
+ validatorData -> {
+ DefaultLog.getInstance().warn("validating logLevelTest for " + validatorData.getBaseDir());
+ try {
+ testingLog.assertNotContains("DEBUG");
+ } finally {
+ DefaultLog.setInstance(null);
+ }
+ }));
+
+ result.add(new TestData(Log.Level.DEBUG.name(),
+ Collections.singletonList(ImmutablePair.of(option, new String[]{Log.Level.DEBUG.name()})),
+ setup,
+ validatorData -> {
+ DefaultLog.getInstance().warn("validating logLevelTest for " + validatorData.getBaseDir());
+ try {
+ testingLog.assertContains("DEBUG");
+ } finally {
+ DefaultLog.setInstance(null);
+ }
+ }));
+ }
+
+ protected void outputArchiveTest(final Set result, final Option option) {
+ for (ReportConfiguration.Processing processing : ReportConfiguration.Processing.values()) {
+ result.add(new TestData(processing.name().toLowerCase(Locale.ROOT),
+ Collections.singletonList(ImmutablePair.of(option, new String[]{processing.name()})),
+ basePath -> {
+ DefaultLog.getInstance().warn("outputArchiveTest setup for " + basePath);
+ File localArchive = new File(basePath.toFile(), "dummy.jar");
+ try (InputStream in = ReportTestDataProvider.class.getResourceAsStream("/tikaFiles/archive/dummy.jar");
+ OutputStream out = Files.newOutputStream(localArchive.toPath())) {
+ Objects.requireNonNull(in);
+ IOUtils.copy(in, out);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ },
+ validatorData -> {
+ DefaultLog.getInstance().warn("validating outputArchiveTest for " + validatorData.getBaseDir());
+ Document document = validatorData.getDocument();
+ try {
+ XmlUtils.assertIsPresent(processing.name(), document, xpath, "/rat-report/resource[@name='/dummy.jar']");
+ switch (processing) {
+ case ABSENCE:
+ XmlUtils.assertIsPresent(processing.name(), document, xpath, "/rat-report/resource[@name='/dummy.jar']/license[@family='AL ']");
+ XmlUtils.assertIsPresent(processing.name(), document, xpath, "/rat-report/resource[@name='/dummy.jar']/license[@family='?????']");
+ break;
+ case PRESENCE:
+ XmlUtils.assertIsPresent(processing.name(), document, xpath, "/rat-report/resource[@name='/dummy.jar']/license[@family='AL ']");
+ XmlUtils.assertIsNotPresent(processing.name(), document, xpath, "/rat-report/resource[@name='/dummy.jar']/license[@family='?????']");
+ break;
+ case NOTIFICATION:
+ XmlUtils.assertIsNotPresent(processing.name(), document, xpath, "/rat-report/resource[@name='/dummy.jar']/license[@family='AL ']");
+ XmlUtils.assertIsNotPresent(processing.name(), document, xpath, "/rat-report/resource[@name='/dummy.jar']/license[@family='?????']");
+ break;
+ default:
+ throw new IllegalArgumentException("Unexpected processing " + processing);
+ }
+ } catch (XPathExpressionException e) {
+ throw new RuntimeException(e);
+ }
+ })
+ );
+ }
+ }
+
+ protected void outputFamiliesTest(final Set result, final Option option) {
+ for (LicenseSetFactory.LicenseFilter filter : LicenseSetFactory.LicenseFilter.values()) {
+ result.add(new TestData(filter.name().toLowerCase(Locale.ROOT),
+ Collections.singletonList(ImmutablePair.of(option, new String[]{filter.name()})),
+ NO_SETUP,
+ validatorData -> {
+ DefaultLog.getInstance().warn("validating outputFamiliesTest for " + validatorData.getBaseDir());
+ Document document = validatorData.getDocument();
+ try {
+ switch (filter) {
+ case ALL:
+ XmlUtils.assertIsPresent(filter.name(), document, xpath, "/rat-report/rat-config/families/family[@id='AL']");
+ XmlUtils.assertIsPresent(filter.name(), document, xpath, "/rat-report/rat-config/families/family[@id='GPL']");
+ break;
+ case APPROVED:
+ XmlUtils.assertIsPresent(filter.name(), document, xpath, "/rat-report/rat-config/families/family[@id='AL']");
+ XmlUtils.assertIsNotPresent(filter.name(), document, xpath, "/rat-report/rat-config/families/family[@id='GPL']");
+ break;
+ case NONE:
+ XmlUtils.assertIsNotPresent(filter.name(), document, xpath, "/rat-report/rat-config/families/family[@id='AL']");
+ XmlUtils.assertIsNotPresent(filter.name(), document, xpath, "/rat-report/rat-config/families/family[@id='GPL']");
+ break;
+ default:
+ throw new IllegalArgumentException("Unexpected filter: " + filter);
+ }
+ } catch (XPathExpressionException e) {
+ throw new RuntimeException(e);
+ }
+ })
+ );
+ }
+ }
+
+ protected void outputFileTest(final Set result, final Option option) {
+ result.add(new TestData("", Collections.singletonList(ImmutablePair.of(option, new String[]{"outexample"})),
+ basePath -> {
+ DefaultLog.getInstance().warn("outputFileTest setup for " + basePath);
+ File baseDir = basePath.toFile();
+ writeFile(baseDir, "apl.txt", "SPDX-License-Identifier: Apache-2.0");
+ },
+ validatorData -> {
+ DefaultLog.getInstance().warn("validating outputFamiliesTest for " + validatorData.getBaseDir());
+ assertCounter(validatorData, ClaimStatistic.Counter.STANDARDS, 1);
+ assertCounter(validatorData, ClaimStatistic.Counter.APPROVED, 1);
+ assertCounter(validatorData, ClaimStatistic.Counter.UNAPPROVED, 0);
+ File outFile = validatorData.getBaseDir().resolve("outexample").toFile();
+
+ try {
+ String actualText = TextUtils.readFile(outFile);
+ TextUtils.assertPatternInTarget("Apache License 2.0: \\d", actualText);
+ TextUtils.assertPatternInTarget("STANDARD:\\s+\\d", actualText);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ })
+ );
+ }
+
+ protected void outputLicensesTest(final Set result, final Option option) {
+ for (LicenseSetFactory.LicenseFilter filter : LicenseSetFactory.LicenseFilter.values()) {
+ result.add(new TestData(filter.name(), Collections.singletonList(ImmutablePair.of(option, new String[]{filter.name()})),
+ NO_SETUP,
+ validatorData -> {
+ DefaultLog.getInstance().warn("validating outputLicensesTest for " + validatorData.getBaseDir());
+ Document document = validatorData.getDocument();
+ try {
+ switch (filter) {
+ case ALL:
+ XmlUtils.assertIsPresent(filter.name(), document, xpath, "/rat-report/rat-config/licenses/license[@id='AL2.0']");
+ XmlUtils.assertIsPresent(filter.name(), document, xpath, "/rat-report/rat-config/licenses/license[@id='GPL1']");
+ break;
+ case APPROVED:
+ XmlUtils.assertIsPresent(filter.name(), document, xpath, "/rat-report/rat-config/licenses/license[@id='AL2.0']");
+ XmlUtils.assertIsNotPresent(filter.name(), document, xpath, "/rat-report/rat-config/licenses/license[@id='GPL1']");
+ break;
+ case NONE:
+ XmlUtils.assertIsNotPresent(filter.name(), document, xpath, "/rat-report/rat-config/licenses/license[@id='AL2.0']");
+ XmlUtils.assertIsNotPresent(filter.name(), document, xpath, "/rat-report/rat-config/licenses/license[@id='GPL1']");
+ break;
+ default:
+ throw new IllegalArgumentException("Unexpected filter: " + filter);
+ }
+ } catch (XPathExpressionException e) {
+ throw new RuntimeException(e);
+ }
+ }));
+ }
+ }
+
+
+ protected void outputStandardTest(final Set result, final Option option) {
+ for (ReportConfiguration.Processing proc : ReportConfiguration.Processing.values()) {
+ result.add(new TestData(proc.name().toLowerCase(Locale.ROOT), Collections.singletonList(ImmutablePair.of(option, new String[]{proc.name()})),
+ basePath -> {
+ DefaultLog.getInstance().warn("outputStandardTest setup for " + basePath);
+ File baseDir = basePath.toFile();
+ writeFile(baseDir, "Test.java", Arrays.asList("/*\n", "SPDX-License-Identifier: Apache-2.0\n",
+ "*/\n\n", "class Test {}\n"));
+ writeFile(baseDir, "Missing.java", Arrays.asList("/* no license */\n\n", "class Test {}\n"));
+ },
+ validatorData -> {
+ DefaultLog.getInstance().warn("validating outputStandardTest for " + validatorData.getBaseDir());
+ Document document = validatorData.getDocument();
+ String testDoc = "/rat-report/resource[@name='/Test.java']";
+ String missingDoc = "/rat-report/resource[@name='/Missing.java']";
+ try {
+ XmlUtils.assertIsPresent(proc.name(), document, xpath, testDoc);
+ XmlUtils.assertIsPresent(proc.name(), document, xpath, missingDoc);
+
+ switch (proc) {
+ case ABSENCE:
+ XmlUtils.assertIsPresent(proc.name(), document, xpath, testDoc + "/license[@family='AL ']");
+ XmlUtils.assertIsPresent(proc.name(), document, xpath, missingDoc + "/license[@family='?????']");
+ break;
+ case PRESENCE:
+ XmlUtils.assertIsPresent(proc.name(), document, xpath, testDoc + "/license[@family='AL ']");
+ XmlUtils.assertIsNotPresent(proc.name(), document, xpath, missingDoc + "/license[@family='?????']");
+ break;
+ case NOTIFICATION:
+ XmlUtils.assertIsNotPresent(proc.name(), document, xpath, testDoc + "/license[@family='AL ']");
+ XmlUtils.assertIsNotPresent(proc.name(), document, xpath, missingDoc + "/license[@family='?????']");
+ break;
+ default:
+ throw new IllegalArgumentException("Unexpected processing " + proc);
+ }
+ } catch (XPathExpressionException ex) {
+ throw new RuntimeException(ex);
+ }
+ }));
+ }
+ }
+
+
+ protected void outputStyleTest(final Set result, final Option option) {
+ Consumer createFile = basePath -> {
+ DefaultLog.getInstance().warn("outputStyleTest setup for " + basePath);
+ File baseDir = basePath.toFile();
+ writeFile(baseDir, "Test.java", Arrays.asList("/*\n", "SPDX-License-Identifier: Apache-2.0\n",
+ "*/\n\n", "class Test {}\n"));
+ writeFile(baseDir, "Missing.java", Arrays.asList("/* no license */\n\n", "class Test {}\n"));
+ };
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ if (!option.hasArg()) {
+ if (option.getLongOpt().equals("xml")) {
+ result.add(new TestData("", Collections.singletonList(ImmutablePair.of(option, null)),
+ createFile,
+ validatorData -> {
+ DefaultLog.getInstance().warn("validating outputStyleTest for " + validatorData.getBaseDir());
+ try (InputStream expected = StyleSheets.XML.getStyleSheet().ioSupplier().get();
+ InputStream actual = validatorData.getConfiguration().getStyleSheet().get()) {
+ assertThat(IOUtils.contentEquals(expected, actual)).as(() -> String.format("'%s' does not match", StyleSheets.XML)).isTrue();
+ baos.reset();
+ validatorData.getOutput().format(validatorData.getConfiguration().getStyleSheet(), () -> baos);
+ String actualText = baos.toString();
+ TextUtils.assertContainsExactly(1, "", actualText);
+ TextUtils.assertContainsExactly(1, "", actualText);
+ } catch (IOException | RatException e) {
+ throw new RuntimeException(e);
+ }
+ }));
+ }
+ } else {
+ for (StyleSheets sheet : StyleSheets.values()) {
+ result.add(new TestData(sheet.name().toLowerCase(Locale.ROOT), Collections.singletonList(ImmutablePair.of(option, new String[]{sheet.arg()})),
+ createFile,
+ validatorData -> {
+ DefaultLog.getInstance().warn("validating outputStyleTest for " + validatorData.getBaseDir());
+ try (InputStream expected = sheet.getStyleSheet().ioSupplier().get();
+ InputStream actual = validatorData.getConfiguration().getStyleSheet().get()) {
+ assertThat(IOUtils.contentEquals(expected, actual)).as(() -> String.format("'%s' does not match", sheet)).isTrue();
+ baos.reset();
+ validatorData.getOutput().format(validatorData.getConfiguration().getStyleSheet(), () -> baos);
+ String actualText = baos.toString();
+ switch (sheet) {
+ case MISSING_HEADERS:
+ TextUtils.assertContainsExactly(1, "Files with missing headers:" + System.lineSeparator() +
+ " /Missing.java", actualText);
+ break;
+ case PLAIN:
+ TextUtils.assertContainsExactly(1, "Unknown license: 1 ", actualText);
+ TextUtils.assertContainsExactly(1, "?????: 1 ", actualText);
+ break;
+ case XML:
+ TextUtils.assertContainsExactly(1, "", actualText);
+ TextUtils.assertContainsExactly(1, "", actualText);
+ break;
+ case UNAPPROVED_LICENSES:
+ TextUtils.assertContainsExactly(1, "Files with unapproved licenses:" + System.lineSeparator() + " /Missing.java", 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;
+ }
+ } catch (IOException | RatException e) {
+ throw new RuntimeException(e);
+ }
+ }));
+ }
+
+ result.add(new TestData("fileStyleSheet",
+ Collections.singletonList(ImmutablePair.of(option, new String[]{"fileStyleSheet.xslt"})),
+ basePath -> {
+ DefaultLog.getInstance().warn("outputStyleTest setup for " + basePath);
+ DocumentName name = DocumentName.builder().setName("fileStyleSheet.xslt")
+ .setBaseName(basePath.toString()).build();
+ try (FileWriter fileWriter = new FileWriter(new File(name.getBaseName(), name.getName()));
+ XmlWriter writer = new XmlWriter(fileWriter)) {
+ writer.startDocument()
+ .comment(DataUtils.ASF_TEXT)
+ .openElement("xsl:stylesheet")
+ .attribute("version", "1.0")
+ .attribute("xmlns:xsl", "http://www.w3.org/1999/XSL/Transform")
+ .openElement("xsl:template")
+ .attribute("match", "@*|node()")
+ .content("Hello World")
+ .closeDocument();
+ } catch (IOException e) {
+ fail(e.getMessage(), e);
+ }
+ },
+
+ validatorData -> {
+ DefaultLog.getInstance().warn("validating outputStyleTest for " + validatorData.getBaseDir());
+ try (InputStream expected = StyleSheets.getStyleSheet("fileStyleSheet.xslt", validatorData.getBaseName()).ioSupplier().get();
+ InputStream actual = validatorData.getConfiguration().getStyleSheet().get()) {
+ assertThat(IOUtils.contentEquals(expected, actual)).as(() -> "'fileStyleSheet.xslt' does not match").isTrue();
+ baos.reset();
+ validatorData.getOutput().format(validatorData.getConfiguration().getStyleSheet(), () -> baos);
+ String actualText = baos.toString();
+ TextUtils.assertContainsExactly(1, "Hello World", actualText);
+ } catch (IOException | RatException e) {
+ throw new RuntimeException(e);
+ }
+ }));
+ }
+ }
+}
diff --git a/apache-rat-core/src/test/java/org/apache/rat/testhelpers/data/TestData.java b/apache-rat-core/src/test/java/org/apache/rat/testhelpers/data/TestData.java
new file mode 100644
index 000000000..84c8c213f
--- /dev/null
+++ b/apache-rat-core/src/test/java/org/apache/rat/testhelpers/data/TestData.java
@@ -0,0 +1,232 @@
+/*
+ * 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 java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Consumer;
+import org.apache.commons.cli.Option;
+import org.apache.commons.lang3.tuple.ImmutablePair;
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.commons.text.WordUtils;
+import org.apache.rat.ui.ArgumentTracker;
+import org.apache.rat.utils.CasedString;
+import org.apache.rat.utils.FileUtils;
+
+/**
+ * The definition of a test.
+ */
+public final class TestData implements Comparable {
+ /** if set, the expected exception from the test. */
+ private Exception expectedException;
+ /** The sub name of the test */
+ private final String name;
+ /** The command line for the test */
+ private final List> commandLine;
+ /** A function to set up the test in a specific path */
+ private final Consumer setupFiles;
+ /** A function to test the results of the test. */
+ private final Consumer validator;
+
+ /**
+ * Constructs the Test data
+ * @param name the sub name of the test. May not be {@code null} but may be an empty string. Should
+ * be specified in Camel case for multiple words.
+ * @param commandLine The command line for the test. May not be {@code null} but may consist of a single {@link ImmutablePair#nullPair()}.
+ * @param setupFiles the method to set up the files for the test. May not be {@code null}.
+ * @param validator the validator for the results of the test. May not be {@code null}.
+ */
+ public TestData(String name, List> commandLine,
+ Consumer setupFiles,
+ Consumer validator) {
+ Objects.requireNonNull(name, " name cannot be null");
+ Objects.requireNonNull(commandLine, "commandLine cannot be null");
+ Objects.requireNonNull(setupFiles, "setupFiles cannot be null");
+ Objects.requireNonNull(validator, "validator cannot be null");
+ if (name.contains("/")) {
+ throw new IllegalArgumentException("name may not contain '/', use camel case instead");
+ }
+ if (commandLine.isEmpty()) {
+ throw new IllegalArgumentException("commandLine may not be empty but contain an ImmutablePair.nullPair()");
+ }
+ this.name = name;
+ this.commandLine = commandLine;
+ this.setupFiles = setupFiles;
+ this.validator = validator;
+ this.expectedException = null;
+ }
+
+ @Override
+ public String toString() {
+ return getTestName();
+ }
+
+ /**
+ * Sets the exception expected from this test.
+ * @param expectedException the expected exception.
+ */
+ void setException(Exception expectedException) {
+ this.expectedException = expectedException;
+ }
+
+ /**
+ * The option for the test. This is the first option specified in the command line.
+ * If the command line is empty this returns {@code null}.
+ * @return the first option in the command line or {@code null} if there is no option.
+ */
+ public Option getOption() {
+ return commandLine.get(0).getLeft();
+ }
+
+ /**
+ * Get the sub name for this test.
+ * @return the sub name for this test.
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Gets the expected exception or {@code null} if not exception is expected.
+ * @return the expected exception or {@code null} if not exception is expected.
+ */
+ public Exception getExpectedException() {
+ return expectedException;
+ }
+
+ /**
+ * Determines if the test is expecting an exception.
+ * @return {@code true} if the test is expecting to throw an exception.
+ */
+ public boolean expectingException() {
+ return expectedException != null;
+ }
+
+ /**
+ * Gets the command line as the string objects that are normally parsed by the
+ * command line parser.
+ * @return the command line strings.
+ */
+ public String[] getCommandLine() {
+ return getCommandLine(null);
+ }
+
+ /**
+ * Gets the command line as the string objects that are normally parsed by the
+ * command line parser. The result will include "--" to terminate a trailing multi
+ * argument option.
+ * @param workingDir the directory to add to the command line. May be {@code null}.
+ * @return the command line strings.
+ */
+ public String[] getCommandLine(String workingDir) {
+ List args = new ArrayList<>(commandLine.size());
+ final boolean[] lastWasMultiArg = {false};
+ commandLine.forEach(pair -> {
+ if (pair.getKey() != null) {
+ if (pair.getKey().hasLongOpt()) {
+ args.add("--" + pair.getKey().getLongOpt());
+ } else {
+ args.add("-" + pair.getKey().getOpt());
+ }
+ if (pair.getValue() != null) {
+ args.addAll(Arrays.asList(pair.getValue()));
+ }
+ lastWasMultiArg[0] = pair.getKey().hasArgs();
+ } else {
+ lastWasMultiArg[0] = false;
+ }
+ });
+ if (lastWasMultiArg[0]) {
+ args.add("--");
+ }
+ if (workingDir != null) {
+ args.add(workingDir);
+ }
+ return args.toArray(new String[0]);
+ }
+
+ /**
+ * Get the arguments for the command line.
+ * @return the arguments for the command line as Option, String[] pairs.
+ */
+ public List extends Pair
> getArgs() {
+ return commandLine;
+ }
+
+ /**
+ * Gets the validator for the test.
+ * @return the validator for the test.
+ */
+ public Consumer getValidator() {
+ return validator;
+ }
+
+ /**
+ * Sets up the files for the test.
+ * @param path the path to use as the base directory. Subdirectories and files may be added
+ * to this path.
+ */
+ public void setupFiles(Path path) {
+ FileUtils.mkDir(path.toFile());
+ setupFiles.accept(path);
+ }
+
+ /**
+ * Gets the test name. This is the option concatenated with the name.
+ * @return the unique test name
+ */
+ public String getTestName() {
+ new ArrayList<>();
+ String result = null;
+ if (getOption() == null) {
+ result = name + "_DefaultTest";
+ } else {
+ result = new CasedString(CasedString.StringCase.KEBAB, ArgumentTracker.extractKey(getOption())).toString();
+ if (!name.isEmpty()) {
+ result += "/" + name;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Gets the test name as a class name. This is based on the option concatenated with the name.
+ * @return the unique Java class name
+ */
+ public String getClassName() {
+ if (getOption() == null) {
+ return WordUtils.capitalize(name) + "_DefaultTest";
+ } else {
+ List parts = new ArrayList<>(Arrays.asList(new CasedString(CasedString.StringCase.KEBAB, ArgumentTracker.extractKey(getOption())).getSegments()));
+ if (!name.isEmpty()) {
+ parts.addAll(Arrays.asList(new CasedString(CasedString.StringCase.CAMEL, name).getSegments()));
+ }
+ return CasedString.StringCase.PASCAL.assemble(parts.toArray(new String[0]));
+ }
+ }
+
+ @Override
+ public int compareTo(TestData other) {
+ return getTestName().compareTo(other.getTestName());
+ }
+
+}
diff --git a/apache-rat-core/src/test/java/org/apache/rat/testhelpers/data/ValidatorData.java b/apache-rat-core/src/test/java/org/apache/rat/testhelpers/data/ValidatorData.java
new file mode 100644
index 000000000..a4745fe4f
--- /dev/null
+++ b/apache-rat-core/src/test/java/org/apache/rat/testhelpers/data/ValidatorData.java
@@ -0,0 +1,96 @@
+/*
+ * 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 java.nio.file.Path;
+import java.nio.file.Paths;
+import org.apache.rat.ReportConfiguration;
+import org.apache.rat.Reporter;
+import org.apache.rat.document.DocumentName;
+import org.apache.rat.report.claim.ClaimStatistic;
+import org.w3c.dom.Document;
+
+/**
+ * The validator for test data.
+ */
+public final class ValidatorData {
+ /** The report output */
+ private final Reporter.Output output;
+ /** the base directory where the test setup was created */
+ private final Path baseDir;
+
+ /**
+ * Constructor.
+ * @param output The report output.
+ * @param baseDir the directory where the test setup was created.
+ */
+ public ValidatorData(final Reporter.Output output, final String baseDir) {
+ this.output = output;
+ this.baseDir = Paths.get(baseDir);
+ }
+
+ /**
+ * Gets the directory where the test setup was created as a DocumentName.
+ * @return the DocumentName for the baseDir directory.
+ */
+ public DocumentName getBaseName() {
+ return DocumentName.builder(baseDir.toFile()).build();
+ }
+
+ /**
+ * Creates a DocumentName for a file name. The root of the document Name will be the baseDir.
+ * @param fileName the file name to create a document name for.
+ * @return the Document name for the file.
+ */
+ public DocumentName mkDocName(String fileName) {
+ return DocumentName.builder().setBaseName(baseDir.toFile()).setName(fileName).build();
+ }
+
+ public Reporter.Output getOutput() {
+ return output;
+ }
+ /**
+ * Gets the document that was generated during execution.
+ * @return the document that was generated during execution.
+ */
+ public Document getDocument() {
+ return output.getDocument();
+ }
+
+ /**
+ * Gets the claim statistics from the run
+ * @return the ClaimStatistic from the run.
+ */
+ public ClaimStatistic getStatistic() {
+ return output.getStatistic();
+ }
+
+ /**
+ * Gets the configuration from the run.
+ * @return the configuration from the run.
+ */
+ public ReportConfiguration getConfiguration() {
+ return output.getConfiguration();
+ }
+
+ public Path getBaseDir() {
+ return baseDir;
+ }
+
+}
diff --git a/apache-rat-core/src/test/java/org/apache/rat/ui/ArgumentTrackerTest.java b/apache-rat-core/src/test/java/org/apache/rat/ui/ArgumentTrackerTest.java
new file mode 100644
index 000000000..74fd2810c
--- /dev/null
+++ b/apache-rat-core/src/test/java/org/apache/rat/ui/ArgumentTrackerTest.java
@@ -0,0 +1,121 @@
+/*
+ * 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.ui;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Predicate;
+import org.apache.commons.cli.Option;
+import org.apache.rat.commandline.Arg;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.apache.rat.ui.UIOptionCollectionTest.TestingUIOptionCollection;
+import static org.apache.rat.ui.UIOptionCollectionTest.TestingUIOption;
+
+class ArgumentTrackerTest {
+ private ArgumentTracker underTest;
+ private TestingUIOptionCollection testingUIOptionCollection;
+
+ @BeforeEach
+ void setUp() {
+ testingUIOptionCollection = new TestingUIOptionCollection();
+ underTest = new ArgumentTracker(testingUIOptionCollection);
+ }
+
+ @Test
+ void extractKey() {
+ assertThat(ArgumentTracker.extractKey(Option.builder().longOpt("foo").build())).isEqualTo("foo");
+ assertThat(ArgumentTracker.extractKey(Option.builder("b").build())).isEqualTo("b");
+ assertThat(ArgumentTracker.extractKey(Option.builder("b").longOpt("foo").build())).isEqualTo("foo");
+ assertThatThrownBy(() -> ArgumentTracker.extractKey(Option.builder().build())) // NOSONAR
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("Either opt or longOpt must be specified");
+ }
+
+ @Test
+ void args() {
+ // no args to start
+ assertThat(underTest.args()).isEmpty();
+ Option option = findOptionWithArgs(1);
+ String string1 = String.format("--%s foo", ArgumentTracker.extractKey(option));
+ TestingUIOption mappedOption = testingUIOptionCollection.getMappedOption(option).get();
+ underTest.setArg(mappedOption, "foo");
+ option = findOptionWithArgs(2);
+ String string2 = String.format("--%s bar baz", ArgumentTracker.extractKey(option));
+ mappedOption = testingUIOptionCollection.getMappedOption(option).get();
+ underTest.addArg(mappedOption, "bar");
+ underTest.addArg(mappedOption, "baz");
+ String join = String.join(" ", underTest.args());
+ assertThat(join).contains(string1, string2);
+ }
+
+ @Test
+ void setArg() {
+ Option option = findOptionWithArgs(1);
+ TestingUIOption mappedOption = testingUIOptionCollection.getMappedOption(option).get();
+ underTest.setArg(mappedOption, "foo");
+ assertThat(underTest.getArg(mappedOption.keyValue())).contains(List.of("foo"));
+ }
+
+ private Option findOptionWithArgs(int number) {
+ Predicate
filter;
+ if (number <= 0) {
+ filter = opt -> !opt.hasArg();
+ } else if (number == 1) {
+ filter = opt -> opt.hasArg() && ! opt.hasArgs();
+ } else {
+ filter = Option::hasArgs;
+ }
+ return Arrays.stream(Arg.values()).map(Arg::option).filter(filter).findAny().orElseThrow();
+ }
+
+ @Test
+ void addArg() {
+ Option option = findOptionWithArgs(2);
+ TestingUIOption mappedOption = testingUIOptionCollection.getMappedOption(option).get();
+ underTest.addArg(mappedOption, "foo");
+ assertThat(underTest.getArg(mappedOption.keyValue())).contains(List.of("foo"));
+ underTest.addArg(mappedOption, "bar");
+ assertThat(underTest.getArg(mappedOption.keyValue())).contains(List.of("foo", "bar"));
+ }
+
+ @Test
+ void setOverridesAddArg() {
+ Option option = findOptionWithArgs(2);
+ TestingUIOption mappedOption = testingUIOptionCollection.getMappedOption(option).get();
+ underTest.addArg(mappedOption, "foo");
+ assertThat(underTest.getArg(mappedOption.keyValue())).contains(List.of("foo"));
+ underTest.addArg(mappedOption, "bar");
+ assertThat(underTest.getArg(mappedOption.keyValue())).contains(List.of("foo", "bar"));
+ underTest.setArg(mappedOption, "baz");
+ assertThat(underTest.getArg(mappedOption.keyValue())).contains(List.of("baz"));
+ }
+
+ @Test
+ void invalidAbstractOption() {
+ Option option = Option.builder().longOpt("notAValidOption").build();
+ TestingUIOption invalidOption = new TestingUIOption(testingUIOptionCollection, option);
+ underTest.addArg(invalidOption, "foo");
+ assertThat(underTest.getArg(invalidOption.keyValue())).isEmpty();
+ }
+
+}
diff --git a/apache-rat-core/src/test/java/org/apache/rat/ui/UIOptionCollectionTest.java b/apache-rat-core/src/test/java/org/apache/rat/ui/UIOptionCollectionTest.java
new file mode 100644
index 000000000..871bc6841
--- /dev/null
+++ b/apache-rat-core/src/test/java/org/apache/rat/ui/UIOptionCollectionTest.java
@@ -0,0 +1,139 @@
+/*
+ * 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.ui;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import org.apache.commons.cli.AlreadySelectedException;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.OptionGroup;
+import org.apache.commons.cli.Options;
+import org.apache.rat.commandline.Arg;
+import org.apache.rat.utils.CasedString;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class UIOptionCollectionTest {
+
+ public static final Option UI_OPTION = Option.builder("ui-option1").build();
+ public static final Option DEPRECATED_UI_OPTION = Option.builder("ui-option2").deprecated().build();
+
+ static class TestingUIOptionCollection extends UIOptionCollection