views:

531

answers:

8

I'm developing a utility class to handle Actions from Java Swing components; I would like to know if there is a way of checking if a given method name (that will be accessed by reflections) exists in compile time, and show a compiler error if not?

--update

Ok, looks like I was not clear, lets talk about the details:

I have a class called TomAction that I use to simplify simple actions declarations in my project. Instead of write something like:

class MyClass {

    public MyClass() {
    Icon icon = null; // putting null to simplify the example
    JButton btn = new JButton(new AbstractAction("Click to go!", icon) {
        public void actionPerformed(ActionEvent ev) {
        try {
            go();
        } catch (Exception e) {
            e.printStackTrace();
            String msg = "Fail to execute 'go' task.";
            JOptionPane.showMessageDialog(null, msg, "Fail", JOptionPane.ERROR_MESSAGE);
        }
        }
    });
    }

    private void go() {
    // do task
    }

}

..I just write:

class MyClass {

    public MyClass() {
    String msg = "Fail to execute 'go' task.";
    Icon icon = null; // putting null to simplify the example
    TomAction act = new TomAction("go", this, "Click to go!", msg, icon);
    JButton btn = new JButton(act);
    }

    private void go() {
    // do task
    }

}

..and the program have the same behaviour.

The problem is that if I type a wrong method name as argument to TomAction, I will see it just in runtime. I would like to see it in compile time. Got it? :)

By the way, this class is working very fine, I just want to do this enhancement.

--update

studing the Annotations approach

+2  A: 

No. There is no way to do that.

Pablo Santa Cruz
Annotations would let you do compile time checking.
Jonathon
+1. Simple as that. Reflections are not compile time for that sort of check. Use interface.
Shervin
He's not asking to use reflection at compile time, he wants to verify that a method name exists associated with some class.
Jonathon
+13  A: 

Sounds as if you'd like to set up a contract that a specific method must be implemented.

In Java you usually do this via an interface:

public interface Frobnicator {
  public void frobnicate();
}

Then in your other code, you simply expect to get an object of that type, this way the compiler will verify that the method exists:

public void frobnicateorize(Frobnicator forb) {
  frob.frobnicate();
}

This way you can even avoid using reflection to call the method.

Edit regarding the update: No, you can't have that kind of code be statically checked by the Java compiler. You might have to write your own tool to do that checking for you.

Joachim Sauer
please, see my update
Tom Brito
maybe something with annotations, as proposed
Tom Brito
+2  A: 

I don't know of a way to do that with any existing software.

However, presuming the method name can be resolved at compile time (in which case it seems unlikely you would use reflection) you could conceivably create an IDE plugin to do it.

Steven Mackenzie
+1  A: 

You could use annotation instead of string literal to denote the invoked action and then use APT to verify that the method exists. APT is invoked automatically by the javac.

http://java.sun.com/javase/6/docs/technotes/tools/windows/javac.html#processing http://java.sun.com/j2se/1.5.0/docs/guide/apt/GettingStarted.html http://java.sun.com/j2se/1.5.0/docs/guide/apt/mirror/overview-summary.html

By taking your example, I would suggest design as follows - you put a binding annotation in code and add the action instantiation code in the annotation processor. The actions in that case need to be class members which is a good practice anyway. Make sure you use resource bundles for icons and text and you'll save a lot of grief in the long run:

class MyClass {
    // the keys should be looked up from resource bundle
    @TomActionBinding("go", "text-key", "msg-key", "icon-key");
    Action act;

    public MyClass() {
       JButton btn = new JButton(act);
    }

    private void go() {
       // do task
    }

}
ddimitrov
Interesting, I will read about and I back here to comment..
Tom Brito
Check also the Better Swing Application Framework. Though not compile time, it throws exception at UI construction time, which is good enough for most cases: http://kenai.com/projects/bsaf/pages/Action_Annotation_HowTo
ddimitrov
A: 

It's possible with reflection:

        Object o = object;
        try {
            Method m = o.getClass().getMethod("methodName");
            m.invoke(o);
        } catch (NoSuchMethodException e) {
            //Method is not available
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
MeLLeR
That will fail at runtime, not at compile time (if the method is not there)
pkaeding
If your method needs arguments, you need to specify the arguments-types in the 'getMethod' call (e.g. o.getClass().getMethod("methodName", Integer.class, String.class)) and in the invoke-method (e.g. m.invoke(o, 5, "Second param"))
MeLLeR
A: 

Use "constants"

The compiler won't complain when passing a string literal as long as it's the proper type.

However, when passing a variable as a parameter, the variable must at least be defined.

You could define some class variable "constants" for each action and suggest that the caller use those. The compiler will give you an error if the variable parameter is undefined.

public class TomAction {
  public static final String GO_ACTION = "go";

  ...
}

To call it, instead of doing this:

TomAction act = new TomAction("go", ...);

Do this:

TomAction act = new TomAction(TomAction.GO_ACTION, ...);

If you try to use an undefined variable:

TomAction act = new TomAction(TomAction.WRONG_ACTION, ...);

You'll get the error:

TestClass.WRONG_CONSTANT cannot be resolved

Use object types

To extend the idea of passing variables, why not pass in objects, and require the object to be the proper type? This way, they're no longer passing String, so they absolutely cannot pass in something that's not defined.

public class ActionType {
  public String action;
}

public class GoAction extends ActionType {
  public GoAction() {
    this.action = "go";
  }
} 

public class TomAction {

  public TomAction(ActionType actionType, ...) {
    // Use actionType.action
    ...
  }
  ...

}

To call it, you do this:

TomAction act = new TomAction(new GoAction(), ...);

Of course this leads us to...

Extend the class

Extend the class, instead of using a parameter.

public class GoAction extends TomAction {
}

Then do this:

TomAction act = new GoAction(...);
Marcus Adams
Again, this can benefit from annotating the constant and using APT to verify there is a method with this name.
ddimitrov
By using constants, the user would need to implement a interface, what this class is trying to avoid.By using object type, I cannot use 2 buttons with 2 different actions.By extending the class, its better use the default AbstractAction.
Tom Brito
I can't see why do you need an interface in order to declare String constants in a class. Can you elaborate please?
ddimitrov
@ddimitrov sorry, you don't need an interface to use the constants, but its still declaring the name of the method as a string passive of rumtime error if the method name is mistyped
Tom Brito
+1  A: 

There is no way to check for a dynamic method at compile time. However, it may be possible to redesign your framework class somewhat to do what want. The only functionality your TomAction class really offers beyond AbstractAction is error handling and the ability to rename the actionPerformed method, at the (high, IMHO) cost of using reflection and losing type safety.

I would implement it this way:

public class TomAction extends AbstractAction {

    public static interface Command {
        // CommandException is implemented elsewhere
        public void execute() throws CommandException;
    }

    private Command cmd;
    // other members omitted for brevity

    public TomAction(Command cmd, String name, String failMsg, Icon icon) {
        super(name, icon);
        this.cmd = cmd;
        this.failMsg = failMsg;
    }

    public void actionPerformed(ActionEvent e) {
        try {
            cmd.execute();
        }
        catch (CommandException e) {
            // handle failure
        }
    }

    // remaining implementation
}

Then to use it:

TomAction.Command goCmd = new TomAction.Command() {
    public void execute() throws CommandException {
        go();
    }
};

TomAction act = new TomAction(goCmd, "Click to go!", msg, icon);
JButton btn = new JButton(act);

It is a bit more verbose than your existing implementation, but it gives you the desired compile-time type safety.

Jason Day
If following this approach, I would declare the derived action class (instead of having anon class and a member variable) and instantiate it at the point of usage. This way the stacktraces read better and you control the access to the action.
ddimitrov
A: 

I think you just need to declare a callback interface to compliment your TomAction class as part of your TomAction "framework" :

interface TACB { // TomActionCallback
    void go();
}

Then your sample usage becomes:

class MyClass implements TACB {

    public MyClass() {
        String msg = "Fail to execure 'go' task.";
        Icon icon = null; // putting null to simplify the example
        TomAction act = new TomAction(this, this, "Click to go!", msg, icon);
        JButton btn = new JButton(act);
    }

    public void go() {
        // do task
    }

}

Alternatively:

class MyClass {

    public MyClass() {
        String msg = "Fail to execure 'go' task.";
        Icon icon = null; // putting null to simplify the example
        TACB tacb1 = new TACB() { public void go() { this.go1() } };
        TomAction act1 = new TomAction(tacb1, this, "Click to go!", msg, icon);
        JButton btn1 = new JButton(act1);
        TACB tacb2 = new TACB() { public void go() { this.go2() } };
        TomAction act2 = new TomAction(tacb2, this, "Click to go!", msg, icon);
        JButton btn2 = new JButton(act2);
    }

    private void go1() {
        // do task
    }

    private void go2() {
        // do task
    }

}

Yes, its a bit more complicated, but that's the cost of the compiler check. If you still don't like this solution, the only thing I can think of is to write your own tool that scans the class files and dissects calls to the TomAction constructor to find the first argument to check for the existence of the correspondingly named method.

Bert F