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

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