All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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: *
    *
  1. Expose a public method, known as the event handler, which accepts a single argument of the type of event * desired;
  2. *
  3. Mark it with a {@link com.squareup.otto.Subscribe} annotation;
  4. *
  5. Pass itself to an Bus instance's {@link #register(Object)} method. *
  6. *
* *

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; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy