The standard entry point to a Java application is the 'main()' method of a principle class. This takes an argument list that is an array of Strings. What is often needed, however, is a robust command language control the application. This is what the Java Command Line Control Framework intends to provide.
One solution to this problem is the Apache Commons CLI, which takes the approach of parsing the arguments into a structured object which can then be queried.
This framework takes a different approach. It instead allows the binding of commands to anonymous inner classes which perform the action for the command, which include processing of subsequent arguments to that command.
It is powerful enough to allow the nesting of sub-commands within commands.
The top-level obejct is CommandSet. Create a new CommandSet in the main entry point class.
To the CommandSet, add a series of Commands. A Command is composed of an actor, and a sequence of arguments. The actor is the name of the Command. All Commands in a set must have unique names.
For example:
delete <filename>
Here the CommandSet is the implicit container (of one command). The command has the name "delete" and it takes a single argument.
Command is an abstract class, and each definition must define how to process its arguments by implementing the method [boolean process(CommandArgs pArgs)]. This method returns whether the arguments were successfully parsed. If false is returned, the execution of the CommandSet is aborted.
Referencing the example above, the 'process' method would need to assert that the argument list contains one element, and return an error if it did not.
The framework is structured so that any error that is encountered is reported in a descriptive manner.
When it is necessary to define muliple levels of commands, it is possible to add SubCommandSet instances to the top-level CommandSet. These subsets can themselves contain either SubCommandSet or Command instances.
For example:
list delete file <name> delete directory <name>
The top-level CommandSet contains two elements; the "list" command and the "delete" SubCommandSet. The "delete" set then contains two Command defintions: "file" and "directory".
If an argument list has been fully processed and no further commands are expected, then the return value of the 'process' method of the Command should return the value from the 'exhausted' method of the CommandList. This asserts that no further opererations are specified on the command line, returning 'false' if there are any.
This is useful when the commands for the application are mutually exclusive and should not be combined.
This is not to be used if multiple command chaining is valid. For example, this may be a valid command execution:
create file1 delete file2 list
The example below demonstrates the implementation of the first four steps. This is the minimal amount of code that compiles, although it does nothing yet.
class MyApplication implements Commandable { public void configure(CommandSet pCommandSet) { // ... } public void run() { // ... } public static void main(String[] args) { CommandSet.execute(new MyApplication(), args); } }
There are several points to note:
After these steps, the remaining work is to fill the logic in the configure() method.
By default, the framework expects at least one command is passed to the application. If no arguments are passed, then it prints the usage string and terminates.
However, some applications may want to allow execution with no arguments. This declared simply in the configure method:
public void configure(CommandSet pCommandSet) { pCommandSet.setAllowEmpty(true); // ... }
The single argument passed to the configure() method is a CommandSet which represent the top-level set of commands. This set is to be populated by calling the define method. Members of this set are either a concrete command, or a subset of commands.
To define a concrete command, a new Command is created and passed. Note that this is an abstract class for which a process() method needs to be define.
Note that the process() method returns a boolean value. This indicates whether the command has been successfully processed or not. If false is returned then the framework aborts, prints an error message, and the application is not executed (the run() method does not get called).
If no arguments are processed after the command, then definition of the process() method is relatively simple.
Consider this example where the "quiet" flag sets a boolean value on the application.
pCommandSet.define(new Command("quiet") { public boolean process(CommandArgs pCommandArgs) { setQuiet(true); return true; } });
Because no arguments are expected, the passed CommandArgs is unused. Because there is no way to incorrectly parse the command, true is always returned.
Use pCommandArgs.hasNext() before explicit access of each.
pCommandSet.define(new Command("concatenate", "<string1> <string2>") { public boolean process(CommandArgs pCommandArgs) { boolean success = false; if (pCommandArgs.hasNext()) { String first = pCommandArgs.next(); if (pCommandArgs.hasNext()) { setResult(first + pCommandArgs.next()); success = true; } } return success; } });
This will only be parsed successfully if exactly two arguments are given.
Use pCommandArgs.hasNext() in a while statement to process each.
Important: when using a variable size argument list, the command and arguments must be the last ones specified. If a different command is given afterward, it will be interpreted as an additional argument.
Consider this example where the "file" command is declared and it takes any number of file names as arguments. Each name is converted to a File object and added to a running list of files:
pCommandSet.define(new Command("files", "<file 1> ... <file N>") { public boolean process(CommandArgs pCommandArgs) { boolean success = false; if (pCommandArgs.hasNext()) { success = true; while (pCommandArgs.hasNext()) { File file = new File(pCommandArgs.next()); if (file.exists()) { getFiles().add(file); } else { LOG.error("Invalid file [" + file + "]."); success = false; } } } return success; } });
Notice that false is returned and an error is reported in two cases:
Everything passed on the command line is expected to be either a command or an argument to a command. There are some occasions where it may be desirable to pass arguments directly to the application, but this framework does not support that. The only way to handle this is to introduce a command to process the arguments. (Basically this means there is no way to use an "implicit" command.)