/**
* 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.undo;
import org.waveprotocol.wave.model.operation.OperationPair;
import org.waveprotocol.wave.model.operation.TransformException;
import org.waveprotocol.wave.model.util.Pair;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
/**
* An undo manager implementation.
*
*
* @param <T> The type of operations.
*/
public final class UndoManagerImpl<T> implements UndoManagerPlus<T> {
/**
* Algorithms required by the undo manager.
*
* @param <T> The type of operations.
*/
public interface Algorithms<T> {
/**
* Inverts the given operation.
*
* @param operation The operation to invert.
* @return The inverse of the given operation.
*/
T invert(T operation);
/**
* Composes the given operations.
*
* @param operations The operations to compose.
* @return The composition of the given operations.
*/
T compose(List<T> operations);
/**
* Transforms the given operations.
*
* @param op1 The first concurrent operation.
* @param op2 The second concurrent operation.
* @return The result of transforming the given operations.
* @throws TransformException If there was a problem with the transform.
*/
OperationPair<T> transform(T op1, T op2) throws TransformException;
}
private static final class Checkpointer {
// TODO(user): Switch to a Deque when GWT supports it.
private final Stack<Integer> partitions = new Stack<Integer>();
private int lastPartition = 0;
void checkpoint() {
if (lastPartition > 0) {
partitions.push(lastPartition);
lastPartition = 0;
}
}
int releaseCheckpoint() {
if (lastPartition > 0) {
int value = lastPartition;
lastPartition = 0;
return value;
}
if (partitions.isEmpty()) {
return 0;
}
return partitions.pop();
}
void increment() {
++lastPartition;
}
}
private final Algorithms<T> algorithms;
private final UndoStack<T> undoStack;
private final UndoStack<T> redoStack;
private final Checkpointer checkpointer = new Checkpointer();
public UndoManagerImpl(Algorithms<T> algorithms) {
this.algorithms = algorithms;
undoStack = new UndoStack<T>(algorithms);
redoStack = new UndoStack<T>(algorithms);
}
@Override
public void undoableOp(T op) {
undoStack.push(op);
checkpointer.increment();
redoStack.clear();
}
@Override
public void nonUndoableOp(T op) {
undoStack.nonUndoableOperation(op);
redoStack.nonUndoableOperation(op);
}
@Override
public void checkpoint() {
checkpointer.checkpoint();
}
// TODO(user): This current implementation does more work than necessary.
@Override
public T undo() {
Pair<T, T> undoPlus = undoPlus();
return undoPlus == null ? null : undoPlus.first;
}
// TODO(user): This current implementation does more work than necessary.
@Override
public T redo() {
Pair<T, T> redoPlus = redoPlus();
return redoPlus == null ? null : redoPlus.first;
}
// TODO(user): This current implementation does more work than necessary.
@Override
public Pair<T, T> undoPlus() {
int numToUndo = checkpointer.releaseCheckpoint();
if (numToUndo == 0) {
return null;
}
List<T> operations = new ArrayList<T>();
for (int i = 0; i < numToUndo - 1; ++i) {
operations.add(undoStack.pop().first);
}
Pair<T, T> ops = undoStack.pop();
operations.add(ops.first);
T op = algorithms.compose(operations);
redoStack.push(op);
return new Pair<T, T>(op, ops.second);
}
@Override
public Pair<T, T> redoPlus() {
Pair<T, T> ops = redoStack.pop();
if (ops != null) {
checkpointer.checkpoint();
undoStack.push(ops.first);
checkpointer.increment();
}
return ops;
}
}