1 19 20 package org.netbeans.modules.refactoring.spi.impl; 21 22 import java.beans.PropertyChangeListener ; 23 import java.beans.PropertyChangeSupport ; 24 import java.util.Collection ; 25 import java.util.HashMap ; 26 import java.util.HashSet ; 27 import java.util.IdentityHashMap ; 28 import java.util.Iterator ; 29 import java.util.LinkedList ; 30 import java.util.Map ; 31 import javax.swing.event.ChangeEvent ; 32 import javax.swing.event.ChangeListener ; 33 import javax.swing.event.DocumentEvent ; 34 import javax.swing.event.DocumentListener ; 35 import javax.swing.text.Document ; 36 import javax.swing.text.StyledDocument ; 37 import org.netbeans.modules.refactoring.spi.BackupFacility; 38 import org.netbeans.modules.refactoring.api.ProgressEvent; 39 import org.netbeans.modules.refactoring.api.ProgressListener; 40 import org.netbeans.modules.refactoring.api.RefactoringSession; 41 import org.openide.LifecycleManager; 42 import org.openide.cookies.EditorCookie; 43 import org.openide.filesystems.FileChangeAdapter; 44 import org.openide.filesystems.FileEvent; 45 import org.openide.filesystems.FileObject; 46 import org.openide.filesystems.FileRenameEvent; 47 import org.openide.loaders.DataObject; 48 import org.openide.loaders.DataObjectNotFoundException; 49 import org.openide.text.CloneableEditorSupport; 50 import org.openide.text.NbDocument; 51 52 56 public class UndoManager extends FileChangeAdapter implements DocumentListener , ChangeListener { 57 58 59 private LinkedList <LinkedList <UndoItem>> undoList; 60 61 62 private LinkedList <LinkedList <UndoItem>> redoList; 63 64 65 private final HashSet <CloneableEditorSupport> allCES = new HashSet (); 66 67 68 private final HashMap <Document , CloneableEditorSupport> documentToCES = new HashMap (); 69 70 71 private final HashMap <InvalidationListener, Collection <? extends CloneableEditorSupport>> listenerToCES = new HashMap (); 72 private boolean listenersRegistered = false; 73 74 public static final String PROP_STATE = "state"; 76 private final PropertyChangeSupport pcs = new PropertyChangeSupport (this); 77 78 private boolean wasUndo = false; 79 private boolean wasRedo = false; 80 private boolean transactionStart; 81 private boolean dontDeleteUndo = false; 82 83 private IdentityHashMap <LinkedList , String > descriptionMap; 84 private String description; 85 private ProgressListener progress; 86 87 private static UndoManager instance; 88 89 90 public static UndoManager getDefault() { 91 if (instance==null) { 92 instance = new UndoManager(); 93 } 94 return instance; 95 } 96 97 private UndoManager() { 98 undoList = new LinkedList (); 99 redoList = new LinkedList (); 100 descriptionMap = new IdentityHashMap (); 101 } 102 103 private UndoManager(ProgressListener progress) { 104 this(); 105 this.progress = progress; 106 } 107 108 public void setUndoDescription(String desc) { 109 description = desc; 110 } 111 112 public String getUndoDescription() { 113 if (undoList.isEmpty()) return null; 114 return descriptionMap.get(undoList.getFirst()); 115 } 116 117 public String getRedoDescription() { 118 if (redoList.isEmpty()) return null; 119 return descriptionMap.get(redoList.getFirst()); 120 } 121 122 124 public void transactionStarted() { 125 transactionStart = true; 126 unregisterListeners(); 127 } 129 130 133 public void transactionEnded(boolean fail) { 134 try { 135 description = null; 136 dontDeleteUndo = true; 137 if (fail && !undoList.isEmpty()) 138 undoList.removeFirst(); 139 else { 140 if (isUndoAvailable() && getUndoDescription() == null) { 142 descriptionMap.remove(undoList.removeFirst()); 143 dontDeleteUndo = false; 144 } 145 146 } 147 148 invalidate(null); 149 dontDeleteUndo = false; 150 } finally { 151 registerListeners(); 152 } 153 fireStateChange(); 154 } 155 156 157 public void undo() { 158 if (isUndoAvailable()) { 160 boolean fail = true; 161 try { 162 transactionStarted(); 163 wasUndo = true; 164 LinkedList undo = (LinkedList ) undoList.getFirst(); 165 fireProgressListenerStart(0, undo.size()); 166 undoList.removeFirst(); 167 Iterator undoIterator = undo.iterator(); 168 UndoItem item; 169 redoList.addFirst(new LinkedList ()); 170 descriptionMap.put(redoList.getFirst(), descriptionMap.remove(undo)); 171 while (undoIterator.hasNext()) { 172 fireProgressListenerStep(); 173 item = (UndoItem) undoIterator.next(); 174 item.undo(); 175 if (item instanceof SessionUndoItem) { 176 addItem(item); 177 } 178 } 179 fail = false; 180 } finally { 181 try { 182 wasUndo = false; 183 transactionEnded(fail); 184 } finally { 185 fireProgressListenerStop(); 186 fireStateChange(); 187 } 188 } 189 } 190 } 191 192 194 public void redo() { 195 if (isRedoAvailable()) { 197 boolean fail = true; 198 try { 199 transactionStarted(); 200 wasRedo = true; 201 LinkedList redo = redoList.getFirst(); 202 fireProgressListenerStart(1, redo.size()); 203 redoList.removeFirst(); 204 Iterator <UndoItem> redoIterator = redo.iterator(); 205 UndoItem item; 206 description = descriptionMap.remove(redo); 207 while (redoIterator.hasNext()) { 208 fireProgressListenerStep(); 209 item = redoIterator.next(); 210 item.redo(); 211 if (item instanceof SessionUndoItem) { 212 addItem(item); 213 } 214 } 215 fail = false; 216 } finally { 217 try { 218 wasRedo = false; 219 transactionEnded(fail); 220 } finally { 221 fireProgressListenerStop(); 222 fireStateChange(); 223 } 224 } 225 } 226 } 227 228 229 public void clear() { 230 undoList.clear(); 231 redoList.clear(); 232 descriptionMap.clear(); 233 BackupFacility.getDefault().clear(); 234 fireStateChange(); 235 } 236 237 public void addItem(RefactoringSession session) { 238 addItem(new SessionUndoItem(session)); 239 } 240 241 242 public void addItem(UndoItem item) { 243 if (wasUndo) { 244 LinkedList redo = this.redoList.getFirst(); 245 redo.addFirst(item); 246 } else { 247 if (transactionStart) { 248 undoList.addFirst(new LinkedList ()); 249 descriptionMap.put(undoList.getFirst(), description); 250 transactionStart = false; 251 } 252 LinkedList undo = this.undoList.getFirst(); 253 undo.addFirst(item); 254 } 255 if (! (wasUndo || wasRedo)) 256 redoList.clear(); 257 } 258 259 public boolean isUndoAvailable() { 260 return !undoList.isEmpty(); 261 } 262 263 public boolean isRedoAvailable() { 264 return !redoList.isEmpty(); 265 } 266 267 public void addPropertyChangeListener(PropertyChangeListener pcl) { 268 pcs.addPropertyChangeListener(pcl); 269 } 270 271 public void removePropertyChangeListener(PropertyChangeListener pcl) { 272 pcs.removePropertyChangeListener(pcl); 273 } 274 275 private void fireStateChange() { 276 pcs.firePropertyChange(PROP_STATE, null, null); 277 } 278 279 public void watch(Collection <? extends CloneableEditorSupport> ceSupports, InvalidationListener l) { 280 synchronized (allCES) { 281 registerListeners(); 282 } 283 for (Iterator <? extends CloneableEditorSupport> it = ceSupports.iterator(); it.hasNext();) { 284 final CloneableEditorSupport ces = it.next(); 285 final Document d = ces.getDocument(); 286 if (d!=null) { 287 NbDocument.runAtomic((StyledDocument )d, new Runnable () { 288 public void run() { 289 synchronized(allCES) { 290 if (allCES.add(ces)) { 291 ces.addChangeListener(UndoManager.this); 292 d.addDocumentListener(UndoManager.this); 293 documentToCES.put(d, ces); 294 } 295 } 296 } 297 }); 298 } else { 299 synchronized(allCES) { 300 if (allCES.add(ces)) { 301 ces.addChangeListener(UndoManager.this); 302 } 303 } 304 } 305 } 306 synchronized(allCES) { 307 if (l != null) { 308 listenerToCES.put(l, ceSupports); 309 } 310 } 311 } 312 313 public void stopWatching(InvalidationListener l) { 314 synchronized (allCES) { 316 listenerToCES.remove(l); 317 clearIfPossible(); 318 } 319 } 321 322 333 private void registerListeners() { 334 if (listenersRegistered) return; 335 Util.addFileSystemsListener(this); 338 for (CloneableEditorSupport ces:allCES) { 339 ces.addChangeListener(this); 340 } 341 for (Document doc:documentToCES.keySet()) { 342 doc.addDocumentListener(this); 343 } 344 listenersRegistered = true; 345 } 346 347 private void unregisterListeners() { 348 if (!listenersRegistered) return; 349 Util.removeFileSystemsListener(this); 350 for (CloneableEditorSupport ces:allCES) { 353 ces.removeChangeListener(this); 354 } 355 for (Document doc:documentToCES.keySet()) { 356 doc.removeDocumentListener(this); 357 } 358 listenersRegistered = false; 359 } 360 361 private void invalidate(CloneableEditorSupport ces) { 362 synchronized (undoList) { 363 if (!(wasRedo || wasUndo) && !dontDeleteUndo) { 364 clear(); 365 } 366 synchronized (allCES) { 367 if (ces == null) { 368 for (InvalidationListener lis:listenerToCES.keySet()) { 370 lis.invalidateObject(); 371 } 372 listenerToCES.clear(); 373 } else { 374 for (Iterator <Map.Entry <InvalidationListener, Collection <? extends CloneableEditorSupport>>> it = listenerToCES.entrySet().iterator(); it.hasNext();) { 375 Map.Entry <InvalidationListener, Collection <? extends CloneableEditorSupport>> e = it.next(); 376 if ((e.getValue()).contains(ces)) { 377 e.getKey().invalidateObject(); 378 it.remove(); 379 } 380 } 381 389 } 390 clearIfPossible(); 391 } 392 } 393 } 394 395 private void clearIfPossible() { 396 if (listenerToCES.isEmpty() && undoList.isEmpty() && redoList.isEmpty()) { 397 unregisterListeners(); 398 allCES.clear(); 399 documentToCES.clear(); 400 } 401 } 402 403 405 public void fileChanged(FileEvent fe) { 406 FileObject file = fe.getFile(); 407 if (file != null) { 408 DataObject obj; 409 try { 410 obj = DataObject.find(file); 411 } catch (DataObjectNotFoundException e) { 412 return; 413 } 414 EditorCookie ec = obj.getCookie(EditorCookie.class); 415 if (ec != null) { 416 CloneableEditorSupport ces = documentToCES.get(ec.getDocument()); 417 if (ces != null) { 418 invalidate(ces); 419 } 420 } 421 } 422 } 423 424 public void fileDeleted(FileEvent fe) { 425 } 429 430 public void fileRenamed(FileRenameEvent fe) { 431 } 435 436 438 public void changedUpdate(DocumentEvent e) { 439 } 440 441 public void insertUpdate(DocumentEvent e) { 442 invalidate(documentToCES.get(e.getDocument())); 443 } 444 445 public void removeUpdate(DocumentEvent e) { 446 invalidate(documentToCES.get(e.getDocument())); 447 } 448 449 public void stateChanged(ChangeEvent e) { 450 synchronized (allCES) { 451 CloneableEditorSupport ces = (CloneableEditorSupport) e.getSource(); 452 Document d = ces.getDocument(); 453 for (Iterator it = documentToCES.entrySet().iterator(); it.hasNext();) { 454 Map.Entry en = (Map.Entry ) it.next(); 455 if (en.getValue() == ces) { 456 ((Document ) en.getKey()).removeDocumentListener(this); 457 it.remove(); 458 break; 459 } 460 } 461 if (d != null) { 462 documentToCES.put(d, ces); 463 d.addDocumentListener(this); 464 } 465 } 466 } 467 468 public void saveAll() { 469 synchronized (allCES) { 470 unregisterListeners(); 471 } 472 try { 473 LifecycleManager.getDefault().saveAll(); 474 } finally { 475 synchronized (allCES) { 476 registerListeners(); 477 } 478 } 479 } 480 481 private void fireProgressListenerStart(int type, int count) { 482 stepCounter = 0; 483 if (progress == null) 484 return; 485 progress.start(new ProgressEvent(this, ProgressEvent.START, type, count)); 486 } 487 488 private int stepCounter = 0; 489 491 private void fireProgressListenerStep() { 492 if (progress == null) 493 return; 494 progress.step(new ProgressEvent(this, ProgressEvent.STEP, 0, ++stepCounter)); 495 } 496 497 499 private void fireProgressListenerStop() { 500 if (progress == null) 501 return; 502 progress.stop(new ProgressEvent(this, ProgressEvent.STOP)); 503 } 504 505 private interface UndoItem { 506 void undo(); 507 void redo(); 508 } 509 510 private final class SessionUndoItem implements UndoItem { 511 512 private RefactoringSession change; 513 514 public SessionUndoItem (RefactoringSession change) { 515 this.change = change; 516 } 517 518 public void undo() { 519 change.undoRefactoring(false); 520 } 521 522 public void redo() { 523 change.doRefactoring(false); 524 } 525 } 526 } 527 | Popular Tags |