views:

194

answers:

3

Hello.

I am trying to do a very simple command line library for interactive Java programs. You start the Java program and it prompts you for commands. The syntax is:

> action [object_1, [object_2, [... object_n] ... ]]

for example:

> addUser name "John Doe" age 42

Here action = "addUser", object_1 = "name", object_2 = "John Doe", object_3 = "age", object_4 = "42".

Everything after action is an object (that is, an action uses objects). You can see that the action and the object are simple strings. Object strings can also be converted to numbers if necessary.

My plan is that the user of this command line library would simply create methods (belonging to any suitable Java object) and assign each method to a specific action. The objects in the command line become parameters for the method the user assigns. A suitable class that implements a method for the example above would be:

class Foo {
    public void addUserHandler(
            String param1, String param2, String param3, Integer param4) {
        do.someThing();
    }
}

When a user types a command, the corresponding function assigned by the programmer gets called with the parameters specified in the command line.

I know that Java doesn't have function pointers (like C) or delegates (like C#) and the way to implement callbacks is through an interface, however, I don't see how can I use interfaces in this scenario. The problem I see with interfaces is that they have:

  1. A fixed number of functions to be implemented
  2. The functions to be implemented have a fixed declaration.

The problem with (1) is that the user of my library may decide to implement any number of functions, for as many actions as he wants to support. The problem with (2) is that the user would want to assign functions with descriptive names, like addUserHandler() for "addUSer" action.

If Java had delegates or function pointers I would just create a Map between Strings (representing actions) and delegates (for the actual implementation of the action in the program). I think I can emulate delegates with Reflection, but it is gory and I lose the type safety, as I'd have to use String names for classes and methods. Is there any better way?

Thanks,

+2  A: 

Kudos to you for the sheer awesomeness of this question. You're pretty much up against the limits of what Java can do. (Though, of course, delegating like you describe would be a breeze in any functional language.)

First of all, limitation #2 should not be an issue. As of 1.5, Java no longer restricts you to fixed declarations in methods. You can use an ellipsis to indicate a variable number of arguments, like so

public static double average( double... numbers ) {
    double total = 0.0;
    for (double d : numbers) {
        total += d;
    }
    return total / numbers.length;
}

I'm not entirely sure how to get around limitation #1, but I'm thinking about something with generics and/or anonymous inner classes. I'm guessing here -- I haven't tried doing this myself -- but you could create a map of function names and delegate classes like so:

public interface Callback {...}

public interface AddUserCallBack extends Callback {...}

public class UserImpl<T extends Callback> {
    public T getDelegateRoutine();
    ... 
}

Generics in Java have some hair-pulling frustrations associated with them, primarily due to type erasure. You may need to juggle both interfaces and abstract base classes before you get it to do what you want. But something like this should work for you.

rtperson
-1 for not understanding varargs. Those methods still have a fixed number of arguments - the ellipsis is just syntactic sugar hiding an array. Also, generics would probably have zilch to do with a good solution here.
gustafc
+1  A: 

The reflective solution is not so bad. You can do something like (syntax may not be 100%, Java is not my best language and I've no compiler here):

interface Action {
    public void Apply(object[] args);
}

class AddUser implements Action {
    Type[] argTypes;
    public AddUser() {
        argTypes = {String, String, String, Integer};
    }
    public void Apply(object[] args) {
        // Now interpret args based on the contents of argTypes, and do your thing.
        // The map would look like "adduser" => new AddUser()
        // and could be invoked as map["adduser"].Apply(args)
    }
}

But as rtperson said, you are running up against some fundamental limits of Java. Almost any other modern language would serve you better.

David Seiler
um, i'm not aware of how "some other magic language" would allow you to write this code in a way that is type safe at compilation time? you have a command loop dealing with strings invoking some methods w/ unknown param types. _something_ has to do the translation at runtime, be it your code or autogenerated code.
james
Quite right; this problem must be solved at runtime. In a language with easy-to-use reflection, such as Ruby, the runtime code is easy to write and read. In a language with good static macros, like D, the generated conversions could be kept from annoying the user. In Java, you're stuck.
David Seiler
+5  A: 

If you want the user to get automagic type translation (e.g. from strings to ints), then reflection is the only way. if you make the user do the work, then you don't need reflection (and you get type safety):

your command interface:

public interface MyCommand {
  public void execute(String[] args);
}

an implementation:

public class MyObj {
  public void doSomething(String param1, Integer param2) {
  }

  private void register() {
    mainApp.registerCommand("doSomething", new MyCommand() {
      @Override public void execute(String[] args) {
        doSomething(args[0], Integer.parseInt(args[1]));
      }});
  }
}
james
Doesn't doSomething() need to be static in this case?
Chris Kaminski
No, 'cause the instance of the anonymous class has an implicit pointer to the enclosing `MyObj` instance.
gustafc
Also, +1 @james for posting the "correct" solution here.
gustafc
Hi, check out my response on your comment. Question "bufferedreader reads more than one line": http://stackoverflow.com/questions/1834362/java-bufferedreader-reads-more-than-a-line
Martijn Courteaux