001 /**
002 * <copyright>
003 *
004 * Copyright (c) 2002-2007 IBM Corporation and others.
005 * All rights reserved. This program and the accompanying materials
006 * are made available under the terms of the Eclipse Public License v1.0
007 * which accompanies this distribution, and is available at
008 * http://www.eclipse.org/legal/epl-v10.html
009 *
010 * Contributors:
011 * IBM - Initial API and implementation
012 *
013 * </copyright>
014 *
015 * $Id: BasicCommandStack.java,v 1.14 2008/05/04 17:03:33 emerks Exp $
016 */
017 package org.eclipse.emf.common.command;
018
019
020 import java.util.ArrayList;
021 import java.util.Collection;
022 import java.util.EventObject;
023 import java.util.Iterator;
024 import java.util.List;
025
026 import org.eclipse.emf.common.CommonPlugin;
027 import org.eclipse.emf.common.util.WrappedException;
028
029
030 /**
031 * A basic and obvious implementation of an undoable stack of commands.
032 * See {@link Command} for more details about the command methods that this implementation uses.
033 */
034 public class BasicCommandStack implements CommandStack
035 {
036 /**
037 * The list of commands.
038 */
039 protected List<Command> commandList;
040
041 /**
042 * The current position within the list from which the next execute, undo, or redo, will be performed.
043 */
044 protected int top;
045
046 /**
047 * The command most recently executed, undone, or redone.
048 */
049 protected Command mostRecentCommand;
050
051 /**
052 * The {@link CommandStackListener}s.
053 */
054 protected Collection<CommandStackListener> listeners;
055
056 /**
057 * The value of {@link #top} when {@link #saveIsDone} is called.
058 */
059 protected int saveIndex = -1;
060
061 /**
062 * Creates a new empty instance.
063 */
064 public BasicCommandStack()
065 {
066 commandList = new ArrayList<Command>();
067 top = -1;
068 listeners = new ArrayList<CommandStackListener>();
069 }
070
071 /*
072 * Javadoc copied from interface.
073 */
074 public void execute(Command command)
075 {
076 // If the command is executable, record and execute it.
077 //
078 if (command != null)
079 {
080 if (command.canExecute())
081 {
082 try
083 {
084 command.execute();
085
086 // Clear the list past the top.
087 //
088 for (Iterator<Command> commands = commandList.listIterator(top + 1); commands.hasNext(); commands.remove())
089 {
090 Command otherCommand = commands.next();
091 otherCommand.dispose();
092 }
093
094 // Record the successfully executed command.
095 //
096 mostRecentCommand = command;
097 commandList.add(command);
098 ++top;
099
100 // This is kind of tricky.
101 // If the saveIndex was in the redo part of the command list which has now been wiped out,
102 // then we can never reach a point where a save is not necessary, not even if we undo all the way back to the beginning.
103 //
104 if (saveIndex >= top)
105 {
106 // This forces isSaveNeded to always be true.
107 //
108 saveIndex = -2;
109 }
110 notifyListeners();
111 }
112 catch (AbortExecutionException exception)
113 {
114 command.dispose();
115 }
116 catch (RuntimeException exception)
117 {
118 handleError(exception);
119 mostRecentCommand = null;
120 command.dispose();
121 notifyListeners();
122 }
123 }
124 else
125 {
126 command.dispose();
127 }
128 }
129 }
130
131 /*
132 * Javadoc copied from interface.
133 */
134 public boolean canUndo()
135 {
136 return top != -1 && commandList.get(top).canUndo();
137 }
138
139 /*
140 * Javadoc copied from interface.
141 */
142 public void undo()
143 {
144 if (canUndo())
145 {
146 Command command = commandList.get(top--);
147 try
148 {
149 command.undo();
150 mostRecentCommand = command;
151 }
152 catch (RuntimeException exception)
153 {
154 handleError(exception);
155
156 mostRecentCommand = null;
157 flush();
158 }
159
160 notifyListeners();
161 }
162 }
163
164 /*
165 * Javadoc copied from interface.
166 */
167 public boolean canRedo()
168 {
169 return top < commandList.size() - 1;
170 }
171
172 /*
173 * Javadoc copied from interface.
174 */
175 public void redo()
176 {
177 if (canRedo())
178 {
179 Command command = commandList.get(++top);
180 try
181 {
182 command.redo();
183 mostRecentCommand = command;
184 }
185 catch (RuntimeException exception)
186 {
187 handleError(exception);
188
189 mostRecentCommand = null;
190
191 // Clear the list past the top.
192 //
193 for (Iterator<Command> commands = commandList.listIterator(top--); commands.hasNext(); commands.remove())
194 {
195 Command otherCommand = commands.next();
196 otherCommand.dispose();
197 }
198 }
199
200 notifyListeners();
201 }
202 }
203
204 /*
205 * Javadoc copied from interface.
206 */
207 public void flush()
208 {
209 // Clear the list.
210 //
211 for (Iterator<Command> commands = commandList.listIterator(); commands.hasNext(); commands.remove())
212 {
213 Command command = commands.next();
214 command.dispose();
215 }
216 commandList.clear();
217 top = -1;
218 saveIndex = -1;
219 notifyListeners();
220 mostRecentCommand = null;
221 }
222
223 /*
224 * Javadoc copied from interface.
225 */
226 public Command getUndoCommand()
227 {
228 return
229 top == -1 || top == commandList.size() ?
230 null :
231 (Command)commandList.get(top);
232 }
233
234 /*
235 * Javadoc copied from interface.
236 */
237 public Command getRedoCommand()
238 {
239 return
240 top + 1 >= commandList.size() ?
241 null :
242 (Command)commandList.get(top + 1);
243 }
244
245 /*
246 * Javadoc copied from interface.
247 */
248 public Command getMostRecentCommand()
249 {
250 return mostRecentCommand;
251 }
252
253 /*
254 * Javadoc copied from interface.
255 */
256 public void addCommandStackListener(CommandStackListener listener)
257 {
258 listeners.add(listener);
259 }
260
261 /*
262 * Javadoc copied from interface.
263 */
264 public void removeCommandStackListener(CommandStackListener listener)
265 {
266 listeners.remove(listener);
267 }
268
269 /**
270 * This is called to ensure that {@link CommandStackListener#commandStackChanged} is called for each listener.
271 */
272 protected void notifyListeners()
273 {
274 for (CommandStackListener commandStackListener : listeners)
275 {
276 commandStackListener.commandStackChanged(new EventObject(this));
277 }
278 }
279
280 /**
281 * Handles an exception thrown during command execution by logging it with the plugin.
282 */
283 protected void handleError(Exception exception)
284 {
285 CommonPlugin.INSTANCE.log
286 (new WrappedException
287 (CommonPlugin.INSTANCE.getString("_UI_IgnoreException_exception"), exception).fillInStackTrace());
288 }
289
290 /**
291 * Called after a save has been successfully performed.
292 */
293 public void saveIsDone()
294 {
295 // Remember where we are now.
296 //
297 saveIndex = top;
298 }
299
300 /**
301 * Returns whether the model has changes since {@link #saveIsDone} was call the last.
302 * @return whether the model has changes since <code>saveIsDone</code> was call the last.
303 */
304 public boolean isSaveNeeded()
305 {
306 // Only if we are at the remembered index do we NOT need to save.
307 //
308 //return top != saveIndex;
309
310 if (saveIndex < -1)
311 {
312 return true;
313 }
314
315 if (top > saveIndex)
316 {
317 for (int i = top; i > saveIndex; --i)
318 {
319 if (!(commandList.get(i) instanceof AbstractCommand.NonDirtying))
320 {
321 return true;
322 }
323 }
324 }
325 else
326 {
327 for (int i = saveIndex; i > top; --i)
328 {
329 if (!(commandList.get(i) instanceof AbstractCommand.NonDirtying))
330 {
331 return true;
332 }
333 }
334 }
335
336 return false;
337 }
338 }