/*
* Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
* ORACLE 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 sun.reflect.misc.
MethodUtil;
import sun.reflect.misc.
ReflectUtil;
/**
* The <code>EventHandler</code> class provides
* support for dynamically generating event listeners whose methods
* execute a simple statement involving an incoming event object
* and a target object.
* <p>
* The <code>EventHandler</code> 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 <em>source</em>)
* to an application logic bean (the <em>target</em>). The most effective
* connections of this kind isolate the application logic from the user
* interface. For example, the <code>EventHandler</code> for a
* connection from a <code>JCheckBox</code> 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.
* <p>
* Inner classes are another, more general way to handle events from
* user interfaces. The <code>EventHandler</code> class
* handles only a subset of what is possible using inner
* classes. However, <code>EventHandler</code> works better
* with the long-term persistence scheme than inner classes.
* Also, using <code>EventHandler</code> in large applications in
* which the same interface is implemented many times can
* reduce the disk and memory footprint of the application.
* <p>
* The reason that listeners created with <code>EventHandler</code>
* have such a small
* footprint is that the <code>Proxy</code> class, on which
* the <code>EventHandler</code> relies, shares implementations
* of identical
* interfaces. For example, if you use
* the <code>EventHandler</code> <code>create</code> methods to make
* all the <code>ActionListener</code>s in an application,
* all the action listeners will be instances of a single class
* (one created by the <code>Proxy</code> class).
* In general, listeners based on
* the <code>Proxy</code> class require one listener class
* to be created per <em>listener type</em> (interface),
* whereas the inner class
* approach requires one class to be created per <em>listener</em>
* (object that implements the interface).
*
* <p>
* You don't generally deal directly with <code>EventHandler</code>
* instances.
* Instead, you use one of the <code>EventHandler</code>
* <code>create</code> methods to create
* an object that implements a given listener interface.
* This listener object uses an <code>EventHandler</code> 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 <code>create</code> methods.
*
* <h2>Examples of Using EventHandler</h2>
*
* The simplest use of <code>EventHandler</code> is to install
* a listener that calls a method on the target object with no arguments.
* In the following example we create an <code>ActionListener</code>
* that invokes the <code>toFront</code> method on an instance
* of <code>javax.swing.JFrame</code>.
*
* <blockquote>
*<pre>
*myButton.addActionListener(
* (ActionListener)EventHandler.create(ActionListener.class, frame, "toFront"));
*</pre>
* </blockquote>
*
* When <code>myButton</code> is pressed, the statement
* <code>frame.toFront()</code> will be executed. One could get
* the same effect, with some additional compile-time type safety,
* by defining a new implementation of the <code>ActionListener</code>
* interface and adding an instance of it to the button:
*
* <blockquote>
*<pre>
//Equivalent code using an inner class instead of EventHandler.
*myButton.addActionListener(new ActionListener() {
* public void actionPerformed(ActionEvent e) {
* frame.toFront();
* }
*});
*</pre>
* </blockquote>
*
* The next simplest use of <code>EventHandler</code> 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 <code>ActionListener</code> that
* sets the <code>nextFocusableComponent</code> property of the target
* (myButton) object to the value of the "source" property of the event.
*
* <blockquote>
*<pre>
*EventHandler.create(ActionListener.class, myButton, "nextFocusableComponent", "source")
*</pre>
* </blockquote>
*
* This would correspond to the following inner class implementation:
*
* <blockquote>
*<pre>
//Equivalent code using an inner class instead of EventHandler.
*new ActionListener() {
* public void actionPerformed(ActionEvent e) {
* myButton.setNextFocusableComponent((Component)e.getSource());
* }
*}
*</pre>
* </blockquote>
*
* It's also possible to create an <code>EventHandler</code> that
* just passes the incoming event object to the target's action.
* If the fourth <code>EventHandler.create</code> argument is
* an empty string, then the event is just passed along:
*
* <blockquote>
*<pre>
*EventHandler.create(ActionListener.class, target, "doActionEvent", "")
*</pre>
* </blockquote>
*
* This would correspond to the following inner class implementation:
*
* <blockquote>
*<pre>
//Equivalent code using an inner class instead of EventHandler.
*new ActionListener() {
* public void actionPerformed(ActionEvent e) {
* target.doActionEvent(e);
* }
*}
*</pre>
* </blockquote>
*
* Probably the most common use of <code>EventHandler</code>
* is to extract a property value from the
* <em>source</em> 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 <code>ActionListener</code> 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.
*
* <blockquote>
*<pre>
*EventHandler.create(ActionListener.class, myButton, "label", "source.text")
*</pre>
* </blockquote>
*
* This would correspond to the following inner class implementation:
*
* <blockquote>
*<pre>
//Equivalent code using an inner class instead of EventHandler.
*new ActionListener {
* public void actionPerformed(ActionEvent e) {
* myButton.setLabel(((JTextField)e.getSource()).getText());
* }
*}
*</pre>
* </blockquote>
*
* The event property may 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.
* <p>
* For example, the following action listener
*
* <blockquote>
*<pre>
*EventHandler.create(ActionListener.class, target, "a", "b.c.d")
*</pre>
* </blockquote>
*
* might be written as the following inner class
* (assuming all the properties had canonical getter methods and
* returned the appropriate types):
*
* <blockquote>
*<pre>
//Equivalent code using an inner class instead of EventHandler.
*new ActionListener {
* public void actionPerformed(ActionEvent e) {
* target.setA(e.getB().getC().isD());
* }
*}
*</pre>
* </blockquote>
* The target property may also be "qualified" with an arbitrary number
* of property prefixs delimited with the "." character. For example, the
* following action listener:
* <pre>
* EventHandler.create(ActionListener.class, target, "a.b", "c.d")
* </pre>
* might be written as the following inner class
* (assuming all the properties had canonical getter methods and
* returned the appropriate types):
* <pre>
* //Equivalent code using an inner class instead of EventHandler.
* new ActionListener {
* public void actionPerformed(ActionEvent e) {
* target.getA().setB(e.getC().isD());
* }
*}
*</pre>
* <p>
* As <code>EventHandler</code> ultimately relies on reflection to invoke
* a method we recommend against targeting an overloaded method. For example,
* if the target is an instance of the class <code>MyTarget</code> which is
* defined as:
* <pre>
* public class MyTarget {
* public void doIt(String);
* public void doIt(Object);
* }
* </pre>
* Then the method <code>doIt</code> is overloaded. EventHandler will invoke
* the method that is appropriate based on the source. If the source is
* null, then either method is appropriate and the one that is invoked is
* undefined. For that reason we recommend against targeting overloaded
* methods.
*
* @see java.lang.reflect.Proxy
* @see java.util.EventObject
*
* @since 1.4
*
* @author Mark Davidson
* @author Philip Milne
* @author Hans Muller
*
*/
public class
EventHandler implements
InvocationHandler {
private
Object target;
private
String action;
private final
String eventPropertyName;
private final
String listenerMethodName;
private final
AccessControlContext acc =
AccessController.
getContext();
/**
* Creates a new <code>EventHandler</code> object;
* you generally use one of the <code>create</code> methods
* instead of invoking this constructor directly. Refer to
* {@link java.beans.EventHandler#create(Class, Object, String, String)
* the general version of create} for a complete description of
* the <code>eventPropertyName</code> and <code>listenerMethodName</code>
* parameter.
*
* @param target the object that will perform the action
* @param action the name of a (possibly qualified) 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
*
* @throws NullPointerException if <code>target</code> is null
* @throws NullPointerException if <code>action</code> is null
*
* @see EventHandler
* @see #create(Class, Object, String, String, String)
* @see #getTarget
* @see #getAction
* @see #getEventPropertyName
* @see #getListenerMethodName
*/
@
ConstructorProperties({"target", "action", "eventPropertyName", "listenerMethodName"})
public
EventHandler(
Object target,
String action,
String eventPropertyName,
String listenerMethodName) {
this.
target =
target;
this.
action =
action;
if (
target == null) {
throw new
NullPointerException("target must be non-null");
}
if (
action == null) {
throw new
NullPointerException("action must be non-null");
}
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 <code>null</code> 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 = null;
if (
target != null) {
getter =
Statement.
getMethod(
target.
getClass(),
"get" +
NameGenerator.
capitalize(
first),
new
Class<?>[]{});
if (
getter == null) {
getter =
Statement.
getMethod(
target.
getClass(),
"is" +
NameGenerator.
capitalize(
first),
new
Class<?>[]{});
}
if (
getter == null) {
getter =
Statement.
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 (
Exception 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 <code>EventHandler</code>.
*
* @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) {
AccessControlContext acc = this.
acc;
if ((
acc == null) && (
System.
getSecurityManager() != null)) {
throw new
SecurityException("AccessControlContext is not set");
}
return
AccessController.
doPrivileged(new
PrivilegedAction<
Object>() {
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 == null ? null :
input.
getClass()};
}
try {
int
lastDot =
action.
lastIndexOf('.');
if (
lastDot != -1) {
target =
applyGetters(
target,
action.
substring(0,
lastDot));
action =
action.
substring(
lastDot + 1);
}
Method targetMethod =
Statement.
getMethod(
target.
getClass(),
action,
argTypes);
if (
targetMethod == null) {
targetMethod =
Statement.
getMethod(
target.
getClass(),
"set" +
NameGenerator.
capitalize(
action),
argTypes);
}
if (
targetMethod == null) {
String argTypeString = (
argTypes.length == 0)
? " with no arguments"
: " with argument " +
argTypes[0];
throw new
RuntimeException(
"No method called " +
action + " on " +
target.
getClass() +
argTypeString);
}
return
MethodUtil.
invoke(
targetMethod,
target,
newArgs);
}
catch (
IllegalAccessException ex) {
throw new
RuntimeException(
ex);
}
catch (
InvocationTargetException ex) {
Throwable th =
ex.
getTargetException();
throw (
th instanceof
RuntimeException)
? (
RuntimeException)
th
: new
RuntimeException(
th);
}
}
return null;
}
/**
* Creates an implementation of <code>listenerInterface</code> in which
* <em>all</em> of the methods in the listener interface apply
* the handler's <code>action</code> to the <code>target</code>. This
* method is implemented by calling the other, more general,
* implementation of the <code>create</code> method with both
* the <code>eventPropertyName</code> and the <code>listenerMethodName</code>
* taking the value <code>null</code>. Refer to
* {@link java.beans.EventHandler#create(Class, Object, String, String)
* the general version of create} for a complete description of
* the <code>action</code> parameter.
* <p>
* To create an <code>ActionListener</code> that shows a
* <code>JDialog</code> with <code>dialog.show()</code>,
* one can write:
*
*<blockquote>
*<pre>
*EventHandler.create(ActionListener.class, dialog, "show")
*</pre>
*</blockquote>
*
* @param <T> the type to create
* @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 (possibly qualified) property or method on
* the target
* @return an object that implements <code>listenerInterface</code>
*
* @throws NullPointerException if <code>listenerInterface</code> is null
* @throws NullPointerException if <code>target</code> is null
* @throws NullPointerException if <code>action</code> is null
*
* @see #create(Class, Object, String, String)
*/
public static <T> T
create(
Class<T>
listenerInterface,
Object target,
String action)
{
return
create(
listenerInterface,
target,
action, null, null);
}
/**
/**
* Creates an implementation of <code>listenerInterface</code> in which
* <em>all</em> of the methods pass the value of the event
* expression, <code>eventPropertyName</code>, to the final method in the
* statement, <code>action</code>, which is applied to the <code>target</code>.
* This method is implemented by calling the
* more general, implementation of the <code>create</code> method with
* the <code>listenerMethodName</code> taking the value <code>null</code>.
* Refer to
* {@link java.beans.EventHandler#create(Class, Object, String, String)
* the general version of create} for a complete description of
* the <code>action</code> and <code>eventPropertyName</code> parameters.
* <p>
* To create an <code>ActionListener</code> that sets the
* the text of a <code>JLabel</code> to the text value of
* the <code>JTextField</code> source of the incoming event,
* you can use the following code:
*
*<blockquote>
*<pre>
*EventHandler.create(ActionListener.class, label, "text", "source.text");
*</pre>
*</blockquote>
*
* This is equivalent to the following code:
*<blockquote>
*<pre>
//Equivalent code using an inner class instead of EventHandler.
*new ActionListener() {
* public void actionPerformed(ActionEvent event) {
* label.setText(((JTextField)(event.getSource())).getText());
* }
*};
*</pre>
*</blockquote>
*
* @param <T> the type to create
* @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 (possibly qualified) 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 <code>listenerInterface</code>
*
* @throws NullPointerException if <code>listenerInterface</code> is null
* @throws NullPointerException if <code>target</code> is null
* @throws NullPointerException if <code>action</code> is null
*
* @see #create(Class, Object, String, String, String)
*/
public static <T> T
create(
Class<T>
listenerInterface,
Object target,
String action,
String eventPropertyName)
{
return
create(
listenerInterface,
target,
action,
eventPropertyName, null);
}
/**
* Creates an implementation of <code>listenerInterface</code> in which
* the method named <code>listenerMethodName</code>
* passes the value of the event expression, <code>eventPropertyName</code>,
* to the final method in the statement, <code>action</code>, which
* is applied to the <code>target</code>. All of the other listener
* methods do nothing.
* <p>
* The <code>eventPropertyName</code> string is used to extract a value
* from the incoming event object that is passed to the target
* method. The common case is the target method takes no arguments, in
* which case a value of null should be used for the
* <code>eventPropertyName</code>. Alternatively if you want
* the incoming event object passed directly to the target method use
* the empty string.
* The format of the <code>eventPropertyName</code> string is a sequence of
* methods or properties where each method or
* property is applied to the value returned by the preceding method
* starting from the incoming event object.
* The syntax is: <code>propertyName{.propertyName}*</code>
* where <code>propertyName</code> matches a method or
* property. For example, to extract the <code>point</code>
* property from a <code>MouseEvent</code>, you could use either
* <code>"point"</code> or <code>"getPoint"</code> as the
* <code>eventPropertyName</code>. To extract the "text" property from
* a <code>MouseEvent</code> with a <code>JLabel</code> source use any
* of the following as <code>eventPropertyName</code>:
* <code>"source.text"</code>,
* <code>"getSource.text"</code> <code>"getSource.getText"</code> or
* <code>"source.getText"</code>. If a method can not be found, or an
* exception is generated as part of invoking a method a
* <code>RuntimeException</code> will be thrown at dispatch time. For
* example, if the incoming event object is null, and
* <code>eventPropertyName</code> is non-null and not empty, a
* <code>RuntimeException</code> will be thrown.
* <p>
* The <code>action</code> argument is of the same format as the
* <code>eventPropertyName</code> argument where the last property name
* identifies either a method name or writable property.
* <p>
* If the <code>listenerMethodName</code> is <code>null</code>
* <em>all</em> methods in the interface trigger the <code>action</code> to be
* executed on the <code>target</code>.
* <p>
* For example, to create a <code>MouseListener</code> that sets the target
* object's <code>origin</code> property to the incoming <code>MouseEvent</code>'s
* location (that's the value of <code>mouseEvent.getPoint()</code>) each
* time a mouse button is pressed, one would write:
*<blockquote>
*<pre>
*EventHandler.create(MouseListener.class, target, "origin", "point", "mousePressed");
*</pre>
*</blockquote>
*
* This is comparable to writing a <code>MouseListener</code> in which all
* of the methods except <code>mousePressed</code> are no-ops:
*
*<blockquote>
*<pre>
//Equivalent code using an inner class instead of EventHandler.
*new MouseAdapter() {
* public void mousePressed(MouseEvent e) {
* target.setOrigin(e.getPoint());
* }
*};
* </pre>
*</blockquote>
*
* @param <T> the type to create
* @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 (possibly qualified) 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 <code>listenerInterface</code>
*
* @throws NullPointerException if <code>listenerInterface</code> is null
* @throws NullPointerException if <code>target</code> is null
* @throws NullPointerException if <code>action</code> is null
*
* @see EventHandler
*/
public static <T> T
create(
Class<T>
listenerInterface,
Object target,
String action,
String eventPropertyName,
String listenerMethodName)
{
// Create this first to verify target/action are non-null
final
EventHandler handler = new
EventHandler(
target,
action,
eventPropertyName,
listenerMethodName);
if (
listenerInterface == null) {
throw new
NullPointerException(
"listenerInterface must be non-null");
}
final
ClassLoader loader =
getClassLoader(
listenerInterface);
final
Class<?>[]
interfaces = {
listenerInterface};
return
AccessController.
doPrivileged(new
PrivilegedAction<T>() {
@
SuppressWarnings("unchecked")
public T
run() {
return (T)
Proxy.
newProxyInstance(
loader,
interfaces,
handler);
}
});
}
private static
ClassLoader getClassLoader(
Class<?>
type) {
ReflectUtil.
checkPackageAccess(
type);
ClassLoader loader =
type.
getClassLoader();
if (
loader == null) {
loader =
Thread.
currentThread().
getContextClassLoader(); // avoid use of BCP
if (
loader == null) {
loader =
ClassLoader.
getSystemClassLoader();
}
}
return
loader;
}
}