1 19 20 package org.netbeans.modules.xml.xam; 21 22 import java.beans.PropertyChangeEvent ; 23 import java.beans.PropertyChangeSupport ; 24 import java.io.IOException ; 25 import java.util.ArrayList ; 26 import java.util.HashMap ; 27 import java.util.HashSet ; 28 import java.util.List ; 29 import java.util.Map ; 30 import java.util.Set ; 31 import java.util.concurrent.Semaphore ; 32 import java.util.logging.Level ; 33 import java.util.logging.Logger ; 34 import javax.swing.SwingUtilities ; 35 import javax.swing.event.EventListenerList ; 36 import javax.swing.event.UndoableEditEvent ; 37 import javax.swing.event.UndoableEditListener ; 38 import javax.swing.undo.CannotRedoException ; 39 import javax.swing.undo.CannotUndoException ; 40 import javax.swing.undo.CompoundEdit ; 41 import javax.swing.undo.UndoManager ; 42 import javax.swing.undo.UndoableEdit ; 43 import javax.swing.undo.UndoableEditSupport ; 44 import org.netbeans.modules.xml.xam.Model.State; 45 46 51 public abstract class AbstractModel<T extends Component<T>> implements Model<T>, UndoableEditListener { 52 53 private PropertyChangeSupport pcs; 54 protected ModelUndoableEditSupport ues; 55 private State status; 56 private boolean inSync; 57 private boolean inUndoRedo; 58 private EventListenerList componentListeners; 59 private Semaphore transactionSemaphore; 60 private Transaction transaction; 61 private ModelSource source; 62 private UndoableEditListener [] savedUndoableEditListeners; 63 64 public AbstractModel(ModelSource source) { 65 this.source = source; 66 pcs = new PropertyChangeSupport (this); 67 ues = new ModelUndoableEditSupport(); 68 componentListeners = new EventListenerList (); 69 transactionSemaphore = new Semaphore (1,true); status = State.VALID; 71 } 72 73 public abstract ModelAccess getAccess(); 74 75 public void removePropertyChangeListener(java.beans.PropertyChangeListener pcl) { 76 pcs.removePropertyChangeListener(pcl); 77 } 78 79 83 public void addPropertyChangeListener(java.beans.PropertyChangeListener pcl) { 84 pcs.addPropertyChangeListener(pcl); 85 } 86 87 public void firePropertyChangeEvent(PropertyChangeEvent event) { 88 assert transaction != null; 89 transaction.addPropertyChangeEvent(event); 90 } 91 92 public void removeUndoableEditListener(javax.swing.event.UndoableEditListener uel) { 93 ues.removeUndoableEditListener(uel); 94 } 95 96 public void addUndoableEditListener(javax.swing.event.UndoableEditListener uel) { 97 ues.addUndoableEditListener(uel); 98 } 99 100 public synchronized void addUndoableRefactorListener(javax.swing.event.UndoableEditListener uel) { 101 savedUndoableEditListeners = ues.getUndoableEditListeners(); 102 if (savedUndoableEditListeners != null) { 103 for (UndoableEditListener saved : savedUndoableEditListeners) { 104 if (saved instanceof UndoManager ) { 105 ((UndoManager )saved).discardAllEdits(); 106 } 107 } 108 } 109 ues = new ModelUndoableEditSupport(); 110 ues.addUndoableEditListener(uel); 111 } 112 113 public synchronized void removeUndoableRefactorListener(javax.swing.event.UndoableEditListener uel) { 114 ues.removeUndoableEditListener(uel); 115 if (savedUndoableEditListeners != null) { 116 ues = new ModelUndoableEditSupport(); 117 for (UndoableEditListener saved : savedUndoableEditListeners) { 118 ues.addUndoableEditListener(saved); 119 } 120 savedUndoableEditListeners = null; 121 } 122 } 123 124 protected CompoundEdit createModelUndoableEdit() { 125 return new ModelUndoableEdit(); 126 } 127 128 protected class ModelUndoableEditSupport extends UndoableEditSupport { 129 130 @Override 131 protected CompoundEdit createCompoundEdit() { 132 return createModelUndoableEdit(); 133 } 134 } 135 136 public boolean inSync() { 137 return inSync; 138 } 139 140 protected void setInSync(boolean v) { 141 inSync = v; 142 } 143 144 public boolean inUndoRedo() { 145 return inUndoRedo; 146 } 147 148 protected void setInUndoRedo(boolean v) { 149 inUndoRedo = v; 150 } 151 152 public State getState() { 153 return status; 154 } 155 156 protected void setState(State s) { 157 if (s == status) { 158 return; 159 } 160 State old = status; 161 status = s; 162 PropertyChangeEvent event = new PropertyChangeEvent (this, STATE_PROPERTY, old, status); 163 if (isIntransaction()) { 164 firePropertyChangeEvent(event); 165 } else { 166 pcs.firePropertyChange(event); 167 } 168 } 169 170 174 protected boolean needsSync() { 175 return true; 176 } 177 178 182 protected void transactionStarted() { 183 184 } 185 186 190 protected void transactionCompleted() { 191 192 } 193 194 198 protected void syncStarted() { 199 200 } 201 202 206 protected void syncCompleted() { 207 208 } 209 210 216 public synchronized void prepareSync() { 217 if (needsSync()) { 218 getAccess().prepareSync(); 219 } 220 } 221 222 public synchronized void sync() throws java.io.IOException { 223 if (needsSync()) { 224 syncStarted(); 225 boolean syncStartedTransaction = false; 226 boolean success = false; 227 try { 228 startTransaction(true, false); syncStartedTransaction = true; 230 setState(getAccess().sync()); 231 endTransaction(); 232 success = true; 233 } catch (IOException e) { 234 setState(State.NOT_WELL_FORMED); 235 endTransaction(false); throw e; 237 } finally { 238 if (syncStartedTransaction && isIntransaction()) { try { 240 endTransaction(true); } catch(Exception ex) { 242 Logger.getLogger(getClass().getName()).log(Level.INFO, "Sync cleanup error.", ex); } 244 } 245 246 if (!success && getState() != State.NOT_WELL_FORMED) { 247 setState(State.NOT_SYNCED); 248 refresh(); 249 } 250 251 setInSync(false); 252 syncCompleted(); 253 } 254 } 255 } 256 257 262 protected void refresh() { 263 setState(State.VALID); 264 } 265 266 public void removeComponentListener(ComponentListener cl) { 267 componentListeners.remove(ComponentListener.class, cl); 268 } 269 270 public void addComponentListener(ComponentListener cl) { 271 componentListeners.add(ComponentListener.class, cl); 272 } 273 274 public void fireComponentChangedEvent(ComponentEvent evt) { 275 assert transaction != null; 276 transaction.addComponentEvent(evt); 277 } 278 279 public synchronized boolean isIntransaction() { 280 return transaction != null; 281 } 282 283 public synchronized void endTransaction() { 284 endTransaction(false); 285 } 286 287 protected synchronized void endTransaction(boolean quiet) { 288 if (transaction == null) return; validateWrite(); try { 291 if (! quiet) { 292 transaction.fireEvents(); 293 } 294 if (! inSync() && transaction.hasEvents() || 296 transaction.hasEventsAfterFiring()) { 297 getAccess().flush(); 298 } 299 if (! inUndoRedo()) { 300 ues.endUpdate(); 301 } 302 } finally { 303 transaction = null; 304 setInSync(false); 305 setInUndoRedo(false); 306 transactionSemaphore.release(); 307 transactionCompleted(); 308 } 309 } 310 311 public boolean startTransaction() { 312 return startTransaction(false, false); 313 } 314 315 private synchronized boolean startTransaction(boolean inSync, boolean inUndoRedo) { 316 if (transaction != null && transaction.currentThreadIsTransactionThread()) { 317 throw new IllegalStateException ( 318 "Current thread has already started a transaction"); 319 } 320 321 if (! inSync && ! getModelSource().isEditable()) { 322 throw new IllegalArgumentException ("Model source is read-only."); 323 } 324 325 transactionSemaphore.acquireUninterruptibly(); 326 assert transaction == null; 330 331 if (! inSync && getState() == State.NOT_WELL_FORMED) { 332 transactionSemaphore.release(); 333 return false; 334 } 335 336 transaction = new Transaction(); 337 transactionStarted(); 338 setInSync(inSync); 339 setInUndoRedo(inUndoRedo); 340 341 if (! inUndoRedo) { 342 ues.beginUpdate(); 343 } 344 345 return true; 346 } 347 348 352 public synchronized void validateWrite() { 353 if (transaction == null || 354 !transaction.currentThreadIsTransactionThread()) { 355 throw new IllegalStateException ("attempted model write without " + 356 "invoking startTransaction"); 357 } 358 } 359 360 private class Transaction { 361 private final List <PropertyChangeEvent > propertyChangeEvents; 362 private final List <ComponentEvent> componentListenerEvents; 363 private final Thread transactionThread; 364 private boolean eventAdded; 365 private Boolean eventsAddedAfterFiring; 366 private boolean hasEvents; 367 368 public Transaction() { 369 propertyChangeEvents = new ArrayList <PropertyChangeEvent >(); 370 componentListenerEvents = new ArrayList <ComponentEvent>(); 371 transactionThread = Thread.currentThread(); 372 eventAdded = false; 373 eventsAddedAfterFiring = null; 374 hasEvents = false; 375 } 376 377 public void addPropertyChangeEvent(PropertyChangeEvent pce) { 378 propertyChangeEvents.add(pce); 379 if (eventsAddedAfterFiring == null || ! inUndoRedo) { 381 eventAdded = true; 382 } 383 if (eventsAddedAfterFiring != null) { 384 eventsAddedAfterFiring = Boolean.TRUE; 385 } 386 hasEvents = true; 387 } 388 389 public void addComponentEvent(ComponentEvent cle) { 390 componentListenerEvents.add(cle); 391 if (eventsAddedAfterFiring == null || ! inUndoRedo) { 393 eventAdded = true; 394 } 395 if (eventsAddedAfterFiring != null) { 396 eventsAddedAfterFiring = Boolean.TRUE; 397 } 398 hasEvents = true; 399 } 400 401 public boolean currentThreadIsTransactionThread() { 402 return Thread.currentThread().equals(transactionThread); 403 } 404 405 public void fireEvents() { 406 if (eventsAddedAfterFiring == null) { 407 eventsAddedAfterFiring = Boolean.FALSE; 408 } 409 while (eventAdded) { 410 eventAdded = false; 411 fireCompleteEventSet(); 412 } 413 } 414 415 419 private void fireCompleteEventSet() { 420 final List <PropertyChangeEvent > clonedEvents = 421 new ArrayList <PropertyChangeEvent >(propertyChangeEvents); 422 propertyChangeEvents.clear(); 424 for (PropertyChangeEvent pce:clonedEvents) { 425 pcs.firePropertyChange(pce); 426 } 427 428 final List <ComponentEvent> cEvents = 429 new ArrayList <ComponentEvent>(componentListenerEvents); 430 componentListenerEvents.clear(); 432 Map <Object , Set <ComponentEvent.EventType>> fired = new HashMap <Object , Set <ComponentEvent.EventType>>(); 433 434 for (ComponentEvent cle:cEvents) { 435 Object source = cle.getSource(); 437 if (fired.keySet().contains(source)) { 438 Set <ComponentEvent.EventType> types = fired.get(source); 439 if (types.contains(cle.getEventType())) { 440 continue; 441 } else { 442 types.add(cle.getEventType()); 443 } 444 } else { 445 Set <ComponentEvent.EventType> types = new HashSet <ComponentEvent.EventType>(); 446 types.add(cle.getEventType()); 447 fired.put(cle.getSource(), types); 448 } 449 450 final ComponentListener[] listeners = 451 componentListeners.getListeners(ComponentListener.class); 452 for (ComponentListener cl : listeners) { 453 cle.getEventType().fireEvent(cle,cl); 454 } 455 } 456 } 457 458 public boolean hasEvents() { 459 return hasEvents; 460 } 461 462 public boolean hasEventsAfterFiring() { 463 return eventsAddedAfterFiring != null && eventsAddedAfterFiring.booleanValue(); 464 } 465 } 466 467 472 public boolean startedFiringEvents() { 473 return transaction != null && transaction.eventsAddedAfterFiring != null; 474 } 475 476 protected class ModelUndoableEdit extends CompoundEdit { 477 static final long serialVersionUID = 1L; 478 479 public boolean addEdit(UndoableEdit anEdit) { 480 if (! isInProgress()) return false; 481 UndoableEdit last = lastEdit(); 482 if (last == null) { 483 return super.addEdit(anEdit); 484 } else { 485 if (! last.addEdit(anEdit)) { 486 return super.addEdit(anEdit); 487 } else { 488 return true; 489 } 490 } 491 } 492 493 @Override 494 public void redo() throws CannotRedoException { 495 boolean redoStartedTransaction = false; 496 boolean needsRefresh = true; 497 try { 498 startTransaction(true, true); redoStartedTransaction = true; 500 AbstractModel.this.getAccess().prepareForUndoRedo(); 501 super.redo(); 502 AbstractModel.this.getAccess().finishUndoRedo(); 503 endTransaction(); 504 needsRefresh = false; 505 } catch(CannotRedoException ex) { 506 needsRefresh = false; 507 throw ex; 508 } finally { 509 if (isIntransaction() && redoStartedTransaction) { 510 try { 511 endTransaction(true); } catch(Exception e) { 513 Logger.getLogger(getClass().getName()).log(Level.INFO, "Redo error", e); } 515 } 516 if (needsRefresh) { 517 setState(State.NOT_SYNCED); 518 refresh(); 519 } 520 } 521 } 522 523 @Override 524 public void undo() throws CannotUndoException { 525 boolean undoStartedTransaction = false; 526 boolean needsRefresh = true; 527 try { 528 startTransaction(true, true); undoStartedTransaction = true; 530 AbstractModel.this.getAccess().prepareForUndoRedo(); 531 super.undo(); 532 AbstractModel.this.getAccess().finishUndoRedo(); 533 endTransaction(); 534 needsRefresh = false; 535 } catch(CannotUndoException ex) { 536 needsRefresh = false; 537 throw ex; 538 } finally { 539 if (undoStartedTransaction && isIntransaction()) { 540 try { 541 endTransaction(true); } catch(Exception e) { 543 Logger.getLogger(getClass().getName()).log(Level.INFO, "Undo error", e); } 545 } 546 if (needsRefresh) { 547 setState(State.NOT_SYNCED); 548 refresh(); 549 } 550 } 551 } 552 } 553 554 public void undoableEditHappened(UndoableEditEvent e) { 555 ues.postEdit(e.getEdit()); 556 } 557 558 public ModelSource getModelSource() { 559 return source; 560 } 561 562 EventListenerList getComponentListenerList() { 563 return componentListeners; 564 } 565 566 public boolean isAutoSyncActive() { 567 return getAccess().isAutoSync(); 568 } 569 570 public void setAutoSyncActive(boolean v) { 571 getAccess().setAutoSync(v); 572 } 573 574 public synchronized void runAutoSync() { 575 prepareSync(); 576 SwingUtilities.invokeLater(new Runnable () { 577 public void run() { 578 try { 579 sync(); 580 } catch(Exception ioe) { 581 } 584 } 585 }); 586 } 587 } 588 589 590 | Popular Tags |