Thursday, October 19, 2017

Java Command-Line Interfaces (Part 25): JCommando

JCommando is described on the JCommando site as "a Java argument parser for command-line parameters." JCommando reads XML configuration to generate a Java class that handles parsing from a Java application. The only Java-based library previously covered in this series of posts on Java command-line parsing libraries that provided XML configuration is JSAP, but it's a secondary form of configuration with that library and I did not cover XML configuration in my post on JSAP.

Because JCommando uses XML to specify command line options to be parsed, the "definition" stage with JCommando is accomplished via XML specification. As with the previous posts in this series, the examples in this post are based on command line options for file path and name and verbosity and their definition in JCommando-compliant XML is shown in the next code listing (options.xml).

JCommando via XML Portion of "Definition" Stage: options.xml

<jcommando>
   <option id="file" long="file" short="f" type="String">
      <description>Path and name of file</description>
   </option>
   <option id="verbose" long="verbose" short="v">
      <description>Verbosity enabled</description>
   </option>
   <commandless id="execute" allow-optionless="true">
      <or>
         <option-ref id="file" />
      </or>
   </commandless>
</jcommando>

JCommando uses the XML file an input and, based on that XML, generates a Java source code file that parses the options specified in the XML. There are two ways to instruct JCommando to parse this XML and use the details to generate Java source code. One way is to use the executable jcomgen executable provided with the JCommando distribution (in its bin directory). The second approach for generating a Java class from the XML is the approach shown here: using Apache Ant and a JCommando-provided Ant task. This is demonstrated in the next XML/Ant listing.

Ant Target for Generating Source from XML with JCommando

  <target name="generateSourceForJCommando"
          description="Generate command line parsing source code that uses JCommando">
    <taskdef name="jcommando" classname="org.jcommando.ant.JCommando">
      <classpath>
        <pathelement location="C:\lib\jcommando-1.2\lib\jcommando.jar"/>
      </classpath>
    </taskdef>

    <jcommando inputfile="jcommando/options.xml"
               classname="MainParser"
               destdir="src"
               packagename="examples.dustin.commandline.jcommando"/>
  </target>

The above Ant target shows how JCommando allows the input XML file (options.xml) to be specified as the "inputfile" and that the generated Java source code file will be placed in the src directory in a subdirectory structure matching the designated package "examples.dustin.commandline.jcommando". The execution of the Ant target and source code generation is shown in the next screen snapshot.

The result of this Ant target is the generated Java source class MainParser.java whose listing is shown next.

Generated Java Source Class MainParser.java

/*
 * THIS IS A GENERATED FILE.  DO NOT EDIT.
 *
 * JCommando (http://jcommando.sourceforge.net)
 */

package examples.dustin.commandline.jcommando;

import org.jcommando.Command;
import org.jcommando.JCommandParser;
import org.jcommando.Option;
import org.jcommando.Grouping;
import org.jcommando.And;
import org.jcommando.Or;
import org.jcommando.Xor;
import org.jcommando.Not;

/**
 * JCommando generated parser class.
 */
public abstract class MainParser extends JCommandParser
{
   /**
     * JCommando generated constructor.
     */
   public MainParser()
   {
      Option file = new Option();
      file.setId("file");
      file.setShortMnemonic("f");
      file.setLongMnemonic("file");
      file.setDescription("Path and name of file");
      addOption(file);

      Option verbose = new Option();
      verbose.setId("verbose");
      verbose.setShortMnemonic("v");
      verbose.setLongMnemonic("verbose");
      verbose.setDescription("Verbosity enabled");
      addOption(verbose);

      Command execute = new Command();
      execute.setName("commandless");
      execute.setId("execute");
      execute.addOption(file);
      execute.setGrouping( createExecuteGrouping() );
      addCommand(execute);

   }

   /**
     * Called by parser to set the 'file' property.
     *
     * @param file the value to set.
     */
   public abstract void setFile(String file);

   /**
     * Called by parser to set the 'verbose' property.
     *
     */
   public abstract void setVerbose();

   /**
     * Called by parser to perform the 'execute' command.
     *
     */
   public abstract void doExecute();

   /**
    * Generate the grouping for the 'execute' command.
    */
   private Grouping createExecuteGrouping()
   {
      Or or1 = new Or();
      or1.addOption(getOptionById("file"));
      return or1;
   }
}

With the Java source code generated, we now have our options definitions. A custom class is written to extend the generated MainParser and to access its parent for parsing. This is demonstrated in the next code listing of the custom written Main class that extends the generated MainParser class.

Custom Class Extending Generated Class

package examples.dustin.commandline.jcommando;

import static java.lang.System.out;

/**
 * Demonstrates JCommando-based parsing of command-line
 * arguments from Java code.
 */
public class Main extends MainParser
{
   private String file;
   private boolean verbose;

   @Override
   public void setFile(final String newFilePathAndName)
   {
      file = newFilePathAndName;
   }

   @Override
   public void setVerbose()
   {
      verbose = true;
   }

   public static void main(final String[] arguments)
   {
      final Main instance = new Main();
      instance.parse(arguments);
   }

   /**
    * Called by parser to execute the 'command'.
    */
   public void doExecute()
   {
      out.println("File path/name is " + file + " and verbosity is " + verbose);
   }
}

As shown in the custom Main.java source code shown above, the "parsing" stage is accomplished in JCommando via execution of the parse(String[]) method inherited from the class that JCommando generated based on the configuration XML (and that generated class gets its definition of that parse method from its parent JCommandParser class).

The custom class that extends the generated class needed to have the "set" methods for the options implemented. With these properly implemented, the "interrogation" stage in JCommando-based applications is as simple as accessing the fields set by those custom implemented "set" methods. This was demonstrated in the doExecute() method shown in the last code listing. That doExecute method was generated as an abstract method in the generated parent class because of the specification of the <commandless> element with id of "execute" in the configuration XML.

The JCommandParser class that the custom class ultimately extends has a method printUsage() that can be used to write "help"/"usage" output to standard output. This can be seen in the source code for Main.java available on GitHub.

The next two screen snapshots demonstrate execution of the sample code discussed in this post. The first screen snapshot shows the "usage information that can be automatically printed, in this case when the required "file" option was not specified. The second screen snapshot demonstrates the combinations of long and short option names for the "vile" and "verbose" options.

The steps involved with using JCommando that have been discussed in this blog post are summarized here.

  1. Define options in XML file.
  2. Generate Java parser source code from XML using one of two approaches.
    • Use jcomgen tool provided in JCommando's bin directory.
    • Use Ant target with JCommand-provided Ant task as demonstrated in this post.
  3. Write Java class that extends generated parser class.

There are characteristics of JCommando to consider when selecting a framework or library to help with command-line parsing in Java.

  • JCommando is open source and available under the zlib/libpng License (Zlib).
  • The jcommando.jar JAR is approximately 27 KB in size and there is no third-party dependency.
  • Defining options in JCommando via XML is a different approach than the other libraries covered in this series, but what I find more interesting about JCommando's options definition is the easy ability to express relationships between options such as "and", "or", "xor", and nested combinations of these.

JCommando implements some novel concepts in terms of Java-based command line options parsing. It requires XML configuration of the potential command line options, but makes it easy to establish relationships between those options. JCommando generates Java source from the XML options configuration and a custom parsing class extends that generated class. JCommando is also the first of the libraries covered in this series to use the Zlib license.

Additional References

No comments: