/** * Copyright 2008 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 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 } } }