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 21KB

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