/** * Copyright 2009 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.indexed; import org.waveprotocol.wave.model.document.indexed.DocumentEvent.AnnotationChanged; 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.TextDeleted; import org.waveprotocol.wave.model.document.indexed.DocumentEvent.TextInserted; import org.waveprotocol.wave.model.document.operation.Attributes; import org.waveprotocol.wave.model.document.operation.AttributesUpdate; import org.waveprotocol.wave.model.document.operation.automaton.DocumentSchema; import org.waveprotocol.wave.model.document.raw.RawDocument; import java.util.ArrayList; import java.util.List; /** * Indexed document that generates events on mutation. * * The events generated by the application of a *single* op contain * efficiently represented information, sufficient to reverse it. * (for the mutation components implemented). * * TODO(danilatos): split/join & annotations. * * Events are simple inserts, deletes, and attribute updates * (splits/joins are expressed as inserts and deletes). * * There is much low-hanging fruit here for optimisation... * * @author danilatos@google.com (Daniel Danilatos) */ public class ObservableIndexedDocument<N, E extends N, T extends N, V> extends IndexedDocumentImpl<N, E, T, V> { private int depth = 0; private final List<EventBundleImpl.Builder<N, E, T>> events = new ArrayList<EventBundleImpl.Builder<N, E, T>>(); private final List<E> splitElementStack = new ArrayList<E>(); private final DocumentHandler<N, E, T> handler; /** Deletion-event builder. Non-null in the scope of an element deletion. */ private ContentDeleted.Builder<N, E, T> deletion; /** * Call this constructor when you have your own annotation set to pass in. * * @param handler handler for document events * @param substrate document substrate * @param annotations annotation set to use * @param schema schema to use for this document */ public ObservableIndexedDocument( DocumentHandler<N, E, T> handler, RawDocument<N, E, T> substrate, RawAnnotationSet<Object> annotations, DocumentSchema schema) { super(substrate, annotations, schema); this.handler = handler; } private static final Object ONE_OBJECT = new Object(); private static final Object ANOTHER_OBJECT = new Object(); /** * Calling this constructor (where you don't pass your own annotation set) * causes one to built internally. * * @param handler handler for document events * @param substrate document substrate * @param schema schema to use for this document */ public ObservableIndexedDocument( DocumentHandler<N, E, T> handler, RawDocument<N, E, T> substrate, DocumentSchema schema) { // We have to chain constructors here since we want to access the // annotation tree after building it so that we can set its listener this(handler, substrate, new AnnotationTree<Object>(ONE_OBJECT, ANOTHER_OBJECT, null), schema); } private ObservableIndexedDocument( DocumentHandler<N, E, T> handler, RawDocument<N, E, T> substrate, AnnotationTree<Object> annotations, DocumentSchema schema) { super(substrate, annotations, schema); AnnotationSetListener<Object> listener = new AnnotationSetListener<Object>() { @Override public void onAnnotationChange(int start, int end, String key, Object newValue) { event(new AnnotationChanged<N, E, T>(start, end, key, (String) newValue)); } }; annotations.setListener(listener); this.handler = handler; } @Override protected void beforeBegin() { assert depth == 0; assert events.isEmpty(); assert splitElementStack.isEmpty(); push(); } @Override protected void afterFinish() { assert events.size() == 1; assert depth == 0; assert splitElementStack.isEmpty(); if (handler != null) { handler.onDocumentEvents(events.get(0).build()); } events.clear(); } @Override protected void onElementStart(E element) { if (depth == 0) { event(new ContentInserted<N, E, T>(element)); } // Remember the element just inserted, to include in event. inserted(element); depth++; } @Override protected void onElementEnd() { depth--; } @Override protected void onDeleteElementStart(int location, E element) { assert (deletion == null) == (depth == 0); if (depth == 0) { deletion = new ContentDeleted.Builder<N, E, T>(location, element); } depth++; // Save the element about to be deleted. deleted(element); deletion.addElementStart(getTagName(element), getAttributes(element)); } @Override protected void onDeleteElementEnd() { assert deletion != null; deletion.addElementEnd(); depth--; if (depth == 0) { event(deletion.build()); deletion = null; } } @Override protected void onModifyAttributes(E element, AttributesUpdate update) { event(new AttributesModified<N, E, T>(element, update)); } @Override protected void onModifyAttributes(E element, Attributes oldAttributes, Attributes newAttributes) { event(new AttributesModified<N, E, T>(element, oldAttributes, newAttributes)); } @Override protected void onCharacters(int location, String characters) { if (depth == 0) { event(new TextInserted<N, E, T>(location, characters)); } } @Override protected void onDeleteCharacters(int location, String characters) { if (depth == 0) { // TODO(danilatos): Drop the TextDeleted event altogether. event(new TextDeleted<N, E, T>(location, characters)); } if (deletion != null) { deletion.addText(characters); } } private void inserted(E element) { currentEvent().addInsertedElement(element); } private void deleted(E element) { currentEvent().addDeletedElement(element); } private void event(DocumentEvent<N, E, T> event) { currentEvent().addComponent(event); } private EventBundleImpl.Builder<N, E, T> currentEvent() { return events.get(events.size() - 1); } private void push() { events.add(new EventBundleImpl.Builder<N, E, T>()); } }