/** * 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.robots.passive; import org.waveprotocol.wave.model.document.operation.algorithm.DocOpInverter; import org.waveprotocol.wave.model.operation.wave.AddParticipant; import org.waveprotocol.wave.model.operation.wave.BlipContentOperation; import org.waveprotocol.wave.model.operation.wave.BlipOperation; import org.waveprotocol.wave.model.operation.wave.BlipOperationVisitor; import org.waveprotocol.wave.model.operation.wave.NoOp; import org.waveprotocol.wave.model.operation.wave.RemoveParticipant; import org.waveprotocol.wave.model.operation.wave.SubmitBlip; import org.waveprotocol.wave.model.operation.wave.VersionUpdateOp; import org.waveprotocol.wave.model.operation.wave.WaveletBlipOperation; import org.waveprotocol.wave.model.operation.wave.WaveletOperation; import org.waveprotocol.wave.model.operation.wave.WaveletOperationContext; import org.waveprotocol.wave.model.operation.wave.WaveletOperationVisitor; /** * Approximately inverts an applied wavelet operation. * * The only contract this inverter provides is that: * * <pre class="code">invert(o) ; o == id </pre> * * i.e., given any state {@code s} that was reached by an operation {@code o}, * the "previous" state {@code s'} can be constructed {@code s' = invert(o)(s)} * such that applying {@code o} to it will return to the given state {@code s}. * * This inverter does not accept operations that do not appear in a wavelet's * history (e.g., {@link VersionUpdateOp}). * * TODO(anorth): Move to {@link org.waveprotocol.wave.model.operation.wave}. * * @author hearnden@google.com (David Hearnden) */ final class WaveletOperationInverter implements WaveletOperationVisitor { /** * Inverts a blip operation, ignoring metadata. */ final static class BlipOperationInverter implements BlipOperationVisitor { private final WaveletOperationContext reverseContext; private BlipOperation inverse; BlipOperationInverter(WaveletOperationContext reverseContext) { this.reverseContext = reverseContext; } static BlipOperation invert(WaveletOperationContext reverseContext, BlipOperation op) { return new BlipOperationInverter(reverseContext).visit(op); } private BlipOperation visit(BlipOperation op) { op.acceptVisitor(this); return inverse; } @Override public void visitBlipContentOperation(BlipContentOperation op) { inverse = new BlipContentOperation(reverseContext, DocOpInverter.invert(op.getContentOp())); } @Override public void visitSubmitBlip(SubmitBlip op) { inverse = new SubmitBlip(reverseContext); } } private final WaveletOperationContext reverseContext; private WaveletOperation inverse; WaveletOperationInverter(WaveletOperationContext reverseContext) { this.reverseContext = reverseContext; } /** * Inverts an operation. */ static WaveletOperation invert(WaveletOperation op) { WaveletOperationContext forwardContext = op.getContext(); WaveletOperationContext reverseContext = new WaveletOperationContext( // forwardContext.getCreator(), // Lie, and keep the same modification time. forwardContext.getTimestamp(), // Correctly invert the version increment. -forwardContext.getVersionIncrement(), // Lie again, and report the hashed version as the same as after op. // This makes it out of sync with the version number. forwardContext.getHashedVersion()); return new WaveletOperationInverter(reverseContext).visit(op); } private WaveletOperation visit(WaveletOperation op) { op.acceptVisitor(this); return inverse; } @Override public void visitWaveletBlipOperation(WaveletBlipOperation op) { inverse = new WaveletBlipOperation( op.getBlipId(), BlipOperationInverter.invert(reverseContext, op.getBlipOp())); } @Override public void visitVersionUpdateOp(VersionUpdateOp op) { throw new UnsupportedOperationException(); } @Override public void visitAddParticipant(AddParticipant op) { inverse = new RemoveParticipant(reverseContext, op.getParticipantId()); } @Override public void visitRemoveParticipant(RemoveParticipant op) { inverse = new AddParticipant(reverseContext, op.getParticipantId()); } @Override public void visitNoOp(NoOp op) { inverse = new NoOp(reverseContext); } }