// Copyright 2009 Google Inc. All Rights Reserved.
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.DocumentHandler;
import org.waveprotocol.wave.model.util.AttributeListener;
import org.waveprotocol.wave.model.util.DeletionListener;
import org.waveprotocol.wave.model.util.ElementListener;
/**
* Event munging boilerplate
*
*
*/
public final class EventPlumber {
/**
* Represents the binding of an event listener in a document.
*/
public interface ListenerRegistration {
/**
* Detaches the bound listener from the document. This method is idempotent.
*/
void detach();
}
/**
* Base class for the different event adapters. This class controls the logic
* of attaching/removing the document listener.
*/
private static abstract class EventAdapter<N, E extends N, T extends N> // \u2620
implements DocumentHandler<N, E, T>, ListenerRegistration {
private final ObservableMutableDocument<N, E, T> doc;
public EventAdapter(ObservableMutableDocument<N, E, T> doc) {
this.doc = doc;
}
final ListenerRegistration attach() {
doc.addListener(this);
return this;
}
@Override
public final void detach() {
doc.removeListener(this);
}
}
/**
* Unfurls an {@link DocumentHandler.EventBundle} to an
* {@link AttributeListener}. Note that attributes gained or lost due to
* element insertions or deletions are not broadcast to the listener.
*/
private static final class AttributeListenerAdapter<N, E extends N, T extends N> // \u2620
extends EventAdapter<N, E, T> {
private final AttributeListener<E> listener;
private AttributeListenerAdapter(ObservableMutableDocument<N, E, T> doc,
AttributeListener<E> listener) {
super(doc);
this.listener = listener;
}
public static <N, E extends N, T extends N> ListenerRegistration dispatch(
ObservableMutableDocument<N, E, T> doc, final AttributeListener<E> listener) {
return new AttributeListenerAdapter<N, E, T>(doc, listener).attach();
}
@Override
public void onDocumentEvents(EventBundle<N, E, T> bundle) {
for (DocumentEvent<N, E, T> event : bundle.getEventComponents()) {
if (event.getType() == DocumentEvent.Type.ATTRIBUTES) {
AttributesModified<N, E, T> am = (AttributesModified<N, E, T>) event;
E target = am.getElement();
listener.onAttributesChanged(target, am.getOldValues(), am.getNewValues());
}
}
}
}
/**
* Unfurls an {@link DocumentHandler.EventBundle} to an
* {@link ElementListener}.
*/
private static final class ElementListenerAdapter<N, E extends N, T extends N> // \u2620
extends EventAdapter<N, E, T> {
private final ElementListener<E> listener;
private ElementListenerAdapter(ObservableMutableDocument<N, E, T> doc,
ElementListener<E> listener) {
super(doc);
this.listener = listener;
}
public static <N, E extends N, T extends N> ListenerRegistration dispatch(
ObservableMutableDocument<N, E, T> doc, ElementListener<E> listener) {
return new ElementListenerAdapter<N, E, T>(doc, listener).attach();
}
@Override
public void onDocumentEvents(EventBundle<N, E, T> bundle) {
// NOTE(koz/hearnden): We call onElementRemoved() before
// onElementAdded() on purpose so that listeners can refer to stored
// references to elements in onElementAdded() without fear that they may
// have been removed. See the note in {@link ElementListener} for more
// details.
for (E deleted : bundle.getDeletedElements()) {
listener.onElementRemoved(deleted);
}
for (E inserted : bundle.getInsertedElements()) {
listener.onElementAdded(inserted);
}
}
}
private static final class DeletionListenerAdapter<N, E extends N, T extends N> // \u2620
extends EventAdapter<N, E, T> {
private final DeletionListener listener;
private final E target;
private DeletionListenerAdapter(ObservableMutableDocument<N, E, T> doc, E target,
DeletionListener listener) {
super(doc);
this.target = target;
this.listener = listener;
}
public static <N, E extends N, T extends N> ListenerRegistration dispatch(
ObservableMutableDocument<N, E, T> doc, E target, DeletionListener listener) {
return new DeletionListenerAdapter<N, E, T>(doc, target, listener).attach();
}
@Override
public void onDocumentEvents(EventBundle<N, E, T> event) {
if (event.wasDeleted(target)) {
detach();
listener.onDeleted();
}
}
}
private EventPlumber() {
}
/**
* Attaches an element listener to a document.
*/
public static <N, E extends N> ListenerRegistration dispatchElementEvents(
ObservableMutableDocument<N, E, ?> doc, ElementListener<E> listener) {
return ElementListenerAdapter.dispatch(doc, listener);
}
/**
* Attaches an attribute listener to a document.
*/
public static <N, E extends N> ListenerRegistration dispatchAttributeEvents(
ObservableMutableDocument<N, E, ?> doc, AttributeListener<E> listener) {
return AttributeListenerAdapter.dispatch(doc, listener);
}
/**
* Attaches a deletion listener to a document element. The document listener
* is detached before the deletion listener is notified.
*/
public static <N, E extends N> ListenerRegistration dispatchDeletionEvent(
ObservableMutableDocument<N, E, ?> doc, E target, DeletionListener listener) {
return DeletionListenerAdapter.dispatch(doc, target, listener);
}
}