An Example: Annotating Event Handlers


One of the more boring tasks in user interface programming is the wiring of listeners to event sources. Many listeners are of the form

 myButton.addActionListener(new    ActionListener()    {       public void actionPerformed(ActionEvent event)       {          doSomething();       }    }); 

In this section, we design an annotation to avoid this drudgery. The annotation has the form

 @ActionListenerFor(source="myButton") void doSomething() { . . . } 

The programmer no longer has to make calls to addActionListener. Instead, each method is simply tagged with an annotation. Example 13-1 shows the ButtonTest program from Volume 1, Chapter 8, reimplemented with these annotations.

We also need to define an annotation interface. The code is in Example 13-2.

Example 13-1. ButtonTest.java
  1. import java.awt.*;  2. import java.awt.event.*;  3. import javax.swing.*;  4.  5. public class ButtonTest  6. {  7.    public static void main(String[] args)  8.    {  9.       ButtonFrame frame = new ButtonFrame(); 10.       frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 11.       frame.setVisible(true); 12.    } 13. } 14. 15. /** 16.    A frame with a button panel 17. */ 18. class ButtonFrame extends JFrame 19. { 20.    public ButtonFrame() 21.    { 22.       setTitle("ButtonTest"); 23.       setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); 24. 25.       panel = new JPanel(); 26.       add(panel); 27. 28.       // create buttons 29. 30.       yellowButton = new JButton("Yellow"); 31.       blueButton = new JButton("Blue"); 32.       redButton = new JButton("Red"); 33. 34.       // add buttons to panel 35. 36.       panel.add(yellowButton); 37.       panel.add(blueButton); 38.       panel.add(redButton); 39. 40.       ActionListenerInstaller.processAnnotations(this); 41.    } 42. 43. 44.    @ActionListenerFor(source="yellowButton") 45.    public void yellowBackground() 46.    { 47.       panel.setBackground(Color.YELLOW); 48.    } 49. 50.    @ActionListenerFor(source="blueButton") 51.    public void blueBackground() 52.    { 53.       panel.setBackground(Color.BLUE); 54.    } 55. 56.    @ActionListenerFor(source="redButton") 57.    public void redBackground() 58.    { 59.       panel.setBackground(Color.RED); 60.    } 61. 62.    public static final int DEFAULT_WIDTH = 300; 63.    public static final int DEFAULT_HEIGHT = 200; 64. 65.    private JPanel panel; 66.    private JButton yellowButton; 67.    private JButton blueButton; 68.    private JButton redButton; 69. } 

Example 13-2. ActionListenerFor.java
 1. import java.lang.annotation.*; 2. 3. @Target(ElementType.METHOD) 4. @Retention(RetentionPolicy.RUNTIME) 5. public @interface ActionListenerFor 6. { 7.    String source(); 8. } 

Of course, the annotations don't do anything by themselves. They sit in the source file. The compiler places them in the class file, and the virtual machine loads them. We now need a mechanism to analyze them and install action listeners. That is the job of the ActionListenerInstaller class. The ButtonFrame constructor calls

 ActionListenerInstaller.processAnnotations(this); 

The static processAnnotations method enumerates all methods of the object that it received. For each method, it gets the ActionListenerFor annotation object and processes it.

 Class cl = obj.getClass(); for (Method m : cl.getDeclaredMethods()) {    ActionListenerFor a = m.getAnnotation(ActionListenerFor.class);    if (a != null) . . . } 

Here, we use the getAnnotation method that is defined in the AnnotatedElement interface. The classes Method, Constructor, Field, Class, and Package implement this interface.

The name of the source field is stored in the annotation object. We retrieve it by calling the source method, and then look up the matching field.

 String fieldName = a.source(); Field f = cl.getDeclaredField(fieldName); 

This shows a limitation of our annotation. The source element must be the name of a field. It cannot be a local variable.

The remainder of the code is rather technical. For each annotated method, we construct a proxy object that implements the ActionListener interface and whose actionPerformed method calls the annotated method. (For more information about proxies, see Volume 1, Chapter 6.) The details are not important. The key observation is that the functionality of the annotations was established by the processAnnotations method.

In this example, the annotations were processed at run time. It would also have been possible to process them at the source level. A source code generator might have produced the code for adding the listeners. Alternatively, the annotations might have been processed at the bytecode level. A bytecode editor might have injected the calls to addActionListener into the frame constructor. This sounds complex, but libraries are available to make this task relatively straightforward. You can see an example on page 962.

Our example was not intended as a serious tool for user interface programmers. A utility method for adding a listener could be just as convenient for the programmer as the annotation. (In fact, the java.beans.EventHandler class tries to do just that. You could easily refine the class to be truly useful by supplying a method that adds the event handler instead of just constructing it.)

However, this example shows the mechanics of annotating a program and of analyzing the annotations. Having seen a concrete example, you are now more prepared (we hope) for the following sections that describe the annotation syntax in complete detail.

Example 13-3. ActionListenerInstaller.java

[View full width]

  1. import java.awt.event.*;  2. import java.lang.annotation.*;  3. import java.lang.reflect.*;  4.  5. public class ActionListenerInstaller  6. {  7.    /**  8.       Processes all ActionListenerFor annotations in the given object.  9.       @param obj an object whose methods may have ActionListenerFor annotations 10.    */ 11.    public static void processAnnotations(Object obj) 12.    { 13.       try 14.       { 15.          Class cl = obj.getClass(); 16.          for (Method m : cl.getDeclaredMethods()) 17.          { 18.             ActionListenerFor a = m.getAnnotation(ActionListenerFor.class); 19.             if (a != null) 20.             { 21.                Field f = cl.getDeclaredField(a.source()); 22.                f.setAccessible(true); 23.                addListener(f.get(obj), obj, m); 24.             } 25.          } 26.       } 27.       catch (Exception e) 28.       { 29.          e.printStackTrace(); 30.       } 31.    } 32. 33.    /** 34.       Adds an action listener that calls a given method. 35.       @param source the event source to which an action listener is added 36.       @param param the implicit parameter of the method that the listener calls 37.       @param m the method that the listener calls 38.    */ 39.    public static void addListener(Object source, final Object param, final Method m) 40.       throws NoSuchMethodException, IllegalAccessException, InvocationTargetException 41.    { 42.       InvocationHandler handler = new 43.          InvocationHandler() 44.          { 45.             public Object invoke(Object proxy, Method mm, Object[] args) throws Throwable 46.             { 47.                return m.invoke(param); 48.             } 49.          }; 50. 51.       Object listener = Proxy.newProxyInstance(null, 52.          new Class[] { java.awt.event.ActionListener.class }, 53.          handler); 54.       Method adder = source.getClass().getMethod("addActionListener", ActionListener .class); 55.       adder.invoke(source, listener); 56.    } 57. } 


 java.lang.AnnotatedElement 5.0 

  • boolean isAnnotationPresent(Class<? extends Annotation> annotationType)

    returns true if this item has an annotation of the given type.

  • <T extends Annotation> T getAnnotation(Class<T> annotationType)

    gets the annotation of the given type, or null if this item has no such annotation.

  • Annotation[] getAnnotations()

    gets all annotations that are present for this item, including inherited annotations. If no annotations are present, an array of length 0 is returned.

  • Annotation[] getDeclaredAnnotations()

    gets all annotations that are declared for this item, excluding inherited annotations. If no annotations are present, an array of length 0 is returned.



    Core JavaT 2 Volume II - Advanced Features
    Building an On Demand Computing Environment with IBM: How to Optimize Your Current Infrastructure for Today and Tomorrow (MaxFacts Guidebook series)
    ISBN: 193164411X
    EAN: 2147483647
    Year: 2003
    Pages: 156
    Authors: Jim Hoskins

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net