Easy Command Line Interface coding with JCommander

Implementing a command line arguments parser can turn quickly from ‘yes it’s just one parameter’ to a heap of for loops with curious string manipulations and conditionals in-between. Try and test that. I guess we’ve all been there. Using apache-commons-cli makes life easier and has been the defacto standard for most projects that require a command line interface (cli) as it makes writing cli’s reasonably easy. However, when I discovered JCommander by Cédric Beust my way of making cli’s changed for the better!. Let me share it with you:


JCommander (jcommander.org) is a small library that uses annotations to declare command line parameters and commands. Typically you have a single class with in it annotated fields to hold and describe the command line interface. By passing an instance of this class into a new instance of the JCommander object, it will parse the arguments and populate the object according to the rules in the annotations. It’s so simple, it’s amazing.


Let me give you an example for a hypothetical program called the Hitchhikers Guide to the Galaxy Quote Finder. So as its purpose is to find quotes we’ll probably have a search option and probably some optional parameter to specify the maximum number of results. With JCommander parsing these arguments comes down to creating a single class to hold the parameters and using the JCommander class to parse them:



public class QuoteFinderParameters {

@Parameter(names = , required = true, description = "searches for a quote.")
private String search;

@Parameter(names = , required = false, description = "max number of results.")
private int maxResults = 10;

public String getSearch() {
return search;
}

public int getMaxResults() {
return maxResults;
}

}


The above class defines two (read-only) fields search and maxResults that become command line parameters for JCommander by annotating them with @Parameter and listing the name, description and optionality of the parameter. When parsing JCommander will use these annotations plus some reflection to set the values of the variables.
The code that parses the command line parameters is very easy as well:



public static void main(String[] args) {
QuoteFinderParameters params = new QuoteFinderParameters();
JCommander cmd = new JCommander(params);
try {
cmd.parse(args);
//
searchH2GT2G(params.getSearch(), params.getMaxResults(), System.out);
//
} catch (ParameterException ex) {
System.out.println(ex.getMessage());
cmd.usage();
}
}


First it creates a new instance of the object to hold the parameters, after which it initializes JCommander with it. Then, in the try block, it tries to parse the arguments. If it succeeds it will invoke the search function. But more interestingly when it fails with a ParameterException the exception will hold a nice user readable error message (in English). In this case I also remind the user of all possible parameters using the usage() method.


The above illustrates a basic usage scenario, but JCommander has many more capabilities. It supports custom parameter types using the IStringConverter interface, parameter validation using the IParameterValidator interface and even IDefaultProvider for complex default values. Implementing these interfaces is trivial as they consist of a single method.


But the thing I like the most of JCommander is its capability for commands. You know, single executable, multiple commands. Currently our quote finder can find quotes, but say, the 7th part of the Hitchhikers Guide has been discovered and needs to be added. So instead of only searching we now have a command line interface that must support two commands. Let’s see how you do this with JCommander. First we’ll need two parameter objects, one for each command:



@Parameters(commandDescription = "Searches for quotes.")
public class SearchCommand {

@Parameter(description = "words to search for")
private List search;

public List getSearch() {
return search;
}

}


@Parameters(commandDescription = "Add a book to the collection.")
public class AddBookCommand {

@Parameter(names = )
private File file;

public File getFile() {
return file;
}

}


As you can see an extra annotation (@Parameters) is added to the parameter object, which lists the description of the command. Now we need to inform the JCommander object of these objects existence, by adding them as commands. After the parsing the getParsedCommand() method will return the name of the command that was parsed. Now we need to go through a bit of if else if code to bind the string to the actual performing of the command. Easy, but verbose. In JDK7 this should be doable with a nice switch statement :)



public static void main(String[] args) {
//
SearchCommand search = new SearchCommand();
AddBookCommand addBook = new AddBookCommand();
//
JCommander cmd = new JCommander();
cmd.addCommand("search", search);
cmd.addCommand("addBook", addBook);
try {
cmd.parse(args);
//
if ("search".equals(cmd.getParsedCommand())) {
searchH2GT2G.searchQuote(search.getSearch());
} else if ("addBook".equals(cmd.getParsedCommand())) {
searchH2GT2G.addBook(addBook.getFile());
} else {
cmd.usage();
}
//
} catch (ParameterException ex) {
System.out.println(ex.getMessage());
cmd.usage();
}
}


As you can see JCommander is an excellent library to quickly and easily build a sophisticated command line interface with minimal code. The library itself is just 32 kilobyte in size and uses the commercially friendly Apache 2 license. I can think of no reason why not to use JCommander when writing your next command line interface.


JCommander : ‘Because life is too short to parse command line parameters’ - highly recommended.

2 comments:

  1. pgสล็อต สล็อตออนไลน์ เว็บตรง แตกง่าย จุดเริ่มของพวกเรานั้นพวกเราก็จำเป็นต้องขอย้อนไปในช่วงเวลาที่พวกเรานั้นยังปฏิบัติงานประจำอยู่เลย pg slot ช่วยให้ท่านได้สนุกไปกับเรา

    ReplyDelete