//////////////////////////////////////////////////////////////////////////////// // Copyright 2013 Michael Schmalle - Teoti Graphix, LLC // // 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 // // Author: Michael Schmalle, Principal Architect // mschmalle at teotigraphix dot com //////////////////////////////////////////////////////////////////////////////// package org.androidtransfuse.event; 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; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; @SuppressWarnings("rawtypes") public class EventManager { private final ReadWriteLock observersLock = new ReentrantReadWriteLock(); 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 RuntimeException("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"); } observersLock.writeLock().lock(); try { nullSafeGet(event).add(observer); } finally { observersLock.writeLock().unlock(); } } 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 */ @SuppressWarnings("unchecked") public void trigger(Object event) { Set<Class> eventTypes = getAllInheritedClasses(event.getClass()); observersLock.readLock().lock(); try { for (Class eventType : eventTypes) { if (observers.containsKey(eventType)) { for (EventObserver eventObserver : observers.get(eventType)) { executionQueue.get().add(new EventExecution(event, eventObserver)); } } } triggerQueue(); } finally { observersLock.readLock().unlock(); } } 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) { observersLock.writeLock().lock(); try { for (Map.Entry<Class, Set<EventObserver>> entry : observers.entrySet()) { entry.getValue().remove(observer); } } finally { observersLock.writeLock().unlock(); } } 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>(); } } }