package org.bushe.swing.event.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.regex.Pattern;
import java.util.Arrays;
import org.bushe.swing.event.EventService;
import org.bushe.swing.event.EventServiceExistsException;
import org.bushe.swing.event.EventServiceLocator;
import org.bushe.swing.event.Logger;
/**
* Enhances classes that use EventService Annotations.
* <p/>
* This class makes the EventService annotations "come alive." This can be used in code like so:
* <pre>
* <code>
* public class MyAppController {
* public MyAppController {
* AnnotationProcessor.process(this);//this line can be avoided with a compile-time tool or an Aspect
* }
* @EventSubscriber
* public void onAppStartingEvent(AppStartingEvent appStartingEvent) {
* //do something
* }
* @EventSubscriber
* public void onAppClosingEvent(AppClosingEvent appClosingEvent) {
* //do something
* }
* }
* ... some other place, needed in some cases when the like a window disposal before it's garbage collected ....
* AnnotationProcessor.unprocess(this);
* </code>
* </pre>
* <p/>
* This class can be leveraged in outside of source code in other ways in which Annotations are used: <ul> <li>In an
* Aspect-Oriented tool <li>In a Swing Framework classloader that wants to load and understand events. <li>In other
* Inversion of Control containers, such as Spring or PicoContainer. <li>In the apt tool, though this does not generate
* code. <li>In a Annotation Processing Tool plugin, when it becomes available. </ul> Support for these other methods
* are not yet implemented.
*/
public class AnnotationProcessor {
protected static final Logger LOG = Logger.getLogger(EventService.class.getName());
/**
* Add the appropriate subscribers to one or more EventServices for an instance of a class with
* EventBus annotations.
* @param obj the instance that may or may not have annotations
*/
public static void process(Object obj) {
processOrUnprocess(obj, true);
}
/**
* Remove the appropriate subscribers from one or more EventServices for an instance of a class with
* EventBus annotations.
* @param obj the instance that may or may not have annotations
*/
public static void unprocess(Object obj) {
processOrUnprocess(obj, false);
}
private static void processOrUnprocess(Object obj, boolean add) {
if (obj == null) {
return;
}
Class cl = obj.getClass();
Method[] methods = cl.getMethods();
if (LOG.isLoggable(Logger.Level.DEBUG)) {
LOG.debug("Looking for EventBus annotations for class " + cl + ", methods:" + Arrays.toString(methods));
}
for (Method method : methods) {
EventSubscriber classAnnotation = method.getAnnotation(EventSubscriber.class);
if (classAnnotation != null) {
if (LOG.isLoggable(Logger.Level.DEBUG)) {
LOG.debug("Found EventSubscriber:"+classAnnotation +" on method:" + method);
}
process(classAnnotation, obj, method, add);
}
EventTopicSubscriber topicAnnotation = method.getAnnotation(EventTopicSubscriber.class);
if (topicAnnotation != null) {
if (LOG.isLoggable(Logger.Level.DEBUG)) {
LOG.debug("Found EventTopicSubscriber: "+topicAnnotation +" on method:" + method);
}
process(topicAnnotation, obj, method, add);
}
EventTopicPatternSubscriber topicPatternAnnotation = method.getAnnotation(EventTopicPatternSubscriber.class);
if (topicPatternAnnotation != null) {
if (LOG.isLoggable(Logger.Level.DEBUG)) {
LOG.debug("Found EventTopicPatternSubscriber: "+topicPatternAnnotation+" on method:" + method);
}
process(topicPatternAnnotation, obj, method, add);
}
RuntimeTopicEventSubscriber runtimeTopicAnnotation = method.getAnnotation(RuntimeTopicEventSubscriber.class);
if (runtimeTopicAnnotation != null) {
if (LOG.isLoggable(Logger.Level.DEBUG)) {
LOG.debug("Found RuntimeTopicEventSubscriber: "+runtimeTopicAnnotation+" on method:" + method);
}
process(runtimeTopicAnnotation, obj, method, add);
}
RuntimeTopicPatternEventSubscriber annotation = method.getAnnotation(RuntimeTopicPatternEventSubscriber.class);
if (annotation != null) {
if (LOG.isLoggable(Logger.Level.DEBUG)) {
LOG.debug("Found RuntimeTopicPatternEventSubscriber:"+annotation+" on method:" + method);
}
process(annotation, obj, method, add);
}
VetoSubscriber vetoClassAnnotation = method.getAnnotation(VetoSubscriber.class);
if (vetoClassAnnotation != null) {
if (LOG.isLoggable(Logger.Level.DEBUG)) {
LOG.debug("Found VetoSubscriber:"+vetoClassAnnotation +" on method:" + method);
}
process(vetoClassAnnotation, obj, method, add);
}
VetoTopicSubscriber vetoTopicAnnotation = method.getAnnotation(VetoTopicSubscriber.class);
if (vetoTopicAnnotation != null) {
if (LOG.isLoggable(Logger.Level.DEBUG)) {
LOG.debug("Found VetoTopicSubscriber: "+vetoTopicAnnotation +" on method:" + method);
}
process(vetoTopicAnnotation, obj, method, add);
}
VetoTopicPatternSubscriber vetoTopicPatternAnnotation = method.getAnnotation(VetoTopicPatternSubscriber.class);
if (vetoTopicPatternAnnotation != null) {
if (LOG.isLoggable(Logger.Level.DEBUG)) {
LOG.debug("Found VetoTopicPatternSubscriber: "+vetoTopicPatternAnnotation+" on method:" + method);
}
process(vetoTopicPatternAnnotation, obj, method, add);
}
VetoRuntimeTopicSubscriber vetoRuntimeTopicAnnotation = method.getAnnotation(VetoRuntimeTopicSubscriber.class);
if (vetoRuntimeTopicAnnotation != null) {
if (LOG.isLoggable(Logger.Level.DEBUG)) {
LOG.debug("Found VetoRuntimeTopicSubscriber: "+vetoRuntimeTopicAnnotation+" on method:" + method);
}
process(vetoRuntimeTopicAnnotation, obj, method, add);
}
VetoRuntimeTopicPatternSubscriber vetoAnnotation = method.getAnnotation(VetoRuntimeTopicPatternSubscriber.class);
if (vetoAnnotation != null) {
if (LOG.isLoggable(Logger.Level.DEBUG)) {
LOG.debug("Found VetoRuntimeTopicPatternSubscriber:"+vetoAnnotation+" on method:" + method);
}
process(vetoAnnotation, obj, method, add);
}
}
}
private static void process(EventTopicPatternSubscriber topicPatternAnnotation, Object obj,
Method method, boolean add) {
//Check args
String topicPattern = topicPatternAnnotation.topicPattern();
if (topicPattern == null) {
throw new IllegalArgumentException("Topic pattern cannot be null for EventTopicPatternSubscriber annotation");
}
//Get event service
Class<? extends EventService> eventServiceClass = topicPatternAnnotation.autoCreateEventServiceClass();
String eventServiceName = topicPatternAnnotation.eventServiceName();
EventService eventService = getEventServiceFromAnnotation(eventServiceName, eventServiceClass);
int priority = topicPatternAnnotation.priority();
//Create proxy and subscribe
Pattern pattern = Pattern.compile(topicPattern);
//See Issue #18
//Also note that this post is wrong: https://eventbus.dev.java.net/servlets/ProjectForumMessageView?messageID=19499&forumID=1834
//Since two WeakReferences are not treated as one. So this always has to be strong and we'll have to clean up occasionally.
if (add) {
ProxyTopicPatternSubscriber subscriber = new ProxyTopicPatternSubscriber(obj, method,
topicPatternAnnotation.referenceStrength(), priority, eventService,
topicPattern, pattern, false);
eventService.subscribeStrongly(pattern, subscriber);
} else {
eventService.unsubscribe(pattern, obj);
}
}
private static void process(EventTopicSubscriber topicAnnotation, Object obj, Method method, boolean add) {
//Check args
String topic = topicAnnotation.topic();
if (topic == null) {
throw new IllegalArgumentException("Topic cannot be null for EventTopicSubscriber annotation");
}
//Get event service
Class<? extends EventService> eventServiceClass = topicAnnotation.autoCreateEventServiceClass();
String eventServiceName = topicAnnotation.eventServiceName();
EventService eventService = getEventServiceFromAnnotation(eventServiceName, eventServiceClass);
int priority = topicAnnotation.priority();
//See Issue #18
//Also note that this post is wrong: https://eventbus.dev.java.net/servlets/ProjectForumMessageView?messageID=19499&forumID=1834
//Since two WeakReferences are not treated as one. So this always has to be strong and we'll have to clean up occasionally.
if (add) {
//Create proxy and subscribe
ProxyTopicSubscriber subscriber = new ProxyTopicSubscriber(obj, method,
topicAnnotation.referenceStrength(), priority, eventService, topic, false);
eventService.subscribeStrongly(topic, subscriber);
} else {
eventService.unsubscribe(topic, obj);
}
}
private static void process(EventSubscriber annotation, Object obj, Method method, boolean add) {
//Check args
Class eventClass = annotation.eventClass();
if (eventClass == null) {
throw new IllegalArgumentException("Event class cannot be null for EventSubscriber annotation");
} else if (UseTheClassOfTheAnnotatedMethodsParameter.class.equals(eventClass)) {
Class[] params = method.getParameterTypes();
if (params.length < 1) {
throw new RuntimeException("Expected annotated method to have one parameter.");
} else {
eventClass = params[0];
}
}
//Get event service
Class<? extends EventService> eventServiceClass = annotation.autoCreateEventServiceClass();
String eventServiceName = annotation.eventServiceName();
EventService eventService = getEventServiceFromAnnotation(eventServiceName, eventServiceClass);
if (add) {
int priority = annotation.priority();
//Create proxy and subscribe
//See https://eventbus.dev.java.net/servlets/ProjectForumMessageView?messageID=19499&forumID=1834
BaseProxySubscriber subscriber = new BaseProxySubscriber(obj, method, annotation.referenceStrength(),
priority, eventService, eventClass, false);
if (annotation.exact()) {
//See Issue #18
//Also note that this post is wrong: https://eventbus.dev.java.net/servlets/ProjectForumMessageView?messageID=19499&forumID=1834
//Since two WeakReferences are not treated as one. So this always has to be strong and we'll have to clean up occasionally.
eventService.subscribeExactlyStrongly(eventClass, subscriber);
} else {
//See Issue #18
//Also note that this post is wrong: https://eventbus.dev.java.net/servlets/ProjectForumMessageView?messageID=19499&forumID=1834
//Since two WeakReferences are not treated as one. So this always has to be strong and we'll have to clean up occasionally.
eventService.subscribeStrongly(eventClass, subscriber);
}
} else {
if (annotation.exact()) {
eventService.unsubscribeExactly(eventClass, obj);
} else {
eventService.unsubscribe(eventClass, obj);
}
}
}
private static void process(final RuntimeTopicEventSubscriber annotation, final Object subscriber, final Method method, boolean add) {
EventTopicSubscriber eventTopicSubscriber = new EventTopicSubscriber() {
//TODO uncomment when language level is set to 1.6 (2.0) @Override
public Class<? extends EventService> autoCreateEventServiceClass() {
return annotation.autoCreateEventServiceClass();
}
//TODO uncomment when language level is set to 1.6 (2.0) @Override
public String eventServiceName() {
return annotation.eventServiceName();
}
//TODO uncomment when language level is set to 1.6 (2.0) @Override
public ReferenceStrength referenceStrength() {
return annotation.referenceStrength();
}
//TODO uncomment when language level is set to 1.6 (2.0) @Override
public int priority() {
return annotation.priority();
}
//TODO uncomment when language level is set to 1.6 (2.0) @Override
public String topic() {
return getTopic(annotation.methodName(), subscriber, method);
}
//TODO uncomment when language level is set to 1.6 (2.0) @Override
public Class<? extends Annotation> annotationType() {
return annotation.annotationType();
}
};
process(eventTopicSubscriber, subscriber, method, add);
}
private static void process(final RuntimeTopicPatternEventSubscriber annotation, final Object subscriber, final Method method, boolean add) {
EventTopicPatternSubscriber eventTopicPatternSubscriber = new EventTopicPatternSubscriber() {
//TODO uncomment when language level is set to 1.6 (2.0) @Override
public Class<? extends EventService> autoCreateEventServiceClass() {
return annotation.autoCreateEventServiceClass();
}
//TODO uncomment when language level is set to 1.6 (2.0) @Override
public String eventServiceName() {
return annotation.eventServiceName();
}
//TODO uncomment when language level is set to 1.6 (2.0) @Override
public ReferenceStrength referenceStrength() {
return annotation.referenceStrength();
}
//TODO uncomment when language level is set to 1.6 (2.0) @Override
public int priority() {
return annotation.priority();
}
//TODO uncomment when language level is set to 1.6 (2.0) @Override
public boolean exact() {
return annotation.exact();
}
//TODO uncomment when language level is set to 1.6 (2.0) @Override
public String topicPattern() {
return getTopic(annotation.methodName(), subscriber, method);
}
//TODO uncomment when language level is set to 1.6 (2.0) @Override
public Class<? extends Annotation> annotationType() {
return annotation.annotationType();
}
};
process(eventTopicPatternSubscriber, subscriber, method, add);
}
/* This is a cut and paste from above practically, sure which Annotations could be extended.
* TODO: When Java 7 comes out, or whatever JSR-308 is delivered, reduce this file by 70% */
private static void process(VetoTopicPatternSubscriber topicPatternAnnotation, Object obj, Method method, boolean add) {
//Check args
String topicPattern = topicPatternAnnotation.topicPattern();
if (topicPattern == null) {
throw new IllegalArgumentException("Topic pattern cannot be null for VetoTopicPatternSubscriber annotation");
}
//Get event service
Class<? extends EventService> eventServiceClass = topicPatternAnnotation.autoCreateEventServiceClass();
String eventServiceName = topicPatternAnnotation.eventServiceName();
EventService eventService = getEventServiceFromAnnotation(eventServiceName, eventServiceClass);
int priority = topicPatternAnnotation.priority();
//Create proxy and subscribe
Pattern pattern = Pattern.compile(topicPattern);
ProxyTopicPatternSubscriber subscriber = new ProxyTopicPatternSubscriber(obj, method, topicPatternAnnotation.referenceStrength(),
priority, eventService, topicPattern, pattern, true);
//See Issue #18
//Also note that this post is wrong: https://eventbus.dev.java.net/servlets/ProjectForumMessageView?messageID=19499&forumID=1834
//Since two WeakReferences are not treated as one. So this always has to be strong and we'll have to clean up occasionally.
if (add) {
eventService.subscribeVetoListenerStrongly(pattern, subscriber);
} else {
eventService.unsubscribeVeto(pattern, obj);
}
}
private static void process(VetoTopicSubscriber topicAnnotation, Object obj, Method method, boolean add) {
//Check args
String topic = topicAnnotation.topic();
if (topic == null) {
throw new IllegalArgumentException("Topic cannot be null for VetoTopicSubscriber annotation");
}
//Get event service
Class<? extends EventService> eventServiceClass = topicAnnotation.autoCreateEventServiceClass();
String eventServiceName = topicAnnotation.eventServiceName();
EventService eventService = getEventServiceFromAnnotation(eventServiceName, eventServiceClass);
int priority = topicAnnotation.priority();
//Create proxy and subscribe
ProxyTopicSubscriber subscriber = new ProxyTopicSubscriber(obj, method,
topicAnnotation.referenceStrength(), priority, eventService, topic, true);
//See Issue #18
//Also note that this post is wrong: https://eventbus.dev.java.net/servlets/ProjectForumMessageView?messageID=19499&forumID=1834
//Since two WeakReferences are not treated as one. So this always has to be strong and we'll have to clean up occasionally.
if (add) {
eventService.subscribeVetoListenerStrongly(topic, subscriber);
} else {
eventService.unsubscribeVeto(topic, obj);
}
}
private static void process(VetoSubscriber annotation, Object obj, Method method, boolean add) {
//Check args
Class eventClass = annotation.eventClass();
if (eventClass == null) {
throw new IllegalArgumentException("Event class cannot be null for VetoSubscriber annotation");
} else if (UseTheClassOfTheAnnotatedMethodsParameter.class.equals(eventClass)) {
Class[] params = method.getParameterTypes();
if (params.length < 1) {
throw new RuntimeException("Expected annotated method to have one parameter.");
} else {
eventClass = params[0];
}
}
//Get event service
Class<? extends EventService> eventServiceClass = annotation.autoCreateEventServiceClass();
String eventServiceName = annotation.eventServiceName();
EventService eventService = getEventServiceFromAnnotation(eventServiceName, eventServiceClass);
int priority = annotation.priority();
//Create proxy and subscribe
//See https://eventbus.dev.java.net/servlets/ProjectForumMessageView?messageID=19499&forumID=1834
BaseProxySubscriber subscriber = new BaseProxySubscriber(obj, method, annotation.referenceStrength(),
priority, eventService, eventClass, true);
if (add) {
if (annotation.exact()) {
//See Issue #18
//Also note that this post is wrong: https://eventbus.dev.java.net/servlets/ProjectForumMessageView?messageID=19499&forumID=1834
//Since two WeakReferences are not treated as one. So this always has to be strong and we'll have to clean up occasionally.
eventService.subscribeVetoListenerExactlyStrongly(eventClass, subscriber);
} else {
//See Issue #18
//Also note that this post is wrong: https://eventbus.dev.java.net/servlets/ProjectForumMessageView?messageID=19499&forumID=1834
//Since two WeakReferences are not treated as one. So this always has to be strong and we'll have to clean up occasionally.
eventService.subscribeVetoListenerStrongly(eventClass, subscriber);
}
} else {
if (annotation.exact()) {
eventService.unsubscribeVetoExactly(eventClass, obj);
} else {
eventService.unsubscribeVeto(eventClass, obj);
}
}
}
private static void process(final VetoRuntimeTopicSubscriber annotation, final Object subscriber, final Method method, boolean add) {
VetoTopicSubscriber eventTopicSubscriber = new VetoTopicSubscriber() {
//TODO uncomment when language level is set to 1.6 (2.0) @Override
public Class<? extends EventService> autoCreateEventServiceClass() {
return annotation.autoCreateEventServiceClass();
}
//TODO uncomment when language level is set to 1.6 (2.0) @Override
public String eventServiceName() {
return annotation.eventServiceName();
}
//TODO uncomment when language level is set to 1.6 (2.0) @Override
public ReferenceStrength referenceStrength() {
return annotation.referenceStrength();
}
//TODO uncomment when language level is set to 1.6 (2.0) @Override
public int priority() {
return annotation.priority();
}
//TODO uncomment when language level is set to 1.6 (2.0) @Override
public String topic() {
return getTopic(annotation.methodName(), subscriber, method);
}
//TODO uncomment when language level is set to 1.6 (2.0) @Override
public Class<? extends Annotation> annotationType() {
return annotation.annotationType();
}
};
process(eventTopicSubscriber, subscriber, method, add);
}
private static void process(final VetoRuntimeTopicPatternSubscriber annotation, final Object subscriber, final Method method, boolean add) {
VetoTopicPatternSubscriber eventTopicPatternSubscriber = new VetoTopicPatternSubscriber() {
//TODO uncomment when language level is set to 1.6 (2.0) @Override
public Class<? extends EventService> autoCreateEventServiceClass() {
return annotation.autoCreateEventServiceClass();
}
//TODO uncomment when language level is set to 1.6 (2.0) @Override
public String eventServiceName() {
return annotation.eventServiceName();
}
//TODO uncomment when language level is set to 1.6 (2.0) @Override
public ReferenceStrength referenceStrength() {
return annotation.referenceStrength();
}
//TODO uncomment when language level is set to 1.6 (2.0) @Override
public int priority() {
return annotation.priority();
}
//TODO uncomment when language level is set to 1.6 (2.0) @Override
public boolean exact() {
return annotation.exact();
}
//TODO uncomment when language level is set to 1.6 (2.0) @Override
public String topicPattern() {
return getTopic(annotation.methodName(), subscriber, method);
}
//TODO uncomment when language level is set to 1.6 (2.0) @Override
public Class<? extends Annotation> annotationType() {
return annotation.annotationType();
}
};
process(eventTopicPatternSubscriber, subscriber, method, add);
}
private static String getTopic(String methodName, Object subscriber, Method method) {
try {
Method runtimeEvalMethod = subscriber.getClass().getMethod(methodName, new Class[0]);
//necessary in case the method does not have public access or if the class it belongs
//to isn't public
runtimeEvalMethod.setAccessible(true);
return runtimeEvalMethod.invoke(subscriber, new Object[0]).toString();
} catch (SecurityException e) {
throw new RuntimeException("Could not retrieve method for subscription. Method: " + methodName, e);
} catch (NoSuchMethodException e) {
throw new RuntimeException("Could not retrieve method for subscription. Method: " + methodName, e);
} catch (InvocationTargetException e) {
e.getTargetException().printStackTrace();
throw new RuntimeException("Could not invoke method for subscription. Method: " + methodName, e);
} catch (IllegalAccessException e) {
throw new RuntimeException("Could not invoke method for subscription. Method: " + methodName, e);
}
}
private static EventService getEventServiceFromAnnotation(String eventServiceName,
Class<? extends EventService> eventServiceClass) {
EventService eventService = EventServiceLocator.getEventService(eventServiceName);
if (eventService == null) {
if (EventServiceLocator.SERVICE_NAME_EVENT_BUS.equals(eventServiceName)) {
//This may be the first time the EventBus is accessed.
eventService = EventServiceLocator.getSwingEventService();
} else {
//The event service does not yet exist, create it
try {
eventService = eventServiceClass.newInstance();
} catch (InstantiationException e) {
throw new RuntimeException("Could not instance of create EventService class " + eventServiceClass, e);
} catch (IllegalAccessException e) {
throw new RuntimeException("Could not instance of create EventService class " + eventServiceClass, e);
}
try {
EventServiceLocator.setEventService(eventServiceName, eventService);
} catch (EventServiceExistsException e) {
//ignore it, it's OK
eventService = EventServiceLocator.getEventService(eventServiceName);
}
}
}
return eventService;
}
}