This is an automated email from the ASF dual-hosted git repository. claude pushed a commit to branch add-ui-package in repository https://gitbox.apache.org/repos/asf/creadur-rat.git
commit d1ac2509d118eba44c354cd2ab9cc2b4337dc212 Author: Claude Warren <[email protected]> AuthorDate: Tue Mar 10 17:07:08 2026 +0000 initial code --- apache-rat-core/pom.xml | 4 + .../org/apache/rat/OptionCollectionParser.java | 327 +++++++++++++++++++++ .../rat/commandline/UpdatableOptionGroup.java | 60 ++++ .../org/apache/rat/ui/AbstractCodeGenerator.java | 143 +++++++++ .../java/org/apache/rat/ui/AbstractOption.java | 232 +++++++++++++++ .../apache/rat/ui/AbstractOptionCollection.java | 162 ++++++++++ .../java/org/apache/rat/ui/ArgumentTracker.java | 211 +++++++++++++ .../src/main/java/org/apache/rat/ui/UI.java | 36 +++ .../main/java/org/apache/rat/ui/package-info.java | 22 ++ .../java/org/apache/rat/ui/spi/UIProvider.java | 25 ++ .../java/org/apache/rat/ui/spi/package-info.java | 22 ++ pom.xml | 5 + 12 files changed, 1249 insertions(+) diff --git a/apache-rat-core/pom.xml b/apache-rat-core/pom.xml index 4819f23b..d4bf3b5d 100644 --- a/apache-rat-core/pom.xml +++ b/apache-rat-core/pom.xml @@ -176,6 +176,10 @@ </plugins> </build> <dependencies> + <dependency> + <groupId>com.github.spotbugs</groupId> + <artifactId>spotbugs-annotations</artifactId> + </dependency> <dependency> <groupId>org.apache.rat</groupId> <artifactId>apache-rat-testdata</artifactId> diff --git a/apache-rat-core/src/main/java/org/apache/rat/OptionCollectionParser.java b/apache-rat-core/src/main/java/org/apache/rat/OptionCollectionParser.java new file mode 100644 index 00000000..21c34367 --- /dev/null +++ b/apache-rat-core/src/main/java/org/apache/rat/OptionCollectionParser.java @@ -0,0 +1,327 @@ +/* + * 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; + +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.Serial; +import java.io.Serializable; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Comparator; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.DefaultParser; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; +import org.apache.rat.api.Document; +import org.apache.rat.commandline.Arg; +import org.apache.rat.commandline.ArgumentContext; +import org.apache.rat.commandline.StyleSheets; +import org.apache.rat.config.exclusion.StandardCollection; +import org.apache.rat.document.DocumentName; +import org.apache.rat.document.DocumentNameMatcher; +import org.apache.rat.document.FileDocument; +import org.apache.rat.help.Licenses; +import org.apache.rat.license.LicenseSetFactory; +import org.apache.rat.report.IReportable; +import org.apache.rat.report.claim.ClaimStatistic; +import org.apache.rat.ui.AbstractOptionCollection; +import org.apache.rat.utils.DefaultLog; +import org.apache.rat.utils.Log.Level; +import org.apache.rat.walker.ArchiveWalker; +import org.apache.rat.walker.DirectoryWalker; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +import static java.lang.String.format; + +/** + * Uses the AbstractOptionCollection to parse the command line options. + * contains utility methods to ReportConfiguration from the options and an array of arguments. + */ +@SuppressFBWarnings("EI_EXPOSE_REP2") +public final class OptionCollectionParser { + /** The OptionCollection that we are working with */ + private final AbstractOptionCollection<?> optionCollection; + + public OptionCollectionParser(final AbstractOptionCollection<?> optionCollection) { + this.optionCollection = optionCollection; + } + + /** The Option comparator to sort the help */ + public static final Comparator<Option> OPTION_COMPARATOR = new OptionComparator(); + + /** + * Join a collection of objects together as a comma separated list of their string values. + * @param args the objects to join together. + * @return the comma separated string. + */ + private static String asString(final Object[] args) { + return Arrays.stream(args).map(Object::toString).collect(Collectors.joining(", ")); + } + + /** + * Parses the standard options to create a ReportConfiguration. + * + * @param workingDirectory The directory to resolve relative file names against. + * @param args the arguments to parse + * @return the ArgumentContext for the process. + * @throws IOException on error. + * @throws ParseException on option parsing error. + */ + public ArgumentContext parseCommands(final File workingDirectory, final String[] args) + throws IOException, ParseException { + return parseCommands(workingDirectory, args, optionCollection.getOptions()); + } + + /** + * Parse the options into the command line. + * @param opts the option definitions. + * @param args the argument to apply the definitions to. + * @return the CommandLine + * @throws ParseException on option parsing error. + */ + //@VisibleForTesting + CommandLine parseCommandLine(final Options opts, final String[] args) throws ParseException { + try { + return DefaultParser.builder().setDeprecatedHandler(DeprecationReporter.getLogReporter()) + .setAllowPartialMatching(true).build().parse(opts, args); + } catch (ParseException e) { + DefaultLog.getInstance().error(e.getMessage()); + DefaultLog.getInstance().error("Please use the \"--help\" option to see a list of valid commands and options.", e); + throw e; + } + } + + /** + * Parses the standard options to create a ReportConfiguration. + * + * @param workingDirectory The directory to resolve relative file names against. + * @param args the arguments to parse. + * @param options An Options object containing Apache command line options. + * @return the ArgumentContext for the process. + * @throws IOException on error. + * @throws ParseException on option parsing error. + */ + private ArgumentContext parseCommands(final File workingDirectory, final String[] args, + final Options options) throws IOException, ParseException { + + CommandLine commandLine = parseCommandLine(options, args); + + Arg.processLogLevel(commandLine); + + ArgumentContext argumentContext = new ArgumentContext(workingDirectory, commandLine); + populateConfiguration(argumentContext); + if (commandLine.hasOption(Arg.HELP_LICENSES.option())) { + new Licenses(argumentContext.getConfiguration(), + new PrintWriter(argumentContext.getConfiguration().getOutput().get(), + false, StandardCharsets.UTF_8)).printHelp(); + } + + return argumentContext; + } + + /** + * Create the report configuration. + * Note: this method is package private for testing. + * You probably want one of the {@code ParseCommands} methods. + * @param argumentContext The context to execute in. + * @return a ReportConfiguration + */ + ReportConfiguration populateConfiguration(final ArgumentContext argumentContext) { + argumentContext.processArgs(); + final ReportConfiguration configuration = argumentContext.getConfiguration(); + final CommandLine commandLine = argumentContext.getCommandLine(); + if (!configuration.hasSource()) { + for (String s : commandLine.getArgs()) { + IReportable reportable = getReportable(new File(s), configuration); + if (reportable != null) { + configuration.addSource(reportable); + } + } + } + return configuration; + } + + /** + * Creates an IReportable object from the directory name and ReportConfiguration + * object. + * + * @param base the directory that contains the files to report on. + * @param config the ReportConfiguration. + * @return the IReportable instance containing the files. + */ + IReportable getReportable(final File base, final ReportConfiguration config) { + File absBase = base.getAbsoluteFile(); + DocumentName documentName = DocumentName.builder(absBase).build(); + if (!absBase.exists()) { + DefaultLog.getInstance().error("Directory '" + documentName + "' does not exist."); + return null; + } + DocumentNameMatcher documentExcluder = config.getDocumentExcluder(documentName); + + Document doc = new FileDocument(documentName, absBase, documentExcluder); + if (!documentExcluder.matches(doc.getName())) { + DefaultLog.getInstance().error("Directory '" + documentName + "' is in excluded list."); + return null; + } + + if (absBase.isDirectory()) { + return new DirectoryWalker(doc); + } + + return new ArchiveWalker(doc); + } + + /** + * This class implements the {@code Comparator} interface for comparing Options. + */ + private static final class OptionComparator implements Comparator<Option>, Serializable { + /** The serial version UID. */ + @Serial + private static final long serialVersionUID = 5305467873966684014L; + + private String getKey(final Option opt) { + String key = opt.getOpt(); + key = key == null ? opt.getLongOpt() : key; + return key; + } + + /** + * Compares its two arguments for order. Returns a negative integer, zero, or a + * positive integer as the first argument is less than, equal to, or greater + * than the second. + * + * @param opt1 The first Option to be compared. + * @param opt2 The second Option to be compared. + * @return a negative integer, zero, or a positive integer as the first argument + * is less than, equal to, or greater than the second. + */ + @Override + public int compare(final Option opt1, final Option opt2) { + return getKey(opt1).compareToIgnoreCase(getKey(opt2)); + } + } + + public enum ArgumentType { + /** + * A plain file. + */ + FILE("File", () -> "A file name."), + /** + * An Integer. + */ + INTEGER("Integer", () -> "An integer value."), + /** + * A directory or archive. + */ + DIRORARCHIVE("DirOrArchive", () -> "A directory or archive file to scan."), + /** + * A matching expression. + */ + EXPRESSION("Expression", () -> "A file matching pattern usually of the form used in Ant build files and " + + "'.gitignore' files (see https://ant.apache.org/manual/dirtasks.html#patterns for examples). " + + "Regular expression patterns may be specified by surrounding the pattern with '%regex[' and ']'. " + + "For example '%regex[[A-Z].*]' would match files and directories that start with uppercase latin letters."), + /** + * A license filter. + */ + LICENSEFILTER("LicenseFilter", () -> format("A defined filter for the licenses to include. Valid values: %s.", + asString(LicenseSetFactory.LicenseFilter.values()))), + /** + * A log level. + */ + LOGLEVEL("LogLevel", () -> format("The log level to use. Valid values %s.", asString(Level.values()))), + /** + * A processing type. + */ + PROCESSINGTYPE("ProcessingType", () -> format("Specifies how to process file types. Valid values are: %s%n", + Arrays.stream(ReportConfiguration.Processing.values()) + .map(v -> format("\t%s: %s", v.name(), v.desc())) + .collect(Collectors.joining(System.lineSeparator())))), + /** + * A style sheet. + */ + STYLESHEET("StyleSheet", () -> format("Either an external XSLT file or one of the internal named sheets. Internal sheets are: %n%s", + Arrays.stream(StyleSheets.values()) + .map(v -> format("\t%s: %s%n", v.arg(), v.desc())) + .collect(Collectors.joining(System.lineSeparator())))), + /** + * A license id. + */ + LICENSEID("LicenseID", () -> "The ID for a license."), + /** + * A license family id. + */ + FAMILYID("FamilyID", () -> "The ID for a license family."), + /** + * A standard collection name. + */ + STANDARDCOLLECTION("StandardCollection", () -> format("Defines standard expression patterns (see above). Valid values are: %n%s%n", + Arrays.stream(StandardCollection.values()) + .map(v -> format("\t%s: %s%n", v.name(), v.desc())) + .collect(Collectors.joining(System.lineSeparator())))), + /** + * A Counter pattern name + */ + COUNTERPATTERN("CounterPattern", () -> format("A pattern comprising one of the following prefixes followed by " + + "a colon and a count (e.g. %s:5). Prefixes are %n%s.", ClaimStatistic.Counter.UNAPPROVED, + Arrays.stream(ClaimStatistic.Counter.values()) + .map(v -> format("\t%s: %s Default range [%s, %s]%n", v.name(), v.getDescription(), + v.getDefaultMinValue(), + v.getDefaultMaxValue() == -1 ? "unlimited" : v.getDefaultMaxValue())) + .collect(Collectors.joining(System.lineSeparator())))), + /** + * A generic argument. + */ + ARG("Arg", () -> "A string"), + /** + * No argument. + */ + NONE("", () -> ""); + + /** + * The display name + */ + private final String displayName; + /** + * A supplier of the description + */ + private final Supplier<String> description; + + ArgumentType(final String name, + final Supplier<String> description) { + this.displayName = name; + this.description = description; + } + + public String getDisplayName() { + return displayName; + } + + public Supplier<String> description() { + return description; + } + } +} diff --git a/apache-rat-core/src/main/java/org/apache/rat/commandline/UpdatableOptionGroup.java b/apache-rat-core/src/main/java/org/apache/rat/commandline/UpdatableOptionGroup.java new file mode 100644 index 00000000..96e08743 --- /dev/null +++ b/apache-rat-core/src/main/java/org/apache/rat/commandline/UpdatableOptionGroup.java @@ -0,0 +1,60 @@ +/* + * 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.commandline; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +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 class UpdatableOptionGroup extends OptionGroup { + /** The set of options to remove */ + private final Set<Option> removedOptions = new HashSet<>(); + + /** + * Disable an option in the group. + * @param option The option to disable. + */ + public final void disableOption(final Option option) { + removedOptions.add(option); + } + + /** + * Reset the group so that all disabled options are re-enabled. + */ + public void reset() { + removedOptions.clear(); + } + + @Override + public Collection<Option> getOptions() { + return super.getOptions().stream().filter(opt -> !removedOptions.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/AbstractCodeGenerator.java b/apache-rat-core/src/main/java/org/apache/rat/ui/AbstractCodeGenerator.java new file mode 100644 index 00000000..b9f76c1f --- /dev/null +++ b/apache-rat-core/src/main/java/org/apache/rat/ui/AbstractCodeGenerator.java @@ -0,0 +1,143 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * 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.io.IOException; +import java.util.function.Function; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.DefaultParser; +import org.apache.commons.cli.HelpFormatter; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; +import org.apache.commons.text.WordUtils; +import org.apache.rat.DeprecationReporter; + +import static java.lang.String.format; +import static org.apache.rat.OptionCollectionParser.ArgumentType.NONE; + +/** + * Generates the ${code org.apache.rat.maven.AbstractMaven} source code. + * @param <T> The concrete implementation of the AbstractOption. + */ +public abstract class AbstractCodeGenerator<T extends AbstractOption<?>> { + /** The base source directory */ + protected final String baseDirectory; + /** + * private constructor. + * @param baseDirectory The base source directory. + */ + protected AbstractCodeGenerator(final String baseDirectory) { + this.baseDirectory = baseDirectory; + } + + /** + * Gets the options for the command line. + * @return the command line options. + */ + private static Options getOptions() { + return new Options() + .addOption(Option.builder("h").longOpt("help").desc("Print this message").build()) + .addOption(Option.builder("p").longOpt("path").required().hasArg().desc("The path to the base of the generated java code directory").build()); + } + + + /** + * Executable entry point. + * @param args the arguments for the executable + * @throws IOException on IO error. + */ + protected static void processArgs(final String syntax, + final Function<String, AbstractCodeGenerator<?>> instance, + final String[] args) + throws IOException { + CommandLine commandLine = null; + try { + commandLine = DefaultParser.builder().setDeprecatedHandler(DeprecationReporter.getLogReporter()) + .build().parse(getOptions(), args); + } catch (ParseException pe) { + HelpFormatter formatter = new HelpFormatter(); + formatter.printHelp(syntax, pe.getMessage(), getOptions(), ""); + System.exit(1); + } + + if (commandLine.hasOption("h")) { + HelpFormatter formatter = new HelpFormatter(); + formatter.printHelp(syntax, getOptions()); + System.exit(0); + } + AbstractCodeGenerator<?> codeGenerator = instance.apply(commandLine.getOptionValue("p")); + codeGenerator.execute(); + } + + /** + * Executes the code generation. + * @throws IOException on IO error + */ + protected abstract void execute() throws IOException; + + /** + * Creates the description for a method. + * @param abstractOption the option generating the method. + * @return the description for the method in {@code AbstractMaven.java}. + */ + protected final String createDesc(final T abstractOption) { + String desc = abstractOption.getDescription(); + if (desc == null) { + throw new IllegalStateException(format("Description for %s may not be null", abstractOption.getName())); + } + if (!desc.contains(".")) { + throw new IllegalStateException(format("First sentence of description for %s must end with a '.'", abstractOption.getName())); + } + if (abstractOption.getArgType() != NONE) { + desc = format("%s Argument%s should be %s%s. (See Argument Types for clarification)", desc, abstractOption.hasArgs() ? "s" : "", + abstractOption.hasArgs() ? "" : "a ", abstractOption.getArgName()); + } + return desc; + } + + /** + * Gets the argument description for the method returned from ${link createMethodName}. + * @param abstractOption the maven option generating the method. + * @param desc the description of the argument. + * @return the argument description for the method in {@code AbstractMaven.java}. + */ + protected String createArgDesc(final T abstractOption, final String desc) { + if (abstractOption.hasArg()) { + String argDesc = desc.substring(desc.indexOf(" ") + 1, desc.indexOf(".") + 1); + return WordUtils.capitalize(argDesc.substring(0, 1)) + argDesc.substring(1); + } else { + return "The state"; + } + } + + /** + * Gets method name for the option. + * @param abstractOption the maven option generating the method. + * @return the method name description for the method in {@code AbstractMaven.java}. + */ + protected abstract String createMethodName(T abstractOption); + + /** + * Gathers all method definitions into a single string. + * @return the definition of all the methods. + */ + protected abstract String gatherMethods(); +} diff --git a/apache-rat-core/src/main/java/org/apache/rat/ui/AbstractOption.java b/apache-rat-core/src/main/java/org/apache/rat/ui/AbstractOption.java new file mode 100644 index 00000000..42957067 --- /dev/null +++ b/apache-rat-core/src/main/java/org/apache/rat/ui/AbstractOption.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.ui; + +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.commons.cli.Option; +import org.apache.commons.lang3.StringUtils; +import org.apache.rat.OptionCollectionParser; + +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. + * @param <T> the concrete implementation of AbstractOption. + */ +public abstract class AbstractOption<T extends AbstractOption<T>> { + /** The pattern to match CLI options in text */ + protected static final Pattern PATTERN = Pattern.compile("-(-[a-z0-9]+)+"); + /** The actual UI-specific name for the option */ + protected final Option option; + /** The name for the option */ + protected final String name; + /** The argument type for this option */ + protected final OptionCollectionParser.ArgumentType argumentType; + /** The AbstractOptionCollection associated with this AbstractOption */ + protected final AbstractOptionCollection<T> optionCollection; + + /** + * Constructor. + * + * @param option The CLI option + * @param name the UI-specific name for the option. + */ + protected <C extends AbstractOptionCollection<T>> AbstractOption(final C optionCollection, final Option option, final String name) { + this.optionCollection = optionCollection; + this.option = option; + this.name = name; + argumentType = option.hasArg() ? + option.getArgName() == null ? OptionCollectionParser.ArgumentType.ARG : + OptionCollectionParser.ArgumentType.valueOf(option.getArgName().toUpperCase(Locale.ROOT)) : + OptionCollectionParser.ArgumentType.NONE; + } + + /** + * Gets the AbstractOptionCollection that this option is a member of. + * @return the AbstractOptionCollection that this option is a member of. + */ + public final AbstractOptionCollection<T> getOptionCollection() { + return optionCollection; + } + + /** + * Gets the option this abstract option is wrapping. + * @return the original Option. + */ + public final Option getOption() { + return option; + } + + /** + * Return default value. + * @return default value or {@code null} if no argument given. + */ + public final String getDefaultValue() { + return optionCollection.getDefaultValue(option); + } + + /** + * Provide means to wrap the given option depending on the UI-specific option implementation. + * @param option The CLI option + * @return the cleaned up option name. + */ + protected abstract String cleanupName(Option option); + + /** + * Gets an example of how to use this option in the native UI. + * @return An example of how to use this option in the native UI. + */ + public abstract String getExample(); + + /** + * Replaces CLI pattern options with implementation specific pattern options. + * @param str the string to clean. + * @return the string with CLI names replaced with implementation specific names. + */ + public String cleanup(final String str) { + String workingStr = str; + if (StringUtils.isNotBlank(workingStr)) { + Map<String, String> maps = new HashMap<>(); + Matcher matcher = PATTERN.matcher(workingStr); + while (matcher.find()) { + String key = matcher.group(); + String optKey = key.substring(2); + Optional<Option> 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<String, String> entry : maps.entrySet()) { + workingStr = workingStr.replaceAll(Pattern.quote(format("%s", entry.getKey())), entry.getValue()); + } + } + return workingStr; + } + + /** + * Gets the implementation specific name for the CLI option. + * @return The implementation specific name for the CLI option. + */ + public final String getName() { + return name; + } + + /** + * return a string showing long and short options if they are available. Will return + * a string. + * @return A string showing long and short options if they are available. Never {@code null}. + */ + public abstract String getText(); + + /** + * Gets the description in implementation specific format. + * + * @return the description or an empty string. + */ + public final String getDescription() { + return cleanup(option.getDescription()); + } + + /** + * Gets the simple class name for the data type for this option. + * Normally "String". + * @return the simple class name for the type. + */ + public final Class<?> getType() { + return option.hasArg() ? ((Class<?>) option.getType()) : boolean.class; + } + + /** + * Gets the argument name if there is one. + * @return the Argument name + */ + public final String getArgName() { + return argumentType.getDisplayName(); + } + + /** + * Gets the argument type if there is one. + * @return the Argument name + */ + public final OptionCollectionParser.ArgumentType getArgType() { + return argumentType; + } + + /** + * Determines if the option is deprecated. + * @return {@code true} if the option is deprecated + */ + public final boolean isDeprecated() { + return option.isDeprecated(); + } + + /** + * Determines if the option is required. + * @return {@code true} if the option is required. + */ + public final boolean isRequired() { + return option.isRequired(); + } + + /** + * Determine if the enclosed option expects an argument. + * @return {@code true} if the enclosed option expects at least one argument. + */ + public final boolean hasArg() { + return option.hasArg(); + } + + /** + * Returns {@code true} if the option has multiple arguments. + * @return {@code true} if the option has multiple arguments. + */ + 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 StringUtils.defaultIfEmpty(option.getLongOpt(), option.getOpt()); + } + + /** + * Gets the deprecated string if the option is deprecated, or an empty string otherwise. + * @return the deprecated string if the option is deprecated, or an empty string otherwise. + */ + public final String getDeprecated() { + return option.isDeprecated() ? cleanup(StringUtils.defaultIfEmpty(option.getDeprecated().toString(), StringUtils.EMPTY)) : StringUtils.EMPTY; + } +} diff --git a/apache-rat-core/src/main/java/org/apache/rat/ui/AbstractOptionCollection.java b/apache-rat-core/src/main/java/org/apache/rat/ui/AbstractOptionCollection.java new file mode 100644 index 00000000..70b4f1e2 --- /dev/null +++ b/apache-rat-core/src/main/java/org/apache/rat/ui/AbstractOptionCollection.java @@ -0,0 +1,162 @@ +/* + * 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.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.function.Function; +import java.util.stream.Stream; + +import org.apache.commons.cli.Option; +import org.apache.commons.cli.Options; +import org.apache.rat.commandline.Arg; +import org.apache.rat.commandline.UpdatableOptionGroup; + +/** + * + * @param <T> the AbstractOption implementation. + */ +public abstract class AbstractOptionCollection<T extends AbstractOption<T>> { + + /** The collection of unsupported options */ + protected final Collection<Option> unsupportedOptions; + /** The additional options for the specific UI. Used for documentation generation */ + protected final Options additionalOptions; + /** + * Create an Instance. + * @param unsupportedOptions the collection of options that are not supported. + */ + protected AbstractOptionCollection(final Collection<Option> unsupportedOptions, final Options additionalOptions) { + this.unsupportedOptions = unsupportedOptions; + this.additionalOptions = additionalOptions; + } + + /** + * Gets the collection of unsupported Options. + * @return the Options comprised for the unsupported options. + */ + public final Options getUnsupportedOptions() { + Options options = new Options(); + unsupportedOptions.forEach(options::addOption); + return options; + } + + /** + * Get a mapping function from an Apache Commons cli Option to the AbstractOption implementation. + * @return a mapping function from an Apache Commons cli Option to the AbstractOption implementation. + */ + protected abstract Function<Option, T> getMapper(); + + /** + * Creates an AbstractOption instance ({@code T}) from an Apache Commons cli Option. + * @param option the option to build the instance from. + * @return an AbstractOption instance from an Apache Commons cli Option. + */ + public final T getMappedOption(final Option option) { + return getMapper().apply(option); + } + + /** + * Gets an Apache Commons cli Options that contains all the Apache Commons cli Options that are understood by this collection. + * @return an Apache Commons cli Options that contains all the Apache Commons cli Options that are understood by this collection. + */ + public final Options getOptions() { + Options result = new Options(); + Options argOptions = Arg.getOptions(); + Set<UpdatableOptionGroup> optionGroups = new HashSet<>(); + for (Option option : argOptions.getOptions()) { + optionGroups.add((UpdatableOptionGroup) argOptions.getOptionGroup(option)); + } + for (Option option : unsupportedOptions) { + UpdatableOptionGroup group = (UpdatableOptionGroup) argOptions.getOptionGroup(option); + if (group != null) { + group.disableOption(option); + } + } + optionGroups.forEach(result::addOptionGroup); + return result.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<T> getMappedOptions() { + return getOptions().getOptions().stream().map(getMapper()); + } + + /** + * Gets a map client option name to specified AbstractOption implementation. + * @return a map client option name to specified AbstractOption implementation + */ + public Map<String, T> getOptionMap() { + Map<String, T> 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() { + return additionalOptions; + } + + /** + * Gets the map of default overrides. + * @return the default overrides. + */ + protected abstract Map<Option, String> defaultOverrides(); + + /** + * Specifies the default value for the option. + * @param option the option to override. + * @param value the value to use as the default. + */ + public abstract void addOverride(Option option, String value); + + /** + * Adds overrides the default value for all options in the Arg. + * @param arg the arg to override. + * @param value the value to set as the default value. + */ + public void addOverride(final Arg arg, final String value) { + arg.group().getOptions().forEach(option -> this.addOverride(option, value)); + } + + /** + * 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 getDefaultValue(final Option option) { + String override = defaultOverrides().get(option); + if (override == null) { + Arg arg = Arg.findArg(option); + if (arg != null) { + override = arg.defaultValue(); + } + } + return override; + } +} 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 00000000..e905356f --- /dev/null +++ b/apache-rat-core/src/main/java/org/apache/rat/ui/ArgumentTracker.java @@ -0,0 +1,211 @@ +/* + * 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 java.util.stream.Collectors; + +import org.apache.commons.cli.Option; +import org.apache.commons.lang3.StringUtils; +import org.apache.rat.DeprecationReporter; +import org.apache.rat.commandline.Arg; +import org.apache.rat.utils.DefaultLog; +import org.apache.rat.utils.Log; + +/** + * Tracks arguments that are set and their values for conversion from native UI to + * Apache Commons command line values. Native values + */ +public final class ArgumentTracker { + + /** + * List of deprecated arguments and their deprecation notice. + */ + private final Map<String, String> deprecatedArgs = new HashMap<>(); + + /** + * A map of CLI-based arguments to values. + */ + private final Map<String, List<String>> args = new HashMap<>(); + + /** + * The arguments set by the UI for the current report execution. + * @param uiOptionList the list of AbstractOption implementations for this UI. + */ + public ArgumentTracker(final List<? extends AbstractOption<?>> uiOptionList) { + for (AbstractOption<?> abstractOption : uiOptionList) { + 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()); + } + + /** + * Sets the deprecation report method. + */ + private void setDeprecationReporter() { + DeprecationReporter.setLogReporter(opt -> { + String msg = deprecatedArgs.get(extractKey(opt)); + if (msg == null) { + DeprecationReporter.getDefault().accept(opt); + } else { + DefaultLog.getInstance().warn(msg); + } + }); + } + + /** + * Gets the list of arguments prepared for the CLI code to parse. + * @return the List of arguments for the CLI command line. + */ + public List<String> args() { + final List<String> result = new ArrayList<>(); + for (Map.Entry<String, List<String>> entry : args.entrySet()) { + result.add("--" + entry.getKey()); + result.addAll(entry.getValue().stream().filter(Objects::nonNull).collect(Collectors.toList())); + } + return result; + } + + /** + * Applies the consumer to each arg and list in turn. + */ + public void apply(final BiConsumer<String, List<String>> consumer) { + args.forEach((key, value) -> consumer.accept(key, new ArrayList<>(value))); + } + + 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)) { + if (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 String key, final String value) { + if (value == null || StringUtils.isNotBlank(value)) { + if (validateSet(key)) { + List<String> values = new ArrayList<>(); + if (DefaultLog.getInstance().isEnabled(Log.Level.DEBUG)) { + DefaultLog.getInstance().debug(String.format("Setting %s to '%s'", key, value)); + } + values.add(value); + args.put(key, values); + } + } + } + + /** + * 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<List<String>> 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 key the key for the map. + * @param value the array of values to set. + */ + public void addArg(final String key, final String[] value) { + List<String> newValues = Arrays.stream(value).filter(StringUtils::isNotBlank).collect(Collectors.toList()); + if (!newValues.isEmpty()) { + if (validateSet(key)) { + if (DefaultLog.getInstance().isEnabled(Log.Level.DEBUG)) { + DefaultLog.getInstance().debug(String.format("Adding [%s] to %s", String.join(", ", Arrays.asList(value)), key)); + } + List<String> values = args.computeIfAbsent(key, k -> new ArrayList<>()); + values.addAll(newValues); + } + } + } + + /** + * Add a value to the key in the argument list. + * If the key does not exist, adds it. + * @param key the key for the map. + * @param value the value to set. + */ + public void addArg(final String key, final String value) { + if (StringUtils.isNotBlank(value)) { + if (validateSet(key)) { + List<String> values = args.get(key); + if (DefaultLog.getInstance().isEnabled(Log.Level.DEBUG)) { + DefaultLog.getInstance().debug(String.format("Adding [%s] to %s", String.join(", ", Arrays.asList(value)), key)); + } + if (values == null) { + values = new ArrayList<>(); + args.put(key, values); + } + values.add(value); + } + } + } + + /** + * Remove a key from the argument list. + * @param key the key to remove from the map. + */ + public void removeArg(final String key) { + args.remove(key); + } +} 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 00000000..7e8afa8e --- /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 + * + * 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; + +public interface UI<T extends AbstractOption<T>> { + /** + * 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. + */ + AbstractOptionCollection<T> getOptionCollection(); +} 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 00000000..0244aa7a --- /dev/null +++ b/apache-rat-core/src/main/java/org/apache/rat/ui/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 + * + * 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. + */ +/** + * Classes to generate UIs + */ +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 00000000..02609943 --- /dev/null +++ b/apache-rat-core/src/main/java/org/apache/rat/ui/spi/UIProvider.java @@ -0,0 +1,25 @@ +/* + * 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.spi; + +import org.apache.rat.ui.UI; + +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 00000000..982ca301 --- /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 + * + * 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 SPI implementation for the UIs + */ +package org.apache.rat.ui.spi; diff --git a/pom.xml b/pom.xml index 7ee69c2e..8e5759bd 100644 --- a/pom.xml +++ b/pom.xml @@ -85,6 +85,11 @@ agnostic home for software distribution comprehension and audit tools. </distributionManagement> <dependencyManagement> <dependencies> + <dependency> + <groupId>com.github.spotbugs</groupId> + <artifactId>spotbugs-annotations</artifactId> + <version>4.9.8</version> + </dependency> <!-- used to render the site and make skin updates more transparent --> <dependency> <groupId>org.apache.maven.skins</groupId>
