te-code logo

Source code for the real world™ Java | .NET

Java Command-line Argument Handling

Andrew S. Townley 08-Jun-2003
This is the original article provided with the 2.0-Beta2 release. It is still relevant for background information and how to do "classic" command-line argument handling, but it does not reflect the current way the library should be used. Please bear this in mind when reading the article. [Ed.]

This example is lifted from the documentation for the com.townleyenterprises.command package. The example illustrates the majority of the features present in the library including:

  • Support for both long and short option matching
  • Support for automatically generated help and usage (a la Red Hat's popt library)
  • Support for matching arguments more than once on the same command line
  • Providing customized handling of arguments when they are matched

Step 1: Import the required classes (normally, you wouldn't just import everything).

import java.util.*;
import com.townleyenterprises.command.*;

Step 2: Define a CommandOption class that will override the default action when the option is matched. In this case, the class supports options that may be specified more than once on the command line.

It is important to note that for the custom option to have the expected behavior later in the program, the parent class must be given the chance to recognize that the option has been matched. This is done by calling super.optionMatched(arg) in the example.

This step (or something similar) is only required if the default behavior of the CommandOption class is not sufficient for the needs of your application. In this case, the application requires supporting the same option more than once on the command line.

class RepeatableOption extends CommandOption
{
	RepeatableOption(String longName, char shortName,
			String argHelp, String argDesc)
	{
		super(longName, shortName, true, argHelp, argDesc);
	}

	public void optionMatched(String arg)
	{
		super.optionMatched(arg);
		_args.add(arg);
	}

	List getArgs()
	{
		return _args;
	}

	ArrayList	_args = new ArrayList();
}

Step 3: Provide a class that implements the CommandListener interface. This class will be responsible for providing all of the options to the CommandParser. In the event where derived classes wish to inherit a common set of options, more than one listener can be registered with the command parser. Each listener provides a specific set of options which may be matched and displayed in the automatically generated help and usage text.

In this case, three options are defined (all of which have both short and long forms). The parameters for creating the options are very similar to the Red Hat popt library which was a big influence on the behavior of the package. They include:

  1. the long option name
  2. the optional short option
  3. if the parameter expects an argument
  4. the help description for the argument
  5. the help description for the whole option

The options could be created as anonymous object instances, but then it would require additional programmer effort to ensure that they were correctly handled. For more information on this topic, please refer to the package documentation.

public class feather implements CommandListener
{
	static CommandOption create = new CommandOption("create", 'c',
			false, null, "Create a new archive.");
	static CommandOption file = new CommandOption("file", 'f',
			true, "<filename>",
			"Specify the name of the archive"
				+ " (default is stdout).");
	static RepeatableOption xclude = new RepeatableOption("exclude",
			'X', "[ <filename> | <directory> ]",
			"Exclude the named file or directory"
				+ " from the archive");
	
	static	CommandOption[] opts = { create, file, xclude };

	public CommandOption[] getOptions()
	{
		return opts;
	}

	public void optionMatched(CommandOption opt, String arg)
	{
		// nothing to do
	}

	public String getDescription()
	{
		return "feather options";
	}
}

Step 4: Parse the options. The main difference between using the package in an object-oriented manner rather than using the "classic" approach for command line parsing is the lack of large conditional blocks to determine if the option has been matched. Using the com.townleyenterprises.command package allows the the framework to take care of 90% of the work of parsing command line options on behalf of the developer. This means that the application is smaller, easier to change and logic for parameter handling can be shared across utilities in the same project or between totally different projects without resorting to "cut-n-paste" code reuse.

Rather than making the programmer track the options and the arguments, these are managed internally. Each CommandOption instance contains a boolean to indicate if it has been matched, and, if it was expecting an argument, the argument which was supplied. This mechanism can result in the elimination of nearly 2/3 of the code which is often required to perform the same command line parsing.

public static void main(String[] args)
{
	CommandParser clp = new CommandParser("feather", "FILE...");
	clp.addCommandListener(new feather());
	clp.parse(args);

	if((file.getMatched() || xclude.getMatched()) &&
			!create.getMatched())
	{
		System.err.println("error:  nothing to do");
		clp.usage();
		System.exit(-1);
	}

	String[] largs = clp.getUnhandledArguments();
	if(create.getMatched() && largs.length == 0)
	{
		System.err.println("error:  refusing to create empty archive.");
		clp.usage();
		System.exit(-2);
	}
	
	for(int i = 0; i < largs.length; ++i)
	{
		System.out.println("largs[" + i + "] = '" +
			largs[i] + "'");
	}

	if(xclude.getMatched())
	{
		System.out.println("Excluded:");

		List xas = xclude.getArgs();
		for(Iterator j = xas.iterator(); j.hasNext();)
		{
			System.out.println(j.next());
		}
	}
}

Step 5: Run the program. By default, the CommandParser has the equivalent to the popt autohelp feature enabled. This results in the generation of output which is nearly equivalent to that of the Red Hat popt library.

To print the long help message for the application, the --help and -? options are automatically recognized.

$ java feather --help
Usage:  feather [OPTION...] FILE...

feather options:
  -c, --create                     Create a new archive.
  -f, --file=<filename>            Specify the name of the archive (default is
                                   stdout).
  -X, --exclude=[ <filename> | <directory> ] Exclude the named file or directory
                                   from the archive

Help options:
  -?, --help                       show this help message
  --usage                          show brief usage message

In a similar manner, specifying the --usage option will produce summary information about the options.

$ java feather --usage
Usage:  feather [-c|--create] [-f|--file=<filename>]
        [-X|--exclude=[ <filename> | <directory> ]] [-?|--help] [--usage]
        FILE...

Finally, give the program something useful to do and prove that it really recognizes all of the command-line options.

$ java feather -X /tmp -X /var -c -f out.feather /
largs[0] = '/'
Excluded:
/tmp
/var

That's all there is to it. The core classes allow the developer to focus on what the utility is supposed to accomplish, not spend a lot of time writing mindless, boilerplate code.

This library owes a great deal to Red Hat's popt parsing library. I used popt quite a bit in the past for C and C++ command line parsing, but, while I thought it was easier than some, there were still some things which I thought were harder than they should be. Of course, popt was written for C and not C++ or Java and so can't be faulted for taking a procedural approach. None of the existing libraries for solving the problem in Java quite seemed "natural", for want of a better word, so this package is the result of me trying to build a better mousetrap. Hopefully, this example points out some of the advantages of this package over some of the other ways of solving the problem.

The complete source code for the above example is included in the examples directory of both the source and binary distributions of the package.