Plan B
Putting type tokens to use
I recently found the need to write a (quasi) command object that's an abstraction of a CLI command. Roughly, it looks like:
public abstract class CLICommand{
//Some implementations go here...
protected abstract String getCLIString();
protected abstract Map getCmdParams();
}
Now you might ask, why not simply have an execute method as in:
public abstract class CLICommand{
//Default implementations go here.
public Object execute();
}
(Or, to further refine my version, why not have a Command interface and an abstract base class providing default implementations of some methods? Agreed, but that's not the point of our discussion now. Moving on...)
The primary reason for not having a conventional command object is circumstantial: in our case, the command objects themselves do not have all the information required to execute the command. In particular, they will not have the authentication information needed to (remote-)execute the command. All they can do is to provide command specific parameters, string representation and such details. Due to legacy reasons the security related information is kept in a ThreadLocal field within the command executor. (Otherwise, the security related information could be stored in a "secure" class could wrap around a bare-bones command and could remain transparent to the executor. Anyways...)
Subclass of the classic Command object might return different types depending on the command executed. So, we'd generify the command object as in:
public abstract class CLICommand<T>{
//Other impl details
public T execute(Map params, ...);
}
But, how would you apply this to the pseudo command object we're dealing with?
Enter type tokens. In Java SE 5, the class Class was generified (with additional type parameters named type tokens). For example, the type of String.class is Class<String> (Of course, Class' methods were further generified in Java SE 6)
So, here's what our neo-Command class would look like:
public abstract class CLICommand<T>{
//Some implementations go here...
protected abstract String getCLIString();
protected abstract Map getCmdParams();
protected abstract T getReturnType();
}
And how would a command executor use this?
public final class CommandExecutor{
public static <T> T executeCommand(CommandIF<T> cmd){
//Relies on a legacy non-generic executor that declares
//return type object, but internally returns a specific type
Object result = oldExecutor.execute(cmd.getCmdString,
cmd.getParams());
return cmd.getReturnType().cast(result);
}
}
And voila! We're done! If you're curious to know how you'd handle a command that returns nothing, here's an implementation of such a command:
public final VoidCommand extends CLICommand<Void>{
public Class<Void> getReturnType() {
return Void.class;
}
//Other details...
}
Update: To be absolutely sure all kinds of type parameters (including parameterized types) can be returned and used, you'd want to use Neal Gafter's super type tokens.
And as if all these considerations weren't enough for the day, a related piece of code that was compiling in Java SE 5 fell apart in 6. The reason: a rather presumptuous, short sighted cast that I had introduced:
Map<String, String> props =
(Map<String, String>) propertiesObject;
It was merrily compiling in Java SE 5 thanks to this compiler bug. Sigh.
Posted at 01:49PM Jan 16, 2007 by Bharath Ravikumar in Java |