In the constructors above, where only one integer argument is present (data
), this value is used to set both defMinData
and defMaxData
to the same value. This means that the number of acceptable data arguments is fixed to exactly that number, and there is no acceptable range for that number.
Adding an option set is possible through these methods:
OptionSet addSet(String setName) OptionSet addSet(String setName, int data) OptionSet addSet(String setName, int minData, int maxData)
Again, the newly created set is returned to allow for subsequent invocation chaining of the addOption()
methods.
Option sets can be accessed through these methods:
OptionSet getSet() OptionSet getSet(String setName)
Note one important concept here: one default OptionSet
instance does not need to be explicitly created. This instance is available through the getSet()
method and is useful for simpler applications that require only one set. In this case, setting up the Options
instance could look like this:
Options options = new Options(args); options.getSet().addOption("a").addOption("b");
Under the hood, this default set is of course based on a standard OptionSet
instance with a name given by:
public final static String DEFAULT_SET = "DEFAULT_OPTION_SET";
Some convenience methods have been added to the Options
class to simplify the creation of the same option for all known sets at the same time:
void addOptionAllSets(String key) void addOptionAllSets(String key, Multiplicity multiplicity) void addOptionAllSets(String key, Separator separator) void addOptionAllSets(String key, Separator separator, Multiplicity multiplicity) void addOptionAllSets(String key, boolean details, Separator separator) void addOptionAllSets(String key, boolean details, Separator separator, Multiplicity multiplicity)
These options correspond directly to the addOption()
methods described earlier for the OptionSet
class. One case where I have found using these methods useful was an optional verbosity option (-v
), which had to be available for all sets of an application:
options.addOptionAllSets("v", Multiplicity.ZERO_OR_ONE);
Perform the checks
Performing the actual checks of the command line arguments against the specified options for all sets is obviously a core component of the Options
class. The following check methods are available:
boolean check(String setName) boolean check(String setName, boolean ignoreUnmatched, boolean requireDataLast) boolean check() boolean check(boolean ignoreUnmatched, boolean requireDataLast)
The first two methods check the specified option set, whereas the latter two check the default option set. The two Booleans have the following meanings.
Table 2: Arguments to the check() methods and their meanings
|
Again, the introduction of these methods is based on the observations made early in the project about the requirements for a class such as Options
.
Two more convenience methods are provided:
OptionSet getMatchingSet() OptionSet getMatchingSet(boolean ignoreUnmatched, boolean requireDataLast)
These methods run the checks for each known OptionSet
and return the first one, which is successfully checked.
The last public method in the list is:
String getCheckErrors()
During the checks, the check()
methods write all observed problems into a StringBuffer
, the value of which can then be accessed through the getCheckErrors()
method. This method proves useful for debugging purposes, but applications can also use it to tell its users about the problem with the provided input.
The actual check process consists of the following steps:
- Some trivial cases are caught. No options have been defined for the set to check, or no command line arguments have been provided.
- All command line arguments are processed in a loop. Using
java.util.regex
's pattern-matching capabilities, these arguments are compared with the known options, and, if a match is found, the value and the detail information are retrieved for options expecting such information. All this information is stored in theOptionData
instance that matched the option. - Any unmatched options are identified and stored in a list. In addition, the data arguments are identified and stored in another list.
- The multiplicity is checked for all the options based on the number of matches found for each one.
- The range of the data arguments is checked against the defined boundaries.
- If desired, data arguments can be checked to verify whether they are last on the command line.
- If desired, the presence of unmatched options are checked.
If all checks are successful, true
returns. If, at any of the stages above, a check failure results, false
returns immediately, and a comment explaining the problem is written to the error log (which is accessible through the getCheckErrors()
method).
Examples
The following examples are designed to demonstrate the use of the Options
class, ranging from a simple case of an application requiring just one option set to a complex case, with many different option sets and multiplicities for the options.
Example 1: A simple case
The first example is a simple case that demonstrates how quickly a tool can leverage the capabilities of the Options
class.
The command line syntax for this example looks like this:
java Example1 [-a] [-log=<logfile>] <inpfile> <outfile>
I used the standard syntax here, which denotes optional data (like [a]
) with square brackets.
The code to handle these options can look like this:
Options opt = new Options(args, 2); opt.getSet().addOption("a", Multiplicity.ZERO_OR_ONE); opt.getSet().addOption("log", Separator.EQUALS, Multiplicity.ZERO_OR_ONE); if (!opt.check()) { // Print usage hints System.exit(1); } // Normal processing if (opt.getSet().isSet("a")) { // React to option -a } if (opt.getSet().isSet("log")) { // React to option -log String logfile = opt.getSet().getOption("log").getResultValue(0); } ... String inpfile = opt.getSet().getData().get(0); String outfile = opt.getSet().getData().get(1); ...
The Options
instance is created, specifying that exactly two data arguments are required. After that, the two options are added with the multiplicity of ZERO_OR_ONE
, which corresponds to the angle brackets. The checks are run by invoking check()
, and if the checks are not successful, a usage description can be written.
Using Options.getSet().isSet()
, you can easily check whether the options in square brackets have been specified, and the program can react accordingly. If -log
was specified, that option's value is available from the OptionData
instance's getResultValue()
method.
The data arguments can be accessed using the getData()
method on the default option set.
Actually, the code above can be further simplified by specifying a different default multiplicity directly in Options
's constructor and by using invocation chaining for the options definition:
Options opt = new Options(args, Multiplicity.ZERO_OR_ONE, 2); opt.getSet().addOption("a").addOption("log", Separator.EQUALS);
Example 2: A more complex case
This more complex example demonstrates using several OptionSet
instances, different option multiplicities, and option details.
The command line syntax looks like this:
java Example2 -c [-v] [-D<detail>=<value> [...]] data1 data2 java Example2 -a [-v] [-check] data1 [data2] [data3] java Example2 -d [-v] -k <kval> -t <tval> data1 data2 [data3] [data4]
So this tool has three main modes of operation, which are chosen by a (mandatory) option (either -c
, -a
, or -d
).
The code could look like this:
Options opt = new Options(args, 2); opt.addSet("cset").addOption("c").addOption("D", true, Separator.EQUALS, Multiplicity.ZERO_OR_MORE); opt.addSet("aset", 1, 3).addOption("a").addOption("check", Multiplicity.ZERO_OR_ONE); opt.addSet("dset", 2, 4).addOption("d").addOption("k", Separator.BLANK).addOption("t", Separator.BLANK); opt.addOptionAllSets("v", Multiplicity.ZERO_OR_ONE); OptionSet set = opt.getMatchingSet(); if (set == null) { // Print usage hints System.exit(1); }
Note how simple it is to capture this complex set of options!
The evaluation section could look like this (where System.out.println()
calls have been inserted for clarity):
// This can be used for ALL sets since we added it using addOptionAllSets() if (set.isSet("v")) { System.out.println("v is set"); } // Evaluate the different option sets if (set.getSetName().equals("cset")) { for (String d : set.getData()) System.out.println(d); OptionData d = set.getOption("D"); for (int i = 0; i < d.getResultCount(); i++) { System.out.println("D detail " + i + " : " + d.getResultDetail(i)); System.out.println("D value " + i + " : " + d.getResultValue(i)); } } else if (set.getSetName().equals("aset")) { for (String d : set.getData()) System.out.println(d); if (set.isSet("check")) System.out.println("check is set"); } else { // We _know_ it has to be the third set now for (String d : set.getData()) System.out.println(d); System.out.println(set.getOption("k").getResultValue(0)); System.out.println(set.getOption("t").getResultValue(0)); }
Even this relatively complex example can be handled easily with the Options
class, and one particular benefit becomes clear here: no check code is required at the application level, since the Options
class handles it. All relevant result data is accessible through a simple and convenient set of methods.
Example 3: A really complex case
For the third example, I decided to retrofit the Options
class into the URLManager package. This package contains the three Java command line tools URLManage, URLCheck, and URLPublish, each of which takes a large set of options. The most complex case is URLManage, whose usage description looks like this:
Create a new entry in the DB: java URLManage [-v] -c <dbprop> <url> <desc> <context> java URLManage [-v] -bc <dbprop> <urlfile> Update the description of an entry in the DB: java URLManage [-v] -u <dbprop> <url> <desc> Delete an entry from the DB: java URLManage [-v] -d <dbprop> <url> Select URL entries from the DB: java URLManage [-v] -s <dbprop> <pattern> java URLManage [-v] -sa <dbprop> Select contexts from the DB: java URLManage [-v] -con <dbprop> Init the tables in the DB: java URLManage [-v] -init <dbprop> Delete the tables from the DB: java URLManage [-v] -drop <dbprop> Add the URL to a specific context: java URLManage [-v] -ac <dbprop> <url> <context> java URLManage [-v] -bac <dbprop> <confile> Remove the URL from a specific context: java URLManage [-v] -rc <dbprop> <url> <context>
It turns out that the Options
class can be used to handle these option sets with limited coding effort; the code resembles Example 2:
... ml.options.Options options = new ml.options.Options(args, 1); options.addSet("create", 4).addOption("c"); options.addSet("createBatch", 2).addOption("bc"); options.addSet("update", 3).addOption("u"); options.addSet("delete", 2).addOption("d"); options.addSet("select", 2).addOption("s"); options.addSet("addURL", 3).addOption("ac"); options.addSet("addURLBatch", 2).addOption("bac"); options.addSet("removeURL", 3).addOption("rc"); options.addSet("selectAll").addOption("sa"); options.addSet("contexts").addOption("con"); options.addSet("initTables").addOption("init"); options.addSet("deleteTables").addOption("drop"); options.addOptionAllSets("v", ml.options.Options.Multiplicity.ZERO_OR_ONE); ml.options.OptionSet optionSet = options.getMatchingSet(); ...
Conclusion
This article describes a Java class that allows for the convenient processing of command line options for Java programs. The structure is flexible enough to handle even complex situations, while at the same time offering an API that allows for the definition of acceptable command line syntax with limited coding effort. The Options
class provides all the checking algorithms required to ensure that acceptable sets of command line arguments are identified, which relieves application programmers of having to hand-code the same algorithms time and again. This class can add a lot of value to every Java application requiring command line options. If some capability is missing, I'd of course appreciate feedback.
This story, "Processing command line arguments in Java: Case closed" was originally published by JavaWorld.