/** * Copyright 2010 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 org.waveprotocol.wave.model.document.util; import org.waveprotocol.wave.model.document.ObservableMutableDocument; import org.waveprotocol.wave.model.document.indexed.DocumentEvent; import org.waveprotocol.wave.model.document.indexed.DocumentEvent.AttributesModified; import org.waveprotocol.wave.model.document.indexed.DocumentEvent.ContentDeleted; import org.waveprotocol.wave.model.document.indexed.DocumentEvent.ContentInserted; import org.waveprotocol.wave.model.document.indexed.DocumentEvent.Type; import org.waveprotocol.wave.model.document.indexed.DocumentHandler; import org.waveprotocol.wave.model.util.AttributeListener; import org.waveprotocol.wave.model.util.CollectionUtils; import org.waveprotocol.wave.model.util.CopyOnWriteSet; import org.waveprotocol.wave.model.util.DeletionListener; import org.waveprotocol.wave.model.util.ElementListener; import java.util.Map; /** * Simple document event router that adds a single listener to the wrapped document * and dispatches events to the listeners registered on it. * * @param <N> The node type of the document * @param <E> The element type of the document. * @param <T> The text node type of the document */ public class DefaultDocumentEventRouter<N, E extends N, T extends N> implements DocumentEventRouter<N, E, T>, DocumentHandler<N, E, T> { private final ObservableMutableDocument<N, E, T> doc; private Map<E, CopyOnWriteSet<ElementListener<E>>> elementListenerMap = null; private Map<E, CopyOnWriteSet<AttributeListener<E>>> attributeListenerMap = null; private Map<E, CopyOnWriteSet<DeletionListener>> deletionListenerMap = null; private int listenerCount = 0; protected DefaultDocumentEventRouter(ObservableMutableDocument<N, E, T> doc) { this.doc = doc; } /** * Create a new document event router wrapping the given document. */ public static <N, E extends N> DocumentEventRouter<N, E, ? extends N> create( ObservableMutableDocument<N, E, ?> doc) { // It should actually be possible for this constructor to take T rather than // a wildcard but eclipse gives a compile error when passing an // ObservableMutableDocument<? super E, E, ?>. It is clearly possible to infer // from the bounds on ObservableMutableDocument that the capture of the last '?' // extends the capture of the first '? super E' -- but alas it doesn't work. return doCreate(doc); } /** * Create above needs to have a bridge call to this function for wildcard capture to * turn the ? into a concrete 'T' we can use in the constructor call. */ private static <N, E extends N, T extends N> DocumentEventRouter<N, E, T> doCreate( ObservableMutableDocument<N, E, T> doc) { return new DefaultDocumentEventRouter<N, E, T>(doc); } private static class Registration<T> implements ListenerRegistration { private final DefaultDocumentEventRouter<?, ?, ?> router; private final Object key; private final T listener; private final Map<?, CopyOnWriteSet<T>> listenerMap; private Registration(DefaultDocumentEventRouter<?, ?, ?> router, Map<?, CopyOnWriteSet<T>> listenerMap, Object key, T listener) { this.router = router; this.key = key; this.listener = listener; this.listenerMap = listenerMap; } public static <T> Registration<T> create(DefaultDocumentEventRouter<?, ?, ?> router, Map<?, CopyOnWriteSet<T>> listenerMap, Object key, T listener) { return new Registration<T>(router, listenerMap, key, listener); } @Override public void detach() { router.listenerRemoved(); CopyOnWriteSet<T> listeners = listenerMap.get(key); if (listeners != null) { listeners.remove(listener); if (listeners.isEmpty()) { listenerMap.remove(key); } } } } @Override public void onDocumentEvents(EventBundle<N, E, T> bundle) { if (attributeListenerMap != null) { for (DocumentEvent<N, E, T> event : bundle.getEventComponents()) { if (event.getType() == Type.ATTRIBUTES) { AttributesModified<N, E, T> am = (AttributesModified<N, E, T>) event; E target = am.getElement(); CopyOnWriteSet<AttributeListener<E>> listeners = attributeListenerMap.get(target); if (listeners != null) { for (AttributeListener<E> listener : listeners) { listener.onAttributesChanged(target, am.getOldValues(), am.getNewValues()); } } } } } if (elementListenerMap != null) { for (DocumentEvent<N, E, T> event : bundle.getEventComponents()) { if (event.getType() == Type.CONTENT_DELETED) { // We can't use deleted.getParent since the parent pointer may have been // cleared at this point. ContentDeleted<N, E, T> deletedEvent = (ContentDeleted<N, E, T>) event; E deleted = deletedEvent.getRoot(); Point<N> deletedLocation = doc.locate(deletedEvent.getLocation()); E parent = Point.enclosingElement(doc, deletedLocation); CopyOnWriteSet<ElementListener<E>> listeners = elementListenerMap.get(parent); if (listeners != null) { for (ElementListener<E> listener : listeners) { listener.onElementRemoved(deleted); } } } } } for (E deleted : bundle.getDeletedElements()) { if (deletionListenerMap != null) { CopyOnWriteSet<DeletionListener> listeners = deletionListenerMap.get(deleted); if (listeners != null) { for (DeletionListener listener : listeners) { listener.onDeleted(); } } } } if (elementListenerMap != null) { for (DocumentEvent<N, E, T> event : bundle.getEventComponents()) { if (event.getType() == Type.CONTENT_INSERTED) { E inserted = ((ContentInserted<N, E, T>) event).getSubtreeElement(); E parent = doc.getParentElement(inserted); CopyOnWriteSet<ElementListener<E>> listeners = elementListenerMap.get(parent); if (listeners != null) { for (ElementListener<E> listener : listeners) { listener.onElementAdded(inserted); } } } } } removeDeadListeners(bundle); } private void removeDeadListeners(EventBundle<N, E, T> bundle) { for (E deleted : bundle.getDeletedElements()) { if (attributeListenerMap != null) { removeListeners(attributeListenerMap, deleted); } if (elementListenerMap != null) { removeListeners(elementListenerMap, deleted); } if (deletionListenerMap != null) { removeListeners(deletionListenerMap, deleted); } } } @Override public ListenerRegistration addChildListener(E parent, ElementListener<E> listener) { if (elementListenerMap == null) { elementListenerMap = CollectionUtils.newHashMap(); } CopyOnWriteSet<ElementListener<E>> list = ensureListenerList(parent, elementListenerMap); list.add(listener); listenerAdded(); return Registration.create(this, elementListenerMap, parent, listener); } @Override public ListenerRegistration addAttributeListener(E target, AttributeListener<E> listener) { if (attributeListenerMap == null) { attributeListenerMap = CollectionUtils.newHashMap(); } CopyOnWriteSet<AttributeListener<E>> list = ensureListenerList(target, attributeListenerMap); list.add(listener); listenerAdded(); return Registration.create(this, attributeListenerMap, target, listener); } @Override public ListenerRegistration addDeletionListener(E target, DeletionListener listener) { if (deletionListenerMap == null) { deletionListenerMap = CollectionUtils.newHashMap(); } CopyOnWriteSet<DeletionListener> list = ensureListenerList(target, deletionListenerMap); list.add(listener); listenerAdded(); return Registration.create(this, deletionListenerMap, target, listener); } @Override public ObservableMutableDocument<N, E, T> getDocument() { return doc; } private <T> CopyOnWriteSet<T> ensureListenerList(E target, Map<E, CopyOnWriteSet<T>> map) { CopyOnWriteSet<T> list = map.get(target); if (list == null) { list = CopyOnWriteSet.createListSet(); map.put(target, list); } return list; } /** * Removes and returns the list of listeners for the given element and updates * the listener count accordingly. */ private <T> CopyOnWriteSet<T> removeListeners(Map<E, CopyOnWriteSet<T>> map, E elm) { CopyOnWriteSet<T> removed = map.remove(elm); if (removed != null) { listenerCount -= removed.size(); if (listenerCount == 0) { doc.removeListener(this); } } return removed; } private void listenerRemoved() { listenerCount--; if (listenerCount == 0) { doc.removeListener(this); } } private void listenerAdded() { listenerCount++; if (listenerCount == 1) { doc.addListener(this); } } }