package roboguice.event;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import roboguice.event.eventListener.ObserverMethodListener;
import roboguice.event.eventListener.factory.EventListenerThreadingDecorator;
import com.google.inject.Guice;
import com.google.inject.Provider;
import com.google.inject.TypeLiteral;
import com.google.inject.HierarchyTraversalFilter;
import com.google.inject.spi.InjectionListener;
import com.google.inject.spi.TypeEncounter;
import com.google.inject.spi.TypeListener;
/**
* Guice driven type listener which scans for the @Observes annotations.
* Registers these methods with the EventManager.
*
* @author Adam Tybor
* @author John Ericksen
*/
public class ObservesTypeListener implements TypeListener {
protected Provider<EventManager> eventManagerProvider;
protected EventListenerThreadingDecorator observerThreadingDecorator;
private HierarchyTraversalFilter filter;
public ObservesTypeListener(Provider<EventManager> eventManagerProvider, EventListenerThreadingDecorator observerThreadingDecorator) {
this.eventManagerProvider = eventManagerProvider;
this.observerThreadingDecorator = observerThreadingDecorator;
}
public <I> void hear(TypeLiteral<I> iTypeLiteral, TypeEncounter<I> iTypeEncounter) {
if( filter == null ) {
filter = Guice.createHierarchyTraversalFilter();
} else {
filter.reset();
}
Class<?> c = iTypeLiteral.getRawType();
while( isWorthScanning(c)) {
for (Method method : filter.getAllMethods(Observes.class.getName(), c))
findContextObserver(method, iTypeEncounter);
for( Class<?> interfaceClass : c.getInterfaces())
for (Method method : filter.getAllMethods(Observes.class.getName(), interfaceClass))
findContextObserver(method, iTypeEncounter);
c = c.getSuperclass();
}
}
private boolean isWorthScanning(Class<?> c) {
return filter.isWorthScanningForMethods(Observes.class.getName(), c);
}
protected <I> void findContextObserver(Method method, TypeEncounter<I> iTypeEncounter) {
final Annotation[][] parameterAnnotations = method.getParameterAnnotations();
for(int i = 0; i < parameterAnnotations.length; i++){
final Annotation[] annotationArray = parameterAnnotations[i];
final Class<?>[] parameterTypes = method.getParameterTypes();
final Class<?> parameterType = parameterTypes[i];
for(Annotation annotation : annotationArray)
if(annotation.annotationType().equals(Observes.class))
registerContextObserver(iTypeEncounter, method, parameterType, ((Observes)annotation).value());
}
}
/**
* Error checks the observed method and registers method with typeEncounter
*
* @param iTypeEncounter
* @param method
* @param parameterType
* @param threadType
* @param <I, T>
*/
protected <I, T> void registerContextObserver(TypeEncounter<I> iTypeEncounter, Method method, Class<T> parameterType, EventThread threadType) {
checkMethodParameters(method);
iTypeEncounter.register(new ContextObserverMethodInjector<I, T>(eventManagerProvider, observerThreadingDecorator, method, parameterType,threadType));
}
/**
* Error checking method, verifies that the method has the correct number of parameters.
*
* @param method
*/
protected void checkMethodParameters(Method method) {
if(method.getParameterTypes().length > 1)
throw new RuntimeException("Annotation @Observes must only annotate one parameter," +
" which must be the only parameter in the listener method.");
}
/**
* Injection listener to handle the observation manager registration.
*
* @param <I>
*/
public static class ContextObserverMethodInjector<I, T> implements InjectionListener<I> {
protected EventListenerThreadingDecorator observerThreadingDecorator;
protected Provider<EventManager> eventManagerProvider;
protected Method method;
protected Class<T> event;
protected EventThread threadType;
public ContextObserverMethodInjector(Provider<EventManager> eventManagerProvider,
EventListenerThreadingDecorator observerThreadingDecorator, Method method,
Class<T> event, EventThread threadType) {
this.observerThreadingDecorator = observerThreadingDecorator;
this.eventManagerProvider = eventManagerProvider;
this.method = method;
this.event = event;
this.threadType = threadType;
}
@SuppressWarnings({ "rawtypes", "unchecked" })
public void afterInjection(I i) {
eventManagerProvider.get().registerObserver( event, observerThreadingDecorator.decorate(threadType, new ObserverMethodListener(i, method)));
}
}
}