/*
* Copyright 2012 Research Studios Austria Forschungsges.m.b.H.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package won.bot.framework.eventbot.bus.impl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import won.bot.framework.eventbot.bus.EventBus;
import won.bot.framework.eventbot.event.Event;
import won.bot.framework.eventbot.listener.EventListener;
import won.bot.framework.eventbot.listener.SubscriptionAware;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.stream.Collectors;
/**
*
*/
public class AsyncEventBusImpl implements EventBus {
private final Logger logger = LoggerFactory.getLogger(getClass());
private Map<Class<? extends Event>, List<EventListener>> listenerMap = new ConcurrentHashMap<>();
private Executor executor;
private Object monitor = new Object();
public AsyncEventBusImpl(final Executor executor) {
this.executor = executor;
}
@Override
public <T extends Event> void publish(final T event) {
logger.debug("publishing event {}", event);
//get the list of listeners registered for the event
List<EventListener> listeners = getEventListenersForEvent(event);
if (listeners == null || listeners.size() == 0) {
logger.debug("no listeners registered for event {}, ignoring", event);
return;
}
//execute asynchronously: call the event listeners one after another
callEventListeners(listeners, event);
}
@Override
public <T extends Event> void subscribe(Class<T> clazz, final EventListener listener) {
logger.debug("subscribing listener {} for type {}", listener, clazz);
synchronized (monitor) {
//we want to synchronize so we don't accidentally add or remove listeners at the same time
List<EventListener> newListenerList = copyOrCreateList(this.listenerMap.get(clazz));
newListenerList.add(listener);
this.listenerMap.put(clazz, Collections.unmodifiableList(newListenerList));
callOnSubscribeIfApplicable(listener, clazz);
}
}
@Override
public <T extends Event> void unsubscribe(Class<T> clazz, final EventListener listener) {
logger.debug("unsubscribing listener {} for type {}", listener, clazz);
synchronized (monitor) {
//we want to synchronize so we don't accidentally add or remove listeners at the same time
List<EventListener> newListenerList = copyOrCreateList(this.listenerMap.get(clazz));
newListenerList.remove(listener);
this.listenerMap.put(clazz, Collections.unmodifiableList(newListenerList));
callOnUnsubscribeIfApplicable(listener, clazz);
}
}
@Override
public void unsubscribe(final EventListener listener) {
logger.debug("unsubscribing listener {} from all events", listener);
synchronized (monitor) {
for (Map.Entry<Class<? extends Event>, List<EventListener>> entry : listenerMap.entrySet()) {
boolean unsubscribed = false; //remember if we had to unsubscribe the listener for the current event type
List<EventListener> listeners = entry.getValue();
if (listeners == null) continue;
listeners = copyOrCreateList(listeners);
Iterator<EventListener> it = listeners.iterator();
while (it.hasNext()) {
EventListener subscribedListener = it.next();
if (subscribedListener.equals(listener)) {
it.remove();
unsubscribed = true;
}
}
entry.setValue(listeners);
if (unsubscribed) {
//if we had to unssubscribe the listener, we may have to call its onUnsubscribe method
callOnUnsubscribeIfApplicable(listener, entry.getKey());
}
}
}
}
private void callEventListeners(final List<EventListener> listeners, final Event event) {
if (listeners == null || listeners.isEmpty()) return;
this.executor.execute(new Runnable() {
@Override
public void run() {
logger.debug("processing event {} with {} listeners", event, listeners.size());
for (EventListener listener : listeners) {
try {
listener.onEvent(event);
} catch (Exception e) {
logger.warn("caught exception during execution of event {} on listener {}", event, listener);
logger.warn("stacktrace:", e);
}
}
}
});
}
private List<EventListener> getEventListenersForEvent(final Event event) {
//the map is secured against concurrent modification, the list inside is unmodifiable
Set<Class<? extends Event>> classes = getEventTypes(event.getClass(),new HashSet<>());
return listenerMap.entrySet().stream()
.filter(entry -> classes.contains(entry.getKey()))
.flatMap(e -> e.getValue().stream())
.collect(Collectors.toList());
}
private Set<Class<? extends Event>> getEventTypes(final Class<? extends Event> clazz, Set<Class<? extends Event>> eventTypes) {
if (eventTypes == null) eventTypes = new HashSet<>();
final Set<Class<? extends Event>> finalEventTypes = eventTypes;
//add interfaces and recurse for interfaces
Arrays.stream(clazz.getInterfaces()).forEach(c -> { if (Event.class.isAssignableFrom(c)) { getEventTypes((Class<? extends Event>) c, finalEventTypes);}});
Class superclass = clazz.getSuperclass();
if (superclass != null && !Event.class.isAssignableFrom(superclass)) {
getEventTypes(superclass, finalEventTypes);
}
finalEventTypes.add(clazz);
return finalEventTypes;
}
private List<EventListener> copyOrCreateList(final List<EventListener> listenerList) {
if (listenerList == null) return new ArrayList<EventListener>(1);
List<EventListener> newListenerList = new ArrayList<EventListener>(listenerList.size() + 1);
newListenerList.addAll(listenerList);
return newListenerList;
}
private <T extends Event> void callOnSubscribeIfApplicable(final EventListener listener, final Class<T> clazz) {
if (listener instanceof SubscriptionAware) {
((SubscriptionAware) listener).onSubscribe(clazz);
}
}
private <T extends Event> void callOnUnsubscribeIfApplicable(final EventListener listener, final Class<T> clazz) {
if (listener instanceof SubscriptionAware) {
((SubscriptionAware) listener).onUnsubscribe(clazz);
}
}
@Override
public EventBusStatistics generateEventBusStatistics() {
EventBusStatistics statistics = new EventBusStatistics();
statistics.setListenerCount(listenerMap.values().stream().flatMap(l -> l.stream()).distinct().count());
statistics.setListenerCountPerEvent(listenerMap.entrySet().stream().collect(
Collectors.toMap(e -> e.getKey(), e -> e.getValue().stream().distinct().count())));
statistics.setListenerCountPerListenerClass(listenerMap.values().stream().flatMap(l -> l.stream()).distinct()
.collect(Collectors.groupingBy(l -> l.getClass(), Collectors.counting())));
return statistics;
}
}