KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > modules > xml > xam > AbstractModel


1 /*
2  * The contents of this file are subject to the terms of the Common Development
3  * and Distribution License (the License). You may not use this file except in
4  * compliance with the License.
5  *
6  * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
7  * or http://www.netbeans.org/cddl.txt.
8  *
9  * When distributing Covered Code, include this CDDL Header Notice in each file
10  * and include the License file at http://www.netbeans.org/cddl.txt.
11  * If applicable, add the following below the CDDL Header, with the fields
12  * enclosed by brackets [] replaced by your own identifying information:
13  * "Portions Copyrighted [year] [name of copyright owner]"
14  *
15  * The Original Software is NetBeans. The Initial Developer of the Original
16  * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
17  * Microsystems, Inc. All Rights Reserved.
18  */

19
20 package org.netbeans.modules.xml.xam;
21
22 import java.beans.PropertyChangeEvent JavaDoc;
23 import java.beans.PropertyChangeSupport JavaDoc;
24 import java.io.IOException JavaDoc;
25 import java.util.ArrayList JavaDoc;
26 import java.util.HashMap JavaDoc;
27 import java.util.HashSet JavaDoc;
28 import java.util.List JavaDoc;
29 import java.util.Map JavaDoc;
30 import java.util.Set JavaDoc;
31 import java.util.concurrent.Semaphore JavaDoc;
32 import java.util.logging.Level JavaDoc;
33 import java.util.logging.Logger JavaDoc;
34 import javax.swing.SwingUtilities JavaDoc;
35 import javax.swing.event.EventListenerList JavaDoc;
36 import javax.swing.event.UndoableEditEvent JavaDoc;
37 import javax.swing.event.UndoableEditListener JavaDoc;
38 import javax.swing.undo.CannotRedoException JavaDoc;
39 import javax.swing.undo.CannotUndoException JavaDoc;
40 import javax.swing.undo.CompoundEdit JavaDoc;
41 import javax.swing.undo.UndoManager JavaDoc;
42 import javax.swing.undo.UndoableEdit JavaDoc;
43 import javax.swing.undo.UndoableEditSupport JavaDoc;
44 import org.netbeans.modules.xml.xam.Model.State;
45
46 /**
47  * @author Chris Webster
48  * @author Rico
49  * @author Nam Nguyen
50  */

51 public abstract class AbstractModel<T extends Component<T>> implements Model<T>, UndoableEditListener JavaDoc {
52     
53     private PropertyChangeSupport JavaDoc pcs;
54     protected ModelUndoableEditSupport ues;
55     private State status;
56     private boolean inSync;
57     private boolean inUndoRedo;
58     private EventListenerList JavaDoc componentListeners;
59     private Semaphore JavaDoc transactionSemaphore;
60     private Transaction transaction;
61     private ModelSource source;
62     private UndoableEditListener JavaDoc[] savedUndoableEditListeners;
63     
64     public AbstractModel(ModelSource source) {
65         this.source = source;
66         pcs = new PropertyChangeSupport JavaDoc(this);
67         ues = new ModelUndoableEditSupport();
68         componentListeners = new EventListenerList JavaDoc();
69         transactionSemaphore = new Semaphore JavaDoc(1,true); // binary semaphore
70
status = State.VALID;
71     }
72
73     public abstract ModelAccess getAccess();
74
75     public void removePropertyChangeListener(java.beans.PropertyChangeListener JavaDoc pcl) {
76         pcs.removePropertyChangeListener(pcl);
77     }
78     
79     /**
80      * Add property change listener which will receive events for any element
81      * in the underlying schema model.
82      */

83     public void addPropertyChangeListener(java.beans.PropertyChangeListener JavaDoc pcl) {
84         pcs.addPropertyChangeListener(pcl);
85     }
86     
87     public void firePropertyChangeEvent(PropertyChangeEvent JavaDoc event) {
88         assert transaction != null;
89         transaction.addPropertyChangeEvent(event);
90     }
91     
92     public void removeUndoableEditListener(javax.swing.event.UndoableEditListener JavaDoc uel) {
93         ues.removeUndoableEditListener(uel);
94     }
95     
96     public void addUndoableEditListener(javax.swing.event.UndoableEditListener JavaDoc uel) {
97         ues.addUndoableEditListener(uel);
98     }
99
100     public synchronized void addUndoableRefactorListener(javax.swing.event.UndoableEditListener JavaDoc uel) {
101         savedUndoableEditListeners = ues.getUndoableEditListeners();
102         if (savedUndoableEditListeners != null) {
103             for (UndoableEditListener JavaDoc saved : savedUndoableEditListeners) {
104                 if (saved instanceof UndoManager JavaDoc) {
105                     ((UndoManager JavaDoc)saved).discardAllEdits();
106                 }
107             }
108         }
109         ues = new ModelUndoableEditSupport();
110         ues.addUndoableEditListener(uel);
111     }
112     
113     public synchronized void removeUndoableRefactorListener(javax.swing.event.UndoableEditListener JavaDoc uel) {
114         ues.removeUndoableEditListener(uel);
115         if (savedUndoableEditListeners != null) {
116             ues = new ModelUndoableEditSupport();
117             for (UndoableEditListener JavaDoc saved : savedUndoableEditListeners) {
118                 ues.addUndoableEditListener(saved);
119             }
120             savedUndoableEditListeners = null;
121         }
122     }
123
124     protected CompoundEdit JavaDoc createModelUndoableEdit() {
125         return new ModelUndoableEdit();
126     }
127
128     protected class ModelUndoableEditSupport extends UndoableEditSupport JavaDoc {
129         
130         @Override JavaDoc
131         protected CompoundEdit JavaDoc 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 JavaDoc event = new PropertyChangeEvent JavaDoc(this, STATE_PROPERTY, old, status);
163         if (isIntransaction()) {
164             firePropertyChangeEvent(event);
165         } else {
166             pcs.firePropertyChange(event);
167         }
168     }
169     
170     /**
171      * This method is overridden by subclasses to determine if sync needs to be
172      * performed. The default implementation simply returns true.
173      */

174     protected boolean needsSync() {
175     return true;
176     }
177     
178     /**
179      * This template method is invoked when a transaction is started. The
180      * default implementation does nothing.
181      */

182     protected void transactionStarted() {
183     
184     }
185     
186     /**
187      * This method is invoked when a transaction has completed. The default
188      * implementation does nothing.
189      */

190     protected void transactionCompleted() {
191     
192     }
193     
194     /**
195      * This method is invoked when sync has started. The default implementation
196      * does nothing.
197      */

198     protected void syncStarted() {
199     
200     }
201     
202     /**
203      * This method is invoked when sync has completed. The default implementation
204      * does nothing.
205      */

206     protected void syncCompleted() {
207     
208     }
209     
210     /**
211      * Prepare for sync. This allow splitting calculation intensive work from
212      * event firing tasks that are mostly running on UI threads. This should be
213      * optional step, meaning the actual call sync() should task care of the
214      * preparation if it is not done.
215      */

216     public synchronized void prepareSync() {
217         if (needsSync()) {
218             getAccess().prepareSync();
219         }
220     }
221     
222     public synchronized void sync() throws java.io.IOException JavaDoc {
223         if (needsSync()) {
224             syncStarted();
225             boolean syncStartedTransaction = false;
226             boolean success = false;
227             try {
228                 startTransaction(true, false); //start pseudo transaction for event firing
229
syncStartedTransaction = true;
230                 setState(getAccess().sync());
231                 endTransaction();
232                 success = true;
233             } catch (IOException JavaDoc e) {
234                 setState(State.NOT_WELL_FORMED);
235                 endTransaction(false); // do want to fire just the state transition event
236
throw e;
237             } finally {
238                 if (syncStartedTransaction && isIntransaction()) { //CR: consider separate try/catch
239
try {
240                         endTransaction(true); // do not fire events
241
} catch(Exception JavaDoc ex) {
242                         Logger.getLogger(getClass().getName()).log(Level.INFO, "Sync cleanup error.", ex); //NOI18N
243
}
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     /**
258      * Refresh the domain model component trees. The model state should be VALID as the
259      * result of this call.
260      * Note: subclasses need to override to provide the actual refresh service.
261      */

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; // just no-op when not in transaction
289
validateWrite(); // ensures that the releasing thread really owns trnx
290
try {
291             if (! quiet) {
292                 transaction.fireEvents();
293             }
294             // no-need to flush or undo/redo support while in sync
295
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 JavaDoc(
318             "Current thread has already started a transaction");
319         }
320         
321         if (! inSync && ! getModelSource().isEditable()) {
322             throw new IllegalArgumentException JavaDoc("Model source is read-only.");
323         }
324         
325         transactionSemaphore.acquireUninterruptibly();
326         // other correctly behaving threads will be blocked acquiring the
327
// semaphore here. Also store the current Thread to ensure that
328
// no other writes are occurring
329
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     /**
349      * This method ensures that a transaction is currently in progress and
350      * that the current thread is able to write.
351      */

352     public synchronized void validateWrite() {
353         if (transaction == null ||
354             !transaction.currentThreadIsTransactionThread()) {
355             throw new IllegalStateException JavaDoc("attempted model write without " +
356                     "invoking startTransaction");
357         }
358     }
359     
360     private class Transaction {
361         private final List JavaDoc<PropertyChangeEvent JavaDoc> propertyChangeEvents;
362         private final List JavaDoc<ComponentEvent> componentListenerEvents;
363         private final Thread JavaDoc transactionThread;
364         private boolean eventAdded;
365         private Boolean JavaDoc eventsAddedAfterFiring;
366         private boolean hasEvents;
367         
368         public Transaction() {
369             propertyChangeEvents = new ArrayList JavaDoc<PropertyChangeEvent JavaDoc>();
370             componentListenerEvents = new ArrayList JavaDoc<ComponentEvent>();
371             transactionThread = Thread.currentThread();
372             eventAdded = false;
373             eventsAddedAfterFiring = null;
374             hasEvents = false;
375         }
376         
377         public void addPropertyChangeEvent(PropertyChangeEvent JavaDoc pce) {
378             propertyChangeEvents.add(pce);
379             // do not chain events during undo/redo
380
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             // do not chain events during undo/redo
392
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         /**
416          * This method is added to allow mutations to occur inside events. The
417      * list is cloned so that additional events can be added.
418          */

419         private void fireCompleteEventSet() {
420             final List JavaDoc<PropertyChangeEvent JavaDoc> clonedEvents =
421                     new ArrayList JavaDoc<PropertyChangeEvent JavaDoc>(propertyChangeEvents);
422             //should clear event list
423
propertyChangeEvents.clear();
424             for (PropertyChangeEvent JavaDoc pce:clonedEvents) {
425                 pcs.firePropertyChange(pce);
426             }
427             
428             final List JavaDoc<ComponentEvent> cEvents =
429                 new ArrayList JavaDoc<ComponentEvent>(componentListenerEvents);
430             //should clear event list
431
componentListenerEvents.clear();
432             Map JavaDoc<Object JavaDoc, Set JavaDoc<ComponentEvent.EventType>> fired = new HashMap JavaDoc<Object JavaDoc, Set JavaDoc<ComponentEvent.EventType>>();
433             
434             for (ComponentEvent cle:cEvents) {
435                 // make sure we only fire one event per component per event type.
436
Object JavaDoc source = cle.getSource();
437                 if (fired.keySet().contains(source)) {
438                     Set JavaDoc<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 JavaDoc<ComponentEvent.EventType> types = new HashSet JavaDoc<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     /**
468      * Whether the model has started firing events. This is the indication of
469      * beginning of endTransaction call and any subsequent mutations are from
470      * handlers of main transaction events or some of their own events.
471      */

472     public boolean startedFiringEvents() {
473         return transaction != null && transaction.eventsAddedAfterFiring != null;
474     }
475     
476     protected class ModelUndoableEdit extends CompoundEdit JavaDoc {
477         static final long serialVersionUID = 1L;
478         
479         public boolean addEdit(UndoableEdit JavaDoc anEdit) {
480             if (! isInProgress()) return false;
481             UndoableEdit JavaDoc 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 JavaDoc
494         public void redo() throws CannotRedoException JavaDoc {
495             boolean redoStartedTransaction = false;
496             boolean needsRefresh = true;
497             try {
498                 startTransaction(true, true); //start pseudo transaction for event firing
499
redoStartedTransaction = true;
500                 AbstractModel.this.getAccess().prepareForUndoRedo();
501                 super.redo();
502                 AbstractModel.this.getAccess().finishUndoRedo();
503                 endTransaction();
504                 needsRefresh = false;
505             } catch(CannotRedoException JavaDoc ex) {
506                 needsRefresh = false;
507                 throw ex;
508             } finally {
509                 if (isIntransaction() && redoStartedTransaction) {
510                     try {
511                         endTransaction(true); // do not fire events
512
} catch(Exception JavaDoc e) {
513                         Logger.getLogger(getClass().getName()).log(Level.INFO, "Redo error", e); //NOI18N
514
}
515                 }
516                 if (needsRefresh) {
517                     setState(State.NOT_SYNCED);
518                     refresh();
519                 }
520             }
521         }
522
523         @Override JavaDoc
524         public void undo() throws CannotUndoException JavaDoc {
525             boolean undoStartedTransaction = false;
526             boolean needsRefresh = true;
527             try {
528                 startTransaction(true, true); //start pseudo transaction for event firing
529
undoStartedTransaction = true;
530                 AbstractModel.this.getAccess().prepareForUndoRedo();
531                 super.undo();
532                 AbstractModel.this.getAccess().finishUndoRedo();
533                 endTransaction();
534                 needsRefresh = false;
535             } catch(CannotUndoException JavaDoc ex) {
536                 needsRefresh = false;
537                 throw ex;
538             } finally {
539                 if (undoStartedTransaction && isIntransaction()) {
540                     try {
541                         endTransaction(true); // do not fire events
542
} catch(Exception JavaDoc e) {
543                         Logger.getLogger(getClass().getName()).log(Level.INFO, "Undo error", e); //NOI18N
544
}
545                 }
546                 if (needsRefresh) {
547                     setState(State.NOT_SYNCED);
548                     refresh();
549                 }
550             }
551         }
552     }
553     
554     public void undoableEditHappened(UndoableEditEvent JavaDoc e) {
555         ues.postEdit(e.getEdit());
556     }
557
558     public ModelSource getModelSource() {
559         return source;
560     }
561     
562     EventListenerList JavaDoc 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 JavaDoc() {
577             public void run() {
578                 try {
579                     sync();
580                 } catch(Exception JavaDoc ioe) {
581                     // just have to be quiet during background autosync
582
// sync() should have handled all faults
583
}
584             }
585         });
586     }
587 }
588
589
590
Popular Tags