Clover coverage report -
Coverage timestamp: Sun Apr 18 2004 21:32:30 EDT
file stats: LOC: 586   Methods: 24
NCLOC: 326   Classes: 2
 
 Source file Conditionals Statements Methods TOTAL
ElementTreePanel.java 14.3% 25.4% 29.2% 22.6%
coverage coverage
 1   
 /*
 2   
  * @(#)ElementTreePanel.java    1.9 99/04/23
 3   
  *
 4   
  * Copyright (c) 1998, 1999 by Sun Microsystems, Inc. All Rights Reserved.
 5   
  *
 6   
  * Sun grants you ("Licensee") a non-exclusive, royalty free, license to use,
 7   
  * modify and redistribute this software in source and binary code form,
 8   
  * provided that i) this copyright notice and license appear on all copies of
 9   
  * the software; and ii) Licensee does not utilize the software in a manner
 10   
  * which is disparaging to Sun.
 11   
  *
 12   
  * This software is provided "AS IS," without a warranty of any kind. ALL
 13   
  * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING ANY
 14   
  * IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR
 15   
  * NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS SHALL NOT BE
 16   
  * LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING
 17   
  * OR DISTRIBUTING THE SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS
 18   
  * LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT,
 19   
  * INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER
 20   
  * CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF
 21   
  * OR INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
 22   
  * POSSIBILITY OF SUCH DAMAGES.
 23   
  *
 24   
  * This software is not designed or intended for use in on-line control of
 25   
  * aircraft, air traffic, aircraft navigation or aircraft communications; or in
 26   
  * the design, construction, operation or maintenance of any nuclear
 27   
  * facility. Licensee represents and warrants that it will not use or
 28   
  * redistribute the Software for such purposes.
 29   
  */
 30   
 
 31   
 import javax.swing.*;
 32   
 import javax.swing.event.*;
 33   
 import javax.swing.text.*;
 34   
 import javax.swing.tree.*;
 35   
 import javax.swing.undo.*;
 36   
 import java.awt.*;
 37   
 import java.beans.*;
 38   
 import java.util.*;
 39   
 
 40   
 /**
 41   
  * Displays a tree showing all the elements in a text Document. Selecting
 42   
  * a node will result in reseting the selection of the JTextComponent.
 43   
  * This also becomes a CaretListener to know when the selection has changed
 44   
  * in the text to update the selected item in the tree.
 45   
  *
 46   
  * @author Scott Violet
 47   
  * @version 1.9 04/23/99
 48   
  */
 49   
 public class ElementTreePanel extends JPanel implements CaretListener, DocumentListener, PropertyChangeListener, TreeSelectionListener {
 50   
     /** Tree showing the documents element structure. */
 51   
     protected JTree             tree;
 52   
     /** Text component showing elemenst for. */
 53   
     protected JTextComponent    editor;
 54   
     /** Model for the tree. */
 55   
     protected ElementTreeModel  treeModel;
 56   
     /** Set to true when updatin the selection. */
 57   
     protected boolean           updatingSelection;
 58   
 
 59  2
     public ElementTreePanel(JTextComponent editor) {
 60  2
     this.editor = editor;
 61   
 
 62  2
     Document document = editor.getDocument();
 63   
 
 64   
     // Create the tree.
 65  2
     treeModel = new ElementTreeModel(document);
 66  2
     tree = new JTree(treeModel) {
 67  22
         public String convertValueToText(Object value, boolean selected,
 68   
                          boolean expanded, boolean leaf,
 69   
                          int row, boolean hasFocus) {
 70   
         // Should only happen for the root
 71  22
         if(!(value instanceof Element))
 72  4
             return value.toString();
 73   
 
 74  18
         Element        e = (Element)value;
 75  18
         AttributeSet   as = e.getAttributes().copyAttributes();
 76  18
         String         asString;
 77   
 
 78  18
         if(as != null) {
 79  18
             StringBuffer       retBuffer = new StringBuffer("[");
 80  18
             Enumeration        names = as.getAttributeNames();
 81   
 
 82  18
             while(names.hasMoreElements()) {
 83  9
             Object        nextName = names.nextElement();
 84   
 
 85  9
             if(nextName != StyleConstants.ResolveAttribute) {
 86  9
                 retBuffer.append(" ");
 87  9
                 retBuffer.append(nextName);
 88  9
                 retBuffer.append("=");
 89  9
                 retBuffer.append(as.getAttribute(nextName));
 90   
             }
 91   
             }
 92  18
             retBuffer.append(" ]");
 93  18
             asString = retBuffer.toString();
 94   
         }
 95   
         else
 96  0
             asString = "[ ]";
 97   
 
 98  18
         if(e.isLeaf())
 99  0
             return e.getName() + " [" + e.getStartOffset() +
 100   
             ", " + e.getEndOffset() +"] Attributes: " + asString;
 101  18
         return e.getName() + " [" + e.getStartOffset() +
 102   
             ", " + e.getEndOffset() + "] Attributes: " +
 103   
                  asString;
 104   
         }
 105   
     };
 106  2
     tree.addTreeSelectionListener(this);
 107   
     // Don't show the root, it is fake.
 108  2
     tree.setRootVisible(false);
 109   
     // Since the display value of every node after the insertion point
 110   
     // changes every time the text changes and we don't generate a change
 111   
     // event for all those nodes the display value can become off.
 112   
     // This can be seen as '...' instead of the complete string value.
 113   
     // This is a temporary workaround, increase the needed size by 15,
 114   
     // hoping that will be enough.
 115  2
     tree.setCellRenderer(new DefaultTreeCellRenderer() {
 116  8
         public Dimension getPreferredSize() {
 117  8
         Dimension retValue = super.getPreferredSize();
 118  8
         if(retValue != null)
 119  8
             retValue.width += 15;
 120  8
         return retValue;
 121   
         }
 122   
     });
 123   
     // become a listener on the document to update the tree.
 124  2
     document.addDocumentListener(this);
 125   
 
 126   
     // become a PropertyChangeListener to know when the Document has
 127   
     // changed.
 128  2
     editor.addPropertyChangeListener(this);
 129   
 
 130   
     // Become a CaretListener
 131  2
     editor.addCaretListener(this);
 132   
 
 133   
     // configure the panel and frame containing it.
 134  2
     setLayout(new BorderLayout());
 135  2
     add(new JScrollPane(tree), BorderLayout.CENTER);
 136   
 
 137   
     // Add a label above tree to describe what is being shown
 138  2
     JLabel     label = new JLabel("Elements that make up the current document", SwingConstants.CENTER);
 139   
 
 140  2
     label.setFont(new Font("Dialog", Font.BOLD, 14));
 141  2
     add(label, BorderLayout.NORTH);
 142   
 
 143  2
     setPreferredSize(new Dimension(400, 400));
 144   
     }
 145   
 
 146   
     /**
 147   
      * Resets the JTextComponent to <code>editor</code>. This will update
 148   
      * the tree accordingly.
 149   
      */
 150  0
     public void setEditor(JTextComponent editor) {
 151  0
     if (this.editor == editor) {
 152  0
         return;
 153   
     }
 154   
 
 155  0
     if (this.editor != null) {
 156  0
         Document      oldDoc = this.editor.getDocument();
 157   
 
 158  0
         oldDoc.removeDocumentListener(this);
 159  0
         this.editor.removePropertyChangeListener(this);
 160  0
         this.editor.removeCaretListener(this);
 161   
     }
 162  0
     this.editor = editor;
 163  0
     if (editor == null) {
 164  0
         treeModel = null;
 165  0
         tree.setModel(null);
 166   
     }
 167   
     else {
 168  0
         Document   newDoc = editor.getDocument();
 169   
 
 170  0
         newDoc.addDocumentListener(this);
 171  0
         editor.addPropertyChangeListener(this);
 172  0
         editor.addCaretListener(this);
 173  0
         treeModel = new ElementTreeModel(newDoc);
 174  0
         tree.setModel(treeModel);
 175   
     }
 176   
     }
 177   
 
 178   
     // PropertyChangeListener
 179   
 
 180   
     /**
 181   
      * Invoked when a property changes. We are only interested in when the
 182   
      * Document changes to reset the DocumentListener.
 183   
      */
 184  0
     public void propertyChange(PropertyChangeEvent e) {
 185  0
     if (e.getSource() == getEditor() &&
 186   
         e.getPropertyName().equals("document")) {
 187  0
         JTextComponent      editor = getEditor();
 188  0
         Document            oldDoc = (Document)e.getOldValue();
 189  0
         Document            newDoc = (Document)e.getNewValue();
 190   
 
 191   
         // Reset the DocumentListener
 192  0
         oldDoc.removeDocumentListener(this);
 193  0
         newDoc.addDocumentListener(this);
 194   
 
 195   
         // Recreate the TreeModel.
 196  0
         treeModel = new ElementTreeModel(newDoc);
 197  0
         tree.setModel(treeModel);
 198   
     }
 199   
     }
 200   
 
 201   
 
 202   
     // DocumentListener
 203   
 
 204   
     /**
 205   
      * Gives notification that there was an insert into the document.  The
 206   
      * given range bounds the freshly inserted region.
 207   
      *
 208   
      * @param e the document event
 209   
      */
 210  0
     public void insertUpdate(DocumentEvent e) {
 211  0
     updateTree(e);
 212   
     }
 213   
 
 214   
     /**
 215   
      * Gives notification that a portion of the document has been
 216   
      * removed.  The range is given in terms of what the view last
 217   
      * saw (that is, before updating sticky positions).
 218   
      *
 219   
      * @param e the document event
 220   
      */
 221  0
     public void removeUpdate(DocumentEvent e) {
 222  0
     updateTree(e);
 223   
     }
 224   
 
 225   
     /**
 226   
      * Gives notification that an attribute or set of attributes changed.
 227   
      *
 228   
      * @param e the document event
 229   
      */
 230  0
     public void changedUpdate(DocumentEvent e) {
 231  0
     updateTree(e);
 232   
     }
 233   
 
 234   
     // CaretListener
 235   
 
 236   
     /**
 237   
      * Messaged when the selection in the editor has changed. Will update
 238   
      * the selection in the tree.
 239   
      */
 240  0
     public void caretUpdate(CaretEvent e) {
 241  0
     if(!updatingSelection) {
 242  0
         JTextComponent     editor = getEditor();
 243  0
         int                selBegin = Math.min(e.getDot(), e.getMark());
 244  0
         int                end = Math.max(e.getDot(), e.getMark());
 245  0
         Vector             paths = new Vector();
 246  0
         TreeModel          model = getTreeModel();
 247  0
         Object             root = model.getRoot();
 248  0
         int                rootCount = model.getChildCount(root);
 249   
 
 250   
         // Build an array of all the paths to all the character elements
 251   
         // in the selection.
 252  0
         for(int counter = 0; counter < rootCount; counter++) {
 253  0
         int            start = selBegin;
 254   
 
 255  0
         while(start <= end) {
 256  0
             TreePath    path = getPathForIndex(start, root,
 257   
                        (Element)model.getChild(root, counter));
 258  0
             Element     charElement = (Element)path.
 259   
                                    getLastPathComponent();
 260   
 
 261  0
             paths.addElement(path);
 262  0
             if(start >= charElement.getEndOffset())
 263  0
             start++;
 264   
             else
 265  0
             start = charElement.getEndOffset();
 266   
         }
 267   
         }
 268   
 
 269   
         // If a path was found, select it (them).
 270  0
         int               numPaths = paths.size();
 271   
 
 272  0
         if(numPaths > 0) {
 273  0
         TreePath[]    pathArray = new TreePath[numPaths];
 274   
 
 275  0
         paths.copyInto(pathArray);
 276  0
         updatingSelection = true;
 277  0
         try {
 278  0
             getTree().setSelectionPaths(pathArray);
 279  0
             getTree().scrollPathToVisible(pathArray[0]);
 280   
         }
 281   
         finally {
 282  0
             updatingSelection = false;
 283   
         }
 284   
         }
 285   
     }
 286   
     }
 287   
 
 288   
     // TreeSelectionListener
 289   
 
 290   
     /**
 291   
       * Called whenever the value of the selection changes.
 292   
       * @param e the event that characterizes the change.
 293   
       */
 294  0
     public void valueChanged(TreeSelectionEvent e) {
 295  0
     JTree       tree = getTree();
 296   
 
 297  0
     if(!updatingSelection && tree.getSelectionCount() == 1) {
 298  0
         TreePath      selPath = tree.getSelectionPath();
 299  0
         Object        lastPathComponent = selPath.getLastPathComponent();
 300   
 
 301  0
         if(!(lastPathComponent instanceof DefaultMutableTreeNode)) {
 302  0
         Element       selElement = (Element)lastPathComponent;
 303   
 
 304  0
         updatingSelection = true;
 305  0
         try {
 306  0
             getEditor().select(selElement.getStartOffset(),
 307   
                        selElement.getEndOffset());
 308   
         }
 309   
         finally {
 310  0
             updatingSelection = false;
 311   
         }
 312   
         }
 313   
     }
 314   
     }
 315   
 
 316   
     // Local methods
 317   
 
 318   
     /**
 319   
      * @return tree showing elements.
 320   
      */
 321  0
     protected JTree getTree() {
 322  0
     return tree;
 323   
     }
 324   
 
 325   
     /**
 326   
      * @return JTextComponent showing elements for.
 327   
      */
 328  0
     protected JTextComponent getEditor() {
 329  0
     return editor;
 330   
     }
 331   
 
 332   
     /**
 333   
      * @return TreeModel implementation used to represent the elements.
 334   
      */
 335  0
     public DefaultTreeModel getTreeModel() {
 336  0
     return treeModel;
 337   
     }
 338   
 
 339   
     /**
 340   
      * Updates the tree based on the event type. This will invoke either
 341   
      * updateTree with the root element, or handleChange.
 342   
      */
 343  0
     protected void updateTree(DocumentEvent event) {
 344  0
     updatingSelection = true;
 345  0
     try {
 346  0
         TreeModel        model = getTreeModel();
 347  0
         Object           root = model.getRoot();
 348   
 
 349  0
         for(int counter = model.getChildCount(root) - 1; counter >= 0;
 350   
         counter--) {
 351  0
         updateTree(event, (Element)model.getChild(root, counter));
 352   
         }
 353   
     }
 354   
     finally {
 355  0
         updatingSelection = false;
 356   
     }
 357   
     }
 358   
 
 359   
     /**
 360   
      * Creates TreeModelEvents based on the DocumentEvent and messages
 361   
      * the treemodel. This recursively invokes this method with children
 362   
      * elements.
 363   
      * @param event indicates what elements in the tree hierarchy have
 364   
      * changed.
 365   
      * @param element Current element to check for changes against.
 366   
      */
 367  0
     protected void updateTree(DocumentEvent event, Element element) {
 368  0
         DocumentEvent.ElementChange ec = event.getChange(element);
 369   
 
 370  0
         if (ec != null) {
 371  0
         Element[]       removed = ec.getChildrenRemoved();
 372  0
         Element[]       added = ec.getChildrenAdded();
 373  0
         int             startIndex = ec.getIndex();
 374   
 
 375   
         // Check for removed.
 376  0
         if(removed != null && removed.length > 0) {
 377  0
         int[]            indices = new int[removed.length];
 378   
 
 379  0
         for(int counter = 0; counter < removed.length; counter++) {
 380  0
             indices[counter] = startIndex + counter;
 381   
         }
 382  0
         getTreeModel().nodesWereRemoved((TreeNode)element, indices,
 383   
                         removed);
 384   
         }
 385   
         // check for added
 386  0
         if(added != null && added.length > 0) {
 387  0
         int[]            indices = new int[added.length];
 388   
 
 389  0
         for(int counter = 0; counter < added.length; counter++) {
 390  0
             indices[counter] = startIndex + counter;
 391   
         }
 392  0
         getTreeModel().nodesWereInserted((TreeNode)element, indices);
 393   
         }
 394   
         }
 395  0
     if(!element.isLeaf()) {
 396  0
         int        startIndex = element.getElementIndex
 397   
                                (event.getOffset());
 398  0
         int        elementCount = element.getElementCount();
 399  0
         int        endIndex = Math.min(elementCount - 1,
 400   
                        element.getElementIndex
 401   
                      (event.getOffset() + event.getLength()));
 402   
 
 403  0
         if(startIndex > 0 && startIndex < elementCount &&
 404   
            element.getElement(startIndex).getStartOffset() ==
 405   
            event.getOffset()) {
 406   
         // Force checking the previous element.
 407  0
         startIndex--;
 408   
         }
 409  0
         if(startIndex != -1 && endIndex != -1) {
 410  0
         for(int counter = startIndex; counter <= endIndex; counter++) {
 411  0
             updateTree(event, element.getElement(counter));
 412   
         }
 413   
         }
 414   
     }
 415   
     else {
 416   
         // Element is a leaf, assume it changed
 417  0
         getTreeModel().nodeChanged((TreeNode)element);
 418   
     }
 419   
     }
 420   
 
 421   
     /**
 422   
      * Returns a TreePath to the element at <code>position</code>.
 423   
      */
 424  0
     protected TreePath getPathForIndex(int position, Object root,
 425   
                        Element rootElement) {
 426  0
     TreePath         path = new TreePath(root);
 427  0
     Element          child = rootElement.getElement
 428   
                                 (rootElement.getElementIndex(position));
 429   
 
 430  0
     path = path.pathByAddingChild(rootElement);
 431  0
     path = path.pathByAddingChild(child);
 432  0
     while(!child.isLeaf()) {
 433  0
         child = child.getElement(child.getElementIndex(position));
 434  0
         path = path.pathByAddingChild(child);
 435   
     }
 436  0
     return path;
 437   
     }
 438   
 
 439   
 
 440   
     /**
 441   
      * ElementTreeModel is an implementation of TreeModel to handle displaying
 442   
      * the Elements from a Document. AbstractDocument.AbstractElement is
 443   
      * the default implementation used by the swing text package to implement
 444   
      * Element, and it implements TreeNode. This makes it trivial to create
 445   
      * a DefaultTreeModel rooted at a particular Element from the Document.
 446   
      * Unfortunately each Document can have more than one root Element.
 447   
      * Implying that to display all the root elements as a child of another
 448   
      * root a fake node has be created. This class creates a fake node as
 449   
      * the root with the children being the root elements of the Document
 450   
      * (getRootElements).
 451   
      * <p>This subclasses DefaultTreeModel. The majority of the TreeModel
 452   
      * methods have been subclassed, primarily to special case the root.
 453   
      */
 454   
     public static class ElementTreeModel extends DefaultTreeModel {
 455   
     protected Element[]         rootElements;
 456   
 
 457  2
     public ElementTreeModel(Document document) {
 458  2
         super(new DefaultMutableTreeNode("root"), false);
 459  2
         rootElements = document.getRootElements();
 460   
     }
 461   
 
 462   
     /**
 463   
      * Returns the child of <I>parent</I> at index <I>index</I> in
 464   
      * the parent's child array.  <I>parent</I> must be a node
 465   
      * previously obtained from this data source. This should
 466   
      * not return null if <i>index</i> is a valid index for
 467   
      * <i>parent</i> (that is <i>index</i> >= 0 && <i>index</i>
 468   
      * < getChildCount(<i>parent</i>)).
 469   
      *
 470   
      * @param   parent  a node in the tree, obtained from this data source
 471   
      * @return  the child of <I>parent</I> at index <I>index</I>
 472   
      */
 473  4
     public Object getChild(Object parent, int index) {
 474  4
         if(parent == root)
 475  4
         return rootElements[index];
 476  0
         return super.getChild(parent, index);
 477   
     }
 478   
 
 479   
 
 480   
     /**
 481   
      * Returns the number of children of <I>parent</I>.  Returns 0
 482   
      * if the node is a leaf or if it has no children.
 483   
      * <I>parent</I> must be a node previously obtained from this
 484   
      * data source.
 485   
      *
 486   
      * @param   parent  a node in the tree, obtained from this data source
 487   
      * @return  the number of children of the node <I>parent</I>
 488   
      */
 489  2
     public int getChildCount(Object parent) {
 490  2
         if(parent == root)
 491  2
         return rootElements.length;
 492  0
         return super.getChildCount(parent);
 493   
     }
 494   
 
 495   
 
 496   
     /**
 497   
      * Returns true if <I>node</I> is a leaf.  It is possible for
 498   
      * this method to return false even if <I>node</I> has no
 499   
      * children.  A directory in a filesystem, for example, may
 500   
      * contain no files; the node representing the directory is
 501   
      * not a leaf, but it also has no children.
 502   
      *
 503   
      * @param   node    a node in the tree, obtained from this data source
 504   
      * @return  true if <I>node</I> is a leaf
 505   
      */
 506  30
     public boolean isLeaf(Object node) {
 507  30
         if(node == root)
 508  12
         return false;
 509  18
         return super.isLeaf(node);
 510   
     }
 511   
 
 512   
     /**
 513   
      * Returns the index of child in parent.
 514   
      */
 515  0
     public int getIndexOfChild(Object parent, Object child) {
 516  0
         if(parent == root) {
 517  0
         for(int counter = rootElements.length - 1; counter >= 0;
 518   
             counter--) {
 519  0
             if(rootElements[counter] == child)
 520  0
             return counter;
 521   
         }
 522  0
         return -1;
 523   
         }
 524  0
         return super.getIndexOfChild(parent, child);
 525   
     }
 526   
 
 527   
     /**
 528   
      * Invoke this method after you've changed how node is to be
 529   
      * represented in the tree.
 530   
      */
 531  0
     public void nodeChanged(TreeNode node) {
 532  0
         if(listenerList != null && node != null) {
 533  0
         TreeNode         parent = node.getParent();
 534   
 
 535  0
         if(parent == null && node != root) {
 536  0
             parent = root;
 537   
         }
 538  0
         if(parent != null) {
 539  0
             int        anIndex = getIndexOfChild(parent, node);
 540   
 
 541  0
             if(anIndex != -1) {
 542  0
             int[]        cIndexs = new int[1];
 543   
 
 544  0
             cIndexs[0] = anIndex;
 545  0
             nodesChanged(parent, cIndexs);
 546   
             }
 547   
         }
 548   
         }
 549   
         }
 550   
 
 551   
     /**
 552   
      * Returns the path to a particluar node. This is recursive.
 553   
      */
 554  0
     protected TreeNode[] getPathToRoot(TreeNode aNode, int depth) {
 555  0
         TreeNode[]              retNodes;
 556   
 
 557   
         /* Check for null, in case someone passed in a null node, or
 558   
            they passed in an element that isn't rooted at root. */
 559  0
         if(aNode == null) {
 560  0
         if(depth == 0)
 561  0
             return null;
 562   
         else
 563  0
             retNodes = new TreeNode[depth];
 564   
         }
 565   
         else {
 566  0
         depth++;
 567  0
         if(aNode == root)
 568  0
             retNodes = new TreeNode[depth];
 569   
         else {
 570  0
             TreeNode parent = aNode.getParent();
 571   
 
 572  0
             if(parent == null)
 573  0
             parent = root;
 574  0
             retNodes = getPathToRoot(parent, depth);
 575   
         }
 576  0
         retNodes[retNodes.length - depth] = aNode;
 577   
         }
 578  0
         return retNodes;
 579   
     }
 580   
     }
 581   
 
 582  0
     public void finalize() {
 583   
       //System.out.println("ElementTreePanel finalize");
 584   
     }
 585   
 }
 586