001/*-------------------------------------------------------------------------+
002|                                                                          |
003| Copyright 2005-2011 The ConQAT Project                                   |
004|                                                                          |
005| Licensed under the Apache License, Version 2.0 (the "License");          |
006| you may not use this file except in compliance with the License.         |
007| You may obtain a copy of the License at                                  |
008|                                                                          |
009|    http://www.apache.org/licenses/LICENSE-2.0                            |
010|                                                                          |
011| Unless required by applicable law or agreed to in writing, software      |
012| distributed under the License is distributed on an "AS IS" BASIS,        |
013| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
014| See the License for the specific language governing permissions and      |
015| limitations under the License.                                           |
016+-------------------------------------------------------------------------*/
017package org.conqat.lib.commons.string;
018
019import java.util.ArrayList;
020import java.util.List;
021
022import org.conqat.lib.commons.algo.Diff;
023import org.conqat.lib.commons.algo.Diff.Delta;
024import org.conqat.lib.commons.assertion.CCSMAssert;
025
026/**
027 * Base class for an undo stack using a string as the underlying model.
028 * 
029 * Please refer to the test case for a demonstration and further explanation of
030 * this class.
031 * 
032 * @author hummelb
033 */
034public abstract class StringUndoStackBase {
035
036        /** The stack of versions. */
037        private final List<Delta<String>> deltas = new ArrayList<>();
038
039        /** The current string. */
040        private String currentVersion;
041
042        /** The index of the currently used version/delta. */
043        private int currentVersionIndex = -1;
044
045        /** The last position used for saving. */
046        private int savePosition = -1;
047
048        /** Constructor. */
049        protected StringUndoStackBase(String initialString) {
050                currentVersion = initialString;
051        }
052
053        /** Returns whether undo is possible. */
054        public boolean canUndo() {
055                return currentVersionIndex >= 0;
056        }
057
058        /** Performs one undo step. */
059        public void undo() {
060                CCSMAssert.isTrue(canUndo(), "Must be allowed to undo!");
061                currentVersion = join(deltas.get(currentVersionIndex--).backwardPatch(split(currentVersion)));
062                setModelFromString(currentVersion);
063                fireStackChanged();
064        }
065
066        /** Returns whether redo is possible. */
067        public boolean canRedo() {
068                return currentVersionIndex + 1 < deltas.size();
069        }
070
071        /** Performs one redo step. */
072        public void redo() {
073                CCSMAssert.isTrue(canRedo(), "Must be allowed to redo!");
074                currentVersion = join(deltas.get(++currentVersionIndex).forwardPatch(split(currentVersion)));
075                setModelFromString(currentVersion);
076                fireStackChanged();
077        }
078
079        /** Returns whether something changed compared to the last safe. */
080        public boolean isDirty() {
081                return currentVersionIndex != savePosition;
082        }
083
084        /** Mark the current position as saved (affects dirty calculation). */
085        public void doSave() {
086                savePosition = currentVersionIndex;
087        }
088
089        /** Inserts a new version of the model (as a string) into this stack. */
090        protected void insertNewVersion(String s) {
091                ++currentVersionIndex;
092                if (savePosition >= currentVersionIndex) {
093                        savePosition = -1;
094                }
095
096                // discard later versions/deltas
097                while (deltas.size() > currentVersionIndex) {
098                        deltas.remove(deltas.size() - 1);
099                }
100
101                deltas.add(Diff.computeDelta(split(currentVersion), split(s)));
102                currentVersion = s;
103                fireStackChanged();
104        }
105
106        /**
107         * Splits the given string (as reported from the implementing class) into
108         * suitable parts used for diffing (lines, words, tokens, etc.).
109         */
110        protected abstract List<String> split(String s);
111
112        /** Joins the parts created by {@link #split(String)}. */
113        protected abstract String join(List<String> parts);
114
115        /**
116         * This should write back the stack content to the model. This is called for
117         * every undo and redo operation.
118         */
119        protected abstract void setModelFromString(String s);
120
121        /** Something about this stack has changed. */
122        protected abstract void fireStackChanged();
123
124        /** Prints the amount of memory currently used by this stack. */
125        protected int debugGetSize() {
126                int size = 2 * currentVersion.length();
127                for (Delta<String> delta : deltas) {
128                        for (int i = 0; i < delta.getSize(); ++i) {
129                                size += 4 + 2 * delta.getT(i).length();
130                        }
131                }
132                return 2 * size;
133        }
134}