Using java enum for command dispatching
Posted by Jim Morris on Sun Apr 12 01:26:06 -0700 2009
I haven't blogged much about Java even though it is my primary programming language. Since I have been doing a lot of Java recently I thought I'd post something about some of the Java idioms I've used over the years.
One I have been using a lot recently is a command dispatcher where the command is text.
In some languages you would probably use a switch statement with the command string being the case statement. However Java does not allow strings as the match part of a case.
A common idiom seen is to use enums (in Java 5 and greater), as they are constants and can be used in a switch statement.
Now enums are very powerful constructs in Java, they are not simply convenient #define work-arounds, although they are commonly used that way...
See this for all the info on enums.
However you can do so much more with enums...
I use them as a super duper switch statement to dispatch text commands, and the cool thing is they are pretty efficient as they use a hash table lookup to do the dispatch. Here's an example of a command line app that uses the java Gnu GetOpt package with long option names that dispatches different commands that can be typed on the command line...
> doit --froboz one two three
> doit --bazznot glozny frobod
where --froboz is a command that takes three parameters, you get the idea.
Ok that looks like a lot of code for a command lookup/dispatcher, but the nice thing about it is it is fairly DRY, to add a new command you simply add another enum term, the rest is taken care of automatically.
The command is looked up in the switches default clause using the
valueOf
method of an enum which basically finds the enum that
matches the given string.
It can be run thusly...
> java -cp ./java-getopt.jar:./classes com.e4net.Test --bazznot 1 2
In bazznot with: 1, 2
> java -cp ./java-getopt.jar:./classes com.e4net.Test --froboz 1 2 3
In froboz with: 1, 2, 3
> java -cp ./java-getopt.jar:./classes com.e4net.Test --blahblah 1 2 3
myprog: unrecognized option '--blahblah'
Usage: myapp [-x filename] [--command] [args...]
where commands is one of:
froboz param1 param2 param3
bazznot param1 param2
A variation on this theme is if say you have an Internet server, that
receives text commands over a socket, we want to lookup the command we
received and dispatch the same way as we do above. This turns out to
be easy, we use the valueOf
method of an enum. Below we call
handle() with the command we got over the wire, and it is neatly
dispatched. valueOf()
is the command that will do the
lookup in a hash table, so is quite efficient if the command list is
long.
The code for this article can be downloaded from here
If you don't like coding your doit() methods inline within the enum, or want to use the same method for multiple commands, then you can declare an abstract class wrapping an abstract method, extend it to create specific classes with specific method implementations, then pass an instance of a wrapper class in your enum constructor. Something like this:
private static abstract class doitMethodWrapper {
abstract boolean doit(int off, String [] argv);
}
private static class frozbozMethodWrapper extends doitMethodWrapper {
boolean doit(int off, String [] argv){
// do whatever froboz does with three arguments
System.out.println("In froboz with: " + argv[off] + ", " + argv[off+1] + ", " + argv[off+2]);
return true;
}
}
private static final frozbozMethodWrapper frozbozMethodWrapperInst = new frozbozMethodWrapper();
private enum Commands {
frozboz('f', 3, frozbozMethodWrapperInst),
bazznot('b', 2, frozbozMethodWrapperInst);
private final int code, size;
private doitMethodWrapper methodWrapperInst;
Commands(int code, int size, doitMethodWrapper methodWrapperInst){
this.code= code;
this.size= size;
this.methodWrapperInst = methodWrapperInst;
}
public int code() { return code; }
public int size() { return size; }
public doitMethodWrapper methodWrapperInst() { return methodWrapperInst; }
};
// then instead of invoking by
// cm.doit(g.getOptind(), args);
// use
// cm.methodWrapperInst().doit(g.getOptind(), args);
tx tipster thats a good idea