/**
* 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 stack.
*
* TODO(user): This can be heavily optimised.
*
*
* @param <T> The type of operations.
*/
final class UndoStack<T> {
private static final class StackEntry<T> {
final T op;
List<T> nonUndoables = new ArrayList<T>();
StackEntry(T op) {
this.op = op;
}
}
// TODO(user): Switch to a Deque when GWT supports it.
private final Stack<StackEntry<T>> stack = new Stack<StackEntry<T>>();
private final UndoManagerImpl.Algorithms<T> algorithms;
UndoStack(UndoManagerImpl.Algorithms<T> algorithms) {
this.algorithms = algorithms;
}
/**
* Pushes an operation onto the undo stack.
*
* @param op the operation to push onto the undo stack
*/
void push(T op) {
stack.push(new StackEntry<T>(op));
}
/**
* Pops an operation from the undo stack and returns the operation that
* effects the undo and the transformed non-undoable operation.
*
* @return a pair containeng the operation that accomplishes the desired undo
* and the transformed non-undoable operation
*/
Pair<T, T> pop() {
if (stack.isEmpty()) {
return null;
}
StackEntry<T> entry = stack.pop();
T op = algorithms.invert(entry.op);
if (entry.nonUndoables.isEmpty()) {
return new Pair<T, T>(op, null);
}
OperationPair<T> pair;
try {
pair = algorithms.transform(op, algorithms.compose(entry.nonUndoables));
} catch (TransformException e) {
throw new IllegalStateException("invalid operation transformation encountered", e);
}
// TODO(user): Change the ternary expression to just stack.peek() after
// switching from Stack to Deque.
StackEntry<T> nextEntry = stack.isEmpty() ? null : stack.peek();
if (nextEntry != null) {
nextEntry.nonUndoables.add(pair.serverOp());
}
return new Pair<T, T>(pair.clientOp(), pair.serverOp());
}
/**
* Intermingles intervening operations that should not be undone.
*
* @param op the operation that should not be undone
*/
void nonUndoableOperation(T op) {
if (!stack.isEmpty()) {
stack.peek().nonUndoables.add(op);
}
}
/**
* Clear the stack.
*/
void clear() {
stack.clear();
}
}