Path: blob/master/src/java.desktop/share/classes/java/beans/EventHandler.java
41152 views
/*1* Copyright (c) 2000, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/24package java.beans;2526import java.lang.reflect.InvocationHandler;27import java.lang.reflect.InvocationTargetException;28import java.lang.reflect.Proxy;29import java.lang.reflect.Method;30import java.security.AccessControlContext;31import java.security.AccessController;32import java.security.PrivilegedAction;3334import sun.reflect.misc.MethodUtil;35import sun.reflect.misc.ReflectUtil;3637/**38* The {@code EventHandler} class provides39* support for dynamically generating event listeners whose methods40* execute a simple statement involving an incoming event object41* and a target object.42* <p>43* The {@code EventHandler} class is intended to be used by interactive tools, such as44* application builders, that allow developers to make connections between45* beans. Typically connections are made from a user interface bean46* (the event <em>source</em>)47* to an application logic bean (the <em>target</em>). The most effective48* connections of this kind isolate the application logic from the user49* interface. For example, the {@code EventHandler} for a50* connection from a {@code JCheckBox} to a method51* that accepts a boolean value can deal with extracting the state52* of the check box and passing it directly to the method so that53* the method is isolated from the user interface layer.54* <p>55* Inner classes are another, more general way to handle events from56* user interfaces. The {@code EventHandler} class57* handles only a subset of what is possible using inner58* classes. However, {@code EventHandler} works better59* with the long-term persistence scheme than inner classes.60* Also, using {@code EventHandler} in large applications in61* which the same interface is implemented many times can62* reduce the disk and memory footprint of the application.63* <p>64* The reason that listeners created with {@code EventHandler}65* have such a small66* footprint is that the {@code Proxy} class, on which67* the {@code EventHandler} relies, shares implementations68* of identical69* interfaces. For example, if you use70* the {@code EventHandler create} methods to make71* all the {@code ActionListener}s in an application,72* all the action listeners will be instances of a single class73* (one created by the {@code Proxy} class).74* In general, listeners based on75* the {@code Proxy} class require one listener class76* to be created per <em>listener type</em> (interface),77* whereas the inner class78* approach requires one class to be created per <em>listener</em>79* (object that implements the interface).80*81* <p>82* You don't generally deal directly with {@code EventHandler}83* instances.84* Instead, you use one of the {@code EventHandler}85* {@code create} methods to create86* an object that implements a given listener interface.87* This listener object uses an {@code EventHandler} object88* behind the scenes to encapsulate information about the89* event, the object to be sent a message when the event occurs,90* the message (method) to be sent, and any argument91* to the method.92* The following section gives examples of how to create listener93* objects using the {@code create} methods.94*95* <h2>Examples of Using EventHandler</h2>96*97* The simplest use of {@code EventHandler} is to install98* a listener that calls a method on the target object with no arguments.99* In the following example we create an {@code ActionListener}100* that invokes the {@code toFront} method on an instance101* of {@code javax.swing.JFrame}.102*103* <blockquote>104*<pre>105*myButton.addActionListener(106* (ActionListener)EventHandler.create(ActionListener.class, frame, "toFront"));107*</pre>108* </blockquote>109*110* When {@code myButton} is pressed, the statement111* {@code frame.toFront()} will be executed. One could get112* the same effect, with some additional compile-time type safety,113* by defining a new implementation of the {@code ActionListener}114* interface and adding an instance of it to the button:115*116* <blockquote>117*<pre>118//Equivalent code using an inner class instead of EventHandler.119*myButton.addActionListener(new ActionListener() {120* public void actionPerformed(ActionEvent e) {121* frame.toFront();122* }123*});124*</pre>125* </blockquote>126*127* The next simplest use of {@code EventHandler} is128* to extract a property value from the first argument129* of the method in the listener interface (typically an event object)130* and use it to set the value of a property in the target object.131* In the following example we create an {@code ActionListener} that132* sets the {@code nextFocusableComponent} property of the target133* (myButton) object to the value of the "source" property of the event.134*135* <blockquote>136*<pre>137*EventHandler.create(ActionListener.class, myButton, "nextFocusableComponent", "source")138*</pre>139* </blockquote>140*141* This would correspond to the following inner class implementation:142*143* <blockquote>144*<pre>145//Equivalent code using an inner class instead of EventHandler.146*new ActionListener() {147* public void actionPerformed(ActionEvent e) {148* myButton.setNextFocusableComponent((Component)e.getSource());149* }150*}151*</pre>152* </blockquote>153*154* It's also possible to create an {@code EventHandler} that155* just passes the incoming event object to the target's action.156* If the fourth {@code EventHandler.create} argument is157* an empty string, then the event is just passed along:158*159* <blockquote>160*<pre>161*EventHandler.create(ActionListener.class, target, "doActionEvent", "")162*</pre>163* </blockquote>164*165* This would correspond to the following inner class implementation:166*167* <blockquote>168*<pre>169//Equivalent code using an inner class instead of EventHandler.170*new ActionListener() {171* public void actionPerformed(ActionEvent e) {172* target.doActionEvent(e);173* }174*}175*</pre>176* </blockquote>177*178* Probably the most common use of {@code EventHandler}179* is to extract a property value from the180* <em>source</em> of the event object and set this value as181* the value of a property of the target object.182* In the following example we create an {@code ActionListener} that183* sets the "label" property of the target184* object to the value of the "text" property of the185* source (the value of the "source" property) of the event.186*187* <blockquote>188*<pre>189*EventHandler.create(ActionListener.class, myButton, "label", "source.text")190*</pre>191* </blockquote>192*193* This would correspond to the following inner class implementation:194*195* <blockquote>196*<pre>197//Equivalent code using an inner class instead of EventHandler.198*new ActionListener {199* public void actionPerformed(ActionEvent e) {200* myButton.setLabel(((JTextField)e.getSource()).getText());201* }202*}203*</pre>204* </blockquote>205*206* The event property may be "qualified" with an arbitrary number207* of property prefixes delimited with the "." character. The "qualifying"208* names that appear before the "." characters are taken as the names of209* properties that should be applied, left-most first, to210* the event object.211* <p>212* For example, the following action listener213*214* <blockquote>215*<pre>216*EventHandler.create(ActionListener.class, target, "a", "b.c.d")217*</pre>218* </blockquote>219*220* might be written as the following inner class221* (assuming all the properties had canonical getter methods and222* returned the appropriate types):223*224* <blockquote>225*<pre>226//Equivalent code using an inner class instead of EventHandler.227*new ActionListener {228* public void actionPerformed(ActionEvent e) {229* target.setA(e.getB().getC().isD());230* }231*}232*</pre>233* </blockquote>234* The target property may also be "qualified" with an arbitrary number235* of property prefixs delimited with the "." character. For example, the236* following action listener:237* <pre>238* EventHandler.create(ActionListener.class, target, "a.b", "c.d")239* </pre>240* might be written as the following inner class241* (assuming all the properties had canonical getter methods and242* returned the appropriate types):243* <pre>244* //Equivalent code using an inner class instead of EventHandler.245* new ActionListener {246* public void actionPerformed(ActionEvent e) {247* target.getA().setB(e.getC().isD());248* }249*}250*</pre>251* <p>252* As {@code EventHandler} ultimately relies on reflection to invoke253* a method we recommend against targeting an overloaded method. For example,254* if the target is an instance of the class {@code MyTarget} which is255* defined as:256* <pre>257* public class MyTarget {258* public void doIt(String);259* public void doIt(Object);260* }261* </pre>262* Then the method {@code doIt} is overloaded. EventHandler will invoke263* the method that is appropriate based on the source. If the source is264* null, then either method is appropriate and the one that is invoked is265* undefined. For that reason we recommend against targeting overloaded266* methods.267*268* @see java.lang.reflect.Proxy269* @see java.util.EventObject270*271* @since 1.4272*273* @author Mark Davidson274* @author Philip Milne275* @author Hans Muller276*277*/278public class EventHandler implements InvocationHandler {279private Object target;280private String action;281private final String eventPropertyName;282private final String listenerMethodName;283@SuppressWarnings("removal")284private final AccessControlContext acc = AccessController.getContext();285286/**287* Creates a new {@code EventHandler} object;288* you generally use one of the {@code create} methods289* instead of invoking this constructor directly. Refer to290* {@link java.beans.EventHandler#create(Class, Object, String, String)291* the general version of create} for a complete description of292* the {@code eventPropertyName} and {@code listenerMethodName}293* parameter.294*295* @param target the object that will perform the action296* @param action the name of a (possibly qualified) property or method on297* the target298* @param eventPropertyName the (possibly qualified) name of a readable property of the incoming event299* @param listenerMethodName the name of the method in the listener interface that should trigger the action300*301* @throws NullPointerException if {@code target} is null302* @throws NullPointerException if {@code action} is null303*304* @see EventHandler305* @see #create(Class, Object, String, String, String)306* @see #getTarget307* @see #getAction308* @see #getEventPropertyName309* @see #getListenerMethodName310*/311@ConstructorProperties({"target", "action", "eventPropertyName", "listenerMethodName"})312public EventHandler(Object target, String action, String eventPropertyName, String listenerMethodName) {313this.target = target;314this.action = action;315if (target == null) {316throw new NullPointerException("target must be non-null");317}318if (action == null) {319throw new NullPointerException("action must be non-null");320}321this.eventPropertyName = eventPropertyName;322this.listenerMethodName = listenerMethodName;323}324325/**326* Returns the object to which this event handler will send a message.327*328* @return the target of this event handler329* @see #EventHandler(Object, String, String, String)330*/331public Object getTarget() {332return target;333}334335/**336* Returns the name of the target's writable property337* that this event handler will set,338* or the name of the method that this event handler339* will invoke on the target.340*341* @return the action of this event handler342* @see #EventHandler(Object, String, String, String)343*/344public String getAction() {345return action;346}347348/**349* Returns the property of the event that should be350* used in the action applied to the target.351*352* @return the property of the event353*354* @see #EventHandler(Object, String, String, String)355*/356public String getEventPropertyName() {357return eventPropertyName;358}359360/**361* Returns the name of the method that will trigger the action.362* A return value of {@code null} signifies that all methods in the363* listener interface trigger the action.364*365* @return the name of the method that will trigger the action366*367* @see #EventHandler(Object, String, String, String)368*/369public String getListenerMethodName() {370return listenerMethodName;371}372373private Object applyGetters(Object target, String getters) {374if (getters == null || getters.isEmpty()) {375return target;376}377int firstDot = getters.indexOf('.');378if (firstDot == -1) {379firstDot = getters.length();380}381String first = getters.substring(0, firstDot);382String rest = getters.substring(Math.min(firstDot + 1, getters.length()));383384try {385Method getter = null;386if (target != null) {387getter = Statement.getMethod(target.getClass(),388"get" + NameGenerator.capitalize(first),389new Class<?>[]{});390if (getter == null) {391getter = Statement.getMethod(target.getClass(),392"is" + NameGenerator.capitalize(first),393new Class<?>[]{});394}395if (getter == null) {396getter = Statement.getMethod(target.getClass(), first, new Class<?>[]{});397}398}399if (getter == null) {400throw new RuntimeException("No method called: " + first +401" defined on " + target);402}403Object newTarget = MethodUtil.invoke(getter, target, new Object[]{});404return applyGetters(newTarget, rest);405}406catch (Exception e) {407throw new RuntimeException("Failed to call method: " + first +408" on " + target, e);409}410}411412/**413* Extract the appropriate property value from the event and414* pass it to the action associated with415* this {@code EventHandler}.416*417* @param proxy the proxy object418* @param method the method in the listener interface419* @return the result of applying the action to the target420*421* @see EventHandler422*/423@SuppressWarnings("removal")424public Object invoke(final Object proxy, final Method method, final Object[] arguments) {425AccessControlContext acc = this.acc;426if ((acc == null) && (System.getSecurityManager() != null)) {427throw new SecurityException("AccessControlContext is not set");428}429return AccessController.doPrivileged(new PrivilegedAction<Object>() {430public Object run() {431return invokeInternal(proxy, method, arguments);432}433}, acc);434}435436private Object invokeInternal(Object proxy, Method method, Object[] arguments) {437String methodName = method.getName();438if (method.getDeclaringClass() == Object.class) {439// Handle the Object public methods.440if (methodName.equals("hashCode")) {441return System.identityHashCode(proxy);442} else if (methodName.equals("equals")) {443return (proxy == arguments[0] ? Boolean.TRUE : Boolean.FALSE);444} else if (methodName.equals("toString")) {445return proxy.getClass().getName() + '@' + Integer.toHexString(proxy.hashCode());446}447}448449if (listenerMethodName == null || listenerMethodName.equals(methodName)) {450Class<?>[] argTypes = null;451Object[] newArgs = null;452453if (eventPropertyName == null) { // Nullary method.454newArgs = new Object[]{};455argTypes = new Class<?>[]{};456}457else {458Object input = applyGetters(arguments[0], getEventPropertyName());459newArgs = new Object[]{input};460argTypes = new Class<?>[]{input == null ? null :461input.getClass()};462}463try {464int lastDot = action.lastIndexOf('.');465if (lastDot != -1) {466target = applyGetters(target, action.substring(0, lastDot));467action = action.substring(lastDot + 1);468}469Method targetMethod = Statement.getMethod(470target.getClass(), action, argTypes);471if (targetMethod == null) {472targetMethod = Statement.getMethod(target.getClass(),473"set" + NameGenerator.capitalize(action), argTypes);474}475if (targetMethod == null) {476String argTypeString = (argTypes.length == 0)477? " with no arguments"478: " with argument " + argTypes[0];479throw new RuntimeException(480"No method called " + action + " on " +481target.getClass() + argTypeString);482}483return MethodUtil.invoke(targetMethod, target, newArgs);484}485catch (IllegalAccessException ex) {486throw new RuntimeException(ex);487}488catch (InvocationTargetException ex) {489Throwable th = ex.getCause();490throw (th instanceof RuntimeException)491? (RuntimeException) th492: new RuntimeException(th);493}494}495return null;496}497498/**499* Creates an implementation of {@code listenerInterface} in which500* <em>all</em> of the methods in the listener interface apply501* the handler's {@code action} to the {@code target}. This502* method is implemented by calling the other, more general,503* implementation of the {@code create} method with both504* the {@code eventPropertyName} and the {@code listenerMethodName}505* taking the value {@code null}. Refer to506* {@link java.beans.EventHandler#create(Class, Object, String, String)507* the general version of create} for a complete description of508* the {@code action} parameter.509* <p>510* To create an {@code ActionListener} that shows a511* {@code JDialog} with {@code dialog.show()},512* one can write:513*514*<blockquote>515*<pre>516*EventHandler.create(ActionListener.class, dialog, "show")517*</pre>518*</blockquote>519*520* @param <T> the type to create521* @param listenerInterface the listener interface to create a proxy for522* @param target the object that will perform the action523* @param action the name of a (possibly qualified) property or method on524* the target525* @return an object that implements {@code listenerInterface}526*527* @throws NullPointerException if {@code listenerInterface} is null528* @throws NullPointerException if {@code target} is null529* @throws NullPointerException if {@code action} is null530* @throws IllegalArgumentException if creating a Proxy for531* {@code listenerInterface} fails for any of the restrictions532* specified by {@link Proxy#newProxyInstance}533* @see #create(Class, Object, String, String)534* @see Proxy#newProxyInstance535*/536public static <T> T create(Class<T> listenerInterface,537Object target, String action)538{539return create(listenerInterface, target, action, null, null);540}541542/**543/**544* Creates an implementation of {@code listenerInterface} in which545* <em>all</em> of the methods pass the value of the event546* expression, {@code eventPropertyName}, to the final method in the547* statement, {@code action}, which is applied to the {@code target}.548* This method is implemented by calling the549* more general, implementation of the {@code create} method with550* the {@code listenerMethodName} taking the value {@code null}.551* Refer to552* {@link java.beans.EventHandler#create(Class, Object, String, String)553* the general version of create} for a complete description of554* the {@code action} and {@code eventPropertyName} parameters.555* <p>556* To create an {@code ActionListener} that sets the557* the text of a {@code JLabel} to the text value of558* the {@code JTextField} source of the incoming event,559* you can use the following code:560*561*<blockquote>562*<pre>563*EventHandler.create(ActionListener.class, label, "text", "source.text");564*</pre>565*</blockquote>566*567* This is equivalent to the following code:568*<blockquote>569*<pre>570//Equivalent code using an inner class instead of EventHandler.571*new ActionListener() {572* public void actionPerformed(ActionEvent event) {573* label.setText(((JTextField)(event.getSource())).getText());574* }575*};576*</pre>577*</blockquote>578*579* @param <T> the type to create580* @param listenerInterface the listener interface to create a proxy for581* @param target the object that will perform the action582* @param action the name of a (possibly qualified) property or method on583* the target584* @param eventPropertyName the (possibly qualified) name of a readable property of the incoming event585*586* @return an object that implements {@code listenerInterface}587*588* @throws NullPointerException if {@code listenerInterface} is null589* @throws NullPointerException if {@code target} is null590* @throws NullPointerException if {@code action} is null591* @throws IllegalArgumentException if creating a Proxy for592* {@code listenerInterface} fails for any of the restrictions593* specified by {@link Proxy#newProxyInstance}594* @see #create(Class, Object, String, String, String)595* @see Proxy#newProxyInstance596*/597public static <T> T create(Class<T> listenerInterface,598Object target, String action,599String eventPropertyName)600{601return create(listenerInterface, target, action, eventPropertyName, null);602}603604/**605* Creates an implementation of {@code listenerInterface} in which606* the method named {@code listenerMethodName}607* passes the value of the event expression, {@code eventPropertyName},608* to the final method in the statement, {@code action}, which609* is applied to the {@code target}. All of the other listener610* methods do nothing.611* <p>612* The {@code eventPropertyName} string is used to extract a value613* from the incoming event object that is passed to the target614* method. The common case is the target method takes no arguments, in615* which case a value of null should be used for the616* {@code eventPropertyName}. Alternatively if you want617* the incoming event object passed directly to the target method use618* the empty string.619* The format of the {@code eventPropertyName} string is a sequence of620* methods or properties where each method or621* property is applied to the value returned by the preceding method622* starting from the incoming event object.623* The syntax is: {@code propertyName{.propertyName}*}624* where {@code propertyName} matches a method or625* property. For example, to extract the {@code point}626* property from a {@code MouseEvent}, you could use either627* {@code "point"} or {@code "getPoint"} as the628* {@code eventPropertyName}. To extract the "text" property from629* a {@code MouseEvent} with a {@code JLabel} source use any630* of the following as {@code eventPropertyName}:631* {@code "source.text"},632* {@code "getSource.text" "getSource.getText"} or633* {@code "source.getText"}. If a method can not be found, or an634* exception is generated as part of invoking a method a635* {@code RuntimeException} will be thrown at dispatch time. For636* example, if the incoming event object is null, and637* {@code eventPropertyName} is non-null and not empty, a638* {@code RuntimeException} will be thrown.639* <p>640* The {@code action} argument is of the same format as the641* {@code eventPropertyName} argument where the last property name642* identifies either a method name or writable property.643* <p>644* If the {@code listenerMethodName} is {@code null}645* <em>all</em> methods in the interface trigger the {@code action} to be646* executed on the {@code target}.647* <p>648* For example, to create a {@code MouseListener} that sets the target649* object's {@code origin} property to the incoming {@code MouseEvent}'s650* location (that's the value of {@code mouseEvent.getPoint()}) each651* time a mouse button is pressed, one would write:652*<blockquote>653*<pre>654*EventHandler.create(MouseListener.class, target, "origin", "point", "mousePressed");655*</pre>656*</blockquote>657*658* This is comparable to writing a {@code MouseListener} in which all659* of the methods except {@code mousePressed} are no-ops:660*661*<blockquote>662*<pre>663//Equivalent code using an inner class instead of EventHandler.664*new MouseAdapter() {665* public void mousePressed(MouseEvent e) {666* target.setOrigin(e.getPoint());667* }668*};669* </pre>670*</blockquote>671*672* @param <T> the type to create673* @param listenerInterface the listener interface to create a proxy for674* @param target the object that will perform the action675* @param action the name of a (possibly qualified) property or method on676* the target677* @param eventPropertyName the (possibly qualified) name of a readable property of the incoming event678* @param listenerMethodName the name of the method in the listener interface that should trigger the action679*680* @return an object that implements {@code listenerInterface}681*682* @throws NullPointerException if {@code listenerInterface} is null683* @throws NullPointerException if {@code target} is null684* @throws NullPointerException if {@code action} is null685* @throws IllegalArgumentException if creating a Proxy for686* {@code listenerInterface} fails for any of the restrictions687* specified by {@link Proxy#newProxyInstance}688* @see EventHandler689* @see Proxy#newProxyInstance690*/691@SuppressWarnings("removal")692public static <T> T create(Class<T> listenerInterface,693Object target, String action,694String eventPropertyName,695String listenerMethodName)696{697// Create this first to verify target/action are non-null698final EventHandler handler = new EventHandler(target, action,699eventPropertyName,700listenerMethodName);701if (listenerInterface == null) {702throw new NullPointerException(703"listenerInterface must be non-null");704}705final ClassLoader loader = getClassLoader(listenerInterface);706final Class<?>[] interfaces = {listenerInterface};707return AccessController.doPrivileged(new PrivilegedAction<T>() {708@SuppressWarnings("unchecked")709public T run() {710return (T) Proxy.newProxyInstance(loader, interfaces, handler);711}712});713}714715private static ClassLoader getClassLoader(Class<?> type) {716ReflectUtil.checkPackageAccess(type);717ClassLoader loader = type.getClassLoader();718if (loader == null) {719loader = Thread.currentThread().getContextClassLoader(); // avoid use of BCP720if (loader == null) {721loader = ClassLoader.getSystemClassLoader();722}723}724return loader;725}726}727728729