1. /*
  2. * @(#)EventHandler.java 1.10 03/01/23
  3. *
  4. * Copyright 2003 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package java.beans;
  8. import java.lang.reflect.InvocationHandler;
  9. import java.lang.reflect.InvocationTargetException;
  10. import java.lang.reflect.Proxy;
  11. import java.lang.reflect.Method;
  12. import java.util.EventObject;
  13. /**
  14. * The <code>EventHandler</code> class provides
  15. * support for dynamically generating event listeners whose methods
  16. * execute a simple statement involving an incoming event object
  17. * and a target object.
  18. * <p>
  19. * The <code>EventHandler</code> class is intended to be used by interactive tools, such as
  20. * application builders, that allow developers to make connections between
  21. * beans. Typically connections are made from a user interface bean
  22. * (the event <em>source</em>)
  23. * to an application logic bean (the <em>target</em>). The most effective
  24. * connections of this kind isolate the application logic from the user
  25. * interface. For example, the <code>EventHandler</code> for a
  26. * connection from a <code>JCheckBox</code> to a method
  27. * that accepts a boolean value can deal with extracting the state
  28. * of the check box and passing it directly to the method so that
  29. * the method is isolated from the user interface layer.
  30. * <p>
  31. * Inner classes are another, more general way to handle events from
  32. * user interfaces. The <code>EventHandler</code> class
  33. * handles only a subset of what is possible using inner
  34. * classes. However, <code>EventHandler</code> works better
  35. * with the long-term persistence scheme than inner classes.
  36. * Also, using <code>EventHandler</code> in large applications in
  37. * which the same interface is implemented many times can
  38. * reduce the disk and memory footprint of the application.
  39. * <p>
  40. * The reason that listeners created with <code>EventHandler</code>
  41. * have such a small
  42. * footprint is that the <code>Proxy</code> class, on which
  43. * the <code>EventHandler</code> relies, shares implementations
  44. * of identical
  45. * interfaces. For example, if you use
  46. * the <code>EventHandler</code> <code>create</code> methods to make
  47. * all the <code>ActionListener</code>s in an application,
  48. * all the action listeners will be instances of a single class
  49. * (one created by the <code>Proxy</code> class).
  50. * In general, listeners based on
  51. * the <code>Proxy</code> class require one listener class
  52. * to be created per <em>listener type</em> (interface),
  53. * whereas the inner class
  54. * approach requires one class to be created per <em>listener</em>
  55. * (object that implements the interface).
  56. *
  57. * <p>
  58. * You don't generally deal directly with <code>EventHandler</code>
  59. * instances.
  60. * Instead, you use one of the <code>EventHandler</code>
  61. * <code>create</code> methods to create
  62. * an object that implements a given listener interface.
  63. * This listener object uses an <code>EventHandler</code> object
  64. * behind the scenes to encapsulate information about the
  65. * event, the object to be sent a message when the event occurs,
  66. * the message (method) to be sent, and any argument
  67. * to the method.
  68. * The following section gives examples of how to create listener
  69. * objects using the <code>create</code> methods.
  70. *
  71. * <h2>Examples of Using EventHandler</h2>
  72. *
  73. * The simplest use of <code>EventHandler</code> is to install
  74. * a listener that calls a method on the target object with no arguments.
  75. * In the following example we create an <code>ActionListener</code>
  76. * that invokes the <code>toFront</code> method on an instance
  77. * of <code>javax.swing.JFrame</code>.
  78. *
  79. * <blockquote>
  80. *<pre>
  81. *myButton.addActionListener(
  82. * (ActionListener)EventHandler.create(ActionListener.class, frame, "toFront"));
  83. *</pre>
  84. * </blockquote>
  85. *
  86. * When <code>myButton</code> is pressed, the statement
  87. * <code>frame.toFront()</code> will be executed. One could get
  88. * the same effect, with some additional compile-time type safety,
  89. * by defining a new implementation of the <code>ActionListener</code>
  90. * interface and adding an instance of it to the button:
  91. *
  92. * <blockquote>
  93. *<pre>
  94. //Equivalent code using an inner class instead of EventHandler.
  95. *myButton.addActionListener(new ActionListener() {
  96. * public void actionPerformed(ActionEvent e) {
  97. * frame.toFront();
  98. * }
  99. *});
  100. *</pre>
  101. * </blockquote>
  102. *
  103. * The next simplest use of <code>EventHandler</code> is
  104. * to extract a property value from the first argument
  105. * of the method in the listener interface (typically an event object)
  106. * and use it to set the value of a property in the target object.
  107. * In the following example we create an <code>ActionListener</code> that
  108. * sets the <code>nextFocusableComponent</code> property of the target
  109. * object to the value of the "source" property of the event.
  110. *
  111. * <blockquote>
  112. *<pre>
  113. *EventHandler.create(ActionListener.class, target, "nextFocusableComponent", "source")
  114. *</pre>
  115. * </blockquote>
  116. *
  117. * This would correspond to the following inner class implementation:
  118. *
  119. * <blockquote>
  120. *<pre>
  121. //Equivalent code using an inner class instead of EventHandler.
  122. *new ActionListener() {
  123. * public void actionPerformed(ActionEvent e) {
  124. * button.setNextFocusableComponent((Component)e.getSource());
  125. * }
  126. *}
  127. *</pre>
  128. * </blockquote>
  129. *
  130. * Probably the most common use of <code>EventHandler</code>
  131. * is to extract a property value from the
  132. * <em>source</em> of the event object and set this value as
  133. * the value of a property of the target object.
  134. * In the following example we create an <code>ActionListener</code> that
  135. * sets the "label" property of the target
  136. * object to the value of the "text" property of the
  137. * source (the value of the "source" property) of the event.
  138. *
  139. * <blockquote>
  140. *<pre>
  141. *EventHandler.create(ActionListener.class, button, "label", "source.text")
  142. *</pre>
  143. * </blockquote>
  144. *
  145. * This would correspond to the following inner class implementation:
  146. *
  147. * <blockquote>
  148. *<pre>
  149. //Equivalent code using an inner class instead of EventHandler.
  150. *new ActionListener {
  151. * public void actionPerformed(ActionEvent e) {
  152. * button.setLabel(((JTextField)e.getSource()).getText());
  153. * }
  154. *}
  155. *</pre>
  156. * </blockquote>
  157. *
  158. * The event property may be be "qualified" with an arbitrary number
  159. * of property prefixes delimited with the "." character. The "qualifying"
  160. * names that appear before the "." characters are taken as the names of
  161. * properties that should be applied, left-most first, to
  162. * the event object.
  163. * <p>
  164. * For example, the following action listener
  165. *
  166. * <blockquote>
  167. *<pre>
  168. *EventHandler.create(ActionListener.class, target, "a", "b.c.d")
  169. *</pre>
  170. * </blockquote>
  171. *
  172. * might be written as the following inner class
  173. * (assuming all the properties had canonical getter methods and
  174. * returned the appropriate types):
  175. *
  176. * <blockquote>
  177. *<pre>
  178. //Equivalent code using an inner class instead of EventHandler.
  179. *new ActionListener {
  180. * public void actionPerformed(ActionEvent e) {
  181. * target.setA(e.getB().getC().isD());
  182. * }
  183. *}
  184. *</pre>
  185. * </blockquote>
  186. *
  187. * @see java.lang.reflect.Proxy
  188. * @see java.util.EventObject
  189. *
  190. * @since 1.4
  191. *
  192. * @author Mark Davidson
  193. * @author Philip Milne
  194. * @author Hans Muller
  195. *
  196. * @version 1.2 10/24/00
  197. */
  198. public class EventHandler implements InvocationHandler {
  199. private static Object[] empty = new Object[]{};
  200. private Object target;
  201. private Method targetMethod;
  202. private String action;
  203. private String eventPropertyName;
  204. private String listenerMethodName;
  205. /**
  206. * Creates a new <code>EventHandler</code> object;
  207. * you generally use one of the <code>create</code> methods
  208. * instead of invoking this constructor directly.
  209. *
  210. * @param target the object that will perform the action
  211. * @param action the (possibly qualified) name of a writable property or method on the target
  212. * @param eventPropertyName the (possibly qualified) name of a readable property of the incoming event
  213. * @param listenerMethodName the name of the method in the listener interface that should trigger the action
  214. *
  215. * @see EventHandler
  216. * @see #create(Class, Object, String, String, String)
  217. * @see #getTarget
  218. * @see #getAction
  219. * @see #getEventPropertyName
  220. * @see #getListenerMethodName
  221. */
  222. public EventHandler(Object target, String action, String eventPropertyName, String listenerMethodName) {
  223. this.target = target;
  224. this.action = action;
  225. this.eventPropertyName = eventPropertyName;
  226. this.listenerMethodName = listenerMethodName;
  227. }
  228. /**
  229. * Returns the object to which this event handler will send a message.
  230. *
  231. * @return the target of this event handler
  232. * @see #EventHandler(Object, String, String, String)
  233. */
  234. public Object getTarget() {
  235. return target;
  236. }
  237. /**
  238. * Returns the name of the target's writable property
  239. * that this event handler will set,
  240. * or the name of the method that this event handler
  241. * will invoke on the target.
  242. *
  243. * @return the action of this event handler
  244. * @see #EventHandler(Object, String, String, String)
  245. */
  246. public String getAction() {
  247. return action;
  248. }
  249. /**
  250. * Returns the property of the event that should be
  251. * used in the action applied to the target.
  252. *
  253. * @return the property of the event
  254. *
  255. * @see #EventHandler(Object, String, String, String)
  256. */
  257. public String getEventPropertyName() {
  258. return eventPropertyName;
  259. }
  260. /**
  261. * Returns the name of the method that will trigger the action.
  262. * A return value of <code>null</code> signifies that all methods in the
  263. * listener interface trigger the action.
  264. *
  265. * @return the name of the method that will trigger the action
  266. *
  267. * @see #EventHandler(Object, String, String, String)
  268. */
  269. public String getListenerMethodName() {
  270. return listenerMethodName;
  271. }
  272. private String capitalize(String propertyName) {
  273. if (propertyName.length() == 0) {
  274. return propertyName;
  275. }
  276. return propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1);
  277. }
  278. private Method getMethod(Class targetClass, String methodName, Class[] argClasses) {
  279. return Statement.getMethod(targetClass, methodName, argClasses);
  280. }
  281. private Object applyGetters(Object target, String getters) {
  282. if (getters == null || getters.equals("")) {
  283. return target;
  284. }
  285. int firstDot = getters.indexOf('.');
  286. if (firstDot == -1) {
  287. firstDot = getters.length();
  288. }
  289. String first = getters.substring(0, firstDot);
  290. String rest = getters.substring(Math.min(firstDot + 1, getters.length()));
  291. try {
  292. Method getter = getMethod(target.getClass(), "get" + capitalize(first), new Class[]{});
  293. if (getter == null) {
  294. getter = getMethod(target.getClass(), "is" + capitalize(first), new Class[]{});
  295. }
  296. if (getter == null) {
  297. getter = getMethod(target.getClass(), first, new Class[]{});
  298. }
  299. if (getter == null) {
  300. System.err.println("No method called: " + first + " defined on " + target);
  301. return null;
  302. }
  303. Object newTarget = getter.invoke(target, new Object[]{});
  304. return applyGetters(newTarget, rest);
  305. }
  306. catch (Throwable e) {
  307. System.out.println(e);
  308. System.err.println("Failed to call method: " + first + " on " + target);
  309. }
  310. return null;
  311. }
  312. /**
  313. * Extract the appropriate property value from the event and
  314. * pass it to the action associated with
  315. * this <code>EventHandler</code>.
  316. *
  317. * @param proxy the proxy object
  318. * @param method the method in the listener interface
  319. * @return the result of applying the action to the target
  320. *
  321. * @see EventHandler
  322. */
  323. public Object invoke(Object proxy, Method method, Object[] arguments) {
  324. String methodName = method.getName();
  325. if (method.getDeclaringClass() == Object.class) {
  326. // Handle the Object public methods.
  327. if (methodName.equals("hashCode")) {
  328. return new Integer(System.identityHashCode(proxy));
  329. } else if (methodName.equals("equals")) {
  330. return (proxy == arguments[0] ? Boolean.TRUE : Boolean.FALSE);
  331. } else if (methodName.equals("toString")) {
  332. return proxy.getClass().getName() + '@' + Integer.toHexString(proxy.hashCode());
  333. }
  334. }
  335. if (listenerMethodName == null || listenerMethodName.equals(methodName)) {
  336. Class[] argTypes = null;
  337. Object[] newArgs = null;
  338. if (eventPropertyName == null) { // Nullary method.
  339. newArgs = new Object[]{};
  340. argTypes = new Class[]{};
  341. }
  342. else {
  343. Object input = applyGetters(arguments[0], getEventPropertyName());
  344. newArgs = new Object[]{input};
  345. argTypes = new Class[]{input.getClass()};
  346. }
  347. try {
  348. if (targetMethod == null) {
  349. targetMethod = getMethod(target.getClass(), action, argTypes);
  350. }
  351. if (targetMethod == null) {
  352. targetMethod = getMethod(target.getClass(), "set" + capitalize(action), argTypes);
  353. }
  354. if (targetMethod == null) {
  355. System.err.println("No target method called: " + action + " defined on class " + target.getClass() + " with argument type " + argTypes[0]);
  356. }
  357. return targetMethod.invoke(target, newArgs);
  358. }
  359. catch (IllegalAccessException ex) {
  360. ex.printStackTrace();
  361. }
  362. catch (InvocationTargetException ex) {
  363. ex.getTargetException().printStackTrace();
  364. }
  365. }
  366. return null;
  367. }
  368. /**
  369. * Creates an implementation of <code>listenerInterface</code> in which
  370. * <em>all</em> of the methods in the listener interface apply
  371. * the handler's <code>action</code> to the <code>target</code>. This
  372. * method is implemented by calling the other, more general,
  373. * implementation of the <code>create</code> method with both
  374. * the <code>eventPropertyName</code> and the <code>listenerMethodName</code>
  375. * taking the value <code>null</code>.
  376. * <p>
  377. * To create an <code>ActionListener</code> that shows a
  378. * <code>JDialog</code> with <code>dialog.show()</code>,
  379. * one can write:
  380. *
  381. *<blockquote>
  382. *<pre>
  383. *EventHandler.create(ActionListener.class, dialog, "show")
  384. *</pre>
  385. *</blockquote>
  386. *
  387. * <p>
  388. * @param listenerInterface the listener interface to create a proxy for
  389. * @param target the object that will perform the action
  390. * @param action the name of a writable property or method on the target
  391. *
  392. * @return an object that implements <code>listenerInterface</code>
  393. *
  394. * @see #create(Class, Object, String, String)
  395. */
  396. public static Object create(Class listenerInterface, Object target, String action) {
  397. return create(listenerInterface, target, action, null, null);
  398. }
  399. /**
  400. * Creates an implementation of <code>listenerInterface</code> in which
  401. * <em>all</em> of the methods pass the value of the event
  402. * expression, <code>eventPropertyName</code>, to the final method in the
  403. * statement, <code>action</code>, which is applied to the <code>target</code>.
  404. * This method is implemented by calling the
  405. * more general, implementation of the <code>create</code> method with
  406. * the <code>listenerMethodName</code> taking the value <code>null</code>.
  407. * <p>
  408. * To create an <code>ActionListener</code> that sets the
  409. * the text of a <code>JLabel</code> to the text value of
  410. * the <code>JTextField</code> source of the incoming event,
  411. * you can use the following code:
  412. *
  413. *<blockquote>
  414. *<pre>
  415. *EventHandler.create(ActionListener.class, label, "text", "source.text");
  416. *</pre>
  417. *</blockquote>
  418. *
  419. * This is equivalent to the following code:
  420. *<blockquote>
  421. *<pre>
  422. //Equivalent code using an inner class instead of EventHandler.
  423. *label.setText((JTextField(event.getSource())).getText())
  424. *</pre>
  425. *</blockquote>
  426. *
  427. * @param listenerInterface the listener interface to create a proxy for
  428. * @param target the object that will perform the action
  429. * @param action the name of a writable property or method on the target
  430. * @param eventPropertyName the (possibly qualified) name of a readable property of the incoming event
  431. *
  432. * @return an object that implements <code>listenerInterface</code>
  433. *
  434. * @see #create(Class, Object, String, String, String)
  435. */
  436. public static Object create(Class listenerInterface, Object target, String action, String eventPropertyName) {
  437. return create(listenerInterface, target, action, eventPropertyName, null);
  438. }
  439. /**
  440. * Creates an implementation of <code>listenerInterface</code> in which
  441. * the method named <code>listenerMethodName</code>
  442. * passes the value of the event expression, <code>eventPropertyName</code>,
  443. * to the final method in the statement, <code>action</code>, which
  444. * is applied to the <code>target</code>. All of the other listener
  445. * methods do nothing.
  446. * <p>
  447. * If the <code>eventPropertyName</code> is <code>null</code> the
  448. * implementation calls a method with the name specified
  449. * in <code>action</code> that takes an <code>EventObject</code>
  450. * or a no-argument method with the same name if a method
  451. * accepting an <code>EventObject</code> is not defined.
  452. * <p>
  453. * If the <code>listenerMethodName</code> is <code>null</code>
  454. * <em>all</em> methods in the interface trigger the <code>action</code> to be
  455. * executed on the <code>target</code>.
  456. * <p>
  457. * For example, to create a <code>MouseListener</code> that sets the target
  458. * object's <code>origin</code> property to the incoming <code>MouseEvent</code>'s
  459. * location (that's the value of <code>mouseEvent.getPoint()</code>) each
  460. * time a mouse button is pressed, one would write:
  461. *<blockquote>
  462. *<pre>
  463. *EventHandler.create(MouseListener.class, "mousePressed", target, "origin", "point");
  464. *</pre>
  465. *</blockquote>
  466. *
  467. * This is comparable to writing a <code>MouseListener</code> in which all
  468. * of the methods except <code>mousePressed</code> are no-ops:
  469. *
  470. *<blockquote>
  471. *<pre>
  472. //Equivalent code using an inner class instead of EventHandler.
  473. *new MouseAdapter() {
  474. * public void mousePressed(MouseEvent e) {
  475. * target.setOrigin(e.getPoint());
  476. * }
  477. *}
  478. * </pre>
  479. *</blockquote>
  480. *
  481. * @param listenerInterface the listener interface to create a proxy for
  482. * @param target the object that will perform the action
  483. * @param action the name of a writable property or method on the target
  484. * @param eventPropertyName the (possibly qualified) name of a readable property of the incoming event
  485. * @param listenerMethodName the name of the method in the listener interface that should trigger the action
  486. *
  487. * @return an object that implements <code>listenerInterface</code>
  488. *
  489. * @see EventHandler
  490. */
  491. public static Object create(Class listenerInterface, Object target, String action, String eventPropertyName, String listenerMethodName) {
  492. return Proxy.newProxyInstance(target.getClass().getClassLoader(),
  493. new Class[] {listenerInterface},
  494. new EventHandler(target, action,
  495. eventPropertyName, listenerMethodName));
  496. }
  497. }