/**
* Copyright 2010 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.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);
}
}