/** * 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; } }