1 /*
   2  * Copyright (c) 1998, 2015, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package javax.swing.tree;
  27 
  28 import javax.swing.event.TreeModelEvent;
  29 import java.awt.Rectangle;
  30 import java.util.Enumeration;
  31 import java.util.Hashtable;
  32 import java.util.NoSuchElementException;
  33 import java.util.Stack;
  34 
  35 import sun.swing.SwingUtilities2;
  36 
  37 /**
  38  * NOTE: This will become more open in a future release.
  39  * <p>
  40  * <strong>Warning:</strong>
  41  * Serialized objects of this class will not be compatible with
  42  * future Swing releases. The current serialization support is
  43  * appropriate for short term storage or RMI between applications running
  44  * the same version of Swing.  As of 1.4, support for long term storage
  45  * of all JavaBeans&trade;
  46  * has been added to the <code>java.beans</code> package.
  47  * Please see {@link java.beans.XMLEncoder}.
  48  *
  49  * @author Scott Violet
  50  */
  51 @SuppressWarnings("serial") // Same-version serialization only
  52 public class FixedHeightLayoutCache extends AbstractLayoutCache {
  53     /** Root node. */
  54     private FHTreeStateNode    root;
  55 
  56     /** Number of rows currently visible. */
  57     private int                rowCount;
  58 
  59     /**
  60      * Used in getting sizes for nodes to avoid creating a new Rectangle
  61      * every time a size is needed.
  62      */
  63     private Rectangle          boundsBuffer;
  64 
  65     /**
  66      * Maps from TreePath to a FHTreeStateNode.
  67      */
  68     private Hashtable<TreePath, FHTreeStateNode> treePathMapping;
  69 
  70     /**
  71      * Used for getting path/row information.
  72      */
  73     private SearchInfo         info;
  74 
  75     private Stack<Stack<TreePath>> tempStacks;
  76 
  77     /**
  78      * Constructs a {@code FixedHeightLayoutCache}.
  79      */
  80     public FixedHeightLayoutCache() {
  81         super();
  82         tempStacks = new Stack<Stack<TreePath>>();
  83         boundsBuffer = new Rectangle();
  84         treePathMapping = new Hashtable<TreePath, FHTreeStateNode>();
  85         info = new SearchInfo();
  86         setRowHeight(1);
  87     }
  88 
  89     /**
  90      * Sets the TreeModel that will provide the data.
  91      *
  92      * @param newModel the TreeModel that is to provide the data
  93      */
  94     public void setModel(TreeModel newModel) {
  95         super.setModel(newModel);
  96         rebuild(false);
  97     }
  98 
  99     /**
 100      * Determines whether or not the root node from
 101      * the TreeModel is visible.
 102      *
 103      * @param rootVisible true if the root node of the tree is to be displayed
 104      * @see #rootVisible
 105      */
 106     public void setRootVisible(boolean rootVisible) {
 107         if(isRootVisible() != rootVisible) {
 108             super.setRootVisible(rootVisible);
 109             if(root != null) {
 110                 if(rootVisible) {
 111                     rowCount++;
 112                     root.adjustRowBy(1);
 113                 }
 114                 else {
 115                     rowCount--;
 116                     root.adjustRowBy(-1);
 117                 }
 118                 visibleNodesChanged();
 119             }
 120         }
 121     }
 122 
 123     /**
 124      * Sets the height of each cell. If rowHeight is less than or equal to
 125      * 0 this will throw an IllegalArgumentException.
 126      *
 127      * @param rowHeight the height of each cell, in pixels
 128      */
 129     public void setRowHeight(int rowHeight) {
 130         if(rowHeight <= 0)
 131             throw new IllegalArgumentException("FixedHeightLayoutCache only supports row heights greater than 0");
 132         if(getRowHeight() != rowHeight) {
 133             super.setRowHeight(rowHeight);
 134             visibleNodesChanged();
 135         }
 136     }
 137 
 138     /**
 139      * Returns the number of visible rows.
 140      */
 141     public int getRowCount() {
 142         return rowCount;
 143     }
 144 
 145     /**
 146      * Does nothing, FixedHeightLayoutCache doesn't cache width, and that
 147      * is all that could change.
 148      */
 149     public void invalidatePathBounds(TreePath path) {
 150     }
 151 
 152 
 153     /**
 154      * Informs the TreeState that it needs to recalculate all the sizes
 155      * it is referencing.
 156      */
 157     public void invalidateSizes() {
 158         // Nothing to do here, rowHeight still same, which is all
 159         // this is interested in, visible region may have changed though.
 160         visibleNodesChanged();
 161     }
 162 
 163     /**
 164       * Returns true if the value identified by row is currently expanded.
 165       */
 166     public boolean isExpanded(TreePath path) {
 167         if(path != null) {
 168             FHTreeStateNode     lastNode = getNodeForPath(path, true, false);
 169 
 170             return (lastNode != null && lastNode.isExpanded());
 171         }
 172         return false;
 173     }
 174 
 175     /**
 176      * Returns a rectangle giving the bounds needed to draw path.
 177      *
 178      * @param path     a TreePath specifying a node
 179      * @param placeIn  a Rectangle object giving the available space
 180      * @return a Rectangle object specifying the space to be used
 181      */
 182     public Rectangle getBounds(TreePath path, Rectangle placeIn) {
 183         if(path == null)
 184             return null;
 185 
 186         FHTreeStateNode      node = getNodeForPath(path, true, false);
 187 
 188         if(node != null)
 189             return getBounds(node, -1, placeIn);
 190 
 191         // node hasn't been created yet.
 192         TreePath       parentPath = path.getParentPath();
 193 
 194         node = getNodeForPath(parentPath, true, false);
 195         if (node != null && node.isExpanded()) {
 196             int              childIndex = treeModel.getIndexOfChild
 197                                  (parentPath.getLastPathComponent(),
 198                                   path.getLastPathComponent());
 199 
 200             if(childIndex != -1)
 201                 return getBounds(node, childIndex, placeIn);
 202         }
 203         return null;
 204     }
 205 
 206     /**
 207       * Returns the path for passed in row.  If row is not visible
 208       * null is returned.
 209       */
 210     public TreePath getPathForRow(int row) {
 211         if(row >= 0 && row < getRowCount()) {
 212             if(root.getPathForRow(row, getRowCount(), info)) {
 213                 return info.getPath();
 214             }
 215         }
 216         return null;
 217     }
 218 
 219     /**
 220       * Returns the row that the last item identified in path is visible
 221       * at.  Will return -1 if any of the elements in path are not
 222       * currently visible.
 223       */
 224     public int getRowForPath(TreePath path) {
 225         if(path == null || root == null)
 226             return -1;
 227 
 228         FHTreeStateNode         node = getNodeForPath(path, true, false);
 229 
 230         if(node != null)
 231             return node.getRow();
 232 
 233         TreePath       parentPath = path.getParentPath();
 234 
 235         node = getNodeForPath(parentPath, true, false);
 236         if(node != null && node.isExpanded()) {
 237             return node.getRowToModelIndex(treeModel.getIndexOfChild
 238                                            (parentPath.getLastPathComponent(),
 239                                             path.getLastPathComponent()));
 240         }
 241         return -1;
 242     }
 243 
 244     /**
 245       * Returns the path to the node that is closest to x,y.  If
 246       * there is nothing currently visible this will return null, otherwise
 247       * it'll always return a valid path.  If you need to test if the
 248       * returned object is exactly at x, y you should get the bounds for
 249       * the returned path and test x, y against that.
 250       */
 251     public TreePath getPathClosestTo(int x, int y) {
 252         if(getRowCount() == 0)
 253             return null;
 254 
 255         int                row = getRowContainingYLocation(y);
 256 
 257         return getPathForRow(row);
 258     }
 259 
 260     /**
 261      * Returns the number of visible children for row.
 262      */
 263     public int getVisibleChildCount(TreePath path) {
 264         FHTreeStateNode         node = getNodeForPath(path, true, false);
 265 
 266         if(node == null)
 267             return 0;
 268         return node.getTotalChildCount();
 269     }
 270 
 271     /**
 272      * Returns an Enumerator that increments over the visible paths
 273      * starting at the passed in location. The ordering of the enumeration
 274      * is based on how the paths are displayed.
 275      */
 276     public Enumeration<TreePath> getVisiblePathsFrom(TreePath path) {
 277         if(path == null)
 278             return null;
 279 
 280         FHTreeStateNode         node = getNodeForPath(path, true, false);
 281 
 282         if(node != null) {
 283             return new VisibleFHTreeStateNodeEnumeration(node);
 284         }
 285         TreePath            parentPath = path.getParentPath();
 286 
 287         node = getNodeForPath(parentPath, true, false);
 288         if(node != null && node.isExpanded()) {
 289             return new VisibleFHTreeStateNodeEnumeration(node,
 290                   treeModel.getIndexOfChild(parentPath.getLastPathComponent(),
 291                                             path.getLastPathComponent()));
 292         }
 293         return null;
 294     }
 295 
 296     /**
 297      * Marks the path <code>path</code> expanded state to
 298      * <code>isExpanded</code>.
 299      */
 300     public void setExpandedState(TreePath path, boolean isExpanded) {
 301         if(isExpanded)
 302             ensurePathIsExpanded(path, true);
 303         else if(path != null) {
 304             TreePath              parentPath = path.getParentPath();
 305 
 306             // YECK! Make the parent expanded.
 307             if(parentPath != null) {
 308                 FHTreeStateNode     parentNode = getNodeForPath(parentPath,
 309                                                                 false, true);
 310                 if(parentNode != null)
 311                     parentNode.makeVisible();
 312             }
 313             // And collapse the child.
 314             FHTreeStateNode         childNode = getNodeForPath(path, true,
 315                                                                false);
 316 
 317             if(childNode != null)
 318                 childNode.collapse(true);
 319         }
 320     }
 321 
 322     /**
 323      * Returns true if the path is expanded, and visible.
 324      */
 325     public boolean getExpandedState(TreePath path) {
 326         FHTreeStateNode       node = getNodeForPath(path, true, false);
 327 
 328         return (node != null) ? (node.isVisible() && node.isExpanded()) :
 329                                  false;
 330     }
 331 
 332     //
 333     // TreeModelListener methods
 334     //
 335 
 336     /**
 337      * <p>Invoked after a node (or a set of siblings) has changed in some
 338      * way. The node(s) have not changed locations in the tree or
 339      * altered their children arrays, but other attributes have
 340      * changed and may affect presentation. Example: the name of a
 341      * file has changed, but it is in the same location in the file
 342      * system.</p>
 343      *
 344      * <p>e.path() returns the path the parent of the changed node(s).</p>
 345      *
 346      * <p>e.childIndices() returns the index(es) of the changed node(s).</p>
 347      */
 348     public void treeNodesChanged(TreeModelEvent e) {
 349         if(e != null) {
 350             int                 changedIndexs[];
 351             FHTreeStateNode     changedParent = getNodeForPath
 352                                   (SwingUtilities2.getTreePath(e, getModel()), false, false);
 353             int                 maxCounter;
 354 
 355             changedIndexs = e.getChildIndices();
 356             /* Only need to update the children if the node has been
 357                expanded once. */
 358             // PENDING(scott): make sure childIndexs is sorted!
 359             if (changedParent != null) {
 360                 if (changedIndexs != null &&
 361                     (maxCounter = changedIndexs.length) > 0) {
 362                     Object       parentValue = changedParent.getUserObject();
 363 
 364                     for(int counter = 0; counter < maxCounter; counter++) {
 365                         FHTreeStateNode    child = changedParent.
 366                                  getChildAtModelIndex(changedIndexs[counter]);
 367 
 368                         if(child != null) {
 369                             child.setUserObject(treeModel.getChild(parentValue,
 370                                                      changedIndexs[counter]));
 371                         }
 372                     }
 373                     if(changedParent.isVisible() && changedParent.isExpanded())
 374                         visibleNodesChanged();
 375                 }
 376                 // Null for root indicates it changed.
 377                 else if (changedParent == root && changedParent.isVisible() &&
 378                          changedParent.isExpanded()) {
 379                     visibleNodesChanged();
 380                 }
 381             }
 382         }
 383     }
 384 
 385     /**
 386      * <p>Invoked after nodes have been inserted into the tree.</p>
 387      *
 388      * <p>e.path() returns the parent of the new nodes
 389      * <p>e.childIndices() returns the indices of the new nodes in
 390      * ascending order.
 391      */
 392     public void treeNodesInserted(TreeModelEvent e) {
 393         if(e != null) {
 394             int                 changedIndexs[];
 395             FHTreeStateNode     changedParent = getNodeForPath
 396                                   (SwingUtilities2.getTreePath(e, getModel()), false, false);
 397             int                 maxCounter;
 398 
 399             changedIndexs = e.getChildIndices();
 400             /* Only need to update the children if the node has been
 401                expanded once. */
 402             // PENDING(scott): make sure childIndexs is sorted!
 403             if(changedParent != null && changedIndexs != null &&
 404                (maxCounter = changedIndexs.length) > 0) {
 405                 boolean          isVisible =
 406                     (changedParent.isVisible() &&
 407                      changedParent.isExpanded());
 408 
 409                 for(int counter = 0; counter < maxCounter; counter++) {
 410                     changedParent.childInsertedAtModelIndex
 411                         (changedIndexs[counter], isVisible);
 412                 }
 413                 if(isVisible && treeSelectionModel != null)
 414                     treeSelectionModel.resetRowSelection();
 415                 if(changedParent.isVisible())
 416                     this.visibleNodesChanged();
 417             }
 418         }
 419     }
 420 
 421     /**
 422      * <p>Invoked after nodes have been removed from the tree.  Note that
 423      * if a subtree is removed from the tree, this method may only be
 424      * invoked once for the root of the removed subtree, not once for
 425      * each individual set of siblings removed.</p>
 426      *
 427      * <p>e.path() returns the former parent of the deleted nodes.</p>
 428      *
 429      * <p>e.childIndices() returns the indices the nodes had before they were deleted in ascending order.</p>
 430      */
 431     public void treeNodesRemoved(TreeModelEvent e) {
 432         if(e != null) {
 433             int                  changedIndexs[];
 434             int                  maxCounter;
 435             TreePath             parentPath = SwingUtilities2.getTreePath(e, getModel());
 436             FHTreeStateNode      changedParentNode = getNodeForPath
 437                                        (parentPath, false, false);
 438 
 439             changedIndexs = e.getChildIndices();
 440             // PENDING(scott): make sure that changedIndexs are sorted in
 441             // ascending order.
 442             if(changedParentNode != null && changedIndexs != null &&
 443                (maxCounter = changedIndexs.length) > 0) {
 444                 Object[]           children = e.getChildren();
 445                 boolean            isVisible =
 446                     (changedParentNode.isVisible() &&
 447                      changedParentNode.isExpanded());
 448 
 449                 for(int counter = maxCounter - 1; counter >= 0; counter--) {
 450                     changedParentNode.removeChildAtModelIndex
 451                                      (changedIndexs[counter], isVisible);
 452                 }
 453                 if(isVisible) {
 454                     if(treeSelectionModel != null)
 455                         treeSelectionModel.resetRowSelection();
 456                     if (treeModel.getChildCount(changedParentNode.
 457                                                 getUserObject()) == 0 &&
 458                                   changedParentNode.isLeaf()) {
 459                         // Node has become a leaf, collapse it.
 460                         changedParentNode.collapse(false);
 461                     }
 462                     visibleNodesChanged();
 463                 }
 464                 else if(changedParentNode.isVisible())
 465                     visibleNodesChanged();
 466             }
 467         }
 468     }
 469 
 470     /**
 471      * <p>Invoked after the tree has drastically changed structure from a
 472      * given node down.  If the path returned by e.getPath() is of length
 473      * one and the first element does not identify the current root node
 474      * the first element should become the new root of the tree.
 475      *
 476      * <p>e.path() holds the path to the node.</p>
 477      * <p>e.childIndices() returns null.</p>
 478      */
 479     public void treeStructureChanged(TreeModelEvent e) {
 480         if(e != null) {
 481             TreePath          changedPath = SwingUtilities2.getTreePath(e, getModel());
 482             FHTreeStateNode   changedNode = getNodeForPath
 483                                                 (changedPath, false, false);
 484 
 485             // Check if root has changed, either to a null root, or
 486             // to an entirely new root.
 487             if (changedNode == root ||
 488                 (changedNode == null &&
 489                  ((changedPath == null && treeModel != null &&
 490                    treeModel.getRoot() == null) ||
 491                   (changedPath != null && changedPath.getPathCount() <= 1)))) {
 492                 rebuild(true);
 493             }
 494             else if(changedNode != null) {
 495                 boolean             wasExpanded, wasVisible;
 496                 FHTreeStateNode     parent = (FHTreeStateNode)
 497                                               changedNode.getParent();
 498 
 499                 wasExpanded = changedNode.isExpanded();
 500                 wasVisible = changedNode.isVisible();
 501 
 502                 int index = parent.getIndex(changedNode);
 503                 changedNode.collapse(false);
 504                 parent.remove(index);
 505 
 506                 if(wasVisible && wasExpanded) {
 507                     int row = changedNode.getRow();
 508                     parent.resetChildrenRowsFrom(row, index,
 509                                                  changedNode.getChildIndex());
 510                     changedNode = getNodeForPath(changedPath, false, true);
 511                     changedNode.expand();
 512                 }
 513                 if(treeSelectionModel != null && wasVisible && wasExpanded)
 514                     treeSelectionModel.resetRowSelection();
 515                 if(wasVisible)
 516                     this.visibleNodesChanged();
 517             }
 518         }
 519     }
 520 
 521 
 522     //
 523     // Local methods
 524     //
 525 
 526     private void visibleNodesChanged() {
 527     }
 528 
 529     /**
 530      * Returns the bounds for the given node. If <code>childIndex</code>
 531      * is -1, the bounds of <code>parent</code> are returned, otherwise
 532      * the bounds of the node at <code>childIndex</code> are returned.
 533      */
 534     private Rectangle getBounds(FHTreeStateNode parent, int childIndex,
 535                                   Rectangle placeIn) {
 536         boolean              expanded;
 537         int                  level;
 538         int                  row;
 539         Object               value;
 540 
 541         if(childIndex == -1) {
 542             // Getting bounds for parent
 543             row = parent.getRow();
 544             value = parent.getUserObject();
 545             expanded = parent.isExpanded();
 546             level = parent.getLevel();
 547         }
 548         else {
 549             row = parent.getRowToModelIndex(childIndex);
 550             value = treeModel.getChild(parent.getUserObject(), childIndex);
 551             expanded = false;
 552             level = parent.getLevel() + 1;
 553         }
 554 
 555         Rectangle      bounds = getNodeDimensions(value, row, level,
 556                                                   expanded, boundsBuffer);
 557         // No node dimensions, bail.
 558         if(bounds == null)
 559             return null;
 560 
 561         if(placeIn == null)
 562             placeIn = new Rectangle();
 563 
 564         placeIn.x = bounds.x;
 565         placeIn.height = getRowHeight();
 566         placeIn.y = row * placeIn.height;
 567         placeIn.width = bounds.width;
 568         return placeIn;
 569     }
 570 
 571     /**
 572      * Adjust the large row count of the AbstractTreeUI the receiver was
 573      * created with.
 574      */
 575     private void adjustRowCountBy(int changeAmount) {
 576         rowCount += changeAmount;
 577     }
 578 
 579     /**
 580      * Adds a mapping for node.
 581      */
 582     private void addMapping(FHTreeStateNode node) {
 583         treePathMapping.put(node.getTreePath(), node);
 584     }
 585 
 586     /**
 587      * Removes the mapping for a previously added node.
 588      */
 589     private void removeMapping(FHTreeStateNode node) {
 590         treePathMapping.remove(node.getTreePath());
 591     }
 592 
 593     /**
 594      * Returns the node previously added for <code>path</code>. This may
 595      * return null, if you to create a node use getNodeForPath.
 596      */
 597     private FHTreeStateNode getMapping(TreePath path) {
 598         return treePathMapping.get(path);
 599     }
 600 
 601     /**
 602      * Sent to completely rebuild the visible tree. All nodes are collapsed.
 603      */
 604     private void rebuild(boolean clearSelection) {
 605         Object            rootUO;
 606 
 607         treePathMapping.clear();
 608         if(treeModel != null && (rootUO = treeModel.getRoot()) != null) {
 609             root = createNodeForValue(rootUO, 0);
 610             root.path = new TreePath(rootUO);
 611             addMapping(root);
 612             if(isRootVisible()) {
 613                 rowCount = 1;
 614                 root.row = 0;
 615             }
 616             else {
 617                 rowCount = 0;
 618                 root.row = -1;
 619             }
 620             root.expand();
 621         }
 622         else {
 623             root = null;
 624             rowCount = 0;
 625         }
 626         if(clearSelection && treeSelectionModel != null) {
 627             treeSelectionModel.clearSelection();
 628         }
 629         this.visibleNodesChanged();
 630     }
 631 
 632     /**
 633       * Returns the index of the row containing location.  If there
 634       * are no rows, -1 is returned.  If location is beyond the last
 635       * row index, the last row index is returned.
 636       */
 637     private int getRowContainingYLocation(int location) {
 638         if(getRowCount() == 0)
 639             return -1;
 640         return Math.max(0, Math.min(getRowCount() - 1,
 641                                     location / getRowHeight()));
 642     }
 643 
 644     /**
 645      * Ensures that all the path components in path are expanded, accept
 646      * for the last component which will only be expanded if expandLast
 647      * is true.
 648      * Returns true if succesful in finding the path.
 649      */
 650     private boolean ensurePathIsExpanded(TreePath aPath,
 651                                            boolean expandLast) {
 652         if(aPath != null) {
 653             // Make sure the last entry isn't a leaf.
 654             if(treeModel.isLeaf(aPath.getLastPathComponent())) {
 655                 aPath = aPath.getParentPath();
 656                 expandLast = true;
 657             }
 658             if(aPath != null) {
 659                 FHTreeStateNode     lastNode = getNodeForPath(aPath, false,
 660                                                               true);
 661 
 662                 if(lastNode != null) {
 663                     lastNode.makeVisible();
 664                     if(expandLast)
 665                         lastNode.expand();
 666                     return true;
 667                 }
 668             }
 669         }
 670         return false;
 671     }
 672 
 673     /**
 674      * Creates and returns an instance of FHTreeStateNode.
 675      */
 676     private FHTreeStateNode createNodeForValue(Object value,int childIndex) {
 677         return new FHTreeStateNode(value, childIndex, -1);
 678     }
 679 
 680     /**
 681      * Messages getTreeNodeForPage(path, onlyIfVisible, shouldCreate,
 682      * path.length) as long as path is non-null and the length is {@literal >} 0.
 683      * Otherwise returns null.
 684      */
 685     private FHTreeStateNode getNodeForPath(TreePath path,
 686                                              boolean onlyIfVisible,
 687                                              boolean shouldCreate) {
 688         if(path != null) {
 689             FHTreeStateNode      node;
 690 
 691             node = getMapping(path);
 692             if(node != null) {
 693                 if(onlyIfVisible && !node.isVisible())
 694                     return null;
 695                 return node;
 696             }
 697             if(onlyIfVisible)
 698                 return null;
 699 
 700             // Check all the parent paths, until a match is found.
 701             Stack<TreePath> paths;
 702 
 703             if(tempStacks.size() == 0) {
 704                 paths = new Stack<TreePath>();
 705             }
 706             else {
 707                 paths = tempStacks.pop();
 708             }
 709 
 710             try {
 711                 paths.push(path);
 712                 path = path.getParentPath();
 713                 node = null;
 714                 while(path != null) {
 715                     node = getMapping(path);
 716                     if(node != null) {
 717                         // Found a match, create entries for all paths in
 718                         // paths.
 719                         while(node != null && paths.size() > 0) {
 720                             path = paths.pop();
 721                             node = node.createChildFor(path.
 722                                                        getLastPathComponent());
 723                         }
 724                         return node;
 725                     }
 726                     paths.push(path);
 727                     path = path.getParentPath();
 728                 }
 729             }
 730             finally {
 731                 paths.removeAllElements();
 732                 tempStacks.push(paths);
 733             }
 734             // If we get here it means they share a different root!
 735             return null;
 736         }
 737         return null;
 738     }
 739 
 740     /**
 741      * FHTreeStateNode is used to track what has been expanded.
 742      * FHTreeStateNode differs from VariableHeightTreeState.TreeStateNode
 743      * in that it is highly model intensive. That is almost all queries to a
 744      * FHTreeStateNode result in the TreeModel being queried. And it
 745      * obviously does not support variable sized row heights.
 746      */
 747     private class FHTreeStateNode extends DefaultMutableTreeNode {
 748         /** Is this node expanded? */
 749         protected boolean         isExpanded;
 750 
 751         /** Index of this node from the model. */
 752         protected int             childIndex;
 753 
 754         /** Child count of the receiver. */
 755         protected int             childCount;
 756 
 757         /** Row of the receiver. This is only valid if the row is expanded.
 758          */
 759         protected int             row;
 760 
 761         /** Path of this node. */
 762         protected TreePath        path;
 763 
 764 
 765         public FHTreeStateNode(Object userObject, int childIndex, int row) {
 766             super(userObject);
 767             this.childIndex = childIndex;
 768             this.row = row;
 769         }
 770 
 771         //
 772         // Overriden DefaultMutableTreeNode methods
 773         //
 774 
 775         /**
 776          * Messaged when this node is added somewhere, resets the path
 777          * and adds a mapping from path to this node.
 778          */
 779         public void setParent(MutableTreeNode parent) {
 780             super.setParent(parent);
 781             if(parent != null) {
 782                 path = ((FHTreeStateNode)parent).getTreePath().
 783                             pathByAddingChild(getUserObject());
 784                 addMapping(this);
 785             }
 786         }
 787 
 788         /**
 789          * Messaged when this node is removed from its parent, this messages
 790          * <code>removedFromMapping</code> to remove all the children.
 791          */
 792         public void remove(int childIndex) {
 793             FHTreeStateNode     node = (FHTreeStateNode)getChildAt(childIndex);
 794 
 795             node.removeFromMapping();
 796             super.remove(childIndex);
 797         }
 798 
 799         /**
 800          * Messaged to set the user object. This resets the path.
 801          */
 802         public void setUserObject(Object o) {
 803             super.setUserObject(o);
 804             if(path != null) {
 805                 FHTreeStateNode      parent = (FHTreeStateNode)getParent();
 806 
 807                 if(parent != null)
 808                     resetChildrenPaths(parent.getTreePath());
 809                 else
 810                     resetChildrenPaths(null);
 811             }
 812         }
 813 
 814         //
 815         //
 816 
 817         /**
 818          * Returns the index of the receiver in the model.
 819          */
 820         public int getChildIndex() {
 821             return childIndex;
 822         }
 823 
 824         /**
 825          * Returns the <code>TreePath</code> of the receiver.
 826          */
 827         public TreePath getTreePath() {
 828             return path;
 829         }
 830 
 831         /**
 832          * Returns the child for the passed in model index, this will
 833          * return <code>null</code> if the child for <code>index</code>
 834          * has not yet been created (expanded).
 835          */
 836         public FHTreeStateNode getChildAtModelIndex(int index) {
 837             // PENDING: Make this a binary search!
 838             for(int counter = getChildCount() - 1; counter >= 0; counter--)
 839                 if(((FHTreeStateNode)getChildAt(counter)).childIndex == index)
 840                     return (FHTreeStateNode)getChildAt(counter);
 841             return null;
 842         }
 843 
 844         /**
 845          * Returns true if this node is visible. This is determined by
 846          * asking all the parents if they are expanded.
 847          */
 848         public boolean isVisible() {
 849             FHTreeStateNode         parent = (FHTreeStateNode)getParent();
 850 
 851             if(parent == null)
 852                 return true;
 853             return (parent.isExpanded() && parent.isVisible());
 854         }
 855 
 856         /**
 857          * Returns the row of the receiver.
 858          */
 859         public int getRow() {
 860             return row;
 861         }
 862 
 863         /**
 864          * Returns the row of the child with a model index of
 865          * <code>index</code>.
 866          */
 867         public int getRowToModelIndex(int index) {
 868             FHTreeStateNode      child;
 869             int                  lastRow = getRow() + 1;
 870             int                  retValue = lastRow;
 871 
 872             // This too could be a binary search!
 873             for(int counter = 0, maxCounter = getChildCount();
 874                 counter < maxCounter; counter++) {
 875                 child = (FHTreeStateNode)getChildAt(counter);
 876                 if(child.childIndex >= index) {
 877                     if(child.childIndex == index)
 878                         return child.row;
 879                     if(counter == 0)
 880                         return getRow() + 1 + index;
 881                     return child.row - (child.childIndex - index);
 882                 }
 883             }
 884             // YECK!
 885             return getRow() + 1 + getTotalChildCount() -
 886                              (childCount - index);
 887         }
 888 
 889         /**
 890          * Returns the number of children in the receiver by descending all
 891          * expanded nodes and messaging them with getTotalChildCount.
 892          */
 893         public int getTotalChildCount() {
 894             if(isExpanded()) {
 895                 FHTreeStateNode      parent = (FHTreeStateNode)getParent();
 896                 int                  pIndex;
 897 
 898                 if(parent != null && (pIndex = parent.getIndex(this)) + 1 <
 899                    parent.getChildCount()) {
 900                     // This node has a created sibling, to calc total
 901                     // child count directly from that!
 902                     FHTreeStateNode  nextSibling = (FHTreeStateNode)parent.
 903                                            getChildAt(pIndex + 1);
 904 
 905                     return nextSibling.row - row -
 906                            (nextSibling.childIndex - childIndex);
 907                 }
 908                 else {
 909                     int retCount = childCount;
 910 
 911                     for(int counter = getChildCount() - 1; counter >= 0;
 912                         counter--) {
 913                         retCount += ((FHTreeStateNode)getChildAt(counter))
 914                                                   .getTotalChildCount();
 915                     }
 916                     return retCount;
 917                 }
 918             }
 919             return 0;
 920         }
 921 
 922         /**
 923          * Returns true if this node is expanded.
 924          */
 925         public boolean isExpanded() {
 926             return isExpanded;
 927         }
 928 
 929         /**
 930          * The highest visible nodes have a depth of 0.
 931          */
 932         public int getVisibleLevel() {
 933             if (isRootVisible()) {
 934                 return getLevel();
 935             } else {
 936                 return getLevel() - 1;
 937             }
 938         }
 939 
 940         /**
 941          * Recreates the receivers path, and all its children's paths.
 942          */
 943         protected void resetChildrenPaths(TreePath parentPath) {
 944             removeMapping(this);
 945             if(parentPath == null)
 946                 path = new TreePath(getUserObject());
 947             else
 948                 path = parentPath.pathByAddingChild(getUserObject());
 949             addMapping(this);
 950             for(int counter = getChildCount() - 1; counter >= 0; counter--)
 951                 ((FHTreeStateNode)getChildAt(counter)).
 952                                resetChildrenPaths(path);
 953         }
 954 
 955         /**
 956          * Removes the receiver, and all its children, from the mapping
 957          * table.
 958          */
 959         protected void removeFromMapping() {
 960             if(path != null) {
 961                 removeMapping(this);
 962                 for(int counter = getChildCount() - 1; counter >= 0; counter--)
 963                     ((FHTreeStateNode)getChildAt(counter)).removeFromMapping();
 964             }
 965         }
 966 
 967         /**
 968          * Creates a new node to represent <code>userObject</code>.
 969          * This does NOT check to ensure there isn't already a child node
 970          * to manage <code>userObject</code>.
 971          */
 972         protected FHTreeStateNode createChildFor(Object userObject) {
 973             int      newChildIndex = treeModel.getIndexOfChild
 974                                      (getUserObject(), userObject);
 975 
 976             if(newChildIndex < 0)
 977                 return null;
 978 
 979             FHTreeStateNode     aNode;
 980             FHTreeStateNode     child = createNodeForValue(userObject,
 981                                                            newChildIndex);
 982             int                 childRow;
 983 
 984             if(isVisible()) {
 985                 childRow = getRowToModelIndex(newChildIndex);
 986             }
 987             else {
 988                 childRow = -1;
 989             }
 990             child.row = childRow;
 991             for(int counter = 0, maxCounter = getChildCount();
 992                 counter < maxCounter; counter++) {
 993                 aNode = (FHTreeStateNode)getChildAt(counter);
 994                 if(aNode.childIndex > newChildIndex) {
 995                     insert(child, counter);
 996                     return child;
 997                 }
 998             }
 999             add(child);
1000             return child;
1001         }
1002 
1003         /**
1004          * Adjusts the receiver, and all its children rows by
1005          * <code>amount</code>.
1006          */
1007         protected void adjustRowBy(int amount) {
1008             row += amount;
1009             if(isExpanded) {
1010                 for(int counter = getChildCount() - 1; counter >= 0;
1011                     counter--)
1012                     ((FHTreeStateNode)getChildAt(counter)).adjustRowBy(amount);
1013             }
1014         }
1015 
1016         /**
1017          * Adjusts this node, its child, and its parent starting at
1018          * an index of <code>index</code> index is the index of the child
1019          * to start adjusting from, which is not necessarily the model
1020          * index.
1021          */
1022         protected void adjustRowBy(int amount, int startIndex) {
1023             // Could check isVisible, but probably isn't worth it.
1024             if(isExpanded) {
1025                 // children following startIndex.
1026                 for(int counter = getChildCount() - 1; counter >= startIndex;
1027                     counter--)
1028                     ((FHTreeStateNode)getChildAt(counter)).adjustRowBy(amount);
1029             }
1030             // Parent
1031             FHTreeStateNode        parent = (FHTreeStateNode)getParent();
1032 
1033             if(parent != null) {
1034                 parent.adjustRowBy(amount, parent.getIndex(this) + 1);
1035             }
1036         }
1037 
1038         /**
1039          * Messaged when the node has expanded. This updates all of
1040          * the receivers children rows, as well as the total row count.
1041          */
1042         protected void didExpand() {
1043             int               nextRow = setRowAndChildren(row);
1044             FHTreeStateNode   parent = (FHTreeStateNode)getParent();
1045             int               childRowCount = nextRow - row - 1;
1046 
1047             if(parent != null) {
1048                 parent.adjustRowBy(childRowCount, parent.getIndex(this) + 1);
1049             }
1050             adjustRowCountBy(childRowCount);
1051         }
1052 
1053         /**
1054          * Sets the receivers row to <code>nextRow</code> and recursively
1055          * updates all the children of the receivers rows. The index the
1056          * next row is to be placed as is returned.
1057          */
1058         protected int setRowAndChildren(int nextRow) {
1059             row = nextRow;
1060 
1061             if(!isExpanded())
1062                 return row + 1;
1063 
1064             int              lastRow = row + 1;
1065             int              lastModelIndex = 0;
1066             FHTreeStateNode  child;
1067             int              maxCounter = getChildCount();
1068 
1069             for(int counter = 0; counter < maxCounter; counter++) {
1070                 child = (FHTreeStateNode)getChildAt(counter);
1071                 lastRow += (child.childIndex - lastModelIndex);
1072                 lastModelIndex = child.childIndex + 1;
1073                 if(child.isExpanded) {
1074                     lastRow = child.setRowAndChildren(lastRow);
1075                 }
1076                 else {
1077                     child.row = lastRow++;
1078                 }
1079             }
1080             return lastRow + childCount - lastModelIndex;
1081         }
1082 
1083         /**
1084          * Resets the receivers children's rows. Starting with the child
1085          * at <code>childIndex</code> (and <code>modelIndex</code>) to
1086          * <code>newRow</code>. This uses <code>setRowAndChildren</code>
1087          * to recursively descend children, and uses
1088          * <code>resetRowSelection</code> to ascend parents.
1089          */
1090         // This can be rather expensive, but is needed for the collapse
1091         // case this is resulting from a remove (although I could fix
1092         // that by having instances of FHTreeStateNode hold a ref to
1093         // the number of children). I prefer this though, making determing
1094         // the row of a particular node fast is very nice!
1095         protected void resetChildrenRowsFrom(int newRow, int childIndex,
1096                                             int modelIndex) {
1097             int              lastRow = newRow;
1098             int              lastModelIndex = modelIndex;
1099             FHTreeStateNode  node;
1100             int              maxCounter = getChildCount();
1101 
1102             for(int counter = childIndex; counter < maxCounter; counter++) {
1103                 node = (FHTreeStateNode)getChildAt(counter);
1104                 lastRow += (node.childIndex - lastModelIndex);
1105                 lastModelIndex = node.childIndex + 1;
1106                 if(node.isExpanded) {
1107                     lastRow = node.setRowAndChildren(lastRow);
1108                 }
1109                 else {
1110                     node.row = lastRow++;
1111                 }
1112             }
1113             lastRow += childCount - lastModelIndex;
1114             node = (FHTreeStateNode)getParent();
1115             if(node != null) {
1116                 node.resetChildrenRowsFrom(lastRow, node.getIndex(this) + 1,
1117                                            this.childIndex + 1);
1118             }
1119             else { // This is the root, reset total ROWCOUNT!
1120                 rowCount = lastRow;
1121             }
1122         }
1123 
1124         /**
1125          * Makes the receiver visible, but invoking
1126          * <code>expandParentAndReceiver</code> on the superclass.
1127          */
1128         protected void makeVisible() {
1129             FHTreeStateNode       parent = (FHTreeStateNode)getParent();
1130 
1131             if(parent != null)
1132                 parent.expandParentAndReceiver();
1133         }
1134 
1135         /**
1136          * Invokes <code>expandParentAndReceiver</code> on the parent,
1137          * and expands the receiver.
1138          */
1139         protected void expandParentAndReceiver() {
1140             FHTreeStateNode       parent = (FHTreeStateNode)getParent();
1141 
1142             if(parent != null)
1143                 parent.expandParentAndReceiver();
1144             expand();
1145         }
1146 
1147         /**
1148          * Expands the receiver.
1149          */
1150         protected void expand() {
1151             if(!isExpanded && !isLeaf()) {
1152                 boolean            visible = isVisible();
1153 
1154                 isExpanded = true;
1155                 childCount = treeModel.getChildCount(getUserObject());
1156 
1157                 if(visible) {
1158                     didExpand();
1159                 }
1160 
1161                 // Update the selection model.
1162                 if(visible && treeSelectionModel != null) {
1163                     treeSelectionModel.resetRowSelection();
1164                 }
1165             }
1166         }
1167 
1168         /**
1169          * Collapses the receiver. If <code>adjustRows</code> is true,
1170          * the rows of nodes after the receiver are adjusted.
1171          */
1172         protected void collapse(boolean adjustRows) {
1173             if(isExpanded) {
1174                 if(isVisible() && adjustRows) {
1175                     int              childCount = getTotalChildCount();
1176 
1177                     isExpanded = false;
1178                     adjustRowCountBy(-childCount);
1179                     // We can do this because adjustRowBy won't descend
1180                     // the children.
1181                     adjustRowBy(-childCount, 0);
1182                 }
1183                 else
1184                     isExpanded = false;
1185 
1186                 if(adjustRows && isVisible() && treeSelectionModel != null)
1187                     treeSelectionModel.resetRowSelection();
1188             }
1189         }
1190 
1191         /**
1192          * Returns true if the receiver is a leaf.
1193          */
1194         public boolean isLeaf() {
1195             TreeModel model = getModel();
1196 
1197             return (model != null) ? model.isLeaf(this.getUserObject()) :
1198                    true;
1199         }
1200 
1201         /**
1202          * Adds newChild to this nodes children at the appropriate location.
1203          * The location is determined from the childIndex of newChild.
1204          */
1205         protected void addNode(FHTreeStateNode newChild) {
1206             boolean         added = false;
1207             int             childIndex = newChild.getChildIndex();
1208 
1209             for(int counter = 0, maxCounter = getChildCount();
1210                 counter < maxCounter; counter++) {
1211                 if(((FHTreeStateNode)getChildAt(counter)).getChildIndex() >
1212                    childIndex) {
1213                     added = true;
1214                     insert(newChild, counter);
1215                     counter = maxCounter;
1216                 }
1217             }
1218             if(!added)
1219                 add(newChild);
1220         }
1221 
1222         /**
1223          * Removes the child at <code>modelIndex</code>.
1224          * <code>isChildVisible</code> should be true if the receiver
1225          * is visible and expanded.
1226          */
1227         protected void removeChildAtModelIndex(int modelIndex,
1228                                                boolean isChildVisible) {
1229             FHTreeStateNode     childNode = getChildAtModelIndex(modelIndex);
1230 
1231             if(childNode != null) {
1232                 int          row = childNode.getRow();
1233                 int          index = getIndex(childNode);
1234 
1235                 childNode.collapse(false);
1236                 remove(index);
1237                 adjustChildIndexs(index, -1);
1238                 childCount--;
1239                 if(isChildVisible) {
1240                     // Adjust the rows.
1241                     resetChildrenRowsFrom(row, index, modelIndex);
1242                 }
1243             }
1244             else {
1245                 int                  maxCounter = getChildCount();
1246                 FHTreeStateNode      aChild;
1247 
1248                 for(int counter = 0; counter < maxCounter; counter++) {
1249                     aChild = (FHTreeStateNode)getChildAt(counter);
1250                     if(aChild.childIndex >= modelIndex) {
1251                         if(isChildVisible) {
1252                             adjustRowBy(-1, counter);
1253                             adjustRowCountBy(-1);
1254                         }
1255                         // Since matched and children are always sorted by
1256                         // index, no need to continue testing with the
1257                         // above.
1258                         for(; counter < maxCounter; counter++)
1259                             ((FHTreeStateNode)getChildAt(counter)).
1260                                               childIndex--;
1261                         childCount--;
1262                         return;
1263                     }
1264                 }
1265                 // No children to adjust, but it was a child, so we still need
1266                 // to adjust nodes after this one.
1267                 if(isChildVisible) {
1268                     adjustRowBy(-1, maxCounter);
1269                     adjustRowCountBy(-1);
1270                 }
1271                 childCount--;
1272             }
1273         }
1274 
1275         /**
1276          * Adjusts the child indexs of the receivers children by
1277          * <code>amount</code>, starting at <code>index</code>.
1278          */
1279         protected void adjustChildIndexs(int index, int amount) {
1280             for(int counter = index, maxCounter = getChildCount();
1281                 counter < maxCounter; counter++) {
1282                 ((FHTreeStateNode)getChildAt(counter)).childIndex += amount;
1283             }
1284         }
1285 
1286         /**
1287          * Messaged when a child has been inserted at index. For all the
1288          * children that have a childIndex &ge; index their index is incremented
1289          * by one.
1290          */
1291         protected void childInsertedAtModelIndex(int index,
1292                                                boolean isExpandedAndVisible) {
1293             FHTreeStateNode                aChild;
1294             int                            maxCounter = getChildCount();
1295 
1296             for(int counter = 0; counter < maxCounter; counter++) {
1297                 aChild = (FHTreeStateNode)getChildAt(counter);
1298                 if(aChild.childIndex >= index) {
1299                     if(isExpandedAndVisible) {
1300                         adjustRowBy(1, counter);
1301                         adjustRowCountBy(1);
1302                     }
1303                     /* Since matched and children are always sorted by
1304                        index, no need to continue testing with the above. */
1305                     for(; counter < maxCounter; counter++)
1306                         ((FHTreeStateNode)getChildAt(counter)).childIndex++;
1307                     childCount++;
1308                     return;
1309                 }
1310             }
1311             // No children to adjust, but it was a child, so we still need
1312             // to adjust nodes after this one.
1313             if(isExpandedAndVisible) {
1314                 adjustRowBy(1, maxCounter);
1315                 adjustRowCountBy(1);
1316             }
1317             childCount++;
1318         }
1319 
1320         /**
1321          * Returns true if there is a row for <code>row</code>.
1322          * <code>nextRow</code> gives the bounds of the receiver.
1323          * Information about the found row is returned in <code>info</code>.
1324          * This should be invoked on root with <code>nextRow</code> set
1325          * to <code>getRowCount</code>().
1326          */
1327         protected boolean getPathForRow(int row, int nextRow,
1328                                         SearchInfo info) {
1329             if(this.row == row) {
1330                 info.node = this;
1331                 info.isNodeParentNode = false;
1332                 info.childIndex = childIndex;
1333                 return true;
1334             }
1335 
1336             FHTreeStateNode            child;
1337             FHTreeStateNode            lastChild = null;
1338 
1339             for(int counter = 0, maxCounter = getChildCount();
1340                 counter < maxCounter; counter++) {
1341                 child = (FHTreeStateNode)getChildAt(counter);
1342                 if(child.row > row) {
1343                     if(counter == 0) {
1344                         // No node exists for it, and is first.
1345                         info.node = this;
1346                         info.isNodeParentNode = true;
1347                         info.childIndex = row - this.row - 1;
1348                         return true;
1349                     }
1350                     else {
1351                         // May have been in last child's bounds.
1352                         int          lastChildEndRow = 1 + child.row -
1353                                      (child.childIndex - lastChild.childIndex);
1354 
1355                         if(row < lastChildEndRow) {
1356                             return lastChild.getPathForRow(row,
1357                                                        lastChildEndRow, info);
1358                         }
1359                         // Between last child and child, but not in last child
1360                         info.node = this;
1361                         info.isNodeParentNode = true;
1362                         info.childIndex = row - lastChildEndRow +
1363                                                 lastChild.childIndex + 1;
1364                         return true;
1365                     }
1366                 }
1367                 lastChild = child;
1368             }
1369 
1370             // Not in children, but we should have it, offset from
1371             // nextRow.
1372             if(lastChild != null) {
1373                 int        lastChildEndRow = nextRow -
1374                                   (childCount - lastChild.childIndex) + 1;
1375 
1376                 if(row < lastChildEndRow) {
1377                     return lastChild.getPathForRow(row, lastChildEndRow, info);
1378                 }
1379                 // Between last child and child, but not in last child
1380                 info.node = this;
1381                 info.isNodeParentNode = true;
1382                 info.childIndex = row - lastChildEndRow +
1383                                              lastChild.childIndex + 1;
1384                 return true;
1385             }
1386             else {
1387                 // No children.
1388                 int         retChildIndex = row - this.row - 1;
1389 
1390                 if(retChildIndex >= childCount) {
1391                     return false;
1392                 }
1393                 info.node = this;
1394                 info.isNodeParentNode = true;
1395                 info.childIndex = retChildIndex;
1396                 return true;
1397             }
1398         }
1399 
1400         /**
1401          * Asks all the children of the receiver for their totalChildCount
1402          * and returns this value (plus stopIndex).
1403          */
1404         protected int getCountTo(int stopIndex) {
1405             FHTreeStateNode    aChild;
1406             int                retCount = stopIndex + 1;
1407 
1408             for(int counter = 0, maxCounter = getChildCount();
1409                 counter < maxCounter; counter++) {
1410                 aChild = (FHTreeStateNode)getChildAt(counter);
1411                 if(aChild.childIndex >= stopIndex)
1412                     counter = maxCounter;
1413                 else
1414                     retCount += aChild.getTotalChildCount();
1415             }
1416             if(parent != null)
1417                 return retCount + ((FHTreeStateNode)getParent())
1418                                    .getCountTo(childIndex);
1419             if(!isRootVisible())
1420                 return (retCount - 1);
1421             return retCount;
1422         }
1423 
1424         /**
1425          * Returns the number of children that are expanded to
1426          * <code>stopIndex</code>. This does not include the number
1427          * of children that the child at <code>stopIndex</code> might
1428          * have.
1429          */
1430         protected int getNumExpandedChildrenTo(int stopIndex) {
1431             FHTreeStateNode    aChild;
1432             int                retCount = stopIndex;
1433 
1434             for(int counter = 0, maxCounter = getChildCount();
1435                 counter < maxCounter; counter++) {
1436                 aChild = (FHTreeStateNode)getChildAt(counter);
1437                 if(aChild.childIndex >= stopIndex)
1438                     return retCount;
1439                 else {
1440                     retCount += aChild.getTotalChildCount();
1441                 }
1442             }
1443             return retCount;
1444         }
1445 
1446         /**
1447          * Messaged when this node either expands or collapses.
1448          */
1449         protected void didAdjustTree() {
1450         }
1451 
1452     } // FixedHeightLayoutCache.FHTreeStateNode
1453 
1454 
1455     /**
1456      * Used as a placeholder when getting the path in FHTreeStateNodes.
1457      */
1458     private class SearchInfo {
1459         protected FHTreeStateNode   node;
1460         protected boolean           isNodeParentNode;
1461         protected int               childIndex;
1462 
1463         protected TreePath getPath() {
1464             if(node == null)
1465                 return null;
1466 
1467             if(isNodeParentNode)
1468                 return node.getTreePath().pathByAddingChild(treeModel.getChild
1469                                             (node.getUserObject(),
1470                                              childIndex));
1471             return node.path;
1472         }
1473     } // FixedHeightLayoutCache.SearchInfo
1474 
1475 
1476     /**
1477      * An enumerator to iterate through visible nodes.
1478      */
1479     // This is very similar to
1480     // VariableHeightTreeState.VisibleTreeStateNodeEnumeration
1481     private class VisibleFHTreeStateNodeEnumeration
1482         implements Enumeration<TreePath>
1483     {
1484         /** Parent thats children are being enumerated. */
1485         protected FHTreeStateNode     parent;
1486         /** Index of next child. An index of -1 signifies parent should be
1487          * visibled next. */
1488         protected int                 nextIndex;
1489         /** Number of children in parent. */
1490         protected int                 childCount;
1491 
1492         protected VisibleFHTreeStateNodeEnumeration(FHTreeStateNode node) {
1493             this(node, -1);
1494         }
1495 
1496         protected VisibleFHTreeStateNodeEnumeration(FHTreeStateNode parent,
1497                                                     int startIndex) {
1498             this.parent = parent;
1499             this.nextIndex = startIndex;
1500             this.childCount = treeModel.getChildCount(this.parent.
1501                                                       getUserObject());
1502         }
1503 
1504         /**
1505          * @return true if more visible nodes.
1506          */
1507         public boolean hasMoreElements() {
1508             return (parent != null);
1509         }
1510 
1511         /**
1512          * @return next visible TreePath.
1513          */
1514         public TreePath nextElement() {
1515             if(!hasMoreElements())
1516                 throw new NoSuchElementException("No more visible paths");
1517 
1518             TreePath                retObject;
1519 
1520             if(nextIndex == -1)
1521                 retObject = parent.getTreePath();
1522             else {
1523                 FHTreeStateNode  node = parent.getChildAtModelIndex(nextIndex);
1524 
1525                 if(node == null)
1526                     retObject = parent.getTreePath().pathByAddingChild
1527                                   (treeModel.getChild(parent.getUserObject(),
1528                                                       nextIndex));
1529                 else
1530                     retObject = node.getTreePath();
1531             }
1532             updateNextObject();
1533             return retObject;
1534         }
1535 
1536         /**
1537          * Determines the next object by invoking <code>updateNextIndex</code>
1538          * and if not succesful <code>findNextValidParent</code>.
1539          */
1540         protected void updateNextObject() {
1541             if(!updateNextIndex()) {
1542                 findNextValidParent();
1543             }
1544         }
1545 
1546         /**
1547          * Finds the next valid parent, this should be called when nextIndex
1548          * is beyond the number of children of the current parent.
1549          */
1550         protected boolean findNextValidParent() {
1551             if(parent == root) {
1552                 // mark as invalid!
1553                 parent = null;
1554                 return false;
1555             }
1556             while(parent != null) {
1557                 FHTreeStateNode      newParent = (FHTreeStateNode)parent.
1558                                                   getParent();
1559 
1560                 if(newParent != null) {
1561                     nextIndex = parent.childIndex;
1562                     parent = newParent;
1563                     childCount = treeModel.getChildCount
1564                                             (parent.getUserObject());
1565                     if(updateNextIndex())
1566                         return true;
1567                 }
1568                 else
1569                     parent = null;
1570             }
1571             return false;
1572         }
1573 
1574         /**
1575          * Updates <code>nextIndex</code> returning false if it is beyond
1576          * the number of children of parent.
1577          */
1578         protected boolean updateNextIndex() {
1579             // nextIndex == -1 identifies receiver, make sure is expanded
1580             // before descend.
1581             if(nextIndex == -1 && !parent.isExpanded()) {
1582                 return false;
1583             }
1584 
1585             // Check that it can have kids
1586             if(childCount == 0) {
1587                 return false;
1588             }
1589             // Make sure next index not beyond child count.
1590             else if(++nextIndex >= childCount) {
1591                 return false;
1592             }
1593 
1594             FHTreeStateNode    child = parent.getChildAtModelIndex(nextIndex);
1595 
1596             if(child != null && child.isExpanded()) {
1597                 parent = child;
1598                 nextIndex = -1;
1599                 childCount = treeModel.getChildCount(child.getUserObject());
1600             }
1601             return true;
1602         }
1603     } // FixedHeightLayoutCache.VisibleFHTreeStateNodeEnumeration
1604 }