The fastest way to introduce you to JCommando is via example.
Using this brief tutorial, we'll create a very basic parser and then
evolve it to cover more complex functionality, adding features
incrementally. Let's create a simple parser for a command like
the Unix 'du' command. 'du' reports disk space usage for a file,
directory, or
directory hierarchy.
By default, 'du' reports usage in bytes, but this can be very difficult
to read for large files, so let's add an option to display sizes in
kilobytes.
The "kilobytes" option is a boolean, it is either on or off.
We'll start by creating the basic JCommando XML file, and we'll include
that option.
<jcommando>
<option id="kilobytes" long="kilobytes"
short="k">
<description>print sizes in
kilobyte format (e.g., 32K)</description>
</option>
Looking at the above we can see there is the primary 'jcommando' tag,
and we've defined the "kilobytes" option. Examining the
option, we see that it has an 'id' attribute, the value of this
attribute must be a valid Java identifier and therefore must follow the
Java language specification for identifiers (TODO: insert JLS reference
link here). The 'long' attribute describes the "long form" of the
command-line option, and is optional, but it is good practice to
include it. The 'short' attribute describes the "short form" of
the command-line option, and is required. The 'short' and 'long'
forms of
the option in this case means that a command-line that looks like:
du -k
or
du --kilobytes
is valid.
I'll provide more context around the 'commandless' tag a little
later. Suffice it for now to say that the specification of the
'allow-optionless' attribute as "true" means that the our 'du' program
can be run without specifying any command-line options. The 'or'
tag within the 'commandless' tag mean that if the "kilobytes" option is
specified on the command line then the
parsing will succeed. There are actually several "grouping"
operators besides 'or' (such as 'and', 'xor', and 'not') which can be
specifed, and they can be nested and combined to create rich option
validation.
Let's assume we put the above into a file named
'example.xml' and that we wish to generate the parser from that
description. We're going to generate a parser class called
'DiskUsageParser' and have it generated in the package named
'org.jcommando.example'. In order to do so, we would invoke the
parser generator like so:
After invoking the parser, there should be directory hierarchy
(org/jcommando/example) that contains a Java file called
DiskUsageParser.java. This is what 'DiskUsageParser.java'
contains:
/*
* THIS IS A GENERATED FILE. DO NOT EDIT.
*
* JCommando (http://jcommando.sourceforge.net)
*/
/**
* Called by parser to set the 'kilobytes'
property.
*
*/
public abstract void setKilobytes();
/**
* 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("kilobytes"));
return or1;
}
}
The majority of this class is uninteresting to the end user
(you). However, note that it is an abstract class, and that there
is an abstract method called setKilobytes().
For each option
that is specified in a JCommando XML file, there will be a 'setter'
generated. Also note that there is an abstract method called doExecute() which corresponds to
the 'id' of the 'commandless' tag. This method will be invoked by
the parser to actually execute the disk usage program.
In order to make use of the parser, the user is
expected to extend it with their own class, like so:
/**
* Disk Usage
*/
package org.jcommando.example;
public class DiskUsage extends DiskUsageParser
{
private boolean useKilobytes;
public static void main(String args[])
{
DiskUsage du = new DiskUsage();
du.parse(args);
}
/**
* Called by parser to set the 'kilobytes'
property.
*/
public void setKilobytes()
{
this.useKilobytes = true;
}
/**
* Called by parser to execute the 'command'.
*/
public void doExecute()
{
System.out.println("Show kilobytes:
" + useKilobytes);
}
}
As you can see, this is pretty straightforward. Our DiskUsage
class extends the generated
parser class, and provides an implementation for the setKilobytes()
method and the doExecute()
method. The setKilobytes()
method will be called if the '-k' or
'--kilobytes' option is supplied on the command-line. We
have also provided a main()
method, which creates an instance of the DiskUsage class and invokes
the parse()
method that it inherits from it's superclass; it simply
passes the arguments
received from the command-line through to the parse()
method. The parser, after invoking the 'setters' that equate to
the command-line options, invokes the doExecute()
method in order to execute the command. Our doExecute()
method simply displays the status of the "useKilobytes" property.
In order to run the disk usage application, we would probably want to
wrap it's invocation in a batch file or shell script. Assume
we've compiled the above class and generated class, and created a
trivial Windows batch
file named 'du.bat', like so:
rem Invoke the Disk Usage
application
java -cp .;./jcommando.jar org.jcommando.example.DiskUsage %1 %2 %3 %4
%5 %6 %7 %8 %9
Notice that jcommando.jar is required at runtime.
Now you can play around with the 'du' executable to see how parsing
works, like so:
> du --kilobytes
Show kilobytes: true
> du
Show kilobytes: false
> du -k
Show kilobytes: true
> du --help
Exception in thread "main" org.jcommando.ParseException: Unknown option
'help'
at
org.jcommando.JCommandParser.parseOption(JCommandParser.java:162)
at
org.jcommando.JCommandParser.parse(JCommandParser.java:101)
at
org.jcommando.example.DiskUsage.main(DiskUsage.java:14)
As you can see, when you pass either the "short form" or "long form" of
the kilobytes option, the setKilobytes()
setter gets called, and the doExecute()
method is invoked.
The last example, with the "--help" option is
meant to show what happens when an unrecognized option is passed, and
to raise the issue of help in general. JCommando can assist you
with providing meaningful help to the user. Let's change our
example to exploit this functionality.
<jcommando>
<option id="kilobytes" long="kilobytes"
short="k">
<description>print sizes in
kilobyte format (e.g., 32K)</description>
</option>
<option id="help" long="help"
short="h">
<description>prints this help
message</description>
</option>
Note the use of the <xor>
tag. This means that either 'help' can be specified, or
'kilobytes' can be specified, but not both; they are exclusive of one
another. Now, re-generate the parser, and we'll implement the new
setHelp()
method that the parser imposes on us. For brevity, only the new
method is shown here:
/**
* Disk Usage
*/
package org.jcommando.example;
public class DiskUsage extends DiskUsageParser
{
...
public void
setHelp()
{
System.out.println("du - Disk Usage
utility\n");
System.out.println("Options:");
printUsage();
System.exit(0);
}
}
The printUsage() method is provided by the generated parser
[super] class. If you invoke the 'du' command again with the'-h'
or
'--help' option, this is what you get:
> du --help
du - Disk Usage utility
Options:
-h, --help prints this help
message
-k, --kilobytes print sizes in
kilobyte format (e.g., 32K)