package org.stevewinfield.suja.idk.game.event.impl;
import org.apache.log4j.Logger;
import org.stevewinfield.suja.idk.game.event.Event;
import org.stevewinfield.suja.idk.game.event.EventPriority;
import org.stevewinfield.suja.idk.game.event.IEventListener;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class EventBus {
private final static Logger logger = Logger.getLogger(EventBus.class);
/**
* Registers a listener
* @param eventClass Event for which the event listener must be called
* @param eventListener Listener that may or may not have a generic type attached
* @param eventPriority Priority of the event
*/
public void registerListener(final Class<? extends Event> eventClass, final IEventListener eventListener, final EventPriority eventPriority) {
lock.writeLock().lock();
try {
Map<EventPriority, List<IEventListener>> eventPriorityMap = eventListenersByPriority.get(eventClass);
if (eventPriorityMap == null) {
eventPriorityMap = new HashMap<>();
eventListenersByPriority.put(eventClass, eventPriorityMap);
}
List<IEventListener> eventListenerList = eventPriorityMap.get(eventPriority);
if (eventListenerList == null) {
eventListenerList = new ArrayList<>();
eventPriorityMap.put(eventPriority, eventListenerList);
}
eventListenerList.add(eventListener);
bakeEventListeners(eventClass);
} finally {
lock.writeLock().unlock();
}
}
/**
* Registers a listener with the {@link EventPriority#DEFAULT} priority
* @param eventClass Event for which the event listener must be called
* @param eventListener Listener that may or may not have a generic type attached
*/
public void registerListener(final Class<? extends Event> eventClass, final IEventListener eventListener) {
registerListener(eventClass, eventListener, EventPriority.NORMAL);
}
/**
* Registers a listener, automatically detecting the event class
* @param eventListener Must have a generic type attached so it can detect the event for which this listener must be registered
* @param eventPriority Priority of the event
*/
public void registerListener(final IEventListener eventListener, final EventPriority eventPriority) {
Type[] interfaces = eventListener.getClass().getGenericInterfaces();
boolean found = false;
for (Type type : interfaces) {
if (type instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) type;
Type rawType = parameterizedType.getRawType();
if (rawType.equals(IEventListener.class)) {
ParameterizedType pt = (ParameterizedType) type;
Type[] types = pt.getActualTypeArguments();
Class<? extends Event> eventClass = (Class) types[0];
registerListener(eventClass, eventListener, eventPriority);
found = true;
}
}
}
if (!found) {
throw new UnsupportedOperationException("Unable to find generic interface of type IEventListener on class " + eventListener.getClass().getName());
}
}
/**
* Registers a listener with the {@link EventPriority#DEFAULT} priority, automatically detecting the event class
* @param eventListener Must have a generic type attached so it can detect the event for which this listener must be registered
*/
public void registerListener(final IEventListener eventListener) {
registerListener(eventListener, EventPriority.NORMAL);
}
/**
* Unregisters a listener, it will be unregistered from all events for which this listener is listed
* @param eventListener Event listener that will be unregistered
*/
public void unregisterListener(final IEventListener eventListener) {
lock.writeLock().lock();
try {
for (Map.Entry<Class<? extends Event>, Map<EventPriority, List<IEventListener>>> eventPriorityListMap : eventListenersByPriority.entrySet()) {
boolean found = false;
for (List<IEventListener> eventListenerList : eventPriorityListMap.getValue().values()) {
while (eventListenerList.contains(eventListener)) {
eventListenerList.remove(eventListener);
found = true;
}
}
if (found) {
bakeEventListeners(eventPriorityListMap.getKey());
}
}
} finally {
lock.writeLock().unlock();
}
}
/**
* Posts an event to all listeners
* @param event Event that will be posted
*/
public void post(final Event event) {
lock.readLock().lock();
try {
IEventListener[] eventListenerList = eventListeners.get(event.getClass());
if (eventListenerList != null) {
for (IEventListener eventListener : eventListenerList) {
try {
eventListener.onEvent(event);
} catch (Throwable t) {
logger.warn("Error dispatching event " + event + " to listener " + eventListener.getClass().getName(), t);
}
}
}
} finally {
lock.readLock().unlock();
}
}
private void bakeEventListeners(final Class<? extends Event> eventClass) {
Map<EventPriority, List<IEventListener>> eventPriorityMap = eventListenersByPriority.get(eventClass);
if (eventPriorityMap != null) {
List<IEventListener> eventListenerList = new ArrayList<>();
for (EventPriority eventPriority : EventPriority.values()) {
List<IEventListener> eventListenersByPriorityMap = eventPriorityMap.get(eventPriority);
if (eventListenersByPriorityMap != null) {
for (IEventListener eventListener : eventListenersByPriorityMap) {
eventListenerList.add(eventListener);
}
}
}
eventListeners.put(eventClass, eventListenerList.toArray(new IEventListener[eventListenerList.size()]));
} else {
eventListeners.put(eventClass, null);
}
}
private final Map<Class<? extends Event>, IEventListener[]> eventListeners = new HashMap<>();
private final Map<Class<? extends Event>, Map<EventPriority, List<IEventListener>>> eventListenersByPriority = new HashMap<>();
private final ReadWriteLock lock = new ReentrantReadWriteLock();
}