|
|||||||||||||||||||
| Source file | Conditionals | Statements | Methods | TOTAL | |||||||||||||||
| CompoundUndoManager.java | 77.8% | 87.9% | 78.9% | 84.2% |
|
||||||||||||||
| 1 | /*BEGIN_COPYRIGHT_BLOCK | |
| 2 | * | |
| 3 | * Copyright (c) 2001-2010, JavaPLT group at Rice University (drjava@rice.edu) | |
| 4 | * All rights reserved. | |
| 5 | * | |
| 6 | * Redistribution and use in source and binary forms, with or without | |
| 7 | * modification, are permitted provided that the following conditions are met: | |
| 8 | * * Redistributions of source code must retain the above copyright | |
| 9 | * notice, this list of conditions and the following disclaimer. | |
| 10 | * * Redistributions in binary form must reproduce the above copyright | |
| 11 | * notice, this list of conditions and the following disclaimer in the | |
| 12 | * documentation and/or other materials provided with the distribution. | |
| 13 | * * Neither the names of DrJava, the JavaPLT group, Rice University, nor the | |
| 14 | * names of its contributors may be used to endorse or promote products | |
| 15 | * derived from this software without specific prior written permission. | |
| 16 | * | |
| 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| 18 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| 19 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| 20 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR | |
| 21 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, | |
| 22 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, | |
| 23 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR | |
| 24 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF | |
| 25 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING | |
| 26 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | |
| 27 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| 28 | * | |
| 29 | * This software is Open Source Initiative approved Open Source Software. | |
| 30 | * Open Source Initative Approved is a trademark of the Open Source Initiative. | |
| 31 | * | |
| 32 | * This file is part of DrJava. Download the current version of this project | |
| 33 | * from http://www.drjava.org/ or http://sourceforge.net/projects/drjava/ | |
| 34 | * | |
| 35 | * END_COPYRIGHT_BLOCK*/ | |
| 36 | ||
| 37 | package edu.rice.cs.drjava.model.definitions; | |
| 38 | ||
| 39 | import java.awt.EventQueue; | |
| 40 | import java.util.LinkedList; | |
| 41 | import javax.swing.undo.*; | |
| 42 | ||
| 43 | import edu.rice.cs.drjava.model.GlobalEventNotifier; | |
| 44 | ||
| 45 | /** Extended UndoManager with increased functionality. Can handle aggregating multiple edits into one for the purposes | |
| 46 | * of undoing and redoing. It exposes editToBeUndone and editToBeRedone (under new names); they are protected methods | |
| 47 | * in UndoManager. The public methods that involve composite state are synchronized, so this manager can be accessed | |
| 48 | * outside of the event thread. The internal data structures _compoundEdits and _keys are not thread safe but they | |
| 49 | * only accessed only by synchronized methods. The synchronization scheme (locking on this) follows UndoManager. | |
| 50 | * @version $Id: CompoundUndoManager.java 5361 2010-08-13 22:39:45Z mgricken $ | |
| 51 | */ | |
| 52 | public class CompoundUndoManager extends UndoManager { | |
| 53 | ||
| 54 | static edu.rice.cs.util.Log LOG = new edu.rice.cs.util.Log("CompoundUndoManager.txt", false); | |
| 55 | ||
| 56 | private static volatile int counter = 0; | |
| 57 | ||
| 58 | private final int id; | |
| 59 | ||
| 60 | /** The compound edits we are storing. Not thread safe! */ | |
| 61 | private final LinkedList<CompoundEdit> _compoundEdits; | |
| 62 | ||
| 63 | /** The keys for the CompoundEdits we are storing. */ | |
| 64 | private final LinkedList<Integer> _keys; | |
| 65 | ||
| 66 | /** The next key to use for nested CompoundEdits. */ | |
| 67 | private volatile int _nextKey; | |
| 68 | ||
| 69 | /** The last edit that was performed before the last save. */ | |
| 70 | private volatile UndoableEdit _savePoint; | |
| 71 | ||
| 72 | /** Keeps track of the listeners to this undo manager. */ | |
| 73 | private final GlobalEventNotifier _notifier; | |
| 74 | ||
| 75 | /** Standard constructor. */ | |
| 76 | 1086 | public CompoundUndoManager(GlobalEventNotifier notifier) { |
| 77 | 1086 | super(); |
| 78 | 1086 | counter++; |
| 79 | 1086 | id = counter; |
| 80 | 1086 | _compoundEdits = new LinkedList<CompoundEdit>(); |
| 81 | 1086 | _keys = new LinkedList<Integer>(); |
| 82 | 1086 | _nextKey = 0; |
| 83 | 1086 | _savePoint = null; |
| 84 | 1086 | _notifier = notifier; |
| 85 | } | |
| 86 | ||
| 87 | /** Starts a compound edit. | |
| 88 | * @return the key for the compound edit | |
| 89 | */ | |
| 90 | 56 | public /* synchronized */ int startCompoundEdit() { |
| 91 | 56 | _compoundEdits.add(0, new CompoundEdit()); |
| 92 | 56 | _keys.add(0, Integer.valueOf(_nextKey)); |
| 93 | 56 | if (_nextKey < Integer.MAX_VALUE) _nextKey++; |
| 94 | 0 | else _nextKey = Integer.MIN_VALUE; |
| 95 | 56 | return _keys.get(0).intValue(); |
| 96 | } | |
| 97 | ||
| 98 | /** Ends the last compound edit that was created. Used when a compound edit is created by the _undoListener in | |
| 99 | * DefinitionsPane and the key is not known in DefinitionsDocument. | |
| 100 | */ | |
| 101 | 77 | public /* synchronized */ void endLastCompoundEdit() { |
| 102 | 73 | if (_keys.size() == 0) return; |
| 103 | // NOTE: The preceding can happen if for example uncomment lines does not modify any text. | |
| 104 | 4 | endCompoundEdit(_keys.get(0).intValue()); |
| 105 | } | |
| 106 | ||
| 107 | /** Ends a compound edit. | |
| 108 | * @param key the key that was returned by startCompoundEdit() | |
| 109 | */ | |
| 110 | 29 | public /* synchronized */ void endCompoundEdit(int key) { |
| 111 | 2 | if (_keys.size() == 0) return; |
| 112 | ||
| 113 | 27 | if (_keys.get(0) == key) { |
| 114 | 27 | _keys.remove(0); |
| 115 | 27 | final CompoundEdit ce = _compoundEdits.remove(0); |
| 116 | ||
| 117 | 27 | ce.end(); |
| 118 | 27 | if (ce.canUndo()) { |
| 119 | 27 | if (! _compoundEditInProgress()) { |
| 120 | 27 | super.addEdit(ce); |
| 121 | 27 | _notifyUndoHappened(); |
| 122 | } | |
| 123 | else { | |
| 124 | 0 | _compoundEdits.get(0).addEdit(ce); |
| 125 | } | |
| 126 | } | |
| 127 | } | |
| 128 | 0 | else throw new IllegalStateException("Improperly nested compound edits."); |
| 129 | } | |
| 130 | ||
| 131 | /** Gets the last Compound Edit entered into the list. Used in making a Compound edit for granular undo. */ | |
| 132 | 0 | public /* synchronized */ CompoundEdit getLastCompoundEdit() { return _compoundEdits.get(0); } |
| 133 | ||
| 134 | /** Gets the next undo. | |
| 135 | * @return the next undo | |
| 136 | */ | |
| 137 | 0 | public UndoableEdit getNextUndo() { return editToBeUndone(); } |
| 138 | ||
| 139 | /** Gets the next redo. | |
| 140 | * @return the next redo | |
| 141 | */ | |
| 142 | 0 | public UndoableEdit getNextRedo() { return editToBeRedone(); } |
| 143 | ||
| 144 | /** Adds an edit. Checks whether or not the current edit is a compound edit. | |
| 145 | * @param e the edit to be added | |
| 146 | * @return true if the add is successful, false otherwise | |
| 147 | */ | |
| 148 | 303 | public /* synchronized */ boolean addEdit(UndoableEdit e) { |
| 149 | 303 | if (_compoundEditInProgress()) { |
| 150 | // _notifyUndoHappened(); // added this for granular undo | |
| 151 | 302 | return _compoundEdits.get(0).addEdit(e); |
| 152 | } | |
| 153 | else { | |
| 154 | 1 | boolean result = super.addEdit(e); |
| 155 | 1 | _notifyUndoHappened(); |
| 156 | 1 | return result; |
| 157 | } | |
| 158 | } | |
| 159 | ||
| 160 | /** Returns whether or not a compound edit is in progress. | |
| 161 | * @return true iff in progress | |
| 162 | */ | |
| 163 | 1683 | public /* synchronized */ boolean _compoundEditInProgress() { return ! _compoundEdits.isEmpty(); } |
| 164 | ||
| 165 | /** Returns true when a compound edit is in progress, or when there are valid stored undoable edits | |
| 166 | * @return true iff undoing is possible | |
| 167 | */ | |
| 168 | 420 | public /* synchronized */ boolean canUndo() { |
| 169 | 420 | LOG.log("canUndo: _compoundEditInProgress() = "+_compoundEditInProgress()+", super.canUndo() = "+super.canUndo()); |
| 170 | 420 | LOG.log(" "+_compoundEdits); |
| 171 | 420 | return _compoundEditInProgress() || super.canUndo(); } |
| 172 | ||
| 173 | /** Returns the presentation name for this undo, or delegates to super if none is available | |
| 174 | * @return the undo's presentation name | |
| 175 | */ | |
| 176 | 68 | public /* synchronized */ String getUndoPresentationName() { |
| 177 | 59 | if (_compoundEditInProgress()) return "Undo Previous Command"; |
| 178 | 9 | return super.getUndoPresentationName(); |
| 179 | } | |
| 180 | ||
| 181 | /** Undoes the last undoable edit, or compound edit created by the user. */ | |
| 182 | 9 | public /* synchronized */ void undo() { |
| 183 | 9 | endCompoundEdit(); |
| 184 | 9 | super.undo(); |
| 185 | } | |
| 186 | ||
| 187 | // Not currently used. | |
| 188 | // /** Overload for undo which allows the initiator of a CompoundEdit to abandon it. | |
| 189 | // * WARNING: this has been used to date and has not been properly tested and very possibly may not work. | |
| 190 | // * @param key the key returned by the last call to startCompoundEdit | |
| 191 | // * @throws IllegalArgumentException if the key is incorrect | |
| 192 | // */ | |
| 193 | // public synchronized void undo(int key) { | |
| 194 | // if (_keys.get(0) == key) { | |
| 195 | // final CompoundEdit ce = _compoundEdits.get(0); | |
| 196 | // _compoundEdits.remove(0); | |
| 197 | // _keys.remove(0); | |
| 198 | // | |
| 199 | // EventQueue.invokeLater(new Runnable() { | |
| 200 | // public void run() { | |
| 201 | // ce.end(); | |
| 202 | // ce.undo(); | |
| 203 | // ce.die(); | |
| 204 | // } | |
| 205 | // }); // unsafe methods inherited from CompoundEdit | |
| 206 | // } | |
| 207 | // else throw new IllegalArgumentException("Bad undo key " + key + "!"); | |
| 208 | // } | |
| 209 | ||
| 210 | /** Overrides redo so that any compound edit in progress is ended before the redo is performed. */ | |
| 211 | 4 | public /* synchronized */ void redo() { |
| 212 | 4 | endCompoundEdit(); |
| 213 | 4 | super.redo(); |
| 214 | } | |
| 215 | ||
| 216 | /** Helper method to notify the view that an undoable edit has occured. Note that lock on this is not held by | |
| 217 | * the event thread (even if called from event thread) when notification happens. | |
| 218 | */ | |
| 219 | 28 | private void _notifyUndoHappened() { |
| 220 | // Use SwingUtilities.invokeLater so that notification is deferred when running in the event thread. | |
| 221 | 28 | EventQueue.invokeLater(new Runnable() { public void run() { _notifier.undoableEditHappened(); } }); |
| 222 | } | |
| 223 | ||
| 224 | /** Ends the compoundEdit in progress if any. Used by undo(), redo(), documentSaved(). */ | |
| 225 | 445 | private /* synchronized */ void endCompoundEdit() { |
| 226 | 445 | Integer[] keys = _keys.toArray(new Integer[_keys.size()]); // unit testing ran into a concurrent modification exception without this copying operation |
| 227 | 445 | if (_compoundEditInProgress()) { |
| 228 | 2 | for (int key: keys) endCompoundEdit(key); |
| 229 | } | |
| 230 | } | |
| 231 | ||
| 232 | /** Informs this undo manager that the document has been saved. */ | |
| 233 | 432 | public /* synchronized */ void documentSaved() { |
| 234 | 432 | endCompoundEdit(); |
| 235 | 432 | _savePoint = editToBeUndone(); |
| 236 | // Utilities.showDebug("_savePoint := " + _savePoint); | |
| 237 | } | |
| 238 | ||
| 239 | /** Determines if the document is in the same undo state as it was when it was last saved. | |
| 240 | * @return true iff all changes have been undone since the last save | |
| 241 | */ | |
| 242 | 1 | public /* synchronized */ boolean isModified() { |
| 243 | // Utilities.showDebug("_savePoint = " + _savePoint + " editToBeUndone() = " + editToBeUndone()); | |
| 244 | 1 | return editToBeUndone() != _savePoint; |
| 245 | } | |
| 246 | ||
| 247 | 0 | public String toString() { return "(CompoundUndoManager: " + id + ")"; } |
| 248 | ||
| 249 | /** Used to help track down memory leaks. */ | |
| 250 | // protected void finalize() throws Throwable{ | |
| 251 | // super.finalize(); | |
| 252 | // } | |
| 253 | } |
|
||||||||||