diff options
Diffstat (limited to 'guava/src/com/google/common/eventbus/EventBus.java')
-rw-r--r-- | guava/src/com/google/common/eventbus/EventBus.java | 184 |
1 files changed, 103 insertions, 81 deletions
diff --git a/guava/src/com/google/common/eventbus/EventBus.java b/guava/src/com/google/common/eventbus/EventBus.java index 94cf2e9..a962fa8 100644 --- a/guava/src/com/google/common/eventbus/EventBus.java +++ b/guava/src/com/google/common/eventbus/EventBus.java @@ -16,28 +16,28 @@ package com.google.common.eventbus; -import static com.google.common.base.Preconditions.checkNotNull; - import com.google.common.annotations.Beta; import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Supplier; import com.google.common.base.Throwables; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; -import com.google.common.collect.HashMultimap; +import com.google.common.collect.Lists; import com.google.common.collect.Multimap; +import com.google.common.collect.Multimaps; import com.google.common.collect.SetMultimap; -import com.google.common.reflect.TypeToken; -import com.google.common.util.concurrent.UncheckedExecutionException; +import com.google.common.collect.Sets; import java.lang.reflect.InvocationTargetException; import java.util.Collection; -import java.util.LinkedList; +import java.util.List; import java.util.Map.Entry; -import java.util.Queue; import java.util.Set; -import java.util.concurrent.locks.ReadWriteLock; -import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.CopyOnWriteArraySet; +import java.util.concurrent.ExecutionException; import java.util.logging.Level; import java.util.logging.Logger; @@ -101,10 +101,6 @@ import java.util.logging.Logger; * receive any Object will never receive a DeadEvent. * * <p>This class is safe for concurrent use. - * - * <p>See the Guava User Guide article on <a href= - * "http://code.google.com/p/guava-libraries/wiki/EventBusExplained"> - * {@code EventBus}</a>. * * @author Cliff Biffle * @since 10.0 @@ -113,32 +109,18 @@ import java.util.logging.Logger; public class EventBus { /** - * A thread-safe cache for flattenHierarchy(). The Class class is immutable. This cache is shared - * across all EventBus instances, which greatly improves performance if multiple such instances - * are created and objects of the same class are posted on all of them. + * All registered event handlers, indexed by event type. */ - private static final LoadingCache<Class<?>, Set<Class<?>>> flattenHierarchyCache = - CacheBuilder.newBuilder() - .weakKeys() - .build(new CacheLoader<Class<?>, Set<Class<?>>>() { - @SuppressWarnings({"unchecked", "rawtypes"}) // safe cast + private final SetMultimap<Class<?>, EventHandler> handlersByType = + Multimaps.newSetMultimap(new ConcurrentHashMap<Class<?>, Collection<EventHandler>>(), + new Supplier<Set<EventHandler>>() { @Override - public Set<Class<?>> load(Class<?> concreteClass) { - return (Set) TypeToken.of(concreteClass).getTypes().rawTypes(); + public Set<EventHandler> get() { + return new CopyOnWriteArraySet<EventHandler>(); } }); /** - * All registered event handlers, indexed by event type. - * - * <p>This SetMultimap is NOT safe for concurrent use; all access should be - * made after acquiring a read or write lock via {@link #handlersByTypeLock}. - */ - private final SetMultimap<Class<?>, EventHandler> handlersByType = - HashMultimap.create(); - private final ReadWriteLock handlersByTypeLock = new ReentrantReadWriteLock(); - - /** * Logger for event dispatch failures. Named by the fully-qualified name of * this class, followed by the identifier provided at construction. */ @@ -152,10 +134,11 @@ public class EventBus { private final HandlerFindingStrategy finder = new AnnotatedHandlerFinder(); /** queues of events for the current thread to dispatch */ - private final ThreadLocal<Queue<EventWithHandler>> eventsToDispatch = - new ThreadLocal<Queue<EventWithHandler>>() { - @Override protected Queue<EventWithHandler> initialValue() { - return new LinkedList<EventWithHandler>(); + private final ThreadLocal<ConcurrentLinkedQueue<EventWithHandler>> + eventsToDispatch = + new ThreadLocal<ConcurrentLinkedQueue<EventWithHandler>>() { + @Override protected ConcurrentLinkedQueue<EventWithHandler> initialValue() { + return new ConcurrentLinkedQueue<EventWithHandler>(); } }; @@ -168,6 +151,38 @@ public class EventBus { }; /** + * A thread-safe cache for flattenHierarch(). The Class class is immutable. + */ + private LoadingCache<Class<?>, Set<Class<?>>> flattenHierarchyCache = + CacheBuilder.newBuilder() + .weakKeys() + .build(new CacheLoader<Class<?>, Set<Class<?>>>() { + @Override + public Set<Class<?>> load(Class<?> concreteClass) throws Exception { + List<Class<?>> parents = Lists.newLinkedList(); + Set<Class<?>> classes = Sets.newHashSet(); + + parents.add(concreteClass); + + while (!parents.isEmpty()) { + Class<?> clazz = parents.remove(0); + classes.add(clazz); + + Class<?> parent = clazz.getSuperclass(); + if (parent != null) { + parents.add(parent); + } + + for (Class<?> iface : clazz.getInterfaces()) { + parents.add(iface); + } + } + + return classes; + } + }); + + /** * Creates a new EventBus named "default". */ public EventBus() { @@ -181,7 +196,7 @@ public class EventBus { * be a valid Java identifier. */ public EventBus(String identifier) { - logger = Logger.getLogger(EventBus.class.getName() + "." + checkNotNull(identifier)); + logger = Logger.getLogger(EventBus.class.getName() + "." + identifier); } /** @@ -193,14 +208,7 @@ public class EventBus { * @param object object whose handler methods should be registered. */ public void register(Object object) { - Multimap<Class<?>, EventHandler> methodsInListener = - finder.findAllHandlers(object); - handlersByTypeLock.writeLock().lock(); - try { - handlersByType.putAll(methodsInListener); - } finally { - handlersByTypeLock.writeLock().unlock(); - } + handlersByType.putAll(finder.findAllHandlers(object)); } /** @@ -212,20 +220,14 @@ public class EventBus { public void unregister(Object object) { Multimap<Class<?>, EventHandler> methodsInListener = finder.findAllHandlers(object); for (Entry<Class<?>, Collection<EventHandler>> entry : methodsInListener.asMap().entrySet()) { - Class<?> eventType = entry.getKey(); + Set<EventHandler> currentHandlers = getHandlersForEventType(entry.getKey()); Collection<EventHandler> eventMethodsInListener = entry.getValue(); - - handlersByTypeLock.writeLock().lock(); - try { - Set<EventHandler> currentHandlers = handlersByType.get(eventType); - if (!currentHandlers.containsAll(eventMethodsInListener)) { - throw new IllegalArgumentException( - "missing event handler for an annotated method. Is " + object + " registered?"); - } - currentHandlers.removeAll(eventMethodsInListener); - } finally { - handlersByTypeLock.writeLock().unlock(); + + if (currentHandlers == null || !currentHandlers.containsAll(entry.getValue())) { + throw new IllegalArgumentException( + "missing event handler for an annotated method. Is " + object + " registered?"); } + currentHandlers.removeAll(eventMethodsInListener); } } @@ -245,18 +247,13 @@ public class EventBus { boolean dispatched = false; for (Class<?> eventType : dispatchTypes) { - handlersByTypeLock.readLock().lock(); - try { - Set<EventHandler> wrappers = handlersByType.get(eventType); - - if (!wrappers.isEmpty()) { - dispatched = true; - for (EventHandler wrapper : wrappers) { - enqueueEvent(event, wrapper); - } + Set<EventHandler> wrappers = getHandlersForEventType(eventType); + + if (wrappers != null && !wrappers.isEmpty()) { + dispatched = true; + for (EventHandler wrapper : wrappers) { + enqueueEvent(event, wrapper); } - } finally { - handlersByTypeLock.readLock().unlock(); } } @@ -272,7 +269,7 @@ public class EventBus { * {@link #dispatchQueuedEvents()}. Events are queued in-order of occurrence * so they can be dispatched in the same order. */ - void enqueueEvent(Object event, EventHandler handler) { + protected void enqueueEvent(Object event, EventHandler handler) { eventsToDispatch.get().offer(new EventWithHandler(event, handler)); } @@ -280,7 +277,7 @@ public class EventBus { * Drain the queue of events to be dispatched. As the queue is being drained, * new events may be posted to the end of the queue. */ - void dispatchQueuedEvents() { + protected void dispatchQueuedEvents() { // don't dispatch if we're already dispatching, that would allow reentrancy // and out-of-order events. Instead, leave the events to be dispatched // after the in-progress dispatch is complete. @@ -290,14 +287,16 @@ public class EventBus { isDispatching.set(true); try { - Queue<EventWithHandler> events = eventsToDispatch.get(); - EventWithHandler eventWithHandler; - while ((eventWithHandler = events.poll()) != null) { + while (true) { + EventWithHandler eventWithHandler = eventsToDispatch.get().poll(); + if (eventWithHandler == null) { + break; + } + dispatch(eventWithHandler.event, eventWithHandler.handler); } } finally { - isDispatching.remove(); - eventsToDispatch.remove(); + isDispatching.set(false); } } @@ -309,7 +308,7 @@ public class EventBus { * @param event event to dispatch. * @param wrapper wrapper that will call the handler. */ - void dispatch(Object event, EventHandler wrapper) { + protected void dispatch(Object event, EventHandler wrapper) { try { wrapper.handleEvent(event); } catch (InvocationTargetException e) { @@ -319,6 +318,29 @@ public class EventBus { } /** + * Retrieves a mutable set of the currently registered handlers for + * {@code type}. If no handlers are currently registered for {@code type}, + * this method may either return {@code null} or an empty set. + * + * @param type type of handlers to retrieve. + * @return currently registered handlers, or {@code null}. + */ + Set<EventHandler> getHandlersForEventType(Class<?> type) { + return handlersByType.get(type); + } + + /** + * Creates a new Set for insertion into the handler map. This is provided + * as an override point for subclasses. The returned set should support + * concurrent access. + * + * @return a new, mutable set for handlers. + */ + protected Set<EventHandler> newHandlerSet() { + return new CopyOnWriteArraySet<EventHandler>(); + } + + /** * Flattens a class's type hierarchy into a set of Class objects. The set * will include all superclasses (transitively), and all interfaces * implemented by these superclasses. @@ -329,8 +351,8 @@ public class EventBus { @VisibleForTesting Set<Class<?>> flattenHierarchy(Class<?> concreteClass) { try { - return flattenHierarchyCache.getUnchecked(concreteClass); - } catch (UncheckedExecutionException e) { + return flattenHierarchyCache.get(concreteClass); + } catch (ExecutionException e) { throw Throwables.propagate(e.getCause()); } } @@ -340,8 +362,8 @@ public class EventBus { final Object event; final EventHandler handler; public EventWithHandler(Object event, EventHandler handler) { - this.event = checkNotNull(event); - this.handler = checkNotNull(handler); + this.event = event; + this.handler = handler; } } } |