Package org.waveprotocol.wave.model.document.indexed

Source Code of org.waveprotocol.wave.model.document.indexed.IndexedDocumentImplTest

/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements.  See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership.  The ASF licenses this file
* to you 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 junit.framework.TestCase;

import org.waveprotocol.wave.model.document.AnnotationInterval;
import org.waveprotocol.wave.model.document.DocumentTestCases;
import org.waveprotocol.wave.model.document.MutableDocument;
import org.waveprotocol.wave.model.document.RangedAnnotation;
import org.waveprotocol.wave.model.document.operation.Attributes;
import org.waveprotocol.wave.model.document.operation.Automatons;
import org.waveprotocol.wave.model.document.operation.DocOp;
import org.waveprotocol.wave.model.document.operation.DocInitialization;
import org.waveprotocol.wave.model.document.operation.Nindo;
import org.waveprotocol.wave.model.document.operation.algorithm.DocOpInverter;
import org.waveprotocol.wave.model.document.operation.automaton.DocumentSchema;
import org.waveprotocol.wave.model.document.operation.automaton.DocOpAutomaton.ViolationCollector;
import org.waveprotocol.wave.model.document.operation.impl.AttributesImpl;
import org.waveprotocol.wave.model.document.operation.impl.DocInitializationBuilder;
import org.waveprotocol.wave.model.document.operation.impl.DocOpUtil;
import org.waveprotocol.wave.model.document.operation.impl.DocOpValidator;
import org.waveprotocol.wave.model.document.raw.TextNodeOrganiser;
import org.waveprotocol.wave.model.document.raw.impl.Element;
import org.waveprotocol.wave.model.document.raw.impl.Node;
import org.waveprotocol.wave.model.document.raw.impl.RawDocumentImpl;
import org.waveprotocol.wave.model.document.raw.impl.Text;
import org.waveprotocol.wave.model.document.util.Annotations;
import org.waveprotocol.wave.model.document.util.ContextProviders;
import org.waveprotocol.wave.model.document.util.DocProviders;
import org.waveprotocol.wave.model.document.util.LocalDocument;
import org.waveprotocol.wave.model.document.util.XmlStringBuilder;
import org.waveprotocol.wave.model.document.util.ContextProviders.TestDocumentContext;
import org.waveprotocol.wave.model.operation.OperationException;
import org.waveprotocol.wave.model.operation.OperationRuntimeException;
import org.waveprotocol.wave.model.util.CollectionUtils;

import java.util.Collections;
import java.util.Iterator;

/**
* Tests for IndexedDocumentImpl.
*
*/

public class IndexedDocumentImplTest extends TestCase {

  /**
   * A parser for documents.
   */
  public static final NindoTestCases.DocumentParser<
      IndexedDocumentImpl<Node, Element, Text, ?>> nindoDocumentParser =
    new NindoTestCases.DocumentParser<IndexedDocumentImpl<Node, Element, Text, ?>>() {

    public IndexedDocumentImpl<Node, Element, Text, ?> parseDocument(String documentString) {
      return doParseDocument(documentString);
    }

    public String asString(IndexedDocumentImpl<Node, Element, Text, ?> document) {
      return document.toXmlString();
    }

    @Override
    public IndexedDocumentImpl<Node, Element, Text, ?> copyDocument(
        IndexedDocumentImpl<Node, Element, Text, ?> other) {
      return doCopyDocument(other);
    }

  };

  /**
   * A parser for documents.
   */
  public static final DocumentTestCases.DocumentParser<
      IndexedDocumentImpl<Node, Element, Text, ?>> documentParser =
    new DocumentTestCases.DocumentParser<IndexedDocumentImpl<Node, Element, Text, ?>>() {

    public IndexedDocumentImpl<Node, Element, Text, ?> parseDocument(String documentString) {
      return doParseDocument(documentString);
    }

    public String asString(IndexedDocumentImpl<Node, Element, Text, ?> document) {
      return document.toXmlString();
    }

    @Override
    public IndexedDocumentImpl<Node, Element, Text, ?> copyDocument(
        IndexedDocumentImpl<Node, Element, Text, ?> other) {
      return doCopyDocument(other);
    }

  };

  private static IndexedDocumentImpl<Node, Element, Text, ?>
      doParseDocument(String documentString) {
    IndexedDocumentImpl<Node, Element, Text, ?> doc =
      new IndexedDocumentImpl<Node, Element, Text, Void>(
          RawDocumentImpl.PROVIDER.parse("<blah>" + documentString + "</blah>"), null,
          DocumentSchema.NO_SCHEMA_CONSTRAINTS);
    return doc;
  }

  private static IndexedDocumentImpl<Node, Element, Text, ?> doCopyDocument(
      IndexedDocumentImpl<Node, Element, Text, ?> other) {
    IndexedDocumentImpl<Node, Element, Text, ?> doc =
      new IndexedDocumentImpl<Node, Element, Text, Void>(
          RawDocumentImpl.PROVIDER.create("doc", Attributes.EMPTY_MAP), null,
          DocumentSchema.NO_SCHEMA_CONSTRAINTS);
    try {
      doc.consume(other.asOperation());
    } catch (OperationException e) {
      throw new OperationRuntimeException("Copy should not fail", e);
    }
    return doc;
  }

  /**
   * Runs the tests for the insertion of text.
   */
  public void testNindoTextInsertion() {
    NindoTestCases.runTextInsertionTests(nindoDocumentParser);
  }

  /**
   * Runs the tests for the deletion of text.
   */
  public void testNindoTextDeletion() {
    NindoTestCases.runTextDeletionTests(nindoDocumentParser);
  }

  /**
   * Runs the tests for the insertion of elements.
   */
  public void testNindoElementInsertion() {
    NindoTestCases.runElementInsertionTests(nindoDocumentParser);
  }

  /**
   * Runs the tests for the deletion of elements.
   */
  public void testNindoElementDeletion() {
    NindoTestCases.runElementDeletionTests(nindoDocumentParser);
  }

  /**
   * Runs the tests for the setting and removal of attributes.
   */
  public void testNindoAttributes() {
    NindoTestCases.runAttributeTests(nindoDocumentParser);
  }

  /**
   * Runs a miscellany of tests.
   */
  public void testNindoMiscellaneous() {
    NindoTestCases.runMiscellaneousTests(nindoDocumentParser);
  }

  /**
   * Runs the tests for the insertion of text.
   */
  public void testTextInsertion() {
    DocumentTestCases.runTextInsertionTests(documentParser);
  }

  /**
   * Runs the tests for the deletion of text.
   */
  public void testTextDeletion() {
    DocumentTestCases.runTextDeletionTests(documentParser);
  }

  /**
   * Runs the tests for the insertion of elements.
   */
  public void testElementInsertion() {
    DocumentTestCases.runElementInsertionTests(documentParser);
  }

  /**
   * Runs the tests for the deletion of elements.
   */
  public void testElementDeletion() {
    DocumentTestCases.runElementDeletionTests(documentParser);
  }

  /**
   * Runs the tests for the setting and removal of attributes.
   */
  public void testAttributes() {
    DocumentTestCases.runAttributeTests(documentParser);
  }

  /**
   * Runs a miscellany of tests.
   */
  public void testMiscellaneous() {
    DocumentTestCases.runMiscellaneousTests(documentParser);
  }

  /**
   * Tests the asOperation method.
   */
  public void testAsOperation() {
    IndexedDocumentImpl<Node, Element, Text, ?> document =
        documentParser.parseDocument(
          "<blip><p><i>ab</i>cd<b>ef</b>gh</p></blip>");
    DocInitialization expected = new DocInitializationBuilder()
        .elementStart("blip", Attributes.EMPTY_MAP)
        .elementStart("p", Attributes.EMPTY_MAP)
        .elementStart("i", Attributes.EMPTY_MAP)
        .characters("ab")
        .elementEnd()
        .characters("cd")
        .elementStart("b", Attributes.EMPTY_MAP)
        .characters("ef")
        .elementEnd()
        .characters("gh")
        .elementEnd()
        .elementEnd()
        .build();
    document.asOperation();
    assertEquals(
        DocOpUtil.toConciseString(expected),
        DocOpUtil.toConciseString(document.asOperation()));
  }

  private void checkApply(IndexedDocument<Node, Element, Text> doc, Nindo op)
      throws OperationException {

    System.out.println("");
    System.out.println("============================================");

    DocInitialization docAsOp = doc.asOperation();
    String initial = DocOpUtil.toXmlString(docAsOp);
    IndexedDocument<Node, Element, Text> copy = DocProviders.POJO.build(docAsOp,
        DocumentSchema.NO_SCHEMA_CONSTRAINTS);
    System.out.println(doc);

    DocOp docOp = doc.consumeAndReturnInvertible(op);

    System.out.println(op + "==========> " + docOp);
    ViolationCollector v = new ViolationCollector();
    if (!DocOpValidator.validate(v, DocumentSchema.NO_SCHEMA_CONSTRAINTS,
        Automatons.fromReadable(copy), docOp).isValid()) {
      v.printDescriptions(System.err);
      fail("Invalid operation");
    }

    copy.consume(docOp);

    System.out.println("=======" + doc + " --------- " + copy);
    assertEquals(
        DocOpUtil.toXmlString(doc.asOperation()),
        DocOpUtil.toXmlString(copy.asOperation()));
    DocOp inverted = DocOpInverter.invert(docOp);
    v = new ViolationCollector();
    if (!DocOpValidator.validate(v, DocumentSchema.NO_SCHEMA_CONSTRAINTS,
        Automatons.fromReadable(copy), inverted).isValid()) {
      v.printDescriptions(System.err);
      fail("Invalid operation");
    }
    copy.consume(inverted);
    assertEquals(initial, DocOpUtil.toXmlString(copy.asOperation()));
  }

  public void testReverseAnnotations() throws OperationException {
    IndexedDocument<Node, Element, Text> doc = DocProviders.POJO.parse("<a></a>");

    Nindo.Builder b;

    b = new Nindo.Builder();
    b.skip(1);
    b.startAnnotation("a", "1");
    b.characters("x");
    b.endAnnotation("a");
    checkApply(doc, b.build());

    // mutating into:
    // <a>
    // x{a=2}
    b = new Nindo.Builder();
    b.skip(1);
    b.startAnnotation("a", "2");
    b.skip(1);
    b.endAnnotation("a");
    checkApply(doc, b.build());

    // mutating into:
    // <a>
    // w{a=2}
    // x{a=2, b=1}
    // y{a=3, b=1}
    // z{a=3, b=2}
    b = new Nindo.Builder();
    b.skip(1);
    b.startAnnotation("a", "2");
    b.characters("w");
    b.endAnnotation("a");
    b.startAnnotation("b", "1");
    b.skip(1);
    b.startAnnotation("a", "3");
    b.characters("y");
    b.startAnnotation("b", "2");
    b.characters("z");
    b.endAnnotation("a");
    b.endAnnotation("b");
    checkApply(doc, b.build());

    // mutating into:
    // <a>
    // y{a=4, b=1}
    b = new Nindo.Builder();
    b.skip(1);
    b.deleteCharacters(2);
    b.startAnnotation("a", "4");
    b.skip(1);
    b.deleteCharacters(1);
    b.endAnnotation("a");
    checkApply(doc, b.build());

  }

  public void testAnnotationThroughInsertionEndingInDeletion() throws OperationException {
    IndexedDocument<Node, Element, Text> doc = DocProviders.POJO.parse("abcdefg");

    Nindo.Builder b;

    b = new Nindo.Builder();
    b.skip(1);
    b.startAnnotation("a", "2");
    b.skip(1);
    b.endAnnotation("a");
    checkApply(doc, b.build());


    b = new Nindo.Builder();
    b.skip(1);
    b.startAnnotation("a", "1");
    b.characters("x");
    b.deleteCharacters(1);
    b.endAnnotation("a");
    checkApply(doc, b.build());
  }

  public void testAnnotationThroughInsertionFollowedByDeletion() throws OperationException {
    IndexedDocument<Node, Element, Text> doc = DocProviders.POJO.parse("abcdefg");

    Nindo.Builder b;

    b = new Nindo.Builder();
    b.skip(1);
    b.startAnnotation("a", "2");
    b.skip(1);
    b.endAnnotation("a");
    checkApply(doc, b.build());


    b = new Nindo.Builder();
    b.skip(1);
    b.startAnnotation("a", "1");
    b.characters("x");
    b.endAnnotation("a");
    b.deleteCharacters(1);
    checkApply(doc, b.build());
  }

  public void testInsertionThenDeletionWithAnnotations() throws OperationException {
    IndexedDocument<Node, Element, Text> doc = DocProviders.POJO.parse("abcdefg");

    Nindo.Builder b;

    b = new Nindo.Builder();
    b.skip(1);
    b.startAnnotation("a", "2");
    b.skip(2);
    b.endAnnotation("a");
    checkApply(doc, b.build());


    b = new Nindo.Builder();
    b.skip(1);
    b.startAnnotation("a", null);
    b.characters("x");
    b.deleteCharacters(1);
    b.skip(1);
    b.endAnnotation("a");
    checkApply(doc, b.build());
  }

  public void testReAnnotate() throws OperationException {
    IndexedDocument<Node, Element, Text> doc = DocProviders.POJO.parse("abcdefg");

    Nindo.Builder b;

    b = new Nindo.Builder();
    b.skip(1);
    b.startAnnotation("a", "2");
    b.skip(1);
    b.startAnnotation("a", "3");
    b.skip(1);
    b.endAnnotation("a");
    checkApply(doc, b.build());

    b = new Nindo.Builder();
    b.skip(1);
    b.startAnnotation("a", "3");
    b.skip(2);
    b.endAnnotation("a");
    checkApply(doc, b.build());
  }

  public void testEndBeforeAndStartAfterDeletion() throws OperationException {
    IndexedDocument<Node, Element, Text> doc = DocProviders.POJO.parse("abcdefg");

    Nindo.Builder b;

    b = new Nindo.Builder();
    b.skip(1);
    b.startAnnotation("a", null);
    b.skip(1);
    b.endAnnotation("a");
    b.deleteCharacters(1);
    b.startAnnotation("a", "1");
    b.skip(1);
    b.endAnnotation("a");
    checkApply(doc, b.build());
  }

  public void testEndBeforeAndStartAfterDeletionThenInsertion() throws OperationException {
    IndexedDocument<Node, Element, Text> doc = DocProviders.POJO.parse("abcdefg");

    Nindo.Builder b;

    b = new Nindo.Builder();
    b.skip(1);
    b.startAnnotation("a", null);
    b.skip(1);
    b.endAnnotation("a");
    b.deleteCharacters(1);
    b.startAnnotation("a", "1");
    b.characters("x");
    b.endAnnotation("a");
    checkApply(doc, b.build());
  }

  public void testChangeBetweenInsertionAndDeletion() throws OperationException {
    IndexedDocument<Node, Element, Text> doc = DocProviders.POJO.parse("abcdefg");

    Nindo.Builder b;

    b = new Nindo.Builder();
    b.skip(1);
    b.startAnnotation("a", "1");
    b.characters("x");
    b.startAnnotation("a", "2");
    b.deleteCharacters(1);
    b.skip(1);
    b.endAnnotation("a");
    checkApply(doc, b.build());
  }

  public void testOpenClose() throws OperationException {
    IndexedDocument<Node, Element, Text> doc = DocProviders.POJO.parse("abcdefg");

    Nindo.Builder b;

    b = new Nindo.Builder();
    b.skip(1);
    b.startAnnotation("a", "1");
    b.startAnnotation("b", "2");
    b.startAnnotation("c", "3");
    b.endAnnotation("a");
    b.endAnnotation("c");
    b.endAnnotation("b");
    checkApply(doc, b.build());
  }

  public void testOpenInsertOpenClose() throws OperationException {
    IndexedDocument<Node, Element, Text> doc = DocProviders.POJO.parse("abcdefg");

    Nindo.Builder b;

    b = new Nindo.Builder();
    b.skip(1);
    b.startAnnotation("a", "1");
    b.characters("xyz");
    b.startAnnotation("a", "1");
    b.endAnnotation("a");
    checkApply(doc, b.build());
  }

  public void testOpenDuringInsertionThenUpdate() throws OperationException {
    IndexedDocument<Node, Element, Text> doc =
  DocProviders.POJO.parse("<q><r/></q>abcdefghijkl");

    Nindo.Builder b;

    b = new Nindo.Builder();
    b.startAnnotation("a", "1");
    b.skip(7);
    b.endAnnotation("a");
    checkApply(doc, b.build());

    b = new Nindo.Builder();
    b.elementStart("p", Attributes.EMPTY_MAP);
    b.startAnnotation("a", null);
    b.elementEnd();
    b.updateAttributes(Collections.singletonMap("u", "v"));
    b.replaceAttributes(new AttributesImpl("v", "u"));
    b.skip(1);
    b.endAnnotation("a");
    checkApply(doc, b.build());
  }

  public void testOpenDuringInsertionThenUpdate2() throws OperationException {
    IndexedDocument<Node, Element, Text> doc =
  DocProviders.POJO.parse("abcdef<q><r/></q>ghijkl");

    Nindo.Builder b;

    b = new Nindo.Builder();
    b.skip(8);
    b.startAnnotation("a", "1");
    b.skip(5);
    b.endAnnotation("a");
    checkApply(doc, b.build());

    b = new Nindo.Builder();
    b.startAnnotation("a", "1");
    b.skip(7);
    b.updateAttributes(Collections.singletonMap("u", "v"));
    //b.replaceAttributes(new AttributesImpl("v", "u"));
    b.skip(3);
    b.endAnnotation("a");
    checkApply(doc, b.build());
  }

  public void testDeletionResets() throws OperationException {
    IndexedDocument<Node, Element, Text> doc = DocProviders.POJO.parse("abcdefghijkl");

    Nindo.Builder b;

    b = new Nindo.Builder();
    b.startAnnotation("a", "1");
    b.skip(3);
    b.deleteCharacters(3);
    b.skip(3);
    b.endAnnotation("a");
    checkApply(doc, b.build());
  }

  public void testRedundantAnnotationsPreserved() throws OperationException {
    IndexedDocument<Node, Element, Text> doc = DocProviders.POJO.parse("abcdefg");
    IndexedDocument<Node, Element, Text> doc2 = DocProviders.POJO.parse("abcdefg");

    Nindo.Builder b;

    b = new Nindo.Builder();
    b.startAnnotation("a", "1");
    b.skip(7);
    b.endAnnotation("a");
    checkApply(doc2, b.build());

    b = new Nindo.Builder();
    b.startAnnotation("a", null);
    b.skip(2);
    b.startAnnotation("a", "2");
    b.skip(2);
    b.startAnnotation("a", null);
    b.skip(3);
    b.endAnnotation("a");
    DocOp docOp = doc.consumeAndReturnInvertible(b.build());

    doc2.consumeAndReturnInvertible(Nindo.fromDocOp(docOp, true));
    assertEquals(
        DocOpUtil.toXmlString(doc.asOperation()),
        DocOpUtil.toXmlString(doc2.asOperation()));
  }

  public void testNoRedundantSkips() throws OperationException {
    IndexedDocument<Node, Element, Text> doc = DocProviders.POJO.parse("abcdefghijkl");

    Nindo.Builder b;

    b = new Nindo.Builder();
    b.skip(1);
    b.startAnnotation("a", "1");
    b.skip(1);
    b.startAnnotation("b", "1");
    b.skip(1);
    b.endAnnotation("a");
    b.skip(1);
    b.startAnnotation("c", "1");
    b.skip(1);
    b.endAnnotation("c");
    b.skip(1);
    b.endAnnotation("b");
    b.skip(1);
    b.startAnnotation("c", "1");
    b.skip(1);
    b.endAnnotation("c");
    checkApply(doc, b.build());

    b = new Nindo.Builder();
    b.startAnnotation("z", "1");
    b.skip(doc.size());
    b.endAnnotation("z");
    DocOp docOp = doc.consumeAndReturnInvertible(b.build());
    assertEquals(3, docOp.size());
  }

  public void testBug1() throws OperationException {
    IndexedDocumentImpl<Node, Element, Text, ?> d = nindoDocumentParser.parseDocument(
      "<a>a</a>");
    Nindo.Builder b = new Nindo.Builder();
    b.skip(1);
    b.deleteCharacters(1);
    checkApply(d, b.build());
  }


//
//  public void testReverseBug1() throws OperationException {
//    IndexedDocumentImpl<Node, Element, Text, ?> d =
//      new IndexedDocumentImpl<Node, Element, Text, Void>(RawDocumentImpl.BUILDER,
//          new AnnotationTree<Object>("a", "b", null));
//    d.begin();
//    d.elementStart("a", Attributes.EMPTY_MAP);
//    d.startAnnotation("b", "3");
//    d.characters("abc");
//    d.endAnnotation("b");
//    d.elementEnd();
//    d.finish();
//
//    OperationContainer reverseSink = new OperationContainer();
//    d.registerReverseSink(reverseSink);
//    String beforeXml = OperationXmlifier.xmlify(d);
//
//    d.begin();
//    d.skip(2);
//    d.startAnnotation("a", "2");
//    d.characters("abcd");
//    d.deleteCharacters(1);
//    d.endAnnotation("a");
//    d.finish();
//
//    String afterXml = OperationXmlifier.xmlify(d);
//    DocumentMutation reverse = reverseSink.operation;
//    reverse.apply(d);
//    String reversedXml = OperationXmlifier.xmlify(d);
//
//    assertEquals(beforeXml, reversedXml);
//
//    DocumentOperationChecker.Recorder r = new DocumentOperationChecker.Recorder();
//    r.begin();
//    r.skip(2);
//    r.deleteCharacters(4);
//    r.startAnnotation("a", null);
//    r.startAnnotation("b", "3");
//    r.characters("b");
//    r.endAnnotation("a");
//    r.endAnnotation("b");
//    r.finish();
//    DocumentOperationChecker checker = r.finishRecording();
//    reverse.apply(checker);
//  }
//
//  public void testReverseBug2() throws OperationException {
//    IndexedDocumentImpl<Node, Element, Text, ?> d =
//      new IndexedDocumentImpl<Node, Element, Text, Void>(RawDocumentImpl.BUILDER,
//          new AnnotationTree<Object>("a", "b", null));
//    d.begin();
//    d.elementStart("a", Attributes.EMPTY_MAP);
//    d.characters("ababa");
//    d.startAnnotation("e", "2");
//    d.characters("d");
//    d.startAnnotation("a", "1");
//    d.characters("abcd");
//    d.endAnnotation("a");
//    d.characters("babc");
//    d.endAnnotation("e");
//    d.characters("de");
//    d.elementEnd();
//    d.finish();
//
//    OperationContainer reverseSink = new OperationContainer();
//    d.registerReverseSink(reverseSink);
//
//    d.begin();
//    d.skip(1);
//    d.skip(14);
//    d.deleteCharacters(1);
//    d.startAnnotation("d", "2");
//    d.startAnnotation("b", null);
//    d.endAnnotation("d");
//    d.endAnnotation("b");
//    d.finish();
//
//    DocumentOperationChecker.Recorder r = new DocumentOperationChecker.Recorder();
//    r.begin();
//    r.skip(15);
//    r.startAnnotation("a", null);
//    r.startAnnotation("e", null);
//    r.characters("d");
//    r.endAnnotation("a");
//    r.endAnnotation("e");
//    r.finish();
//    DocumentOperationChecker checker = r.finishRecording();
//    reverseSink.operation.apply(checker);
//  }
//
//  public void testReverseBug3() throws OperationException {
//    IndexedDocumentImpl<Node, Element, Text, ?> d =
//      new IndexedDocumentImpl<Node, Element, Text, Void>(RawDocumentImpl.BUILDER,
//          new AnnotationTree<Object>("a", "b", null));
//    d.begin();
//    d.elementStart("a", Attributes.EMPTY_MAP);
//    d.characters("babcdefabcdabfabcdefabcdefghabcdefgh");
//    d.startAnnotation("d", "3");
//    d.characters("gab");
//    d.startAnnotation("e", "1");
//    d.characters("gababcabcefghidefghefaababcdefghiabcdefgh");
//    d.endAnnotation("d");
//    d.characters("defghi");
//    d.startAnnotation("a", "1");
//    d.characters("abcd");
//    d.endAnnotation("e");
//    d.characters("efg");
//    d.endAnnotation("a");
//    d.characters("cdefe");
//    d.startAnnotation("b", "3");
//    d.characters("f");
//    d.endAnnotation("b");
//    d.elementEnd();
//    d.finish();
//
//    OperationContainer reverseSink = new OperationContainer();
//    d.registerReverseSink(reverseSink);
//
//    String beforeXml = OperationXmlifier.xmlify(d);
//
//    d.begin();
//    d.skip(1);
//    d.skip(15);
//    d.startAnnotation("e", "1");
//    d.skip(3);
//    d.characters("abcd");
//    d.skip(2);
//    d.characters("abcdefgh");
//    d.deleteCharacters(1);
//    d.startAnnotation("a", "2");
//    d.skip(24);
//    d.startAnnotation("e", "3");
//    d.skip(4);
//    d.characters("abcd");
//    d.startAnnotation("a", "1");
//    d.deleteCharacters(1);
//    d.skip(3);
//    d.characters("abcdefghi");
//    d.deleteCharacters(1);
//    d.skip(13);
//    d.startAnnotation("b", "1");
//    d.endAnnotation("e");
//    d.endAnnotation("b");
//    d.endAnnotation("a");
//    d.finish();
//
//    String afterXml = OperationXmlifier.xmlify(d);
//    DocumentMutation reverse = reverseSink.operation;
//    reverse.apply(d);
//    String reversedXml = OperationXmlifier.xmlify(d);
//
//    assertEquals(beforeXml, reversedXml);
//
//    DocumentOperationChecker.Recorder r = new DocumentOperationChecker.Recorder();
//    r.begin();
//    r.skip(16);
//    r.startAnnotation("e", null);
//    r.skip(3);
//    r.deleteCharacters(4);
//    r.skip(2);
//    r.deleteCharacters(8);
//    r.startAnnotation("a", null);
//    r.startAnnotation("b", null);
//    r.startAnnotation("d", null);
//    r.characters("a");
//    r.endAnnotation("b");
//    r.endAnnotation("d");
//    r.skip(18);
//    r.endAnnotation("e");
//    r.skip(6);
//    r.startAnnotation("e", "1");
//    r.skip(4);
//    r.deleteCharacters(4);
//    r.startAnnotation("b", null);
//    r.startAnnotation("d", "3");
//    r.characters("f");
//    r.endAnnotation("b");
//    r.endAnnotation("d");
//    r.skip(3);
//    r.deleteCharacters(9);
//    r.startAnnotation("b", null);
//    r.startAnnotation("d", "3");
//    r.characters("d");
//    r.endAnnotation("b");
//    r.endAnnotation("d");
//    r.skip(13);
//    r.endAnnotation("a");
//    r.endAnnotation("e");
//    r.finish();
//    DocumentOperationChecker checker = r.finishRecording();
//    reverse.apply(checker);
//
//    assertEquals(beforeXml, reversedXml);
//  }
//
//  public void testReverseBug4() throws OperationException {
//    IndexedDocumentImpl<Node, Element, Text, ?> d =
//      new IndexedDocumentImpl<Node, Element, Text, Void>(RawDocumentImpl.BUILDER,
//          new AnnotationTree<Object>("a", "b", null));
//    d.begin();
//    d.elementStart("a", Attributes.EMPTY_MAP);
//    d.characters("a");
//    d.startAnnotation("d", "2");
//    d.characters("bc");
//    d.endAnnotation("d");
//    d.elementEnd();
//    d.finish();
//
//    OperationContainer reverseSink = new OperationContainer();
//    d.registerReverseSink(reverseSink);
//
//    String beforeXml = OperationXmlifier.xmlify(d);
//
//    d.begin();
//    d.skip(1);
//    d.startAnnotation("e", "3");
//    d.skip(1);
//    d.endAnnotation("e");
//    d.characters("x");
//    d.deleteCharacters(1);
//    d.finish();
//
//    String afterXml = OperationXmlifier.xmlify(d);
//    DocumentMutation reverse = reverseSink.operation;
//    reverse.apply(d);
//    String reversedXml = OperationXmlifier.xmlify(d);
//
//    DocumentOperationChecker.Recorder r = new DocumentOperationChecker.Recorder();
//    r.begin();
//    r.skip(1);
//    r.startAnnotation("e", null);
//    r.skip(1);
//    r.deleteCharacters(1);
//    r.startAnnotation("d", "2");
//    r.characters("b");
//    r.endAnnotation("d");
//    r.endAnnotation("e");
//    r.finish();
//    DocumentOperationChecker checker = r.finishRecording();
//    reverse.apply(checker);
//
//    assertEquals(beforeXml, reversedXml);
//  }
//
//  public void testReverseBug5() throws OperationException {
//    IndexedDocumentImpl<Node, Element, Text, ?> d =
//      new IndexedDocumentImpl<Node, Element, Text, Void>(RawDocumentImpl.BUILDER,
//          new AnnotationTree<Object>("a", "b", null));
//    d.begin();
//    d.elementStart("a", Attributes.EMPTY_MAP);
//    d.startAnnotation("e", "1");
//    d.characters("aab");
//    d.endAnnotation("e");
//    d.characters("c");
//    d.elementEnd();
//    d.finish();
//
//    OperationContainer reverseSink = new OperationContainer();
//    d.registerReverseSink(reverseSink);
//
//    String beforeXml = OperationXmlifier.xmlify(d);
//
//    d.begin();
//    d.skip(1);
//    d.skip(1);
//    d.startAnnotation("e", "2");
//    d.characters("a");
//    d.deleteCharacters(1);
//    d.skip(1);
//    d.deleteCharacters(1);
//    d.endAnnotation("e");
//    d.finish();
//
//    String afterXml = OperationXmlifier.xmlify(d);
//    DocumentMutation reverse = reverseSink.operation;
//    reverse.apply(d);
//    String reversedXml = OperationXmlifier.xmlify(d);
//
//    assertEquals(beforeXml, reversedXml);
//
//    DocumentOperationChecker.Recorder r = new DocumentOperationChecker.Recorder();
//    r.begin();
//    r.skip(2);
//    r.deleteCharacters(1);
//    r.startAnnotation("e", "1");
//    r.characters("a");
//    r.skip(1);
//    r.startAnnotation("e", null);
//    r.characters("c");
//    r.endAnnotation("e");
//    r.finish();
//    DocumentOperationChecker checker = r.finishRecording();
//    reverse.apply(checker);
//  }
//
//  public void testReverseBug6() throws OperationException {
//    IndexedDocumentImpl<Node, Element, Text, ?> d =
//      new IndexedDocumentImpl<Node, Element, Text, Void>(RawDocumentImpl.BUILDER,
//          new AnnotationTree<Object>("a", "b", null));
//    d.begin();
//    d.elementStart("a", Attributes.EMPTY_MAP);
//    d.startAnnotation("d", "2");
//    d.characters("a");
//    d.startAnnotation("e", "1");
//    d.characters("b");
//    d.endAnnotation("d");
//    d.endAnnotation("e");
//    d.characters("b");
//    d.elementEnd();
//    d.finish();
//    OperationContainer reverseSink = new OperationContainer();
//    d.registerReverseSink(reverseSink);
//
//    String beforeXml = OperationXmlifier.xmlify(d);
//
//    d.begin();
//    d.skip(1);
//    d.startAnnotation("e", null);
//    d.skip(2);
//    d.endAnnotation("e");
//    d.deleteCharacters(1);
//    d.finish();
//
//    String afterXml = OperationXmlifier.xmlify(d);
//    DocumentMutation reverse = reverseSink.operation;
//    reverse.apply(d);
//    String reversedXml = OperationXmlifier.xmlify(d);
//
//    assertEquals(beforeXml, reversedXml);
//
//    DocumentOperationChecker.Recorder r = new DocumentOperationChecker.Recorder();
//    r.begin();
//    r.skip(2);
//    r.startAnnotation("e", "1");
//    r.skip(1);
//    r.startAnnotation("d", null);
//    r.startAnnotation("e", null);
//    r.characters("b");
//    r.endAnnotation("d");
//    r.endAnnotation("e");
//    r.finish();
//    DocumentOperationChecker checker = r.finishRecording();
//    reverse.apply(checker);
//  }
//
//  public void testConcurrentModificationException() throws OperationException {
//    // The test is that this doesn't throw a ConcurrentModificationException.
//    IndexedDocumentImpl<Node, Element, Text, ?> d =
//      new IndexedDocumentImpl<Node, Element, Text, Void>(RawDocumentImpl.PROVIDER,
//          new AnnotationTree<Object>("a", "b", null));
//    // initial
//    d.begin();
//    d.elementStart("blip", Attributes.EMPTY_MAP);
//    {
//      Map<String, String> a = new HashMap<String, String>();
//      a.put("_t", "title");
//      a.put("t", "h1");
//      d.elementStart("p", new Attributes(a));
//    }
//    d.elementEnd();
//    d.elementEnd();
//    d.finish();
//    // mutation
//    d.begin();
//    d.skip(1);
//    d.setAttributes(new Attributes("_t", "title"));
//    d.finish();
//  }
//
//  public void testNPE1() throws OperationException {
//    // The test is that this doesn't throw a NullPointerException.
//    IndexedDocumentImpl<Node, Element, Text, ?> d =
//      new IndexedDocumentImpl<Node, Element, Text, Void>(
//          RawDocumentImpl.PROVIDER,
//          new AnnotationTree<Object>("a", "b", null));
//
//    // initialization steps
//    d.begin();
//    d.elementStart("blip", Attributes.EMPTY_MAP);
//    d.elementStart("p", new Attributes("_t", "title"));
//
//    d.elementEnd();
//    d.elementStart("p", Attributes.EMPTY_MAP);
//    d.characters("a");
//    d.elementEnd();
//    d.elementStart("p", Attributes.EMPTY_MAP);
//
//    d.elementEnd();
//    d.elementStart("p", new Attributes("_t", "title"));
//    d.elementEnd();
//    d.elementStart("p", Attributes.EMPTY_MAP);
//    d.elementEnd();
//    d.elementEnd();
//    d.finish();
//
//    d.begin();
//    d.skip(2);
//    d.deleteAntiElementStart();
//    d.deleteElementStart();
//    d.deleteCharacters(1);
//    d.deleteElementEnd();
//    d.deleteAntiElementEnd(new Attributes("t", ""));
//    d.finish();
//
//    d.begin();
//    d.skip(1);
//    d.deleteElementStart();
//    d.deleteElementEnd();
//    d.finish();
//
//    d.begin();
//    d.skip(5);
//    d.elementStart("p", Attributes.EMPTY_MAP);
//    d.elementEnd();
//    d.finish();
//
//    // mutation that crashes
//    // current state: <blip><p _t=title></p><p></p><p></p></blip>
//    d.begin();
//    d.skip(4);
//    d.deleteAntiElementStart();
//    d.deleteAntiElementEnd(Attributes.EMPTY_MAP);
//    d.antiElementStart();
//    d.antiElementEnd(Attributes.EMPTY_MAP);
//    d.finish();
//  }

  public void testAnnotationIntervalIterator() throws OperationException {
    IndexedDocumentImpl<Node, Element, Text, ?> doc =
        new IndexedDocumentImpl<Node, Element, Text, Void>(
            RawDocumentImpl.PROVIDER.parse("<doc><x><p>abcdefgh</p></x></doc>"),
            new AnnotationTree<Object>("a", "b", null), DocumentSchema.NO_SCHEMA_CONSTRAINTS);
    // 1-3: a=1, c=1
    // 3-5: a=1, b=1, c=1
    // 5-6: a=2, b=1, c=1
    // 6-8: b=1, c=1
    doc.consumeAndReturnInvertible(Nindo.setAnnotation(1, 5, "a", "1"));
    doc.consumeAndReturnInvertible(Nindo.setAnnotation(5, 6, "a", "2"));
    doc.consumeAndReturnInvertible(Nindo.setAnnotation(3, 8, "b", "1"));
    doc.consumeAndReturnInvertible(Nindo.setAnnotation(1, 8, "c", "1"));

    {
      Iterator<AnnotationInterval<String>> iterator =
        doc.annotationIntervals(2, 10, CollectionUtils.newStringSet("a")).iterator();
      {
        AnnotationInterval<String> i = iterator.next();
        assertEquals(2, i.start());
        assertEquals(5, i.end());
        assertEquals(1, CollectionUtils.newJavaMap(i.annotations()).size());
        assertEquals("1", i.annotations().get("a", "x"));
        assertEquals(0, CollectionUtils.newJavaMap(i.diffFromLeft()).size());
        assertEquals("x", i.diffFromLeft().get("a", "x"));
      }
      {
        AnnotationInterval<String> i = iterator.next();
        assertEquals(5, i.start());
        assertEquals(6, i.end());
        assertEquals(1, CollectionUtils.newJavaMap(i.annotations()).size());
        assertEquals("2", i.annotations().get("a", "x"));
        assertEquals(1, CollectionUtils.newJavaMap(i.diffFromLeft()).size());
        assertEquals("2", i.diffFromLeft().get("a", "x"));
      }
      {
        AnnotationInterval<String> i = iterator.next();
        assertEquals(6, i.start());
        assertEquals(10, i.end());
        assertEquals(1, CollectionUtils.newJavaMap(i.annotations()).size());
        assertEquals(null, i.annotations().get("a", "x"));
        assertEquals(1, CollectionUtils.newJavaMap(i.diffFromLeft()).size());
        assertEquals(null, i.diffFromLeft().get("a", "x"));
      }
      assertFalse(iterator.hasNext());
    }

    // 1-3: a=1, c=1
    // 3-5: a=1, b=1, c=1
    // 5-6: a=2, b=1, c=1
    // 6-8: b=1, c=1
    {
      Iterator<AnnotationInterval<String>> iterator =
        doc.annotationIntervals(2, 10, null).iterator();
      {
        AnnotationInterval<String> i = iterator.next();
        assertEquals(2, i.start());
        assertEquals(3, i.end());
        assertEquals(3, CollectionUtils.newJavaMap(i.annotations()).size());
        assertEquals("1", i.annotations().get("a", "x"));
        assertEquals(null, i.annotations().get("b", "x"));
        assertEquals("1", i.annotations().get("c", "x"));
        assertEquals(0, CollectionUtils.newJavaMap(i.diffFromLeft()).size());
        assertEquals("x", i.diffFromLeft().get("a", "x"));
        assertEquals("x", i.diffFromLeft().get("b", "x"));
        assertEquals("x", i.diffFromLeft().get("c", "x"));
      }
      {
        AnnotationInterval<String> i = iterator.next();
        assertEquals(3, i.start());
        assertEquals(5, i.end());
        assertEquals(3, CollectionUtils.newJavaMap(i.annotations()).size());
        assertEquals("1", i.annotations().get("a", "x"));
        assertEquals("1", i.annotations().get("b", "x"));
        assertEquals("1", i.annotations().get("c", "x"));
        assertEquals(1, CollectionUtils.newJavaMap(i.diffFromLeft()).size());
        assertEquals("x", i.diffFromLeft().get("a", "x"));
        assertEquals("1", i.diffFromLeft().get("b", "x"));
        assertEquals("x", i.diffFromLeft().get("c", "x"));
      }
      {
        AnnotationInterval<String> i = iterator.next();
        assertEquals(5, i.start());
        assertEquals(6, i.end());
        assertEquals(3, CollectionUtils.newJavaMap(i.annotations()).size());
        assertEquals("2", i.annotations().get("a", "x"));
        assertEquals("1", i.annotations().get("b", "x"));
        assertEquals("1", i.annotations().get("c", "x"));
        assertEquals(1, CollectionUtils.newJavaMap(i.diffFromLeft()).size());
        assertEquals("2", i.diffFromLeft().get("a", "x"));
      }
      {
        AnnotationInterval<String> i = iterator.next();
        assertEquals(6, i.start());
        assertEquals(8, i.end());
        assertEquals(3, CollectionUtils.newJavaMap(i.annotations()).size());
        assertEquals(null, i.annotations().get("a", "x"));
        assertEquals("1", i.annotations().get("b", "x"));
        assertEquals("1", i.annotations().get("c", "x"));
        assertEquals(1, CollectionUtils.newJavaMap(i.diffFromLeft()).size());
        assertEquals(null, i.diffFromLeft().get("a", "x"));
        assertEquals("x", i.diffFromLeft().get("b", "x"));
        assertEquals("x", i.diffFromLeft().get("c", "x"));
      }
      {
        AnnotationInterval<String> i = iterator.next();
        assertEquals(8, i.start());
        assertEquals(10, i.end());
        assertEquals(3, CollectionUtils.newJavaMap(i.annotations()).size());
        assertEquals(null, i.annotations().get("a", "x"));
        assertEquals(null, i.annotations().get("b", "x"));
        assertEquals(null, i.annotations().get("c", "x"));
        assertEquals(2, CollectionUtils.newJavaMap(i.diffFromLeft()).size());
        assertEquals("x", i.diffFromLeft().get("a", "x"));
        assertEquals(null, i.diffFromLeft().get("b", "x"));
        assertEquals(null, i.diffFromLeft().get("c", "x"));
      }
      assertFalse(iterator.hasNext());
    }

    // 1-3: a=1, c=1
    // 3-5: a=1, b=1, c=1
    // 5-6: a=2, b=1, c=1
    // 6-8: b=1, c=1
    {
      Iterator<AnnotationInterval<String>> iterator =
        doc.annotationIntervals(3, 4, null).iterator();
      {
        AnnotationInterval<String> i = iterator.next();
        assertEquals(3, i.start());
        assertEquals(4, i.end());
        assertEquals(3, CollectionUtils.newJavaMap(i.annotations()).size());
        assertEquals("1", i.annotations().get("a", "x"));
        assertEquals("1", i.annotations().get("b", "x"));
        assertEquals("1", i.annotations().get("c", "x"));
        assertEquals(1, CollectionUtils.newJavaMap(i.diffFromLeft()).size());
        assertEquals("x", i.diffFromLeft().get("a", "x"));
        assertEquals("1", i.diffFromLeft().get("b", "x"));
        assertEquals("x", i.diffFromLeft().get("c", "x"));
      }
      assertFalse(iterator.hasNext());
    }

    {
      Iterator<AnnotationInterval<String>> iterator =
        doc.annotationIntervals(3, 3, null).iterator();
      assertFalse(iterator.hasNext());
    }
  }

  public void testRangedAnnotationIterator() throws OperationException {
    IndexedDocumentImpl<Node, Element, Text, ?> doc =
        new IndexedDocumentImpl<Node, Element, Text, Void>(
            RawDocumentImpl.PROVIDER.parse("<doc><x><p>abcdefgh</p></x></doc>"),
            new AnnotationTree<Object>("a", "b", null), DocumentSchema.NO_SCHEMA_CONSTRAINTS);
    // 1-3: a=1, c=1
    // 3-5: a=1, b=1, c=1
    // 5-6: a=2, b=1, c=1
    // 6-8: b=1, c=1
    doc.consumeAndReturnInvertible(Nindo.setAnnotation(1, 5, "a", "1"));
    doc.consumeAndReturnInvertible(Nindo.setAnnotation(5, 6, "a", "2"));
    doc.consumeAndReturnInvertible(Nindo.setAnnotation(3, 8, "b", "1"));
    doc.consumeAndReturnInvertible(Nindo.setAnnotation(1, 8, "c", "1"));

    {
      Iterator<RangedAnnotation<String>> iterator =
        doc.rangedAnnotations(2, 10, CollectionUtils.newStringSet("a")).iterator();
      {
        RangedAnnotation<String> r = iterator.next();
        assertEquals("a", r.key());
        assertEquals("1", r.value());
        assertEquals(1, r.start());
        assertEquals(5, r.end());
      }
      {
        RangedAnnotation<String> r = iterator.next();
        assertEquals("a", r.key());
        assertEquals("2", r.value());
        assertEquals(5, r.start());
        assertEquals(6, r.end());
      }
      {
        RangedAnnotation<String> r = iterator.next();
        assertEquals("a", r.key());
        assertEquals(null, r.value());
        assertEquals(6, r.start());
        assertEquals(12, r.end());
      }
      assertFalse(iterator.hasNext());
    }

    {
      Iterator<RangedAnnotation<String>> iterator =
        doc.rangedAnnotations(2, 10, null).iterator();
      {
        RangedAnnotation<String> r = iterator.next();
        assertEquals("b", r.key());
        assertEquals(null, r.value());
        assertEquals(0, r.start());
        assertEquals(3, r.end());
      }
      {
        RangedAnnotation<String> r = iterator.next();
        RangedAnnotation<String> r2 = iterator.next();
        // Order of these two ranges is unspecified; normalize it.
        if ("c".equals(r.key())) {
          RangedAnnotation<String> tmp = r2;
          r2 = r;
          r = tmp;
        }

        assertEquals("a", r.key());
        assertEquals("1", r.value());
        assertEquals(1, r.start());
        assertEquals(5, r.end());

        assertEquals("c", r2.key());
        assertEquals("1", r2.value());
        assertEquals(1, r2.start());
        assertEquals(8, r2.end());
      }
      {
        RangedAnnotation<String> r = iterator.next();
        assertEquals("b", r.key());
        assertEquals("1", r.value());
        assertEquals(3, r.start());
        assertEquals(8, r.end());
      }
      {
        RangedAnnotation<String> r = iterator.next();
        assertEquals("a", r.key());
        assertEquals("2", r.value());
        assertEquals(5, r.start());
        assertEquals(6, r.end());
      }
      {
        RangedAnnotation<String> r = iterator.next();
        assertEquals("a", r.key());
        assertEquals(null, r.value());
        assertEquals(6, r.start());
        assertEquals(12, r.end());
      }
      {
        RangedAnnotation<String> r = iterator.next();
        RangedAnnotation<String> r2 = iterator.next();
        // Order of these two ranges is unspecified; normalize it.
        if ("c".equals(r.key())) {
          RangedAnnotation<String> tmp = r2;
          r2 = r;
          r = tmp;
        }

        assertEquals("b", r.key());
        assertEquals(null, r.value());
        assertEquals(8, r.start());
        assertEquals(12, r.end());

        assertEquals("c", r2.key());
        assertEquals(null, r2.value());
        assertEquals(8, r2.start());
        assertEquals(12, r2.end());
      }
      assertFalse(iterator.hasNext());
    }

    {
      Iterator<AnnotationInterval<String>> iterator =
        doc.annotationIntervals(3, 3, null).iterator();
      assertFalse(iterator.hasNext());
    }
  }

  public void testRangedAnnotationIteratorWithNonStrings() throws OperationException {
    AnnotationTree<Object> annotations = new AnnotationTree<Object>(
        "a", "b", null);
    IndexedDocumentImpl<Node, Element, Text, ?> doc =
        new IndexedDocumentImpl<Node, Element, Text, Void>(
            RawDocumentImpl.PROVIDER.parse("<doc><x><p>abcdefgh</p></x></doc>"), annotations,
            DocumentSchema.NO_SCHEMA_CONSTRAINTS);

    // 1-3: a=1, @c=Object
    // 3-5: a=1
    doc.consumeAndReturnInvertible(Nindo.setAnnotation(1, 5, "a", "1"));
    annotations.begin();
    annotations.skip(1);
    String c = Annotations.makeLocal("c");
    annotations.startAnnotation(c,  new Object());
    annotations.skip(2);
    annotations.endAnnotation(c);
    annotations.finish();

    {
      Iterator<RangedAnnotation<String>> iterator =
          doc.rangedAnnotations(2, 10, CollectionUtils.newStringSet("a")).iterator();
      {
        RangedAnnotation<String> r = iterator.next();
        assertEquals("a", r.key());
        assertEquals("1", r.value());
        assertEquals(1, r.start());
        assertEquals(5, r.end());
      }
      {
        RangedAnnotation<String> r = iterator.next();
        assertEquals("a", r.key());
        assertEquals(null, r.value());
        assertEquals(5, r.start());
        assertEquals(12, r.end());
      }
      assertFalse(iterator.hasNext());
    }
    {
      Iterator<RangedAnnotation<String>> iterator =
          doc.rangedAnnotations(2, 10, null).iterator();
      {
        RangedAnnotation<String> r = iterator.next();
        assertEquals("a", r.key());
        assertEquals("1", r.value());
        assertEquals(1, r.start());
        assertEquals(5, r.end());
      }
      {
        RangedAnnotation<String> r = iterator.next();
        assertEquals("a", r.key());
        assertEquals(null, r.value());
        assertEquals(5, r.start());
        assertEquals(12, r.end());
      }
      assertFalse(iterator.hasNext());
    }
    {
      Iterator<AnnotationInterval<String>> iterator =
          doc.annotationIntervals(3, 3, null).iterator();
      assertFalse(iterator.hasNext());
    }
  }

  public void testSplitTextNeverReturnsSibling() {
    TestDocumentContext<Node, Element, Text> cxt = ContextProviders.createTestPojoContext(
        DocProviders.POJO.parse("ab").asOperation(),
        null, null, null, DocumentSchema.NO_SCHEMA_CONSTRAINTS);

    TextNodeOrganiser<Text> organiser = cxt.textNodeOrganiser();
    MutableDocument<Node, Element, Text> doc = cxt.document();
    Text first = (Text) doc.getFirstChild(doc.getDocumentElement());
    Text text = organiser.splitText(first, 1);
    LocalDocument<Node, Element, Text> local = cxt.annotatableContent();

    Element tr = local.transparentCreate("l", Attributes.EMPTY_MAP, doc.getDocumentElement(), text);
    local.transparentMove(tr, text, null, null);

    assertNull(cxt.getIndexedDoc().splitText(first, 1));
    assertNull(organiser.splitText(first, 1));

    assertSame(first, organiser.splitText(first, 0));
    assertSame(first, organiser.splitText(first, 0));

    assertEquals("a<l>b</l>", XmlStringBuilder.innerXml(local).toString());
    assertEquals("ab", XmlStringBuilder.innerXml(doc).toString());
  }

  public void testCantGetLocationOfInvalidNode() throws OperationException {
    AnnotationTree<Object> annotations = new AnnotationTree<Object>(
        "a", "b", null);
    RawDocumentImpl rawDoc = RawDocumentImpl.PROVIDER.parse("<doc><p></p></doc>");
    IndexedDocumentImpl<Node, Element, Text, ?> doc =
        new IndexedDocumentImpl<Node, Element, Text, Void>(rawDoc, annotations,
            DocumentSchema.NO_SCHEMA_CONSTRAINTS);

    Node element = doc.getDocumentElement().getFirstChild();

    // element is valid
    assertEquals(0, doc.getLocation(element));

    doc.consumeAndReturnInvertible(Nindo.deleteElement(0));

    // element was deleted
    try {
      doc.getLocation(element);
      fail("Expected: IllegalArgumentException");
    } catch (IllegalArgumentException iae) {
      // OK
    }

    // element that was never valid to begin with
    try {
      doc.getLocation(rawDoc.createElement("abc", Collections.<String, String>emptyMap(),
          doc.getDocumentElement(), null));
      fail("Expected: IllegalArgumentException");
    } catch (IllegalArgumentException iae) {
      // OK
    }
  }
}
TOP

Related Classes of org.waveprotocol.wave.model.document.indexed.IndexedDocumentImplTest

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.