/** * 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.examples.fedone.common; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.protobuf.ByteString; import org.waveprotocol.wave.federation.Proto.ProtocolDocumentOperation; import org.waveprotocol.wave.federation.Proto.ProtocolHashedVersion; import org.waveprotocol.wave.federation.Proto.ProtocolWaveletDelta; import org.waveprotocol.wave.federation.Proto.ProtocolWaveletOperation; 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.BufferedDocOp; import org.waveprotocol.wave.model.document.operation.DocOp; import org.waveprotocol.wave.model.document.operation.DocOpCursor; 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.NoOp; import org.waveprotocol.wave.model.operation.wave.RemoveParticipant; import org.waveprotocol.wave.model.operation.wave.WaveletDelta; import org.waveprotocol.wave.model.operation.wave.WaveletDocumentOperation; import org.waveprotocol.wave.model.operation.wave.WaveletOperation; import org.waveprotocol.wave.model.util.Pair; import org.waveprotocol.wave.model.wave.ParticipantId; import org.waveprotocol.wave.examples.fedone.waveserver.WaveClientRpc.WaveletSnapshot; import org.waveprotocol.wave.examples.fedone.waveserver.WaveClientRpc.WaveletSnapshot.DocumentSnapshot; import java.util.List; import java.util.Map; /** * Utility class for serialising/deserialising wavelet operations (and their components) to/from * their protocol buffer representations (and their components). * * */ public class WaveletOperationSerializer { private WaveletOperationSerializer() { } /** * Serialize a {@link WaveletDelta} as a {@link ProtocolWaveletDelta} at a * specific version. * * @param waveletDelta to serialize * @param version version at which the delta applies * @return serialized protocol buffer wavelet delta */ public static ProtocolWaveletDelta serialize(WaveletDelta waveletDelta, HashedVersion version) { ProtocolWaveletDelta.Builder protobufDelta = ProtocolWaveletDelta.newBuilder(); for (WaveletOperation waveletOp : waveletDelta.getOperations()) { protobufDelta.addOperation(serialize(waveletOp)); } protobufDelta.setAuthor(waveletDelta.getAuthor().getAddress()); protobufDelta.setHashedVersion(serialize(version)); return protobufDelta.build(); } /** * Serialize a {@link WaveletOperation} as a {@link ProtocolWaveletOperation}. * * @param waveletOp wavelet operation to serialize * @return serialized protocol buffer wavelet operation */ public static ProtocolWaveletOperation serialize(WaveletOperation waveletOp) { ProtocolWaveletOperation.Builder protobufOp = ProtocolWaveletOperation.newBuilder(); if (waveletOp instanceof NoOp) { protobufOp.setNoOp(true); } else if (waveletOp instanceof AddParticipant) { protobufOp.setAddParticipant( ((AddParticipant) waveletOp).getParticipantId().getAddress()); } else if (waveletOp instanceof RemoveParticipant) { protobufOp.setRemoveParticipant( ((RemoveParticipant) waveletOp).getParticipantId().getAddress()); } else if (waveletOp instanceof WaveletDocumentOperation) { ProtocolWaveletOperation.MutateDocument.Builder mutation = ProtocolWaveletOperation.MutateDocument.newBuilder(); mutation.setDocumentId(((WaveletDocumentOperation) waveletOp).getDocumentId()); mutation.setDocumentOperation( serialize(((WaveletDocumentOperation) waveletOp).getOperation())); protobufOp.setMutateDocument(mutation.build()); } else { throw new IllegalArgumentException("Unsupported operation type: " + waveletOp); } return protobufOp.build(); } /** * Serialize a {@link DocOp} as a {@link ProtocolDocumentOperation}. * * @param inputOp document operation to serialize * @return serialized protocol buffer document operation */ public static ProtocolDocumentOperation serialize(DocOp inputOp) { final ProtocolDocumentOperation.Builder output = ProtocolDocumentOperation.newBuilder(); inputOp.apply(new DocOpCursor() { private ProtocolDocumentOperation.Component.Builder newComponentBuilder() { return ProtocolDocumentOperation.Component.newBuilder(); } @Override public void retain(int itemCount) { output.addComponent(newComponentBuilder().setRetainItemCount(itemCount).build()); } @Override public void characters(String characters) { output.addComponent(newComponentBuilder().setCharacters(characters).build()); } @Override public void deleteCharacters(String characters) { output.addComponent(newComponentBuilder().setDeleteCharacters(characters).build()); } @Override public void elementStart(String type, Attributes attributes) { ProtocolDocumentOperation.Component.ElementStart e = makeElementStart(type, attributes); output.addComponent(newComponentBuilder().setElementStart(e).build()); } @Override public void deleteElementStart(String type, Attributes attributes) { ProtocolDocumentOperation.Component.ElementStart e = makeElementStart(type, attributes); output.addComponent(newComponentBuilder().setDeleteElementStart(e).build()); } private ProtocolDocumentOperation.Component.ElementStart makeElementStart( String type, Attributes attributes) { ProtocolDocumentOperation.Component.ElementStart.Builder e = ProtocolDocumentOperation.Component.ElementStart.newBuilder(); e.setType(type); for (String name : attributes.keySet()) { e.addAttribute(ProtocolDocumentOperation.Component.KeyValuePair.newBuilder() .setKey(name).setValue(attributes.get(name)).build()); } return e.build(); } @Override public void elementEnd() { output.addComponent(newComponentBuilder().setElementEnd(true).build()); } @Override public void deleteElementEnd() { output.addComponent(newComponentBuilder().setDeleteElementEnd(true).build()); } @Override public void replaceAttributes(Attributes oldAttributes, Attributes newAttributes) { ProtocolDocumentOperation.Component.ReplaceAttributes.Builder r = ProtocolDocumentOperation.Component.ReplaceAttributes.newBuilder(); if (oldAttributes.isEmpty() && newAttributes.isEmpty()) { r.setEmpty(true); } else { for (String name : oldAttributes.keySet()) { r.addOldAttribute(ProtocolDocumentOperation.Component.KeyValuePair.newBuilder() .setKey(name).setValue(oldAttributes.get(name)).build()); } for (String name : newAttributes.keySet()) { r.addNewAttribute(ProtocolDocumentOperation.Component.KeyValuePair.newBuilder() .setKey(name).setValue(newAttributes.get(name)).build()); } } output.addComponent(newComponentBuilder().setReplaceAttributes(r.build()).build()); } @Override public void updateAttributes(AttributesUpdate attributes) { ProtocolDocumentOperation.Component.UpdateAttributes.Builder u = ProtocolDocumentOperation.Component.UpdateAttributes.newBuilder(); if (attributes.changeSize() == 0) { u.setEmpty(true); } else { for (int i = 0; i < attributes.changeSize(); i++) { u.addAttributeUpdate(makeKeyValueUpdate( attributes.getChangeKey(i), attributes.getOldValue(i), attributes.getNewValue(i))); } } output.addComponent(newComponentBuilder().setUpdateAttributes(u.build()).build()); } @Override public void annotationBoundary(AnnotationBoundaryMap map) { ProtocolDocumentOperation.Component.AnnotationBoundary.Builder a = ProtocolDocumentOperation.Component.AnnotationBoundary.newBuilder(); if (map.endSize() == 0 && map.changeSize() == 0) { a.setEmpty(true); } else { for (int i = 0; i < map.endSize(); i++) { a.addEnd(map.getEndKey(i)); } for (int i = 0; i < map.changeSize(); i++) { a.addChange(makeKeyValueUpdate( map.getChangeKey(i), map.getOldValue(i), map.getNewValue(i))); } } output.addComponent(newComponentBuilder().setAnnotationBoundary(a.build()).build()); } private ProtocolDocumentOperation.Component.KeyValueUpdate makeKeyValueUpdate( String key, String oldValue, String newValue) { ProtocolDocumentOperation.Component.KeyValueUpdate.Builder kvu = ProtocolDocumentOperation.Component.KeyValueUpdate.newBuilder(); kvu.setKey(key); if (oldValue != null) { kvu.setOldValue(oldValue); } if (newValue != null) { kvu.setNewValue(newValue); } return kvu.build(); } }); return output.build(); } /** * Deserializes a {@link ProtocolWaveletDelta} as a {@link WaveletDelta} and * {@link HashedVersion}. * * @param delta protocol buffer wavelet delta to deserialize * @return deserialized wavelet delta and version */ public static Pair<WaveletDelta, HashedVersion> deserialize(ProtocolWaveletDelta delta) { List<WaveletOperation> ops = Lists.newArrayList(); for (ProtocolWaveletOperation op : delta.getOperationList()) { ops.add(deserialize(op)); } HashedVersion hashedVersion = deserialize(delta.getHashedVersion()); return Pair.of(new WaveletDelta(new ParticipantId(delta.getAuthor()), ops), hashedVersion); } /** Deserializes a protobuf to a HashedVersion POJO. */ public static HashedVersion deserialize(ProtocolHashedVersion hashedVersion) { return new HashedVersion(hashedVersion.getVersion(), hashedVersion.getHistoryHash().toByteArray()); } /** Serializes a HashedVersion POJO to a protobuf. */ public static ProtocolHashedVersion serialize(HashedVersion hashedVersion) { return ProtocolHashedVersion.newBuilder().setVersion(hashedVersion.getVersion()). setHistoryHash(ByteString.copyFrom(hashedVersion.getHistoryHash())).build(); } /** * Deserialize a {@link ProtocolWaveletOperation} as a {@link WaveletOperation}. * * @param protobufOp protocol buffer wavelet operation to deserialize * @return deserialized wavelet operation */ public static WaveletOperation deserialize(ProtocolWaveletOperation protobufOp) { if (protobufOp.hasNoOp()) { return NoOp.INSTANCE; } else if (protobufOp.hasAddParticipant()) { return new AddParticipant(new ParticipantId(protobufOp.getAddParticipant())); } else if (protobufOp.hasRemoveParticipant()) { return new RemoveParticipant(new ParticipantId(protobufOp.getRemoveParticipant())); } else if (protobufOp.hasMutateDocument()) { return new WaveletDocumentOperation( protobufOp.getMutateDocument().getDocumentId(), deserialize(protobufOp.getMutateDocument().getDocumentOperation())); } else { throw new IllegalArgumentException("Unsupported operation: " + protobufOp); } } /** * Deserialize a {@link WaveletSnapshot} into a list of {@link WaveletOperation}s. * * @param snapshot snapshot protocol buffer to deserialize * @return a list of operations */ public static List<WaveletOperation> deserialize(WaveletSnapshot snapshot) { List<WaveletOperation> ops = Lists.newArrayList(); for (String participant : snapshot.getParticipantIdList()) { AddParticipant addOp = new AddParticipant(new ParticipantId(participant)); ops.add(addOp); } for (DocumentSnapshot document : snapshot.getDocumentList()) { WaveletDocumentOperation docOp = new WaveletDocumentOperation(document.getDocumentId(), deserialize(document.getDocumentOperation())); ops.add(docOp); } return ops; } /** * Deserialize a {@link ProtocolDocumentOperation} into a {@link DocOp}. * * @param op protocol buffer document operation to deserialize * @return deserialized DocOp */ public static BufferedDocOp deserialize(ProtocolDocumentOperation op) { DocOpBuilder output = new DocOpBuilder(); for (ProtocolDocumentOperation.Component c : op.getComponentList()) { if (c.hasAnnotationBoundary()) { if (c.getAnnotationBoundary().getEmpty()) { output.annotationBoundary(AnnotationBoundaryMapImpl.EMPTY_MAP); } else { String[] ends = new String[c.getAnnotationBoundary().getEndCount()]; String[] changeKeys = new String[c.getAnnotationBoundary().getChangeCount()]; String[] oldValues = new String[c.getAnnotationBoundary().getChangeCount()]; String[] newValues = new String[c.getAnnotationBoundary().getChangeCount()]; if (c.getAnnotationBoundary().getEndCount() > 0) { c.getAnnotationBoundary().getEndList().toArray(ends); } for (int i = 0; i < changeKeys.length; i++) { ProtocolDocumentOperation.Component.KeyValueUpdate kvu = c.getAnnotationBoundary().getChange(i); changeKeys[i] = kvu.getKey(); oldValues[i] = kvu.hasOldValue() ? kvu.getOldValue() : null; newValues[i] = kvu.hasNewValue() ? kvu.getNewValue() : null; } output.annotationBoundary( new AnnotationBoundaryMapImpl(ends, changeKeys, oldValues, newValues)); } } else if (c.hasCharacters()) { output.characters(c.getCharacters()); } else if (c.hasElementStart()) { Map<String, String> attributesMap = Maps.newHashMap(); for (ProtocolDocumentOperation.Component.KeyValuePair pair : c.getElementStart().getAttributeList()) { attributesMap.put(pair.getKey(), pair.getValue()); } output.elementStart(c.getElementStart().getType(), new AttributesImpl(attributesMap)); } else if (c.hasElementEnd()) { output.elementEnd(); } else if (c.hasRetainItemCount()) { output.retain(c.getRetainItemCount()); } else if (c.hasDeleteCharacters()) { output.deleteCharacters(c.getDeleteCharacters()); } else if (c.hasDeleteElementStart()) { Map<String, String> attributesMap = Maps.newHashMap(); for (ProtocolDocumentOperation.Component.KeyValuePair pair : c.getDeleteElementStart().getAttributeList()) { attributesMap.put(pair.getKey(), pair.getValue()); } output.deleteElementStart(c.getDeleteElementStart().getType(), new AttributesImpl(attributesMap)); } else if (c.hasDeleteElementEnd()) { output.deleteElementEnd(); } else if (c.hasReplaceAttributes()) { if (c.getReplaceAttributes().getEmpty()) { output.replaceAttributes(AttributesImpl.EMPTY_MAP, AttributesImpl.EMPTY_MAP); } else { Map<String, String> oldAttributesMap = Maps.newHashMap(); Map<String, String> newAttributesMap = Maps.newHashMap(); for (ProtocolDocumentOperation.Component.KeyValuePair pair : c.getReplaceAttributes().getOldAttributeList()) { oldAttributesMap.put(pair.getKey(), pair.getValue()); } for (ProtocolDocumentOperation.Component.KeyValuePair pair : c.getReplaceAttributes().getNewAttributeList()) { newAttributesMap.put(pair.getKey(), pair.getValue()); } output.replaceAttributes(new AttributesImpl(oldAttributesMap), new AttributesImpl(newAttributesMap)); } } else if (c.hasUpdateAttributes()) { if (c.getUpdateAttributes().getEmpty()) { output.updateAttributes(AttributesUpdateImpl.EMPTY_MAP); } else { String[] triplets = new String[c.getUpdateAttributes().getAttributeUpdateCount()*3]; for (int i = 0, j = 0; i < c.getUpdateAttributes().getAttributeUpdateCount(); i++) { ProtocolDocumentOperation.Component.KeyValueUpdate kvu = c.getUpdateAttributes().getAttributeUpdate(i); triplets[j++] = kvu.getKey(); triplets[j++] = kvu.hasOldValue() ? kvu.getOldValue() : null; triplets[j++] = kvu.hasNewValue() ? kvu.getNewValue() : null; } output.updateAttributes(new AttributesUpdateImpl(triplets)); } } else { //throw new IllegalArgumentException("Unsupported operation component: " + c); } } return output.build(); } }