KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > modules > xml > text > navigator > TreeNodeAdapter


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.text.navigator;
21
22 import java.util.ArrayList JavaDoc;
23 import java.util.Collections JavaDoc;
24 import java.util.Enumeration JavaDoc;
25 import java.util.HashMap JavaDoc;
26 import java.util.Iterator JavaDoc;
27 import javax.swing.JTree JavaDoc;
28 import javax.swing.text.AttributeSet JavaDoc;
29 import javax.swing.text.BadLocationException JavaDoc;
30 import javax.swing.text.Document JavaDoc;
31 import javax.swing.tree.DefaultTreeModel JavaDoc;
32 import javax.swing.tree.TreeNode JavaDoc;
33 import javax.swing.tree.TreePath JavaDoc;
34 import org.netbeans.editor.BaseDocument;
35 import org.netbeans.modules.editor.structure.api.DocumentElement;
36 import org.netbeans.modules.editor.structure.api.DocumentElementEvent;
37 import org.netbeans.modules.editor.structure.api.DocumentElementListener;
38 import org.netbeans.modules.xml.text.folding.XmlFoldTypes;
39 import org.netbeans.modules.xml.text.structure.XMLDocumentModelProvider;
40 import org.openide.ErrorManager;
41
42 /**
43  * TreeNodeAdapter is an implementation of j.s.t.TreeNode encapsulating a DocumentElement
44  * instance and listening on its changes.
45  *
46  * @author Marek Fukala
47  */

48 public class TreeNodeAdapter implements TreeNode JavaDoc, DocumentElementListener {
49     
50     private DocumentElement de;
51     private DefaultTreeModel JavaDoc tm;
52     private TreeNode JavaDoc parent;
53     private JTree JavaDoc tree;
54     
55     private ArrayList JavaDoc children = null; //a list of non-content nodes
56
private ArrayList JavaDoc textElements = new ArrayList JavaDoc(); //stores wrappers of DocumentElement-s which are children of the DocumentElement held by this TreeNode
57
private Object JavaDoc childrenLock = new Object JavaDoc();
58     private String JavaDoc textContent = new String JavaDoc();
59     
60     //if the node itself contains an error
61
private boolean containsError = false;
62     //if one of its descendants contains an error
63
private int childrenErrorCount = 0;
64     
65     public TreeNodeAdapter(DocumentElement de, DefaultTreeModel JavaDoc tm, JTree JavaDoc tree, TreeNode JavaDoc parent) {
66         this.de = de;
67         this.tm = tm;
68         this.tree = tree;
69         this.parent = parent;
70     }
71     
72     /**Returns a text content of this node. The content is fetched from all text document elements which
73      * are children of the element held by this tree node.
74      *
75      * @return the text content of this node.
76      */

77     public String JavaDoc getDocumentContent() {
78         checkChildrenAdapters();
79         checkDocumentContent();
80         return textContent;
81     }
82     
83     public java.util.Enumeration JavaDoc children() {
84         checkChildrenAdapters();
85         return Collections.enumeration(children);
86     }
87     
88     public boolean getAllowsChildren() {
89         return true;
90     }
91     
92     public TreeNode JavaDoc getChildAt(int param) {
93         checkChildrenAdapters();
94         return (TreeNode JavaDoc)children.get(param);
95     }
96     
97     public int getChildCount() {
98         checkChildrenAdapters();
99         return children.size();
100     }
101     
102     public int getIndex(TreeNode JavaDoc treeNode) {
103         checkChildrenAdapters();
104         return children.indexOf(treeNode);
105     }
106     
107     public TreeNode JavaDoc getParent() {
108         return parent;
109     }
110     
111     public boolean isLeaf() {
112         return getChildCount() == 0;
113     }
114     
115     public DocumentElement getDocumentElement() {
116         return de;
117     }
118     
119     private TreeNodeAdapter getChildTreeNode(DocumentElement de) {
120         checkChildrenAdapters();
121         Iterator JavaDoc i = children.iterator();
122         while(i.hasNext()) {
123             TreeNodeAdapter tn = (TreeNodeAdapter)i.next();
124             if(tn.getDocumentElement().equals(de)) return tn;
125         }
126         return null;
127     }
128     
129     public boolean containsError() {
130         checkChildrenAdapters();
131         return containsError;
132     }
133     
134     //returns a number of ancestors with error
135
public int getChildrenErrorCount() {
136         checkChildrenAdapters();
137         return childrenErrorCount;
138     }
139     
140     public String JavaDoc toString() {
141         return getText(false);
142     }
143     
144     public String JavaDoc getText(boolean html) {
145         if(de.getType().equals(XMLDocumentModelProvider.XML_TAG)
146         || de.getType().equals(XMLDocumentModelProvider.XML_EMPTY_TAG)) {
147             //XML TAG text
148
String JavaDoc attribsVisibleText = "";
149             AttributeSet JavaDoc attribs = getDocumentElement().getAttributes();
150             
151             if(attribs.getAttributeCount() > 0) {
152                 String JavaDoc attribsText = getAttribsText();
153                 if(NavigatorContent.showAttributes) {
154                     attribsVisibleText = attribsText.length() > ATTRIBS_MAX_LEN ? attribsText.substring(0,ATTRIBS_MAX_LEN) + "..." : attribsText.toString();
155                 }
156             }
157             
158             String JavaDoc contentText = "";
159             String JavaDoc documentText = getDocumentContent();
160             if(NavigatorContent.showContent) {
161                 contentText = documentText.length() > TEXT_MAX_LEN ? documentText.substring(0,TEXT_MAX_LEN) + "..." : documentText;
162             }
163             
164             StringBuffer JavaDoc text = new StringBuffer JavaDoc();
165             text.append(html ? "<html>" : "");
166             text.append(html && containsError ? "<font color=FF0000><b>": ""); //red
167
text.append(getDocumentElement().getName());
168             text.append(html && containsError ? "</b></font>": "");
169             text.append(html ? "<font color=888888>" : "");//gray
170
if(attribsVisibleText.trim().length() > 0) {
171                 text.append(" ");
172                 text.append(attribsVisibleText);
173             }
174             text.append(html ? "</font>" : "");
175             if(contentText.trim().length() > 0) {
176                 text.append(" (");
177                 text.append(HTMLTextEncoder.encodeHTMLText(contentText));
178                 text.append(")");
179             }
180             text.append(html ? "</html>" : "");
181             
182             return text.toString();
183             
184         } else if(de.getType().equals(XMLDocumentModelProvider.XML_PI)) {
185             //PI text
186
String JavaDoc documentText = getPIText();
187             documentText = documentText.length() > TEXT_MAX_LEN ? documentText.substring(0,TEXT_MAX_LEN) + "..." : documentText;
188             return documentText;
189         } else if(de.getType().equals(XMLDocumentModelProvider.XML_DOCTYPE)) {
190             //limit the text length
191
String JavaDoc documentText = getDoctypeText();
192             String JavaDoc visibleText = documentText.length() > TEXT_MAX_LEN ? documentText.substring(0,TEXT_MAX_LEN) + "..." : documentText;
193             return visibleText;
194         } else if(de.getType().equals(XMLDocumentModelProvider.XML_CDATA)) {
195             //limit the text length
196
String JavaDoc documentText = getCDATAText();
197             String JavaDoc visibleText = documentText.length() > TEXT_MAX_LEN ? documentText.substring(0,TEXT_MAX_LEN) + "..." : documentText;
198             return visibleText;
199         }
200         
201         return de.getName() + " [unknown content]";
202     }
203     
204     public String JavaDoc getToolTipText() {
205         if(de.getType().equals(XMLDocumentModelProvider.XML_TAG)
206         || de.getType().equals(XMLDocumentModelProvider.XML_EMPTY_TAG)) {
207             return getAttribsText() + " " + getDocumentContent();
208         } else if(de.getType().equals(XMLDocumentModelProvider.XML_PI)) {
209             return getPIText();
210         } else if(de.getType().equals(XMLDocumentModelProvider.XML_DOCTYPE)) {
211             return getDoctypeText();
212         } else if(de.getType().equals(XMLDocumentModelProvider.XML_CDATA)) {
213             return getCDATAText();
214         }
215         return "";
216     }
217     
218     private String JavaDoc getPIText() {
219         String JavaDoc documentText = null;
220         try {
221             documentText = de.getDocumentModel().getDocument().getText(de.getStartOffset(), de.getEndOffset() - de.getStartOffset());
222             //cut the leading PI name and the <?
223
int index = "<?".length() + de.getName().length();
224             if(index > (documentText.length() - 1)) index = documentText.length() - 1;
225             if(documentText.length() > 0) documentText = documentText.substring(index, documentText.length() - 1).trim();
226         }catch(BadLocationException JavaDoc e) {
227             return "???";
228         }
229         return documentText;
230     }
231     
232     private String JavaDoc getDoctypeText() {
233         String JavaDoc documentText = "???";
234         try {
235             documentText = de.getDocumentModel().getDocument().getText(de.getStartOffset(), de.getEndOffset() - de.getStartOffset());
236             //cut the leading PI name and the <?
237
if(documentText.length() > 0) documentText = documentText.substring("<!DOCTYPE ".length() + de.getName().length(), documentText.length() - 1).trim();
238         }catch(BadLocationException JavaDoc e) {
239             return "???";
240         }
241         return documentText;
242     }
243     
244     private String JavaDoc getCDATAText() {
245         String JavaDoc documentText = "???";
246         try {
247             documentText = de.getDocumentModel().getDocument().getText(de.getStartOffset(), de.getEndOffset() - de.getStartOffset());
248             //cut the leading PI name and the <?
249
if(documentText.length() > 0) documentText = documentText.substring("<![CDATA[".length(), documentText.length() - "]]>".length()).trim();
250         }catch(BadLocationException JavaDoc e) {
251             return "???";
252         }
253         return documentText;
254     }
255     
256     public void childrenReordered(DocumentElementEvent ce) {
257         //notify treemodel - do that in event dispath thread
258
tm.nodeStructureChanged(TreeNodeAdapter.this);
259     }
260     
261     public String JavaDoc getAttribsText() {
262         StringBuffer JavaDoc attribsText = new StringBuffer JavaDoc();
263         Enumeration JavaDoc attrNames = getDocumentElement().getAttributes().getAttributeNames();
264         if(attrNames.hasMoreElements()) {
265             while(attrNames.hasMoreElements()) {
266                 String JavaDoc aname = (String JavaDoc)attrNames.nextElement();
267                 String JavaDoc value = (String JavaDoc)getDocumentElement().getAttributes().getAttribute(aname);
268                 attribsText.append(aname);
269                 attribsText.append("=\"");
270                 attribsText.append(value);
271                 attribsText.append("\"");
272                 if(attrNames.hasMoreElements()) attribsText.append(", ");
273             }
274         }
275         return attribsText.toString();
276     }
277     
278     public void elementAdded(DocumentElementEvent e) {
279         DocumentElement ade = e.getChangedChild();
280         
281         if(debug) System.out.println(">>> +EVENT called on " + hashCode() + " - " + de + ": element " + ade + " is going to be added");
282         if(ade.getType().equals(XMLDocumentModelProvider.XML_CONTENT)) {
283             //create a text node listener
284
textElements.add(new TextElementWrapper(ade));
285             //update text text content of the node
286
childTextElementChanged();
287         } else if(ade.getType().equals(XMLDocumentModelProvider.XML_ERROR)) {
288             //handle error element
289
markNodeAsError(this);
290         } else if(ade.getType().equals(XMLDocumentModelProvider.XML_COMMENT)) {
291             //do nothing for comments
292
} else {
293             TreeNode JavaDoc tn = new TreeNodeAdapter(ade, tm, tree, this);
294             int insertIndex = getVisibleChildIndex(ade);
295             //add the element only when there isn't such one
296
if(getChildTreeNode(ade) == null) {
297                 //check whether the insert index doesn't go beyond the actual children length (which states an error)
298
if(children.size() < insertIndex /*||
299                         children.size() + 1 /* it doesn't contain the currently added element != getDocumentElement().getElementCount()*/
) {
300                     //error => try to recover by refreshing the current node
301
//debugError(e);
302
//notify treemodel
303
tm.nodeStructureChanged(TreeNodeAdapter.this);
304                 } else {
305                     children.add(insertIndex, tn);
306                     final int tnIndex = getIndex(tn);
307                     tm.nodesWereInserted(TreeNodeAdapter.this, new int[]{tnIndex});
308                 }
309             }
310             if(debug)System.out.println("<<<EVENT finished (node " + tn + " added)");
311         }
312         
313         //fix: if a new nodes are added into the root element (set as invisible), the navigator
314
//window is empty. So we need to always expand the root element when adding something into
315
if(de.equals(de.getDocumentModel().getRootElement())) {
316             //expand path
317
tree.expandPath(new TreePath JavaDoc(this));
318         }
319         
320     }
321     
322     private void debugError(DocumentElementEvent e) {
323         StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
324         sb.append("An inconsistency between XML navigator and XML DocumentModel occured when adding a new element in the XML DocumentModel! Please report the problem and add following debug messages to the issue along with the XML document you are editing.");
325         sb.append("Debug for Node " + this + ":\n"); //NOI18N
326
sb.append("Children of current node:\n"); //NOI18N
327
Iterator JavaDoc itr = children.iterator();
328         while(itr.hasNext()) {
329             TreeNodeAdapter tna = (TreeNodeAdapter)itr.next();
330             sb.append(tna.toString());
331             sb.append("\n"); //NOI18N
332
}
333         sb.append("\nChildren of DocumentElement (" + getDocumentElement() + ") wrapped by the current node:\n"); //NOI18N
334
Iterator JavaDoc currChildrenItr = getDocumentElement().getChildren().iterator();
335         while(itr.hasNext()) {
336             DocumentElement de = (DocumentElement)itr.next();
337             sb.append(de.toString());
338             sb.append("\n"); //NOI18N
339
}
340         sb.append("------------"); //NOI18N
341

342         ErrorManager.getDefault().log(ErrorManager.INFORMATIONAL, sb.toString());
343     }
344     
345     private void markNodeAsError(final TreeNodeAdapter tna) {
346         tna.containsError = true;
347         //mark all its ancestors as "childrenContainsError"
348
TreeNodeAdapter parent = tna;
349         tm.nodeChanged(tna);
350         while((parent = (TreeNodeAdapter)parent.getParent()) != null) {
351             if(parent.getParent() != null) parent.childrenErrorCount++; //do not fire for root element
352
tm.nodeChanged(parent);
353         }
354     }
355     
356     private int getVisibleChildIndex(DocumentElement de) {
357         int index = 0;
358         Iterator JavaDoc children = getDocumentElement().getChildren().iterator();
359         while(children.hasNext()) {
360             DocumentElement child = (DocumentElement)children.next();
361             if(child.equals(de)) return index;
362             
363             //skip text and error tokens
364
if(!child.getType().equals(XMLDocumentModelProvider.XML_CONTENT)
365             && !child.getType().equals(XMLDocumentModelProvider.XML_ERROR)
366             && !child.getType().equals(XMLDocumentModelProvider.XML_COMMENT)) index++;
367         }
368         return -1;
369     }
370     
371     public void elementRemoved(DocumentElementEvent e) {
372         DocumentElement rde = e.getChangedChild();
373         
374         if(debug) System.out.println(">>> -EVENT on " + hashCode() + " - " + de + ": element " + rde + " is going to be removed ");
375         
376         if(rde.getType().equals(XMLDocumentModelProvider.XML_CONTENT)) {
377             if(debug) System.out.println(">>> removing CONTENT element");
378             //remove the text eleemnt listener
379
Iterator JavaDoc i = textElements.iterator();
380             ArrayList JavaDoc toRemove = new ArrayList JavaDoc();
381             while(i.hasNext()) {
382                 TextElementWrapper tew = (TextElementWrapper)i.next();
383                 if(rde.equals(tew.getDocumentElement())) toRemove.add(tew);
384             }
385             textElements.removeAll(toRemove);
386             //update text text content of the node
387
childTextElementChanged();
388         } else if(rde.getType().equals(XMLDocumentModelProvider.XML_ERROR)) {
389             unmarkNodeAsError(this);
390         } else if(rde.getType().equals(XMLDocumentModelProvider.XML_COMMENT)) {
391             //do nothing for comments
392
} else {
393             if(debug) System.out.println(">>> removing tag element");
394             final TreeNode JavaDoc tn = getChildTreeNode(rde);
395             final int tnIndex = getIndex(tn);
396             
397             if(tn != null) {
398                 children.remove(tn);
399                 //notify treemodel - do that in event dispath thread
400
tm.nodesWereRemoved(TreeNodeAdapter.this, new int[]{tnIndex}, new Object JavaDoc[]{tn});
401             } else if(debug) System.out.println("Warning: TreeNode for removed element doesn't exist!!!");
402             
403         }
404         if(debug) System.out.println("<<<EVENT finished (node removed)");
405     }
406     
407     private void unmarkNodeAsError(final TreeNodeAdapter tna) {
408         //handle error element
409
tna.containsError = false;
410         //unmark all its ancestors as "childrenContainsError"
411
TreeNodeAdapter parent = tna;
412         tm.nodeChanged(tna);
413         while((parent = (TreeNodeAdapter)parent.getParent()) != null) {
414             if(parent.getParent() != null) parent.childrenErrorCount--; //do not fire for root element
415
tm.nodeChanged(parent);
416         }
417     }
418     
419     public void attributesChanged(DocumentElementEvent e) {
420         if(debug)System.out.println("Attributes of treenode " + this + " has changed.");
421         tm.nodeChanged(TreeNodeAdapter.this);
422     }
423     
424     public void contentChanged(DocumentElementEvent e) {
425         if(debug) System.out.println("treenode " + this + " changed.");
426         tm.nodeChanged(TreeNodeAdapter.this);
427     }
428     
429     //---- private -----
430

431     private void checkChildrenAdapters() {
432         synchronized (childrenLock) {
433             if(children == null) {
434                 //attach myself to the document element as a listener
435
de.addDocumentElementListener(this);
436                 
437                 //lazyloading children for node
438
children = new ArrayList JavaDoc();
439                 Iterator JavaDoc i = de.getChildren().iterator();
440                 boolean textElementAdded = false;
441                 while(i.hasNext()) {
442                     DocumentElement chde = (DocumentElement)i.next();
443                     if(chde.getType().equals(XMLDocumentModelProvider.XML_CONTENT)) {
444                         //create a text node listener
445
textElements.add(new TextElementWrapper(chde));
446                         textElementAdded = true;
447                     } else if(chde.getType().equals(XMLDocumentModelProvider.XML_ERROR)) {
448                         markNodeAsError(this);
449                     } else if(chde.getType().equals(XMLDocumentModelProvider.XML_COMMENT)) {
450                         //do nothing for comments
451
} else {
452                         //add the adapter only when there isn't any
453
if(getChildTreeNode(chde) == null) {
454                             TreeNodeAdapter tna = new TreeNodeAdapter(chde, tm, tree, this);
455                             children.add(tna);
456                         }
457                     }
458                 }
459                 //update text text content of the node
460
if(textElementAdded) childTextElementChanged();
461             }
462         }
463     }
464     
465     private void childTextElementChanged() {
466         textContent = null;
467         tm.nodeChanged(this);
468     }
469
470     //generate this node text if one of its nodes has changed
471
private void checkDocumentContent() {
472         if(textContent == null) {
473             //get all text elements children of the document element held by this node
474
Iterator JavaDoc children = getDocumentElement().getChildren().iterator();
475             StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
476             //System.out.println("childTextElementChanged(): " + getDocumentElement() + " has these children:");
477
while(children.hasNext()) {
478                 DocumentElement del = (DocumentElement)children.next();
479                 if(del.getType().equals(XMLDocumentModelProvider.XML_CONTENT)) {
480                     try {
481                         //the endoffset if increased by +1 due to still not yet resolved issue with element boundaries
482
//should be removed once it is properly fixed. On the other hand the issue has no user impact now.
483
int endOfs = del.getEndOffset() - del.getStartOffset() + 1;
484                         //check document boundary - the condition should never be true
485
endOfs = endOfs > del.getDocument().getLength() ? del.getDocument().getLength() : endOfs;
486                         buf.append((del.getDocument().getText(del.getStartOffset(), endOfs)).trim());
487                     }catch(BadLocationException JavaDoc e) {
488                         buf.append("???");
489                     }
490                 }
491             }
492             textContent = buf.toString();
493             //fire a change event for this node
494
tm.nodeChanged(TreeNodeAdapter.this);
495         }
496     }
497     
498     private final class TextElementWrapper implements DocumentElementListener {
499         
500         private DocumentElement de;
501         
502         public TextElementWrapper(DocumentElement de) {
503             this.de = de;
504             de.addDocumentElementListener(TextElementWrapper.this);
505         }
506         
507         public DocumentElement getDocumentElement() {
508             return de;
509         }
510         
511         public void contentChanged(DocumentElementEvent e) {
512             TreeNodeAdapter.this.childTextElementChanged();
513         }
514         
515         //no need to implement these methods
516
public void elementAdded(DocumentElementEvent e) {
517             //just a test
518
System.err.println("????? a child node added into a text element!!!!");
519         }
520         public void elementRemoved(DocumentElementEvent e) {}
521         public void childrenReordered(DocumentElementEvent e) {}
522         public void attributesChanged(DocumentElementEvent e) {}
523         
524     }
525     
526     private static final boolean debug = Boolean.getBoolean("org.netbeans.modules.xml.text.structure.debug");
527     
528     private static final int ATTRIBS_MAX_LEN = 30;
529     private static final int TEXT_MAX_LEN = ATTRIBS_MAX_LEN;
530     
531 }
532
Popular Tags