Friday, June 30, 2017

Java Command-Line Interfaces (Part 5): JewelCli

After looking at command-line processing in Java with Apache Commons CLI, args4j, jbock, and Commandline in previous posts, I turn attention in this post to using JewelCli to accomplish similar processing of command-line arguments in Java.

Several Java command-line processing libraries use annotations to define the command-line options. Three of the four libraries covered in this series of posts so far use annotations and so does JewelCli. JewelCli is unique among the libraries I've covered so far because its annotations are applied on a Java interface rather than on a Java class or class's constructs. The next code listing demonstrates how to use annotations on a Java interface to implement the "definition" stage of command-line parsing with JewelCli.

JewelCli "Definition" Implemented with Annotated Interface

package examples.dustin.commandline.jewelcli;

import com.lexicalscope.jewel.cli.Option;

/**
 * Interface defining JewelCli-friendly command-line parameters.
 */
public interface MainCommandLine
{
   @Option(shortName="f", description="Name and path of file to be used.")
   String getFile();

   @Option(shortName="v", description="Indicate whether status should be reported verbosely.")
   boolean isVerbose();

   @Option(helpRequest=true, description="Usage details on command-line arguments.")
   boolean getHelp();
}

The simple interface shown above packs a lot related to command-line processing. The options have their single-hyphen short names explicitly specified with shortName annotation type element and implicitly specified via the name of the "get" method (though a longName annotation type element is available for explicitly specifying the long name [double hyphens] version of the switch). The command-line options also have their respective descriptions provided via the Option annotation. The use of helpRequest=true describes what command-line switch should be used to display usage/help information. In this case, because the annotation method is named getHelp(), the --help switch will display usage information. Had I named the method getDustin() and annotated it with @Option(helpRequest=true), the switch would be --dustin to display usage.

JewelCli takes advantage of convention over configuration in cases besides the long name of the switch matching the method names. With the command-line options' corresponding interface method definitions annotated as shown above, the verbosity switch (which returns a boolean) is optional. The file name switch is required because its corresponding getFile() method returns a String. If I wanted to make the file name optional, I could provide a defaultValue to the @Option annotation on the getFile() method such as @Option(defaultValue="").

With the interface (named MainCommandLine in this case) annotated with JewelCli @Option annotations, we can move to the "parsing" stage with JewelCli. This is demonstrated, along with the "interrogation" stage, in the next code listing for Main.

"Parsing" and "Interrogation" Stages with JewelCli

package examples.dustin.commandline.jewelcli;

import static java.lang.System.out;

import com.lexicalscope.jewel.cli.CliFactory;

/**
 * Demonstrates use of JewelCli for parsing command-line
 * parameters in Java.
 */
public class Main
{
   public static void main(final String[] arguments)
   {
      final MainCommandLine main = CliFactory.parseArguments(MainCommandLine.class, arguments);
      out.println("You specified file '" + main.getFile() + "' with verbosity setting of '" + main.isVerbose() + "'.");
   }
}

The Main class just shown has one line that "parses" [the call to CliFactory.parseArguments(Class<T>, String...)] and one line that "interrogates" [the line that accesses the methods defined on the JewelCli-annotated interface shown earlier].

The following three screen snapshots demonstrate the JewelCli-based code examples in action. The first image demonstrates use of --help to see usage (notice that a stack trace is included in the output). The second image shows different combinations of long (-) and short (--) option switches. The third image shows the output message and associated stack trace that are presented when a required command-line argument (--file or -f in this case) is not provided.

The code listings for both classes used in this post to demonstrate application of JewelCli are available on GitHub.

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

  • JewelCli is open source and licensed under an Apache Software License, Version 2.
  • The current JewelCli (0.8.9) JAR (jewelcli-0.8.9.jar / February 2014) is approximately 542 KB in size.
  • No additional libraries are needed to use JewelCli.
  • As shown in the example above, JewelCli uses annotations on Java interfaces for the "definition" stage. Any attempt to annotate class "get" methods in a similar way results in a message such as "IllegalArgumentException: ... is not an interface" at runtime.
  • JewelCli allows interfaces to inherit from super interfaces and @Options defined in parent interfaces will be supported in the inheriting interfaces.
  • The return data types of the methods annotated in the interface provide type enforcement of the command-line options' values. Enums can even be used as return data types to narrow down the possible command-line option types to a finite set of possibilities.

JewelCli is easy to use and, thanks to its convention over configuration approach, requires very little code to define, parse, and interrogate command-line arguments. I find the recommended approach of annotating an interface for defining the parseable command-line options to be aesthetically pleasing as well.

Additional Resources

1 comment:

Richard Fearn said...

> The first image demonstrates use of --help to see usage (notice that a stack trace is included in the output).

I guess the idea is that you catch the HelpRequestedException, and just display its message, to avoid the stack trace being displayed.