Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/src/share/classes/java/beans/EventHandler.java
38829 views
/*1* Copyright (c) 2000, 2013, 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</code> 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</code> 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</code> for a50* connection from a <code>JCheckBox</code> 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</code> class57* handles only a subset of what is possible using inner58* classes. However, <code>EventHandler</code> works better59* with the long-term persistence scheme than inner classes.60* Also, using <code>EventHandler</code> 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</code>65* have such a small66* footprint is that the <code>Proxy</code> class, on which67* the <code>EventHandler</code> relies, shares implementations68* of identical69* interfaces. For example, if you use70* the <code>EventHandler</code> <code>create</code> methods to make71* all the <code>ActionListener</code>s in an application,72* all the action listeners will be instances of a single class73* (one created by the <code>Proxy</code> class).74* In general, listeners based on75* the <code>Proxy</code> 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</code>83* instances.84* Instead, you use one of the <code>EventHandler</code>85* <code>create</code> methods to create86* an object that implements a given listener interface.87* This listener object uses an <code>EventHandler</code> 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</code> methods.94*95* <h2>Examples of Using EventHandler</h2>96*97* The simplest use of <code>EventHandler</code> 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</code>100* that invokes the <code>toFront</code> method on an instance101* of <code>javax.swing.JFrame</code>.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</code> is pressed, the statement111* <code>frame.toFront()</code> 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</code>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</code> 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</code> that132* sets the <code>nextFocusableComponent</code> 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</code> that155* just passes the incoming event object to the target's action.156* If the fourth <code>EventHandler.create</code> 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</code>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</code> 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</code> 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</code> 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</code> 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;283private final AccessControlContext acc = AccessController.getContext();284285/**286* Creates a new <code>EventHandler</code> object;287* you generally use one of the <code>create</code> methods288* instead of invoking this constructor directly. Refer to289* {@link java.beans.EventHandler#create(Class, Object, String, String)290* the general version of create} for a complete description of291* the <code>eventPropertyName</code> and <code>listenerMethodName</code>292* parameter.293*294* @param target the object that will perform the action295* @param action the name of a (possibly qualified) property or method on296* the target297* @param eventPropertyName the (possibly qualified) name of a readable property of the incoming event298* @param listenerMethodName the name of the method in the listener interface that should trigger the action299*300* @throws NullPointerException if <code>target</code> is null301* @throws NullPointerException if <code>action</code> is null302*303* @see EventHandler304* @see #create(Class, Object, String, String, String)305* @see #getTarget306* @see #getAction307* @see #getEventPropertyName308* @see #getListenerMethodName309*/310@ConstructorProperties({"target", "action", "eventPropertyName", "listenerMethodName"})311public EventHandler(Object target, String action, String eventPropertyName, String listenerMethodName) {312this.target = target;313this.action = action;314if (target == null) {315throw new NullPointerException("target must be non-null");316}317if (action == null) {318throw new NullPointerException("action must be non-null");319}320this.eventPropertyName = eventPropertyName;321this.listenerMethodName = listenerMethodName;322}323324/**325* Returns the object to which this event handler will send a message.326*327* @return the target of this event handler328* @see #EventHandler(Object, String, String, String)329*/330public Object getTarget() {331return target;332}333334/**335* Returns the name of the target's writable property336* that this event handler will set,337* or the name of the method that this event handler338* will invoke on the target.339*340* @return the action of this event handler341* @see #EventHandler(Object, String, String, String)342*/343public String getAction() {344return action;345}346347/**348* Returns the property of the event that should be349* used in the action applied to the target.350*351* @return the property of the event352*353* @see #EventHandler(Object, String, String, String)354*/355public String getEventPropertyName() {356return eventPropertyName;357}358359/**360* Returns the name of the method that will trigger the action.361* A return value of <code>null</code> signifies that all methods in the362* listener interface trigger the action.363*364* @return the name of the method that will trigger the action365*366* @see #EventHandler(Object, String, String, String)367*/368public String getListenerMethodName() {369return listenerMethodName;370}371372private Object applyGetters(Object target, String getters) {373if (getters == null || getters.equals("")) {374return target;375}376int firstDot = getters.indexOf('.');377if (firstDot == -1) {378firstDot = getters.length();379}380String first = getters.substring(0, firstDot);381String rest = getters.substring(Math.min(firstDot + 1, getters.length()));382383try {384Method getter = null;385if (target != null) {386getter = Statement.getMethod(target.getClass(),387"get" + NameGenerator.capitalize(first),388new Class<?>[]{});389if (getter == null) {390getter = Statement.getMethod(target.getClass(),391"is" + NameGenerator.capitalize(first),392new Class<?>[]{});393}394if (getter == null) {395getter = Statement.getMethod(target.getClass(), first, new Class<?>[]{});396}397}398if (getter == null) {399throw new RuntimeException("No method called: " + first +400" defined on " + target);401}402Object newTarget = MethodUtil.invoke(getter, target, new Object[]{});403return applyGetters(newTarget, rest);404}405catch (Exception e) {406throw new RuntimeException("Failed to call method: " + first +407" on " + target, e);408}409}410411/**412* Extract the appropriate property value from the event and413* pass it to the action associated with414* this <code>EventHandler</code>.415*416* @param proxy the proxy object417* @param method the method in the listener interface418* @return the result of applying the action to the target419*420* @see EventHandler421*/422public Object invoke(final Object proxy, final Method method, final Object[] arguments) {423AccessControlContext acc = this.acc;424if ((acc == null) && (System.getSecurityManager() != null)) {425throw new SecurityException("AccessControlContext is not set");426}427return AccessController.doPrivileged(new PrivilegedAction<Object>() {428public Object run() {429return invokeInternal(proxy, method, arguments);430}431}, acc);432}433434private Object invokeInternal(Object proxy, Method method, Object[] arguments) {435String methodName = method.getName();436if (method.getDeclaringClass() == Object.class) {437// Handle the Object public methods.438if (methodName.equals("hashCode")) {439return new Integer(System.identityHashCode(proxy));440} else if (methodName.equals("equals")) {441return (proxy == arguments[0] ? Boolean.TRUE : Boolean.FALSE);442} else if (methodName.equals("toString")) {443return proxy.getClass().getName() + '@' + Integer.toHexString(proxy.hashCode());444}445}446447if (listenerMethodName == null || listenerMethodName.equals(methodName)) {448Class[] argTypes = null;449Object[] newArgs = null;450451if (eventPropertyName == null) { // Nullary method.452newArgs = new Object[]{};453argTypes = new Class<?>[]{};454}455else {456Object input = applyGetters(arguments[0], getEventPropertyName());457newArgs = new Object[]{input};458argTypes = new Class<?>[]{input == null ? null :459input.getClass()};460}461try {462int lastDot = action.lastIndexOf('.');463if (lastDot != -1) {464target = applyGetters(target, action.substring(0, lastDot));465action = action.substring(lastDot + 1);466}467Method targetMethod = Statement.getMethod(468target.getClass(), action, argTypes);469if (targetMethod == null) {470targetMethod = Statement.getMethod(target.getClass(),471"set" + NameGenerator.capitalize(action), argTypes);472}473if (targetMethod == null) {474String argTypeString = (argTypes.length == 0)475? " with no arguments"476: " with argument " + argTypes[0];477throw new RuntimeException(478"No method called " + action + " on " +479target.getClass() + argTypeString);480}481return MethodUtil.invoke(targetMethod, target, newArgs);482}483catch (IllegalAccessException ex) {484throw new RuntimeException(ex);485}486catch (InvocationTargetException ex) {487Throwable th = ex.getTargetException();488throw (th instanceof RuntimeException)489? (RuntimeException) th490: new RuntimeException(th);491}492}493return null;494}495496/**497* Creates an implementation of <code>listenerInterface</code> in which498* <em>all</em> of the methods in the listener interface apply499* the handler's <code>action</code> to the <code>target</code>. This500* method is implemented by calling the other, more general,501* implementation of the <code>create</code> method with both502* the <code>eventPropertyName</code> and the <code>listenerMethodName</code>503* taking the value <code>null</code>. Refer to504* {@link java.beans.EventHandler#create(Class, Object, String, String)505* the general version of create} for a complete description of506* the <code>action</code> parameter.507* <p>508* To create an <code>ActionListener</code> that shows a509* <code>JDialog</code> with <code>dialog.show()</code>,510* one can write:511*512*<blockquote>513*<pre>514*EventHandler.create(ActionListener.class, dialog, "show")515*</pre>516*</blockquote>517*518* @param <T> the type to create519* @param listenerInterface the listener interface to create a proxy for520* @param target the object that will perform the action521* @param action the name of a (possibly qualified) property or method on522* the target523* @return an object that implements <code>listenerInterface</code>524*525* @throws NullPointerException if <code>listenerInterface</code> is null526* @throws NullPointerException if <code>target</code> is null527* @throws NullPointerException if <code>action</code> is null528*529* @see #create(Class, Object, String, String)530*/531public static <T> T create(Class<T> listenerInterface,532Object target, String action)533{534return create(listenerInterface, target, action, null, null);535}536537/**538/**539* Creates an implementation of <code>listenerInterface</code> in which540* <em>all</em> of the methods pass the value of the event541* expression, <code>eventPropertyName</code>, to the final method in the542* statement, <code>action</code>, which is applied to the <code>target</code>.543* This method is implemented by calling the544* more general, implementation of the <code>create</code> method with545* the <code>listenerMethodName</code> taking the value <code>null</code>.546* Refer to547* {@link java.beans.EventHandler#create(Class, Object, String, String)548* the general version of create} for a complete description of549* the <code>action</code> and <code>eventPropertyName</code> parameters.550* <p>551* To create an <code>ActionListener</code> that sets the552* the text of a <code>JLabel</code> to the text value of553* the <code>JTextField</code> source of the incoming event,554* you can use the following code:555*556*<blockquote>557*<pre>558*EventHandler.create(ActionListener.class, label, "text", "source.text");559*</pre>560*</blockquote>561*562* This is equivalent to the following code:563*<blockquote>564*<pre>565//Equivalent code using an inner class instead of EventHandler.566*new ActionListener() {567* public void actionPerformed(ActionEvent event) {568* label.setText(((JTextField)(event.getSource())).getText());569* }570*};571*</pre>572*</blockquote>573*574* @param <T> the type to create575* @param listenerInterface the listener interface to create a proxy for576* @param target the object that will perform the action577* @param action the name of a (possibly qualified) property or method on578* the target579* @param eventPropertyName the (possibly qualified) name of a readable property of the incoming event580*581* @return an object that implements <code>listenerInterface</code>582*583* @throws NullPointerException if <code>listenerInterface</code> is null584* @throws NullPointerException if <code>target</code> is null585* @throws NullPointerException if <code>action</code> is null586*587* @see #create(Class, Object, String, String, String)588*/589public static <T> T create(Class<T> listenerInterface,590Object target, String action,591String eventPropertyName)592{593return create(listenerInterface, target, action, eventPropertyName, null);594}595596/**597* Creates an implementation of <code>listenerInterface</code> in which598* the method named <code>listenerMethodName</code>599* passes the value of the event expression, <code>eventPropertyName</code>,600* to the final method in the statement, <code>action</code>, which601* is applied to the <code>target</code>. All of the other listener602* methods do nothing.603* <p>604* The <code>eventPropertyName</code> string is used to extract a value605* from the incoming event object that is passed to the target606* method. The common case is the target method takes no arguments, in607* which case a value of null should be used for the608* <code>eventPropertyName</code>. Alternatively if you want609* the incoming event object passed directly to the target method use610* the empty string.611* The format of the <code>eventPropertyName</code> string is a sequence of612* methods or properties where each method or613* property is applied to the value returned by the preceding method614* starting from the incoming event object.615* The syntax is: <code>propertyName{.propertyName}*</code>616* where <code>propertyName</code> matches a method or617* property. For example, to extract the <code>point</code>618* property from a <code>MouseEvent</code>, you could use either619* <code>"point"</code> or <code>"getPoint"</code> as the620* <code>eventPropertyName</code>. To extract the "text" property from621* a <code>MouseEvent</code> with a <code>JLabel</code> source use any622* of the following as <code>eventPropertyName</code>:623* <code>"source.text"</code>,624* <code>"getSource.text"</code> <code>"getSource.getText"</code> or625* <code>"source.getText"</code>. If a method can not be found, or an626* exception is generated as part of invoking a method a627* <code>RuntimeException</code> will be thrown at dispatch time. For628* example, if the incoming event object is null, and629* <code>eventPropertyName</code> is non-null and not empty, a630* <code>RuntimeException</code> will be thrown.631* <p>632* The <code>action</code> argument is of the same format as the633* <code>eventPropertyName</code> argument where the last property name634* identifies either a method name or writable property.635* <p>636* If the <code>listenerMethodName</code> is <code>null</code>637* <em>all</em> methods in the interface trigger the <code>action</code> to be638* executed on the <code>target</code>.639* <p>640* For example, to create a <code>MouseListener</code> that sets the target641* object's <code>origin</code> property to the incoming <code>MouseEvent</code>'s642* location (that's the value of <code>mouseEvent.getPoint()</code>) each643* time a mouse button is pressed, one would write:644*<blockquote>645*<pre>646*EventHandler.create(MouseListener.class, target, "origin", "point", "mousePressed");647*</pre>648*</blockquote>649*650* This is comparable to writing a <code>MouseListener</code> in which all651* of the methods except <code>mousePressed</code> are no-ops:652*653*<blockquote>654*<pre>655//Equivalent code using an inner class instead of EventHandler.656*new MouseAdapter() {657* public void mousePressed(MouseEvent e) {658* target.setOrigin(e.getPoint());659* }660*};661* </pre>662*</blockquote>663*664* @param <T> the type to create665* @param listenerInterface the listener interface to create a proxy for666* @param target the object that will perform the action667* @param action the name of a (possibly qualified) property or method on668* the target669* @param eventPropertyName the (possibly qualified) name of a readable property of the incoming event670* @param listenerMethodName the name of the method in the listener interface that should trigger the action671*672* @return an object that implements <code>listenerInterface</code>673*674* @throws NullPointerException if <code>listenerInterface</code> is null675* @throws NullPointerException if <code>target</code> is null676* @throws NullPointerException if <code>action</code> is null677*678* @see EventHandler679*/680public static <T> T create(Class<T> listenerInterface,681Object target, String action,682String eventPropertyName,683String listenerMethodName)684{685// Create this first to verify target/action are non-null686final EventHandler handler = new EventHandler(target, action,687eventPropertyName,688listenerMethodName);689if (listenerInterface == null) {690throw new NullPointerException(691"listenerInterface must be non-null");692}693final ClassLoader loader = getClassLoader(listenerInterface);694final Class<?>[] interfaces = {listenerInterface};695return AccessController.doPrivileged(new PrivilegedAction<T>() {696@SuppressWarnings("unchecked")697public T run() {698return (T) Proxy.newProxyInstance(loader, interfaces, handler);699}700});701}702703private static ClassLoader getClassLoader(Class<?> type) {704ReflectUtil.checkPackageAccess(type);705ClassLoader loader = type.getClassLoader();706if (loader == null) {707loader = Thread.currentThread().getContextClassLoader(); // avoid use of BCP708if (loader == null) {709loader = ClassLoader.getSystemClassLoader();710}711}712return loader;713}714}715716717