You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ListenerMethod.java 24KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648
  1. /*
  2. @ITMillApache2LicenseForJavaFiles@
  3. */
  4. package com.vaadin.event;
  5. import java.io.IOException;
  6. import java.io.NotSerializableException;
  7. import java.io.Serializable;
  8. import java.lang.reflect.Method;
  9. import java.util.Arrays;
  10. import java.util.EventListener;
  11. import java.util.EventObject;
  12. import java.util.logging.Level;
  13. import java.util.logging.Logger;
  14. /**
  15. * <p>
  16. * One registered event listener. This class contains the listener object
  17. * reference, listened event type, the trigger method to call when the event
  18. * fires, and the optional argument list to pass to the method and the index of
  19. * the argument to replace with the event object.
  20. * </p>
  21. *
  22. * <p>
  23. * This Class provides several constructors that allow omission of the optional
  24. * arguments, and giving the listener method directly, or having the constructor
  25. * to reflect it using merely the name of the method.
  26. * </p>
  27. *
  28. * <p>
  29. * It should be pointed out that the method
  30. * {@link #receiveEvent(EventObject event)} is the one that filters out the
  31. * events that do not match with the given event type and thus do not result in
  32. * calling of the trigger method.
  33. * </p>
  34. *
  35. * @author IT Mill Ltd.
  36. * @version
  37. * @VERSION@
  38. * @since 3.0
  39. */
  40. @SuppressWarnings("serial")
  41. public class ListenerMethod implements EventListener, Serializable {
  42. private static final Logger logger = Logger.getLogger(ListenerMethod.class
  43. .getName());
  44. /**
  45. * Type of the event that should trigger this listener. Also the subclasses
  46. * of this class are accepted to trigger the listener.
  47. */
  48. private final Class<?> eventType;
  49. /**
  50. * The object containing the trigger method.
  51. */
  52. private final Object object;
  53. /**
  54. * The trigger method to call when an event passing the given criteria
  55. * fires.
  56. */
  57. private transient Method method;
  58. /**
  59. * Optional argument set to pass to the trigger method.
  60. */
  61. private Object[] arguments;
  62. /**
  63. * Optional index to <code>arguments</code> that point out which one should
  64. * be replaced with the triggering event object and thus be passed to the
  65. * trigger method.
  66. */
  67. private int eventArgumentIndex;
  68. /* Special serialization to handle method references */
  69. private void writeObject(java.io.ObjectOutputStream out) throws IOException {
  70. try {
  71. out.defaultWriteObject();
  72. String name = method.getName();
  73. Class<?>[] paramTypes = method.getParameterTypes();
  74. out.writeObject(name);
  75. out.writeObject(paramTypes);
  76. } catch (NotSerializableException e) {
  77. logger.severe("Fatal error in serialization of the application: Class "
  78. + object.getClass().getName()
  79. + " must implement serialization.");
  80. throw e;
  81. }
  82. };
  83. /* Special serialization to handle method references */
  84. private void readObject(java.io.ObjectInputStream in) throws IOException,
  85. ClassNotFoundException {
  86. in.defaultReadObject();
  87. try {
  88. String name = (String) in.readObject();
  89. Class<?>[] paramTypes = (Class<?>[]) in.readObject();
  90. // We can not use getMethod directly as we want to support anonymous
  91. // inner classes
  92. method = findHighestMethod(object.getClass(), name, paramTypes);
  93. } catch (SecurityException e) {
  94. logger.log(Level.SEVERE, "Internal deserialization error", e);
  95. }
  96. };
  97. private static Method findHighestMethod(Class<?> cls, String method,
  98. Class<?>[] paramTypes) {
  99. Class<?>[] ifaces = cls.getInterfaces();
  100. for (int i = 0; i < ifaces.length; i++) {
  101. Method ifaceMethod = findHighestMethod(ifaces[i], method,
  102. paramTypes);
  103. if (ifaceMethod != null) {
  104. return ifaceMethod;
  105. }
  106. }
  107. if (cls.getSuperclass() != null) {
  108. Method parentMethod = findHighestMethod(cls.getSuperclass(),
  109. method, paramTypes);
  110. if (parentMethod != null) {
  111. return parentMethod;
  112. }
  113. }
  114. Method[] methods = cls.getMethods();
  115. for (int i = 0; i < methods.length; i++) {
  116. // we ignore parameter types for now - you need to add this
  117. if (methods[i].getName().equals(method)) {
  118. return methods[i];
  119. }
  120. }
  121. return null;
  122. }
  123. /**
  124. * <p>
  125. * Constructs a new event listener from a trigger method, it's arguments and
  126. * the argument index specifying which one is replaced with the event object
  127. * when the trigger method is called.
  128. * </p>
  129. *
  130. * <p>
  131. * This constructor gets the trigger method as a parameter so it does not
  132. * need to reflect to find it out.
  133. * </p>
  134. *
  135. * @param eventType
  136. * the event type that is listener listens to. All events of this
  137. * kind (or its subclasses) result in calling the trigger method.
  138. * @param object
  139. * the object instance that contains the trigger method
  140. * @param method
  141. * the trigger method
  142. * @param arguments
  143. * the arguments to be passed to the trigger method
  144. * @param eventArgumentIndex
  145. * An index to the argument list. This index points out the
  146. * argument that is replaced with the event object before the
  147. * argument set is passed to the trigger method. If the
  148. * eventArgumentIndex is negative, the triggering event object
  149. * will not be passed to the trigger method, though it is still
  150. * called.
  151. * @throws java.lang.IllegalArgumentException
  152. * if <code>method</code> is not a member of <code>object</code>
  153. * .
  154. */
  155. public ListenerMethod(Class<?> eventType, Object object, Method method,
  156. Object[] arguments, int eventArgumentIndex)
  157. throws java.lang.IllegalArgumentException {
  158. // Checks that the object is of correct type
  159. if (!method.getDeclaringClass().isAssignableFrom(object.getClass())) {
  160. throw new java.lang.IllegalArgumentException();
  161. }
  162. // Checks that the event argument is null
  163. if (eventArgumentIndex >= 0 && arguments[eventArgumentIndex] != null) {
  164. throw new java.lang.IllegalArgumentException();
  165. }
  166. // Checks the event type is supported by the method
  167. if (eventArgumentIndex >= 0
  168. && !method.getParameterTypes()[eventArgumentIndex]
  169. .isAssignableFrom(eventType)) {
  170. throw new java.lang.IllegalArgumentException();
  171. }
  172. this.eventType = eventType;
  173. this.object = object;
  174. this.method = method;
  175. this.arguments = arguments;
  176. this.eventArgumentIndex = eventArgumentIndex;
  177. }
  178. /**
  179. * <p>
  180. * Constructs a new event listener from a trigger method name, it's
  181. * arguments and the argument index specifying which one is replaced with
  182. * the event object. The actual trigger method is reflected from
  183. * <code>object</code>, and <code>java.lang.IllegalArgumentException</code>
  184. * is thrown unless exactly one match is found.
  185. * </p>
  186. *
  187. * @param eventType
  188. * the event type that is listener listens to. All events of this
  189. * kind (or its subclasses) result in calling the trigger method.
  190. * @param object
  191. * the object instance that contains the trigger method.
  192. * @param methodName
  193. * the name of the trigger method. If the object does not contain
  194. * the method or it contains more than one matching methods
  195. * <code>java.lang.IllegalArgumentException</code> is thrown.
  196. * @param arguments
  197. * the arguments to be passed to the trigger method.
  198. * @param eventArgumentIndex
  199. * An index to the argument list. This index points out the
  200. * argument that is replaced with the event object before the
  201. * argument set is passed to the trigger method. If the
  202. * eventArgumentIndex is negative, the triggering event object
  203. * will not be passed to the trigger method, though it is still
  204. * called.
  205. * @throws java.lang.IllegalArgumentException
  206. * unless exactly one match <code>methodName</code> is found in
  207. * <code>object</code>.
  208. */
  209. public ListenerMethod(Class<?> eventType, Object object, String methodName,
  210. Object[] arguments, int eventArgumentIndex)
  211. throws java.lang.IllegalArgumentException {
  212. // Finds the correct method
  213. final Method[] methods = object.getClass().getMethods();
  214. Method method = null;
  215. for (int i = 0; i < methods.length; i++) {
  216. if (methods[i].getName().equals(methodName)) {
  217. method = methods[i];
  218. }
  219. }
  220. if (method == null) {
  221. throw new IllegalArgumentException();
  222. }
  223. // Checks that the event argument is null
  224. if (eventArgumentIndex >= 0 && arguments[eventArgumentIndex] != null) {
  225. throw new java.lang.IllegalArgumentException();
  226. }
  227. // Checks the event type is supported by the method
  228. if (eventArgumentIndex >= 0
  229. && !method.getParameterTypes()[eventArgumentIndex]
  230. .isAssignableFrom(eventType)) {
  231. throw new java.lang.IllegalArgumentException();
  232. }
  233. this.eventType = eventType;
  234. this.object = object;
  235. this.method = method;
  236. this.arguments = arguments;
  237. this.eventArgumentIndex = eventArgumentIndex;
  238. }
  239. /**
  240. * <p>
  241. * Constructs a new event listener from the trigger method and it's
  242. * arguments. Since the the index to the replaced parameter is not specified
  243. * the event triggering this listener will not be passed to the trigger
  244. * method.
  245. * </p>
  246. *
  247. * <p>
  248. * This constructor gets the trigger method as a parameter so it does not
  249. * need to reflect to find it out.
  250. * </p>
  251. *
  252. * @param eventType
  253. * the event type that is listener listens to. All events of this
  254. * kind (or its subclasses) result in calling the trigger method.
  255. * @param object
  256. * the object instance that contains the trigger method.
  257. * @param method
  258. * the trigger method.
  259. * @param arguments
  260. * the arguments to be passed to the trigger method.
  261. * @throws java.lang.IllegalArgumentException
  262. * if <code>method</code> is not a member of <code>object</code>
  263. * .
  264. */
  265. public ListenerMethod(Class<?> eventType, Object object, Method method,
  266. Object[] arguments) throws java.lang.IllegalArgumentException {
  267. // Check that the object is of correct type
  268. if (!method.getDeclaringClass().isAssignableFrom(object.getClass())) {
  269. throw new java.lang.IllegalArgumentException();
  270. }
  271. this.eventType = eventType;
  272. this.object = object;
  273. this.method = method;
  274. this.arguments = arguments;
  275. eventArgumentIndex = -1;
  276. }
  277. /**
  278. * <p>
  279. * Constructs a new event listener from a trigger method name and it's
  280. * arguments. Since the the index to the replaced parameter is not specified
  281. * the event triggering this listener will not be passed to the trigger
  282. * method.
  283. * </p>
  284. *
  285. * <p>
  286. * The actual trigger method is reflected from <code>object</code>, and
  287. * <code>java.lang.IllegalArgumentException</code> is thrown unless exactly
  288. * one match is found.
  289. * </p>
  290. *
  291. * @param eventType
  292. * the event type that is listener listens to. All events of this
  293. * kind (or its subclasses) result in calling the trigger method.
  294. * @param object
  295. * the object instance that contains the trigger method.
  296. * @param methodName
  297. * the name of the trigger method. If the object does not contain
  298. * the method or it contains more than one matching methods
  299. * <code>java.lang.IllegalArgumentException</code> is thrown.
  300. * @param arguments
  301. * the arguments to be passed to the trigger method.
  302. * @throws java.lang.IllegalArgumentException
  303. * unless exactly one match <code>methodName</code> is found in
  304. * <code>object</code>.
  305. */
  306. public ListenerMethod(Class<?> eventType, Object object, String methodName,
  307. Object[] arguments) throws java.lang.IllegalArgumentException {
  308. // Find the correct method
  309. final Method[] methods = object.getClass().getMethods();
  310. Method method = null;
  311. for (int i = 0; i < methods.length; i++) {
  312. if (methods[i].getName().equals(methodName)) {
  313. method = methods[i];
  314. }
  315. }
  316. if (method == null) {
  317. throw new IllegalArgumentException();
  318. }
  319. this.eventType = eventType;
  320. this.object = object;
  321. this.method = method;
  322. this.arguments = arguments;
  323. eventArgumentIndex = -1;
  324. }
  325. /**
  326. * <p>
  327. * Constructs a new event listener from a trigger method. Since the argument
  328. * list is unspecified no parameters are passed to the trigger method when
  329. * the listener is triggered.
  330. * </p>
  331. *
  332. * <p>
  333. * This constructor gets the trigger method as a parameter so it does not
  334. * need to reflect to find it out.
  335. * </p>
  336. *
  337. * @param eventType
  338. * the event type that is listener listens to. All events of this
  339. * kind (or its subclasses) result in calling the trigger method.
  340. * @param object
  341. * the object instance that contains the trigger method.
  342. * @param method
  343. * the trigger method.
  344. * @throws java.lang.IllegalArgumentException
  345. * if <code>method</code> is not a member of <code>object</code>
  346. * .
  347. */
  348. public ListenerMethod(Class<?> eventType, Object object, Method method)
  349. throws java.lang.IllegalArgumentException {
  350. // Checks that the object is of correct type
  351. if (!method.getDeclaringClass().isAssignableFrom(object.getClass())) {
  352. throw new java.lang.IllegalArgumentException();
  353. }
  354. this.eventType = eventType;
  355. this.object = object;
  356. this.method = method;
  357. eventArgumentIndex = -1;
  358. final Class<?>[] params = method.getParameterTypes();
  359. if (params.length == 0) {
  360. arguments = new Object[0];
  361. } else if (params.length == 1 && params[0].isAssignableFrom(eventType)) {
  362. arguments = new Object[] { null };
  363. eventArgumentIndex = 0;
  364. } else {
  365. throw new IllegalArgumentException();
  366. }
  367. }
  368. /**
  369. * <p>
  370. * Constructs a new event listener from a trigger method name. Since the
  371. * argument list is unspecified no parameters are passed to the trigger
  372. * method when the listener is triggered.
  373. * </p>
  374. *
  375. * <p>
  376. * The actual trigger method is reflected from <code>object</code>, and
  377. * <code>java.lang.IllegalArgumentException</code> is thrown unless exactly
  378. * one match is found.
  379. * </p>
  380. *
  381. * @param eventType
  382. * the event type that is listener listens to. All events of this
  383. * kind (or its subclasses) result in calling the trigger method.
  384. * @param object
  385. * the object instance that contains the trigger method.
  386. * @param methodName
  387. * the name of the trigger method. If the object does not contain
  388. * the method or it contains more than one matching methods
  389. * <code>java.lang.IllegalArgumentException</code> is thrown.
  390. * @throws java.lang.IllegalArgumentException
  391. * unless exactly one match <code>methodName</code> is found in
  392. * <code>object</code>.
  393. */
  394. public ListenerMethod(Class<?> eventType, Object object, String methodName)
  395. throws java.lang.IllegalArgumentException {
  396. // Finds the correct method
  397. final Method[] methods = object.getClass().getMethods();
  398. Method method = null;
  399. for (int i = 0; i < methods.length; i++) {
  400. if (methods[i].getName().equals(methodName)) {
  401. method = methods[i];
  402. }
  403. }
  404. if (method == null) {
  405. throw new IllegalArgumentException();
  406. }
  407. this.eventType = eventType;
  408. this.object = object;
  409. this.method = method;
  410. eventArgumentIndex = -1;
  411. final Class<?>[] params = method.getParameterTypes();
  412. if (params.length == 0) {
  413. arguments = new Object[0];
  414. } else if (params.length == 1 && params[0].isAssignableFrom(eventType)) {
  415. arguments = new Object[] { null };
  416. eventArgumentIndex = 0;
  417. } else {
  418. throw new IllegalArgumentException();
  419. }
  420. }
  421. /**
  422. * Receives one event from the <code>EventRouter</code> and calls the
  423. * trigger method if it matches with the criteria defined for the listener.
  424. * Only the events of the same or subclass of the specified event class
  425. * result in the trigger method to be called.
  426. *
  427. * @param event
  428. * the fired event. Unless the trigger method's argument list and
  429. * the index to the to be replaced argument is specified, this
  430. * event will not be passed to the trigger method.
  431. */
  432. public void receiveEvent(EventObject event) {
  433. // Only send events supported by the method
  434. if (eventType.isAssignableFrom(event.getClass())) {
  435. try {
  436. if (eventArgumentIndex >= 0) {
  437. if (eventArgumentIndex == 0 && arguments.length == 1) {
  438. method.invoke(object, new Object[] { event });
  439. } else {
  440. final Object[] arg = new Object[arguments.length];
  441. for (int i = 0; i < arg.length; i++) {
  442. arg[i] = arguments[i];
  443. }
  444. arg[eventArgumentIndex] = event;
  445. method.invoke(object, arg);
  446. }
  447. } else {
  448. method.invoke(object, arguments);
  449. }
  450. } catch (final java.lang.IllegalAccessException e) {
  451. // This should never happen
  452. throw new java.lang.RuntimeException(
  453. "Internal error - please report", e);
  454. } catch (final java.lang.reflect.InvocationTargetException e) {
  455. // An exception was thrown by the invocation target. Throw it
  456. // forwards.
  457. throw new MethodException("Invocation of method " + method
  458. + " failed.", e.getTargetException());
  459. }
  460. }
  461. }
  462. /**
  463. * Checks if the given object and event match with the ones stored in this
  464. * listener.
  465. *
  466. * @param target
  467. * the object to be matched against the object stored by this
  468. * listener.
  469. * @param eventType
  470. * the type to be tested for equality against the type stored by
  471. * this listener.
  472. * @return <code>true</code> if <code>target</code> is the same object as
  473. * the one stored in this object and <code>eventType</code> equals
  474. * the event type stored in this object. *
  475. */
  476. public boolean matches(Class<?> eventType, Object target) {
  477. return (target == object) && (eventType.equals(this.eventType));
  478. }
  479. /**
  480. * Checks if the given object, event and method match with the ones stored
  481. * in this listener.
  482. *
  483. * @param target
  484. * the object to be matched against the object stored by this
  485. * listener.
  486. * @param eventType
  487. * the type to be tested for equality against the type stored by
  488. * this listener.
  489. * @param method
  490. * the method to be tested for equality against the method stored
  491. * by this listener.
  492. * @return <code>true</code> if <code>target</code> is the same object as
  493. * the one stored in this object, <code>eventType</code> equals with
  494. * the event type stored in this object and <code>method</code>
  495. * equals with the method stored in this object
  496. */
  497. public boolean matches(Class<?> eventType, Object target, Method method) {
  498. return (target == object)
  499. && (eventType.equals(this.eventType) && method
  500. .equals(this.method));
  501. }
  502. @Override
  503. public int hashCode() {
  504. int hash = 7;
  505. hash = 31 * hash + eventArgumentIndex;
  506. hash = 31 * hash + (eventType == null ? 0 : eventType.hashCode());
  507. hash = 31 * hash + (object == null ? 0 : object.hashCode());
  508. hash = 31 * hash + (method == null ? 0 : method.hashCode());
  509. return hash;
  510. }
  511. @Override
  512. public boolean equals(Object obj) {
  513. if (this == obj) {
  514. return true;
  515. }
  516. // return false if obj is a subclass (do not use instanceof check)
  517. if ((obj == null) || (obj.getClass() != getClass())) {
  518. return false;
  519. }
  520. // obj is of same class, test it further
  521. ListenerMethod t = (ListenerMethod) obj;
  522. return eventArgumentIndex == t.eventArgumentIndex
  523. && (eventType == t.eventType || (eventType != null && eventType
  524. .equals(t.eventType)))
  525. && (object == t.object || (object != null && object
  526. .equals(t.object)))
  527. && (method == t.method || (method != null && method
  528. .equals(t.method)))
  529. && (arguments == t.arguments || (Arrays.equals(arguments,
  530. t.arguments)));
  531. }
  532. /**
  533. * Exception that wraps an exception thrown by an invoked method. When
  534. * <code>ListenerMethod</code> invokes the target method, it may throw
  535. * arbitrary exception. The original exception is wrapped into
  536. * MethodException instance and rethrown by the <code>ListenerMethod</code>.
  537. *
  538. * @author IT Mill Ltd.
  539. * @version
  540. * @VERSION@
  541. * @since 3.0
  542. */
  543. public class MethodException extends RuntimeException implements
  544. Serializable {
  545. private final Throwable cause;
  546. private String message;
  547. private MethodException(String message, Throwable cause) {
  548. super(message);
  549. this.cause = cause;
  550. }
  551. /**
  552. * Retrieves the cause of this throwable or <code>null</code> if the
  553. * cause does not exist or not known.
  554. *
  555. * @return the cause of this throwable or <code>null</code> if the cause
  556. * is nonexistent or unknown.
  557. * @see java.lang.Throwable#getCause()
  558. */
  559. @Override
  560. public Throwable getCause() {
  561. return cause;
  562. }
  563. /**
  564. * Returns the error message string of this throwable object.
  565. *
  566. * @return the error message.
  567. * @see java.lang.Throwable#getMessage()
  568. */
  569. @Override
  570. public String getMessage() {
  571. return message;
  572. }
  573. /**
  574. * @see java.lang.Throwable#toString()
  575. */
  576. @Override
  577. public String toString() {
  578. String msg = super.toString();
  579. if (cause != null) {
  580. msg += "\nCause: " + cause.toString();
  581. }
  582. return msg;
  583. }
  584. }
  585. public boolean isType(Class<?> eventType) {
  586. return this.eventType == eventType;
  587. }
  588. }