Click here to Skip to main content
Click here to Skip to main content

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

By , 28 Jan 2010
 

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.

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:

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:

@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:

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

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:

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)

About the Author

Anton Grigoryev
Russian Federation Russian Federation
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
Hint: For improved responsiveness ensure Javascript is enabled and choose 'Normal' from the Layout dropdown and hit 'Update'.
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralThanks and maybe some help about parameter namesmemberPablo Grisafi25 Mar '10 - 6:36 
Generaljust a quick thank youmemberalexbtr2922 Feb '10 - 5:00 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web01 | 2.6.130516.1 | Last Updated 28 Jan 2010
Article Copyright 2010 by Anton Grigoryev
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid