com.sk89q.eduardo.service.event.EventBus.java Source code

Java tutorial

Introduction

Here is the source code for com.sk89q.eduardo.service.event.EventBus.java

Source

/*
 * Eduardo, an IRC bot framework
 * Copyright (C) sk89q <http://www.sk89q.com>
 * Copyright (C) Eduardo team and contributors
 *
 * This program is free software: you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by the
 * Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 Lesser General Public License
 * for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

package com.sk89q.eduardo.service.event;

import com.google.common.base.Supplier;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import com.google.common.collect.SetMultimap;

import java.lang.reflect.InvocationTargetException;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;

import static com.google.common.base.Preconditions.checkNotNull;

/**
 * Dispatches events to listeners, and provides ways for listeners to register
 * themselves.
 *
 * <p>This class is based on Guava's {@link EventBus} but priority is supported
 * and events are dispatched at the time of call, rather than being queued up.
 * This does allow dispatching during an in-progress dispatch.</p>
 *
 * <p>This implementation utilizes naive synchronization on all getter and
 * setter methods. Dispatch does not occur when a lock has been acquired,
 * however.</p>
 */
public class EventBus {

    private final Logger logger = Logger.getLogger(EventBus.class.getCanonicalName());

    private final SetMultimap<Class<?>, EventHandler> handlersByType = Multimaps
            .newSetMultimap(new HashMap<Class<?>, Collection<EventHandler>>(), new Supplier<Set<EventHandler>>() {
                @Override
                public Set<EventHandler> get() {
                    return newHandlerSet();
                }
            });

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

    private HierarchyCache flattenHierarchyCache = new HierarchyCache();

    /**
     * Registers the given handler for the given class to receive events.
     *
     * @param clazz the event class to register
     * @param handler the handler to register
     */
    public synchronized void subscribe(Class<?> clazz, EventHandler handler) {
        checkNotNull(clazz);
        checkNotNull(handler);
        handlersByType.put(clazz, handler);
    }

    /**
     * Registers the given handler for the given class to receive events.
     *
     * @param handlers a map of handlers
     */
    public synchronized void subscribeAll(Multimap<Class<?>, EventHandler> handlers) {
        checkNotNull(handlers);
        handlersByType.putAll(handlers);
    }

    /**
     * Unregisters the given handler for the given class.
     *
     * @param clazz the class
     * @param handler the handler
     */
    public synchronized void unsubscribe(Class<?> clazz, EventHandler handler) {
        checkNotNull(clazz);
        checkNotNull(handler);
        handlersByType.remove(clazz, handler);
    }

    /**
     * Unregisters the given handlers.
     *
     * @param handlers a map of handlers
     */
    public synchronized void unsubscribeAll(Multimap<Class<?>, EventHandler> handlers) {
        checkNotNull(handlers);
        for (Map.Entry<Class<?>, Collection<EventHandler>> entry : handlers.asMap().entrySet()) {
            Set<EventHandler> currentHandlers = getHandlersForEventType(entry.getKey());
            Collection<EventHandler> eventMethodsInListener = entry.getValue();

            if (currentHandlers != null && !currentHandlers.containsAll(entry.getValue())) {
                currentHandlers.removeAll(eventMethodsInListener);
            }
        }
    }

    /**
     * Registers all handler methods on {@code object} to receive events.
     * Handler methods are selected and classified using this EventBus's
     * {@link SubscriberFindingStrategy}; the default strategy is the
     * {@link AnnotatedSubscriberFinder}.
     *
     * @param object object whose handler methods should be registered.
     */
    public void register(Object object) {
        subscribeAll(finder.findAllSubscribers(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) {
        unsubscribeAll(finder.findAllSubscribers(object));
    }

    /**
     * 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.
     *
     * @param event  event to post.
     */
    public void post(Object event) {
        List<EventHandler> dispatching = new ArrayList<EventHandler>();

        synchronized (this) {
            Set<Class<?>> dispatchTypes = flattenHierarchy(event.getClass());

            for (Class<?> eventType : dispatchTypes) {
                Set<EventHandler> wrappers = getHandlersForEventType(eventType);

                if (wrappers != null && !wrappers.isEmpty()) {
                    dispatching.addAll(wrappers);
                }
            }
        }

        Collections.sort(dispatching);

        for (EventHandler handler : dispatching) {
            dispatch(event, handler);
        }
    }

    /**
     * Dispatches {@code event} to the handler in {@code handler}.  This method
     * is an appropriate override point for subclasses that wish to make
     * event delivery asynchronous.
     *
     * @param event  event to dispatch.
     * @param handler  handler that will call the handler.
     */
    protected void dispatch(Object event, EventHandler handler) {
        try {
            handler.handleEvent(event);
        } catch (InvocationTargetException e) {
            logger.log(Level.SEVERE, "Could not dispatch event: " + event + " to handler " + handler, e);
        }
    }

    /**
     * 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}.
     */
    synchronized 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 synchronized Set<EventHandler> newHandlerSet() {
        return new HashSet<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.
     */
    Set<Class<?>> flattenHierarchy(Class<?> concreteClass) {
        return flattenHierarchyCache.get(concreteClass);
    }

}