Skip to main content

Strongly Typed Java Delegates

Posted by alexwinston on April 11, 2005 at 2:54 PM PDT

Weakly typed sudo java delegate implementations seem to be a dime a dozen these days. After reading the latest article on JDJ http://sys-con.com/story/?storyid=49097&DE=1 about yet another weakly typed implementation for java delegates I decided it was high time to put my brain to work on implementing strongly typed delegates. With a little imagination and some magic it was quite simple, while also providing a rather elegant solution.



Unfortunately every implementation that I have seen requires the use of strings to delegate to named methods. For example the most recent JDJ article utilizes the following syntax.



Delegate delegate = new Delegate(
  System.out, "println");
delegate.invoke("Hello World!");



This is readable but as you are no doubt aware if you mistype the method name then you are out of luck. It also does not not allow method completion in your favorite IDE.



As I set out to solve this problem I stumbled upon Micorsoft's implementation of delegates in J++ outlined in Sun's white paper at http://java.sun.com/docs/white/sidebar.html. As such I attempted to model my implementation as closely as I could to the example provided because it provided a starting point for envisioning a possible solution.



delegate long IntOp(int a, int b);

public class Example {
    long add(int a, int b) {
        return a + b;
    }

    public long doIt() {
        IntOp op = new IntOp(this.add);
        long result = op.invoke(2, 3);

        return retult;
    }
}



Several points should be made, because delegates are method pointers you must call invoke(...) to execute the delegate. In my opinion you lose some readability utilizing this method because it is difficult to identify that you are actually invoking add(int a, int b). Additionally it appears that a cast may be required upon invocation of the delegate in order to store the result. I did not test the above code so I am not cartain.



In light of this I dreamt up the following code fragment which would provide type safety as well as providing the ability to invoke the delegate method by name instead of an equivalent invoke(...) method.



abstract class IntOp { abstract long add(int a, int b); }

public abstract class Example {
    abstract IntOp add();
    public long add(Integer a, Integer b) {
        return a + b;
    }

    public static long doIt() throws Exception {
        IntOp op = delegate(Example.class).add();
        long result = op.add(2, 3);

        return result;
    }
}



Several observations. The code fragment is only one line longer than the equivalent J++ example. Additionally the delegate keyword obviously does not exist so an abstract class was used instead for IntOp. The only additional difference is the addition of the "abstract IntOp add()" method. As I begin to further explain this example below it will become apparent that this method is the key to allowing type safety.



All of the magic obviously happens in the static delegate method. It accepts a class and returns an object instance. This allows for the ability to implement the "abstract IntOp add()" method such that it creates an IntOp proxy object. The proxied object in turn delegates to a matching method signature in the Example class with the given arguments. This is a very simple approach. A more elegant solution would provide some sort of Handler that would allow the delegation to differently named methods, but nontheless this is a good starting point. Thus the "abstract IntOp add()" method could be read to mean, provide a delegate of type IntOp which delegates the add method.



In order to proxy the object like this I turned to cglib2 because the Proxy facility in java makes this a bit too complex.



The implementation of the delegate method follows.



public class Delegate {
    public static T delegate(T t) throws Exception {
        return (T) Delegate.delegate(t.getClass());
    }

    public static T delegate(Class t) throws Exception {
            return (T) Enhancer.create(t, new ProxyInterceptor(t));
    }

    public static class ProxyInterceptor implements MethodInterceptor {
        private Object proxy;

        public ProxyInterceptor(Class delegate) {
            this.proxy = Enhancer.create(delegate, NoOp.INSTANCE);
        }

        public Object intercept(final Object o, final Method m,
                Object[] args, MethodProxy mp) throws Throwable {
            return Enhancer.create(m.getReturnType(),
                new MethodInterceptor() {
                    public Object intercept(Object o1, Method m1,
                            Object[] args1, MethodProxy mp1) throws Throwable {
                        return proxy.getClass().getMethod(
                            m.getName(), classes(args1)).invoke(proxy, args1);
                    }
                });
        }
    }

    private static Class[] classes(Object[] objects) {
        Class[] classes = new Class[objects.length];
        for (int i = 0; i < objects.length; i++)
            classes[i] = objects[i].getClass();
        return classes;
    }
}



I will not attempt to explain the details of this class because it is extremely rough and would require some background in cglib2. Needless to say it does what we need it to do.



Having this little trick in our bag now we can utilize delegates in a variety of circumstances which follow with code examples.



The first example is a modification of the example presented in Microsoft's argument for delegates at http://msdn.microsoft.com/vjsharp/productinfo/visualj/visualj6/technical....



public class SimpleFrame extends JFrame
{
    Button buttonOK = new Button();
    JButton buttonReset = new JButton();
    JButton buttonCancel = new JButton();

    public static void main(String[] args) throws Exception {
        SimpleFrame frame = new SimpleFrame();
        frame.initForm();
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frame.setPreferredSize(new Dimension(100, 100));
        frame.pack();
        frame.setVisible(true);
    }

    void initForm() throws Exception
    {
        buttonOK.setLabel("Ok");
        buttonOK.addActionListener(
            delegate(SimpleFrameHandler.class).ok());

        buttonReset.setText("Reset");
        buttonReset.addActionListener(
            delegate(SimpleFrameHandler.class).reset());

        buttonCancel.setText("Cancel");
        buttonCancel.addActionListener(
            delegate(SimpleFrameHandler.class).cancel());

        this.add(buttonOK, BorderLayout.NORTH);
        this.add(buttonReset, BorderLayout.CENTER);
        this.add(buttonCancel, BorderLayout.SOUTH);
    }


    public abstract class SimpleFrameHandler {
        abstract ActionListener ok();
        public void ok(ActionEvent e)
        {
            System.out.println("Ok clicked");
        }

        abstract ActionListener reset();
        public void reset(ActionEvent e)
        {
            System.out.println("Reset clicked");
        }

        abstract ActionListener cancel();
        public void cancel(ActionEvent e)
        {
            System.out.println("Cancel clicked");
        }
    }
}



As you can no doubt see strongly typed delegates allow us to add ActionListener(s) without the need for anonymous classes. The modifications actually closely resemble those presented to illustrate the advantages of C# delegates in .Net. Unfortunately this isn't anything particularly special. All that is really happening is that the anonymous class is created for you and accessed via the abstract method defining what interface or abstract class to implement and delegate to. It does however provide a useful abstraction that could be used in more complex situations providing the potential for powerful functionality.



The last example modifies the code presented in the most recent java.net article http://today.java.net/pub/a/today/2005/04/07/pojostrategy.html outlining uses of the Comparator class.



public abstract class Sort {
    abstract Comparator compareIgnoreCase();
    public int compareIgnoreCase(String s1, String s2) {
        return s1.toLowerCase().compareTo(s2.toLowerCase());
    }

    static String[] alphabeticallyIgnoreCase(String... unsorted) throws Exception {
        String[] sorted = unsorted.clone();
        Arrays.sort(sorted, delegate(Sort.class).compareIgnoreCase());

        return sorted;
    }
}



As you can see the implementation delegates to a type safe case comparison method returned by the proxied Comparator.



As I attempted to set out to solve type safe delegates in java I initially thought it would be quite difficult but as mentioned, with a little imagination and the magic of cglib2 anything is possible. Say goodbye to type unsafe delegates.

Related Topics >>