Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / Java / JavaSE / J2SE5

Create Minimal UI in No Time at all Using Cliché Shell

4.33/5 (3 votes)
28 Jan 2010CPOL4 min read 40.1K   123  
Command-line UI auto-generation to easily make code runnable.

Introduction: The Use Case

We all often need to experiment with code: maybe to learn a new algorithm, or to play with a library to discover its peculiarities. Or, we develop a tool just for ourselves and don't want to spend any time designing complex UI for the software, just make it work as quickly as possible.

The tool described here serves the very purpose: make Java code runnable in no time by providing extremely simple yet powerful UI auto-generation.

I came up with the idea to create this tool when I was working on an administration console for database management of a web app. I had the database class which already had methods for all I need, but there were quite a lot of them, so creating a 'normal' UI would be a difficult task. I decided to create an interactive command-line UI, kind of a shell. To avoid writing a lot of similar code for command-line processing, I went with Reflection-based mapping of methods to commands. Thus, the Cliché Shell was born.

"Hello, World!"

Let's write a method to add two numbers. Since it's Java, you'll need a class and other stuff.

Java
public class HelloWorld {
    public int add(int a, int b) {
        return a + b;
    }
}

Now we want to make the code runnable.

The simplest traditional way is to add a main() method, then convert params[] to int. Not so difficult, but this approach doesn't scale: if there are several methods with varying arguments, you'll have to write a huge switching construct. I hope you understand all the evil.

The Cliché way:

Java
import asg.cliche.ShellFactory;
import asg.cliche.Command;
import java.io.IOException;
public class HelloWorld {
    @Command
    public int add(int a, int b) {
        return a + b;
    }
    public static void main(String[] params) throws IOException {
        ShellFactory.createConsoleShell(“hello”, null, new HelloWorld())
                .commandLoop();
    }
}

Run the code and type add 4 6 at the prompt, 'hello>'. Type exit to quit the shell.

It's simple, and it's scalable: for each new method, you'll need to add just a @Command annotation.

The key limitation to this simplicity is type conversion: if your method requires a type other than String or a primitive (and their Object analogs, like Integer), then you'll need a wrapper method or a Converter. Converters are Cliché Shell extensions, and are discussed later.

The Shell Language

Here are the language basics.

Naming convention is different from that of Java. Namely, commandName becomes command-name by automatic name translation.

Abbreviations of command names consist of first letters of words in the command name, e.g., the default abbreviation for command-name is cn.

If a command method name starts with cmd or cli, the prefix doesn't get included in the command name. E.g., cmdSomeMethod becomes some-method.

Quotation is also different: single and double quotes are equivalent, and there's no escape sequence except for quotes:

  • "a string" is the same as 'a string' and translates to a string
  • 'a "string" 2' is the same as "a ""string"" 2' and translates to "string" 2
  • You can write a" "string, that's the same as a string.

Here are the most important built-in commands:

  • exit is the command to quit the shell.
  • ?list (or ?l) lists all the user-defined commands.
  • ?list-all (or ?la) lists all the available commands, including built-in ones.
  • ?help command-name outputs detailed information on the command (if any).

See ?la for the complete list.

Shell Functionality

Documenting Commands

While default command names are based on method names and are rather descriptive, Java doesn't keep information on method parameter names (if I'm wrong, please let me know). Cliché Shell has support for parameter naming through the @Param annotation. You can also rename the command or add detailed information using the @Command annotation:

Java
@Command(description="Varargs example")
public Integer add(
        @Param(name="numbers", description="some numbers to add")
        Integer... numbers) {
    int result = 0;
    for (int i : numbers) {
        result += i;
    }
    return result;
}

Here, you also see that Cliché supports varargs methods. (Note the use of the Integer class: Cliché supports primitive types such as int, except when you want to override type conversion).

Input Conversion

You can either define converters for new classes, or override the built-in conversion, like in this code:

Java
public static final InputConverter[] CLI_INPUT_CONVERTERS = {
    // You can use Input Converters to support named constants
    new InputConverter() {
        public Integer convertInput(String original, Class toClass) throws Exception {
            if (toClass.equals(Integer.class)) {
                if (original.equals("one")) return 1;
                if (original.equals("two")) return 2;
                if (original.equals("three")) return 3;
            }
            return null;
        }
    }
};

Now, you could write add one two and get 3.

CLI_INPUT_CONVERTERS is a special field which is examined by the Shell in search of input converters.

Three points to consider:

  • Converters in CLI_INPUT_CONVERTERS take precedence over built-in conversion rules.
  • If you don't know what to do with a given type, return null: the Shell will try to find a more appropriate converter.
  • Since it works with objects, you can't convert to primitive types, and thus can't override conversion for primitive types.

Output Conversion

You can also override the way the Shell converts command method results to strings (the default is to call toString()).

Java
public static final OutputConverter[] CLI_OUTPUT_CONVERTERS = {
    new OutputConverter() {
        public Object convertOutput(Object o) {
            if (o.getClass().equals(Integer.class)) {
                int num = (Integer) o;
                if (num == 1) return "one";
                if (num == 2) return "two";
                if (num == 3) return "three";
            }
            return null;
        }
    }
};

Now, add one two returns three. Of course, there are better uses. I once wrote a converter for double[] that displayed a Swing frame with a graph.

And, since there's no need to make the CLI_OUTPUT_CONVERTERS field static final, you can control the conversion with other commands:

Java
private boolean displayResults = false;
@Command(description="Turns on table function display")
public void enableResults() {
    displayResults = true;
}
public OutputConverter[] CLI_OUTPUT_CONVERTERS = {
    new OutputConverter() {
        public Object convertOutput(Object toBeFormatted) {
            if (toBeFormatted instanceof U[]) {
                return displayResults ? toBeFormatted : String.format(
                        "[%d rows skipped; see '?h er']", ((U[])toBeFormatted).length);
            } else {
                return null;
            }
        }
    }
};

There are some other features that I didn't describe here. See the example code, the Shell is very simple and useful!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)