/* * 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); assert removed : "redundant remove call"; 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); } } }