Parsing command line arguments — JSAP

Although today web and rich client applications are in mostly, sometimes you end up creating a simple command line tool which does not need any graphical user interface (perhaps developing the UI costs you more time than writing the application itself). And to enable configuration of your application you are likely to add command line arguments. If you want the input to be more elastic you can add named (or qualified) arguments but parsing them would be an extra utility.

For this I’ll introduce a new article series in two parts about Java command line argument parsers. In the first article (so this one) I’ll have a look at JSAP from Martian Software, in the second I’ll introduce Commons CLI from Apache. Both of the tools is old (last update from JSAP was in 2006, from CLI in 2009). This is because there is not much about releasing newer version for command line argument parsing because once done there are rarely changes how the argument should be parsed.

JSAP

The abbreviation stands for Java Simple Argument Parser and there is no reference to the ERP system and its Java version 🙂 JSAP was last updated to the version 2.1 in 2006. From the two tools this is the older one so I take the first look at this one. For a detailed documentation take a look at the project homepage. In this article I’ll look at the website scraper project introduced in the article series Website scraping with JSoup and XMLBeam. As you can remember, I needed some input parameters to the application per default or to make it run with JSoup and from the clients infrastructure too:

  • output path
  • output filename
  • connection timeout
  • proxy server address
  • proxy server port

And if you want to have a proxy you need to add all the five parameters. So I did change this behavior. First with JSAP.

Defining the input parameters

First of all I needed to identify and define the parameters: are they optional, are they always needed or are there pairs which belong together. And which should be flagged (to be use with a flag such as “-v” for example). Output path and filename are mandatory properties and they do not need any flags. They have to be provided to the application in this order. Proxy and connection timeout are optional and they can be flagged. Proxy host and port belong together, however I could introduce a default port (8080) because most proxies run 8080. Eventually I could define a combined flag where proxy could be provided as host:port so one parameter less. But I’ll first implement the actual behavior with JSAP. And to keep the current behavior too I need some other, optional parameters where the users can input the port, proxy host and proxy port without flags. Yes, this makes the usage of a command line tool as complex as it was previously but you know how it is to keep up with customers who develop their own batch files to launch your application. Sometimes they bellyache about changing the configuration. So if I’d had developed the application from the start with a command line tool I wouldn’t have this problem.

General about JSAP parameters and options

Before we dig into the configuration let me tell you in some words about JSAP and its configuration options. Every option has an ID — defined through a constructor argument. It has to be unique because later on you’ll need the value contained in this parameter — and you can ask it with its ID. The ID will be used later as the name of the parameter in the usage information. However you can override the display with a custom value.

FlaggedOption option = new FlaggedOption("connection timeout");
option.setUsageName("connection timeout");

Because the command line arguments are String objects your application has to parse them. For every option in JSAP you can define a parser. The String-to-String parser is default so if you want to keep that parameter as a String you do not have to define a parser. Other conversions (for example to an Integer) is done with parsers. Naturally you can define your own parser but JSAP returns an Object in this case because it cannot know about your class.

option.setStringParser(JSAP.INTEGER_PARSER);

For each option you can define a help text which will be shown if you print the usage help of the application. Here you can describe what your argument does and what values are expected.

option.setHelp("Connection timeout in seconds. Optional.");

You can set your parameters mandatory. If you have one option which is required and is not present, your parsing will not succeed and you can tell it to the user.

option.setRequired(true);

Optional arguments can have a default value. This is good if you have some parameter which you enable to overwrite. For example the timeout in my example case. The default value is always a String because if the parameter is not provided via the list of arguments JSAP parses this default value with its parser. So the timeout default value results in an integer of 30.

option.setDefault("30");

Sometimes you need a list of argument as a parameter. This is possible too. You can define the option to receive a list with the defined list separator.

option.setList(true).setListSeparator(';')

The “greedy” property of an UnflaggedOption does almost the same as a list just with the space as the separator.

UnflaggedOption

UnflaggedOptions are those options which do not have a flag so can be provided as plain arguments. For example if you call

javac TestClass.java

then “TestClass.java” is an UnflaggedOption argument. As I mentioned a few lines above the UnflaggedOption has beside the above listed properties another one: greedy. This makes this unflagged option swallow all arguments provided without a flag to the application. This could be of great use if you want to enable the users of the application to provide unnecessary data while startup and you do not want your JSAP validation to result in a failure. Or as in my case this is the tool to enable the compatibility mode of the application.

UnflaggedOption remaining = new UnflaggedOption("other parameters").setGreedy(true);

FlaggedOption

FlaggedOptions come with flags — as they name can tell. You can have short and long flags. Short flags are only one character flags (for example ‘-t’), long flags are longer and they need two dashes when calling (for example ‘–timeout’).

FlaggedOptions cannot be greedy however you can enable a flag to appear multiple times. In this case you can access the multiple values as an array of arguments.

FlaggedOption timeoutOption = new FlaggedOption("connectionTimeout").setShortFlag('t').setLongFlag("timeout").setAllowMultipleDeclarations(true);

Switch

Switches do what their name stays for: they are a setting which is either on or off. For example you could define a switch ‘-v’ for verbose output. If the switch is present as an argument the value returned by JSAP is true, otherwise it is false.

Using JSAP

After so much about the tool you definitely want to see how to combine the blocks together.

First you should define your arguments needed at the command line. You can do this with creating the Flagged- or UnflaggedOptions or Switches, setting their parameters (as described above). After this you have to tell JSAP to “Hey, I have some defined constraints, could you verify the command line arguments if the fit or not?”.

For this you need to instantiate a JSAP object and register your parameters:

JSAP jsap = new JSAP();
jsap.registerParameter(pathOption);
jsap.registerParameter(filenameOption);
jsap.registerParameter(timeoutOption);
jsap.registerParameter(proxyHostOption);
jsap.registerParameter(proxyPortOption);
jsap.registerParameter(greedyOption);

Now JSAP is ready to decide if the passed arguments fit the requirements or not. The following code shows how you can parse the arguments and tell the user what went wrong:

JSAPResult configuration = jsap.parse(args);

if (!configuration.success()) {
    for (java.util.Iterator errs = configuration.getErrorMessageIterator(); errs.hasNext(); ) {
        System.err.println("ERROR: " + errs.next());
    }
    System.err.println();
    System.err.println("Usage:");
    System.err.println("\tjava -jar commandLine.jar ");
    System.err.println("\t\t" + jsap.getUsage());
    System.err.println();
    System.err.println(jsap.getHelp());
    return;
}

In line 13 (highlighted above) you can decide what to do later on. I do not mean to let your application continue to run if the arguments provided are not valid. I just suggest to use eventually an exit code instead of return. However if you have your argument parsing in the main method (as it should be) the return statement let the application terminate. But sometimes it is not enough to use a return statement: you can misuse JSAP and validate some other array of Strings in your application and if the result is not good enough you can terminate on-the-fly.

Getting the provided data is easy too: just call the right getter method with the ID you defined when creating your option. For example:

int timeout = configuration.getInt("connection timeout"); // default value is returned if nothing set

Simple solution

For this article series I’ve set up a small application which redirects the provided arguments to JSAP and CLI (coming in the next article). It is packed into a Maven archive (shaded for convenience). But I guess if you need one part of it it won’t be a big deal to build your own solution.

And as you can see, the solution is really just barely good enough for my requirements. Well, the greedy option makes it a bit bloated but who cares?

XML configuration

Besides the Java solution to plug the options together there is another way: with XML configuration. I won’t go into detail how this works instead I’ll just show the configuration for the example above.

Note: the homepage of JSAP tells that XML configuration is experimental. So I cannot guarantee it will work. Let’s hope for the best.

The XML configuration’s base is the XStream library from codehaus. Now it is supported at ThoughtWorks. I’ll try to use the newest version first and see how they work together. The actual version of XStream is 1.4.7 — and it seems working.

The main idea behind XML configuration of JSAP is that you do not mess around in your Java code and can provide the configuration of your required arguments in a structured, separate file. However the handling of the arguments has to happen in the Java code itself so you cannot remove the whole process of handling arguments from your code.

And as stated above: you make the configuration of your parameters in an XML file. The names of the tags are the same as of the classes — and the method names too, so you can use the same names to get the same results. And naturally JSAP registers your parameters automatically after reading them from the XML file.

The configuration of the following example will look like this:

<jsap>
   <parameters>
      <unflaggedOption>
         <id>output path</id>
         <required>true</required>
         <help>Output path where the CSV file should be saved. Mandatory.</help>
      </unflaggedOption>
      <unflaggedOption>
         <id>output filename</id>
         <required>true</required>
         <help>The name of the resulting CSV file. Mandatory.</help>
      </unflaggedOption>
      <flaggedOption>
         <id>connection timeout</id>
         <stringParser>
            <classname>IntegerStringParser</classname>
         </stringParser>
         <shortFlag>t</shortFlag>
         <longFlag>timeout</longFlag>
         <defaults>
            <string>25</string>
         </defaults>
         <help>Connection timeout in seconds. Optional.</help>
      </flaggedOption>
      <flaggedOption>
         <id>proxy host</id>
         <shortFlag>h</shortFlag>
         <longFlag>host</longFlag>
         <help>Host of the proxy server. Optional. Example: 'http://localhost'</help>
      </flaggedOption>
      <flaggedOption>
         <id>proxy port</id>
         <stringParser>
            <classname>IntegerStringParser</classname>
         </stringParser>
         <shortFlag>p</shortFlag>
         <longFlag>port</longFlag>
         <defaults>
            <string>8080</string>
         </defaults>
         <help>Port of the proxy server. Optional.</help>
      </flaggedOption>
      <unflaggedOption>
         <id>greedy</id>
         <stringParser>
            <classname>StringStringParser</classname>
         </stringParser>
         <required>false</required>
         <greedy>true</greedy>
         <help>This is a greedy option to make life more complicated with swallowing any unflagged option provided but not expected.</help>
      </unflaggedOption>
   </parameters>
</jsap>

As you can see, the required parameter defaults to false and the default stringParser is the StringStringParser — so you need not provide them. Neither in XML nor in Java. I let it with the greedy option as an example.

To let JSAP load the configuration from an XML file you have to provide the file as a URL to the JSAP constructor:

JSAP jsap = new JSAP(JSAPWithXML.class.getResource(&quot;/configuration.xml&quot;));

The “/” indicates that the configuration is in the root folder of the JAR not beside the JSAPWithXML class somewhere in a sub-folder.

Note: The XML configuration feature was implemented with the version 2.0 so if you plan to use an older version be aware that you do not have access to XML configuration.

Note 2: It is a bit creepy but you have to add a string tag to your defaults even if it will be converted to an integer.

Conclusion

JSAP is a nice tool to use in applications which need to handle command line arguments.  It is easy to use and configure and it is stable since some years.

Source Code

Like most of the time, the source code for this article can be found at GitHub.

Note: Java 8 is required to compile and run the code — but it can work with 6 and 7 too.

Next time…

I’ll take the same defined requirements and try to realise them with CLI from Apache. Stay tuned to see which fits you better.

Advertisements

One thought on “Parsing command line arguments — JSAP

  1. Pingback: Parsing command line arguments — CLI | JaPy Software

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s