| /* |
| * Copyright 2011 Google Inc. |
| * |
| * 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.google.web.bindery.event.shared; |
| |
| import com.google.web.bindery.event.shared.Event.Type; |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.ListIterator; |
| import java.util.Map; |
| import java.util.Set; |
| |
| /** |
| * Basic implementation of {@link EventBus}. |
| */ |
| public class SimpleEventBus extends EventBus { |
| private interface Command { |
| void execute(); |
| } |
| |
| private final boolean isReverseOrder; |
| |
| private int firingDepth = 0; |
| |
| /** |
| * Add and remove operations received during dispatch. |
| */ |
| private List<Command> deferredDeltas; |
| |
| /** |
| * Map of event type to map of event source to list of their handlers. |
| */ |
| private final Map<Event.Type<?>, Map<Object, List<?>>> map = |
| new HashMap<Event.Type<?>, Map<Object, List<?>>>(); |
| |
| public SimpleEventBus() { |
| this(false); |
| } |
| |
| /** |
| * Allows creation of an instance that fires its handlers in the reverse of |
| * the order in which they were added, although filtered handlers all fire |
| * before unfiltered handlers. |
| * <p> |
| * |
| * @deprecated This is a legacy feature, required by GWT's old HandlerManager. |
| * Reverse order is not honored for handlers tied to a specific |
| * event source (via {@link #addHandlerToSource}. |
| */ |
| @Deprecated |
| protected SimpleEventBus(boolean fireInReverseOrder) { |
| isReverseOrder = fireInReverseOrder; |
| } |
| |
| @Override |
| public <H> HandlerRegistration addHandler(Type<H> type, H handler) { |
| return doAdd(type, null, handler); |
| } |
| |
| @Override |
| public <H> HandlerRegistration addHandlerToSource(final Event.Type<H> type, final Object source, |
| final H handler) { |
| if (source == null) { |
| throw new NullPointerException("Cannot add a handler with a null source"); |
| } |
| |
| return doAdd(type, source, handler); |
| } |
| |
| @Override |
| public void fireEvent(Event<?> event) { |
| doFire(event, null); |
| } |
| |
| @Override |
| public void fireEventFromSource(Event<?> event, Object source) { |
| if (source == null) { |
| throw new NullPointerException("Cannot fire from a null source"); |
| } |
| doFire(event, source); |
| } |
| |
| /** |
| * @deprecated required by legacy features in GWT's old HandlerManager |
| */ |
| @Deprecated |
| protected <H> void doRemove(Event.Type<H> type, Object source, H handler) { |
| if (firingDepth > 0) { |
| enqueueRemove(type, source, handler); |
| } else { |
| doRemoveNow(type, source, handler); |
| } |
| } |
| |
| /** |
| * @deprecated required by legacy features in GWT's old HandlerManager |
| */ |
| @Deprecated |
| protected <H> H getHandler(Event.Type<H> type, int index) { |
| assert index < getHandlerCount(type) : "handlers for " + type.getClass() + " have size: " |
| + getHandlerCount(type) + " so do not have a handler at index: " + index; |
| |
| List<H> l = getHandlerList(type, null); |
| return l.get(index); |
| } |
| |
| /** |
| * @deprecated required by legacy features in GWT's old HandlerManager |
| */ |
| @Deprecated |
| protected int getHandlerCount(Event.Type<?> eventKey) { |
| return getHandlerList(eventKey, null).size(); |
| } |
| |
| /** |
| * @deprecated required by legacy features in GWT's old HandlerManager |
| */ |
| @Deprecated |
| protected boolean isEventHandled(Event.Type<?> eventKey) { |
| return map.containsKey(eventKey); |
| } |
| |
| private void defer(Command command) { |
| if (deferredDeltas == null) { |
| deferredDeltas = new ArrayList<Command>(); |
| } |
| deferredDeltas.add(command); |
| } |
| |
| private <H> HandlerRegistration doAdd(final Event.Type<H> type, final Object source, |
| final H handler) { |
| if (type == null) { |
| throw new NullPointerException("Cannot add a handler with a null type"); |
| } |
| if (handler == null) { |
| throw new NullPointerException("Cannot add a null handler"); |
| } |
| |
| if (firingDepth > 0) { |
| enqueueAdd(type, source, handler); |
| } else { |
| doAddNow(type, source, handler); |
| } |
| |
| return new HandlerRegistration() { |
| public void removeHandler() { |
| doRemove(type, source, handler); |
| } |
| }; |
| } |
| |
| private <H> void doAddNow(Event.Type<H> type, Object source, H handler) { |
| List<H> l = ensureHandlerList(type, source); |
| l.add(handler); |
| } |
| |
| private <H> void doFire(Event<H> event, Object source) { |
| if (event == null) { |
| throw new NullPointerException("Cannot fire null event"); |
| } |
| try { |
| firingDepth++; |
| |
| if (source != null) { |
| setSourceOfEvent(event, source); |
| } |
| |
| List<H> handlers = getDispatchList(event.getAssociatedType(), source); |
| Set<Throwable> causes = null; |
| |
| ListIterator<H> it = |
| isReverseOrder ? handlers.listIterator(handlers.size()) : handlers.listIterator(); |
| while (isReverseOrder ? it.hasPrevious() : it.hasNext()) { |
| H handler = isReverseOrder ? it.previous() : it.next(); |
| |
| try { |
| dispatchEvent(event, handler); |
| } catch (Throwable e) { |
| if (causes == null) { |
| causes = new HashSet<Throwable>(); |
| } |
| causes.add(e); |
| } |
| } |
| |
| if (causes != null) { |
| throw new UmbrellaException(causes); |
| } |
| } finally { |
| firingDepth--; |
| if (firingDepth == 0) { |
| handleQueuedAddsAndRemoves(); |
| } |
| } |
| } |
| |
| private <H> void doRemoveNow(Event.Type<H> type, Object source, H handler) { |
| List<H> l = getHandlerList(type, source); |
| |
| boolean removed = l.remove(handler); |
| |
| if (removed && l.isEmpty()) { |
| prune(type, source); |
| } |
| } |
| |
| private <H> void enqueueAdd(final Event.Type<H> type, final Object source, final H handler) { |
| defer(new Command() { |
| public void execute() { |
| doAddNow(type, source, handler); |
| } |
| }); |
| } |
| |
| private <H> void enqueueRemove(final Event.Type<H> type, final Object source, final H handler) { |
| defer(new Command() { |
| public void execute() { |
| doRemoveNow(type, source, handler); |
| } |
| }); |
| } |
| |
| private <H> List<H> ensureHandlerList(Event.Type<H> type, Object source) { |
| Map<Object, List<?>> sourceMap = map.get(type); |
| if (sourceMap == null) { |
| sourceMap = new HashMap<Object, List<?>>(); |
| map.put(type, sourceMap); |
| } |
| |
| // safe, we control the puts. |
| @SuppressWarnings("unchecked") |
| List<H> handlers = (List<H>) sourceMap.get(source); |
| if (handlers == null) { |
| handlers = new ArrayList<H>(); |
| sourceMap.put(source, handlers); |
| } |
| |
| return handlers; |
| } |
| |
| private <H> List<H> getDispatchList(Event.Type<H> type, Object source) { |
| List<H> directHandlers = getHandlerList(type, source); |
| if (source == null) { |
| return directHandlers; |
| } |
| |
| List<H> globalHandlers = getHandlerList(type, null); |
| |
| List<H> rtn = new ArrayList<H>(directHandlers); |
| rtn.addAll(globalHandlers); |
| return rtn; |
| } |
| |
| private <H> List<H> getHandlerList(Event.Type<H> type, Object source) { |
| Map<Object, List<?>> sourceMap = map.get(type); |
| if (sourceMap == null) { |
| return Collections.emptyList(); |
| } |
| |
| // safe, we control the puts. |
| @SuppressWarnings("unchecked") |
| List<H> handlers = (List<H>) sourceMap.get(source); |
| if (handlers == null) { |
| return Collections.emptyList(); |
| } |
| |
| return handlers; |
| } |
| |
| private void handleQueuedAddsAndRemoves() { |
| if (deferredDeltas != null) { |
| try { |
| for (Command c : deferredDeltas) { |
| c.execute(); |
| } |
| } finally { |
| deferredDeltas = null; |
| } |
| } |
| } |
| |
| private void prune(Event.Type<?> type, Object source) { |
| Map<Object, List<?>> sourceMap = map.get(type); |
| |
| List<?> pruned = sourceMap.remove(source); |
| |
| assert pruned != null : "Can't prune what wasn't there"; |
| assert pruned.isEmpty() : "Pruned unempty list!"; |
| |
| if (sourceMap.isEmpty()) { |
| map.remove(type); |
| } |
| } |
| } |