Tuesday, June 20, 2017

Java Command-Line Interfaces (Part 1): Apache Commons CLI

Although I typically use Groovy to write JVM-hosted scripts to be run from the command-line, there are times when I need to parse command-line parameters in Java applications and there is a plethora of libraries available for Java developers to use to parse command-line parameters. In this post, I look at one of the best known of these Java command line parsing libraries: Apache Commons CLI.

I have blogged on Apache Commons CLI before, but that post is over eight years old and describes Apache Commons CLI 1.1. Two classes that I demonstrated in that post, GnuParser and PosixParser, have since been deprecated. The examples in this current post are based on Apache Commons CLI 1.4 and use the newer DefaultParser that was introduced with CLI 1.3 to replace GnuParser and PosixParser.

The Apache Commons CLI documentation's "Introduction" explains how Commons CLI accomplishes the "three stages [of] command line processing" ("definition", "parsing", and "interrogation"). These three stages map in Commons CLI to classes Option and Options ("definition"), to interface CommandLineParser ("parsing"), and to class CommandLine ("interrogation").

For the examples built here with Apache Commons CLI, the expected command-line arguments are relatively simple. One argument is optional and, when specified, indicates that verbose output is enabled. The other argument is required and is used to specify a file to be processed by the imaginary application. The optional argument does not have a value associated with the flag and is expressed as -v or --verbose. The required argument should be followed by a value which is the path and name of the file. This flag is either -f or --file. The next code listing demonstrates using Commons CLI's Option.Builder (introduced with Commons CLI 1.3) to build up the expected options as part of the "definition" stage.

Example of Using Apache Commons CLI Option.Builder for "Definition Stage

/**
 * "Definition" stage of command-line parsing with Apache Commons CLI.
 * @return Definition of command-line options.
 */
private static Options generateOptions()
{
   final Option verboseOption = Option.builder("v")
      .required(false)
      .hasArg(false)
      .longOpt(VERBOSE_OPTION)
      .desc("Print status with verbosity.")
      .build();
   final Option fileOption = Option.builder("f")
      .required()
      .longOpt(FILE_OPTION)
      .hasArg()
      .desc("File to be processed.")
      .build();
   final Options options = new Options();
   options.addOption(verboseOption);
   options.addOption(fileOption);
   return options;
}

The "Builder" pattern implemented for Apache Commons CLI as shown in the above example features the benefits of the builder pattern such as creating an Option in a fully completed state in one statement and use of highly readable builder methods to set that instance's various fields. My older post on Apache Commons CLI demonstrates use of the alternate traditional constructor approach to instantiating Option instances.

With the command-line options defined, it's time to move to the "parsing" stage and the next code listing demonstrates how to parse with Apache Commons CLI by simply invoking the method CommandLinePaser.parse().

Parsing Command-line Options with Commons CLI

/**
 * "Parsing" stage of command-line processing demonstrated with
 * Apache Commons CLI.
 *
 * @param options Options from "definition" stage.
 * @param commandLineArguments Command-line arguments provided to application.
 * @return Instance of CommandLine as parsed from the provided Options and
 *    command line arguments; may be {@code null} if there is an exception
 *    encountered while attempting to parse the command line options.
 */
private static CommandLine generateCommandLine(
   final Options options, final String[] commandLineArguments)
{
   final CommandLineParser cmdLineParser = new DefaultParser();
   CommandLine commandLine = null;
   try
   {
      commandLine = cmdLineParser.parse(options, commandLineArguments);
   }
   catch (ParseException parseException)
   {
      out.println(
           "ERROR: Unable to parse command-line arguments "
         + Arrays.toString(commandLineArguments) + " due to: "
         + parseException);
   }
   return commandLine;
}

Note that this code using a newer version of Apache Commons CLI instantiates a DefaultParser for doing the parsing rather than a PosxParser or GnuParser as was done in the older code.

With the command-line objects defined and the command-line parsed, it is time for the interrogation stage. The next code listing demonstrates Apache Commons CLI's support for command-line interrogation.

Interrogating Command-line with Commons CLI

final boolean verbose =
   commandLine.hasOption(VERBOSE_OPTION);
final String fileName =
   commandLine.getOptionValue(FILE_OPTION);
out.println("The file '" + fileName + "' was provided and verbosity is set to '" + verbose + "'.");

The above code listing demonstrates use of CommandLine.hasOption() to determine if an option's particular flag is present without regard for whether a value is provided for that flag (appropriate for -v/--verbose in our example). Likewise, the code shows that CommandLine.getOptionValue() can be used to obtain the value associated with the the provided command-line flag (appropriate for the -f/--file option in our example).

The next screen snapshot demonstrates the output from the simple example whose code listings were shown above and they demonstrate the support for the verbosity and file path/location command-line options described above.

The second screen snapshot demonstrates Commons CLI' output when the command-line parameters don't include a required command-line argument.

A useful piece of functionality for any framework for building Java command-line parsing is the ability to support usage and help information. This is accomplished via Commons CLI's HelpFormatter. The next code listing demonstrates using HelpFormatter for printing help and usage information and the screen snapshot following the code listing demonstrates the appearance of the help and usage when employed.

Acquiring "usage" and "help" Details with Commons CLI

/**
 * Generate usage information with Apache Commons CLI.
 *
 * @param options Instance of Options to be used to prepare
 *    usage formatter.
 * @return HelpFormatter instance that can be used to print
 *    usage information.
 */
private static void printUsage(final Options options)
{
   final HelpFormatter formatter = new HelpFormatter();
   final String syntax = "Main";
   out.println("\n=====");
   out.println("USAGE");
   out.println("=====");
   final PrintWriter pw  = new PrintWriter(out);
   formatter.printUsage(pw, 80, syntax, options);
   pw.flush();
}

/**
 * Generate help information with Apache Commons CLI.
 *
 * @param options Instance of Options to be used to prepare
 *    help formatter.
 * @return HelpFormatter instance that can be used to print
 *    help information.
 */
private static void printHelp(final Options options)
{
   final HelpFormatter formatter = new HelpFormatter();
   final String syntax = "Main";
   final String usageHeader = "Example of Using Apache Commons CLI";
   final String usageFooter = "See http://marxsoftware.blogspot.com/ for further details.";
   out.println("\n====");
   out.println("HELP");
   out.println("====");
   formatter.printHelp(syntax, usageHeader, options, usageFooter);
}

This post has demonstrated using Apache Commons CLI to achieve some of the most common functionality related to command-line parsing in Java applications including option "definition", command-line arguments "parsing", "interrogation" of the parsed command-line arguments, and help/usage details related to the command-line arguments. 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.

  • Apache Commons CLI is open source and licensed with the Apache License, Version 2.0.
  • Current version of Apache Commons CLI (1.4) requires J2SE 5 or later.
  • Apache Commons CLI does not require any third-party libraries to be downloaded or referenced separately.
  • The Apache Commons CLI 1.4 main JAR (commons-cli-1.4.jar) is approximately 53 KB in size.
  • Apache Groovy provides out-of-the-box command-line parsing capabilities based on Apache Commons CLI via CliBuilder.
  • The Maven Repository shows almost 1800 dependencies on Apache Commons CLI including Apache Groovy.
  • Apache Commons CLI has been around for a while; its initial 1.0 release was in November 2002.
  • Commons CLI supports both long and short syntax for command-line arguments. Its main page lists support for these types of option formats (and that page includes examples of each style):
    • "POSIX like options" (single hyphen)
    • "GNU like long options" (double hyphen)
    • "Java like properties" (using -D)
    • "Short options with value attached"
    • "Long options with single hyphen"

For me, one of the biggest advantages of Apache Commons CLI when implementing command-line interfaces in simple Java applications is that I'm already familiar with Groovy's built-in use of CliBuilder. Because I use Groovy far more often for simple command-line based scripts and tools than I use Java, this Groovy familiarity with the basic Apache Commons CLI usage is helpful when moving back to Java.

Additional References

No comments: