blow.eventbus.OrderedEventBus.java Source code

Java tutorial

Introduction

Here is the source code for blow.eventbus.OrderedEventBus.java

Source

/*
 * Copyright (c) 2012, the authors.
 *
 *   This file is part of Blow.
 *
 *   Blow is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   Blow is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with Blow.  If not, see <http://www.gnu.org/licenses/>.
 */

package blow.eventbus;

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.*;
import com.google.common.eventbus.DeadEvent;

import java.lang.reflect.InvocationTargetException;
import java.util.Collection;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutionException;
import java.util.logging.Logger;

/**
 * NOTE: THIS FILE IS A DERIVATIVE WORK OF THE GOOGLE GUAVA LIBRARY
 *
 *
 * @author Paolo Di Tommaso
 */

@Beta
public class OrderedEventBus {

    /**
     * All registered event handlers, indexed by event type.
     */
    private final ListMultimap<Class<?>, EventHandler> handlersByType = Multimaps.newListMultimap(
            new ConcurrentHashMap<Class<?>, Collection<EventHandler>>(), new Supplier<List<EventHandler>>() {
                @Override
                public List<EventHandler> get() {
                    return new CopyOnWriteArrayList<EventHandler>();
                }
            });

    /**
     * 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;

    /**
     * Strategy for finding handler methods in registered objects.  Currently,
     * only the {@link AnnotatedHandlerFinder} is supported, but this is
     * encapsulated for future expansion.
     */
    private final HandlerFindingStrategy finder = new AnnotatedHandlerFinder();

    /** queues of events for the current thread to dispatch */
    private final ThreadLocal<ConcurrentLinkedQueue<EventWithHandler>> eventsToDispatch = new ThreadLocal<ConcurrentLinkedQueue<EventWithHandler>>() {
        @Override
        protected ConcurrentLinkedQueue<EventWithHandler> initialValue() {
            return new ConcurrentLinkedQueue<EventWithHandler>();
        }
    };

    /** true if the current thread is currently dispatching an event */
    private final ThreadLocal<Boolean> isDispatching = new ThreadLocal<Boolean>() {
        @Override
        protected Boolean initialValue() {
            return false;
        }
    };

    /**
     * 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 OrderedEventBus() {
        this("default");
    }

    /**
     * Creates a new EventBus with the given {@code identifier}.
     *
     * @param identifier  a brief name for this bus, for logging purposes.  Should
     *                  be a valid Java identifier.
     */
    public OrderedEventBus(String identifier) {
        logger = Logger.getLogger(OrderedEventBus.class.getName() + "." + identifier);
    }

    /**
     * Registers all handler methods on {@code object} to receive events.
     * Handler methods are selected and classified using this EventBus's
     * {@link HandlerFindingStrategy}; the default strategy is the
     * {@link AnnotatedHandlerFinder}.
     *
     * @param object  object whose handler methods should be registered.
     */
    public void register(Object object) {
        handlersByType.putAll(finder.findAllHandlers(object));
    }

    /**
     * Unregisters all handler methods on a registered {@code object}.
     *
     * @param object  object whose handler methods should be unregistered.
     * @throws IllegalArgumentException if the object was not previously registered.
     */
    public void unregister(Object object) {
        Multimap<Class<?>, EventHandler> methodsInListener = finder.findAllHandlers(object);
        for (Entry<Class<?>, Collection<EventHandler>> entry : methodsInListener.asMap().entrySet()) {
            List<EventHandler> currentHandlers = getHandlersForEventType(entry.getKey());
            Collection<EventHandler> eventMethodsInListener = entry.getValue();

            if (currentHandlers == null || !currentHandlers.containsAll(entry.getValue())) {
                throw new IllegalArgumentException(
                        "missing event handler for an annotated method. Is " + object + " registered?");
            }
            currentHandlers.removeAll(eventMethodsInListener);
        }
    }

    /**
     * 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.
     *
     * <p>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) throws InvocationTargetException {
        Set<Class<?>> dispatchTypes = flattenHierarchy(event.getClass());

        boolean dispatched = false;
        for (Class<?> eventType : dispatchTypes) {
            List<EventHandler> 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() throws InvocationTargetException {
        // 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) throws InvocationTargetException {
        wrapper.handleEvent(event);
    }

    /**
     * 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}.
     */
    List<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 List<EventHandler> newHandlerSet() {
        return new CopyOnWriteArrayList<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.
     *
     * @param concreteClass  class whose type hierarchy will be retrieved.
     * @return {@code clazz}'s complete type hierarchy, flattened and uniqued.
     */
    @VisibleForTesting
    Set<Class<?>> flattenHierarchy(Class<?> concreteClass) {
        try {
            return flattenHierarchyCache.get(concreteClass);
        } catch (ExecutionException e) {
            throw Throwables.propagate(e.getCause());
        }
    }

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