/** * Copyright 2011-2015 John Ericksen * * 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 org.androidtransfuse.event; import org.androidtransfuse.util.TransfuseRuntimeException; import javax.inject.Singleton; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CopyOnWriteArraySet; /** * Centralized Bus for registering, unregistering and triggering events. An event may be any object and the EventManager * triggers observers based on the non-generic type of the event object. Observers may be registered by calling * the `register()` method, which relates an observer to an event type. * * Transfuse will generate the calls to `register()` and `unregister()` based on the `@Observes` * annotation. Any method in an object managed by Transfuse annotated with `@Observes` will register a call * to that method with the first parameter in the method as the Event type. * * For instance: * [source,java] * -- * @Observes * public void drink(Coffee coffee){...} * -- * * will result in Transfuse generating a the following `EventObserver`: * * [source,java] * -- * public class DrinkCoffeeEventObserver<Coffee>{ * //... * void trigger(Coffee coffee){ * managedInstance.drink(coffee); * } * } * -- * * and the following registration: * * * [source,java] * -- * eventManager.register(Coffee.class, drinkCoffeeEventObserver); * -- * * * @author John Ericksen */ @Singleton public class EventManager { private final ConcurrentMap<Class, Set<EventObserver>> observers = new ConcurrentHashMap<Class, java.util.Set<EventObserver>>(); private final ThreadLocal<ConcurrentLinkedQueue<EventExecution>> executionQueue = new ExecutionQueueThreadLocal(); private final ThreadLocal<Boolean> executing = new BooleanThreadLocal(); private static final class EventExecution<T>{ private final T event; private final EventObserver<T> observer; private EventExecution(T event, EventObserver<T> observer) { this.event = event; this.observer = observer; } public void trigger() { try{ observer.trigger(event); } catch (Exception e){ throw new TransfuseRuntimeException("Exception caught during event trigger", e); } } } /** * Register the given observer to be triggered if the given event type is triggered. * * @param event type * @param observer event observer * @param <T> relating type */ public <T> void register(Class<T> event, EventObserver<T> observer){ if(event == null){ throw new IllegalArgumentException("Null Event type passed to register"); } if(observer == null){ throw new IllegalArgumentException("Null observer passed to register"); } nullSafeGet(event).add(observer); } private Set<EventObserver> nullSafeGet(Class<?> clazz) { Set<EventObserver> result = observers.get(clazz); if (result == null) { Set<EventObserver> value = new CopyOnWriteArraySet<EventObserver>(); result = observers.putIfAbsent(clazz, value); if (result == null) { result = value; } } return result; } /** * Triggers an event through the EventManager. This will call the registered EventObservers with the provided * event. * * @param event object */ public void trigger(Object event){ Set<Class> eventTypes = getAllInheritedClasses(event.getClass()); for (Class eventType : eventTypes) { if(observers.containsKey(eventType)){ for (EventObserver eventObserver : observers.get(eventType)) { executionQueue.get().add(new EventExecution(event, eventObserver)); } } } triggerQueue(); } private void triggerQueue(){ //avoid reentrant events if(executing.get()){ return; } executing.set(true); try{ EventExecution execution = executionQueue.get().poll(); while(execution != null){ execution.trigger(); execution = executionQueue.get().poll(); } } finally{ executing.set(false); } } private Set<Class> getAllInheritedClasses(Class type){ Set<Class> inheritedClasses = new HashSet<Class>(); addAllInheritedClasses(inheritedClasses, type); return inheritedClasses; } private void addAllInheritedClasses(Set<Class> inheritedClasses, Class type){ if(type != null){ inheritedClasses.add(type); addAllInheritedClasses(inheritedClasses, type.getSuperclass()); for(Class interf : type.getInterfaces()){ addAllInheritedClasses(inheritedClasses, interf); } } } /** * Unregisters an EventObserver by equality. * * @param observer Event Observer */ public void unregister(EventObserver<?> observer){ for (Map.Entry<Class, Set<EventObserver>> entry : observers.entrySet()) { entry.getValue().remove(observer); } } private static class BooleanThreadLocal extends ThreadLocal<Boolean>{ @Override protected Boolean initialValue() { return false; } } private static class ExecutionQueueThreadLocal extends ThreadLocal<ConcurrentLinkedQueue<EventExecution>> { @Override protected ConcurrentLinkedQueue<EventExecution> initialValue() { return new ConcurrentLinkedQueue<EventExecution>(); } } }