/**
* 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.box.server.common;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import junit.framework.TestCase;
import org.waveprotocol.wave.federation.Proto.ProtocolWaveletDelta;
import org.waveprotocol.wave.model.document.operation.AnnotationBoundaryMap;
import org.waveprotocol.wave.model.document.operation.Attributes;
import org.waveprotocol.wave.model.document.operation.AttributesUpdate;
import org.waveprotocol.wave.model.document.operation.DocOp;
import org.waveprotocol.wave.model.document.operation.impl.AnnotationBoundaryMapImpl;
import org.waveprotocol.wave.model.document.operation.impl.AttributesImpl;
import org.waveprotocol.wave.model.document.operation.impl.AttributesUpdateImpl;
import org.waveprotocol.wave.model.document.operation.impl.DocOpBuilder;
import org.waveprotocol.wave.model.operation.wave.AddParticipant;
import org.waveprotocol.wave.model.operation.wave.BlipContentOperation;
import org.waveprotocol.wave.model.operation.wave.NoOp;
import org.waveprotocol.wave.model.operation.wave.RemoveParticipant;
import org.waveprotocol.wave.model.operation.wave.WaveletBlipOperation;
import org.waveprotocol.wave.model.operation.wave.WaveletDelta;
import org.waveprotocol.wave.model.operation.wave.WaveletOperation;
import org.waveprotocol.wave.model.operation.wave.WaveletOperationContext;
import org.waveprotocol.wave.model.version.HashedVersion;
import org.waveprotocol.wave.model.wave.ParticipantId;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
/**
* Tests {@link CoreWaveletOperationSerializer}.
*
*
*/
public class WaveletOperationSerializerTest extends TestCase {
// A context for ops that don't have one.
private static final WaveletOperationContext OP_CONTEXT =
new WaveletOperationContext(ParticipantId.ofUnsafe("test@example.com"), 0, 1);
private static void assertDeepEquals(WaveletDelta a, WaveletDelta b) {
assertEquals(a.getAuthor(), b.getAuthor());
assertEquals(a.size(), b.size());
Iterator<WaveletOperation> aItr = a.iterator();
Iterator<WaveletOperation> bItr = b.iterator();
while(aItr.hasNext()) {
assertEquals(aItr.next(), bItr.next());
}
}
/**
* Assert that an operation is unchanged when serialised then deserialised.
*
* @param op operation to check
*/
private static void assertReversible(WaveletOperation op) {
// Test both (de)serialising a single operation...
assertEquals(op, CoreWaveletOperationSerializer.deserialize(
CoreWaveletOperationSerializer.serialize(op), OP_CONTEXT));
List<WaveletOperation> ops = ImmutableList.of(op, op, op);
ParticipantId author = new ParticipantId("kalman@google.com");
HashedVersion hashedVersion = HashedVersion.unsigned(0);
WaveletDelta delta = new WaveletDelta(author, hashedVersion, ops);
ProtocolWaveletDelta serialized = CoreWaveletOperationSerializer.serialize(delta);
WaveletDelta deserialized = CoreWaveletOperationSerializer.deserialize(serialized);
assertEquals(hashedVersion.getVersion(), serialized.getHashedVersion().getVersion());
assertTrue(Arrays.equals(hashedVersion.getHistoryHash(),
serialized.getHashedVersion().getHistoryHash().toByteArray()));
assertDeepEquals(delta, deserialized);
}
public void testNoOp() {
assertReversible(new NoOp(OP_CONTEXT));
}
public void testAddParticipant() {
assertReversible(new AddParticipant(OP_CONTEXT, new ParticipantId("kalman@google.com")));
}
public void testRemoveParticipant() {
assertReversible(new RemoveParticipant(OP_CONTEXT, new ParticipantId("kalman@google.com")));
}
public void testEmptyDocumentMutation() {
assertReversible(makeBlipOp("empty", new DocOpBuilder().build()));
}
public void testSingleCharacters() {
DocOpBuilder m = new DocOpBuilder();
m.characters("hello");
assertReversible(makeBlipOp("single", m.build()));
}
public void testManyCharacters() {
DocOpBuilder m = new DocOpBuilder();
m.characters("hello");
m.characters("world");
m.characters("foo");
m.characters("bar");
assertReversible(makeBlipOp("many", m.build()));
}
public void testRetain() {
DocOpBuilder m = new DocOpBuilder();
m.characters("hello");
m.retain(5);
m.characters("world");
m.retain(10);
m.characters("foo");
m.retain(13);
m.characters("bar");
m.retain(16);
assertReversible(makeBlipOp("retain", m.build()));
}
public void testDeleteCharacters() {
DocOpBuilder m = new DocOpBuilder();
m.characters("hello");
m.retain(1);
m.deleteCharacters("ab");
m.characters("world");
m.retain(2);
m.deleteCharacters("cd");
assertReversible(makeBlipOp("deleteCharacters", m.build()));
}
public void testElements() {
DocOpBuilder m = new DocOpBuilder();
Attributes a = new AttributesImpl(ImmutableMap.of("a1", "1", "a2", "2"));
Attributes b = new AttributesImpl();
Attributes c = new AttributesImpl(ImmutableMap.of("c1", "1", "c2", "2", "c3", "3"));
m.elementStart("a", a);
m.elementStart("b", b);
m.elementStart("c", c);
m.elementEnd();
m.elementEnd();
m.elementEnd();
assertReversible(makeBlipOp("elements", m.build()));
}
public void testCharactersAndElements() {
DocOpBuilder m = new DocOpBuilder();
Attributes a = new AttributesImpl(ImmutableMap.of("a1", "1", "a2", "2"));
Attributes b = new AttributesImpl();
Attributes c = new AttributesImpl(ImmutableMap.of("c1", "1", "c2", "2", "c3", "3"));
m.elementStart("a", a);
m.characters("hello");
m.elementStart("b", b);
m.characters("world");
m.elementStart("c", c);
m.elementEnd();
m.characters("blah");
m.elementEnd();
m.elementEnd();
assertReversible(makeBlipOp("charactersAndElements", m.build()));
}
public void testDeleteElements() {
DocOpBuilder m = new DocOpBuilder();
Attributes a = new AttributesImpl(ImmutableMap.of("a1", "1", "a2", "2"));
Attributes b = new AttributesImpl();
Attributes c = new AttributesImpl(ImmutableMap.of("c1", "1", "c2", "2", "c3", "3"));
m.deleteElementStart("a", a);
m.deleteElementStart("b", b);
m.deleteElementStart("c", c);
m.deleteElementEnd();
m.deleteElementEnd();
m.deleteElementEnd();
assertReversible(makeBlipOp("deleteElements", m.build()));
}
public void testDeleteCharactersAndElements() {
DocOpBuilder m = new DocOpBuilder();
Attributes a = new AttributesImpl(ImmutableMap.of("a1", "1", "a2", "2"));
Attributes b = new AttributesImpl();
Attributes c = new AttributesImpl(ImmutableMap.of("c1", "1", "c2", "2", "c3", "3"));
m.deleteElementStart("a", a);
m.deleteCharacters("hello");
m.deleteElementStart("b", b);
m.deleteCharacters("world");
m.deleteElementStart("c", c);
m.deleteElementEnd();
m.deleteCharacters("blah");
m.deleteElementEnd();
m.deleteElementEnd();
assertReversible(makeBlipOp("deleteCharactersAndElements", m.build()));
}
public void testAnnotationBoundary() {
DocOpBuilder m = new DocOpBuilder();
Attributes a = new AttributesImpl(ImmutableMap.of("a1", "1", "a2", "2"));
AnnotationBoundaryMap mapA = new AnnotationBoundaryMapImpl(
new String[]{},new String[]{"a"},new String[]{null},new String[]{"b"});
AnnotationBoundaryMap mapB = new AnnotationBoundaryMapImpl(
new String[]{},new String[]{"a"},new String[]{"b"},new String[]{null});
AnnotationBoundaryMap mapC = new AnnotationBoundaryMapImpl(
new String[]{"a"},new String[]{},new String[]{},new String[]{});
m.elementStart("a", a);
m.annotationBoundary(mapA);
m.characters("test");
m.annotationBoundary(mapB);
m.characters("text");
m.annotationBoundary(mapC);
m.elementEnd();
assertReversible(makeBlipOp("annotationBoundary", m.build()));
}
public void testEmptyAnnotationBoundary() {
DocOpBuilder m = new DocOpBuilder();
Attributes a = new AttributesImpl(ImmutableMap.of("a1", "1", "a2", "2"));
m.elementStart("a", a);
m.annotationBoundary(AnnotationBoundaryMapImpl.EMPTY_MAP);
m.characters("text");
m.annotationBoundary(AnnotationBoundaryMapImpl.EMPTY_MAP);
m.elementEnd();
assertReversible(makeBlipOp("emptyAnnotationBoundary", m.build()));
}
public void testReplaceAttributes() {
DocOpBuilder m = new DocOpBuilder();
Attributes oldA = new AttributesImpl(ImmutableMap.of("a1", "1", "a2", "2"));
Attributes newA = new AttributesImpl(ImmutableMap.of("a1", "3", "a2", "4"));
m.retain(4);
m.replaceAttributes(oldA, newA);
m.retain(4);
assertReversible(makeBlipOp("replaceAttributes", m.build()));
}
public void testEmptyReplaceAttributes() {
DocOpBuilder m = new DocOpBuilder();
m.retain(4);
m.replaceAttributes(AttributesImpl.EMPTY_MAP, AttributesImpl.EMPTY_MAP);
m.retain(4);
assertReversible(makeBlipOp("emptyReplaceAttributes", m.build()));
}
public void testUpdateAttributes() {
DocOpBuilder m = new DocOpBuilder();
AttributesUpdate u = new AttributesUpdateImpl(new String[]{"a", null, "2", "b", "1", null});
m.retain(4);
m.updateAttributes(u);
m.retain(4);
assertReversible(makeBlipOp("updateAttributes", m.build()));
}
public void testEmptyUpdateAttributes() {
DocOpBuilder m = new DocOpBuilder();
m.retain(4);
m.updateAttributes(AttributesUpdateImpl.EMPTY_MAP);
m.retain(4);
assertReversible(makeBlipOp("emptyUpdateAttributes", m.build()));
}
private static WaveletBlipOperation makeBlipOp(String blipId, DocOp mutation) {
return new WaveletBlipOperation(blipId, new BlipContentOperation(OP_CONTEXT, mutation));
}
}