Console

Overview

This project offers a simple API to manipulate command-lines and create command-line interfaces.

Learning by Example

The examples below should prove a thorough enough introduction. For more details, see the javadoc.

Creating a command-line object...

...from a string

// creating a CmdLine from a string        
CmdLine cmd = 
 CmdLine.parse("arg1 arg2 -opt1 -opt2 \"this is opt2\"");

...from a string array

...
public static void main(String[] args){        
  CmdLine cmd = CmdLine.parse(args);
}
...

...programmatically

CmdLine cmd = new CmdLine();
cmd.addArg("arg1")
   .addArg("arg2")
   .addOption("opt1")
   .addOption("opt2", ""\this is opt2\"");

Iterating...

...through options and arguments

CmdLineElement elem;

while(cmd.hasNext()){
  elem = cmd.next();
  System.out.println("name: " + elem.getName());
  if(elem instanceof Option){
    System.out.println(((Option)elem).getValue());
  }
}

...through the arguments only

// the filterArgs() method returns the
// Arg instances (no Option instances)
// in a new CmdLine instance - the 
// original CmdLine instance remains
// intact.

CmdLine args = cmd.filterArgs();
Arg arg;

while(cmd.hasNext()){
  arg = (Arg)cmd.next();
  System.out.println("name: " + arg.getName());
}

// or (to avoid the casting to Arg):

while(cmd.hasNext()){
  arg = cmd.assertNextArg();
  System.out.println("name: " + arg.getName());
}

Resetting to iterate again...

while(cmd.hasNext()){
  arg = (Arg)cmd.next();
  System.out.println("name: " + arg.getName());
}        
cmd.reset();
while(cmd.hasNext()){
  arg = (Arg)cmd.next();
  System.out.println("name: " + arg.getName());
}        
cmd.reset();

Playing with options

Testing that an option exists

// prints 'Test 1' if an option with
// the given name exists AND has a value
// (is of the form: -opt1 optValue)
if(cmd.containsOption("opt1", true){
  System.out.println("Test 1");
}

// prints 'Test 2' if an option with
// the given name exists and whether it 
// has a value or not
// (is of the form: -opt1 optValue
// or -opt1 -opt2 -opt3...)
if(cmd.containsOption("opt1", false){
  System.out.println("Test 2");
}

Asserting

try{
  // Returns the Option object with the
  // given name if it exists AND has a value
  // (is of the form: -opt1 optValue),
  // else throws an exception.
  Option opt1 = cmd.assertOption("opt1", true);
}catch(InputException e){
  System.out.println(e.getMessage());
}

try{
  // Returns the Option object with the
  // given name if it exists, with a a value
  // or not, else throws an exception.
  Option opt1 = cmd.assertOption("opt1", false);
}catch(InputException e){
  System.out.println(e.getMessage());
}

Playing with arguments

Is the next element an argument?

if(cmd.isNextArg()){
  Arg arg = cmd.assertNextArg();
  ...
}
else{
 // do something...
}

// alternative:

try{
  Arg arg = cmd.assertNextArg();
  ...
}catch(InputException e){
  // next element is not an Arg...
  // do something...
}

Test for the presence of required arguments

String[] required = new String[]{"arg1", "arg2", "arg3"};
try{
  // will work
  Arg arg = cmd.assertNextArg(required);
  if(arg1.getName().equals("arg1"){
    //...
  }
  else if(arg1.getName().equals("arg2"){
    //...
  }  
  else {
    //...
  }    
}catch(InputException e){
  // no arg found with name 
  // arg1, arg2 or arg3.
}

Converting...

...to an array

String[] args = cmd.toArray();

...to an array with options at the beginning

String[] args = cmd.toArrayOptionsFirst();

...to an array with options at the end

String[] args = cmd.toArrayOptionsLast();

...to a string

String args = cmd.toString();

Executing Native processes...

Cmd cmd = CmdLine.parse("java -help");
ExecHandle processHandle = cmd.exec();

// you can get a hold of the java.lang.Process object:
Process proc = processHandle.getProcess();

// you can acquire the processes input and error streams:
InputStream in  = processHandle.getInputStream();
InputStream err = processHandle.getErrStream();

// you MUST always call close() on the ExecHandle when finished:

try{
  // do stuff with handle
}finally{
  processHandle.close();
}

// calling close() on one of the streams that you have acquired 
// from the ExecHandle will also close that handle (so if you
// call close() on one of the streams, you need not calling close()
// on the handle.

try{
  // do stuff with stream
}finally{
  in.close();
}

Implementing a Command-Line Interface

The API provides you with a few classes an interfaces that could help you build command-line interfaces. This section describes the steps to follow, in a no-nonsense approach.

Architecture

The architecture separates input and display from logic. Input and display are handled by the "console", logic is performed by "commands" that are parsed from the input. The main classes/interfaces involved are the following:

  1. CommandConsole: waits for user input, and displays output. Creates executable commands from the input.
  2. Command: the object form of the user input - which is parsed by the console; command instances are more then a passive representation of user input: they implement a command's business logic.
  3. CommandFactory: a CommandConsole takes a pluggable CommandFactory to create command instances.
  4. Upon execution, a Command instance is given a Context that encapsulates the "owning" CommandConsole, and the command-line that was entered by the user - in the form of a CmdLine object - (minus the first argument, which is assumed to be the command's name and is handed over to the CommandFactory, which creates Command instances based on the command name).

Flow

  • A CommandConsole is created with a CommandFactory;
  • the start() method is called on the console;
  • the start() method:
    • waits for user input;
    • parses the input to create a CmdLine;
    • takes the first argument from the command-line object (assumed to be the command name) and hands it over to the command factory;
    • the command factory:
      • creates a Command object from the name it receives and returns it...
      • ... or throws a CommandNotFoundException if it does not know about the command.
    • If the factory could not find the command, the console displays a message stating so.
    • Else it calls the command's execute() method, passing to the latter a Context instance.
    • The command performs its operations and outputs the result to the console - accessing the latter through its context
    • .
    • Once a command has been executed, the start() method goes back to wait on user input.

Implementation

Implement the Factory

public class HelloWorldFactory implements CommandFactory{

  /**
   * Constructor for HelloWorldFactory.
   */
  public HelloWorldFactory() {
    super();
  }
  
  public Command getCommandFor(String name) 
    throws CommandNotFoundException {
    
    if(name.equalsIgnoreCase("hello")){
      return new HelloWorldCommand();
    }
    else if(name.equalsIgnoreCase("quit") || 
             name.equalsIgnoreCase("exit")){
      return new QuitCommand();
    }
    else{
      throw new CommandNotFoundException(name);
    }
  }
}

Implement the Console

public class HelloWorldConsole extends CommandConsole{

  /**
   * Constructor for CustomConsole.
   */
  public HelloWorldConsole() {
    super(new HelloWorldFactory());
  }
  
  public static void main(String[] args) {
    HelloWorldConsole console = new HelloWorldConsole();
    console.start();
  }
}

Implement the commands

public class HelloWorldCommand implements Command{

  /**
   * Constructor for HelloWorldCommand.
   */
  public HelloWorldCommand() {
    super();
  }

  /**
   * @see org.sapia.console.Command#execute(Context)
   */
  public void execute(Context ctx) 
              throws AbortException, InputException {
    ctx.getConsole().println("Hello World");
  }

}

The following throws an AbortException, that signals to the console that it should terminate (the console's start() method exits from its loop).

public class QuitCommand implements Command{

  /**
   * Constructor for QuitCommand.
   */
  public QuitCommand() {
    super();
  }
  
  /**
   * @see org.sapia.console.Command#execute(Context)
   */
  public void execute(Context ctx) 
              throws AbortException, InputException {
    throw new AbortException();
  }

}

Trying it Out

  • Start the HelloWorldConsole;
  • type "hello" in the console's window;
  • "hello world" is displayed;
  • type "quit";
  • "Bye..." is displayed and the console terminates.

Where does this "Bye..." come from? A ConsoleListener can be set on the console and is called by the latter when certain events occur. The interface specifies the following methods (described by the javadoc):

public interface ConsoleListener {
  
  /**
   * Called when the console starts (can be implemented to 
   * display
   * welcome message).
   */
  public void onStart(Console cons);

  /**
   * Called when the console cannot interpret a given 
   * command (Can be 
   * implemented to display an error message).
   */  
  public void onCommandNotFound(Console cons, 
                                String commandName);  

  /**
   * Called when the console exits (can be implemented 
   * to display a good-bye message).
   */  
  public void onAbort(Console cons);  

}

By default, the CommandConsole class uses a ConsoleListenerImpl instance - that displays "Bye..." in its onAbort() method. You could implement your own and assign it to the console, through the setCommandListener() method (offered by the CommandConsole class.

By the same token, you could pass a custom context to the command instances (suppose you want to encapsulate other objects in the context). To do so:

  • Create your context class by inheriting from Context;
  • Override the newContext() template method in the CommandConsole class so that it returns instances of your context class.
A Context instance is created for every command object.

Conclusion

We hope this hands-on tutorial/implentation guide as been useful. Use the Console API to manipulate command-lines or to create command-line interfaces/consoles, and dig into the javadoc to explore the various "extension points". Command-line interfaces are a quick, powerful alternative to GUIs and can prove a quick way to implement interaction with your applications.