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    }