View Javadoc

1   /**********************************************
2    * Copyright (C) 2010 Lukas Laag
3    * This file is part of lib-gwt-svg.
4    * 
5    * libgwtsvg is free software: you can redistribute it and/or modify
6    * it under the terms of the GNU Lesser General Public License as published by
7    * the Free Software Foundation, either version 3 of the License, or
8    * (at your option) any later version.
9    * 
10   * libgwtsvg is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   * GNU Lesser General Public License for more details.
14   * 
15   * You should have received a copy of the GNU Lesser General Public License
16   * along with libgwtsvg.  If not, see http://www.gnu.org/licenses/
17   **********************************************/
18  /*
19   * Copyright 2011 Google Inc.
20   * 
21   * Licensed under the Apache License, Version 2.0 (the "License"); you may not
22   * use this file except in compliance with the License. You may obtain a copy of
23   * the License at
24   * 
25   * http://www.apache.org/licenses/LICENSE-2.0
26   * 
27   * Unless required by applicable law or agreed to in writing, software
28   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
29   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
30   * License for the specific language governing permissions and limitations under
31   * the License.
32   */
33  package org.vectomatic.dom.svg.impl;
34  
35  import java.util.ArrayList;
36  import java.util.Collections;
37  import java.util.HashMap;
38  import java.util.HashSet;
39  import java.util.List;
40  import java.util.ListIterator;
41  import java.util.Map;
42  import java.util.Set;
43  
44  import org.vectomatic.dom.svg.OMElement;
45  import org.vectomatic.dom.svg.utils.DOMHelper;
46  
47  import com.google.gwt.dom.client.Element;
48  import com.google.gwt.event.dom.client.DomEvent;
49  import com.google.gwt.event.shared.EventBus;
50  import com.google.gwt.event.shared.EventHandler;
51  import com.google.gwt.event.shared.GwtEvent;
52  import com.google.gwt.event.shared.GwtEvent.Type;
53  import com.google.gwt.event.shared.HandlerRegistration;
54  import com.google.web.bindery.event.shared.Event;
55  import com.google.web.bindery.event.shared.UmbrellaException;
56  
57  /**
58   * Implementation of {@link EventBus} which invokes
59   * {@link org.vectomatic.dom.svg.utils.DOMHelper#unbindEventListener(Element, String)}
60   * on DOM element sources when they no longer have any event registered handlers
61   * for the corresponding event type.
62   */
63  public class DOMEventBus extends EventBus {
64  	private interface Command {
65  		void execute();
66  	}
67  
68  	private int firingDepth = 0;
69  
70  	/**
71  	 * Add and remove operations received during dispatch.
72  	 */
73  	private List<Command> deferredDeltas;
74  
75  	/**
76  	 * Map of event type to map of event source to list of their handlers.
77  	 */
78  	private final Map<Event.Type<?>, Map<Object, List<?>>> map = new HashMap<Event.Type<?>, Map<Object, List<?>>>();
79  
80  	@Override
81  	public <H extends EventHandler> HandlerRegistration addHandler(Type<H> type, H handler) {
82  	    return doAdd(type, null, handler);
83  	}
84  
85  	@Override
86  	public <H extends EventHandler> HandlerRegistration addHandlerToSource(Type<H> type, Object source, H handler) {
87  		if (source == null) {
88  			throw new NullPointerException("Cannot add a handler with a null source");
89  		}
90  		return doAdd(type, source, handler);
91  	}
92  
93  	@Override
94  	public void fireEvent(GwtEvent<?> event) {
95  	    doFire(event, null);
96  	}
97  
98  	@Override
99  	public void fireEventFromSource(GwtEvent<?> event, Object source) {
100 		if (source == null) {
101 			throw new NullPointerException("Cannot fire from a null source");
102 		}
103 		doFire(event, source);
104 	}
105 	
106 
107 	@Override
108 	public <H> HandlerRegistration addHandlerToSource(final Event.Type<H> type,
109 			final Object source, final H handler) {
110 		if (source == null) {
111 			throw new NullPointerException("Cannot add a handler with a null source");
112 		}
113 		return doAdd(type, source, handler);
114 	}
115 
116 	protected <H> void doRemove(Event.Type<H> type, Object source, H handler) {
117 		if (firingDepth > 0) {
118 			enqueueRemove(type, source, handler);
119 		} else {
120 			doRemoveNow(type, source, handler);
121 		}
122 	}
123 
124 	protected <H> H getHandler(Event.Type<H> type, int index) {
125 		assert index < getHandlerCount(type) : "handlers for "
126 				+ type.getClass() + " have size: " + getHandlerCount(type)
127 				+ " so do not have a handler at index: " + index;
128 
129 		List<H> l = getHandlerList(type, null);
130 		return l.get(index);
131 	}
132 
133 	protected int getHandlerCount(Event.Type<?> eventKey) {
134 		return getHandlerList(eventKey, null).size();
135 	}
136 
137 	protected boolean isEventHandled(Event.Type<?> eventKey) {
138 		return map.containsKey(eventKey);
139 	}
140 
141 	private void defer(Command command) {
142 		if (deferredDeltas == null) {
143 			deferredDeltas = new ArrayList<Command>();
144 		}
145 		deferredDeltas.add(command);
146 	}
147 
148 	private <H> HandlerRegistration doAdd(final Event.Type<H> type,
149 			final Object source, final H handler) {
150 		if (type == null) {
151 			throw new NullPointerException(
152 					"Cannot add a handler with a null type");
153 		}
154 		if (handler == null) {
155 			throw new NullPointerException("Cannot add a null handler");
156 		}
157 
158 		if (firingDepth > 0) {
159 			enqueueAdd(type, source, handler);
160 		} else {
161 			doAddNow(type, source, handler);
162 		}
163 
164 		return new HandlerRegistration() {
165 			public void removeHandler() {
166 				doRemove(type, source, handler);
167 			}
168 		};
169 	}
170 
171 	private <H> void doAddNow(Event.Type<H> type, Object source, H handler) {
172 		List<H> l = ensureHandlerList(type, source);
173 		l.add(handler);
174 	}
175 
176 	private <H> void doFire(Event<H> event, Object source) {
177 		if (event == null) {
178 			throw new NullPointerException("Cannot fire null event");
179 		}
180 		try {
181 			firingDepth++;
182 
183 			if (source != null) {
184 				setSourceOfEvent(event, source);
185 			}
186 
187 			List<H> handlers = getDispatchList(event.getAssociatedType(), source);
188 			Set<Throwable> causes = null;
189 
190 			ListIterator<H> it = handlers.listIterator();
191 			while (it.hasNext()) {
192 				H handler = it.next();
193 
194 				try {
195 					dispatchEvent(event, handler);
196 				} catch (Throwable e) {
197 					if (causes == null) {
198 						causes = new HashSet<Throwable>();
199 					}
200 					causes.add(e);
201 				}
202 			}
203 
204 			if (causes != null) {
205 				throw new UmbrellaException(causes);
206 			}
207 		} finally {
208 			firingDepth--;
209 			if (firingDepth == 0) {
210 				handleQueuedAddsAndRemoves();
211 			}
212 		}
213 	}
214 
215 	private <H> void doRemoveNow(Event.Type<H> type, Object source, H handler) {
216 		List<H> l = getHandlerList(type, source);
217 
218 		boolean removed = l.remove(handler);
219 		assert removed : "redundant remove call";
220 		if (removed && l.isEmpty()) {
221 			if (type instanceof DomEvent.Type && source instanceof OMElement) {
222 				Element elem = ((OMElement)source).getElement();
223 				String eventName = ((DomEvent.Type)type).getName();
224 				DOMHelper.unbindEventListener(elem, eventName);
225 			}
226 			prune(type, source);
227 		}
228 	}
229 
230 	private <H> void enqueueAdd(final Event.Type<H> type, final Object source, final H handler) {
231 		defer(new Command() {
232 			public void execute() {
233 				doAddNow(type, source, handler);
234 			}
235 		});
236 	}
237 
238 	private <H> void enqueueRemove(final Event.Type<H> type, final Object source, final H handler) {
239 		defer(new Command() {
240 			public void execute() {
241 				doRemoveNow(type, source, handler);
242 			}
243 		});
244 	}
245 
246 	private <H> List<H> ensureHandlerList(Event.Type<H> type, Object source) {
247 		Map<Object, List<?>> sourceMap = map.get(type);
248 		if (sourceMap == null) {
249 			sourceMap = new HashMap<Object, List<?>>();
250 			map.put(type, sourceMap);
251 		}
252 
253 		// safe, we control the puts.
254 		@SuppressWarnings("unchecked")
255 		List<H> handlers = (List<H>) sourceMap.get(source);
256 		if (handlers == null) {
257 			handlers = new ArrayList<H>();
258 			sourceMap.put(source, handlers);
259 		}
260 
261 		return handlers;
262 	}
263 
264 	private <H> List<H> getDispatchList(Event.Type<H> type, Object source) {
265 		List<H> directHandlers = getHandlerList(type, source);
266 		if (source == null) {
267 			return directHandlers;
268 		}
269 
270 		List<H> globalHandlers = getHandlerList(type, null);
271 
272 		List<H> rtn = new ArrayList<H>(directHandlers);
273 		rtn.addAll(globalHandlers);
274 		return rtn;
275 	}
276 
277 	private <H> List<H> getHandlerList(Event.Type<H> type, Object source) {
278 		Map<Object, List<?>> sourceMap = map.get(type);
279 		if (sourceMap == null) {
280 			return Collections.emptyList();
281 		}
282 
283 		// safe, we control the puts.
284 		@SuppressWarnings("unchecked")
285 		List<H> handlers = (List<H>) sourceMap.get(source);
286 		if (handlers == null) {
287 			return Collections.emptyList();
288 		}
289 
290 		return handlers;
291 	}
292 
293 	private void handleQueuedAddsAndRemoves() {
294 		if (deferredDeltas != null) {
295 			try {
296 				for (Command c : deferredDeltas) {
297 					c.execute();
298 				}
299 			} finally {
300 				deferredDeltas = null;
301 			}
302 		}
303 	}
304 
305 	private void prune(Event.Type<?> type, Object source) {
306 		Map<Object, List<?>> sourceMap = map.get(type);
307 
308 		List<?> pruned = sourceMap.remove(source);
309 
310 		assert pruned != null : "Can't prune what wasn't there";
311 		assert pruned.isEmpty() : "Pruned unempty list!";
312 
313 		if (sourceMap.isEmpty()) {
314 			map.remove(type);
315 		}
316 	}
317 
318 }