1 19 20 package org.netbeans.modules.xml.text.navigator; 21 22 import java.util.ArrayList ; 23 import java.util.Collections ; 24 import java.util.Enumeration ; 25 import java.util.HashMap ; 26 import java.util.Iterator ; 27 import javax.swing.JTree ; 28 import javax.swing.text.AttributeSet ; 29 import javax.swing.text.BadLocationException ; 30 import javax.swing.text.Document ; 31 import javax.swing.tree.DefaultTreeModel ; 32 import javax.swing.tree.TreeNode ; 33 import javax.swing.tree.TreePath ; 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 48 public class TreeNodeAdapter implements TreeNode , DocumentElementListener { 49 50 private DocumentElement de; 51 private DefaultTreeModel tm; 52 private TreeNode parent; 53 private JTree tree; 54 55 private ArrayList children = null; private ArrayList textElements = new ArrayList (); private Object childrenLock = new Object (); 58 private String textContent = new String (); 59 60 private boolean containsError = false; 62 private int childrenErrorCount = 0; 64 65 public TreeNodeAdapter(DocumentElement de, DefaultTreeModel tm, JTree tree, TreeNode parent) { 66 this.de = de; 67 this.tm = tm; 68 this.tree = tree; 69 this.parent = parent; 70 } 71 72 77 public String getDocumentContent() { 78 checkChildrenAdapters(); 79 checkDocumentContent(); 80 return textContent; 81 } 82 83 public java.util.Enumeration children() { 84 checkChildrenAdapters(); 85 return Collections.enumeration(children); 86 } 87 88 public boolean getAllowsChildren() { 89 return true; 90 } 91 92 public TreeNode getChildAt(int param) { 93 checkChildrenAdapters(); 94 return (TreeNode )children.get(param); 95 } 96 97 public int getChildCount() { 98 checkChildrenAdapters(); 99 return children.size(); 100 } 101 102 public int getIndex(TreeNode treeNode) { 103 checkChildrenAdapters(); 104 return children.indexOf(treeNode); 105 } 106 107 public TreeNode 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 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 public int getChildrenErrorCount() { 136 checkChildrenAdapters(); 137 return childrenErrorCount; 138 } 139 140 public String toString() { 141 return getText(false); 142 } 143 144 public String getText(boolean html) { 145 if(de.getType().equals(XMLDocumentModelProvider.XML_TAG) 146 || de.getType().equals(XMLDocumentModelProvider.XML_EMPTY_TAG)) { 147 String attribsVisibleText = ""; 149 AttributeSet attribs = getDocumentElement().getAttributes(); 150 151 if(attribs.getAttributeCount() > 0) { 152 String 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 contentText = ""; 159 String documentText = getDocumentContent(); 160 if(NavigatorContent.showContent) { 161 contentText = documentText.length() > TEXT_MAX_LEN ? documentText.substring(0,TEXT_MAX_LEN) + "..." : documentText; 162 } 163 164 StringBuffer text = new StringBuffer (); 165 text.append(html ? "<html>" : ""); 166 text.append(html && containsError ? "<font color=FF0000><b>": ""); text.append(getDocumentElement().getName()); 168 text.append(html && containsError ? "</b></font>": ""); 169 text.append(html ? "<font color=888888>" : ""); 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 String 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 String documentText = getDoctypeText(); 192 String 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 String documentText = getCDATAText(); 197 String 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 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 getPIText() { 219 String documentText = null; 220 try { 221 documentText = de.getDocumentModel().getDocument().getText(de.getStartOffset(), de.getEndOffset() - de.getStartOffset()); 222 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 e) { 227 return "???"; 228 } 229 return documentText; 230 } 231 232 private String getDoctypeText() { 233 String documentText = "???"; 234 try { 235 documentText = de.getDocumentModel().getDocument().getText(de.getStartOffset(), de.getEndOffset() - de.getStartOffset()); 236 if(documentText.length() > 0) documentText = documentText.substring("<!DOCTYPE ".length() + de.getName().length(), documentText.length() - 1).trim(); 238 }catch(BadLocationException e) { 239 return "???"; 240 } 241 return documentText; 242 } 243 244 private String getCDATAText() { 245 String documentText = "???"; 246 try { 247 documentText = de.getDocumentModel().getDocument().getText(de.getStartOffset(), de.getEndOffset() - de.getStartOffset()); 248 if(documentText.length() > 0) documentText = documentText.substring("<![CDATA[".length(), documentText.length() - "]]>".length()).trim(); 250 }catch(BadLocationException e) { 251 return "???"; 252 } 253 return documentText; 254 } 255 256 public void childrenReordered(DocumentElementEvent ce) { 257 tm.nodeStructureChanged(TreeNodeAdapter.this); 259 } 260 261 public String getAttribsText() { 262 StringBuffer attribsText = new StringBuffer (); 263 Enumeration attrNames = getDocumentElement().getAttributes().getAttributeNames(); 264 if(attrNames.hasMoreElements()) { 265 while(attrNames.hasMoreElements()) { 266 String aname = (String )attrNames.nextElement(); 267 String value = (String )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 textElements.add(new TextElementWrapper(ade)); 285 childTextElementChanged(); 287 } else if(ade.getType().equals(XMLDocumentModelProvider.XML_ERROR)) { 288 markNodeAsError(this); 290 } else if(ade.getType().equals(XMLDocumentModelProvider.XML_COMMENT)) { 291 } else { 293 TreeNode tn = new TreeNodeAdapter(ade, tm, tree, this); 294 int insertIndex = getVisibleChildIndex(ade); 295 if(getChildTreeNode(ade) == null) { 297 if(children.size() < insertIndex ) { 300 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 if(de.equals(de.getDocumentModel().getRootElement())) { 316 tree.expandPath(new TreePath (this)); 318 } 319 320 } 321 322 private void debugError(DocumentElementEvent e) { 323 StringBuffer sb = new StringBuffer (); 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"); sb.append("Children of current node:\n"); Iterator itr = children.iterator(); 328 while(itr.hasNext()) { 329 TreeNodeAdapter tna = (TreeNodeAdapter)itr.next(); 330 sb.append(tna.toString()); 331 sb.append("\n"); } 333 sb.append("\nChildren of DocumentElement (" + getDocumentElement() + ") wrapped by the current node:\n"); Iterator currChildrenItr = getDocumentElement().getChildren().iterator(); 335 while(itr.hasNext()) { 336 DocumentElement de = (DocumentElement)itr.next(); 337 sb.append(de.toString()); 338 sb.append("\n"); } 340 sb.append("------------"); 342 ErrorManager.getDefault().log(ErrorManager.INFORMATIONAL, sb.toString()); 343 } 344 345 private void markNodeAsError(final TreeNodeAdapter tna) { 346 tna.containsError = true; 347 TreeNodeAdapter parent = tna; 349 tm.nodeChanged(tna); 350 while((parent = (TreeNodeAdapter)parent.getParent()) != null) { 351 if(parent.getParent() != null) parent.childrenErrorCount++; tm.nodeChanged(parent); 353 } 354 } 355 356 private int getVisibleChildIndex(DocumentElement de) { 357 int index = 0; 358 Iterator children = getDocumentElement().getChildren().iterator(); 359 while(children.hasNext()) { 360 DocumentElement child = (DocumentElement)children.next(); 361 if(child.equals(de)) return index; 362 363 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 Iterator i = textElements.iterator(); 380 ArrayList toRemove = new ArrayList (); 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 childTextElementChanged(); 388 } else if(rde.getType().equals(XMLDocumentModelProvider.XML_ERROR)) { 389 unmarkNodeAsError(this); 390 } else if(rde.getType().equals(XMLDocumentModelProvider.XML_COMMENT)) { 391 } else { 393 if(debug) System.out.println(">>> removing tag element"); 394 final TreeNode tn = getChildTreeNode(rde); 395 final int tnIndex = getIndex(tn); 396 397 if(tn != null) { 398 children.remove(tn); 399 tm.nodesWereRemoved(TreeNodeAdapter.this, new int[]{tnIndex}, new Object []{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 tna.containsError = false; 410 TreeNodeAdapter parent = tna; 412 tm.nodeChanged(tna); 413 while((parent = (TreeNodeAdapter)parent.getParent()) != null) { 414 if(parent.getParent() != null) parent.childrenErrorCount--; 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 431 private void checkChildrenAdapters() { 432 synchronized (childrenLock) { 433 if(children == null) { 434 de.addDocumentElementListener(this); 436 437 children = new ArrayList (); 439 Iterator 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 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 } else { 452 if(getChildTreeNode(chde) == null) { 454 TreeNodeAdapter tna = new TreeNodeAdapter(chde, tm, tree, this); 455 children.add(tna); 456 } 457 } 458 } 459 if(textElementAdded) childTextElementChanged(); 461 } 462 } 463 } 464 465 private void childTextElementChanged() { 466 textContent = null; 467 tm.nodeChanged(this); 468 } 469 470 private void checkDocumentContent() { 472 if(textContent == null) { 473 Iterator children = getDocumentElement().getChildren().iterator(); 475 StringBuffer buf = new StringBuffer (); 476 while(children.hasNext()) { 478 DocumentElement del = (DocumentElement)children.next(); 479 if(del.getType().equals(XMLDocumentModelProvider.XML_CONTENT)) { 480 try { 481 int endOfs = del.getEndOffset() - del.getStartOffset() + 1; 484 endOfs = endOfs > del.getDocument().getLength() ? del.getDocument().getLength() : endOfs; 486 buf.append((del.getDocument().getText(del.getStartOffset(), endOfs)).trim()); 487 }catch(BadLocationException e) { 488 buf.append("???"); 489 } 490 } 491 } 492 textContent = buf.toString(); 493 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 public void elementAdded(DocumentElementEvent e) { 517 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 |