/*
* @(#)EventHandler.java 1.16 05/08/09
*
* Copyright 2005 Sun Microsystems, Inc. All rights reserved.
* SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*/
package java.beans;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Proxy;
import java.lang.reflect.Method;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.EventObject;
import sun.reflect.misc.MethodUtil;
/**
* The EventHandler
class provides
* support for dynamically generating event listeners whose methods
* execute a simple statement involving an incoming event object
* and a target object.
*
* The EventHandler
class is intended to be used by interactive tools, such as
* application builders, that allow developers to make connections between
* beans. Typically connections are made from a user interface bean
* (the event source)
* to an application logic bean (the target). The most effective
* connections of this kind isolate the application logic from the user
* interface. For example, the EventHandler
for a
* connection from a JCheckBox
to a method
* that accepts a boolean value can deal with extracting the state
* of the check box and passing it directly to the method so that
* the method is isolated from the user interface layer.
*
* Inner classes are another, more general way to handle events from
* user interfaces. The EventHandler
class
* handles only a subset of what is possible using inner
* classes. However, EventHandler
works better
* with the long-term persistence scheme than inner classes.
* Also, using EventHandler
in large applications in
* which the same interface is implemented many times can
* reduce the disk and memory footprint of the application.
*
* The reason that listeners created with EventHandler
* have such a small
* footprint is that the Proxy
class, on which
* the EventHandler
relies, shares implementations
* of identical
* interfaces. For example, if you use
* the EventHandler
create
methods to make
* all the ActionListener
s in an application,
* all the action listeners will be instances of a single class
* (one created by the Proxy
class).
* In general, listeners based on
* the Proxy
class require one listener class
* to be created per listener type (interface),
* whereas the inner class
* approach requires one class to be created per listener
* (object that implements the interface).
*
*
* You don't generally deal directly with EventHandler
* instances.
* Instead, you use one of the EventHandler
* create
methods to create
* an object that implements a given listener interface.
* This listener object uses an EventHandler
object
* behind the scenes to encapsulate information about the
* event, the object to be sent a message when the event occurs,
* the message (method) to be sent, and any argument
* to the method.
* The following section gives examples of how to create listener
* objects using the create
methods.
*
*
EventHandler
is to install
* a listener that calls a method on the target object with no arguments.
* In the following example we create an ActionListener
* that invokes the toFront
method on an instance
* of javax.swing.JFrame
.
*
* ** * When*myButton.addActionListener( * (ActionListener)EventHandler.create(ActionListener.class, frame, "toFront")); **
myButton
is pressed, the statement
* frame.toFront()
will be executed. One could get
* the same effect, with some additional compile-time type safety,
* by defining a new implementation of the ActionListener
* interface and adding an instance of it to the button:
*
* ** * The next simplest use of//Equivalent code using an inner class instead of EventHandler. *myButton.addActionListener(new ActionListener() { * public void actionPerformed(ActionEvent e) { * frame.toFront(); * } *}); **
EventHandler
is
* to extract a property value from the first argument
* of the method in the listener interface (typically an event object)
* and use it to set the value of a property in the target object.
* In the following example we create an ActionListener
that
* sets the nextFocusableComponent
property of the target
* object to the value of the "source" property of the event.
*
* ** * This would correspond to the following inner class implementation: * **EventHandler.create(ActionListener.class, target, "nextFocusableComponent", "source") **
** * Probably the most common use of//Equivalent code using an inner class instead of EventHandler. *new ActionListener() { * public void actionPerformed(ActionEvent e) { * button.setNextFocusableComponent((Component)e.getSource()); * } *} **
EventHandler
* is to extract a property value from the
* source of the event object and set this value as
* the value of a property of the target object.
* In the following example we create an ActionListener
that
* sets the "label" property of the target
* object to the value of the "text" property of the
* source (the value of the "source" property) of the event.
*
* ** * This would correspond to the following inner class implementation: * **EventHandler.create(ActionListener.class, button, "label", "source.text") **
** * The event property may be be "qualified" with an arbitrary number * of property prefixes delimited with the "." character. The "qualifying" * names that appear before the "." characters are taken as the names of * properties that should be applied, left-most first, to * the event object. *//Equivalent code using an inner class instead of EventHandler. *new ActionListener { * public void actionPerformed(ActionEvent e) { * button.setLabel(((JTextField)e.getSource()).getText()); * } *} **
* For example, the following action listener * *
** * might be written as the following inner class * (assuming all the properties had canonical getter methods and * returned the appropriate types): * **EventHandler.create(ActionListener.class, target, "a", "b.c.d") **
** * @see java.lang.reflect.Proxy * @see java.util.EventObject * * @since 1.4 * * @author Mark Davidson * @author Philip Milne * @author Hans Muller * * @version 1.2 10/24/00 */ public class EventHandler implements InvocationHandler { private static Object[] empty = new Object[]{}; private Object target; private Method targetMethod; private String action; private String eventPropertyName; private String listenerMethodName; private AccessControlContext acc; /** * Creates a new//Equivalent code using an inner class instead of EventHandler. *new ActionListener { * public void actionPerformed(ActionEvent e) { * target.setA(e.getB().getC().isD()); * } *} **
EventHandler
object;
* you generally use one of the create
methods
* instead of invoking this constructor directly.
*
* @param target the object that will perform the action
* @param action the (possibly qualified) name of a writable property or method on the target
* @param eventPropertyName the (possibly qualified) name of a readable property of the incoming event
* @param listenerMethodName the name of the method in the listener interface that should trigger the action
*
* @see EventHandler
* @see #create(Class, Object, String, String, String)
* @see #getTarget
* @see #getAction
* @see #getEventPropertyName
* @see #getListenerMethodName
*/
public EventHandler(Object target, String action, String eventPropertyName, String listenerMethodName) {
this.acc = AccessController.getContext();
this.target = target;
this.action = action;
this.eventPropertyName = eventPropertyName;
this.listenerMethodName = listenerMethodName;
}
/**
* Returns the object to which this event handler will send a message.
*
* @return the target of this event handler
* @see #EventHandler(Object, String, String, String)
*/
public Object getTarget() {
return target;
}
/**
* Returns the name of the target's writable property
* that this event handler will set,
* or the name of the method that this event handler
* will invoke on the target.
*
* @return the action of this event handler
* @see #EventHandler(Object, String, String, String)
*/
public String getAction() {
return action;
}
/**
* Returns the property of the event that should be
* used in the action applied to the target.
*
* @return the property of the event
*
* @see #EventHandler(Object, String, String, String)
*/
public String getEventPropertyName() {
return eventPropertyName;
}
/**
* Returns the name of the method that will trigger the action.
* A return value of null
signifies that all methods in the
* listener interface trigger the action.
*
* @return the name of the method that will trigger the action
*
* @see #EventHandler(Object, String, String, String)
*/
public String getListenerMethodName() {
return listenerMethodName;
}
private Object applyGetters(Object target, String getters) {
if (getters == null || getters.equals("")) {
return target;
}
int firstDot = getters.indexOf('.');
if (firstDot == -1) {
firstDot = getters.length();
}
String first = getters.substring(0, firstDot);
String rest = getters.substring(Math.min(firstDot + 1, getters.length()));
try {
Method getter = ReflectionUtils.getMethod(target.getClass(),
"get" + NameGenerator.capitalize(first),
new Class[]{});
if (getter == null) {
getter = ReflectionUtils.getMethod(target.getClass(),
"is" + NameGenerator.capitalize(first),
new Class[]{});
}
if (getter == null) {
getter = ReflectionUtils.getMethod(target.getClass(), first, new Class[]{});
}
if (getter == null) {
throw new RuntimeException("No method called: " + first +
" defined on " + target);
}
Object newTarget = MethodUtil.invoke(getter, target, new Object[]{});
return applyGetters(newTarget, rest);
}
catch (Throwable e) {
throw new RuntimeException("Failed to call method: " + first +
" on " + target, e);
}
}
/**
* Extract the appropriate property value from the event and
* pass it to the action associated with
* this EventHandler
.
*
* @param proxy the proxy object
* @param method the method in the listener interface
* @return the result of applying the action to the target
*
* @see EventHandler
*/
public Object invoke(final Object proxy, final Method method, final Object[] arguments) {
return AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
return invokeInternal(proxy, method, arguments);
}
}, acc);
}
private Object invokeInternal(Object proxy, Method method, Object[] arguments) {
String methodName = method.getName();
if (method.getDeclaringClass() == Object.class) {
// Handle the Object public methods.
if (methodName.equals("hashCode")) {
return new Integer(System.identityHashCode(proxy));
} else if (methodName.equals("equals")) {
return (proxy == arguments[0] ? Boolean.TRUE : Boolean.FALSE);
} else if (methodName.equals("toString")) {
return proxy.getClass().getName() + '@' + Integer.toHexString(proxy.hashCode());
}
}
if (listenerMethodName == null || listenerMethodName.equals(methodName)) {
Class[] argTypes = null;
Object[] newArgs = null;
if (eventPropertyName == null) { // Nullary method.
newArgs = new Object[]{};
argTypes = new Class[]{};
}
else {
Object input = applyGetters(arguments[0], getEventPropertyName());
newArgs = new Object[]{input};
argTypes = new Class[]{input.getClass()};
}
try {
if (targetMethod == null) {
targetMethod = ReflectionUtils.getMethod(target.getClass(),
action, argTypes);
}
if (targetMethod == null) {
targetMethod = ReflectionUtils.getMethod(target.getClass(),
"set" + NameGenerator.capitalize(action), argTypes);
}
if (targetMethod == null) {
throw new RuntimeException("No method called: " +
action + " on class " +
target.getClass() + " with argument "
+ argTypes[0]);
}
return MethodUtil.invoke(targetMethod, target, newArgs);
}
catch (IllegalAccessException ex) {
throw new RuntimeException(ex);
}
catch (InvocationTargetException ex) {
throw new RuntimeException(ex.getTargetException());
}
}
return null;
}
/**
* Creates an implementation of listenerInterface
in which
* all of the methods in the listener interface apply
* the handler's action
to the target
. This
* method is implemented by calling the other, more general,
* implementation of the create
method with both
* the eventPropertyName
and the listenerMethodName
* taking the value null
.
*
* To create an ActionListener
that shows a
* JDialog
with dialog.show()
,
* one can write:
*
*
** **EventHandler.create(ActionListener.class, dialog, "show") **
* @param listenerInterface the listener interface to create a proxy for
* @param target the object that will perform the action
* @param action the name of a writable property or method on the target
*
* @return an object that implements
* To create an
* If the
* If the
* For example, to create a listenerInterface
*
* @see #create(Class, Object, String, String)
*/
public static listenerInterface
in which
* all of the methods pass the value of the event
* expression, eventPropertyName
, to the final method in the
* statement, action
, which is applied to the target
.
* This method is implemented by calling the
* more general, implementation of the create
method with
* the listenerMethodName
taking the value null
.
* ActionListener
that sets the
* the text of a JLabel
to the text value of
* the JTextField
source of the incoming event,
* you can use the following code:
*
*
*
*
* This is equivalent to the following code:
*
*EventHandler.create(ActionListener.class, label, "text", "source.text");
*
*
*
*
* @param listenerInterface the listener interface to create a proxy for
* @param target the object that will perform the action
* @param action the name of a writable property or method on the target
* @param eventPropertyName the (possibly qualified) name of a readable property of the incoming event
*
* @return an object that implements
//Equivalent code using an inner class instead of EventHandler.
*label.setText((JTextField(event.getSource())).getText())
*
*listenerInterface
*
* @see #create(Class, Object, String, String, String)
*/
public static listenerInterface
in which
* the method named listenerMethodName
* passes the value of the event expression, eventPropertyName
,
* to the final method in the statement, action
, which
* is applied to the target
. All of the other listener
* methods do nothing.
* eventPropertyName
is null
the
* implementation calls a method with the name specified
* in action
that takes an EventObject
* or a no-argument method with the same name if a method
* accepting an EventObject
is not defined.
* listenerMethodName
is null
* all methods in the interface trigger the action
to be
* executed on the target
.
* MouseListener
that sets the target
* object's origin
property to the incoming MouseEvent
's
* location (that's the value of mouseEvent.getPoint()
) each
* time a mouse button is pressed, one would write:
*
*
*
* This is comparable to writing a
*EventHandler.create(MouseListener.class, "mousePressed", target, "origin", "point");
*
*MouseListener
in which all
* of the methods except mousePressed
are no-ops:
*
*
*
*
* @param listenerInterface the listener interface to create a proxy for
* @param target the object that will perform the action
* @param action the name of a writable property or method on the target
* @param eventPropertyName the (possibly qualified) name of a readable property of the incoming event
* @param listenerMethodName the name of the method in the listener interface that should trigger the action
*
* @return an object that implements
//Equivalent code using an inner class instead of EventHandler.
*new MouseAdapter() {
* public void mousePressed(MouseEvent e) {
* target.setOrigin(e.getPoint());
* }
*}
*
*listenerInterface
*
* @see EventHandler
*/
public static