1 /* 2 * Copyright (c) 2000, 2009, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 package java.beans; 26 27 import java.lang.reflect.InvocationHandler; 28 import java.lang.reflect.InvocationTargetException; 29 import java.lang.reflect.Proxy; 30 import java.lang.reflect.Method; 31 import java.security.AccessControlContext; 32 import java.security.AccessController; 33 import java.security.PrivilegedAction; 34 35 import sun.reflect.misc.MethodUtil; 36 37 /** 38 * The <code>EventHandler</code> class provides 39 * support for dynamically generating event listeners whose methods 40 * execute a simple statement involving an incoming event object 41 * and a target object. 42 * <p> 43 * The <code>EventHandler</code> class is intended to be used by interactive tools, such as 44 * application builders, that allow developers to make connections between 45 * beans. Typically connections are made from a user interface bean 46 * (the event <em>source</em>) 47 * to an application logic bean (the <em>target</em>). The most effective 48 * connections of this kind isolate the application logic from the user 49 * interface. For example, the <code>EventHandler</code> for a 50 * connection from a <code>JCheckBox</code> to a method 51 * that accepts a boolean value can deal with extracting the state 52 * of the check box and passing it directly to the method so that 53 * the method is isolated from the user interface layer. 54 * <p> 55 * Inner classes are another, more general way to handle events from 56 * user interfaces. The <code>EventHandler</code> class 57 * handles only a subset of what is possible using inner 58 * classes. However, <code>EventHandler</code> works better 59 * with the long-term persistence scheme than inner classes. 60 * Also, using <code>EventHandler</code> in large applications in 61 * which the same interface is implemented many times can 62 * 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 small 66 * footprint is that the <code>Proxy</code> class, on which 67 * the <code>EventHandler</code> relies, shares implementations 68 * of identical 69 * interfaces. For example, if you use 70 * the <code>EventHandler</code> <code>create</code> methods to make 71 * all the <code>ActionListener</code>s in an application, 72 * all the action listeners will be instances of a single class 73 * (one created by the <code>Proxy</code> class). 74 * In general, listeners based on 75 * the <code>Proxy</code> class require one listener class 76 * to be created per <em>listener type</em> (interface), 77 * whereas the inner class 78 * 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 create 86 * an object that implements a given listener interface. 87 * This listener object uses an <code>EventHandler</code> object 88 * behind the scenes to encapsulate information about the 89 * event, the object to be sent a message when the event occurs, 90 * the message (method) to be sent, and any argument 91 * to the method. 92 * The following section gives examples of how to create listener 93 * 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 install 98 * 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 instance 101 * 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 statement 111 * <code>frame.toFront()</code> will be executed. One could get 112 * 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> is 128 * to extract a property value from the first argument 129 * 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> that 132 * sets the <code>nextFocusableComponent</code> property of the target 133 * (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> that 155 * just passes the incoming event object to the target's action. 156 * If the fourth <code>EventHandler.create</code> argument is 157 * 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 the 180 * <em>source</em> of the event object and set this value as 181 * the value of a property of the target object. 182 * In the following example we create an <code>ActionListener</code> that 183 * sets the "label" property of the target 184 * object to the value of the "text" property of the 185 * 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 number 207 * of property prefixes delimited with the "." character. The "qualifying" 208 * names that appear before the "." characters are taken as the names of 209 * properties that should be applied, left-most first, to 210 * the event object. 211 * <p> 212 * For example, the following action listener 213 * 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 class 221 * (assuming all the properties had canonical getter methods and 222 * 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 number 235 * of property prefixs delimited with the "." character. For example, the 236 * 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 class 241 * (assuming all the properties had canonical getter methods and 242 * 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 invoke 253 * 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 is 255 * 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 invoke 263 * the method that is appropriate based on the source. If the source is 264 * null, then either method is appropriate and the one that is invoked is 265 * undefined. For that reason we recommend against targeting overloaded 266 * methods. 267 * 268 * @see java.lang.reflect.Proxy 269 * @see java.util.EventObject 270 * 271 * @since 1.4 272 * 273 * @author Mark Davidson 274 * @author Philip Milne 275 * @author Hans Muller 276 * 277 */ 278 public class EventHandler implements InvocationHandler { 279 private Object target; 280 private String action; 281 private final String eventPropertyName; 282 private final String listenerMethodName; 283 private final AccessControlContext acc = AccessController.getContext(); 284 285 /** 286 * Creates a new <code>EventHandler</code> object; 287 * you generally use one of the <code>create</code> methods 288 * instead of invoking this constructor directly. Refer to 289 * {@link java.beans.EventHandler#create(Class, Object, String, String) 290 * the general version of create} for a complete description of 291 * the <code>eventPropertyName</code> and <code>listenerMethodName</code> 292 * parameter. 293 * 294 * @param target the object that will perform the action 295 * @param action the name of a (possibly qualified) property or method on 296 * the target 297 * @param eventPropertyName the (possibly qualified) name of a readable property of the incoming event 298 * @param listenerMethodName the name of the method in the listener interface that should trigger the action 299 * 300 * @throws NullPointerException if <code>target</code> is null 301 * @throws NullPointerException if <code>action</code> is null 302 * 303 * @see EventHandler 304 * @see #create(Class, Object, String, String, String) 305 * @see #getTarget 306 * @see #getAction 307 * @see #getEventPropertyName 308 * @see #getListenerMethodName 309 */ 310 @ConstructorProperties({"target", "action", "eventPropertyName", "listenerMethodName"}) 311 public EventHandler(Object target, String action, String eventPropertyName, String listenerMethodName) { 312 this.target = target; 313 this.action = action; 314 if (target == null) { 315 throw new NullPointerException("target must be non-null"); 316 } 317 if (action == null) { 318 throw new NullPointerException("action must be non-null"); 319 } 320 this.eventPropertyName = eventPropertyName; 321 this.listenerMethodName = listenerMethodName; 322 } 323 324 /** 325 * Returns the object to which this event handler will send a message. 326 * 327 * @return the target of this event handler 328 * @see #EventHandler(Object, String, String, String) 329 */ 330 public Object getTarget() { 331 return target; 332 } 333 334 /** 335 * Returns the name of the target's writable property 336 * that this event handler will set, 337 * or the name of the method that this event handler 338 * will invoke on the target. 339 * 340 * @return the action of this event handler 341 * @see #EventHandler(Object, String, String, String) 342 */ 343 public String getAction() { 344 return action; 345 } 346 347 /** 348 * Returns the property of the event that should be 349 * used in the action applied to the target. 350 * 351 * @return the property of the event 352 * 353 * @see #EventHandler(Object, String, String, String) 354 */ 355 public String getEventPropertyName() { 356 return eventPropertyName; 357 } 358 359 /** 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 the 362 * listener interface trigger the action. 363 * 364 * @return the name of the method that will trigger the action 365 * 366 * @see #EventHandler(Object, String, String, String) 367 */ 368 public String getListenerMethodName() { 369 return listenerMethodName; 370 } 371 372 private Object applyGetters(Object target, String getters) { 373 if (getters == null || getters.equals("")) { 374 return target; 375 } 376 int firstDot = getters.indexOf('.'); 377 if (firstDot == -1) { 378 firstDot = getters.length(); 379 } 380 String first = getters.substring(0, firstDot); 381 String rest = getters.substring(Math.min(firstDot + 1, getters.length())); 382 383 try { 384 Method getter = null; 385 if (target != null) { 386 getter = Statement.getMethod(target.getClass(), 387 "get" + NameGenerator.capitalize(first), 388 new Class<?>[]{}); 389 if (getter == null) { 390 getter = Statement.getMethod(target.getClass(), 391 "is" + NameGenerator.capitalize(first), 392 new Class<?>[]{}); 393 } 394 if (getter == null) { 395 getter = Statement.getMethod(target.getClass(), first, new Class<?>[]{}); 396 } 397 } 398 if (getter == null) { 399 throw new RuntimeException("No method called: " + first + 400 " defined on " + target); 401 } 402 Object newTarget = MethodUtil.invoke(getter, target, new Object[]{}); 403 return applyGetters(newTarget, rest); 404 } 405 catch (Exception e) { 406 throw new RuntimeException("Failed to call method: " + first + 407 " on " + target, e); 408 } 409 } 410 411 /** 412 * Extract the appropriate property value from the event and 413 * pass it to the action associated with 414 * this <code>EventHandler</code>. 415 * 416 * @param proxy the proxy object 417 * @param method the method in the listener interface 418 * @return the result of applying the action to the target 419 * 420 * @see EventHandler 421 */ 422 public Object invoke(final Object proxy, final Method method, final Object[] arguments) { 423 AccessControlContext acc = this.acc; 424 if ((acc == null) && (System.getSecurityManager() != null)) { 425 throw new SecurityException("AccessControlContext is not set"); 426 } 427 return AccessController.doPrivileged(new PrivilegedAction<Object>() { 428 public Object run() { 429 return invokeInternal(proxy, method, arguments); 430 } 431 }, acc); 432 } 433 434 private Object invokeInternal(Object proxy, Method method, Object[] arguments) { 435 String methodName = method.getName(); 436 if (method.getDeclaringClass() == Object.class) { 437 // Handle the Object public methods. 438 if (methodName.equals("hashCode")) { 439 return new Integer(System.identityHashCode(proxy)); 440 } else if (methodName.equals("equals")) { 441 return (proxy == arguments[0] ? Boolean.TRUE : Boolean.FALSE); 442 } else if (methodName.equals("toString")) { 443 return proxy.getClass().getName() + '@' + Integer.toHexString(proxy.hashCode()); 444 } 445 } 446 447 if (listenerMethodName == null || listenerMethodName.equals(methodName)) { 448 Class[] argTypes = null; 449 Object[] newArgs = null; 450 451 if (eventPropertyName == null) { // Nullary method. 452 newArgs = new Object[]{}; 453 argTypes = new Class<?>[]{}; 454 } 455 else { 456 Object input = applyGetters(arguments[0], getEventPropertyName()); 457 newArgs = new Object[]{input}; 458 argTypes = new Class<?>[]{input == null ? null : 459 input.getClass()}; 460 } 461 try { 462 int lastDot = action.lastIndexOf('.'); 463 if (lastDot != -1) { 464 target = applyGetters(target, action.substring(0, lastDot)); 465 action = action.substring(lastDot + 1); 466 } 467 Method targetMethod = Statement.getMethod( 468 target.getClass(), action, argTypes); 469 if (targetMethod == null) { 470 targetMethod = Statement.getMethod(target.getClass(), 471 "set" + NameGenerator.capitalize(action), argTypes); 472 } 473 if (targetMethod == null) { 474 String argTypeString = (argTypes.length == 0) 475 ? " with no arguments" 476 : " with argument " + argTypes[0]; 477 throw new RuntimeException( 478 "No method called " + action + " on " + 479 target.getClass() + argTypeString); 480 } 481 return MethodUtil.invoke(targetMethod, target, newArgs); 482 } 483 catch (IllegalAccessException ex) { 484 throw new RuntimeException(ex); 485 } 486 catch (InvocationTargetException ex) { 487 Throwable th = ex.getTargetException(); 488 throw (th instanceof RuntimeException) 489 ? (RuntimeException) th 490 : new RuntimeException(th); 491 } 492 } 493 return null; 494 } 495 496 /** 497 * Creates an implementation of <code>listenerInterface</code> in which 498 * <em>all</em> of the methods in the listener interface apply 499 * the handler's <code>action</code> to the <code>target</code>. This 500 * method is implemented by calling the other, more general, 501 * implementation of the <code>create</code> method with both 502 * the <code>eventPropertyName</code> and the <code>listenerMethodName</code> 503 * taking the value <code>null</code>. Refer to 504 * {@link java.beans.EventHandler#create(Class, Object, String, String) 505 * the general version of create} for a complete description of 506 * the <code>action</code> parameter. 507 * <p> 508 * To create an <code>ActionListener</code> that shows a 509 * <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 listenerInterface the listener interface to create a proxy for 519 * @param target the object that will perform the action 520 * @param action the name of a (possibly qualified) property or method on 521 * the target 522 * @return an object that implements <code>listenerInterface</code> 523 * 524 * @throws NullPointerException if <code>listenerInterface</code> is null 525 * @throws NullPointerException if <code>target</code> is null 526 * @throws NullPointerException if <code>action</code> is null 527 * 528 * @see #create(Class, Object, String, String) 529 */ 530 public static <T> T create(Class<T> listenerInterface, 531 Object target, String action) 532 { 533 return create(listenerInterface, target, action, null, null); 534 } 535 536 /** 537 /** 538 * Creates an implementation of <code>listenerInterface</code> in which 539 * <em>all</em> of the methods pass the value of the event 540 * expression, <code>eventPropertyName</code>, to the final method in the 541 * statement, <code>action</code>, which is applied to the <code>target</code>. 542 * This method is implemented by calling the 543 * more general, implementation of the <code>create</code> method with 544 * the <code>listenerMethodName</code> taking the value <code>null</code>. 545 * Refer to 546 * {@link java.beans.EventHandler#create(Class, Object, String, String) 547 * the general version of create} for a complete description of 548 * the <code>action</code> and <code>eventPropertyName</code> parameters. 549 * <p> 550 * To create an <code>ActionListener</code> that sets the 551 * the text of a <code>JLabel</code> to the text value of 552 * the <code>JTextField</code> source of the incoming event, 553 * you can use the following code: 554 * 555 *<blockquote> 556 *<pre> 557 *EventHandler.create(ActionListener.class, label, "text", "source.text"); 558 *</pre> 559 *</blockquote> 560 * 561 * This is equivalent to the following code: 562 *<blockquote> 563 *<pre> 564 //Equivalent code using an inner class instead of EventHandler. 565 *new ActionListener() { 566 * public void actionPerformed(ActionEvent event) { 567 * label.setText(((JTextField)(event.getSource())).getText()); 568 * } 569 *}; 570 *</pre> 571 *</blockquote> 572 * 573 * @param listenerInterface the listener interface to create a proxy for 574 * @param target the object that will perform the action 575 * @param action the name of a (possibly qualified) property or method on 576 * the target 577 * @param eventPropertyName the (possibly qualified) name of a readable property of the incoming event 578 * 579 * @return an object that implements <code>listenerInterface</code> 580 * 581 * @throws NullPointerException if <code>listenerInterface</code> is null 582 * @throws NullPointerException if <code>target</code> is null 583 * @throws NullPointerException if <code>action</code> is null 584 * 585 * @see #create(Class, Object, String, String, String) 586 */ 587 public static <T> T create(Class<T> listenerInterface, 588 Object target, String action, 589 String eventPropertyName) 590 { 591 return create(listenerInterface, target, action, eventPropertyName, null); 592 } 593 594 /** 595 * Creates an implementation of <code>listenerInterface</code> in which 596 * the method named <code>listenerMethodName</code> 597 * passes the value of the event expression, <code>eventPropertyName</code>, 598 * to the final method in the statement, <code>action</code>, which 599 * is applied to the <code>target</code>. All of the other listener 600 * methods do nothing. 601 * <p> 602 * The <code>eventPropertyName</code> string is used to extract a value 603 * from the incoming event object that is passed to the target 604 * method. The common case is the target method takes no arguments, in 605 * which case a value of null should be used for the 606 * <code>eventPropertyName</code>. Alternatively if you want 607 * the incoming event object passed directly to the target method use 608 * the empty string. 609 * The format of the <code>eventPropertyName</code> string is a sequence of 610 * methods or properties where each method or 611 * property is applied to the value returned by the preceeding method 612 * starting from the incoming event object. 613 * The syntax is: <code>propertyName{.propertyName}*</code> 614 * where <code>propertyName</code> matches a method or 615 * property. For example, to extract the <code>point</code> 616 * property from a <code>MouseEvent</code>, you could use either 617 * <code>"point"</code> or <code>"getPoint"</code> as the 618 * <code>eventPropertyName</code>. To extract the "text" property from 619 * a <code>MouseEvent</code> with a <code>JLabel</code> source use any 620 * of the following as <code>eventPropertyName</code>: 621 * <code>"source.text"</code>, 622 * <code>"getSource.text"</code> <code>"getSource.getText"</code> or 623 * <code>"source.getText"</code>. If a method can not be found, or an 624 * exception is generated as part of invoking a method a 625 * <code>RuntimeException</code> will be thrown at dispatch time. For 626 * example, if the incoming event object is null, and 627 * <code>eventPropertyName</code> is non-null and not empty, a 628 * <code>RuntimeException</code> will be thrown. 629 * <p> 630 * The <code>action</code> argument is of the same format as the 631 * <code>eventPropertyName</code> argument where the last property name 632 * identifies either a method name or writable property. 633 * <p> 634 * If the <code>listenerMethodName</code> is <code>null</code> 635 * <em>all</em> methods in the interface trigger the <code>action</code> to be 636 * executed on the <code>target</code>. 637 * <p> 638 * For example, to create a <code>MouseListener</code> that sets the target 639 * object's <code>origin</code> property to the incoming <code>MouseEvent</code>'s 640 * location (that's the value of <code>mouseEvent.getPoint()</code>) each 641 * time a mouse button is pressed, one would write: 642 *<blockquote> 643 *<pre> 644 *EventHandler.create(MouseListener.class, target, "origin", "point", "mousePressed"); 645 *</pre> 646 *</blockquote> 647 * 648 * This is comparable to writing a <code>MouseListener</code> in which all 649 * of the methods except <code>mousePressed</code> are no-ops: 650 * 651 *<blockquote> 652 *<pre> 653 //Equivalent code using an inner class instead of EventHandler. 654 *new MouseAdapter() { 655 * public void mousePressed(MouseEvent e) { 656 * target.setOrigin(e.getPoint()); 657 * } 658 *}; 659 * </pre> 660 *</blockquote> 661 * 662 * @param listenerInterface the listener interface to create a proxy for 663 * @param target the object that will perform the action 664 * @param action the name of a (possibly qualified) property or method on 665 * the target 666 * @param eventPropertyName the (possibly qualified) name of a readable property of the incoming event 667 * @param listenerMethodName the name of the method in the listener interface that should trigger the action 668 * 669 * @return an object that implements <code>listenerInterface</code> 670 * 671 * @throws NullPointerException if <code>listenerInterface</code> is null 672 * @throws NullPointerException if <code>target</code> is null 673 * @throws NullPointerException if <code>action</code> is null 674 * 675 * @see EventHandler 676 */ 677 @SuppressWarnings("unchecked") 678 public static <T> T create(Class<T> listenerInterface, 679 Object target, String action, 680 String eventPropertyName, 681 String listenerMethodName) 682 { 683 // Create this first to verify target/action are non-null 684 EventHandler eventHandler = new EventHandler(target, action, 685 eventPropertyName, 686 listenerMethodName); 687 if (listenerInterface == null) { 688 throw new NullPointerException( 689 "listenerInterface must be non-null"); 690 } 691 return (T)Proxy.newProxyInstance(target.getClass().getClassLoader(), 692 new Class<?>[] {listenerInterface}, 693 eventHandler); 694 } 695 }