KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > modules > xml > tax > cookies > TreeEditorCookieImpl


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 package org.netbeans.modules.xml.tax.cookies;
20
21 import java.io.IOException JavaDoc;
22 import java.util.Enumeration JavaDoc;
23 import java.lang.ref.SoftReference JavaDoc;
24 import java.lang.ref.WeakReference JavaDoc;
25 import java.lang.ref.ReferenceQueue JavaDoc;
26 import java.beans.PropertyChangeListener JavaDoc;
27 import java.beans.PropertyChangeEvent JavaDoc;
28 import java.beans.PropertyChangeSupport JavaDoc;
29 import javax.swing.text.Document JavaDoc;
30
31 import org.xml.sax.InputSource JavaDoc;
32
33 import org.openide.loaders.OpenSupport;
34 import org.openide.loaders.DataObject;
35 import org.openide.loaders.MultiDataObject;
36 import org.openide.nodes.CookieSet;
37 import org.openide.nodes.Node;
38 import org.openide.util.Task;
39 import org.openide.util.RequestProcessor;
40 import org.openide.*;
41 import org.openide.cookies.*;
42
43 import org.netbeans.tax.*;
44 import org.netbeans.tax.event.TreeEvent;
45
46 import org.netbeans.modules.xml.core.*;
47 import org.netbeans.modules.xml.core.tree.*;
48 import org.netbeans.modules.xml.core.cookies.*;
49 import org.netbeans.modules.xml.core.text.*;
50 import org.netbeans.modules.xml.core.sync.*;
51 import org.netbeans.modules.xml.tax.cookies.TreeDocumentCookie;
52 import org.netbeans.modules.xml.tax.cookies.TreeEditorCookie;
53 import org.netbeans.modules.xml.tax.*;
54 import org.netbeans.modules.xml.tax.parser.DTDParsingSupport;
55 import org.netbeans.modules.xml.tax.parser.ParsingSupport;
56 import org.netbeans.modules.xml.tax.parser.XMLParsingSupport;
57 import org.openide.util.Utilities;
58
59 /**
60  * @author Libor Kramolis
61  */

62 public class TreeEditorCookieImpl implements TreeEditorCookie, UpdateDocumentCookie {
63
64     /** */
65     private Task prepareTask;
66     /** */
67     private final Exception JavaDoc[] prepareException = new Exception JavaDoc[1];
68     /** */
69     private XMLDataObjectLook xmlDO;
70     /** */
71     private final PropertyChangeSupport JavaDoc pchs;
72     /** */
73     private final CookieManagerCookie cookieMgr;
74     /** */
75     private int status;
76     /** */
77     private int oldStatus;
78     
79     /** Edited model. */
80     private TreeReference tree;
81
82     private Object JavaDoc treeLock = new TreeLock();
83
84     /** Listener registered to listen at tree root that updatesText. */
85     private PropertyChangeListener JavaDoc treeListener = null;
86     
87     private TreeDocumentCookie treeDocumentCookie; // last added cookie
88

89     private Representation rep; // last added representation
90

91     
92     //
93
// init
94
//
95

96     /** */
97     public TreeEditorCookieImpl (XMLDataObjectLook xmlDO) {
98     this.xmlDO = xmlDO;
99         this.cookieMgr = xmlDO.getCookieManager();
100         this.status = TreeEditorCookie.STATUS_NOT;
101         this.oldStatus = status;
102         this.pchs = new PropertyChangeSupport JavaDoc (this);
103     }
104
105
106     //
107
// TreeEditorCookie
108
//
109

110     /**
111      */

112     public TreeDocumentRoot openDocumentRoot () throws IOException JavaDoc, TreeException {
113         if ( Util.THIS.isLoggable() ) /* then */ Util.THIS.debug("TreeEditorCookieImpl.openDocumentRoot()"); // NOI18N
114

115         for (;;) {
116         
117             prepareDocumentRoot().waitFinished();
118
119             synchronized (this) { // atomic test of two variables: root and prepareException
120
TreeDocumentRoot root = getDocumentRoot();
121                 if (root == null) {
122                     if (prepareException[0] instanceof IOException JavaDoc) {
123                         throw (IOException JavaDoc) prepareException[0];
124                     } else if (prepareException[0] instanceof TreeException) {
125                         throw (TreeException) prepareException[0];
126                     } else {
127                         if ( Util.THIS.isLoggable() ) /* then */ Util.THIS.debug("\tTree parsing retry due to (expected null): " + prepareException[0]); // NOI18N
128

129                         prepareTask = null;
130                         continue; // null root & exception, try load again
131
// may be gc collected us
132
}
133                 } else {
134                     if ( Util.THIS.isLoggable() ) /* then */ Util.THIS.debug("TreeEditorCookieImpl.openDocumentRoot() = " + root); // NOI18N
135

136                     return root;
137                 }
138             }
139         }
140     }
141     
142     /*
143      * It prepares <b>the first</b> instance of tree.
144      */

145     public Task prepareDocumentRoot () {
146         
147         synchronized (this) { // atomic test and set
148

149             if (prepareTask != null) return prepareTask;
150
151             prepareTask = new Task(new Runnable JavaDoc() {
152                 public void run() {
153                     try {
154
155                         InputSource JavaDoc src = inputSource();
156                         parseTree(src, true);
157
158                         prepareException[0] = null;
159                     } catch (IOException JavaDoc ex) {
160                         ErrorManager em = ErrorManager.getDefault();
161                         em.annotate(ex, Util.THIS.getString("BK0001"));
162                         prepareException[0] = ex;
163                     } catch (TreeException ex) {
164                         ErrorManager em = ErrorManager.getDefault();
165                         em.annotate(ex, Util.THIS.getString("BK0001"));
166                         prepareException[0] = ex;
167                     }
168
169                     fireTreeAndStatus();
170
171                 }
172             });
173
174         }
175         
176         new Thread JavaDoc(prepareTask, "Parsing tree...").start(); // NOI18N
177
return prepareTask;
178     }
179     
180     /*
181      * Return current tree.
182      */

183     public TreeDocumentRoot getDocumentRoot () {
184         if (tree == null)
185             return null;
186                 
187         TreeDocumentRoot root = tree.getDocumentRoot();
188         if (root == null) {
189         
190             //??? if returns null we were collected
191
// but cleaner task does not recognired it, yet
192
// also status is out of date until cleaner rescans the queue
193

194             return null; //!!!
195

196         } else {
197             return root;
198         }
199     }
200
201     public int getStatus () {
202         return status;
203     }
204     
205     public void addPropertyChangeListener (PropertyChangeListener JavaDoc l) {
206         pchs.addPropertyChangeListener (l);
207     }
208     
209     public void removePropertyChangeListener (PropertyChangeListener JavaDoc l) {
210         pchs.removePropertyChangeListener (l);
211     }
212
213
214     //
215
// other
216
//
217

218     /**
219      * Set it or merge with existing and begin listening on it fo tree changes,
220      * register new representation and cookies accordingly.
221      * @fire merge fires tree node changes
222      */

223     private void setTree (TreeDocumentRoot doc) {
224         if ( Util.THIS.isLoggable() ) /* then */ Util.THIS.debug ("TreeEditorCookieImpl::setTree: " + doc);//, new RuntimeException()); // NOI18N
225

226         if ( (doc == getDocumentRoot()) &&
227              (doc != null) ) {
228             return;
229         }
230
231         if ( Util.THIS.isLoggable() ) /* then */ Util.THIS.debug ("\tdifferent doc delivered, merging"); // NOI18N
232

233         TreeDocumentRoot oldTreeDoc = getDocumentRoot();
234         
235     try {
236             
237             if (doc == null) { // remove
238

239                 if ( Util.THIS.isLoggable() ) /* then */ Util.THIS.debug ("\tbefore REMOVE : " + getDocumentRoot()); // NOI18N
240

241                 removeTreeDocumentCookie();
242                 
243                 if (rep != null) {
244                 // should be done on STATUS_NOT
245
// xmlDO.getSyncInterface().removeRepresentation(rep);
246
}
247                 
248                 if (getDocumentRoot() != null) {
249                     ((TreeObject)getDocumentRoot()).removePropertyChangeListener (treeListener);
250                     treeListener = null;
251                 }
252                 tree = null;
253                 
254             } else if (tree == null) { //add
255

256                 if ( Util.THIS.isLoggable() ) /* then */ Util.THIS.debug ("\tbefore ADD : " + getDocumentRoot()); // NOI18N
257

258         tree = new TreeReference(doc);
259                 
260                 treeListener = new TreeListener();
261                 ((TreeObject)getDocumentRoot()).addPropertyChangeListener (treeListener);
262                 
263                 addTreeDocumentCookie();
264                 
265             } else { //update
266

267                 if ( Util.THIS.isLoggable() ) /* then */ Util.THIS.debug ("\tbefore MERGE : " + getDocumentRoot()); // NOI18N
268

269                 // do not listen for own changes #18503
270

271                 TreeObject root = (TreeObject)getDocumentRoot();
272                 root.removePropertyChangeListener(treeListener);
273                 root.merge ((TreeObject)doc);
274                 root.addPropertyChangeListener(treeListener);
275                 
276             }
277             
278             if (rep == null) {
279                 
280                 if (xmlDO instanceof XMLDataObject) {
281                     rep = new XMLTreeRepresentation(this, xmlDO.getSyncInterface());
282                 } else {
283                     rep = new DTDTreeRepresentation(this, xmlDO.getSyncInterface());
284                 }
285
286                 xmlDO.getSyncInterface().addRepresentation(rep);
287             }
288
289     } catch (CannotMergeException exc) { //???
290
if ( Util.THIS.isLoggable() ) /* then */ Util.THIS.debug ("MERGE FATAL ERROR:"); // NOI18N
291
exc.printStackTrace();
292     } finally {
293             
294     }
295     }
296
297
298     /*
299      * Update tree by content of given InputSource
300      * @param input must be an InputSource
301      */

302     public void updateTree (Object JavaDoc input) {
303         try {
304             parseTree((InputSource JavaDoc) input, false);
305             
306         } catch (TreeException ex) {
307             
308         } catch (IOException JavaDoc ex) {
309             
310         }
311         
312         fireTreeAndStatus();
313     }
314     
315     /*
316      * Update tree for given InputSource if exist or create it by force.
317      * IT DOES NOT FIRE THE CHANGE EVENT beacuse it can be called from lock,
318      * <p>
319      * It translates all parser RuntimeExceptions into TreeException.
320      *
321      * @param force if true create the tree by force replacing any exiting.
322      */

323     private void parseTree (InputSource JavaDoc input, boolean force) throws TreeException, IOException JavaDoc {
324
325         if (input == null) throw new IOException JavaDoc();
326         
327         String JavaDoc annotation = null;
328         
329         // check whether a tree exists (has been required by getDocumentRoot())
330
if ((status == STATUS_NOT) && (getDocumentRoot() == null) && (force == false))
331             return;
332         
333         if ( Util.THIS.isLoggable() ) /* then */ Util.THIS.debug ("XMLTreeEditorCookieImpl::updateTree(force=" + force + ")");//, new RuntimeException ("Updating tree...........")); // NOI18N
334

335         try {
336             
337             ParsingSupport parser = null;
338             
339             if (xmlDO instanceof XMLDataObject) {
340                 parser = new XMLParsingSupport();
341             } else if (xmlDO instanceof DTDDataObject) {
342                 parser = new DTDParsingSupport();
343             } else {
344                 if ( Util.THIS.isLoggable() ) /* then */ Util.THIS.debug("parseTree() Unexpected instance: " + xmlDO.getClass()); // NOI18N
345
}
346             
347             annotation = Util.THIS.getString ("MSG_Unexpected_exception_in_parser_or_handler");
348             
349             if ( Util.THIS.isLoggable() ) /* then */ Util.THIS.debug("PARSING: " + input.getSystemId()); // NOI18N
350

351             TreeDocumentRoot doc = (TreeDocumentRoot) parser.parse(input);
352             
353             annotation = Util.THIS.getString ("MSG_Unexpected_exception_in_merge");
354             setTreeAndStatus (doc, STATUS_OK);
355             
356         } catch (Exception JavaDoc ex) {
357             
358             if ( Util.THIS.isLoggable() ) /* then */ Util.THIS.debug ("XMLTreeEditorCookieImpl::updateTree", ex); // NOI18N
359

360             setTreeAndStatus (null, STATUS_ERROR);
361
362             // for diagnostic purposes log it
363
Util.THIS.getErrorManager().annotate (ex, annotation);
364             Util.THIS.debug(ex);
365
366             if (ex instanceof TreeException || ex instanceof IOException JavaDoc) {
367                 // text modification caused error
368
// we treat IOException in same way because problems with external entities
369
// are reported as IOExceptions
370

371                 
372                 //??? run XML compiler if not just being used to simplify error localization?
373
// it may cause focus problems
374

375                 if (ex instanceof IOException JavaDoc) throw (IOException JavaDoc) ex;
376                 if (ex instanceof TreeException) throw (TreeException) ex;
377                 
378             } else if (ex instanceof RuntimeException JavaDoc) {
379
380                 // it masks real reason why parsing failer (an internal error)
381
throw new TreeException(ex);
382             }
383         }
384     }
385     
386         
387     private void addTreeDocumentCookie() {
388         treeDocumentCookie = new TreeDocumentCookieImpl();
389         cookieMgr.addCookie(treeDocumentCookie);
390     }
391     
392     private void removeTreeDocumentCookie() {
393         if ( treeDocumentCookie != null ) {
394             cookieMgr.removeCookie (treeDocumentCookie);
395         }
396     }
397
398             
399     /** Set atomically tree and its status without firing. */
400     private void setTreeAndStatus(final TreeDocumentRoot doc, final int newStatus) {
401         
402         synchronized (treeLock) {
403             setTree (doc); // treeDocument = doc;
404
oldStatus = status;
405             status = newStatus;
406
407             if ( Util.THIS.isLoggable() ) /* then */ Util.THIS.debug ("Tree status transition: " + oldStatus + "=>" + newStatus); // NOI18N
408
}
409     }
410
411     /** Fire tree and status change. */
412     private void fireTreeAndStatus() {
413         final int fireStatus = status;
414         final int fireOldStatus = oldStatus;
415         final Object JavaDoc fireTree = getDocumentRoot();
416
417         if ( Util.THIS.isLoggable() ) /* then */ Util.THIS.debug("Firing tree status transition: " + oldStatus + "=>" + status); // NOI18N
418

419         RequestProcessor.postRequest( new Runnable JavaDoc() {
420             public void run() {
421                 pchs.firePropertyChange (PROP_STATUS, fireOldStatus, fireStatus);
422                 pchs.firePropertyChange (PROP_DOCUMENT_ROOT, null, fireTree);
423             }
424         });
425     }
426     
427     /**
428      * Reload external entities.
429      */

430     // comes from action thread
431
// it MUST respect synchronization atomicity
432
public void updateDocumentRoot() {
433
434         TreeDocumentCookie cookie = (TreeDocumentCookie) xmlDO.getCookie(TreeDocumentCookie.class);
435         if (cookie != null && cookie.getDocumentRoot() != null) {
436             Task task = new Task( new Runnable JavaDoc() {
437                 public void run() {
438                     try {
439                         parseTree(inputSource(), true);
440                     } catch (TreeException ex) {
441                         Util.THIS.debug (ex);
442                     } catch (IOException JavaDoc ex) {
443                         Util.THIS.debug (ex);
444                     }
445                 }
446             });
447
448             xmlDO.getSyncInterface().postRequest(task);
449             task.waitFinished();
450             fireTreeAndStatus();
451         }
452
453     }
454
455     //
456
// get most prefered input source
457
//
458
private InputSource JavaDoc inputSource() {
459         Representation primary = xmlDO.getSyncInterface().getPrimaryRepresentation();
460         InputSource JavaDoc src = (InputSource JavaDoc) primary.getChange(InputSource JavaDoc.class);
461         if (src == null) {
462             if ( Util.THIS.isLoggable() ) /* then */ Util.THIS.debug("Primary representation can not provide InputSource: " + primary ); // NOI18N
463
}
464         return src;
465     }
466     
467
468     //
469
// class TreeDocumentCookieImpl
470
//
471

472     private class TreeDocumentCookieImpl implements TreeDocumentCookie {
473                             
474         public TreeDocumentRoot getDocumentRoot() {
475             try {
476                 return TreeEditorCookieImpl.this.openDocumentRoot();
477             } catch (IOException JavaDoc ex) {
478                 return null;
479             } catch (TreeException ex) {
480                 return null;
481             }
482         }
483
484     } // end of class TreeDocumentCookieImpl
485

486
487     //
488
// class TreeListener
489
//
490

491     /**
492      * Listens just at tree. Knows that double events are fired and
493      * that these double events are of the same instance.
494      * @see org.netbeans.tax.TreeObject
495      */

496     private class TreeListener implements PropertyChangeListener JavaDoc {
497
498         private PropertyChangeEvent JavaDoc last;
499
500         public void propertyChange (PropertyChangeEvent JavaDoc e) {
501             if ( Util.THIS.isLoggable() ) /* then */ Util.THIS.debug ("\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); // NOI18N
502
if ( Util.THIS.isLoggable() ) /* then */ Util.THIS.debug ("!!! TreeEditorCookieImpl::TreeListener::propertyChange: propertyName = '" + e.getPropertyName() + "'"); // NOI18N
503
if ( Util.THIS.isLoggable() ) /* then */ Util.THIS.debug ("!!! ::TreeListener::propertyChange: last = " + last); // NOI18N
504
if ( Util.THIS.isLoggable() ) /* then */ Util.THIS.debug ("!!! ::TreeListener::propertyChange: e = " + e); // NOI18N
505

506 // if ( TreeNode.PROP_NODE.equals (e.getPropertyName()) && (e != last) ) {
507

508             if ( e != last ) {
509                 last = e;
510                 xmlDO.getSyncInterface().representationChanged (TreeDocument.class);
511                 
512                 if ( Util.THIS.isLoggable() ) /* then */ Util.THIS.debug ("!!! ::TreeListener::propertyChange: *after* representationChanged (TreeDocument.class)"); // NOI18N
513
if ( Util.THIS.isLoggable() ) /* then */ Util.THIS.debug ("!!! ::TreeListener::propertyChange: ( e instanceof TreeEvent ) => " + ( e instanceof TreeEvent )); // NOI18N
514

515                 boolean updateFromText = false;
516
517                 if ( ( e instanceof TreeEvent ) && ( (TreeEvent)e).isBubbling() ) {
518                     TreeEvent treeEvent = (TreeEvent)e;
519                     if ( ( treeEvent.getOriginalSource() instanceof TreeDocumentType ) &&
520                          ( TreeDocumentType.PROP_PUBLIC_ID.equals (treeEvent.getOriginalPropertyName()) ||
521                            TreeDocumentType.PROP_SYSTEM_ID.equals (treeEvent.getOriginalPropertyName()) ) ) {
522                         updateFromText = true;
523                     }
524                 } else if ( TreeParentNode.PROP_CHILD_LIST.equals (e.getPropertyName()) &&
525                             ( e.getNewValue() instanceof TreeDocumentType ) ) {
526                     updateFromText = true;
527                 }
528                 
529                 if ( Util.THIS.isLoggable() ) /* then */ Util.THIS.debug ("!!! ::TreeListener::propertyChange: updateFromText = " + updateFromText); // NOI18N
530

531                 if ( updateFromText ) {
532                     xmlDO.getSyncInterface().representationChanged (Document JavaDoc.class); // swing Document
533
}
534             }
535         }
536     } // end of class TreeListener
537

538
539     //
540
// class TreeReference
541
//
542

543     /*
544      * Reference which remembers which editor created stored TreeDocumentRoot.
545      */

546     private class TreeReference extends WeakReference JavaDoc implements Runnable JavaDoc {
547         
548         TreeReference (TreeDocumentRoot root) {
549             super(root, Utilities.activeReferenceQueue());
550         }
551         
552         public TreeDocumentRoot getDocumentRoot() {
553             return (TreeDocumentRoot) super.get();
554         }
555         
556         public TreeEditorCookieImpl getEditor() {
557             return TreeEditorCookieImpl.this;
558         }
559         
560         public String JavaDoc toString() {
561             return "TreeReference[" + getEditor().xmlDO.getName() + "]";
562         }
563         
564         // ACTIVE_REFERENCE_QUEUE calls it to let us clean related data
565
public void run() {
566             if ( Util.THIS.isLoggable() ) Util.THIS.debug("" + this + " reclaimed."); // NOI18N
567
getEditor().setTreeAndStatus(null, STATUS_NOT);
568             getEditor().fireTreeAndStatus();
569         }
570     } // end of class TreeReference
571

572
573         
574     //
575
// class CookieFactoryImpl
576
//
577

578     public static class CookieFactoryImpl extends CookieFactory {
579     
580         private WeakReference JavaDoc editor;
581
582         private final XMLDataObjectLook dobj; // used while creating the editor
583

584         
585         public CookieFactoryImpl (XMLDataObjectLook dobj) {
586             this.dobj = dobj;
587         }
588         
589         /** Creates a Node.Cookie of given class. The method
590          * may be called more than once.
591          */

592         public Node.Cookie createCookie (Class JavaDoc klass) {
593             if ( klass.isAssignableFrom(TreeEditorCookie.class) ) {
594                 return createEditor();
595             } else if ( klass.isAssignableFrom(UpdateDocumentCookie.class) ) {
596                 return createEditor();
597             } else {
598                 return null;
599             }
600         }
601         
602         // create same intance for text and tree editor cookie implementation
603
private synchronized TreeEditorCookieImpl createEditor() { // atomic test and set
604
if (editor == null) {
605                 return prepareEditor();
606             } else {
607                 TreeEditorCookieImpl cached = (TreeEditorCookieImpl) editor.get();
608                 if (cached == null) {
609                     return prepareEditor();
610                 } else {
611                     return cached;
612                 }
613             }
614         }
615         
616         private TreeEditorCookieImpl prepareEditor() {
617             if ( Util.THIS.isLoggable() ) /* then */ Util.THIS.debug ("Initializing TreeEditorCookieImpl ..."); // NOI18N
618

619             TreeEditorCookieImpl cake = new TreeEditorCookieImpl (dobj);
620             editor = new WeakReference JavaDoc (cake);
621             return cake;
622         }
623
624         public Class JavaDoc[] supportedCookies() {
625             return new Class JavaDoc[] {
626                 TreeEditorCookie.class,
627                 UpdateDocumentCookie.class,
628             };
629         }
630
631     } // end of class CookieFactoryImpl
632

633
634     //
635
// TreeLock
636
//
637

638     /** Subclass for better debugging. */
639     private class TreeLock {
640     }
641
642 }
643
Popular Tags