Monday, July 10, 2017

Java Command-Line Interfaces (Part 7): JCommander

This is the seventh post in my series that briefly introduces various libraries for processing command-line arguments in Java. This post returns to coverage of an annotation-based library that seems to be one of the better known and more popular of the numerous available libraries for processing command line arguments from Java: JCommander.

JCommander's web page states, "Because life is too short to parse command line parameters" and the Overview introduces JCommander as "a very small Java framework that makes it trivial to parse command line parameters." The code examples and associated screen snapshots of the executing code in this post are based on JCommander 1.72 (June 2017). The full code for the demonstrations shown here is available on GitHub.

JCommander uses annotations to implement the "definition" stage of command-line processing. This is demonstrated in the next code listing snippet.

"Definition" Stage with JCommander

/**
 * Demonstrates use of JCommander for Java-based command-line processing.
 */
public class Main
{
   @Parameter(names={"-v","--verbose"},
              description="Enable verbose logging")
   private boolean verbose;

   @Parameter(names={"-f","--file"},
              description="Path and name of file to use",
              required=true)
   private String file;

   @Parameter(names={"-h", "--help"},
              description="Help/Usage",
              help=true)
   private boolean help;

   // . . .

final JCommander commander
   = JCommander.newBuilder()
              .programName("JCommander Demonstration")
             .addObject(this)
             .build();

The just-shown code listing demonstrates use of JCommander's @Parameter annotation to define the command-line options via annotation of class fields. The examples demonstrate specification of names to indicate multiple option flags to be associated with a single option, description to provide a description of each option, required=true to enforce presence of a command-line argument, and help=true to indicate a "help" or "usage" command-line argument (instructs JCommander to not throw exception if required arguments are not also provided).

With the class attributes annotated with @Parameter annotations, an instance of the class with annotated fields can be used to create an instance of the JCommander class. In the code example above, I took advantage of the JCommander.Builder for the greater fluency and other advantages associated with use of builders. In particular, the instance with annotated class fields is added via the addObject(Object) method.

The "parsing" stage of command-line processing with JCommander is accomplished via a single line invocation of the parse(String...) method on the instance of JCommander that was just instantiated. This is demonstrated in the next code listing.

"Parsing" Stage with JCommander

commander.parse(arguments);

The "interrogation" stage of command-line processing with JCommander involves simply accessing the annotated fields of the instance passed to the JCommander class instantiation. This is demonstrated in the next code listing.

"Interrogation" Stage with JCommander

if (help)
{
   commander.usage();
}
else
{
   out.println(
      "The file name provided is '" + file + "' and verbosity is set to " + verbose);
}

The last code listing demonstrates the ability to determine if the boolean attribute with name help was set by the specification of --help or -h. Because it's a simple boolean, it can be used in the conditional and, if true, the help/usage information is presented. In the case when the "help" flag was not set, values associated with the other command line options ("verbose"/-v/--verbose and "file"/-f/--file) are accessed.

The most recent code listing also demonstrates writing the usage information to standard output via an invocation of the method usage() on the instance of the JCommander class. It's worth noting that ParameterException also has a usage() method.

The next series of screen snapshots demonstrate using JCommander with a simple application that includes the above code snippets. The first image shows running the JCommander-based application without any arguments and shows the ParameterException that is displayed in that case because the required --file/-f option was not specified.

The next screen snapshot demonstrates "normal" execution when the expected command line arguments are provided.

The next screen snapshot demonstrates use of the "help" option. Because this was annotated with help=true, the absence of the required "file" command-line argument does not lead to an exception and the automatically generated help/usage information is written to standard output.

JCommander provides a feature that I really like for developing with and learning JCommander. One can specify increased verbosity of the JCommander parsing by invoking the method verbose(int) on JCommandBuilder.

Increasing JCommander's Verbosity

final JCommander commander
   = JCommander.newBuilder()
               .programName("JCommander Demonstration")
               .addObject(this)
               .verbose(1)
               .build();

With the increased verbosity, greater insight into what JCommander is doing related to command-line processing can be discovered and this is demonstrated in the following two screen snapshots.

Here are some additional characteristics of JCommander to consider when selecting a framework or library to help with command-line parsing in Java.

Additional References

No comments: