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
-d oracle -j jdbc:oracle:thin:@//host:port/databa
-dialect oracle -jdbcUrl jdbc:oracle:thin:@//host:port/databa
Use case 2:
-d oracle -h host -p 1521 -s database -U user -P pwd
-dialect oracle -host host -port 1521 -sid database -user user -password pwd
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:
-d oracle
: Missing required options: [-j ...], [-h ..., -p ..., -s ..., -U ..., -P ...]-d oracle -jdbcUrl xxx
: Missing required option: [-h ..., -p ..., -s ..., -U ..., -P ...]-d oracle -h yyy
: Missing required option: [-j ...]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.
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 arnold.somogyi@gmail.com.",
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 arnold.somogyi@gmail.com.
Documentation, source code: https://github.com/zappee/sql-runner.git
This look is much better than the Apache CLI generated help.