package be.selckin.swu.events; import com.google.common.base.Preconditions; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap.Builder; import org.apache.wicket.Component; import org.apache.wicket.IEventDispatcher; import org.apache.wicket.event.IEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.lang.reflect.Method; public class AnnotationEventDispatcher implements IEventDispatcher { private static final Logger log = LoggerFactory.getLogger(AnnotationEventDispatcher.class); private final LoadingCache<Class<?>, EventAnnotations> annotations = CacheBuilder.newBuilder().build(new ClassEventAnnotationsFunction()); @Override public void dispatchEvent(Object sink, IEvent<?> event, Component component) { if (sink != null && sink instanceof Component && event != null && event.getPayload() != null) { EventCaller caller = annotations.getUnchecked(sink.getClass()).getCaller(event.getPayload().getClass()); if (caller != null) caller.call(sink, event); } } public static class EventAnnotations { private final ImmutableMap<Class<?>, EventCaller> callers; public EventAnnotations(ImmutableMap<Class<?>, EventCaller> methods) { this.callers = Preconditions.checkNotNull(methods); } public EventCaller getCaller(Class<?> type) { return callers.get(type); } } private static class ClassEventAnnotationsFunction extends CacheLoader<Class<?>, EventAnnotations> { @Override public EventAnnotations load(Class<?> input) throws Exception { Builder<Class<?>, EventCaller> builder = ImmutableMap.builder(); Class<?> current = input; while (current != null && current != Object.class) { for (Method method : current.getDeclaredMethods()) { if (method.isAnnotationPresent(OnEvent.class)) { if (!registerMethod(builder, method)) logInvalidMethod(current, method); } } current = current.getSuperclass(); } return new EventAnnotations(builder.build()); } private boolean registerMethod(Builder<Class<?>, EventCaller> builder, Method method) { if (!method.isAccessible()) method.setAccessible(true); Class<?>[] parameters = method.getParameterTypes(); if (parameters.length == 1) { Class<?> payloadType = parameters[0]; if (!IEvent.class.isAssignableFrom(payloadType)) { builder.put(payloadType, new PayloadOnlyCaller(method)); return true; } } else if (parameters.length == 2) { Class<?> payloadType = parameters[0]; Class<?> iEventType = parameters[1]; if (!IEvent.class.isAssignableFrom(payloadType) && IEvent.class.isAssignableFrom(iEventType)) { builder.put(payloadType, new PayloadAndEventCaller(method)); return true; } } return false; } private void logInvalidMethod(Class<?> current, Method method) { log.warn("Invalid @OnEvent method {} in {}", method, current); } } public static class EventException extends RuntimeException { public EventException(String message, Throwable cause) { super(message, cause); } } }