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}