Processing command line arguments in Java: Case closed

Facilitate command line argument processing for Java tools with a simple helper class

Page 2 of 2

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

ValueDescription Default
ignoreUnmatchedSpecifies whether command line options for which no corresponding OptionData instance was created are acceptable. Applications can choose to ignore such unmatched options or react with an error.false
requireDataLastSpecifies whether the actual data arguments need to be the last arguments on the command line or whether they can be interspersed within the options.true

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:

  1. Some trivial cases are caught. No options have been defined for the set to check, or no command line arguments have been provided.
  2. 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 the OptionData instance that matched the option.
  3. Any unmatched options are identified and stored in a list. In addition, the data arguments are identified and stored in another list.
  4. The multiplicity is checked for all the options based on the number of matches found for each one.
  5. The range of the data arguments is checked against the defined boundaries.
  6. If desired, data arguments can be checked to verify whether they are last on the command line.
  7. 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.

Dr. Matthias Laux is a senior engineer for Sun Microsystems working in the Global SAP-Sun Competence Center in Walldorf, Germany. His main interests are Java and J2EE technology, architecture, and programming, as well as Web services and XML technology in general, databases, and performance and benchmarking. Although he also has a background in aerospace engineering and HPC/parallel programming, today his languages of choice are Java and Perl. He is a certified Solaris Administrator, Java Programmer, and Java Enterprise Architect.

Learn more about this topic

| 1 2 Page 2