Monday, July 17, 2017

Java Command-Line Interfaces (Part 8): Argparse4j

Argparse4j is a "Java command-line argument parser library" that its main page describes as "a command line argument parser library for Java based on Python's argparse module." In this post, I will look briefly at using Argparse4j 0.7.0 to process command-line arguments similar to those parsed in the seven earlier posts in this series on command-line processing in Java.

The arguments "definition" stage of command-line processing with Argparse4j can be accomplished via the ArgumentParser interface and its addArgument(String...) method. The return type of the addArgument(String...) method is an instance of the Argument interface. Implementations of that interface (usually ArgumentImpl) provide methods for setting the characteristics of each argument. Because each of these methods returns an instance of Argument, these calls can be chained together in a highly fluent manner. This is demonstrated in the next screen snapshot.

"Definition" Stage with Argparse4j

final ArgumentParser argumentParser =
   ArgumentParsers.newArgumentParser("Main", true);
argumentParser.addArgument("-f", "--file")
              .dest("file")
              .required(true)
              .help("Path and name of file");
argumentParser.addArgument("-v", "--verbose")
              .dest("verbose")
              .type(Boolean.class)
              .nargs("?")
              .setConst(true)
              .help("Enable verbose output.");

In the above code listing, an instance of ArgumentParser is instantiated with a static initialization method that expects a String argument representing the script or program name that will be included in usage/help output. The second argument to the ArgumentParsers's newArgumentParse(String, boolean) method specifies that "help" options -h and --help will automatically be supported.

The first argument defined in the above code listing allows for a file path and name to be specified on the command line. The strings "-f" and "--file" are passed to the addArgument(String...) method, meaning that either -f or --file can be used on the command line to specify the file path and name. Three additional methods [dest(String), required(boolean), and help(String)] are called on the instances of Argument created as part of the specification of this first argument. These three methods respectively specify a name by which the argument can be referenced in the code, that the argument must be present on the command line, and the string to be displayed when help is requested for that argument.

The second argument defined in the above code listing passes the strings "-v" and "--verbose" to the addArgument(String...) method to allow this argument to be represented on the command line with either the short or long option name. Like the first argument, this one has the name it will be referenced by in the code set by the dest(String) method and has its string for "help" output specified with the help(String) method. This second argument is not required and so the required(boolean) method is unnecessary here.

The second argument's definition has a few additional methods on Argument called. I used type(Class<T>) to demonstrate the ability to explicitly specify the Java data type expected for the argument. I also needed to specify the combination of the nargs(String) and setConst(Object) methods to specify that the verbosity argument does not need a value provided with the flag. This allows me to specify -v or --verbose with no "true" or "false" after those options expected to be explicitly stated.

The "parsing" stage of command-line processing is supported in argparse4j with a call to the ArgumentParser's parseArgs(String[]) method. The next code listing demonstrates this.

"Parsing" Command Line Arguments with Argparse4j

final Namespace response = argumentParser.parseArgs(arguments);

Parsing requires only a single statement and returns an instance of Namespace.

The "interrogation" stage of command line processing with Argparse4j involves accessing the parsed command-line arguments from the Map that the Namespace instance wraps. The keys of this map are the strings specified with the dest(String) method and the values of the map are the values associated with those argument names. Interrogating these values is demonstrated in the next code listing.

"Interrogating" Command Line Arguments with Argparse4j

final String filePathAndName = response.getString("file");
final Boolean verbositySet = response.getBoolean("verbose");

out.println(
     "Path/name of file is '" + filePathAndName
   + "' and verbosity is "
   + (Boolean.TRUE.equals(verbositySet) ? "SET" : "NOT SET")
   + ".");

In the just listed code, the keys of "file" and "verbose" were used because those same strings were provided with the dest(String) method when defining the expected arguments.

The full source code from which the code snippets above were extracted can be seen on GitHub.

The next screen snapshot demonstrates running the simple Java application without any arguments and the message that is displayed regarding the missing required "file" argument.

The all uppercase "FILE" shown in the above screen snapshot comes from the string that was specified in the dest(String) method when defining the expected argument. In other words, that dest(String) specification set both the string by which the argument mapping is keyed internally and the target string displayed in the help/usage.

The next screen snapshot demonstrates several variations of typical uses of the "file" and "verbose" options.

The final screen snapshot demonstrates that help information that is provided for -h or --help options because the original instance of ArgumentParser was created with the "addHelp" argument set to true.

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

  • Argparse4j is open source and licensed with the MIT License.
  • The argparse4j-0.7.0.jar (December 2015) is approximately 89 KB in size and has no additional third-party library dependencies.
  • Argparse4j does not make use of annotations.
  • The online documentation includes a Clojure example.
  • I suspect that Java developers who write their scripts in Python (particularly if they use argparse) would experience advantages when using argparse4j in their Java applications that need to parse command-line arguments.
    • (I find Apache Commons CLI to be intuitive when processing command-line arguments in Java because I much more frequently parse command line arguments in Groovy than in Java and Groovy supplies built-in Apache Commons CLI support)
  • Argparse4j inspired the development of argparse4s for Scala.

Argparse4j is just one of many Java-based command line processing libraries. The characteristic of Argparse4j that most sets it apart from the numerous other options is its argparse heritage. Given that, I believe that the Java developers most likely to select Argparse4j for their Java command line processing needs would be those developers who frequently parse command line arguments in Python-based scripts and tools using argparse or who prefer command parsing semantics of Python and argparse.

Additional References

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

Saturday, July 8, 2017

Java Command-Line Interfaces (Part 6): JOpt Simple

The main web page for JOpt Simple calls this Java-based library "a Java library for parsing command line options, such as those you might pass to an invocation of javac," that "attempts to honor the command line option syntaxes of POSIX getopt() and GNU getopt_long()." This is the sixth post of my series of command-line arguments processing in Java and its focus is on JOpt Simple.

Most of the libraries I've reviewed in this series of command-line processing in Java take use annotations in some way. JOpt Simple, like Apache Commons CLI, does not use annotations. JOpt Simple supports "fluent interfaces" instead. This original post's examples (code listings) and output (screen snapshots) are based on compiling and running against JOpt Simple 4.9, but they have worked similarly for me (and without code changes) when compiling them and running them with JOpt Simple 5.0.3.

The next code listing demonstrates the "definition" stage of command-line processing with JOpt Simple and this example is intentionally similar to that used in the previous posts on command-line processing in Java.

Defining Command-line Options in JOpt Simple

final OptionParser optionParser = new OptionParser();
final String[] fileOptions = {"f", "file"};
optionParser.acceptsAll(Arrays.asList(fileOptions), "Path and name of file.").withRequiredArg().required();
final String[] verboseOptions = {"v", "verbose"};
optionParser.acceptsAll(Arrays.asList(verboseOptions), "Verbose logging.");
final String[] helpOptions = {"h", "help"};
optionParser.acceptsAll(Arrays.asList(helpOptions), "Display help/usage information").forHelp();

This code listing demonstrates use of the "fluent API" approach to defining command-line options. An OptionParser is instantiated and then one of its overloaded acceptsAll methods is called for each potential command-line option. The use of acceptsAll allows multiple flag/option names to be associated with a single option. This support for option synonyms allows for use of "-f" and "--file" for the same option.

The code above demonstrates that a command-line option can be specified as required with the .required() method invocation. In this case, a "file" is required. If an argument is expected to be placed on the command line in association with the option/flag, the withRequiredArg() method can be used. The "help" option in the above code listing takes advantage of the forHelp() method to tell JOpt Simple to not throw an exception if a required option is not on the command-line if the option associated with the forHelp() is on the command-line. This works, in my example, to ensure that the operator could run the application with -h or --help and without any other required option and avoid an exception being thrown.

The JOpt Simple Examples of Usage page provides significant details about the many different possibilities available when defining command-line options and uses JUnit-based assertions to demonstrate how these different tactics for defining command-line options configure differently what is parsed. My code listing shown above only shows a minor subset of what's available. Note that the Javadoc comments for the OptionParser class also contain significant details.

The code above can be even more concise if one statically imports the Arrays.asList and passes the potential command-line options' names as strings directly to that asList(String...) method instead of using the approach I used of creating an array of Strings first and then converting them to a list. I used this approach in this introductory post to make it very clear what was happening, but it's likely that the version of the code associated with this post on GitHub will be changed to use the static import approach.

The "parsing" stage of command-line processing with JOpt Simple is, well, simple:

final OptionSet options = optionParser.parse(arguments);

"Parsing" with JOpt Simple entails invocation of the method OptionParser.parse(String ...)

The "interrogation" stage of command-line processing with JOpt Simple is also simple and is demonstrated in the next code listing.

out.println("Will write to file " + options.valueOf("file") + " and verbosity is set to " + options.has("verbose"));

The single line of code demonstrates that interrogation consists of calling convenient methods on the instance of OptionSet returned by the parsing call. In this case, two demonstrated methods called on OptionSet are OptionSet.valueOf(String) and OptionSet.has(String).

JOpt Simple also supports automatic generation of a usage/help statement. The next code listing demonstrates doing this.

optionParser.printHelpOn(out);

The single line of code just shown writes the usage/help information generated by the instance of OptionParser to the output stream provided to it via its printHelpOn(OutputStream) method.

With the most significant code needed for applying JOpt Simple shown, it's time to see how the simple application that uses this code behaves. The following screen snapshots demonstrate the code in action. The first screen snapshot demonstrates the MissingRequiredOptionsException printed when the required "file" command-line option is not provided.

The next screen snapshot demonstrates specifying the "file" and "verbose" options on the command lines.

The automatic usage/help message provided by JOpt Simple is demonstrated in the next screen snapshot.

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

  • JOpt Simple is open source and is licensed under the MIT License.
  • As of this writing, the latest versions of JOpt Simple are 5.0.3 and 6.0 Alpha 1; JOpt Simple 4.9 (latest version currently listed in the JOpt Simple change log and version currently shown in Maven dependency example) was used in this post.
  • The jopt-simple-4.9.jar is approximately 65 KB in size and has no compile-time dependencies on any third-party libraries.
  • JOpt Simple has been or is used by several influential libraries and frameworks. These include Spring Framework (optional compile dependency) and JMH (compile dependency).
    • The main page for the JOpt Simple web page quotes Mark Reinhold, "I thought you might be interested to know that we're using your jopt-simple library in the open-source Java Development Kit. Thanks for writing such a nice little library! It's far cleaner than any of the other alternatives out there."
  • JOpt Simple has been available for several years, but appears to still be maintained (latest on Maven Central is December 2016).
  • JOpt Simple does not use annotations and instead relies on fluent API calls.
  • JOpt Simple supports relationships between command-line options such as required dependent options.

It is typically a positive sign of a library's usefulness when other well-received and useful tools and libraries make use of that library. JOpt Simple's selection as the command-line processing library of choice for some such tools and libraries definitely speaks well of JOpt Simple. JOpt Simple provides a useful and powerful alternative to Apache Commons CLI for those who prefer Java command-line processing that does not use annotations. JOpt Simple provides significant more capability than that shown in this post and this capability is best discovered by reading the unit test-based "tour through JOpt Simple's features."

Additional References