2004-08-19

Using Reflection for Actions

There is no doubt that inner classes are an important aspect of the Java programming language. The easiest way to define an action in a GUI application is to subclass the javax.swing.AbstractAction class and override its actionPerformed method, usually in an inner class. Yet, actions are so frequent in these applications that it would be nice to simplify the process further. Here, I suggest a way that I have found very useful.

If we have an action named "Open", we might expect it to call a method like "onOpenExecute" without creating a specific class for it. This can be done, using the Java Reflection API.

The ReflectiveAction class, whose code is presented below, uses the reflection API to find its relevant execute method in the owner object. The method's name is constructed using the "key" property of the ReflectiveAction instance. For example, if a ReflectiveAction has a key value of "open", it will reflect on the owner object's class, to see if it contains a public method called "onOpenExecute". If this method is found, it will be called upon firing of this action. For a key value of "file.print", the execute method must be named onFilePrintExecute. Period character is ignored, so a key value of "Choose..." needs a method called onChooseExecute. Other special characters should not be used in the key field (I have done my best to keep the code as simple as possible).

The source code for the ReflectiveAction class is presented below. It must be taken in mind that in this example, an enhanced for loop has been used, which is a feature recently added to Java since JDK 1.5. When compiling this file with javac, a -source 1.5 parameter must be supplied to show that the source is compliant with JDK 1.5 and not the earlier versions. Interestingly, when I used the -target 1.5 parameter in addition to that, the reflection stuff kept hanging the JVM when running. I don't know if this bug is still there or has been corrected in later SDKs (I have build 1.5.0-beta-b31), but anyway, without this option, everything goes well, and I am not in the mood of keeping track of bugs!

Listing 1. Source code of ReflectiveAction.java
/* 
 * ReflectiveAction.java 
 * (c) Ghasem Kiani 
 * 18/08/2004 06:33:49 PM 
 * ghasemkiani@yahoo.com 
 */ 
 
package com.ghasemkiani.temp; 
 
import java.awt.*; 
import java.awt.event.*; 
import javax.swing.*; 
import java.lang.reflect.Method; 
 
public class ReflectiveAction extends AbstractAction 
{ 
  private Method executeMethod; 
  private String key; 
  public void setKey(String key) 
  { 
    this.key = key; 
  } 
  public String getKey() 
  { 
    if(key == null) key = new String(); 
    return key; 
  } 
  private String getKeyAsTitleCase() 
  { 
    String[] parts = getKey().split("\\."); 
    String result = ""; 
    for(String part: parts) 
    { 
      if(part != null) 
      { 
        if(part.length() > 0) 
          result += part.substring(0, 1).toUpperCase(); 
        if(part.length() > 1) 
          result += part.substring(1); 
      } 
    } 
    return result; 
  } 
  private Object owner; 
  public void setOwner(Object owner) 
  { 
    this.owner = owner; 
    if(owner != null) 
    { 
      Class oc = owner.getClass(); 
      String em = "on" + getKeyAsTitleCase() + "Execute"; 
      try 
      { 
        executeMethod = oc.getMethod(em, new Class[0]); 
      } 
      catch(Exception e) 
      { 
        executeMethod = null; 
      } 
    } 
    else 
    { 
      executeMethod = null; 
    } 
  } 
  public Object getOwner() 
  { 
    return owner; 
  } 
  public ReflectiveAction() 
  { 
    super(); 
  } 
  public ReflectiveAction(Object owner, String key) 
  { 
    super(key); 
    setKey(key); 
    setOwner(owner); 
  } 
  public ReflectiveAction(Object owner, String key, String name) 
  { 
    super(name); 
    setKey(key); 
    setOwner(owner); 
  } 
  public ReflectiveAction(Object owner, String key, Icon icon) 
  { 
    super(key, icon); 
    setKey(key); 
    setOwner(owner); 
  } 
  public ReflectiveAction(Object owner, String key, 
    String name, Icon icon) 
  { 
    super(name, icon); 
    setKey(key); 
    setOwner(owner); 
  } 
  public void actionPerformed(ActionEvent ae) 
  { 
    try 
    { 
      if(executeMethod != null) 
        executeMethod.invoke(owner, new Object[0]); 
    } 
    catch(Exception e) 
    { 
      e.printStackTrace(); 
    } 
  } 
} 

Here is a sample application using the ReflectiveAction class. I think that the code is self-descriptive. Also, be warned that I hate comments in my code. As unacceptable as it may be, I use them very rarely.

Listing 2. Source code of TestApplication.java
/* 
 * TestApplication.java 
 * (c) Ghasem Kiani 
 * 18/08/2004 06:58:34 PM 
 * ghasemkiani@yahoo.com 
 */ 
 
package com.ghasemkiani.temp; 
 
import java.awt.*; 
import javax.swing.*; 
import com.ghasemkiani.temp.ReflectiveAction; 
 
public class TestApplication extends JFrame 
{ 
  JPanel jp; 
  public TestApplication() 
  { 
    super("Test Application"); 
    setDefaultCloseOperation(DISPOSE_ON_CLOSE); 
    JToolBar jtb = new JToolBar(); 
    getContentPane().add(jtb, BorderLayout.NORTH); 
    jp = new JPanel(); 
    getContentPane().add(jp); 
    jtb.add(new ReflectiveAction(this, "Green")); 
    jtb.add(new ReflectiveAction(this, "Blue")); 
    jtb.add(new ReflectiveAction(this, "Red")); 
    jtb.add(new ReflectiveAction(this, "Choose...")); 
    jtb.add(new ReflectiveAction(this, "Dummy")); 
    jtb.add(new ReflectiveAction(this, "Exit")); 
    setBounds(new Rectangle(100, 100, 400, 240)); 
    setVisible(true); 
  } 
  public void onGreenExecute() 
  { 
    JOptionPane.showMessageDialog(this, 
      "I don't actually become green!"); 
  } 
  public void onBlueExecute() 
  { 
    JOptionPane.showMessageDialog(this, 
      "I don't actually become blue!"); 
  } 
  public void onRedExecute() 
  { 
    jp.setBackground(Color.red); 
  } 
  public void onChooseExecute() 
  { 
    Color c = JColorChooser.showDialog(this, 
      "Choose a color", jp.getBackground()); 
    if(c != null) jp.setBackground(c); 
  } 
  public void onExitExecute() 
  { 
    dispose(); 
  } 
  public static void main(String[] args) 
  { 
    JFrame.setDefaultLookAndFeelDecorated(true); 
    JDialog.setDefaultLookAndFeelDecorated(true); 
    new TestApplication(); 
  } 
} 

Since I am willing to see the result of a code when I read an example code in an article, I present here a few screenshots of the TestApplication class running.

Figure 1 This is a picture of the application window.

This figure shows that the reflection has actually taken place, and the right method hase been called after clicking the Red and Green button.

Figure 2 This is what happens after clicking the Green button.

Another screenshot, just to make it a little saltier! (I don't know if you can take the Persian nuances of words.)

Figure 3 This is what has happened after clicking the Choose... button.

Some may understandably argue that this is againtst the spirit of the Java programming language, having a strong object-oriented orientation. But, it is an equally important argument that such an attitude is the rule in languages like Delphi and lisp (from a totally different point of view relative to Delphi -- here I mean lexical closures and metaprogramming, if I use the right terms).

The source and compiled classes of this example can be downloaded from here.

No comments: