PageRenderTime 20ms CodeModel.GetById 11ms app.highlight 7ms RepoModel.GetById 0ms app.codeStats 0ms

/worldedit-core/src/main/java/com/sk89q/worldedit/util/eventbus/EventBus.java

https://gitlab.com/Skull3x/WorldEdit
Java | 229 lines | 89 code | 27 blank | 113 comment | 8 complexity | d1bb742b15d8b1576a31f5f46c0acc01 MD5 | raw file
  1/*
  2 * WorldEdit, a Minecraft world manipulation toolkit
  3 * Copyright (C) sk89q <http://www.sk89q.com>
  4 * Copyright (C) WorldEdit team and contributors
  5 *
  6 * This program is free software: you can redistribute it and/or modify it
  7 * under the terms of the GNU Lesser General Public License as published by the
  8 * Free Software Foundation, either version 3 of the License, or
  9 * (at your option) any later version.
 10 *
 11 * This program is distributed in the hope that it will be useful, but WITHOUT
 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
 14 * for more details.
 15 *
 16 * You should have received a copy of the GNU Lesser General Public License
 17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 18 */
 19
 20package com.sk89q.worldedit.util.eventbus;
 21
 22import com.google.common.base.Supplier;
 23import com.google.common.collect.Multimap;
 24import com.google.common.collect.Multimaps;
 25import com.google.common.collect.SetMultimap;
 26import com.google.common.eventbus.DeadEvent;
 27import com.sk89q.worldedit.internal.annotation.RequiresNewerGuava;
 28
 29import java.lang.reflect.InvocationTargetException;
 30import java.util.*;
 31import java.util.logging.Level;
 32import java.util.logging.Logger;
 33
 34import static com.google.common.base.Preconditions.checkNotNull;
 35
 36/**
 37 * Dispatches events to listeners, and provides ways for listeners to register
 38 * themselves.
 39 *
 40 * <p>This class is based on Guava's {@link EventBus} but priority is supported
 41 * and events are dispatched at the time of call, rather than being queued up.
 42 * This does allow dispatching during an in-progress dispatch.</p>
 43 *
 44 * <p>This implementation utilizes naive synchronization on all getter and
 45 * setter methods. Dispatch does not occur when a lock has been acquired,
 46 * however.</p>
 47 */
 48public class EventBus {
 49
 50    private final Logger logger = Logger.getLogger(EventBus.class.getCanonicalName());
 51
 52    private final SetMultimap<Class<?>, EventHandler> handlersByType =
 53            Multimaps.newSetMultimap(new HashMap<Class<?>, Collection<EventHandler>>(),
 54                    new Supplier<Set<EventHandler>>() {
 55                        @Override
 56                        public Set<EventHandler> get() {
 57                            return newHandlerSet();
 58                        }
 59                    });
 60
 61    /**
 62     * Strategy for finding handler methods in registered objects.  Currently,
 63     * only the {@link AnnotatedSubscriberFinder} is supported, but this is
 64     * encapsulated for future expansion.
 65     */
 66    private final SubscriberFindingStrategy finder = new AnnotatedSubscriberFinder();
 67
 68    @RequiresNewerGuava
 69    private HierarchyCache flattenHierarchyCache = new HierarchyCache();
 70
 71    /**
 72     * Registers the given handler for the given class to receive events.
 73     *
 74     * @param clazz the event class to register
 75     * @param handler the handler to register
 76     */
 77    public synchronized void subscribe(Class<?> clazz, EventHandler handler) {
 78        checkNotNull(clazz);
 79        checkNotNull(handler);
 80        handlersByType.put(clazz, handler);
 81    }
 82
 83    /**
 84     * Registers the given handler for the given class to receive events.
 85     *
 86     * @param handlers a map of handlers
 87     */
 88    public synchronized void subscribeAll(Multimap<Class<?>, EventHandler> handlers) {
 89        checkNotNull(handlers);
 90        handlersByType.putAll(handlers);
 91    }
 92
 93    /**
 94     * Unregisters the given handler for the given class.
 95     *
 96     * @param clazz the class
 97     * @param handler the handler
 98     */
 99    public synchronized void unsubscribe(Class<?> clazz, EventHandler handler) {
100        checkNotNull(clazz);
101        checkNotNull(handler);
102        handlersByType.remove(clazz, handler);
103    }
104
105    /**
106     * Unregisters the given handlers.
107     *
108     * @param handlers a map of handlers
109     */
110    public synchronized void unsubscribeAll(Multimap<Class<?>, EventHandler> handlers) {
111        checkNotNull(handlers);
112        for (Map.Entry<Class<?>, Collection<EventHandler>> entry : handlers.asMap().entrySet()) {
113            Set<EventHandler> currentHandlers = getHandlersForEventType(entry.getKey());
114            Collection<EventHandler> eventMethodsInListener = entry.getValue();
115
116            if (currentHandlers != null &&!currentHandlers.containsAll(entry.getValue())) {
117                currentHandlers.removeAll(eventMethodsInListener);
118            }
119        }
120    }
121
122    /**
123     * Registers all handler methods on {@code object} to receive events.
124     * Handler methods are selected and classified using this EventBus's
125     * {@link SubscriberFindingStrategy}; the default strategy is the
126     * {@link AnnotatedSubscriberFinder}.
127     *
128     * @param object object whose handler methods should be registered.
129     */
130    public void register(Object object) {
131        subscribeAll(finder.findAllSubscribers(object));
132    }
133
134    /**
135     * Unregisters all handler methods on a registered {@code object}.
136     *
137     * @param object  object whose handler methods should be unregistered.
138     * @throws IllegalArgumentException if the object was not previously registered.
139     */
140    public void unregister(Object object) {
141        unsubscribeAll(finder.findAllSubscribers(object));
142    }
143
144    /**
145     * Posts an event to all registered handlers.  This method will return
146     * successfully after the event has been posted to all handlers, and
147     * regardless of any exceptions thrown by handlers.
148     *
149     * <p>If no handlers have been subscribed for {@code event}'s class, and
150     * {@code event} is not already a {@link DeadEvent}, it will be wrapped in a
151     * DeadEvent and reposted.
152     *
153     * @param event  event to post.
154     */
155    public void post(Object event) {
156        List<EventHandler> dispatching = new ArrayList<EventHandler>();
157
158        synchronized (this) {
159            Set<Class<?>> dispatchTypes = flattenHierarchy(event.getClass());
160
161            for (Class<?> eventType : dispatchTypes) {
162                Set<EventHandler> wrappers = getHandlersForEventType(eventType);
163
164                if (wrappers != null && !wrappers.isEmpty()) {
165                    dispatching.addAll(wrappers);
166                }
167            }
168        }
169
170        Collections.sort(dispatching);
171
172        for (EventHandler handler : dispatching) {
173            dispatch(event, handler);
174        }
175    }
176
177    /**
178     * Dispatches {@code event} to the handler in {@code handler}.  This method
179     * is an appropriate override point for subclasses that wish to make
180     * event delivery asynchronous.
181     *
182     * @param event  event to dispatch.
183     * @param handler  handler that will call the handler.
184     */
185    protected void dispatch(Object event, EventHandler handler) {
186        try {
187            handler.handleEvent(event);
188        } catch (InvocationTargetException e) {
189            logger.log(Level.SEVERE,
190                    "Could not dispatch event: " + event + " to handler " + handler, e);
191        }
192    }
193
194    /**
195     * Retrieves a mutable set of the currently registered handlers for
196     * {@code type}.  If no handlers are currently registered for {@code type},
197     * this method may either return {@code null} or an empty set.
198     *
199     * @param type  type of handlers to retrieve.
200     * @return currently registered handlers, or {@code null}.
201     */
202    synchronized Set<EventHandler> getHandlersForEventType(Class<?> type) {
203        return handlersByType.get(type);
204    }
205
206    /**
207     * Creates a new Set for insertion into the handler map.  This is provided
208     * as an override point for subclasses. The returned set should support
209     * concurrent access.
210     *
211     * @return a new, mutable set for handlers.
212     */
213    protected synchronized Set<EventHandler> newHandlerSet() {
214        return new HashSet<EventHandler>();
215    }
216
217    /**
218     * Flattens a class's type hierarchy into a set of Class objects.  The set
219     * will include all superclasses (transitively), and all interfaces
220     * implemented by these superclasses.
221     *
222     * @param concreteClass  class whose type hierarchy will be retrieved.
223     * @return {@code clazz}'s complete type hierarchy, flattened and uniqued.
224     */
225    Set<Class<?>> flattenHierarchy(Class<?> concreteClass) {
226        return flattenHierarchyCache.get(concreteClass);
227    }
228
229}