Introduction

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.

Overview

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.

SubCommandSet definitions

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".

Command Name Subsetting

Implicit Help

Termination

Exhausted argument list

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

Allow empty

By default, the framework expects a non-empty list of commands be given to the application when it is executed. However, if it is valid for no arguments, then setAllowEmpty(true) can be configured for the CommandSet.

Cookbook

  1. Make the application implement the Commandable interface.
  2. Implement the configure() method required by the Commandable interface.
  3. Implement the run() method required by the Runnable interface (which is the parent of the Commandable interface).
  4. Implement the main() method which executes the framework.
  5. If it can be run with no arguments, declare it so.
  6. Define each command.

The First Four Steps

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:

  • The configure method is where the commands are defined.
  • The run method is what gets called once all the commands are parsed and the application is ready to execute.
  • The main method instantiates the class (using the default constructor), and it calls the framework which will then parse the command line and execute the application.

    After these steps, the remaining work is to fill the logic in the configure() method.

Allow an Empty Command List

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

    // ...
}

Configure Each Command

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.

A single command

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).

Defining a command with no arguments

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.

Defining a command with a fixed number of arguments

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.

Defining a command with a variable number arguments

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:

  1. If no arguments are given.
  2. If any of the files does not exist.
Non-commanded arguments

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.)


Project Overview