/**
* 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 static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import org.waveprotocol.wave.model.document.AnnotationSetTestBase;
import org.waveprotocol.wave.model.operation.OperationException;
import org.waveprotocol.wave.model.util.CollectionFactory;
import org.waveprotocol.wave.model.util.CollectionUtils;
/**
* Some basic tests for AnnotationTree.
*
* @author ohler@google.com (Christian Ohler)
*/
public class AnnotationTreeTest extends AnnotationSetTestBase {
@Override
protected AnnotationTree<Object> getNewSet(AnnotationSetListener<Object> listener) {
return new AnnotationTree<Object>(new Object(), new Object(), listener);
}
CollectionFactory getFactory() {
return CollectionUtils.getCollectionFactory();
}
void setAnnotation(RawAnnotationSet<Object> a, int start, int end, String key, Object value)
throws OperationException {
a.begin();
if (start > 0) {
a.skip(start);
}
a.startAnnotation(key, value);
if (end - start > 0) {
a.skip(end - start);
}
a.endAnnotation(key);
a.finish();
}
void insert(RawAnnotationSet<Object> a, int firstShiftedIndex, int length)
throws OperationException {
a.begin();
if (firstShiftedIndex > 0) {
a.skip(firstShiftedIndex);
}
if (length > 0) {
a.insert(length);
}
a.finish();
}
void delete(RawAnnotationSet<Object> a, int start, int length) throws OperationException {
a.begin();
if (start > 0) {
a.skip(start);
}
if (length > 0) {
a.delete(length);
}
a.finish();
}
public void testOffBalance() throws OperationException {
AnnotationTree<Object> tree = new AnnotationTree<Object>(new Object(),
new Object(), null);
final int size = 15;
insert(tree, 0, size);
for (int i = 0; i < size - 1; i++) {
setAnnotation(tree, i, i + 1, "a", "" + i);
tree.checkSomeInvariants();
}
}
public void testRemoveAll() throws OperationException {
AnnotationTree<Object> tree = new AnnotationTree<Object>(new Object(),
new Object(), null);
insert(tree, 0, 1);
setAnnotation(tree, 0, 1, "a", "0");
setAnnotation(tree, 0, 1, "a", null);
delete(tree, 0, 1);
}
public void testEraseMergeDuringSetAnnotation() throws OperationException {
{
AnnotationTree<Object> tree = new AnnotationTree<Object>(new Object(),
new Object(), null);
insert(tree, 0, 3);
setAnnotation(tree, 0, 1, "a", "1");
setAnnotation(tree, 1, 2, "a", "2");
setAnnotation(tree, 2, 3, "a", "3");
setAnnotation(tree, 0, 2, "a", "5");
}
{
AnnotationTree<Object> tree = new AnnotationTree<Object>(new Object(),
new Object(), null);
insert(tree, 0, 3);
setAnnotation(tree, 0, 1, "a", "1");
setAnnotation(tree, 1, 2, "a", "2");
setAnnotation(tree, 2, 3, "a", "3");
setAnnotation(tree, 1, 3, "a", "5");
}
}
public void testSplitAnnotations() throws OperationException {
AnnotationTree<Object> tree = new AnnotationTree<Object>(new Object(),
new Object(), null);
// The test is that none of this throws an exception.
tree.begin();
tree.startAnnotation("a", "1");
tree.insert(1);
tree.startAnnotation("a", "2");
tree.insert(5);
tree.startAnnotation("a", "1");
tree.insert(1);
tree.endAnnotation("a");
tree.finish();
tree.checkSomeInvariants();
// cut off one item on the left
tree.begin();
tree.skip(2);
tree.startAnnotation("a", "3");
tree.skip(4);
tree.endAnnotation("a");
tree.finish();
tree.checkSomeInvariants();
// cut off one item on the right
tree.begin();
tree.skip(2);
tree.startAnnotation("a", "4");
tree.skip(3);
tree.endAnnotation("a");
tree.finish();
tree.checkSomeInvariants();
// cut off one item on the left and one on the right
tree.begin();
tree.skip(3);
tree.startAnnotation("a", "5");
tree.skip(1);
tree.endAnnotation("a");
tree.finish();
tree.checkSomeInvariants();
}
@SuppressWarnings("unchecked")
public void testListenerBasics() throws OperationException {
final AnnotationSetListener<Object> listener = mock(AnnotationSetListener.class);
RawAnnotationSet<Object> m = getNewSet(listener);
m.begin();
m.startAnnotation("a", "1");
m.insert(1);
m.endAnnotation("a");
m.insert(1);
m.startAnnotation("a", "2");
m.insert(1);
m.startAnnotation("a", "1");
m.insert(1);
m.startAnnotation("a", "2");
m.insert(1);
m.startAnnotation("a", "1");
m.insert(1);
m.endAnnotation("a");
m.finish();
m.begin();
m.startAnnotation("a", "1");
m.skip(6);
m.endAnnotation("a");
m.finish();
verify(listener).onAnnotationChange(0, 1, "a", "1");
verify(listener).onAnnotationChange(2, 3, "a", "2");
verify(listener).onAnnotationChange(3, 4, "a", "1");
verify(listener).onAnnotationChange(4, 5, "a", "2");
verify(listener).onAnnotationChange(5, 6, "a", "1");
// These assertions are too strict; the way the AnnotationSet splits its
// notifications is actually undefined, and there would be several
// alternatives here.
verify(listener).onAnnotationChange(0, 6, "a", "1");
}
@SuppressWarnings("unchecked")
public void testListenerBasics2() throws OperationException {
final AnnotationSetListener<Object> listener = mock(AnnotationSetListener.class);
RawAnnotationSet<Object> m = getNewSet(listener);
m.begin();
m.insert(1);
m.startAnnotation("a", "1");
m.insert(1);
m.startAnnotation("a", null);
m.insert(1);
m.startAnnotation("a", "1");
m.insert(1);
m.startAnnotation("a", null);
m.insert(1);
m.startAnnotation("a", "1");
m.insert(1);
m.endAnnotation("a");
m.finish();
m.begin();
m.startAnnotation("a", "1");
m.skip(6);
m.endAnnotation("a");
m.finish();
verify(listener).onAnnotationChange(1, 2, "a", "1");
verify(listener).onAnnotationChange(2, 3, "a", null);
verify(listener).onAnnotationChange(3, 4, "a", "1");
verify(listener).onAnnotationChange(4, 5, "a", null);
verify(listener).onAnnotationChange(5, 6, "a", "1");
// These assertions are too strict; the way the AnnotationSet splits its
// notifications is actually undefined, and there would be several
// alternatives here.
verify(listener).onAnnotationChange(0, 6, "a", "1");
}
@SuppressWarnings("unchecked")
public void testModificationFromListener() throws OperationException {
final int callCounter[] = new int[] { 0 };
// Chicken-and-egg problem: listener needs a reference to m in a final local
// variable declared before it, m's constructor needs listener.
final RawAnnotationSet<Object> m1[] = new RawAnnotationSet[1];
AnnotationSetListener<Object> listener = new AnnotationSetListener<Object>() {
@Override
public void onAnnotationChange(int start, int end, String key, Object newValue) {
switch (callCounter[0]) {
case 0:
assertEquals(1, start);
assertEquals(2, end);
assertEquals("a", key);
assertEquals("1", newValue);
break;
case 1:
assertEquals(2, start);
assertEquals(3, end);
assertEquals("a", key);
assertEquals(null, newValue);
break;
case 2:
assertEquals(0, start);
assertEquals(3, end);
assertEquals("a", key);
assertEquals("1", newValue);
m1[0].begin();
m1[0].startAnnotation("b", "1");
m1[0].skip(2);
m1[0].endAnnotation("b");
m1[0].finish();
break;
case 3:
assertEquals(0, start);
assertEquals(2, end);
assertEquals("b", key);
assertEquals("1", newValue);
break;
default:
fail();
}
callCounter[0]++;
}
};
RawAnnotationSet<Object> m = getNewSet(listener);
m1[0] = m;
m.begin();
m.insert(1);
m.startAnnotation("a", "1");
m.insert(1);
m.startAnnotation("a", null);
m.insert(1);
m.endAnnotation("a");
m.finish();
m.begin();
m.startAnnotation("a", "1");
m.skip(3);
m.endAnnotation("a");
m.finish();
assertEquals(4, callCounter[0]);
}
// The behavior tested here is not currently implemented in
// SimpleAnnotationSet.
public void testDoubleBeginFailsHard() {
RawAnnotationSet<Object> m = getNew();
m.begin();
try {
m.begin();
fail();
} catch (IllegalStateException e) {
// ok
}
}
// The behavior tested here is not currently implemented in
// SimpleAnnotationSet.
public void testUnmatchedFinishFailsHard() {
{
RawAnnotationSet<Object> m = getNew();
try {
m.finish();
fail();
} catch (IllegalStateException e) {
// ok
}
}
{
RawAnnotationSet<Object> m = getNew();
m.begin();
m.finish();
try {
m.finish();
fail();
} catch (IllegalStateException e) {
// ok
}
}
}
public void testConstructorChecksArguments() {
try {
new AnnotationTree<String>(null, "a", null);
fail();
} catch (NullPointerException e) {
// ok
}
try {
new AnnotationTree<String>("a", null, null);
fail();
} catch (NullPointerException e) {
// ok
}
try {
new AnnotationTree<String>("a", "a", null);
fail();
} catch (IllegalArgumentException e) {
// ok
}
try {
new AnnotationTree<String>("b", "b", null);
fail();
} catch (IllegalArgumentException e) {
// ok
}
// Should not throw.
new AnnotationTree<String>("a", "b", null);
}
public void testCleanupKnownKeys() {
AnnotationTree<Object> t = getNewSet(null);
t.begin();
t.startAnnotation("a", "1");
t.insert(10);
t.endAnnotation("a");
t.finish();
assertEquals(1, t.knownKeys().countEntries());
t.begin();
t.startAnnotation("a", null);
t.skip(10);
t.endAnnotation("a");
t.finish();
assertEquals(0, t.knownKeys().countEntries());
t.begin();
t.startAnnotation("a", null);
t.skip(4);
t.endAnnotation("a");
t.finish();
assertEquals(0, t.knownKeys().countEntries());
t.begin();
t.startAnnotation("a", "1");
t.insert(10);
t.startAnnotation("a", "2");
t.skip(10);
t.endAnnotation("a");
t.finish();
assertEquals(1, t.knownKeys().countEntries());
t.begin();
t.skip(2);
t.startAnnotation("b", "1");
t.skip(18);
t.endAnnotation("b");
t.finish();
assertEquals(2, t.knownKeys().countEntries());
t.begin();
t.startAnnotation("b", null);
t.skip(5);
t.endAnnotation("b");
t.delete(15);
t.finish();
assertEquals(1, t.knownKeys().countEntries());
t.begin();
t.delete(2);
t.startAnnotation("a", null);
t.skip(3);
t.endAnnotation("a");
t.finish();
assertEquals(0, t.knownKeys().countEntries());
}
}