com.squareup.otto.Bus Maven / Gradle / Ivy
/*
* Copyright (C) 2012 Square, Inc.
* Copyright (C) 2007 The Guava Authors
*
* 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 com.squareup.otto;
import java.lang.reflect.InvocationTargetException;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.logging.Level;
import java.util.logging.Logger;
import static com.squareup.otto.AnnotatedHandlerFinder.findAllProducers;
import static com.squareup.otto.AnnotatedHandlerFinder.findAllSubscribers;
/**
* Dispatches events to listeners, and provides ways for listeners to register themselves.
*
* The Bus allows publish-subscribe-style communication between components without requiring the components to
* explicitly register with one another (and thus be aware of each other). It is designed exclusively to replace
* traditional Android in-process event distribution using explicit registration or listeners. It is not a
* general-purpose publish-subscribe system, nor is it intended for interprocess communication.
*
*
Receiving Events
* To receive events, an object should:
*
* - Expose a public method, known as the event handler, which accepts a single argument of the type of event
* desired;
* - Mark it with a {@link com.squareup.otto.Subscribe} annotation;
* - Pass itself to an Bus instance's {@link #register(Object)} method.
*
*
*
* Posting Events
* To post an event, simply provide the event object to the {@link #post(Object)} method. The Bus instance will
* determine the type of event and route it to all registered listeners.
*
* Events are routed based on their type — an event will be delivered to any handler for any type to which the
* event is assignable. This includes implemented interfaces, all superclasses, and all interfaces implemented
* by superclasses.
*
*
When {@code post} is called, all registered handlers for an event are run in sequence, so handlers should be
* reasonably quick. If an event may trigger an extended process (such as a database load), spawn a thread or queue it
* for later.
*
*
Handler Methods
* Event handler methods must accept only one argument: the event.
*
* Handlers should not, in general, throw. If they do, the Bus will catch and log the exception. This is rarely the
* right solution for error handling and should not be relied upon; it is intended solely to help find problems during
* development.
*
*
The Bus by default enforces that all interactions occur on the main thread. You can provide an alternate
* enforcement by passing a {@link ThreadEnforcer} to the constructor.
*
*
Producer Methods
* Producer methods should accept no arguments and return their event type. When a subscriber is registered for a type
* that a producer is also already registered for, the subscriber will be called with the return value from the
* producer.
*
* Dead Events
* If an event is posted, but no registered handlers can accept it, it is considered "dead." To give the system a
* second chance to handle dead events, they are wrapped in an instance of {@link com.squareup.otto.DeadEvent} and
* reposted.
*
* This class is safe for concurrent use.
*
* @author Cliff Biffle
* @author Jake Wharton
*/
public class Bus {
public static final String DEFAULT_IDENTIFIER = "default";
/** All registered event handlers, indexed by event type. */
private final Map, Set> handlersByType = new ConcurrentHashMap, Set>();
/** All registered event producers, index by event type. */
private final Map, EventProducer> producersByType = new ConcurrentHashMap, EventProducer>();
/**
* Logger for event dispatch failures. Named by the fully-qualified name of
* this class, followed by the identifier provided at construction.
*/
private final Logger logger;
/** Identifier used to differentiate the event bus instance. */
private final String identifier;
/** Thread enforcer for register, unregister, and posting events. */
private final ThreadEnforcer enforcer;
/** Queues of events for the current thread to dispatch. */
private final ThreadLocal> eventsToDispatch =
new ThreadLocal>() {
@Override protected ConcurrentLinkedQueue initialValue() {
return new ConcurrentLinkedQueue();
}
};
/** True if the current thread is currently dispatching an event. */
private final ThreadLocal isDispatching = new ThreadLocal() {
@Override protected Boolean initialValue() {
return false;
}
};
/** Creates a new Bus named "default" that enforces actions on the main thread. */
public Bus() {
this(DEFAULT_IDENTIFIER);
}
/**
* Creates a new Bus with the given {@code identifier} that enforces actions on the main thread.
*
* @param identifier a brief name for this bus, for logging purposes. Should be a valid Java identifier.
*/
public Bus(String identifier) {
this(ThreadEnforcer.MAIN, identifier);
}
/**
* Creates a new Bus named "default" with the given {@code enforcer} for actions.
*
* @param enforcer Thread enforcer for register, unregister, and post actions.
*/
public Bus(ThreadEnforcer enforcer) {
this(enforcer, DEFAULT_IDENTIFIER);
}
/**
* Creates a new Bus with the given {@code enforcer} for actions and the given {@code identifier}.
*
* @param enforcer Thread enforcer for register, unregister, and post actions.
* @param identifier A brief name for this bus, for logging purposes. Should be a valid Java identifier.
*/
public Bus(ThreadEnforcer enforcer, String identifier) {
this.enforcer = enforcer;
this.identifier = identifier;
logger = Logger.getLogger(Bus.class.getName() + "." + identifier);
}
@Override public String toString() {
return "[Bus \"" + identifier + "\"]";
}
/**
* Registers all handler methods on {@code object} to receive events and producer methods to provide events.
*
* If any subscribers are registering for types which already have a producer they will be called immediately
* with the result of calling that producer.
*
* If any producers are registering for types which already have subscribers, each subscriber will be called with
* the value from the result of calling the producer.
*
* @param object object whose handler methods should be registered.
*/
public void register(Object object) {
enforcer.enforce(this);
Map, EventProducer> foundProducers = findAllProducers(object);
for (Class> type : foundProducers.keySet()) {
if (producersByType.containsKey(type)) {
throw new IllegalArgumentException("Producer method for type " + type + " already registered.");
}
final EventProducer producer = foundProducers.get(type);
producersByType.put(type, producer);
Set handlers = handlersByType.get(type);
if (handlers != null && !handlers.isEmpty()) {
for (EventHandler handler : handlers) {
dispatchProducerResultToHandler(handler, producer);
}
}
}
Map, Set> foundHandlersMap = findAllSubscribers(object);
for (Class> type : foundHandlersMap.keySet()) {
Set handlers = handlersByType.get(type);
if (handlers == null) {
handlers = new CopyOnWriteArraySet();
handlersByType.put(type, handlers);
}
final Set foundHandlers = foundHandlersMap.get(type);
handlers.addAll(foundHandlers);
EventProducer producer = producersByType.get(type);
if (producer != null) {
for (EventHandler foundHandler : foundHandlers) {
dispatchProducerResultToHandler(foundHandler, producer);
}
}
}
}
private void dispatchProducerResultToHandler(EventHandler handler, EventProducer producer) {
try {
final Object event = producer.produceEvent();
if (event == null) {
throw new IllegalStateException("Producer " + producer + " must return a non-null value.");
}
handler.handleEvent(event);
} catch (InvocationTargetException e) {
logger.log(Level.SEVERE, "Could not dispatch event from " + producer + " to handler " + handler, e);
}
}
/**
* Unregisters all producer and handler methods on a registered {@code object}.
*
* @param object object whose producer and handler methods should be unregistered.
* @throws IllegalArgumentException if the object was not previously registered.
*/
public void unregister(Object object) {
enforcer.enforce(this);
Map, EventProducer> producersInListener = findAllProducers(object);
for (Map.Entry, EventProducer> entry : producersInListener.entrySet()) {
final Class> key = entry.getKey();
EventProducer producer = getProducerForEventType(key);
EventProducer value = entry.getValue();
if (value == null || !value.equals(producer)) {
throw new IllegalArgumentException(
"Missing event producer for an annotated method. Is " + object + " registered?");
}
producersByType.remove(key);
}
Map, Set> handlersInListener = findAllSubscribers(object);
for (Map.Entry, Set> entry : handlersInListener.entrySet()) {
Set currentHandlers = getHandlersForEventType(entry.getKey());
Collection eventMethodsInListener = entry.getValue();
if (currentHandlers == null || !currentHandlers.removeAll(eventMethodsInListener)) {
throw new IllegalArgumentException(
"Missing event handler for an annotated method. Is " + object + " registered?");
}
}
}
/**
* Posts an event to all registered handlers. This method will return successfully after the event has been posted to
* all handlers, and regardless of any exceptions thrown by handlers.
*
* If no handlers have been subscribed for {@code event}'s class, and {@code event} is not already a
* {@link DeadEvent}, it will be wrapped in a DeadEvent and reposted.
*
* @param event event to post.
*/
public void post(Object event) {
enforcer.enforce(this);
Set> dispatchTypes = flattenHierarchy(event.getClass());
boolean dispatched = false;
for (Class> eventType : dispatchTypes) {
Set wrappers = getHandlersForEventType(eventType);
if (wrappers != null && !wrappers.isEmpty()) {
dispatched = true;
for (EventHandler wrapper : wrappers) {
enqueueEvent(event, wrapper);
}
}
}
if (!dispatched && !(event instanceof DeadEvent)) {
post(new DeadEvent(this, event));
}
dispatchQueuedEvents();
}
/**
* Queue the {@code event} for dispatch during {@link #dispatchQueuedEvents()}. Events are queued in-order of
* occurrence so they can be dispatched in the same order.
*/
protected void enqueueEvent(Object event, EventHandler handler) {
eventsToDispatch.get().offer(new EventWithHandler(event, handler));
}
/**
* 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.
*/
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.
if (isDispatching.get()) {
return;
}
isDispatching.set(true);
try {
while (true) {
EventWithHandler eventWithHandler = eventsToDispatch.get().poll();
if (eventWithHandler == null) {
break;
}
dispatch(eventWithHandler.event, eventWithHandler.handler);
}
} finally {
isDispatching.set(false);
}
}
/**
* Dispatches {@code event} to the handler in {@code wrapper}. This method is an appropriate override point for
* subclasses that wish to make event delivery asynchronous.
*
* @param event event to dispatch.
* @param wrapper wrapper that will call the handler.
*/
protected void dispatch(Object event, EventHandler wrapper) {
try {
wrapper.handleEvent(event);
} catch (InvocationTargetException e) {
logger.log(Level.SEVERE, "Could not dispatch event: " + event + " to handler " + wrapper, e);
}
}
/**
* Retrieves a mutable set of the currently registered producers for {@code type}. If no producers are currently
* registered for {@code type}, this method will return {@code null}.
*
* @param type type of producers to retrieve.
* @return currently registered producer, or {@code null}.
*/
EventProducer getProducerForEventType(Class> type) {
return producersByType.get(type);
}
/**
* 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 getHandlersForEventType(Class> type) {
return handlersByType.get(type);
}
/**
* 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.
*
* @param concreteClass class whose type hierarchy will be retrieved.
* @return {@code clazz}'s complete type hierarchy, flattened and uniqued.
*/
Set> flattenHierarchy(Class> concreteClass) {
Set> classes = flattenHierarchyCache.get(concreteClass);
if (classes == null) {
classes = getClassesFor(concreteClass);
flattenHierarchyCache.put(concreteClass, classes);
}
return classes;
}
private Set> getClassesFor(Class> concreteClass) {
List> parents = new LinkedList>();
Set> classes = new HashSet>();
parents.add(concreteClass);
while (!parents.isEmpty()) {
Class> clazz = parents.remove(0);
classes.add(clazz);
Class> parent = clazz.getSuperclass();
if (parent != null) {
parents.add(parent);
}
}
return classes;
}
private final Map, Set>> flattenHierarchyCache =
new HashMap, Set>>();
/** Simple struct representing an event and it's handler. */
static class EventWithHandler {
final Object event;
final EventHandler handler;
public EventWithHandler(Object event, EventHandler handler) {
this.event = event;
this.handler = handler;
}
}
}