1 package cz.cuni.amis.pogamut.base.communication.worldview.listener.annotation;
2
3 import java.lang.reflect.Constructor;
4 import java.lang.reflect.Method;
5 import java.lang.reflect.Modifier;
6 import java.util.ArrayList;
7 import java.util.List;
8 import java.util.Map;
9 import java.util.logging.Level;
10 import java.util.logging.Logger;
11
12 import cz.cuni.amis.pogamut.base.communication.worldview.IWorldView;
13 import cz.cuni.amis.pogamut.base.communication.worldview.event.IWorldEvent;
14 import cz.cuni.amis.pogamut.base.communication.worldview.event.IWorldEventListener;
15 import cz.cuni.amis.pogamut.base.communication.worldview.listener.IListenerRegistrator;
16 import cz.cuni.amis.pogamut.base.communication.worldview.listener.ListenerLevel;
17 import cz.cuni.amis.pogamut.base.communication.worldview.listener.annotation.exception.ListenerMethodParametersException;
18 import cz.cuni.amis.pogamut.base.communication.worldview.listener.annotation.exception.MissingConstructorException;
19 import cz.cuni.amis.pogamut.base.communication.worldview.listener.annotation.exception.MoreThanOneListenerLevelAnnotationException;
20 import cz.cuni.amis.pogamut.base.communication.worldview.listener.exception.ListenersAlreadyRegisteredException;
21 import cz.cuni.amis.pogamut.base.communication.worldview.object.IWorldObject;
22 import cz.cuni.amis.pogamut.base.communication.worldview.object.IWorldObjectEvent;
23 import cz.cuni.amis.pogamut.base.communication.worldview.object.IWorldObjectEventListener;
24 import cz.cuni.amis.pogamut.base.communication.worldview.object.IWorldObjectListener;
25 import cz.cuni.amis.pogamut.base.communication.worldview.object.WorldObjectId;
26 import cz.cuni.amis.utils.ClassUtils;
27 import cz.cuni.amis.utils.Lazy;
28 import cz.cuni.amis.utils.NullCheck;
29 import cz.cuni.amis.utils.exception.PogamutException;
30 import cz.cuni.amis.utils.maps.LazyMap;
31
32
33
34
35
36
37
38
39
40
41
42
43
44 public class AnnotationListenerRegistrator implements IListenerRegistrator{
45
46
47
48
49
50
51
52
53
54 public static WorldObjectId getId(Method method, Class idClass, String id) {
55 try {
56 Object newId = null;
57
58
59 Constructor constructor = null;
60
61 try {
62 constructor = idClass.getConstructor(String.class);
63 } catch (Exception e) {
64 }
65 if (constructor != null) {
66 newId = idClass.getConstructor(String.class).newInstance(id);
67 } else {
68 Method getMethod = null;
69
70 try {
71 getMethod = idClass.getMethod("get", String.class);
72 } catch (Exception e) {
73 }
74
75 if (getMethod != null && isStaticMethod(getMethod) && getMethod.getReturnType().isAssignableFrom(idClass)) {
76 newId = getMethod.invoke(idClass, id);
77 } else {
78 throw new IllegalArgumentException("Creation of ID for the annotation on the method " + ClassUtils.getMethodSignature(method) + " has failed. Can't create object ID, id class " + idClass + " for id '" + id + "', as id class has neither constructor with String as parameter, nor static get(String) method for obtaining ids.");
79 }
80 }
81
82 if (newId == null) {
83 throw new IllegalArgumentException("Creation of ID for the annotation on the method " + ClassUtils.getMethodSignature(method) + " has failed. Failed to instantiate ID '" + id + "' using id class " + idClass +", result id is NULL! Have you specified correct idClass?");
84 }
85
86 if (!newId.getClass().isAssignableFrom(idClass)) {
87 throw new IllegalArgumentException("Creation of ID for the annotation on the method " + ClassUtils.getMethodSignature(method) + " has failed. Failed to instantiate CORRECT ID '" + id + "' using id class " + idClass +". Result id is of incompatible class " + newId.getClass() + ". Have you specified correct idClass?");
88 }
89
90 if (!WorldObjectId.class.isAssignableFrom(newId.getClass())) {
91 throw new IllegalArgumentException("Creation of ID for the annotation on the method " + ClassUtils.getMethodSignature(method) + " has failed. Failed to instantiate CORRECT ID '" + id + "' using id class " + idClass +". Result id is of incompatible class " + newId.getClass() + " as it does not extends WorldObjectId class. Have you specified correct idClass?");
92 }
93
94 return (WorldObjectId) newId;
95
96 } catch (Exception e) {
97 throw new IllegalArgumentException("Creation of ID for the annotation on the method " + ClassUtils.getMethodSignature(method) + " has failed. Failed to instantiate CORRECT ID '" + id + "' using id class " + idClass +". Exception occured during reflection.", e);
98 }
99 }
100
101
102
103
104
105
106
107 public static WorldObjectId getId(Method method, ObjectListener annotation) {
108 NullCheck.check(method, "method");
109 NullCheck.check(annotation, "annotation");
110 if (annotation.idClass() == null) {
111 throw new IllegalArgumentException(ClassUtils.getMethodSignature(method) + "-@ObjectListener.idClass == null, specify class of the id!");
112 }
113 if (annotation.objectId() == null) {
114 throw new IllegalArgumentException(ClassUtils.getMethodSignature(method) + "-@ObjectListener.idClass == null, specify class of the id!");
115 }
116 return getId(method, annotation.idClass(), annotation.objectId());
117 }
118
119
120
121
122
123
124 public static boolean isStaticMethod(Method method) {
125 return Modifier.isStatic(method.getModifiers());
126 }
127
128
129
130
131
132
133
134 public static WorldObjectId getId(Method method, ObjectEventListener annotation) {
135 NullCheck.check(method, "method");
136 NullCheck.check(annotation, "annotation");
137 if (annotation.idClass() == null) {
138 throw new IllegalArgumentException(ClassUtils.getMethodSignature(method) + "-@ObjectEventListener.idClass == null, specify class of the id!");
139 }
140 if (annotation.objectId() == null) {
141 throw new IllegalArgumentException(ClassUtils.getMethodSignature(method) + "-@ObjectEventListener.idClass == null, specify class of the id!");
142 }
143 return getId(method, annotation.idClass(), annotation.objectId());
144 }
145
146
147
148
149
150
151 public static ListenerLevel getListenerLevel(Method method) {
152 ListenerLevel level = null;
153 if (method.isAnnotationPresent(EventListener.class)) {
154 level = ListenerLevel.A;
155 }
156 if (method.isAnnotationPresent(ObjectClassListener.class)) {
157 if (level != null) throw new MoreThanOneListenerLevelAnnotationException(method, AnnotationListenerRegistrator.class);
158 level = ListenerLevel.B;
159 }
160 if (method.isAnnotationPresent(ObjectClassEventListener.class)) {
161 if (level != null) throw new MoreThanOneListenerLevelAnnotationException(method, AnnotationListenerRegistrator.class);
162 level = ListenerLevel.C;
163 }
164 if (method.isAnnotationPresent(ObjectListener.class)) {
165 if (level != null) throw new MoreThanOneListenerLevelAnnotationException(method, AnnotationListenerRegistrator.class);
166 level = ListenerLevel.D;
167 }
168 if (method.isAnnotationPresent(ObjectEventListener.class)) {
169 if (level != null) throw new MoreThanOneListenerLevelAnnotationException(method, AnnotationListenerRegistrator.class);
170 level = ListenerLevel.E;
171 }
172 return level;
173 }
174
175
176
177
178
179
180
181 private class LevelAListener implements IWorldEventListener {
182
183 Method method;
184
185 public LevelAListener(Method method) {
186 NullCheck.check(method, "method");
187 this.method = method;
188
189
190 if (getAnnotation() == null) {
191 throw new ListenerMethodParametersException(method, "There is no EventListener annotation on the method!", AnnotationListenerRegistrator.this);
192 }
193 if (getEventClass() == null) {
194 throw new ListenerMethodParametersException(method, "EventListener.eventClass can't be null!", AnnotationListenerRegistrator.this);
195 }
196 if (!(IWorldEvent.class.isAssignableFrom(getEventClass()))) {
197 throw new ListenerMethodParametersException(method, "EventListener.eventClass == " + getEventClass() + " is not instance of IWorldEvent! Are you trying to use the listener for IWorldObjects? If so, see usage of ObjectClassEventListener or ObjectClassListener or ObjectEventListener or ObjectListener as used inside ResponsiveBot PogamutUT2004 example!", AnnotationListenerRegistrator.this);
198 }
199
200
201 if (method.getParameterTypes().length != 1 ||
202 !method.getParameterTypes()[0].isAssignableFrom(method.getAnnotation(EventListener.class).eventClass())) {
203 throw new ListenerMethodParametersException(method, method.getAnnotation(EventListener.class), AnnotationListenerRegistrator.this);
204 }
205 if (method.getParameterTypes()[0].isAssignableFrom(IWorldObject.class)) {
206 throw new ListenerMethodParametersException(method, "EventListener can't be used to listen for OBJECTS! You must use ObjectClassEventListener or ObjectClassListener or ObjectEventListener or ObjectListener annotation for that! See ResponsiveBot PogamutUT2004 example!", AnnotationListenerRegistrator.this);
207 }
208 }
209
210 public EventListener getAnnotation() {
211 return method.getAnnotation(EventListener.class);
212 }
213
214 public Class getEventClass() {
215 return getAnnotation().eventClass();
216 }
217
218 @Override
219 public void notify(Object event) {
220 try {
221 method.setAccessible(true);
222 method.invoke(obj, event);
223 method.setAccessible(false);
224 } catch (Exception e) {
225 throw new PogamutException("Could not invoke LevelA listener " + ClassUtils.getMethodSignature(method) + " with parameter of class " + event.getClass() + ".", e, log, this);
226 }
227 }
228
229 }
230
231 private class LevelBListener implements IWorldObjectListener {
232
233 Method method;
234
235 public LevelBListener(Method method) {
236 NullCheck.check(method, "method");
237 this.method = method;
238
239
240 if (getAnnotation() == null) {
241 throw new ListenerMethodParametersException(method, "There is no ObjectClassListener annotation on the method!", AnnotationListenerRegistrator.this);
242 }
243 if (getObjectClass() == null) {
244 throw new ListenerMethodParametersException(method, "ObjectClassListener.objectClass can't be null!", AnnotationListenerRegistrator.this);
245 }
246 if (!IWorldObject.class.isAssignableFrom(getObjectClass())) {
247 throw new ListenerMethodParametersException(method, "ObjectClassListener.objectClass == " + getObjectClass() + " is not instance of IWorldObject! Are you trying to use the listener for IWorldEvent? If so, use EventListener, see the example inside ResponsiveBot PogamutUT2004!", AnnotationListenerRegistrator.this);
248 }
249
250
251 if (method.getParameterTypes().length != 1 ||
252 !method.getParameterTypes()[0].isAssignableFrom(IWorldObjectEvent.class)) {
253 throw new ListenerMethodParametersException(method, method.getAnnotation(ObjectClassListener.class), AnnotationListenerRegistrator.this);
254 }
255
256 }
257
258 public ObjectClassListener getAnnotation() {
259 return method.getAnnotation(ObjectClassListener.class);
260 }
261
262 public Class getObjectClass() {
263 return getAnnotation().objectClass();
264 }
265
266 @Override
267 public void notify(Object event) {
268 try {
269 method.setAccessible(true);
270 method.invoke(obj, event);
271 method.setAccessible(false);
272 } catch (Exception e) {
273 throw new PogamutException("Could not invoke LevelB listener " + ClassUtils.getMethodSignature(method) + " with parameter of class " + event.getClass() + ".", e, log, this);
274 }
275 }
276
277 }
278
279 private class LevelCListener implements IWorldObjectEventListener {
280
281 Method method;
282
283 public LevelCListener(Method method) {
284 NullCheck.check(method, "method");
285 this.method = method;
286
287
288 if (getAnnotation() == null) {
289 throw new ListenerMethodParametersException(method, "There is no ObjectClassEventListener annotation on the method!", AnnotationListenerRegistrator.this);
290 }
291 if (getEventClass() == null) {
292 throw new ListenerMethodParametersException(method, "ObjectClassEventListener.eventClass can't be null!", AnnotationListenerRegistrator.this);
293 }
294 if (!IWorldObjectEvent.class.isAssignableFrom(getEventClass())) {
295 throw new ListenerMethodParametersException(method, "ObjectClassEventListener.eventClass == " + getEventClass() + " is not instance of IWorldObjectEvent! Are you trying to use the listener for IWorldEvent? If so, use EventListener, see the example inside ResponsiveBot PogamutUT2004!", AnnotationListenerRegistrator.this);
296 }
297 if (getObjectClass() == null) {
298 throw new ListenerMethodParametersException(method, "ObjectClassEventListener.objectClass can't be null!", AnnotationListenerRegistrator.this);
299 }
300 if (!IWorldObject.class.isAssignableFrom(getObjectClass())) {
301 throw new ListenerMethodParametersException(method, "ObjectClassEventListener.objectClass == " + getObjectClass() + " is not instance of IWorldObject! Are you trying to use the listener for IWorldEvent? If so, use EventListener, see the example inside ResponsiveBot PogamutUT2004!", AnnotationListenerRegistrator.this);
302 }
303
304
305 if (method.getParameterTypes().length != 1 ||
306 !method.getParameterTypes()[0].isAssignableFrom(method.getAnnotation(ObjectClassEventListener.class).eventClass())) {
307 throw new ListenerMethodParametersException(method, method.getAnnotation(ObjectClassEventListener.class), AnnotationListenerRegistrator.this);
308 }
309 }
310
311 public ObjectClassEventListener getAnnotation() {
312 return method.getAnnotation(ObjectClassEventListener.class);
313 }
314
315 public Class getEventClass() {
316 return getAnnotation().eventClass();
317 }
318
319 public Class getObjectClass() {
320 return getAnnotation().objectClass();
321 }
322
323 @Override
324 public void notify(Object event) {
325 try {
326 method.setAccessible(true);
327 method.invoke(obj, event);
328 method.setAccessible(false);
329 } catch (Exception e) {
330 throw new PogamutException("Could not invoke LevelC listener " + ClassUtils.getMethodSignature(method) + " with parameter of class " + event.getClass() + ".", e, log, this);
331 }
332 }
333
334 }
335
336 private class LevelDListener implements IWorldObjectListener {
337
338 Method method;
339
340 WorldObjectId objectId;
341
342 public LevelDListener(Method method) {
343 NullCheck.check(method, "method");
344 this.method = method;
345
346
347 if (getAnnotation() == null) {
348 throw new ListenerMethodParametersException(method, "There is no ObjectListener annotation on the method!", AnnotationListenerRegistrator.this);
349 }
350
351
352 if (method.getParameterTypes().length != 1 ||
353 !method.getParameterTypes()[0].isAssignableFrom(IWorldObjectEvent.class)) {
354 throw new ListenerMethodParametersException(method, method.getAnnotation(ObjectListener.class), AnnotationListenerRegistrator.this);
355 }
356
357 objectId = getId(method, getAnnotation());
358 }
359
360 public ObjectListener getAnnotation() {
361 return method.getAnnotation(ObjectListener.class);
362 }
363
364 public WorldObjectId getObjectId() {
365 return objectId;
366 }
367
368 @Override
369 public void notify(Object event) {
370 try {
371 method.setAccessible(true);
372 method.invoke(obj, event);
373 method.setAccessible(false);
374 } catch (Exception e) {
375 throw new PogamutException("Could not invoke LevelD listener " + ClassUtils.getMethodSignature(method) + " with parameter of class " + event.getClass() + ".", e, log, this);
376 }
377 }
378
379 }
380
381 private class LevelEListener implements IWorldObjectEventListener {
382
383 Method method;
384
385 WorldObjectId objectId;
386
387 public LevelEListener(Method method) {
388 NullCheck.check(method, "method");
389 this.method = method;
390
391
392 if (getAnnotation() == null) {
393 throw new ListenerMethodParametersException(method, "There is no ObjectListener annotation on the method!", AnnotationListenerRegistrator.this);
394 }
395 if (getEventClass() == null) {
396 throw new ListenerMethodParametersException(method, "ObjectEventListener.eventClass can't be null!", AnnotationListenerRegistrator.this);
397 }
398 if (!IWorldObjectEvent.class.isAssignableFrom(getEventClass())) {
399 throw new ListenerMethodParametersException(method, "ObjectEventListener.eventClass == " + getEventClass() + " is not instance of IWorldObjectEvent! Are you trying to use the listener for IWorldEvent? If so, use EventListener, see the example inside ResponsiveBot PogamutUT2004!", AnnotationListenerRegistrator.this);
400 }
401
402
403 if (method.getParameterTypes().length != 1 ||
404 !method.getParameterTypes()[0].isAssignableFrom(method.getAnnotation(ObjectEventListener.class).eventClass())) {
405 throw new ListenerMethodParametersException(method, method.getAnnotation(ObjectEventListener.class), AnnotationListenerRegistrator.this);
406 }
407
408 objectId = getId(method, getAnnotation());
409 }
410
411 public ObjectEventListener getAnnotation() {
412 return method.getAnnotation(ObjectEventListener.class);
413 }
414
415 public WorldObjectId getObjectId() {
416 return objectId;
417 }
418
419 public Class getEventClass() {
420 return getAnnotation().eventClass();
421 }
422
423 @Override
424 public void notify(Object event) {
425 try {
426 method.setAccessible(true);
427 method.invoke(obj, event);
428 method.setAccessible(false);
429 } catch (Exception e) {
430 throw new PogamutException("Could not invoke LevelE listener " + ClassUtils.getMethodSignature(method) + " with parameter of class " + event.getClass() + ".", e, log, this);
431 }
432 }
433
434 }
435
436 private IWorldView worldView;
437 private Object obj;
438 private boolean listenersRegistered = false;
439
440 private Lazy<List<Method>> methods = new Lazy<List<Method>>() {
441
442 @Override
443 protected List<Method> create() {
444 return probeMethods();
445 }
446
447 };
448
449 private Map<ListenerLevel, List<IWorldEventListener>> listeners = new LazyMap<ListenerLevel, List<IWorldEventListener>>() {
450
451 @Override
452 protected List<IWorldEventListener> create(ListenerLevel key) {
453 return new ArrayList<IWorldEventListener>();
454 }
455
456 };
457
458 private Logger log;
459
460 public AnnotationListenerRegistrator(Object obj, IWorldView worldView, Logger log) {
461 this.worldView = worldView;
462 NullCheck.check(this.worldView, "worldView");
463 this.obj = obj;
464 NullCheck.check(this.obj, "obj");
465 this.log = log;
466 NullCheck.check(this.log, "log");
467 }
468
469
470
471
472
473 private List<Method> probeMethods() {
474 List<Method> methods = new ArrayList<Method>();
475 for (Method method : obj.getClass().getDeclaredMethods()) {
476 if (getListenerLevel(method) != null) methods.add(method);
477 }
478 return methods;
479 }
480
481
482
483
484
485
486
487 @Override
488 public synchronized void addListeners() throws ListenersAlreadyRegisteredException {
489 if (listenersRegistered) throw new ListenersAlreadyRegisteredException(this);
490 if (log.isLoggable(Level.FINER)) log.finer(obj + " -> " + worldView + ": Registering listeners.");
491 for (Method method : this.methods.getVal()) {
492 switch(getListenerLevel(method)){
493 case A:
494 if (log.isLoggable(Level.FINE)) log.fine(obj + " -> " + worldView + ": Registering level A listener for " + ClassUtils.getMethodSignature(method));
495 LevelAListener listenerA = new LevelAListener(method);
496 worldView.addEventListener(listenerA.getEventClass(), listenerA);
497 listeners.get(ListenerLevel.A).add(listenerA);
498 break;
499 case B:
500 if (log.isLoggable(Level.FINE)) log.fine(obj + " -> " + worldView + ": Registering level B listener for " + ClassUtils.getMethodSignature(method));
501 LevelBListener listenerB = new LevelBListener(method);
502 worldView.addObjectListener(listenerB.getObjectClass(), listenerB);
503 listeners.get(ListenerLevel.B).add(listenerB);
504 break;
505 case C:
506 if (log.isLoggable(Level.FINE)) log.fine(obj + " -> " + worldView + ": Registering level C listener for " + ClassUtils.getMethodSignature(method));
507 LevelCListener listenerC = new LevelCListener(method);
508 worldView.addObjectListener(listenerC.getObjectClass(), listenerC.getEventClass(), listenerC);
509 listeners.get(ListenerLevel.C).add(listenerC);
510 break;
511 case D:
512 if (log.isLoggable(Level.FINE)) log.fine(obj + " -> " + worldView + ": Registering level D listener for " + ClassUtils.getMethodSignature(method));
513 LevelDListener listenerD = new LevelDListener(method);
514 worldView.addObjectListener(listenerD.getObjectId(), listenerD);
515 listeners.get(ListenerLevel.D).add(listenerD);
516 break;
517 case E:
518 if (log.isLoggable(Level.FINE)) log.fine(obj + " -> " + worldView + ": Registering level E listener for " + ClassUtils.getMethodSignature(method));
519 LevelEListener listenerE = new LevelEListener(method);
520 worldView.addObjectListener(listenerE.getObjectId(), listenerE.getEventClass(), listenerE);
521 listeners.get(ListenerLevel.E).add(listenerE);
522 break;
523 }
524 }
525 if (log.isLoggable(Level.INFO)) log.info(obj + " -> " + worldView + ": Registered " + listeners.size() + " listeners.");
526 }
527
528 public int getListenersCount() {
529 return listeners.get(ListenerLevel.A).size() + listeners.get(ListenerLevel.B).size() + listeners.get(ListenerLevel.C).size() + listeners.get(ListenerLevel.D).size() + listeners.get(ListenerLevel.E).size();
530 }
531
532 @Override
533 public synchronized void removeListeners() {
534 if (!listenersRegistered) return;
535 if (log.isLoggable(Level.FINER)) log.finer(obj + " -> " + worldView + ": Removing " + getListenersCount() + " listeners.");
536 for (IWorldEventListener l : listeners.get(ListenerLevel.A)) {
537 LevelAListener listenerA = (LevelAListener) l;
538 worldView.removeEventListener(listenerA.getEventClass(), listenerA);
539 }
540 for (IWorldEventListener l : listeners.get(ListenerLevel.B)) {
541 LevelBListener listenerB = (LevelBListener) l;
542 worldView.removeObjectListener(listenerB.getObjectClass(), listenerB);
543 }
544 for (IWorldEventListener l : listeners.get(ListenerLevel.C)) {
545 LevelCListener listenerC = (LevelCListener) l;
546 worldView.removeObjectListener(listenerC.getObjectClass(), listenerC.getEventClass(), listenerC);
547 }
548 for (IWorldEventListener l : listeners.get(ListenerLevel.D)) {
549 LevelDListener listenerD = (LevelDListener) l;
550 worldView.removeObjectListener(listenerD.getObjectId(), listenerD);
551 }
552 for (IWorldEventListener l : listeners.get(ListenerLevel.E)) {
553 LevelEListener listenerE = (LevelEListener) l;
554 worldView.removeObjectListener(listenerE.getObjectId(), listenerE.getEventClass(), listenerE);
555 }
556 if (log.isLoggable(Level.INFO)) log.info(obj + " -> " + worldView + ": Listeners removed.");
557 }
558
559 public Logger getLog() {
560 return log;
561 }
562
563 }