package com.github.czyzby.autumn.processor.event;
import java.util.Iterator;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.utils.ObjectMap;
import com.badlogic.gdx.utils.ObjectSet;
import com.badlogic.gdx.utils.reflect.Method;
import com.github.czyzby.autumn.annotation.OnMessage;
import com.github.czyzby.autumn.context.Context;
import com.github.czyzby.autumn.context.ContextDestroyer;
import com.github.czyzby.autumn.context.ContextInitializer;
import com.github.czyzby.autumn.context.error.ContextInitiationException;
import com.github.czyzby.autumn.processor.AbstractAnnotationProcessor;
import com.github.czyzby.autumn.processor.event.impl.ReflectionMessageListener;
import com.github.czyzby.kiwi.util.gdx.collection.lazy.LazyObjectMap;
/** Processes messages. Can be injected and used to invoke registered listeners with {@link #postMessage(String)}. If
* this processor is not injected into any component, it will mostly likely get garbage-collected after context
* initiation, even if there are methods or components annotated with {@link OnMessage}.
*
* @author MJ */
public class MessageDispatcher extends AbstractAnnotationProcessor<OnMessage> {
private final ObjectMap<String, ObjectSet<MessageListener>> listeners = LazyObjectMap.newMapOfSets();
private final ObjectMap<String, ObjectSet<MessageListener>> mainThreadListeners = LazyObjectMap.newMapOfSets();
@Override
public Class<OnMessage> getSupportedAnnotationType() {
return OnMessage.class;
}
@Override
public boolean isSupportingMethods() {
return true;
}
@Override
public void processMethod(final Method method, final OnMessage annotation, final Object component,
final Context context, final ContextInitializer initializer, final ContextDestroyer contextDestroyer) {
addListener(new ReflectionMessageListener(method, component, context, annotation.removeAfterInvocation(),
annotation.strict()), annotation);
}
@Override
public boolean isSupportingTypes() {
return true;
}
@Override
public void processType(final Class<?> type, final OnMessage annotation, final Object component,
final Context context, final ContextInitializer initializer, final ContextDestroyer contextDestroyer) {
if (component instanceof MessageListener) {
addListener((MessageListener) component, annotation);
} else {
throw new ContextInitiationException("Unable to register listener. " + component
+ " is annotated with OnMessage, but does not implement MessageListener interface.");
}
}
/** @param listener will be registered.
* @param annotation contains listener's data. */
public void addListener(final MessageListener listener, final OnMessage annotation) {
addListener(listener, annotation.value(), annotation.forceMainThread());
}
/**
* @param listener will be registered. Invoked as soon as the message is posted.
* @param messageContent content of handled message. If the message is posted, listener will be invoked.
*/
public void addListener(final MessageListener listener, final String messageContent) {
addListener(listener, messageContent, false);
}
/**
* @param listener will be registered.
* @param messageContent content of handled message. If the message is posted, listener will be invoked.
* @param forceMainThread if true, listener will be invoked only on main LibGDX thread with
* Gdx.app.postRunnable(Runnable). Otherwise the listener is invoked as soon as the message is posted.
*/
public void addListener(final MessageListener listener, final String messageContent,
final boolean forceMainThread) {
if (forceMainThread) {
mainThreadListeners.get(messageContent).add(listener);
} else {
listeners.get(messageContent).add(listener);
}
}
/**
* @param listener will be removed (if registered).
* @param messageContent content of message that the listener is registered to handle.
*/
public void removeListener(final MessageListener listener, final String messageContent) {
listeners.get(messageContent).remove(listener);
mainThreadListeners.get(messageContent).remove(listener);
}
/**
* @param messageContent all listeners registered to handle this message will be removed.
*/
public void removeListenersForMessage(final String messageContent) {
listeners.remove(messageContent);
mainThreadListeners.remove(messageContent);
}
/**
* Removes all registered listeners. Use with care.
*/
public void clearListeners() {
listeners.clear();
mainThreadListeners.clear();
}
/** @param message will be posted and invoke all listeners registered to its exact content. Nulls are ignored. */
public void postMessage(final String message) {
if (message == null) {
return;
}
if (listeners.containsKey(message)) {
invokeMessageListeners(listeners.get(message));
}
if (mainThreadListeners.containsKey(message)) {
Gdx.app.postRunnable(new MessageRunnable(mainThreadListeners.get(message)));
}
}
/** @param listeners their message was just posted, so they will be invoked. */
protected static void invokeMessageListeners(final ObjectSet<MessageListener> listeners) {
for (final Iterator<MessageListener> iterator = listeners.iterator(); iterator.hasNext();) {
final MessageListener listener = iterator.next();
if (!listener.processMessage()) {
iterator.remove();
}
}
}
/** Invokes listeners.
*
* @author MJ */
public static class MessageRunnable implements Runnable {
private final ObjectSet<MessageListener> listeners;
public MessageRunnable(final ObjectSet<MessageListener> listeners) {
this.listeners = listeners;
}
@Override
public void run() {
invokeMessageListeners(listeners);
}
}
}