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

claude pushed a commit to branch create-UIOptionCollections
in repository https://gitbox.apache.org/repos/asf/creadur-rat.git

commit 3e47c4326df52e397032f7822426b6542f0b7b29
Author: Claude Warren <[email protected]>
AuthorDate: Sun Mar 22 08:21:54 2026 +0000

    initial working code
---
 apache-rat-core/pom.xml                            |  25 +-
 .../src/main/java/org/apache/rat}/CLIOption.java   |  20 +-
 .../java/org/apache/rat/CLIOptionCollection.java   |  41 ++
 .../main/java/org/apache/rat/OptionCollection.java |  12 +-
 ...Collection.java => OptionCollectionParser.java} | 156 ++---
 .../main/java/org/apache/rat/commandline/Arg.java  | 703 ++++++++++-----------
 .../apache/rat/commandline/ArgumentContext.java    |   7 +-
 .../java/org/apache/rat/help/AbstractHelp.java     |   5 +-
 .../org/apache/rat/ui/AbstractCodeGenerator.java   | 151 +++++
 .../java/org/apache/rat/ui/ArgumentTracker.java    | 239 +++++++
 .../src/main/java/org/apache/rat/ui/UI.java        |  36 ++
 .../src/main/java/org/apache/rat/ui/UIOption.java  | 224 +++++++
 .../java/org/apache/rat/ui/UIOptionCollection.java | 299 +++++++++
 .../org/apache/rat/ui/UpdatableOptionGroup.java    |  85 +++
 .../rat/ui/UpdatableOptionGroupCollection.java     | 110 ++++
 .../main/java/org/apache/rat/ui/package-info.java  |  23 +
 .../java/org/apache/rat/ui/spi/UIProvider.java     |  25 +
 .../java/org/apache/rat/ui/spi/package-info.java   |  22 +
 .../java/org/apache/rat/commandline/ArgTests.java  |   3 +-
 .../org/apache/rat/ui/ArgumentTrackerTest.java     | 122 ++++
 .../org/apache/rat/ui/UIOptionCollectionTest.java  | 160 +++++
 .../rat/documentation/options/CLIOption.java       |   2 +-
 pom.xml                                            |  10 +
 23 files changed, 1983 insertions(+), 497 deletions(-)

diff --git a/apache-rat-core/pom.xml b/apache-rat-core/pom.xml
index 4819f23b..2cce9a2a 100644
--- a/apache-rat-core/pom.xml
+++ b/apache-rat-core/pom.xml
@@ -37,7 +37,6 @@
         <directory>src/main/filtered-resources</directory>
       </resource>
     </resources>
-    <pluginManagement>
       <plugins>
         <plugin>
           <groupId>org.apache.rat</groupId>
@@ -56,9 +55,6 @@
             </inputExcludes>
           </configuration>
         </plugin>
-      </plugins>
-    </pluginManagement>
-    <plugins>
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-jar-plugin</artifactId>
@@ -177,8 +173,20 @@
   </build>
   <dependencies>
     <dependency>
-      <groupId>org.apache.rat</groupId>
-      <artifactId>apache-rat-testdata</artifactId>
+      <groupId>com.github.spotbugs</groupId>
+      <artifactId>spotbugs-annotations</artifactId>
+    </dependency>
+    <dependency>
+            <groupId>org.apache.velocity</groupId>
+            <artifactId>velocity-engine-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.velocity.tools</groupId>
+            <artifactId>velocity-tools-generic</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.reflections</groupId>
+            <artifactId>reflections</artifactId>
       <scope>test</scope>
     </dependency>
     <dependency>
@@ -213,11 +221,6 @@
       <artifactId>junit-jupiter-api</artifactId>
       <scope>test</scope>
     </dependency>
-    <dependency>
-      <groupId>org.junit.vintage</groupId>
-      <artifactId>junit-vintage-engine</artifactId>
-      <scope>test</scope>
-    </dependency>
     <dependency>
       <groupId>org.junit.jupiter</groupId>
       <artifactId>junit-jupiter-params</artifactId>
diff --git 
a/apache-rat-tools/src/main/java/org/apache/rat/documentation/options/CLIOption.java
 b/apache-rat-core/src/main/java/org/apache/rat/CLIOption.java
similarity index 81%
copy from 
apache-rat-tools/src/main/java/org/apache/rat/documentation/options/CLIOption.java
copy to apache-rat-core/src/main/java/org/apache/rat/CLIOption.java
index fc00e5e1..03b79044 100644
--- 
a/apache-rat-tools/src/main/java/org/apache/rat/documentation/options/CLIOption.java
+++ b/apache-rat-core/src/main/java/org/apache/rat/CLIOption.java
@@ -16,19 +16,21 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.rat.documentation.options;
+package org.apache.rat;
 
 import org.apache.commons.cli.Option;
 import org.apache.commons.lang3.StringUtils;
+import org.apache.rat.ui.ArgumentTracker;
+import org.apache.rat.ui.UIOption;
+import org.apache.rat.ui.UIOptionCollection;
 
-public class CLIOption extends AbstractOption {
-
-    public static String createName(final Option option) {
-        return StringUtils.defaultIfBlank(option.getLongOpt(), 
option.getOpt());
-    }
+/**
+ * The CLI option definition.
+ */
+public final class CLIOption extends UIOption<CLIOption> {
 
-    public CLIOption(final Option option) {
-        super(option, createName(option));
+    public CLIOption(final UIOptionCollection<CLIOption> collection, final 
Option option) {
+        super(collection, option, ArgumentTracker.extractKey(option));
     }
 
     @Override
@@ -47,7 +49,7 @@ public class CLIOption extends AbstractOption {
 
     @Override
     protected String cleanupName(final Option option) {
-        return createName(option);
+        return ArgumentTracker.extractKey(option);
     }
 
     @Override
diff --git 
a/apache-rat-core/src/main/java/org/apache/rat/CLIOptionCollection.java 
b/apache-rat-core/src/main/java/org/apache/rat/CLIOptionCollection.java
new file mode 100644
index 00000000..feb1cbf5
--- /dev/null
+++ b/apache-rat-core/src/main/java/org/apache/rat/CLIOptionCollection.java
@@ -0,0 +1,41 @@
+/*
+ * 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 org.apache.commons.cli.Option;
+import org.apache.rat.ui.UIOptionCollection;
+
+public final class CLIOptionCollection extends UIOptionCollection<CLIOption> {
+    /** The Help option */
+    static final Option HELP = new Option("?", "help", false, "Print help for 
the RAT command line interface and exit.");
+
+    /** The instance of the collection */
+    public static final CLIOptionCollection INSTANCE = new 
CLIOptionCollection();
+
+    private CLIOptionCollection() {
+        super(new Builder().uiOption(HELP)
+                .mapper(CLIOption::new));
+    }
+
+    private static final class Builder extends 
UIOptionCollection.Builder<CLIOption, Builder> {
+        private Builder() {
+            super();
+        }
+    }
+}
diff --git a/apache-rat-core/src/main/java/org/apache/rat/OptionCollection.java 
b/apache-rat-core/src/main/java/org/apache/rat/OptionCollection.java
index 6ce2e1c2..b16ebd09 100644
--- a/apache-rat-core/src/main/java/org/apache/rat/OptionCollection.java
+++ b/apache-rat-core/src/main/java/org/apache/rat/OptionCollection.java
@@ -140,9 +140,8 @@ public final class OptionCollection {
             // for "commandLine"
         }
 
-        Arg.processLogLevel(commandLine);
-
         ArgumentContext argumentContext = new 
ArgumentContext(workingDirectory, commandLine);
+        Arg.processLogLevel(argumentContext, CLIOptionCollection.INSTANCE);
 
         if (commandLine.hasOption(HELP)) {
             helpCmd.accept(opts);
@@ -175,14 +174,15 @@ public final class OptionCollection {
      * @see #parseCommands(File, String[], Consumer, boolean)
      */
     static ReportConfiguration createConfiguration(final ArgumentContext 
argumentContext) {
-        argumentContext.processArgs();
+        argumentContext.processArgs(CLIOptionCollection.INSTANCE);
         final ReportConfiguration configuration = 
argumentContext.getConfiguration();
         final CommandLine commandLine = argumentContext.getCommandLine();
-        if (Arg.DIR.isSelected()) {
+        if (CLIOptionCollection.INSTANCE.isSelected(Arg.DIR)) {
             try {
-                
configuration.addSource(getReportable(commandLine.getParsedOptionValue(Arg.DIR.getSelected()),
 configuration));
+                
configuration.addSource(getReportable(commandLine.getParsedOptionValue(
+                        
CLIOptionCollection.INSTANCE.getSelected(Arg.DIR).get()), configuration));
             } catch (ParseException e) {
-                throw new ConfigurationException("Unable to set parse " + 
Arg.DIR.getSelected(), e);
+                throw new ConfigurationException("Unable to set parse " + 
CLIOptionCollection.INSTANCE.getSelected(Arg.DIR).get(), e);
             }
         }
         for (String s : commandLine.getArgs()) {
diff --git a/apache-rat-core/src/main/java/org/apache/rat/OptionCollection.java 
b/apache-rat-core/src/main/java/org/apache/rat/OptionCollectionParser.java
similarity index 70%
copy from apache-rat-core/src/main/java/org/apache/rat/OptionCollection.java
copy to apache-rat-core/src/main/java/org/apache/rat/OptionCollectionParser.java
index 6ce2e1c2..5798b738 100644
--- a/apache-rat-core/src/main/java/org/apache/rat/OptionCollection.java
+++ b/apache-rat-core/src/main/java/org/apache/rat/OptionCollectionParser.java
@@ -21,14 +21,11 @@ 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.Collections;
 import java.util.Comparator;
-import java.util.Map;
-import java.util.TreeMap;
-import java.util.function.Consumer;
 import java.util.function.Supplier;
 import java.util.stream.Collectors;
 
@@ -49,49 +46,32 @@ 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.UIOptionCollection;
 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;
 
 /**
- * The collection of standard options for the CLI as well as utility methods 
to manage them and methods to create the
- * ReportConfiguration from the options and an array of arguments.
+ * Uses the AbstractOptionCollection to parse the command line options.
+ * contains utility methods to ReportConfiguration from the options and an 
array of arguments.
  */
-public final class OptionCollection {
+@SuppressFBWarnings("EI_EXPOSE_REP2")
+public final class OptionCollectionParser {
+    /** The OptionCollection that we are working with */
+    private final UIOptionCollection<?> uiOptionCollection;
 
-    private OptionCollection() {
-        // do not instantiate
+    public OptionCollectionParser(final UIOptionCollection<?> 
optionCollection) {
+        this.uiOptionCollection = optionCollection;
     }
 
     /** The Option comparator to sort the help */
     public static final Comparator<Option> OPTION_COMPARATOR = new 
OptionComparator();
 
-    /** The Help option */
-    public static final Option HELP = new Option("?", "help", false, "Print 
help for the RAT command line interface and exit.");
-
-    /** A mapping of {@code argName(value)} values to a description of those 
values. */
-    @Deprecated
-    private static final Map<String, Supplier<String>> ARGUMENT_TYPES;
-    static {
-        ARGUMENT_TYPES = new TreeMap<>();
-        for (ArgumentType argType : ArgumentType.values()) {
-            ARGUMENT_TYPES.put(argType.getDisplayName(), argType.description);
-        }
-    }
-
-    /**
-     * Gets the mapping of {@code argName(value)} values to a description of 
those values.
-     * @return the mapping of {@code argName(value)} values to a description 
of those values.
-     * @deprecated use {@link ArgumentType}
-     */
-    @Deprecated
-    public static Map<String, Supplier<String>> getArgumentTypes() {
-        return Collections.unmodifiableMap(ARGUMENT_TYPES);
-    }
-
     /**
      * Join a collection of objects together as a comma separated list of 
their string values.
      * @param args the objects to join together.
@@ -106,63 +86,58 @@ public final class OptionCollection {
      *
      * @param workingDirectory The directory to resolve relative file names 
against.
      * @param args the arguments to parse
-     * @param helpCmd the help command to run when necessary.
-     * @return a ReportConfiguration or {@code null} if Help was printed.
+     * @return the ArgumentContext for the process.
      * @throws IOException on error.
+     * @throws ParseException on option parsing error.
      */
-    public static ReportConfiguration parseCommands(final File 
workingDirectory, final String[] args, final Consumer<Options> helpCmd) throws 
IOException {
-        return parseCommands(workingDirectory, args, helpCmd, false);
+    public ArgumentContext parseCommands(final File workingDirectory, final 
String[] args)
+            throws IOException, ParseException {
+        return parseCommands(workingDirectory, args, 
uiOptionCollection.getOptions());
     }
 
     /**
-     * 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 helpCmd the help command to run when necessary.
-     * @param noArgs If {@code true} then the commands do not need extra 
arguments.
-     * @return a ReportConfiguration or {@code null} if Help was printed.
-     * @throws IOException on error.
+     * 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.
      */
-    public static ReportConfiguration parseCommands(final File 
workingDirectory, final String[] args,
-                                                    final Consumer<Options> 
helpCmd, final boolean noArgs) throws IOException {
-
-        Options opts = buildOptions();
-        CommandLine commandLine;
+    //@VisibleForTesting
+    CommandLine parseCommandLine(final Options opts, final String[] args) 
throws ParseException {
         try {
-            commandLine = 
DefaultParser.builder().setDeprecatedHandler(DeprecationReporter.getLogReporter())
+            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);
-            System.exit(1);
-            return null; // dummy return (won't be reached) to avoid Eclipse 
complaint about possible NPE
-            // for "commandLine"
+            throw e;
         }
+    }
 
-        Arg.processLogLevel(commandLine);
+    /**
+     * 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);
         ArgumentContext argumentContext = new 
ArgumentContext(workingDirectory, commandLine);
-
-        if (commandLine.hasOption(HELP)) {
-            helpCmd.accept(opts);
-            return null;
+        Arg.processLogLevel(argumentContext, uiOptionCollection);
+        populateConfiguration(argumentContext);
+        if (uiOptionCollection.isSelected(Arg.HELP_LICENSES)) {
+            new Licenses(argumentContext.getConfiguration(),
+                    new 
PrintWriter(argumentContext.getConfiguration().getOutput().get(),
+                            false, StandardCharsets.UTF_8)).printHelp();
         }
 
-        if (commandLine.hasOption(Arg.HELP_LICENSES.option())) {
-            new Licenses(createConfiguration(argumentContext), new 
PrintWriter(System.out, false, StandardCharsets.UTF_8)).printHelp();
-            return null;
-        }
-
-        ReportConfiguration configuration = 
createConfiguration(argumentContext);
-        if (!noArgs && !configuration.hasSource()) {
-            String msg = "No directories or files specified for scanning. Did 
you forget to close a multi-argument option?";
-            DefaultLog.getInstance().error(msg);
-            helpCmd.accept(opts);
-            return null;
-        }
-
-        return configuration;
+        return argumentContext;
     }
 
     /**
@@ -171,38 +146,22 @@ public final class OptionCollection {
      * You probably want one of the {@code ParseCommands} methods.
      * @param argumentContext The context to execute in.
      * @return a ReportConfiguration
-     * @see #parseCommands(File, String[], Consumer)
-     * @see #parseCommands(File, String[], Consumer, boolean)
      */
-    static ReportConfiguration createConfiguration(final ArgumentContext 
argumentContext) {
-        argumentContext.processArgs();
+    private ReportConfiguration populateConfiguration(final ArgumentContext 
argumentContext) {
+        argumentContext.processArgs(uiOptionCollection);
         final ReportConfiguration configuration = 
argumentContext.getConfiguration();
         final CommandLine commandLine = argumentContext.getCommandLine();
-        if (Arg.DIR.isSelected()) {
-            try {
-                
configuration.addSource(getReportable(commandLine.getParsedOptionValue(Arg.DIR.getSelected()),
 configuration));
-            } catch (ParseException e) {
-                throw new ConfigurationException("Unable to set parse " + 
Arg.DIR.getSelected(), e);
-            }
-        }
-        for (String s : commandLine.getArgs()) {
-            IReportable reportable = getReportable(new File(s), configuration);
-            if (reportable != null) {
-                configuration.addSource(reportable);
+        if (!configuration.hasSource()) {
+            for (String s : commandLine.getArgs()) {
+                IReportable reportable = getReportable(new File(s), 
configuration);
+                if (reportable != null) {
+                    configuration.addSource(reportable);
+                }
             }
         }
         return configuration;
     }
 
-    /**
-     * Create an {@code Options} object from the list of defined Options.
-     * Mutually exclusive options must be listed in an OptionGroup.
-     * @return the Options comprised of the Options defined in this class.
-     */
-    public static Options buildOptions() {
-        return Arg.getOptions().addOption(HELP);
-    }
-
     /**
      * Creates an IReportable object from the directory name and 
ReportConfiguration
      * object.
@@ -211,7 +170,7 @@ public final class OptionCollection {
      * @param config the ReportConfiguration.
      * @return the IReportable instance containing the files.
      */
-    static IReportable getReportable(final File base, final 
ReportConfiguration config) {
+    IReportable getReportable(final File base, final ReportConfiguration 
config) {
         File absBase = base.getAbsoluteFile();
         DocumentName documentName = DocumentName.builder(absBase).build();
         if (!absBase.exists()) {
@@ -238,6 +197,7 @@ public final class OptionCollection {
      */
     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) {
@@ -301,7 +261,7 @@ public final class OptionCollection {
         /**
          * A style sheet.
          */
-        STYLESHEET("StyleSheet", () -> format("Either an external xsl file or 
one of the internal named sheets. Internal sheets are: %n%s",
+        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())))),
diff --git a/apache-rat-core/src/main/java/org/apache/rat/commandline/Arg.java 
b/apache-rat-core/src/main/java/org/apache/rat/commandline/Arg.java
index a9a2d5e7..c7888b47 100644
--- a/apache-rat-core/src/main/java/org/apache/rat/commandline/Arg.java
+++ b/apache-rat-core/src/main/java/org/apache/rat/commandline/Arg.java
@@ -21,15 +21,13 @@ package org.apache.rat.commandline;
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
-import java.io.OutputStream;
 import java.lang.reflect.Array;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
+import java.util.function.BiConsumer;
 import java.util.function.Predicate;
 
 import org.apache.commons.cli.AlreadySelectedException;
@@ -40,10 +38,11 @@ import org.apache.commons.cli.OptionGroup;
 import org.apache.commons.cli.Options;
 import org.apache.commons.cli.ParseException;
 import org.apache.commons.io.IOUtils;
-import org.apache.commons.io.function.IOSupplier;
+import org.apache.commons.io.output.CloseShieldOutputStream;
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.rat.ConfigurationException;
 import org.apache.rat.Defaults;
+import org.apache.rat.ImplementationException;
 import org.apache.rat.ReportConfiguration;
 import org.apache.rat.config.AddLicenseHeaders;
 import org.apache.rat.config.exclusion.ExclusionUtils;
@@ -52,6 +51,7 @@ import org.apache.rat.document.DocumentName;
 import org.apache.rat.document.DocumentNameMatcher;
 import org.apache.rat.license.LicenseSetFactory;
 import org.apache.rat.report.claim.ClaimStatistic.Counter;
+import org.apache.rat.ui.UIOptionCollection;
 import org.apache.rat.utils.DefaultLog;
 import org.apache.rat.utils.Log;
 
@@ -64,7 +64,6 @@ import static java.lang.String.format;
  * This allows us to deprecate options as we move forward in development.
  */
 public enum Arg {
-
     ///////////////////////// EDIT OPTIONS
     /**
      * Defines options to add copyright to files
@@ -79,7 +78,10 @@ public enum Arg {
             .addOption(Option.builder().longOpt("edit-copyright").hasArg()
                     .desc("The copyright message to use in the license 
headers. Usually in the form of \"Copyright 2008 Foo\".  "
                             + "Only valid with --edit-license")
-                    .build())),
+                    .build()),
+            (context, selected) -> {
+                throw new ImplementationException(String.format("'%s' should 
not be executed directly", selected));
+            }),
 
     /**
      * Causes file updates to overwrite existing files.
@@ -93,7 +95,11 @@ public enum Arg {
             .addOption(Option.builder().longOpt("edit-overwrite")
                     .desc("Forces any changes in files to be written directly 
to the source files so that new files are not created. "
                             + "Only valid with --edit-license.")
-                    .build())),
+                    .build()),
+            (context, selected) -> {
+                throw new ImplementationException(String.format("'%s' should 
not be executed directly", selected));
+            }
+    ),
 
     /**
      * Defines options to add licenses to files
@@ -112,7 +118,11 @@ public enum Arg {
                     "Add the Apache-2.0 license header to any file with an 
unknown license that is not in the exclusion list. "
                             + "By default new files will be created with the 
license header, "
                             + "to force the modification of existing files use 
the --edit-overwrite option.").build()
-            )),
+            ),
+            (context, selected) -> {
+                throw new ImplementationException(String.format("'%s' should 
not be executed directly", selected));
+            }
+    ),
 
     //////////////////////////// CONFIGURATION OPTIONS
     /**
@@ -129,7 +139,11 @@ public enum Arg {
                     
.deprecated(DeprecatedAttributes.builder().setSince("0.17").setForRemoval(true).setDescription(StdMsgs.useMsg("--config")).get())
                     .converter(Converters.FILE_CONVERTER)
                     .type(File.class)
-                    .build())),
+                    .build()),
+            (context, selected) -> {
+                throw new ImplementationException(String.format("'%s' should 
not be executed directly", selected));
+            }
+    ),
 
     /**
      * Group of options that skip the default configuration file
@@ -143,16 +157,22 @@ public enum Arg {
                             .setForRemoval(true)
                             
.setDescription(StdMsgs.useMsg("--configuration-no-defaults")).get())
                     .desc("Ignore default configuration.")
-                    .build())),
+                    .build()),
+            (context, selected) -> {
+                throw new ImplementationException(String.format("'%s' should 
not be executed directly", selected));
+            }),
 
     /**
      * Option that adds approved licenses to the list
      */
     LICENSES_APPROVED(new 
OptionGroup().addOption(Option.builder().longOpt("licenses-approved").hasArg().argName("LicenseID")
             .desc("A comma separated list of approved License IDs. These 
licenses will be added to the list of approved licenses.")
-                    .converter(Converters.TEXT_LIST_CONVERTER)
-                    .type(String[].class)
-            .build())),
+            .converter(Converters.TEXT_LIST_CONVERTER)
+            .type(String[].class)
+            .build()),
+            (context, selected) ->
+                
context.getConfiguration().addApprovedLicenseIds(processArrayArg(context, 
selected))
+    ),
 
     /**
      * Option that adds approved licenses from a file
@@ -161,7 +181,9 @@ public enum Arg {
             .desc("Name of file containing comma separated lists of approved 
License IDs.")
             .converter(Converters.FILE_CONVERTER)
             .type(File.class)
-            .build())),
+            .build()),
+            (context, selected) ->
+                    
context.getConfiguration().addApprovedLicenseIds(processArrayFile(context, 
selected))),
 
     /**
      * Option that specifies approved license families
@@ -170,7 +192,8 @@ public enum Arg {
             .desc("A comma separated list of approved license family IDs. 
These license families will be added to the list of approved license families.")
             .converter(Converters.TEXT_LIST_CONVERTER)
             .type(String[].class)
-            .build())),
+            .build()),
+            (context, selected) -> 
context.getConfiguration().addApprovedLicenseCategories(processArrayArg(context,
 selected))),
 
     /**
      * Option that specifies approved license families from a file
@@ -179,7 +202,9 @@ public enum Arg {
             .desc("Name of file containing comma separated lists of approved 
family IDs.")
             .converter(Converters.FILE_CONVERTER)
             .type(File.class)
-            .build())),
+            .build()),
+            (context, selected) -> 
context.getConfiguration().addApprovedLicenseCategories(processArrayFile(context,
 selected))
+    ),
 
     /**
      * Option to remove licenses from the approved list
@@ -190,7 +215,8 @@ public enum Arg {
                     "Once licenses are removed they can not be added back.")
             .converter(Converters.TEXT_LIST_CONVERTER)
             .type(String[].class)
-            .build())),
+            .build()),
+            (context, selected) -> 
context.getConfiguration().removeApprovedLicenseIds(processArrayArg(context, 
selected))),
 
     /**
      * Option to read a file licenses to be removed from the approved list.
@@ -201,7 +227,8 @@ public enum Arg {
             .desc("Name of file containing comma separated lists of the denied 
license IDs. " +
                     "These licenses will be removed from the list of approved 
licenses. " +
                     "Once licenses are removed they can not be added back.")
-            .build())),
+            .build()),
+            (context, selected) -> 
context.getConfiguration().removeApprovedLicenseIds(processArrayFile(context, 
selected))),
 
     /**
      * Option to list license families to remove from the approved list.
@@ -213,7 +240,8 @@ public enum Arg {
                     "Once license families are removed they can not be added 
back.")
             .converter(Converters.TEXT_LIST_CONVERTER)
             .type(String[].class)
-            .build())),
+            .build()),
+            (context, selected) -> 
context.getConfiguration().removeApprovedLicenseCategories(processArrayArg(context,
 selected))),
 
     /**
      * Option to read a list of license families to remove from the approved 
list.
@@ -224,7 +252,8 @@ public enum Arg {
                     "Once license families are removed they can not be added 
back.")
             .type(File.class)
             .converter(Converters.FILE_CONVERTER)
-            .build())),
+            .build()),
+            (context, selected) -> 
context.getConfiguration().removeApprovedLicenseCategories(processArrayFile(context,
 selected))),
 
     /**
      * Option to specify an acceptable number of various counters.
@@ -233,7 +262,14 @@ public enum Arg {
             .desc("The acceptable maximum number for the specified counter. A 
value of '-1' specifies an unlimited number.")
             .converter(Converters.COUNTER_CONVERTER)
             .type(Pair.class)
-            .build())),
+            .build()),
+            (context, selected) -> {
+                for (String arg : 
context.getCommandLine().getOptionValues(selected)) {
+                    Pair<Counter, Integer> pair = 
Converters.COUNTER_CONVERTER.apply(arg);
+                    int limit = pair.getValue();
+                    
context.getConfiguration().getClaimValidator().setMax(pair.getKey(), limit < 0 
? Integer.MAX_VALUE : limit);
+                }
+            }),
 
     /**
      * Option to specify an acceptable number of various counters.
@@ -242,7 +278,13 @@ public enum Arg {
             .desc("The minimum number for the specified counter.")
             .converter(Converters.COUNTER_CONVERTER)
             .type(Pair.class)
-            .build())),
+            .build()),
+            (context, selected) -> {
+                for (String arg : 
context.getCommandLine().getOptionValues(selected)) {
+                    Pair<Counter, Integer> pair = 
Converters.COUNTER_CONVERTER.apply(arg);
+                    
context.getConfiguration().getClaimValidator().setMin(pair.getKey(), 
pair.getValue());
+                }
+            }),
 
 ////////////////// INPUT OPTIONS
     /**
@@ -256,7 +298,13 @@ public enum Arg {
                             "argument is located.")
                     .converter(Converters.FILE_CONVERTER)
                     .type(File.class)
-                    .build())),
+                    .build()),
+            (context, selected) -> {
+                File[] files = getParsedOptionValues(selected, 
context.getCommandLine());
+                for (File f : files) {
+                    context.getConfiguration().addSource(f);
+                }
+            }),
 
     /**
      * Excludes files by expression
@@ -269,7 +317,13 @@ public enum Arg {
                     .build())
             
.addOption(Option.builder().longOpt("input-exclude").hasArgs().argName("Expression")
                     .desc("Excludes files matching <Expression>.")
-                    .build())),
+                    .build()),
+            (context, selected) -> {
+                String[] excludes = 
context.getCommandLine().getOptionValues(selected);
+                if (excludes != null) {
+                    
context.getConfiguration().addExcludedPatterns(Arrays.asList(excludes));
+                }
+            }),
 
     /**
      * Excludes files based on the contents of a file.
@@ -286,7 +340,17 @@ public enum Arg {
                     .argName("File").hasArg().type(File.class)
                     .converter(Converters.FILE_CONVERTER)
                     .desc("Reads <Expression> entries from a file. Entries 
will be excluded from processing.")
-                    .build())),
+                    .build()),
+            (context, selected) -> {
+                try {
+                    File excludeFileName = 
context.getCommandLine().getParsedOptionValue(selected);
+                    if (excludeFileName != null) {
+                        
context.getConfiguration().addExcludedPatterns(ExclusionUtils.asIterable(excludeFileName,
 "#"));
+                    }
+                } catch (Exception e) {
+                    throw ConfigurationException.from(e);
+                }
+            }),
     /**
      * Excludes files based on standard groupings.
      */
@@ -296,8 +360,12 @@ public enum Arg {
                     .desc("Excludes files defined in standard collections 
based on commonly occurring groups. " +
                             "Excludes any path matcher actions but DOES NOT 
exclude any file processor actions.")
                     .type(StandardCollection.class)
-                    .build())
-    ),
+                    .build()),
+            (context, selected) -> {
+                for (String s : 
context.getCommandLine().getOptionValues(selected)) {
+                    
context.getConfiguration().addExcludedCollection(StandardCollection.valueOf(s));
+                }
+            }),
 
     /**
      * Excludes files if they are smaller than the given threshold.
@@ -306,8 +374,20 @@ public enum Arg {
             
.addOption(Option.builder().longOpt("input-exclude-size").argName("Integer")
                     .hasArg().type(Integer.class)
                     .desc("Excludes files with sizes less than the number of 
bytes specified.")
-                    .build())
-    ),
+                    .build()),
+            (context, selected) -> {
+                try {
+                    final int maxSize = 
context.getCommandLine().getParsedOptionValue(selected);
+                    DocumentNameMatcher matcher = new 
DocumentNameMatcher(String.format("File size < %s bytes", maxSize),
+                            (Predicate<DocumentName>) documentName -> {
+                                File f = new File(documentName.getName());
+                                return f.isFile() && f.length() < maxSize;
+                            });
+                    context.getConfiguration().addExcludedMatcher(matcher);
+                } catch (Exception e) {
+                    throw ConfigurationException.from(e);
+                }
+            }),
     /**
      * Excludes files by expression.
      */
@@ -319,8 +399,13 @@ public enum Arg {
                     .desc("Includes files matching <Expression>. Will override 
excluded files.")
                     
.deprecated(DeprecatedAttributes.builder().setForRemoval(true).setSince("0.17")
                             
.setDescription(StdMsgs.useMsg("--input-include")).get())
-                    .build())
-    ),
+                    .build()),
+            (context, selected) -> {
+                String[] includes = 
context.getCommandLine().getOptionValues(selected);
+                if (includes != null) {
+                    
context.getConfiguration().addIncludedPatterns(Arrays.asList(includes));
+                }
+            }),
 
     /**
      * Includes files based on the contents of a file.
@@ -337,7 +422,17 @@ public enum Arg {
                     .desc("Reads <Expression> entries from a file. Entries 
will be excluded from processing.")
                     
.deprecated(DeprecatedAttributes.builder().setForRemoval(true).setSince("0.17")
                             
.setDescription(StdMsgs.useMsg("--input-include-file")).get())
-                    .build())),
+                    .build()),
+            (context, selected) -> {
+                try {
+                    File includeFileName = 
context.getCommandLine().getParsedOptionValue(selected);
+                    if (includeFileName != null) {
+                        
context.getConfiguration().addIncludedPatterns(ExclusionUtils.asIterable(includeFileName,
 "#"));
+                    }
+                } catch (Exception e) {
+                    throw ConfigurationException.from(e);
+                }
+            }),
 
     /**
      * Includes files based on standard groups.
@@ -353,8 +448,17 @@ public enum Arg {
                     .desc("Scans hidden directories.")
                     
.deprecated(DeprecatedAttributes.builder().setForRemoval(true).setSince("0.17")
                             
.setDescription(StdMsgs.useMsg("--input-include-std with 'HIDDEN_DIR' 
argument")).get()).build()
-            )
-    ),
+            ),
+            (context, selected) -> {
+                // display deprecation log if needed.
+                if 
(context.getCommandLine().hasOption("scan-hidden-directories")) {
+                    
context.getConfiguration().addIncludedCollection(StandardCollection.HIDDEN_DIR);
+                } else {
+                    for (String s : 
context.getCommandLine().getOptionValues(selected)) {
+                        
context.getConfiguration().addIncludedCollection(StandardCollection.valueOf(s));
+                    }
+                }
+            }),
 
     /**
      * Excludes files based on SCM exclusion file processing.
@@ -366,17 +470,32 @@ public enum Arg {
                     .desc("Parse SCM based exclusion files to exclude 
specified files and directories. " +
                             "This action can apply to any standard collection 
that implements a file processor.")
                     .type(StandardCollection.class)
-                    .build())
-    ),
+                    .build()),
+            (context, selected) -> {
+                StandardCollection[] collections = 
getParsedOptionValues(selected, context.getCommandLine());
+                final ReportConfiguration configuration = 
context.getConfiguration();
+                for (StandardCollection collection : collections) {
+                    if (collection == StandardCollection.ALL) {
+                        
Arrays.asList(StandardCollection.values()).forEach(configuration::addExcludedFileProcessor);
+                        
Arrays.asList(StandardCollection.values()).forEach(configuration::addExcludedCollection);
+                    } else {
+                        configuration.addExcludedFileProcessor(collection);
+                        configuration.addExcludedCollection(collection);
+                    }
+                }
+            }),
 
     /**
      * Stop processing an input stream and declare an input file.
      */
     DIR(new 
OptionGroup().addOption(Option.builder().option("d").longOpt("dir").hasArg()
-                    .type(File.class)
+            .type(File.class)
             .desc("Used to indicate end of list when using options that take 
multiple arguments.").argName("DirOrArchive")
             
.deprecated(DeprecatedAttributes.builder().setForRemoval(true).setSince("0.17")
-                    .setDescription("Use the standard '--' to signal the end 
of arguments.").get()).build())),
+                    .setDescription("Use the standard '--' to signal the end 
of arguments.").get()).build()),
+            (context, selected) -> {
+                throw new ImplementationException(String.format("'%s' should 
not be executed directly", selected));
+            }),
 
     /////////////// OUTPUT OPTIONS
     /**
@@ -397,7 +516,22 @@ public enum Arg {
                             .setForRemoval(true)
                             .setDescription(StdMsgs.useMsg("--output-style 
with the 'xml' argument")).get())
                     .desc("forces XML output rather than the textual report.")
-                    .build())),
+                    .build()),
+            (context, selected) -> {
+                String key = selected.getKey(); // is not null due to above 
isSelected()-call
+                if ("x".equals(key)) {
+                    // display deprecated message.
+                    context.getCommandLine().hasOption("x");
+                    
context.getConfiguration().setStyleSheet(StyleSheets.getStyleSheet("xml"));
+                } else {
+                    String[] style = 
context.getCommandLine().getOptionValues(selected);
+                    if (style.length != 1) {
+                        DefaultLog.getInstance().error("Please specify a 
single stylesheet");
+                        throw new ConfigurationException("Please specify a 
single stylesheet");
+                    }
+                    
context.getConfiguration().setStyleSheet(StyleSheets.getStyleSheet(style[0]));
+                }
+            }),
 
     /**
      * Specifies the license definitions that should be included in the output.
@@ -411,7 +545,14 @@ public enum Arg {
                     .desc("List the defined licenses.")
                     .converter(s -> 
LicenseSetFactory.LicenseFilter.valueOf(s.toUpperCase()))
                     
.deprecated(DeprecatedAttributes.builder().setSince("0.17").setForRemoval(true).setDescription(StdMsgs.useMsg("--output-licenses")).get())
-                    .build())),
+                    .build()),
+            (context, selected) -> {
+                try {
+                    
context.getConfiguration().listLicenses(context.getCommandLine().getParsedOptionValue(selected));
+                } catch (ParseException e) {
+                    context.logParseException(e, selected, 
Defaults.LIST_LICENSES);
+                }
+            }),
 
     /**
      * Specifies the license families that should be included in the output.
@@ -425,7 +566,14 @@ public enum Arg {
                     .desc("List the defined license families.")
                     .converter(s -> 
LicenseSetFactory.LicenseFilter.valueOf(s.toUpperCase()))
                     
.deprecated(DeprecatedAttributes.builder().setSince("0.17").setForRemoval(true).setDescription(StdMsgs.useMsg("--output-families")).get())
-                    .build())),
+                    .build()),
+            (context, selected) -> {
+                try {
+                    
context.getConfiguration().listFamilies(context.getCommandLine().getParsedOptionValue(selected));
+                } catch (ParseException e) {
+                    context.logParseException(e, selected, 
Defaults.LIST_FAMILIES);
+                }
+            }),
 
     /**
      * Specifies the log level to log messages at.
@@ -434,14 +582,25 @@ public enum Arg {
             .hasArg().argName("LogLevel")
             .desc("Sets the log level.")
             .converter(s -> Log.Level.valueOf(s.toUpperCase()))
-            .build())),
+            .build()),
+            (context, selected) -> {
+                Log dLog = DefaultLog.getInstance();
+                try {
+                    
dLog.setLevel(context.getCommandLine().getParsedOptionValue(selected));
+                } catch (ParseException e) {
+                    logParseException(DefaultLog.getInstance(), e, selected, 
context.getCommandLine(), dLog.getLevel());
+                }
+            }),
 
     /**
      * Specifies that the run should not perform any updates to files.
      */
     DRY_RUN(new OptionGroup().addOption(Option.builder().longOpt("dry-run")
             .desc("If set do not update the files but generate the reports.")
-            .build())),
+            .build()),
+            (context, selected) ->
+                    context.getConfiguration().setDryRun(true)
+    ),
 
     /**
      * Specifies where the output should be written.
@@ -457,7 +616,20 @@ public enum Arg {
                     .desc("Define the output file where to write a report to.")
                     .type(File.class)
                     .converter(Converters.FILE_CONVERTER)
-                    .build())),
+                    .build()),
+            (context, selected) -> {
+                try {
+                    File file = 
context.getCommandLine().getParsedOptionValue(selected);
+                    File parent = file.getParentFile();
+                    if (!parent.mkdirs() && !parent.isDirectory()) {
+                        DefaultLog.getInstance().error("Could not create 
report parent directory " + file);
+                    }
+                    context.getConfiguration().setOut(file);
+                } catch (ParseException e) {
+                    context.logParseException(e, selected, "System.out");
+                    context.getConfiguration().setOut(() -> 
CloseShieldOutputStream.wrap(System.out));
+                }
+            }),
 
     /**
      * Specifies the level of reporting detail for archive files.
@@ -466,7 +638,15 @@ public enum Arg {
             
.addOption(Option.builder().longOpt("output-archive").hasArg().argName("ProcessingType")
                     .desc("Specifies the level of detail in ARCHIVE file 
reporting.")
                     .converter(s -> 
ReportConfiguration.Processing.valueOf(s.toUpperCase()))
-                    .build())),
+                    .build()),
+            (context, selected) -> {
+                try {
+                    
context.getConfiguration().setArchiveProcessing(context.getCommandLine().getParsedOptionValue(selected));
+                } catch (ParseException e) {
+                    context.logParseException(e, selected, 
Defaults.ARCHIVE_PROCESSING);
+                }
+            }
+    ),
 
     /**
      * Specifies the level of reporting detail for standard files.
@@ -475,51 +655,57 @@ public enum Arg {
             
.addOption(Option.builder().longOpt("output-standard").hasArg().argName("ProcessingType")
                     .desc("Specifies the level of detail in STANDARD file 
reporting.")
                     .converter(s -> 
ReportConfiguration.Processing.valueOf(s.toUpperCase()))
-                    .build())),
+                    .build()),
+            (context, selected) -> {
+                try {
+                    
context.getConfiguration().setStandardProcessing(context.getCommandLine().getParsedOptionValue(selected));
+                } catch (ParseException e) {
+                    context.logParseException(e, selected, 
Defaults.STANDARD_PROCESSING);
+                }
+            }),
 
     /**
      * Provide license definition listing of registered licenses.
      */
     HELP_LICENSES(new OptionGroup()
             .addOption(Option.builder().longOpt("help-licenses") //
-                    .desc("Print information about registered 
licenses.").build()));
+                    .desc("Print information about registered 
licenses.").build()),
+            (context, selected) -> {
+                throw new ImplementationException(String.format("'%s' should 
not be executed directly", selected));
+            });
 
-    /** The option group for the argument */
+    /**
+     * The option group for the argument
+     */
     private final OptionGroup group;
 
+    /**
+     * The apply the option to update the state of the context.configuration.
+     */
+    private final BiConsumer<ArgumentContext, Option> process;
+
     /**
      * Creates an Arg from an Option group.
      *
      * @param group The option group.
      */
-    Arg(final OptionGroup group) {
+    Arg(final OptionGroup group, final BiConsumer<ArgumentContext, Option> 
process) {
         this.group = group;
+        this.process = process;
     }
 
-    /**
-     * Determines if the group has a selected element.
-     *
-     * @return {@code true} if the group has a selected element.
-     */
-    public boolean isSelected() {
-        return group.getSelected() != null;
+    private void execute(final ArgumentContext context, final 
UIOptionCollection<?> optionCollection) {
+        optionCollection.getSelected(this)
+                .ifPresent(selected -> this.process.accept(context, selected));
     }
 
     /**
-     * Gets the select element from the group.
+     * Determines if all the options have been removed from this argument.
      *
-     * @return the selected element or {@code null} if no element is selected.
-     */
-    public Option getSelected() {
-        String s = group.getSelected();
-        if (s != null) {
-            for (Option result : group.getOptions()) {
-                if (result.getKey().equals(s)) {
-                    return result;
-                }
-            }
-        }
-        return null;
+     * @return {@code true} if all the options have been removed from this 
argument.
+     */
+    public boolean isEmpty() {
+        return this.group().getOptions().isEmpty();
     }
 
     /**
@@ -538,14 +724,6 @@ public enum Arg {
         throw new IllegalArgumentException("Can not find " + key);
     }
 
-    /**
-     * Gets the default value for this arg.
-     * @return default value of this arg.
-     */
-    public String defaultValue() {
-        return DEFAULT_VALUES.get(this);
-    }
-
     /**
      * Gets the group for this arg.
      *
@@ -558,20 +736,25 @@ public enum Arg {
     /**
      * Returns the first non-deprecated option from the group.
      *
-     * @return the first non-deprecated option or {@code null} if no 
non-deprecated option is available.
+     * @return the first non-deprecated option or, if no non-deprecated option 
is available, the first option.
      */
     public Option option() {
+        Option first = null;
         for (Option result : group.getOptions()) {
+            if (first == null) {
+                first = result;
+            }
             if (!result.isDeprecated()) {
                 return result;
             }
         }
-        return null;
+        return first;
     }
 
     /**
      * Gets the full set of options.
-     * @return  the full set of options for this Arg.
+     *
+     * @return the full set of options for this Arg.
      */
     public static Options getOptions() {
         Options options = new Options();
@@ -586,36 +769,44 @@ public enum Arg {
      *
      * @param context the context to work with.
      */
-    private static void processEditArgs(final ArgumentContext context) {
-        if (EDIT_ADD.isSelected()) {
-            context.getCommandLine().hasOption(Arg.EDIT_ADD.getSelected());
-            boolean force = EDIT_OVERWRITE.isSelected();
+    private static void processEditArgs(final ArgumentContext context, final 
UIOptionCollection<?> optionCollection) {
+        optionCollection.getSelected(EDIT_ADD).ifPresent(option -> {
+            // prints deprecation
+            context.getCommandLine().hasOption(option);
+            boolean force = optionCollection.isSelected(EDIT_OVERWRITE);
             if (force) {
-                
context.getCommandLine().hasOption(EDIT_OVERWRITE.getSelected());
+                // prints deprecation
+                
optionCollection.getSelected(EDIT_OVERWRITE).ifPresent(context.getCommandLine()::hasOption);
             }
             context.getConfiguration().setAddLicenseHeaders(force ? 
AddLicenseHeaders.FORCED : AddLicenseHeaders.TRUE);
-            if (EDIT_COPYRIGHT.isSelected()) {
-                
context.getConfiguration().setCopyrightMessage(context.getCommandLine().getOptionValue(EDIT_COPYRIGHT.getSelected()));
-            }
-        }
+            optionCollection.getSelected(EDIT_COPYRIGHT).
+                    ifPresent(editOption -> 
context.getConfiguration().setCopyrightMessage(context.getCommandLine().getOptionValue(editOption)));
+        });
     }
 
-    private static List<String> processArrayArg(final ArgumentContext context, 
final Arg arg) throws ParseException {
-        String[] ids = 
context.getCommandLine().getParsedOptionValue(arg.getSelected());
-        return Arrays.asList(ids);
+    private static List<String> processArrayArg(final ArgumentContext context, 
final Option selected) {
+        try {
+            return 
Arrays.asList(context.getCommandLine().getParsedOptionValue(selected));
+        } catch (ParseException e) {
+            throw new ConfigurationException(e);
+        }
     }
 
-    private static List<String> processArrayFile(final ArgumentContext 
context, final Arg arg) throws ParseException {
+    private static List<String> processArrayFile(final ArgumentContext 
context, final Option selected) {
         List<String> result = new ArrayList<>();
-        File file = 
context.getCommandLine().getParsedOptionValue(arg.getSelected());
-        try (InputStream in = Files.newInputStream(file.toPath())) {
-            for (String line : IOUtils.readLines(in, StandardCharsets.UTF_8)) {
-                String[] ids = Converters.TEXT_LIST_CONVERTER.apply(line);
-                result.addAll(Arrays.asList(ids));
+        try {
+            File file = 
context.getCommandLine().getParsedOptionValue(selected);
+            try (InputStream in = Files.newInputStream(file.toPath())) {
+                for (String line : IOUtils.readLines(in, 
StandardCharsets.UTF_8)) {
+                    String[] ids = Converters.TEXT_LIST_CONVERTER.apply(line);
+                    result.addAll(Arrays.asList(ids));
+                }
+                return result;
+            } catch (IOException e) {
+                throw new ConfigurationException(e);
             }
-            return result;
-        } catch (IOException e) {
-            throw new ConfigurationException(e);
+        } catch (ParseException e) {
+            throw ConfigurationException.from(e);
         }
     }
 
@@ -625,64 +816,28 @@ public enum Arg {
      * @param context the context to process.
      * @throws ConfigurationException if configuration files can not be read.
      */
-    private static void processConfigurationArgs(final ArgumentContext 
context) throws ConfigurationException {
-        try {
-            Defaults.Builder defaultBuilder = Defaults.builder();
-            if (CONFIGURATION.isSelected()) {
-                File[] files = 
CONFIGURATION.getParsedOptionValues(context.getCommandLine());
-                for (File file : files) {
-                    defaultBuilder.add(file);
-                }
-            }
-            if (CONFIGURATION_NO_DEFAULTS.isSelected()) {
-                // display deprecation log if needed.
-                
context.getCommandLine().hasOption(CONFIGURATION_NO_DEFAULTS.getSelected());
-                defaultBuilder.noDefault();
-            }
-            context.getConfiguration().setFrom(defaultBuilder.build());
-
-            if (FAMILIES_APPROVED.isSelected()) {
-                
context.getConfiguration().addApprovedLicenseCategories(processArrayArg(context,
 FAMILIES_APPROVED));
-            }
-            if (FAMILIES_APPROVED_FILE.isSelected()) {
-                
context.getConfiguration().addApprovedLicenseCategories(processArrayFile(context,
 FAMILIES_APPROVED_FILE));
-            }
-            if (FAMILIES_DENIED.isSelected()) {
-                
context.getConfiguration().removeApprovedLicenseCategories(processArrayArg(context,
 FAMILIES_DENIED));
-            }
-            if (FAMILIES_DENIED_FILE.isSelected()) {
-                
context.getConfiguration().removeApprovedLicenseCategories(processArrayFile(context,
 FAMILIES_DENIED_FILE));
-            }
-
-            if (LICENSES_APPROVED.isSelected()) {
-                
context.getConfiguration().addApprovedLicenseIds(processArrayArg(context, 
LICENSES_APPROVED));
-            }
+    private static void processConfigurationArgs(final ArgumentContext 
context, final UIOptionCollection<?> optionCollection) throws 
ConfigurationException {
 
-            if (LICENSES_APPROVED_FILE.isSelected()) {
-                
context.getConfiguration().addApprovedLicenseIds(processArrayFile(context, 
LICENSES_APPROVED_FILE));
-            }
-            if (LICENSES_DENIED.isSelected()) {
-                
context.getConfiguration().removeApprovedLicenseIds(processArrayArg(context, 
LICENSES_DENIED));
-            }
+        Defaults.Builder defaultBuilder = Defaults.builder();
 
-            if (LICENSES_DENIED_FILE.isSelected()) {
-                
context.getConfiguration().removeApprovedLicenseIds(processArrayFile(context, 
LICENSES_DENIED_FILE));
-            }
-            if (COUNTER_MAX.isSelected()) {
-                for (String arg : 
context.getCommandLine().getOptionValues(COUNTER_MAX.getSelected())) {
-                    Pair<Counter, Integer> pair = 
Converters.COUNTER_CONVERTER.apply(arg);
-                    int limit = pair.getValue();
-                    
context.getConfiguration().getClaimValidator().setMax(pair.getKey(), limit < 0 
? Integer.MAX_VALUE : limit);
-                }
-            }
-            if (COUNTER_MIN.isSelected()) {
-                for (String arg : 
context.getCommandLine().getOptionValues(COUNTER_MIN.getSelected())) {
-                    Pair<Counter, Integer> pair = 
Converters.COUNTER_CONVERTER.apply(arg);
-                    
context.getConfiguration().getClaimValidator().setMin(pair.getKey(), 
pair.getValue());
-                }
-            }
-        } catch (Exception e) {
-            throw ConfigurationException.from(e);
+        optionCollection.getSelected(CONFIGURATION).ifPresent(
+                selected -> {
+                    File[] files = getParsedOptionValues(selected, 
context.getCommandLine());
+                    for (File file : files) {
+                        defaultBuilder.add(file);
+                    }
+                });
+        
optionCollection.getSelected(CONFIGURATION_NO_DEFAULTS).ifPresent(selected -> {
+            // display deprecation log if needed.
+            context.getCommandLine().hasOption(selected);
+            defaultBuilder.noDefault();
+        });
+        context.getConfiguration().setFrom(defaultBuilder.build());
+
+        for (Arg arg : new Arg[]{FAMILIES_APPROVED, FAMILIES_APPROVED_FILE, 
FAMILIES_DENIED, FAMILIES_DENIED_FILE,
+                LICENSES_APPROVED, LICENSES_APPROVED_FILE, LICENSES_DENIED, 
LICENSES_DENIED_FILE,
+                COUNTER_MAX, COUNTER_MIN}) {
+            arg.execute(context, optionCollection);
         }
     }
 
@@ -692,91 +847,21 @@ public enum Arg {
      * @param context the context to work in.
      * @throws ConfigurationException if an exclude file can not be read.
      */
-    private static void processInputArgs(final ArgumentContext context) throws 
ConfigurationException {
-        try {
-            if (SOURCE.isSelected()) {
-                File[] files = 
SOURCE.getParsedOptionValues(context.getCommandLine());
-                for (File f : files) {
-                    context.getConfiguration().addSource(f);
-                }
-            }
-            // TODO when include/exclude processing is updated check calling 
methods to ensure that all specified
-            // directories are handled in the list of directories.
-            if (EXCLUDE.isSelected()) {
-                String[] excludes = 
context.getCommandLine().getOptionValues(EXCLUDE.getSelected());
-                if (excludes != null) {
-                    
context.getConfiguration().addExcludedPatterns(Arrays.asList(excludes));
-                }
-            }
-            if (EXCLUDE_FILE.isSelected()) {
-                File excludeFileName = 
context.getCommandLine().getParsedOptionValue(EXCLUDE_FILE.getSelected());
-                if (excludeFileName != null) {
-                    
context.getConfiguration().addExcludedPatterns(ExclusionUtils.asIterable(excludeFileName,
 "#"));
-                }
-            }
-            if (EXCLUDE_STD.isSelected()) {
-                for (String s : 
context.getCommandLine().getOptionValues(EXCLUDE_STD.getSelected())) {
-                    
context.getConfiguration().addExcludedCollection(StandardCollection.valueOf(s));
-                }
-            }
-            if (EXCLUDE_PARSE_SCM.isSelected()) {
-                StandardCollection[] collections = 
EXCLUDE_PARSE_SCM.getParsedOptionValues(context.getCommandLine());
-                final ReportConfiguration configuration = 
context.getConfiguration();
-                for (StandardCollection collection : collections) {
-                    if (collection == StandardCollection.ALL) {
-                        
Arrays.asList(StandardCollection.values()).forEach(configuration::addExcludedFileProcessor);
-                        
Arrays.asList(StandardCollection.values()).forEach(configuration::addExcludedCollection);
-                    } else {
-                        configuration.addExcludedFileProcessor(collection);
-                        configuration.addExcludedCollection(collection);
-                    }
-                }
-            }
-            if (EXCLUDE_SIZE.isSelected()) {
-                final int maxSize = 
EXCLUDE_SIZE.getParsedOptionValue(context.getCommandLine());
-                DocumentNameMatcher matcher = new 
DocumentNameMatcher(String.format("File size < %s bytes", maxSize),
-                        (Predicate<DocumentName>) documentName -> {
-                        File f = new File(documentName.getName());
-                        return f.isFile() && f.length() < maxSize;
-                });
-                context.getConfiguration().addExcludedMatcher(matcher);
-            }
-            if (INCLUDE.isSelected()) {
-                String[] includes = 
context.getCommandLine().getOptionValues(INCLUDE.getSelected());
-                if (includes != null) {
-                    
context.getConfiguration().addIncludedPatterns(Arrays.asList(includes));
-                }
-            }
-            if (INCLUDE_FILE.isSelected()) {
-                File includeFileName = 
context.getCommandLine().getParsedOptionValue(INCLUDE_FILE.getSelected());
-                if (includeFileName != null) {
-                    
context.getConfiguration().addIncludedPatterns(ExclusionUtils.asIterable(includeFileName,
 "#"));
-                }
-            }
-            if (INCLUDE_STD.isSelected()) {
-                Option selected = INCLUDE_STD.getSelected();
-                // display deprecation log if needed.
-                if 
(context.getCommandLine().hasOption("scan-hidden-directories")) {
-                    
context.getConfiguration().addIncludedCollection(StandardCollection.HIDDEN_DIR);
-                } else {
-                    for (String s : 
context.getCommandLine().getOptionValues(selected)) {
-                        
context.getConfiguration().addIncludedCollection(StandardCollection.valueOf(s));
-                    }
-                }
-            }
-        } catch (Exception e) {
-            throw ConfigurationException.from(e);
+    private static void processInputArgs(final ArgumentContext context, final 
UIOptionCollection<?> optionCollection) throws ConfigurationException {
+        for (Arg arg : new Arg[]{SOURCE, EXCLUDE, EXCLUDE_FILE, EXCLUDE_STD, 
EXCLUDE_PARSE_SCM, EXCLUDE_SIZE,
+                INCLUDE, INCLUDE_FILE, INCLUDE_STD}) {
+            arg.execute(context, optionCollection);
         }
     }
 
     /**
      * Logs a ParseException as a warning.
      *
-     * @param log       the Log to write to
+     * @param log the Log to write to
      * @param exception the parse exception to log
-     * @param opt       the option being processed
-     * @param cl        the command line being processed
-     * @param defaultValue      The default value the option is being set to.
+     * @param opt the option being processed
+     * @param cl the command line being processed
+     * @param defaultValue The default value the option is being set to.
      */
     private static void logParseException(final Log log, final ParseException 
exception, final Option opt, final CommandLine cl, final Object defaultValue) {
         log.warn(format("Invalid %s specified: %s ", opt.getOpt(), 
cl.getOptionValue(opt)));
@@ -787,17 +872,10 @@ public enum Arg {
     /**
      * Process the log level setting.
      *
-     * @param commandLine The command line to process.
+     * @param context The argument context
      */
-    public static void processLogLevel(final CommandLine commandLine) {
-        if (LOG_LEVEL.getSelected() != null) {
-            Log dLog = DefaultLog.getInstance();
-            try {
-                
dLog.setLevel(commandLine.getParsedOptionValue(LOG_LEVEL.getSelected()));
-            } catch (ParseException e) {
-                logParseException(DefaultLog.getInstance(), e, 
LOG_LEVEL.getSelected(), commandLine, dLog.getLevel());
-            }
-        }
+    public static void processLogLevel(final ArgumentContext context, final 
UIOptionCollection<?> optionCollection) throws ConfigurationException {
+        LOG_LEVEL.execute(context, optionCollection);
     }
 
     /**
@@ -806,12 +884,12 @@ public enum Arg {
      * @param context the context in which to process the args.
      * @throws ConfigurationException on error
      */
-    public static void processArgs(final ArgumentContext context) throws 
ConfigurationException {
+    public static void processArgs(final ArgumentContext context, final 
UIOptionCollection<?> optionCollection) throws ConfigurationException {
         
Converters.FILE_CONVERTER.setWorkingDirectory(context.getWorkingDirectory());
-        processOutputArgs(context);
-        processEditArgs(context);
-        processInputArgs(context);
-        processConfigurationArgs(context);
+        processOutputArgs(context, optionCollection);
+        processEditArgs(context, optionCollection);
+        processInputArgs(context, optionCollection);
+        processConfigurationArgs(context, optionCollection);
     }
 
     /**
@@ -819,69 +897,9 @@ public enum Arg {
      *
      * @param context the context in which to process the args.
      */
-    private static void processOutputArgs(final ArgumentContext context) {
-        context.getConfiguration().setDryRun(DRY_RUN.isSelected());
-
-        if (OUTPUT_FAMILIES.isSelected()) {
-            try {
-                
context.getConfiguration().listFamilies(context.getCommandLine().getParsedOptionValue(OUTPUT_FAMILIES.getSelected()));
-            } catch (ParseException e) {
-                context.logParseException(e, OUTPUT_FAMILIES.getSelected(), 
Defaults.LIST_FAMILIES);
-            }
-        }
-
-        if (OUTPUT_LICENSES.isSelected()) {
-            try {
-                
context.getConfiguration().listLicenses(context.getCommandLine().getParsedOptionValue(OUTPUT_LICENSES.getSelected()));
-            } catch (ParseException e) {
-                context.logParseException(e, OUTPUT_LICENSES.getSelected(), 
Defaults.LIST_LICENSES);
-            }
-        }
-
-        if (OUTPUT_ARCHIVE.isSelected()) {
-            try {
-                
context.getConfiguration().setArchiveProcessing(context.getCommandLine().getParsedOptionValue(OUTPUT_ARCHIVE.getSelected()));
-            } catch (ParseException e) {
-                context.logParseException(e, OUTPUT_ARCHIVE.getSelected(), 
Defaults.ARCHIVE_PROCESSING);
-            }
-        }
-
-        if (OUTPUT_STANDARD.isSelected()) {
-            try {
-                
context.getConfiguration().setStandardProcessing(context.getCommandLine().getParsedOptionValue(OUTPUT_STANDARD.getSelected()));
-            } catch (ParseException e) {
-                context.logParseException(e, OUTPUT_STANDARD.getSelected(), 
Defaults.STANDARD_PROCESSING);
-            }
-        }
-
-        if (OUTPUT_FILE.isSelected()) {
-            try {
-                File file = 
context.getCommandLine().getParsedOptionValue(OUTPUT_FILE.getSelected());
-                File parent = file.getParentFile();
-                if (!parent.mkdirs() && !parent.isDirectory()) {
-                    DefaultLog.getInstance().error("Could not create report 
parent directory " + file);
-                }
-                context.getConfiguration().setOut(file);
-            } catch (ParseException e) {
-                context.logParseException(e, OUTPUT_FILE.getSelected(), 
"System.out");
-                context.getConfiguration().setOut((IOSupplier<OutputStream>) 
null);
-            }
-        }
-
-        if (OUTPUT_STYLE.isSelected()) {
-            String selected = OUTPUT_STYLE.getSelected().getKey(); // is not 
null due to above isSelected()-call
-            if ("x".equals(selected)) {
-                // display deprecated message.
-                context.getCommandLine().hasOption("x");
-                
context.getConfiguration().setStyleSheet(StyleSheets.getStyleSheet("xml"));
-            } else {
-                String[] style = 
context.getCommandLine().getOptionValues(OUTPUT_STYLE.getSelected());
-                if (style.length != 1) {
-                    DefaultLog.getInstance().error("Please specify a single 
stylesheet");
-                    throw new ConfigurationException("Please specify a single 
stylesheet");
-                }
-                
context.getConfiguration().setStyleSheet(StyleSheets.getStyleSheet(style[0]));
-            }
+    private static void processOutputArgs(final ArgumentContext context, final 
UIOptionCollection<?> optionCollection) throws ConfigurationException {
+        for (Arg arg : new Arg[]{DRY_RUN, OUTPUT_FAMILIES, OUTPUT_LICENSES, 
OUTPUT_ARCHIVE, OUTPUT_STANDARD, OUTPUT_FILE, OUTPUT_STYLE}) {
+            arg.execute(context, optionCollection);
         }
     }
 
@@ -898,27 +916,9 @@ public enum Arg {
         }
     }
 
-    /**
-     * Finds the Arg that an Option is in.
-     *
-     * @param optionToFind the Option to locate.
-     * @return The Arg or {@code null} if no Arg is found.
-     */
-    public static Arg findArg(final Option optionToFind) {
-        if (optionToFind != null) {
-            for (Arg arg : Arg.values()) {
-                for (Option candidate : arg.group.getOptions()) {
-                    if (optionToFind.equals(candidate)) {
-                        return arg;
-                    }
-                }
-            }
-        }
-        return null;
-    }
-
     /**
      * Finds the Arg that contains an Option with the specified key.
+     *
      * @param key the key for the Option to locate.
      * @return The Arg or {@code null} if no Arg is found.
      */
@@ -935,31 +935,18 @@ public enum Arg {
         return null;
     }
 
-    private <T> T getParsedOptionValue(final CommandLine commandLine) throws 
ParseException {
-        return commandLine.getParsedOptionValue(this.getSelected());
-    }
-
-    private String getOptionValue(final CommandLine commandLine) {
-        return commandLine.getOptionValue(this.getSelected());
-    }
-
-    private String[] getOptionValues(final CommandLine commandLine) {
-        return commandLine.getOptionValues(this.getSelected());
-    }
-
-    private <T> T[] getParsedOptionValues(final CommandLine commandLine)  {
-        Option option = getSelected();
+    private static <T> T[] getParsedOptionValues(final Option selected, final 
CommandLine commandLine) {
         try {
-            Class<? extends T> clazz = (Class<? extends T>) option.getType();
-            String[] values = commandLine.getOptionValues(option);
+            Class<? extends T> clazz = (Class<? extends T>) selected.getType();
+            String[] values = commandLine.getOptionValues(selected);
             T[] result = (T[]) Array.newInstance(clazz, values.length);
             for (int i = 0; i < values.length; i++) {
-                result[i] = clazz.cast(option.getConverter().apply(values[i]));
+                result[i] = 
clazz.cast(selected.getConverter().apply(values[i]));
             }
             return result;
         } catch (Throwable t) {
-            throw new ConfigurationException(format("'%s' converter for %s 
'%s' does not produce a class of type %s", this,
-                    option.getKey(), 
option.getConverter().getClass().getName(), option.getType()), t);
+            throw new ConfigurationException(format("'%s' converter for %s 
'%s' does not produce a class of type %s", selected,
+                    selected.getKey(), 
selected.getConverter().getClass().getName(), selected.getType()), t);
         }
     }
 
@@ -981,18 +968,4 @@ public enum Arg {
             return format("Use %s instead.", name);
         }
     }
-
-    /**
-     * The default values description map
-     */
-    private static final Map<Arg, String> DEFAULT_VALUES = new HashMap<>();
-
-    static {
-        DEFAULT_VALUES.put(OUTPUT_FILE, "System.out");
-        DEFAULT_VALUES.put(LOG_LEVEL, Log.Level.WARN.name());
-        DEFAULT_VALUES.put(OUTPUT_ARCHIVE, Defaults.ARCHIVE_PROCESSING.name());
-        DEFAULT_VALUES.put(OUTPUT_STANDARD, 
Defaults.STANDARD_PROCESSING.name());
-        DEFAULT_VALUES.put(OUTPUT_LICENSES, Defaults.LIST_LICENSES.name());
-        DEFAULT_VALUES.put(OUTPUT_FAMILIES, Defaults.LIST_FAMILIES.name());
-    }
 }
diff --git 
a/apache-rat-core/src/main/java/org/apache/rat/commandline/ArgumentContext.java 
b/apache-rat-core/src/main/java/org/apache/rat/commandline/ArgumentContext.java
index ea672bc8..d4374a57 100644
--- 
a/apache-rat-core/src/main/java/org/apache/rat/commandline/ArgumentContext.java
+++ 
b/apache-rat-core/src/main/java/org/apache/rat/commandline/ArgumentContext.java
@@ -25,6 +25,7 @@ import org.apache.commons.cli.Option;
 import org.apache.commons.cli.ParseException;
 import org.apache.rat.ReportConfiguration;
 import org.apache.rat.document.DocumentName;
+import org.apache.rat.ui.UIOptionCollection;
 import org.apache.rat.utils.DefaultLog;
 
 import static java.lang.String.format;
@@ -33,7 +34,7 @@ import static java.lang.String.format;
  * Provides the context necessary to process various arguments.
  * @since 0.17
  */
-public class ArgumentContext {
+public final class ArgumentContext {
     /** The report configuration that is being built */
     private final ReportConfiguration configuration;
     /** The command line that is building the configuration */
@@ -65,8 +66,8 @@ public class ArgumentContext {
     /**
      * Process the arguments specified in this context.
      */
-    public void processArgs() {
-        Arg.processArgs(this);
+    public void processArgs(final UIOptionCollection<?> uiOptionCollection) {
+        Arg.processArgs(this,  uiOptionCollection);
     }
 
     /**
diff --git 
a/apache-rat-core/src/main/java/org/apache/rat/help/AbstractHelp.java 
b/apache-rat-core/src/main/java/org/apache/rat/help/AbstractHelp.java
index eff2b49a..e7a8e336 100644
--- a/apache-rat-core/src/main/java/org/apache/rat/help/AbstractHelp.java
+++ b/apache-rat-core/src/main/java/org/apache/rat/help/AbstractHelp.java
@@ -30,9 +30,9 @@ import org.apache.commons.cli.Option;
 import org.apache.commons.cli.Options;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.text.WordUtils;
+import org.apache.rat.CLIOptionCollection;
 import org.apache.rat.OptionCollection;
 import org.apache.rat.VersionInfo;
-import org.apache.rat.commandline.Arg;
 
 import static java.lang.String.format;
 
@@ -185,8 +185,7 @@ public abstract class AbstractHelp {
                     optBuf.append(END_OF_OPTION_MSG);
                 }
                 // check for default value
-                Arg arg = Arg.findArg(option);
-                String defaultValue = arg == null ? null : arg.defaultValue();
+                String defaultValue = 
CLIOptionCollection.INSTANCE.defaultValue(option);
                 if (defaultValue != null) {
                     optBuf.append(format(" (Default value = %s)", 
defaultValue));
                 }
diff --git 
a/apache-rat-core/src/main/java/org/apache/rat/ui/AbstractCodeGenerator.java 
b/apache-rat-core/src/main/java/org/apache/rat/ui/AbstractCodeGenerator.java
new file mode 100644
index 00000000..3eb5cdae
--- /dev/null
+++ b/apache-rat-core/src/main/java/org/apache/rat/ui/AbstractCodeGenerator.java
@@ -0,0 +1,151 @@
+/*
+ * 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.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 org.apache.velocity.app.VelocityEngine;
+import org.apache.velocity.runtime.RuntimeConstants;
+
+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 UIOption<?>> {
+    /** The base source directory */
+    protected final String baseDirectory;
+    /** the velocity engine to generate files with */
+    protected final VelocityEngine velocityEngine;
+    /**
+     * private constructor.
+     * @param baseDirectory The base source directory.
+     */
+    protected AbstractCodeGenerator(final String baseDirectory) {
+        this.baseDirectory = baseDirectory;
+        velocityEngine = new VelocityEngine();
+        velocityEngine.setProperty(RuntimeConstants.RESOURCE_LOADER, 
"classpath");
+        velocityEngine.setProperty("classpath.resource.loader.class", 
"org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
+        velocityEngine.init();
+    }
+
+    /**
+     * 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/ArgumentTracker.java 
b/apache-rat-core/src/main/java/org/apache/rat/ui/ArgumentTracker.java
new file mode 100644
index 00000000..d1203b2b
--- /dev/null
+++ b/apache-rat-core/src/main/java/org/apache/rat/ui/ArgumentTracker.java
@@ -0,0 +1,239 @@
+/*
+ * 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 Arg values that are set and their values for conversion from native 
UI to
+ * Apache Commons command line values.
+ */
+public final class ArgumentTracker {
+
+    /**
+     * List of deprecated arguments and their deprecation notice.
+     */
+    private final Map<String, String> deprecatedArgs = new HashMap<>();
+
+    /**
+     * A map of CLI-based arguments to values.
+     */
+    private final Map<String, List<String>> args = new HashMap<>();
+
+    /**
+     * The arguments understood by the UI for the current report execution.
+     * @param optionCollection The AbstractOptionCollection for this UI.
+     */
+    public ArgumentTracker(final UIOptionCollection<?> optionCollection) {
+        for (UIOption<?> abstractOption : 
optionCollection.getMappedOptions().toList()) {
+            if (abstractOption.isDeprecated()) {
+                deprecatedArgs.put(abstractOption.getName(),
+                        String.format("Use of deprecated option '%s'. %s", 
abstractOption.getName(), abstractOption.getDeprecated()));
+            }
+        }
+    }
+
+    /**
+     * Extract the core name from the option.  This is the {@link 
Option#getLongOpt()} if defined, otherwise
+     * the {@link Option#getOpt()}.
+     * @param option the commons cli option.
+     * @return the common cli based name.
+     */
+    public static String extractKey(final Option option) {
+        return StringUtils.defaultIfBlank(option.getLongOpt(), 
option.getOpt());
+    }
+
+    /**
+     * Sets the deprecation report method in the Apache Commons CLI processes.
+     */
+    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)));
+    }
+
+    /**
+     * Validate that the option is defined in Args and has not already been 
set.
+     * This check will verify tha only one of the keys in the group can be set.
+     * @param key the key to check
+     * @return true if the key may be set.
+     */
+    private boolean validateSet(final String key) {
+        final Arg arg = Arg.findArg(key);
+        if (arg != null) {
+            final Option opt = arg.find(key);
+            final Option main = arg.option();
+            if (opt.isDeprecated()) {
+                args.remove(extractKey(main));
+                // deprecated options must be explicitly set so let it go.
+                return true;
+            }
+            // non-deprecated options may have default so ignore it if another 
option has already been set.
+            for (Option o : arg.group().getOptions()) {
+                if (!o.equals(main)) {
+                    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 UIOption<?> key, final String value) {
+        setArg(key.keyValue(), value);
+    }
+
+    /**
+     * Set a key and value into the argument list.
+     * Replaces any existing value.
+     * @param trackerKey the key for the map.
+     * @param value the value to set.
+     */
+    public void setArg(final String trackerKey, final String value) {
+        if (value == null || StringUtils.isNotBlank(value)) {
+            if (validateSet(trackerKey)) {
+                Option ratOption = Arg.findArg(trackerKey).find(trackerKey);
+                if (ratOption.hasArg()) {
+                    List<String> values = new ArrayList<>();
+                    if (DefaultLog.getInstance().isEnabled(Log.Level.DEBUG)) {
+                        DefaultLog.getInstance().debug(String.format("Setting 
%s to '%s'", trackerKey, value));
+                    }
+                    values.add(value);
+                    args.put(trackerKey, values);
+                } else {
+                    DefaultLog.getInstance().warn(String.format("Key '%s' does 
not accept arguments.", trackerKey));
+                }
+            } else {
+                DefaultLog.getInstance().warn(String.format("Key '%s' is 
unknown", trackerKey));
+            }
+        }
+    }
+
+    /**
+     * Get the list of values for a key.
+     * @param key the key for the map.
+     * @return the list of values for the key or {@code null} if not set.
+     */
+    public Optional<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 option the option to add values for.
+     * @param value the array of values to set.
+     */
+    public void addArg(final UIOption<?> option, final String... value) {
+        addArg(option.keyValue(), value);
+    }
+
+    /**
+     * Add values to the key in the argument list.
+     * empty values are ignored. If no non-empty values are present no change 
is made.
+     * If the key does not exist, adds it.
+     * @param trackerKey the key add values for.
+     * @param value the array of values to set.
+     */
+    public void addArg(final String trackerKey, final String... value) {
+        List<String> newValues = 
Arrays.stream(value).filter(StringUtils::isNotBlank).collect(Collectors.toList());
+        if (!newValues.isEmpty()) {
+            if (validateSet(trackerKey)) {
+                Option ratOption = Arg.findArg(trackerKey).find(trackerKey);
+                if (ratOption.hasArgs()) {
+                    if (DefaultLog.getInstance().isEnabled(Log.Level.DEBUG)) {
+                        DefaultLog.getInstance().debug(String.format("Adding 
[%s] to %s", String.join(", ", Arrays.asList(value)), trackerKey));
+                    }
+                    List<String> values = args.computeIfAbsent(trackerKey, k 
-> new ArrayList<>());
+                    values.addAll(newValues);
+                } else {
+                    DefaultLog.getInstance().warn(String.format("Key '%s' does 
not accept %sarguments.", trackerKey,
+                            ratOption.hasArg() ? "more that one " : ""));
+                }
+            } else {
+                DefaultLog.getInstance().warn(String.format("Key '%s' is 
unknown", trackerKey));
+            }
+        }
+    }
+
+    /**
+     * Remove a key from the argument list.
+     * @param option the option to remove the key for.
+     */
+    public void removeArg(final UIOption<?> option) {
+        args.remove(option.keyValue());
+    }
+
+    /**
+     * Remove a key from the argument list.
+     * @param trackerKey the key remove.
+     */
+    public void removeArg(final String trackerKey) {
+        args.remove(trackerKey);
+    }
+}
diff --git a/apache-rat-core/src/main/java/org/apache/rat/ui/UI.java 
b/apache-rat-core/src/main/java/org/apache/rat/ui/UI.java
new file mode 100644
index 00000000..db1df6a5
--- /dev/null
+++ b/apache-rat-core/src/main/java/org/apache/rat/ui/UI.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.rat.ui;
+
+public interface UI<T extends UIOption<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.
+     */
+    UIOptionCollection<T> getOptionCollection();
+}
diff --git a/apache-rat-core/src/main/java/org/apache/rat/ui/UIOption.java 
b/apache-rat-core/src/main/java/org/apache/rat/ui/UIOption.java
new file mode 100644
index 00000000..677eafe2
--- /dev/null
+++ b/apache-rat-core/src/main/java/org/apache/rat/ui/UIOption.java
@@ -0,0 +1,224 @@
+/*
+ * 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 UIOption<T extends UIOption<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 UIOptionCollection<T> optionCollection;
+
+    /**
+     * Constructor.
+     *
+     * @param option The CLI option
+     * @param name the UI-specific name for the option.
+     */
+    protected <C extends UIOptionCollection<T>> UIOption(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 UIOptionCollection<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.defaultValue(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();
+    }
+
+    /**
+     * 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/UIOptionCollection.java 
b/apache-rat-core/src/main/java/org/apache/rat/ui/UIOptionCollection.java
new file mode 100644
index 00000000..ab6dcf94
--- /dev/null
+++ b/apache-rat-core/src/main/java/org/apache/rat/ui/UIOptionCollection.java
@@ -0,0 +1,299 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   https://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ */
+package org.apache.rat.ui;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.TreeMap;
+import java.util.function.BiFunction;
+import java.util.stream.Stream;
+
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.Options;
+import org.apache.rat.Defaults;
+import org.apache.rat.commandline.Arg;
+import org.apache.rat.utils.Log;
+
+/**
+ * A collection of options supported by the UI.  This includes RAT options and 
UI specific options.
+ * @param <T> the AbstractOption implementation.
+ */
+public class UIOptionCollection<T extends UIOption<T>> {
+    /** map of ARG to the associated UpdatableOptionGroup */
+    private final Map<Arg, UpdatableOptionGroup> argMap;
+    /** set of RAT OptionGroups with unsupported options for this UI removed */
+    private final UpdatableOptionGroupCollection supportedRatOptions;
+    /** set of UI specific options */
+    private final Map<Option, T> uiOptions;
+    /**
+     * Map of option to overridden default value.  Generally applies to 
supported rat options but may be ui
+     * specific options as well
+     */
+    private final Map <Option, String> defaultValues;
+
+    /**
+     * The function to generate a concrete BaseOption instance.
+     */
+    private final BiFunction<UIOptionCollection<T>, Option, T> mapper;
+
+    /**
+     * Construct the UIOptionCollection from the builder.
+     * @param builder the builder to build from.
+     */
+    protected UIOptionCollection(final Builder<T, ?> builder) {
+        Objects.requireNonNull(builder.mapper, "Builder.mapper");
+        argMap = new TreeMap<>();
+        mapper = builder.mapper;
+        supportedRatOptions = new UpdatableOptionGroupCollection();
+
+        for (Arg arg : Arg.values()) {
+            argMap.put(arg, supportedRatOptions.add(arg.group()));
+        }
+
+        for (Option opt : builder.unsupportedRatOptions) {
+            supportedRatOptions.findGroups(opt).forEach(group -> 
group.disableOption(opt));
+        }
+        uiOptions = new HashMap<>();
+        supportedRatOptions.options().getOptions()
+                .forEach(option -> uiOptions.put(option, mapper.apply(this, 
option)));
+        builder.uiOptions.stream().filter(option -> 
!uiOptions.containsKey(option))
+                .forEach(option -> uiOptions.put(option, mapper.apply(this, 
option)));
+        defaultValues = new HashMap<>(builder.defaultValues);
+    }
+
+    /**
+     * Checks if an Arg is selected.
+     * @param arg the Arg to check.
+     * @return {@code true} if the arg is selected.
+     */
+    public final boolean isSelected(final Arg arg) {
+        UpdatableOptionGroup group = argMap.get(arg);
+        return group != null && group.getSelected() != null;
+    }
+
+    /**
+     * Gets the selected Option for the arg.
+     * @param arg the arg to check.
+     * @return an Optional containing the selected option, or an empty 
Optional if none was selected.
+     */
+    public final Optional<Option> getSelected(final Arg arg) {
+        UpdatableOptionGroup group = argMap.get(arg);
+        String s = group == null ? null : group.getSelected();
+        if (s != null) {
+            for (Option result : group.getOptions()) {
+                if (result.getKey().equals(s)) {
+                    return Optional.of(result);
+                }
+            }
+        }
+        return Optional.empty();
+    }
+
+    /**
+     * Gets the collection of unsupported Options.
+     * @return the Options comprised for the unsupported options.
+     */
+    public final Options getUnsupportedOptions() {
+        return supportedRatOptions.unsupportedOptions();
+    }
+
+    /**
+     * Gets the UiOption instance for the Option.
+     * @param option the option to find the instance of.
+     * @return an UIOption instance that wraps the option.
+     */
+    public final T getMappedOption(final Option option) {
+        return uiOptions.get(option);
+    }
+
+    /**
+     * Gets an Options that contains the RAT Arg defined Option instances that 
are understood by this collection.
+     * OptionGroups are registered in the resulting Options object.
+     * @return an Options that contains the RAT Arg defined Option instances 
that are understood by this collection.
+     */
+    public final Options getOptions() {
+        return supportedRatOptions.options().addOptions(additionalOptions());
+    }
+
+    /**
+     * Gets the Stream of AbstractOption implementations  understood by this 
collection.
+     * @return the Stream of AbstractOption implementations understood by this 
collection.
+     */
+    public final Stream<T> getMappedOptions() {
+        return uiOptions.values().stream();
+    }
+
+    /**
+     * Gets a map client option name to specified AbstractOption 
implementation.
+     * @return a map client option name to specified AbstractOption 
implementation
+     */
+    public final Map<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() {
+        Options options = new Options();
+        uiOptions.keySet().stream()
+                .filter(option -> !supportedRatOptions.contains(option))
+                .forEach(options::addOption);
+        return options;
+    }
+
+    /**
+     * Gets the default value for the option.
+     * @param option the option to lookup.
+     * @return the default value or {@code null} if not set.
+     */
+    public final String defaultValue(final Option option) {
+        return defaultValues.get(option);
+    }
+
+    /**
+     * Builder for a BaseOptionCollection.
+     * @param <T> the concreate type of the BaseOption.
+     * @param <S> the concrete type being built.
+     */
+    protected static class Builder<T extends UIOption<T>, S extends Builder<T, 
S>> {
+        /** set of additional UI specific options */
+        private final List<Option> uiOptions;
+        /**
+         * Map of option to overridden default value.  Generally applies to 
supported rat options but may be ui
+         * specific options as well
+         */
+        private final Map <Option, String> defaultValues;
+        /** The list of unsupported Rat options */
+        protected final List<Option> unsupportedRatOptions;
+        /** The function to convert an option into a UIOption. */
+        private BiFunction<UIOptionCollection<T>, Option, T> mapper;
+
+        /**
+         * Constructor for the builder.
+         */
+        protected Builder() {
+            uiOptions = new ArrayList<>();
+            defaultValues = new HashMap<>();
+            unsupportedRatOptions = new ArrayList<>();
+            defaultValue(Arg.LOG_LEVEL,  Log.Level.WARN.name());
+            defaultValue(Arg.OUTPUT_ARCHIVE, 
Defaults.ARCHIVE_PROCESSING.name());
+            defaultValue(Arg.OUTPUT_STANDARD, 
Defaults.STANDARD_PROCESSING.name());
+            defaultValue(Arg.OUTPUT_LICENSES, Defaults.LIST_LICENSES.name());
+            defaultValue(Arg.OUTPUT_FAMILIES, Defaults.LIST_FAMILIES.name());
+        }
+
+        /**
+         * build the UIOptionCollection.
+         * @return the UIOptionCollection.
+         */
+        public UIOptionCollection<T> build() {
+            return new UIOptionCollection<>(this);
+        }
+
+        /**
+         * Returns this cast to {@code <S>} class.
+         * @return this as {@code <S>} class.
+         */
+        protected final S self() {
+            return (S) this;
+        }
+
+        /**
+         * Set the mapper for the builder.
+         * @param mapper the function to convert an option into a UIOption 
({@code <T>} object).
+         * @return this
+         */
+        public S mapper(final BiFunction<UIOptionCollection<T>, Option, T> 
mapper) {
+            this.mapper = mapper;
+            return self();
+        }
+
+        /**
+         * Add a UI option to the collection.
+         * @param uiOption the UI Option to add.
+         * @return this
+         */
+        public S uiOption(final Option uiOption) {
+            uiOptions.add(uiOption);
+            return self();
+        }
+
+        /**
+         * Add a UI options to the collection.
+         * @param uiOption the UIOptions ({@code <T>} objects) to add.
+         * @return this
+         */
+        public S uiOptions(final Option... uiOption) {
+            uiOptions.addAll(Arrays.asList(uiOption));
+            return self();
+        }
+
+        /**
+         * Register an option as unsupported.
+         * @param option the option that is not be supported.  This should be 
an option in the
+         * {@link Arg} collection.
+         * @return this
+         */
+        public S unsupported(final Option option) {
+            unsupportedRatOptions.add(option);
+            return self();
+        }
+
+        /**
+         * Register multiple options as unsupported.
+         * Will ignore all the options associated with the specified Arg.
+         * @param arg The Arg to ignore.
+         * @return this
+         */
+        public S unsupported(final Arg arg) {
+            unsupportedRatOptions.addAll(arg.group().getOptions());
+            return self();
+        }
+
+        /**
+         * Specify the default values for an option.
+         * @param option the option to specify the default value for.
+         * @param value the value for the option.
+         * @return this
+         */
+        public S defaultValue(final Option option, final String value) {
+            defaultValues.put(option, value);
+            return self();
+        }
+
+        /**
+         * Specify the default values for an Arg.
+         * @param arg the Arg to specify the default value for.
+         * @param value the value for the option.
+         * @return this
+         */
+        public S defaultValue(final Arg arg, final String value) {
+            return defaultValue(arg.option(), value);
+        }
+    }
+}
diff --git 
a/apache-rat-core/src/main/java/org/apache/rat/ui/UpdatableOptionGroup.java 
b/apache-rat-core/src/main/java/org/apache/rat/ui/UpdatableOptionGroup.java
new file mode 100644
index 00000000..e936357e
--- /dev/null
+++ b/apache-rat-core/src/main/java/org/apache/rat/ui/UpdatableOptionGroup.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.rat.ui;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.stream.Stream;
+
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.OptionGroup;
+
+/**
+ * An implementation of Apache Commons CLI OptionGroup that allows options to 
be removed (disabled).
+ */
+public final class UpdatableOptionGroup extends OptionGroup {
+    /** The set of options to remove */
+    private final Set<Option> disabledOptions = new HashSet<>();
+
+    /**
+     * Converts the group into an UpdatableOptionGroup if it is not already an 
instance
+     * @param group the group to convert.
+     * @return an UpdatableOptionGroup.
+     */
+    public static UpdatableOptionGroup create(final OptionGroup group) {
+        return group instanceof UpdatableOptionGroup ? (UpdatableOptionGroup) 
group : new UpdatableOptionGroup(group);
+    }
+
+    private UpdatableOptionGroup(final OptionGroup group) {
+        group.getOptions().forEach(super::addOption);
+    }
+
+    /**
+     * Disable an option in the group.
+     * @param option The option to disable.
+     */
+    public void disableOption(final Option option) {
+        disabledOptions.add(option);
+    }
+
+    public boolean isEmpty() {
+        return getOptions().isEmpty();
+    }
+
+    /**
+     * Gets the disabled options for this group.
+     * @return the set of disabled options for this group.
+     */
+    public Stream<Option> getDisableOptions() {
+        return disabledOptions.stream();
+    }
+    /**
+     * Reset the group so that all disabled options are re-enabled.
+     */
+    public void reset() {
+        disabledOptions.clear();
+    }
+
+    @Override
+    public Collection<Option> getOptions() {
+        return super.getOptions().stream().filter(opt -> 
!disabledOptions.contains(opt)).toList();
+    }
+
+    @Override
+    public UpdatableOptionGroup addOption(final Option option) {
+        super.addOption(option);
+        return this;
+    }
+}
diff --git 
a/apache-rat-core/src/main/java/org/apache/rat/ui/UpdatableOptionGroupCollection.java
 
b/apache-rat-core/src/main/java/org/apache/rat/ui/UpdatableOptionGroupCollection.java
new file mode 100644
index 00000000..893db66a
--- /dev/null
+++ 
b/apache-rat-core/src/main/java/org/apache/rat/ui/UpdatableOptionGroupCollection.java
@@ -0,0 +1,110 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   https://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ */
+package org.apache.rat.ui;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Stream;
+
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.OptionGroup;
+import org.apache.commons.cli.Options;
+
+/**
+ * A collection of UpdatableOptionGroups.
+ */
+public class UpdatableOptionGroupCollection {
+    /** the contained UpdatableOptionGroups */
+    private final List<UpdatableOptionGroup> updatableOptionGroups;
+
+    /**
+     * Creates an empty collection.
+     */
+    public UpdatableOptionGroupCollection() {
+        updatableOptionGroups = new ArrayList<>();
+    }
+
+    /**
+     * Adds an OptionGroup to the collection.  If the OptionGroup is not an 
UpdatableOptionGroup
+     * it is converted first.
+     * @param optionGroup an OptionGroup to add.
+     * @return the UpdatableOptionGroup that was added.
+     */
+    public UpdatableOptionGroup add(final OptionGroup optionGroup) {
+        UpdatableOptionGroup uog = UpdatableOptionGroup.create(optionGroup);
+        updatableOptionGroups.add(uog);
+        return uog;
+    }
+
+    /**
+     * Gets an Options object from this collection.
+     * @return an Options object.
+     */
+    public Options options() {
+        Options result = new Options();
+        updatableOptionGroups.forEach(result::addOptionGroup);
+        return result;
+    }
+
+    /**
+     * Gets ll the UpdatableOptionGroups that the option is in.
+     * @param option the option to searhc for.
+     * @return the stream of UpdatableOptionGroups the option is in.
+     */
+    public Stream<UpdatableOptionGroup> findGroups(final Option option) {
+        return updatableOptionGroups.stream().filter(og -> 
og.getOptions().contains(option));
+    }
+
+    /**
+     * Gets the set of removed Options from the collection.
+     * @return the set of removed options.
+     */
+    public Set<Option> removedOptions() {
+        Set<Option> result = new HashSet<>();
+        updatableOptionGroups.forEach(uog -> 
uog.getDisableOptions().forEach(result::add));
+        return result;
+    }
+
+    /**
+     * Gets the unsupported options
+     * If multiple options from the a group are disabled they will be added to 
the
+     * options in a group together.
+     * @return the Options object containing all the unsupported options.
+     */
+    public Options unsupportedOptions() {
+        Options result = new Options();
+        for (UpdatableOptionGroup uog : updatableOptionGroups) {
+            OptionGroup group = new OptionGroup();
+            uog.getDisableOptions().forEach(group::addOption);
+            result.addOptionGroup(group);
+        }
+        return result;
+    }
+
+    /**
+     * Returns true if the option is in any of the groups.
+     * @param option the option.
+     * @return {@code true} if the option is in any of the groups.
+     */
+    public boolean contains(final Option option) {
+        return updatableOptionGroups.stream().anyMatch(og -> 
og.getOptions().contains(option));
+    }
+}
diff --git a/apache-rat-core/src/main/java/org/apache/rat/ui/package-info.java 
b/apache-rat-core/src/main/java/org/apache/rat/ui/package-info.java
new file mode 100644
index 00000000..765d92c1
--- /dev/null
+++ b/apache-rat-core/src/main/java/org/apache/rat/ui/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ */
+
+/**
+ * Classes that support UI generation and interoperability.
+ */
+package org.apache.rat.ui;
diff --git 
a/apache-rat-core/src/main/java/org/apache/rat/ui/spi/UIProvider.java 
b/apache-rat-core/src/main/java/org/apache/rat/ui/spi/UIProvider.java
new file mode 100644
index 00000000..9870b3cf
--- /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
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.rat.ui.spi;
+
+import org.apache.rat.ui.UI;
+
+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..9d724675
--- /dev/null
+++ b/apache-rat-core/src/main/java/org/apache/rat/ui/spi/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/**
+ * The SPI implementation for the UIs
+ */
+package org.apache.rat.ui.spi;
diff --git 
a/apache-rat-core/src/test/java/org/apache/rat/commandline/ArgTests.java 
b/apache-rat-core/src/test/java/org/apache/rat/commandline/ArgTests.java
index 7f27cb8d..28e856b0 100644
--- a/apache-rat-core/src/test/java/org/apache/rat/commandline/ArgTests.java
+++ b/apache-rat-core/src/test/java/org/apache/rat/commandline/ArgTests.java
@@ -23,6 +23,7 @@ import org.apache.commons.cli.CommandLine;
 import org.apache.commons.cli.DefaultParser;
 import org.apache.commons.cli.Options;
 import org.apache.commons.cli.ParseException;
+import org.apache.rat.CLIOptionCollection;
 import org.apache.rat.DeprecationReporter;
 import org.apache.rat.OptionCollection;
 import org.apache.rat.ReportConfiguration;
@@ -58,7 +59,7 @@ public class ArgTests {
         CommandLine commandLine = createCommandLine(new String[] 
{"--output-file", fileName});
         OutputFileConfig configuration = new OutputFileConfig();
         ArgumentContext ctxt = new ArgumentContext(new File("."), 
configuration, commandLine);
-        Arg.processArgs(ctxt);
+        Arg.processArgs(ctxt, CLIOptionCollection.INSTANCE);
         
assertThat(configuration.actual.getAbsolutePath()).isEqualTo(expected.getCanonicalPath());
     }
 }
diff --git 
a/apache-rat-core/src/test/java/org/apache/rat/ui/ArgumentTrackerTest.java 
b/apache-rat-core/src/test/java/org/apache/rat/ui/ArgumentTrackerTest.java
new file mode 100644
index 00000000..80c5e1cc
--- /dev/null
+++ b/apache-rat-core/src/test/java/org/apache/rat/ui/ArgumentTrackerTest.java
@@ -0,0 +1,122 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.rat.ui;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Predicate;
+import org.apache.commons.cli.Option;
+import org.apache.rat.commandline.Arg;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static 
org.apache.rat.ui.UIOptionCollectionTest.TestingUIOptionCollection;
+import static org.apache.rat.ui.UIOptionCollectionTest.TestingUIOption;
+
+public class ArgumentTrackerTest {
+    private ArgumentTracker underTest;
+    private TestingUIOptionCollection testingUIOptionCollection;
+
+    @BeforeEach
+    public void setUp() {
+        testingUIOptionCollection = new TestingUIOptionCollection();
+        underTest = new ArgumentTracker(testingUIOptionCollection);
+    }
+
+    @Test
+    void extractKey() {
+        
assertThat(ArgumentTracker.extractKey(Option.builder().longOpt("foo").build())).isEqualTo("foo");
+        
assertThat(ArgumentTracker.extractKey(Option.builder("b").build())).isEqualTo("b");
+        
assertThat(ArgumentTracker.extractKey(Option.builder("b").longOpt("foo").build())).isEqualTo("foo");
+        assertThatThrownBy(() -> 
ArgumentTracker.extractKey(Option.builder().build()))
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageContaining("Either opt or longOpt must be 
specified");
+    }
+
+    @Test
+    void args() {
+        // no args to start
+        assertThat(underTest.args()).isEmpty();
+        Option option = findOptionWithArgs(1);
+        String string1 = String.format("--%s foo", 
ArgumentTracker.extractKey(option));
+        TestingUIOption mappedOption = 
testingUIOptionCollection.getMappedOption(option);
+        underTest.setArg(mappedOption, "foo");
+        option = findOptionWithArgs(2);
+        String string2 = String.format("--%s bar baz", 
ArgumentTracker.extractKey(option));
+        mappedOption = testingUIOptionCollection.getMappedOption(option);
+        underTest.addArg(mappedOption, "bar");
+        underTest.addArg(mappedOption, "baz");
+        String join = String.join(" ", underTest.args());
+        assertThat(join).contains(string1);
+        assertThat(join).contains(string2);
+    }
+
+    @Test
+    void setArg() {
+        Option option = findOptionWithArgs(1);
+        TestingUIOption mappedOption = 
testingUIOptionCollection.getMappedOption(option);
+        underTest.setArg(mappedOption, "foo");
+        
assertThat(underTest.getArg(mappedOption.keyValue())).contains(List.of("foo"));
+    }
+
+    private Option findOptionWithArgs(int number) {
+        Predicate<Option> filter;
+        if (number <= 0) {
+            filter = opt -> !opt.hasArg();
+        } else if (number == 1) {
+            filter = opt -> opt.hasArg() && ! opt.hasArgs();
+        } else {
+            filter = opt -> opt.hasArgs();
+        }
+        return 
Arrays.stream(Arg.values()).map(Arg::option).filter(filter).findAny().orElseThrow();
+    }
+
+    @Test
+    void addArg() {
+        Option option = findOptionWithArgs(2);
+        TestingUIOption mappedOption = 
testingUIOptionCollection.getMappedOption(option);
+        underTest.addArg(mappedOption, "foo");
+        
assertThat(underTest.getArg(mappedOption.keyValue())).contains(List.of("foo"));
+        underTest.addArg(mappedOption, "bar");
+        
assertThat(underTest.getArg(mappedOption.keyValue())).contains(List.of("foo", 
"bar"));
+    }
+
+    @Test
+    void setOverridesAddArg() {
+        Option option = findOptionWithArgs(2);
+        TestingUIOption mappedOption = 
testingUIOptionCollection.getMappedOption(option);
+        underTest.addArg(mappedOption, "foo");
+        
assertThat(underTest.getArg(mappedOption.keyValue())).contains(List.of("foo"));
+        underTest.addArg(mappedOption, "bar");
+        
assertThat(underTest.getArg(mappedOption.keyValue())).contains(List.of("foo", 
"bar"));
+        underTest.setArg(mappedOption, "baz");
+        
assertThat(underTest.getArg(mappedOption.keyValue())).contains(List.of("baz"));
+    }
+
+    @Test
+    void invalidAbstractOption() {
+        Option option = Option.builder().longOpt("notAValidOption").build();
+        TestingUIOption invalidOption = new 
TestingUIOption(testingUIOptionCollection, option);
+        underTest.addArg(invalidOption, "foo");
+        assertThat(underTest.getArg(invalidOption.keyValue())).isEmpty();
+    }
+
+}
diff --git 
a/apache-rat-core/src/test/java/org/apache/rat/ui/UIOptionCollectionTest.java 
b/apache-rat-core/src/test/java/org/apache/rat/ui/UIOptionCollectionTest.java
new file mode 100644
index 00000000..9325df75
--- /dev/null
+++ 
b/apache-rat-core/src/test/java/org/apache/rat/ui/UIOptionCollectionTest.java
@@ -0,0 +1,160 @@
+/*
+ * 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.Collection;
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Predicate;
+import java.util.stream.Stream;
+import org.apache.commons.cli.AlreadySelectedException;
+import org.apache.commons.cli.DeprecatedAttributes;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.OptionGroup;
+import org.apache.commons.cli.Options;
+import org.apache.rat.commandline.Arg;
+import org.apache.rat.utils.CasedString;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class UIOptionCollectionTest {
+
+    public static final Option UI_OPTION = 
Option.builder("ui-option1").build();
+    public static final Option DEPRECATED_UI_OPTION = 
Option.builder("ui-option2").deprecated().build();
+
+    public static class TestingUIOptionCollection extends 
UIOptionCollection<TestingUIOption> {
+        public TestingUIOptionCollection() {
+            super(new Builder());
+
+        }
+        private static class Builder extends 
UIOptionCollection.Builder<TestingUIOption, Builder> {
+            Builder() {
+                mapper(TestingUIOption::new)
+                        .uiOption(UI_OPTION)
+                        .uiOption(DEPRECATED_UI_OPTION)
+                        .unsupported(Arg.COUNTER_MAX)
+                        .unsupported(Arg.EXCLUDE.option())
+                        .defaultValue(UI_OPTION, "foo");
+
+            }
+        }
+    }
+
+    static class TestingUIOption extends UIOption<TestingUIOption> {
+
+        TestingUIOption(final UIOptionCollection<TestingUIOption> collection, 
final Option option) {
+            super(collection, option, new 
CasedString(CasedString.StringCase.KEBAB, 
ArgumentTracker.extractKey(option)).toCase(CasedString.StringCase.DOT));
+        }
+
+        @Override
+        protected String cleanupName(Option option) {
+            return new CasedString(CasedString.StringCase.KEBAB, 
ArgumentTracker.extractKey(option)).toCase(CasedString.StringCase.DOT);
+        }
+
+        @Override
+        public String getExample() {
+            return String.format("The example for $s", cleanupName(option));
+        }
+
+        @Override
+        public String getText() {
+            return String.format("Short and long options for ", 
cleanupName(option));
+        }
+    }
+
+    private final TestingUIOptionCollection underTest = new 
TestingUIOptionCollection();
+
+    private Optional<Option> findDeprecatedArgOption() {
+        Collection<Option> options = underTest.getOptions().getOptions();
+        return 
Arg.getOptions().getOptions().stream().filter(Option::isDeprecated)
+                .filter(option -> options.contains(option)).findAny();
+    }
+
+    private Optional<Option> findDeprecatedOption() {
+        Collection<Option> options = underTest.getOptions().getOptions();
+        return 
Arg.getOptions().getOptions().stream().filter(Option::isDeprecated)
+                .filter(option -> !options.contains(option)).findAny();
+    }
+
+    @Test
+    void getMappedOption() {
+        TestingUIOption one = underTest.getMappedOption(UI_OPTION);
+        assertThat(one.option).isEqualTo(UI_OPTION);
+        assertThat(one.name).isEqualTo("ui.option1");
+        assertThat(one.isDeprecated()).isFalse();
+        TestingUIOption two = underTest.getMappedOption(DEPRECATED_UI_OPTION);
+        assertThat(two.option).isEqualTo(DEPRECATED_UI_OPTION);
+        assertThat(two.name).isEqualTo("ui.option2");
+        assertThat(two.isDeprecated()).isTrue();
+
+        assertThat(underTest.getMappedOption(Arg.EXCLUDE.option())).isNull();
+        for (Option option : Arg.COUNTER_MAX.group().getOptions()) {
+            assertThat(underTest.getMappedOption(option)).isNull();
+        }
+
+        TestingUIOption config = 
underTest.getMappedOption(Arg.CONFIGURATION.option());
+        assertThat(config).isNotNull();
+        assertThat(config.option).isEqualTo(Arg.CONFIGURATION.option());
+        assertThat(config.name).isEqualTo("config");
+
+        config = underTest.getMappedOption(Option.builder("foo").build());
+        assertThat(config).isNull();
+    }
+
+    @Test
+    void getSelected() throws AlreadySelectedException {
+        assertThat(underTest.isSelected(Arg.CONFIGURATION)).isFalse();
+        assertThat(underTest.getSelected(Arg.CONFIGURATION)).isEmpty();
+
+        Option option = Arg.CONFIGURATION.option();
+        OptionGroup group = 
underTest.getOptions().getOptionGroup(Arg.CONFIGURATION.option());
+        group.setSelected(option);
+
+        assertThat(underTest.isSelected(Arg.CONFIGURATION)).isTrue();
+        assertThat(underTest.getSelected(Arg.CONFIGURATION)).contains(option);
+    }
+
+    @Test
+    void getUnsupportedOptions() {
+        Options options = underTest.getUnsupportedOptions();
+        Collection<Option> OptionCollection = options.getOptions();
+        List<Option> expected = new ArrayList<Option>();
+        expected.addAll(Arg.COUNTER_MAX.group().getOptions());
+        expected.add(Arg.EXCLUDE.option());
+        
assertThat(options.getOptions()).containsExactlyInAnyOrderElementsOf(expected);
+    }
+
+    @Test
+    void getAdditionalOptions() {
+        
assertThat(underTest.additionalOptions().getOptions()).containsExactlyInAnyOrder(UI_OPTION,
 DEPRECATED_UI_OPTION);
+    }
+
+    @Test
+    void defaultValue() {
+        assertThat(underTest.defaultValue(UI_OPTION)).isEqualTo("foo");
+        assertThat(underTest.defaultValue(DEPRECATED_UI_OPTION)).isNull();
+    }
+}
diff --git 
a/apache-rat-tools/src/main/java/org/apache/rat/documentation/options/CLIOption.java
 
b/apache-rat-tools/src/main/java/org/apache/rat/documentation/options/CLIOption.java
index fc00e5e1..5ac78b82 100644
--- 
a/apache-rat-tools/src/main/java/org/apache/rat/documentation/options/CLIOption.java
+++ 
b/apache-rat-tools/src/main/java/org/apache/rat/documentation/options/CLIOption.java
@@ -7,7 +7,7 @@
  * "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
+ *   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
diff --git a/pom.xml b/pom.xml
index 253189e0..8865a5f0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -85,6 +85,16 @@ 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>
+      <dependency>
+        <groupId>org.reflections</groupId>
+        <artifactId>reflections</artifactId>
+        <version>0.10.2</version>
+      </dependency>
       <!-- used to render the site and make skin updates more transparent -->
       <dependency>
         <groupId>org.apache.maven.skins</groupId>


Reply via email to