You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@creadur.apache.org by cl...@apache.org on 2026/03/10 17:07:40 UTC

(creadur-rat) branch add-ui-package created (now d1ac2509)

This is an automated email from the ASF dual-hosted git repository.

claude pushed a change to branch add-ui-package
in repository https://gitbox.apache.org/repos/asf/creadur-rat.git


      at d1ac2509 initial code

This branch includes the following new commits:

     new d1ac2509 initial code

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



(creadur-rat) 01/01: initial code

Posted by cl...@apache.org.
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 <cl...@xenei.com>
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>