zappee
zappee

Reputation: 22668

Two exclusive OptionGroup with Apache Commons CLI

I am building a command-line Java application and I have a problem with parsing the command line parameters with Apache Commons CLI.

I am trying to cover my scenario where I need to have two exclusive command-line param groups with long (--abc) and short (-a) arguments as well.

Use case 1

Use case 2:

So I created two OptionGroup with the proper Option items:

OptionGroup jdbcUrlGroup = new OptionGroup();
jdbcUrlGroup.setRequired(true);
jdbcUrlGroup.addOption(jdbcUrl);

second group:

OptionGroup customConfigurationGroup = new OptionGroup();
customConfigurationGroup.setRequired(true);
customConfigurationGroup.addOption(host);
customConfigurationGroup.addOption(port);
customConfigurationGroup.addOption(sid);
customConfigurationGroup.addOption(user);
customConfigurationGroup.addOption(password);

Then I build the Options object this way:

Options options = new Options();
options.addOptionGroup(jdbcUrlGroup);
options.addOptionGroup(customConfigurationGroup);
options.addOption(dialect);

But this does not work because it expects to define both groups.

This is how the dialect Option is defined:

Option dialect = Option
        .builder("d")
        .longOpt("dialect")
        .required(false)
        .hasArg()
        .argName("DIALECT")
        .desc("supported SQL dialects: oracle. Default value: oracle")
        .build();

The other mandatory Option definitions look similar except this one property:

.required(true)

Result:

But what I want is the following: if the JDBC URL is provided then the host, port, etc, params are not needed or the opposite.

Upvotes: 2

Views: 1905

Answers (2)

the_new_mr
the_new_mr

Reputation: 3733

Option groups are for defining groups of arguments where one of them must be specified.

Option optionA = new Option("a", "aaa", true, "Description for A");
Option optionB = new Option("b", "bbb", true, "Description for B");
Option optionC = new Option("c", "ccc", true, "Description for C");

OptionGroup optionsGroup = new OptionGroup();
optionsGroup.addOption(optionA);
optionsGroup.addOption(optionB);

options = new Options();
options.addOptionGroup(optionsGroup);
    
options.addOption(optionC);

In the example above, one must specify one of either option A or option B but NOT both.

Upvotes: 1

zappee
zappee

Reputation: 22668

I think that it is time to forget Apache Commons CLI and mark it as a deprecated library. Okay, if you have only a few command-line arguments then you can use it, otherwise better not to use. Fact that this Apache project was updated recently (17 February 2019), but still many features are missing from it and a little bit painful to work with Apache Commons CLI library.

The picocli project looks like a better candidate for parsing command line parameters. It is a quite intuitive library, easy to use, and has a nice and comprehensive documentation as well. I think that a middle rated tool with perfect documentation is better than a shiny project without any documentation.

Anyway picocli is a very nice library with perfect documentation, so I give double plus-plus to it :)

This is how I covered my use cases with picocli:

import picocli.CommandLine;
import picocli.CommandLine.ArgGroup;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import picocli.CommandLine.Parameters;

@Command(name = "SqlRunner",
        sortOptions = false,
        usageHelpWidth = 100,
        description = "SQL command line tool. It executes the given SQL and show the result on the standard output.\n",
        parameterListHeading = "General options:\n",
        footerHeading = "\nPlease report issues at [email protected].",
        footer = "\nDocumentation, source code: https://github.com/zappee/sql-runner.git")
public class SqlRunner implements Runnable {

    /**
     * Definition of the general command line options.
     */
    @Option(names = {"-?", "--help"}, usageHelp = true, description = "Display this help and exit.")
    private boolean help;

    @Option(names = {"-d", "--dialect"}, defaultValue = "oracle", showDefaultValue = CommandLine.Help.Visibility.ALWAYS, description = "Supported SQL dialects: oracle.")
    private static String dialect;

    @ArgGroup(exclusive = true, multiplicity = "1", heading = "\nProvide a JDBC URL:\n")
    MainArgGroup mainArgGroup;

    /**
     * Two exclusive parameter groups:
     *    (1) JDBC URL parameter
     *    (2) Custom connection parameters
     */
    static class MainArgGroup {
        /**
         * JDBC URL option (only one parameter).
         */
        @Option(names = {"-j", "--jdbcUrl"}, arity = "1", description = "JDBC URL, example: jdbc:oracle:<drivertype>:@//<host>:<port>/<database>.")
        private static String jdbcUrl;

        /**
         * Custom connection parameter group.
         */
        @ArgGroup(exclusive = false, multiplicity = "1", heading = "\nCustom configuration:\n")
        CustomConfigurationGroup customConfigurationGroup;
    }

    /**
     * Definition of the SQL which will be executed.
     */
    @Parameters(index = "0", arity = "1", description = "SQL to be executed. Example: 'select 1 from dual'")
    String sql;

    /**
     * Custom connection parameters.
     */
    static class CustomConfigurationGroup {
        @Option(names = {"-h", "--host"}, required = true, description = "Name of the database server.")
        private static String host;

        @Option(names = {"-p", "--port"}, required = true, description = "Number of the port where the server listens for requests.")
        private static String port;

        @Option(names = {"-s", "--sid"}, required = true, description = "Name of the particular database on the server. Also known as the SID in Oracle terminology.")
        private static String sid;

        @Option(names = {"-U", "--user"}, required = true, description = "Name for the login.")
        private static String user;

        @Option(names = {"-P", "--password"}, required = true, description = "Password for the connecting user.")
        private static String password;
    }

    /**
     * The entry point of the executable JAR.
     *
     * @param args command line parameters
     */
    public static void main(String[] args) {
        CommandLine cmd = new CommandLine(new SqlRunner());
        int exitCode = cmd.execute(args);
        System.exit(exitCode);
    }

    /**
     * It is used to create a thread.
     */
    @Override
    public void run() {
        int exitCode = 0; //executeMyStaff();
        System.exit(exitCode);
    }
}

And this is how the generated help looks like:

$ java -jar target/sql-runner-1.0-shaded.jar --help
Usage: SqlRunner [-?] [-d=<dialect>] (-j=<jdbcUrl> | (-h=<host> -p=<port> -s=<sid> -U=<user>
                 -P=<password>)) <sql>
SQL command line tool. It executes the given SQL and show the result on the standard output.

General settings:
      <sql>                 SQL to be executed. Example: 'select 1 from dual'
  -?, --help                Display this help and exit.
  -d, --dialect=<dialect>   Supported SQL dialects: oracle.
                              Default: oracle

Custom configuration:
  -h, --host=<host>         Name of the database server.
  -p, --port=<port>         Number of the port where the server listens for requests.
  -s, --sid=<sid>           Name of the particular database on the server. Also known as the SID in
                              Oracle terminology.
  -U, --user=<user>         Name for the login.
  -P, --password=<password> Password for the connecting user.

Provide a JDBC URL:
  -j, --jdbcUrl=<jdbcUrl>   JDBC URL, example: jdbc:oracle:<drivertype>:@//<host>:<port>/<database>.

Please report issues at [email protected].
Documentation, source code: https://github.com/zappee/sql-runner.git

This look is much better than the Apache CLI generated help.

Upvotes: 4

Related Questions