Package com.townleyenterprises.command

Provides classes to assist in easily creating command-line Java applications.

See:
          Description

Interface Summary
CommandListener This interface must be implemented by classes that intend to respond to command-line arguments processed by the CommandParser.
 

Class Summary
AbstractCommandListener This class is intended to more easily support the implementation of command-line argument handler classes by providing an empty optionMatched method.
CommandOption This class provides support for defining command-line arguments.
CommandParser This class provides support for parsing command-line arguments.
DefaultCommandListener This class allows boilerplate nested classes to be elimitated by providing all of the things supplied by the CommandListener interface as arguments to the constructor.
DelimitedCommandOption This class provides support for multi-valued options which are specified using delmited values.
JoinedCommandOption This class provides support for "joined" command options.
MutexOptionConstraint This class provides an implementation of a mutual exclusion constraint for two options.
OptionConstraint This is the base class for all option constraints.
PosixCommandOption This class provides support for POSIX-compliant command options.
RepeatableCommandOption This class provides the basic support for repeatable command line arguments.
RequiredOptionConstraint This class provides an implementation of a constraint which requires the specific option to be matched.
RequiresAnyOptionConstraint This class provides an implementation of a dependency constraint between options.
 

Package com.townleyenterprises.command Description

Provides classes to assist in easily creating command-line Java applications. The implementation is roughly based on the Red Hat popt library, but is not intended to be compatible with this API.

Usage | Limitations

Usage

There are basically two ways to use this package. The first approach is probably most familiar to users of the Red Hat popt packages and C programmers used to processing command line arguments in general, while the second approach applies Object Oriented Programming principles to handling the arguments. With either approach, there is complete support for accessing additional or "left over" arguments which are supplied on the command line but which do not explicitly apply to a single option.

The Classic Model

With this usage model, the application wishing to respond to command line arguments simply defines instances of the arguments it wishes to recognize and then registers itself as a CommandListener with the appropriate CommandParser instance. After this is done, most of the work of dealing with the application takes place in the CommandListener's implementation of optionMatched method. While there is nothing wrong with this approach (it will be very familiar to anyone who has used the popt or similar libraries), it can easily result in a very large if statement if the application supports many long options or many case's of a switch statement if primarily short options are used.

	import com.townleyenterprises.command.*;

	public class cltest implements CommandListener
	{
		static  CommandOption[] opts = {
			new CommandOption("foo", 'f',
					false, null, "isn't this foo?"),
			new CommandOption("bar", 'b',
					true, "<name>", "specifies the bar"),
			new CommandOption("only-long", (char)0,
					true, "<opt>",
					"this option only has a long form")
		};

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

		public void optionMatched(CommandOption opt, String arg)
		{
			switch(opt.getShortName().charValue())
			{
				case 'f':
					System.out.println("matched f");
					break;
				case 'b':
					System.out.println("matched bar:  " + arg);
					break;
			}
		}

		public String getName()
		{
			return "cltest options";
		}

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

			String[] largs = clp.getUnhandledArguments();
			for(int i = 0; i < largs.length; ++i)
			{
				System.out.println("largs[" + i + "] = '" +
					largs[i] + "'");
			}
		}
	}
Example 1: Classic Command Parsing

If the command-line parsing needs of an application are modest, this approach is easy to understand and straightforward to implement quickly. However, applications which have more complex command-line argument handling needs might be better off using the object-oriented approach.

The Object-Oriented Model

Since this library does not intend to be compatible with existing command-line parsing libraries and since Java is an object-oriented language, it makes sense to also provide an OO approach to the command-line argument problem. Rather than separating the behavior of what should happen when each option is matched from the option itself, the normal operations of detecting if the option has been matched and capturing the option argument is encapsulated into the CommandOption class.

The ComandParser calls the optionMatched method of each option it matches before it notifies all of the registered listeners. In the default case, the method simply sets a boolean value to indicate if the option has been matched and captures the argument for later retrieval. If the option is declared as a named object rather than as an anonymous object in the CommandObject array returned from the listener, the application can check if the option has been matched by simply asking the option. There is no need to provide any code in the listener's optionMatched method. This approach is illustrated in Example 2.

	import com.townleyenterprises.command.*;

	public class cltest implements CommandListener
	{
		static CommandOption doa = new CommandOption("aop",
				'a', false, null,
				"The 'a' operation should be performed");
		static CommandOption dob = new CommandOption("bop",
				'b', false, null,
				"The 'b' operation should be performed");
		static CommandOption doc = new CommandOption("cop",
				'c', false, null,
				"The 'c' operation should be performed");

		static	CommandOption[] opts = { doa, dob, doc };

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

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

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

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

			if(doa.getMatched())
			{
				System.out.println("Supposed to do a");
			}

			if(dob.getMatched())
			{
				System.out.println("Supposed to do b");
			}

			if(doc.getMatched())
			{
				System.out.println("Supposed to do c");
			}

			String[] largs = clp.getUnhandledArguments();
			for(int i = 0; i < largs.length; ++i)
			{
				System.out.println("largs[" + i + "] = '" +
					largs[i] + "'");
			}
		}
	}
Example 2: OO Command Parsing Take 1

The above example might not seem to be a large improvement over the Classic Mode at first glance. The main advantage in this case is that the application is going to do something more complicated than print some text if any of the options, or any combination of the options, have been matched by the command parser. Using the Classic Mode or in some other command-line parsing libraries, boolean values would have to be set in the listener's optionMatched method. This approach partially approximates the flag concept present in the popt library.

A variation on Example 2 may be used by applications which want to perform all argument parsing and then perform the requested actions rather than potentially performing only some of the requested commands. This approach also does not require the listener to implement a body to the optionMatched method. Each argument must be named and is responsible for capturing the arguments for later use. This technique can be implemented simply by making minor modifications to Example 2 as shown below:

	import com.townleyenterprises.command.*;

	public class cltest implements CommandListener
	{
		static CommandOption doa = new CommandOption("aop",
				'a', true, "<arg>",
				"The 'a' operation should be performed");
		static CommandOption dob = new CommandOption("bop",
				'b', true, "<arg>",
				"The 'b' operation should be performed");
		static CommandOption doc = new CommandOption("cop",
				'c', false, null,
				"The 'c' operation should be performed");

		static	CommandOption[] opts = { doa, dob, doc };

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

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

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

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

			if(doa.getMatched())
			{
				System.out.println("Supposed to do a using '"
					+ doa.getArg() + "'");
			}

			if(dob.getMatched())
			{
				System.out.println("Supposed to do b using '"
					+ dob.getArg() + "'");
			}

			if(doc.getMatched())
			{
				System.out.println("Supposed to do c");
			}

			String[] largs = clp.getUnhandledArguments();
			for(int i = 0; i < largs.length; ++i)
			{
				System.out.println("largs[" + i + "] = '" +
					largs[i] + "'");
			}
		}
	}
Example 3: OO Command Parsing Take 2

The facilities described above should handle most of the command-line argument parsing needs of an application in a more efficient manner than is often required when using a non-OO approach. The specific parameters and switches which are often created within the application to determine if arguments have been matched have been replaced with allowing the options to track themselves, thus removing a lot of tedious programming overhead from the application developer.

However, to handle the remainder of the possible needs of command line option parsing, it is possible to provide additional behavior by deriving a new class from the CommandOption class and implementing additional behavior in the optionMatched method. The most obvious examples of this type of need would be:

As an example, take a hypothetical Java program which works on directories and files. The program can optionally exclude any files or directories in the list of files to process by using the --exclude option. If the Classic Model was used, the specific logic to handle multiple instances of the option would be embedded in the CommandListener. However, the library provides a clean way to implement this feature in a reusable way. Example 4 illustrates a way to implement this functionality.

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

	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();
	}


	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";
		}

		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());
				}
			}
		}
	}
Example 4: Putting it All Together

The above code will correctly process the following command line:

	$ java feather -X /tmp -X /var -c -f out.feather /

and print the following output:

	largs[0] = '/'
	Excluded:
	/tmp
	/var

The library is designed to make parsing command lines easy and to eliminate the tedious nature of doing so, much in the same way that the javax.swing.Action class streamlined the handling of GUI events. However, the application developer ultimately is the one making the decision about how the library best fits in with the way they want do develop the application. Both the Classic Model and the OO Model have been used in production code and both work equally well. The main difference is in how much extra, repetative code must be written to perform the same tasks.

Limitations

At the present time, the following limitations/issues are present in the package:



Copyright © 2002-2004, Andrew S. Townley and Townley Enterprises. All Rights Reserved.
This project is hosted on http://te-code.sourceforge.net.