My name is Vladimir Vivien software engineer in the Tampa Bay area (US). I run the Tampa Java User Group (Tampa, FL) and have a wide range of technology interests including Java, Groovy/Grails, JavaFx, SunSPOT, BugLabs, component-based design, API design, and general ubiquity of the Java platform. Vladimir has posted 8 posts at DZone. View Full User Profile

Clamshell-Cli: A Framework for Creating Console-Based Shell Applications in Java

03.26.2012
| 4319 views |
  • submit to reddit

Sometimes, you try to solve one problem and end up creating a solution for something completely different.  Such is the story of Clamshell-Cli.  On several occasions I came across the need of a purely console-based JMX shell.  Since I did some work in JMX before, I decided to create one that was usable and basically provided some of the more useful features of JConsole.  After looking around for a framework to build comand-line shells-based apps, it became clear that I would have to create one (yes, I considered OSGi and runtime implementations such as Felix,  but OSGi comes with its own set of constraints that solve problems other than what I wanted to solve).

So I created my own framework to build command-line shell tools. My requirements were simple: create a flexible, component-based, highly-extensible framework that would not get in developers' way.  The API had to be light, easy to learn, and the runtime had to be super simple to use.

Enter: Clamshell-Cli –  http://code.google.com/p/clamshell-cli/

Clamshell-Cli Features


  • Easy to get started
  • Small API footprint with low learning curve
  • Ability to build complex CLI tools such as REPL using plugin architecture
  • Simple component model that imposes little constraints on your design
  • The plugin architecture is designed for extensibility and feature-scalability:
    • If you don’t like how the default implementation works, you can change it completely
    • Implement the components you want to change and your feature will be included next time the console is restarted
  • Each extension point is mapped to a Java type for easy implementation
  • Plugins are deployed as simple jar files
  • Support for input hints (tab-press at the console)
  • Support for input buffer history

The Design

The Clamshell-Cli API is kept simple on purpose.  It uses a plugin paradigm based on Java’s ServiceLoader API.  The idea is not to make Clamshell-Cli a bloated piece of software trying to handle everything, but rather provide an extensible platform that lets developers build console-based tools by implementing pieces of functionality via plugins.

All major aspects of a working command-line shell are represented by statically defined interfaces. For instance, if you want to change the console prompt, you simply implement the Prompt interface to return the prompt you want displayed.  The Clamshell-Cli interfaces include :

  • SplashScreen - interface to render the first splash screen of console-app
  • ConsoleIO - interface to handle input and output streams
  • Prompt - interface to provide command prompt for the shell tool
  • InputController - Interface to handle text input at command prompt
  • Command - interface to handle action to be taken based on command input

The Default Runtime

When you download the default runtime, you get a basic shell environment with the following directory structure:

  • cli.config - Clamshell-Cli configuration file
  • cli.jar - the launcher jar file
  • clilib - lib files to boot Clamshell-Cli
  • lib - place your dependency jars here
  • plugins - location for Clamshell-Cli plugin jars

When you start the default runtime, you immediately encounter the SplashScreen plugin and the Pormpt plugin:

Clamshell-Cli Default Splash Screen 

The defaul runtime implements an input controller that interprets the input one line at time.  It assumes that the first word of the input is the command.   The controller then delegates the handling of the command to one of the registered Command instances.  For instance, if you type ‘help’ at the prompt, the InputController a) locates the Command mapped to command word ‘help’, then delegate handling of the command to the HelpCmd plugin:

prompt> help
Available Commands
------------------
      exit       Exits ClamShell.
      help       Displays help information for available commands.
   sysinfo       Displays current JVM runtime information.
      time       Prints current date/time
prompt> _  

Using the API

To extend the default runtime and add your own command,  a developer would simply provide a plugin that implements the Command interface.  Package the class as jar that obeys the configuration rules of a Service Loader / Service Provider (see here), then place the jar in the plugins directory.  Next time the Clamshell-Cli console is restarted, the command would be available.  The following code shows how simple it is to create your own command using the Command interface. Class TimeCmd, shown below, implements the ‘time’ command as part of the default runtime:

public class TimeCmd implements Command {
    private static final String NAMESPACE = "syscmd";
    private static final String ACTION_NAME = "time";

    @Override
    public Object execute(Context ctx) {
        IOConsole console = ctx.getIoConsole();
        console.writeOutput(String.format("%n%s%n%n",new Date().toString()));
        return null;
    }

    @Override
    public void plug(Context plug) {
        // no load-time setup needed
    }
    
    @Override
    public Command.Descriptor getDescriptor(){
        return new Command.Descriptor() {
            @Override public String getNamespace() {return NAMESPACE;}
            
            @Override
            public String getName() {
                return ACTION_NAME;
            }

            @Override
            public String getDescription() {
               return "Prints current date/time";
            }

            @Override
            public String getUsage() {
                return "Type 'time'";
            }

            @Override
            public Map<String, String> getArguments() {
                return Collections.emptyMap();
            }
        };
    }
} 

A quick explanation of the code is in order:

  • Method execute() - invoked by the input controller instance when it detects the String time from the command-line. The method retrieves the IOConsole from the context object and use it to print the time. It returns null to the controller (indicating the command did not generate a result).
  • Method plug() - a lifecycle method that is invoked by the framework when the command is first initialized. For our example, there nothing to do.
  • Method getDescriptor() - returns an instance of interface Command.Descriptor which is used to describe the features and document the Command. For our example, the Descriptor interface is implemented anonymously with the following methods:
    • Method Descriptor.getNamespace() - returns a string identifying the command’s namespace. This value can be used by input controllers to avoid command name collisions.
    • Method Descriptor.getName() - returns the string mapped to this command object. In our implementation, it returns “time”.
    • Method Descriptor.getUsage() - intended to provide a descriptive way of using the command.
    • Method Descriptor.getArguments() - returns a Map containing the description for each arguments that may be attached to the command. This example uses none.

Clamshell-Cli Examples

The best way to learn how to use the Clamshell-Cli API is to download the source code and look at how the plugins are implemented. You can also check out:

References

http://code.google.com/p/clamshell-cli/ - Clamshell-Cli Home

http://code.google.com/p/clamshell-cli/wiki/CreatingCommandPlugin - How to create a Command plugin

http://code.google.com/p/clamshell-cli/wiki/DeployPlugins - How to deploy a Plugin

https://github.com/vladimirvivien/jmx-cli - A JMX command-line tool (yes I did build it) – built using Clamshell-Cli (discussed in future post)

http://docs.oracle.com/javase/6/docs/api/java/util/ServiceLoader.html - Documentation on ServiceLoader API

http://java.sun.com/developer/technicalArticles/javase/extensible/ - Article on creating extensible application using Java

0
Published at DZone with permission of its author, Vladimir Vivien.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)