JavaPopt developer's guide

Kevin Boone, March 2000

What is JavaPopt?

JavaPopt is a general-purpose command-line argument processor for Java applications. It is intended to make the processing of command-line arguments a trivial matter in most circumstances. JavaPopt supports most forms of command line, including single-dash and double-dash switches, numeric (real and integer) arguments, flags, etc.

JavaPopt is based on the GNU/Linux popt library for C programs, but with modifications to make it more suitable for the Java environment. Developers familiar with C popt should find JavaPopt straightforward.

Legal and disclaimer

Usual disclaimers apply. Please read the `limitations' section at the end of this document before adopting the use of this package: it won't be suitable for all applications.

This software has been made available in the hope that it will be found useful. There is no warranty; please e-mail comments, bug reports and (even better) bug fixes to the author. Please feel free to use this software in any way you see fit, except to claim it as your own work.

JavaPopt concepts

What is involved in command-line parsing?

Consider the three following command-line invocations, which between them illustrate most of the issues that make it difficult to process command-line arguments:
java -classpath classes:moreclasses -Xdebug -Dx.y=hello -Dx.c=hello2 myclass
rpm -i --force -nodeps mypackage.rpm
g++ -I include1 -I include2 -fstrict -mcpu=i386 file1.cpp file2.cpp
It the first example, the switch `-classpath' is followed by one argument, and this combination should appear exactly once in the invocation. `-Xdebug', similarly, appears exactly once, but has no arguments. There can be any number of `-D' switches, but note that the argument follows `-D' directly, there is no space between the -D and argument.

In Popt terminology, `-class' and `-D' are `switch arguments', that is, they are introduced and specified by switches (-xxx). `-Xdebug' is also a switch argument, but with no arguments. We will refer to this simply as a `switch' or a `flag'. `myclass' is a `non-switch argument'. It is not introduced by a switch, and therefore its interpretation depends on the order in which it appears on the command line.

In the second example we have three different types of flag: a single-char flag `-i', a long-name flag `--force' and a long-name flag `-nodeps' specified as if it were a single-char flag. Each of these must appear exactly once, or not at all. Conventionally some applications support the grouping of single-char flags, e.g., rm -rf file, which is equivalent to rm -r -f file. The `rpm' example also requires a single non-switch argument.

In the third example we have a repeated switch argument `-I', where the argument and the switch can appear any number of times. There is a also a long-name switch argument where the argument `-mcpu=i386' where the argument is separated from the switch by an equal sign, not a space.

JavaPopt command-line support

JavaPopt does not set out to support all these modes of operation, but a fair subset that will be appropriate for the needs of most programs. Specifically it supports the following types of command-line usage: In particular, the following are not supported.

Identifiers

The arguments parsed by JavaPopt are located by means of identifiers. This system allows the application to retrieve the different arguments using the same technique, regardless of how they were supplied. A switch argument may have a long name, or a short name, or both; a non-switch argument has neither. However, as they are all supplied with unique identifiers, when the application executes, for example,
String name = popt.getString("name");
it does not matter how the name was supplied. This means that the command-line syntax can be modified without changing the logic of the program.

Type checking

JavaPopt can do limited type-checking on arguments. When specifying the details of an argument, the application indicates its type. Currently String, Integer and Double are supported. String is, of course, the most general, and can contain any text. Double and Integer attempt to convert the supplied text into the appropriate class. This may fail, of course, if the command-line does not supply appropriate data. There is, as yet, no support for other Java type (Date, for example), so these should be entered as Strings and converted by the application.

Using JavaPopt

Basic usage

For most applications, JavaPopt can do all the argument processing internally. The basic steps are as follows. Alternatively, the application can process the non-switch arguments itself, by calling parseSwitchArguments() to separate the switch and non-switch arguments, and then retrieving the non-switch arguments by calling getLeftoverArgs(). Or, as another alternative, the application can set a bunch of non-switch args by calling setLeftoverArgs() and then call parseNonSwitchArgs to process them. Here is an example of this mode of operation.
  Popt popt = new Popt (args); 
  
  // Tell Popt what arguments we accept. There are two versions of addSwitchSpec(), both
  //  used here. The first version is for flags, which don't take any arguments. The
  //  second is for switch arguments, where the argument name and type are required. Note that
  //  the argument name is only used for usage messages.
  // The arguments for the first versions are: identifier, shortname, 
  //     longname, isCompulsory, description, allowDuplicates
  // For the second version the additional arguments are for the name and type
  popt.addSwitchSpec ("version", 'v', "version", false, "show version", false);
  popt.addSwitchSpec ("double", 'd', "double", false, "a real number", "double", Double.class, true);
  popt.addSwitchSpec ("string", 's', "string", false, "a piece of text", "string", String.class, true);
  popt.addSwitchSpec ("integer", 'i', "integer", false, "an integer", "string", Integer.class, true);
  popt.addSwitchSpec ("longflag", (char)0, "longflag", false, "a flag", true);
   
  // addNonSwitchSpec() specifies arguments that are not introduced by switches. If there are
  //  a variable number of them the application may prefer to handle them itself.
  popt.addNonSwitchSpec ("message", "message", false, "a piece of text", String.class);

  // Add help arguments for --help, -h, and -?
  popt.addHelp (true, true, true);

  // Parse the command line.
  //  parse() returns false if it fails
  if (popt.parse() == false)
    {
    // Parse failed: say why...
    System.err.println (popt.getErrorMessage());
    System.err.println ("Usage: Popt " + popt.getShortUsage());
    // ...and show the usage message 
    System.err.println (popt.getUsage());
    }
  else if (popt.supplied("help"))
    {
    // `help' is a flag, not at argument. It is either supplied or not
    System.out.println ("Usage: Popt " + popt.getShortUsage());
    System.out.println (popt.getUsage());
    }
  else if (popt.supplied("version"))
     {
     // `version' is also a flag
     System.out.println ("JavaPopt version 1.0 (c)2000 Kevin Boone");
     }
  else
    {
    // `double' takes a value (a number). If supplied, get the number.
    if (popt.supplied("double"))
      System.out.println ("double=" + popt.getDouble("double"));
    else
      System.out.println ("double argument not supplied");
    //  ... process other arguments ...
    }

More sophisticated usage

The problem with the simple example above is that it can't handle duplicate arguments. Although the `allowDuplicates' argument is set to `true', indicating that supplying duplicates is not an error, only the last version can be retrieved using the `getXXX()' methods. To handle duplicates we need to use a PoptParseHandler. This interface can be implemented by any class that wants to know when an argument has been matched. If has one method, handleArg which is called for each match, specifying the identifier and the argument as an Object. The application's implementation of PoptParseHandler is responsible for casting this Object into the real type.

A parse handler is set like this: popt.setPoptParseHandler(new MyParseHandler()); setting it to null disables this feature.

Now, every time the duplicate copies are matched the handler will be called, and it can take whatever action is required. The handler should return true if the processing was successful, and false otherwise; in the latter case parsing will be abandonned. The handler should ideally call setErrorMessage on the supplied Popt object, because the application will probably go on to call getErrorMessage() to find why parsing failed.

Popt notes

Autohelp and usage

JavaPopt can generate short and long usage messages, based on the argument specifications supplied. See the Javadoc comments for getShortUsage() and getUsage for more details.

In addition, it can add `help' arguments to the argument list, if the application calls addHelp(). However, JavaPopt can't respond to a `--help' request by printing a usage message automatically, as this would not be appropriate in many applications. So the application will still need to check the `help' argument after parsing.

Case sensitivity

Long switch names are always matched without regard to case. This is sensible, as the purpose of using long switch names is to avoid the need to use switches with different cases to get a sufficient number of switches. So in this command line
--hello --Hello
if they match anything, both arguments will match the same entry. Short switch names can be made case insensitive, if the ignoreCase argument to the constructor is true. Popt does not check whether this it is safe to do this. In general it will be unsafe if you have entries in the switch table that are distinguished only by the case of their short names. In other words, if you want this line
-h -H
to be treated as two separate switches you must ensure that ignoreCase is false.

Whether you set case sensitivity on or off, it is generally regarded to have command-line switches that are distinguished only by case. Most people find command-line arguments awkward enough at best.

Retrieving results

After parsing, the application can find the values of the arguments by calling getArg. This is the most general technique, and returns an Object, or null if the argument was not supplied. Convenience functions are supplied to make it easier to retrieve arguments of known type. getString(), getInt() and getDouble() return the arguments as a String, an int and a double. Note that there are no possible int or double values that mean `no value supplied', so these methods throw a run-time exception if the value requested was not supplied by the command line. Therefore it is best to call Popt.supplied() to ensure that a value exists.

Default values

JavaPopt does not have a built-in mechanism for storing default values for arguments that can be returned if no data was supplied. However, there are a number of convenience functions that simplify the handling of defaults. For example, getIntOrDefault takes two arguments: the identifier of the argument, and the default value. If the argument was not supplied on the command line, the default is returned instead.

Data validation

Each argument must have a specified type. Currently Popt supports Strings, integers, and doubles. The equivalent to `don't care' is, of course, String, as the command-line arguments are Strings to begin with. In other words, there is no separate type specified for `don't care' (in particular, don't use null). Popt correctly detects attempts to specify numbers that are out of range. E.g., parse() will return false if the user tries to specify an integer argument `200000000000000', as this is too big for an integer. If you need to process large-precision numbers, specify them as Strings, and parse them in the application.

Internationalization

Popt has no support for internationalization, and it does embed some English text. This text only appears in error messages, and in the automatically-generated `help' arguments. All error messages are generated by specific method calls, so if you want a version of Popt that produces error messages in a language other than English, you will need to create a sub-class of it, and override the message generator methods (or hack the source, of course). Here is a list of methods that are relevant here:
protected String getUnmatchedSwitchMessage();
protected String getDuplicateSwitchMessage();
protected String getBadDoubleMessage();
protected String getBadIntegerMessage();
Look in the javadoc comments for details of the circumstances under which these messages will be generated.

If you want `--help', etc., in a different language, simply forego the use of addHelp and add your own arguments in the usual way.

Dashes in arguments, and numeric switches

This is a tricky semantic issue and, no doubt, the strategy used by JavaPopt will not suit all applications. Consider the following command line: myprog -2 Is `-2' a switch called `2', or a negative number? If there is no switch `-2' listed in the specification, does the user mean minus-2, or is it a typing mistake (e.g., should have been -w, for example)? JavaPopt assumes that the minus sign followed by a digit is a number, not a numeric switch, unless the argument numericSwitches was true in the constructor. In the latter case, arguments cannot be negative numbers. This strategy is chosen for its simplicity, and is unambguous, but will not work where a mixture of negative numbers and numeric options can appear on the command line.

Limitations