pos. Subclassed
     * here to load the children if necessary.
     *
     * @param pos the position of the child node to fetch
     *
     * @return the childnode at the specified position
     */
    public TreeNode getChildAt(int pos)
    {
      loadChildren();
      return super.getChildAt(pos);
    }
    public boolean isLeaf()
    {
      return childValue == null || !(childValue instanceof Hashtable
          || childValue instanceof Vector
          || childValue.getClass().isArray());
    }
    public static void createChildren(DefaultMutableTreeNode parent,
                                      Object children)
    {
      if (children instanceof Hashtable)
        {
          Hashtable tab = (Hashtable) children;
          Enumeration e = tab.keys();
          while (e.hasMoreElements())
            {
              Object key = e.nextElement();
              Object val = tab.get(key);
              parent.add(new DynamicUtilTreeNode(key, val));
            }
        }
      else if (children instanceof Vector)
        {
          Iterator i = ((Vector) children).iterator();
          while (i.hasNext())
            {
              Object n = i.next();
              parent.add(new DynamicUtilTreeNode(n, n));
            }
        }
      else if (children != null && children.getClass().isArray())
        {
          Object[] arr = (Object[]) children;
          for (int i = 0; i < arr.length; ++i)
            parent.add(new DynamicUtilTreeNode(arr[i], arr[i]));
        }
    }
  }
  /**
   * Listens to the model of the JTree and updates the property
   * expandedState if nodes are removed or changed.
   */
  protected class TreeModelHandler implements TreeModelListener
  {
    /**
     * Creates a new instance of TreeModelHandler.
     */
    protected TreeModelHandler()
    {
      // Nothing to do here.
    }
    /**
     * Notifies when a node has changed in some ways. This does not include
     * that a node has changed its location or changed it's children. It
     * only means that some attributes of the node have changed that might
     * affect its presentation.
     *
     * This method is called after the actual change occured.
     *
     * @param ev the TreeModelEvent describing the change
     */
    public void treeNodesChanged(TreeModelEvent ev)
    {
      // Nothing to do here.
    }
    /**
     * Notifies when a node is inserted into the tree.
     *
     * This method is called after the actual change occured.
     *
     * @param ev the TreeModelEvent describing the change
     */
    public void treeNodesInserted(TreeModelEvent ev)
    {
      // nothing to do here
    }
    /**
     * Notifies when a node is removed from the tree.
     *
     * This method is called after the actual change occured.
     *
     * @param ev the TreeModelEvent describing the change
         */
    public void treeNodesRemoved(TreeModelEvent ev)
    {
      if (ev != null)
        {
          TreePath parent = ev.getTreePath();
          Object[] children = ev.getChildren();
          TreeSelectionModel sm = getSelectionModel();
          if (children != null)
            {
              TreePath path;
              Vector toRemove = new Vector();
              // Collect items that we must remove.
              for (int i = children.length - 1; i >= 0; i--)
                {
                  path = parent.pathByAddingChild(children[i]);
                  if (nodeStates.containsKey(path))
                    toRemove.add(path);
                  // Clear selection while we are at it.
                  if (sm != null)
                    removeDescendantSelectedPaths(path, true);
                }
              if (toRemove.size() > 0)
                removeDescendantToggledPaths(toRemove.elements());
              TreeModel model = getModel();
              if (model == null || model.isLeaf(parent.getLastPathComponent()))
                nodeStates.remove(parent);
            }
        }
    }
    /**
     * Notifies when the structure of the tree is changed.
     *
     * This method is called after the actual change occured.
     *
     * @param ev the TreeModelEvent describing the change
     */
    public void treeStructureChanged(TreeModelEvent ev)
    {
      if (ev != null)
        {
          TreePath parent = ev.getTreePath();
          if (parent != null)
            {
              if (parent.getPathCount() == 1)
                {
                  // We have a new root, clear everything.
                  clearToggledPaths();
                  Object root = treeModel.getRoot();
                  if (root != null && treeModel.isLeaf(root))
                    nodeStates.put(parent, Boolean.TRUE);
                }
              else if (nodeStates.containsKey(parent))
                {
                  Vector toRemove = new Vector();
                  boolean expanded = isExpanded(parent);
                  toRemove.add(parent);
                  removeDescendantToggledPaths(toRemove.elements());
                  if (expanded)
                    {
                      TreeModel model = getModel();
                      if (model != null
                          || model.isLeaf(parent.getLastPathComponent()))
                        collapsePath(parent);
                      else
                        nodeStates.put(parent, Boolean.TRUE);
                    }
                }
              removeDescendantSelectedPaths(parent, false);
            }
        }
    }
  }
  /**
   * This redirects TreeSelectionEvents and rewrites the source of it to be
   * this JTree. This is typically done when the tree model generates an
   * event, but the JTree object associated with that model should be listed
   * as the actual source of the event.
   */
  protected class TreeSelectionRedirector implements TreeSelectionListener,
                                                     Serializable
  {
    /** The serial version UID. */
    private static final long serialVersionUID = -3505069663646241664L;
    /**
     * Creates a new instance of TreeSelectionRedirector
     */
    protected TreeSelectionRedirector()
    {
      // Nothing to do here.
    }
    /**
     * Notifies when the tree selection changes.
     *
     * @param ev the TreeSelectionEvent that describes the change
     */
    public void valueChanged(TreeSelectionEvent ev)
    {
      TreeSelectionEvent rewritten =
        (TreeSelectionEvent) ev.cloneWithSource(JTree.this);
      fireValueChanged(rewritten);
    }
  }
  /**
   * A TreeModel that does not allow anything to be selected.
   */
  protected static class EmptySelectionModel extends DefaultTreeSelectionModel
  {
    /** The serial version UID. */
    private static final long serialVersionUID = -5815023306225701477L;
    /**
     * The shared instance of this model.
     */
    protected static final EmptySelectionModel sharedInstance =
      new EmptySelectionModel();
    /**
     * Creates a new instance of EmptySelectionModel.
     */
    protected EmptySelectionModel()
    {
      // Nothing to do here.
    }
    /**
     * Returns the shared instance of EmptySelectionModel.
     *
     * @return the shared instance of EmptySelectionModel
     */
    public static EmptySelectionModel sharedInstance()
    {
      return sharedInstance;
    }
    /**
     * This catches attempts to set a selection and sets nothing instead.
     *
     * @param paths not used here
     */
    public void setSelectionPaths(TreePath[] paths)
    {
      // We don't allow selections in this class.
    }
    /**
     * This catches attempts to add something to the selection.
     *
     * @param paths not used here
     */
    public void addSelectionPaths(TreePath[] paths)
    {
      // We don't allow selections in this class.
    }
    /**
     * This catches attempts to remove something from the selection.
     *
     * @param paths not used here
     */
    public void removeSelectionPaths(TreePath[] paths)
    {
      // We don't allow selections in this class.
    }
  }
  private static final long serialVersionUID = 7559816092864483649L;
  public static final String CELL_EDITOR_PROPERTY = "cellEditor";
  public static final String CELL_RENDERER_PROPERTY = "cellRenderer";
  public static final String EDITABLE_PROPERTY = "editable";
  public static final String INVOKES_STOP_CELL_EDITING_PROPERTY =
    "invokesStopCellEditing";
  public static final String LARGE_MODEL_PROPERTY = "largeModel";
  public static final String ROOT_VISIBLE_PROPERTY = "rootVisible";
  public static final String ROW_HEIGHT_PROPERTY = "rowHeight";
  public static final String SCROLLS_ON_EXPAND_PROPERTY = "scrollsOnExpand";
  public static final String SELECTION_MODEL_PROPERTY = "selectionModel";
  public static final String SHOWS_ROOT_HANDLES_PROPERTY = "showsRootHandles";
  public static final String TOGGLE_CLICK_COUNT_PROPERTY = "toggleClickCount";
  public static final String TREE_MODEL_PROPERTY = "model";
  public static final String VISIBLE_ROW_COUNT_PROPERTY = "visibleRowCount";
  /** @since 1.3 */
  public static final String ANCHOR_SELECTION_PATH_PROPERTY =
    "anchorSelectionPath";
        /** @since 1.3 */
  public static final String LEAD_SELECTION_PATH_PROPERTY = "leadSelectionPath";
  /** @since 1.3 */
  public static final String EXPANDS_SELECTED_PATHS_PROPERTY =
    "expandsSelectedPaths";
  private static final Object EXPANDED = Boolean.TRUE;
  private static final Object COLLAPSED = Boolean.FALSE;
  private boolean dragEnabled;
  private boolean expandsSelectedPaths;
  private TreePath anchorSelectionPath;
  /**
   * This contains the state of all nodes in the tree. Al/ entries map the
   * TreePath of a note to to its state. Valid states are EXPANDED and
   * COLLAPSED. Nodes not in this Hashtable are assumed state COLLAPSED.
   *
   * This is package private to avoid accessor methods.
   */
  Hashtable nodeStates = new Hashtable();
  protected transient TreeCellEditor cellEditor;
  protected transient TreeCellRenderer cellRenderer;
  protected boolean editable;
  protected boolean invokesStopCellEditing;
  protected boolean largeModel;
  protected boolean rootVisible;
  protected int rowHeight;
  protected boolean scrollsOnExpand;
  protected transient TreeSelectionModel selectionModel;
  protected boolean showsRootHandles;
  protected int toggleClickCount;
  protected transient TreeModel treeModel;
  protected int visibleRowCount;
  /**
   * Handles TreeModelEvents to update the expandedState.
   */
  protected transient TreeModelListener treeModelListener;
  /**
   * Redirects TreeSelectionEvents so that the source is this JTree.
   */
  protected TreeSelectionRedirector selectionRedirector =
    new TreeSelectionRedirector();
  /**
   * Indicates if the rowHeight property has been set by a client
   * program or by the UI.
   *
   * @see #setUIProperty(String, Object)
   * @see LookAndFeel#installProperty(JComponent, String, Object)
   */
  private boolean clientRowHeightSet = false;
  /**
   * Indicates if the scrollsOnExpand property has been set by a client
   * program or by the UI.
   *
   * @see #setUIProperty(String, Object)
   * @see LookAndFeel#installProperty(JComponent, String, Object)
   */
  private boolean clientScrollsOnExpandSet = false;
  /**
   * Indicates if the showsRootHandles property has been set by a client
   * program or by the UI.
   *
   * @see #setUIProperty(String, Object)
   * @see LookAndFeel#installProperty(JComponent, String, Object)
   */
  private boolean clientShowsRootHandlesSet = false;
  /**
   * Creates a new JTree object.
   */
  public JTree()
  {
    this(getDefaultTreeModel());
  }
  /**
   * Creates a new JTree object.
   *
   * @param value the initial nodes in the tree
   */
  public JTree(Hashtable, ?> value)
  {
    this(createTreeModel(value));
  }
  /**
   * Creates a new JTree object.
   *
   * @param value the initial nodes in the tree
   */
  public JTree(Object[] value)
  {
    this(createTreeModel(value));
  }
  /**
   * Creates a new JTree object.
   *
   * @param model the model to use
   */
  public JTree(TreeModel model)
  {
    setRootVisible(true);
    setSelectionModel( new DefaultTreeSelectionModel() );
    // The root node appears expanded by default.
    nodeStates = new Hashtable();
    // The cell renderer gets set by the UI.
    cellRenderer = null;
    // Install the UI before installing the model. This way we avoid double
    // initialization of lots of UI and model stuff inside the UI and related
    // classes. The necessary UI updates are performed via property change
    // events to the UI.
    updateUI();
    setModel(model);
  }
  /**
   * Creates a new JTree object.
   *
   * @param root the root node
   */
  public JTree(TreeNode root)
  {
    this(root, false);
  }
  /**
   * Creates a new JTree object.
   *
   * @param root the root node
   * @param asksAllowChildren if false, all nodes without children are leaf
   *        nodes. If true, only nodes that do not allow children are leaf
   *        nodes.
   */
  public JTree(TreeNode root, boolean asksAllowChildren)
  {
    this(new DefaultTreeModel(root, asksAllowChildren));
  }
  /**
   * Creates a new JTree object.
   *
   * @param value the initial nodes in the tree
   */
  public JTree(Vector> value)
  {
    this(createTreeModel(value));
  }
  public int getRowForPath(TreePath path)
  {
    TreeUI ui = getUI();
    if (ui != null)
      return ui.getRowForPath(this, path);
    return -1;
  }
  public TreePath getPathForRow(int row)
  {
    TreeUI ui = getUI();
    return ui != null ? ui.getPathForRow(this, row) : null;
  }
  /**
   * Get the pathes that are displayes between the two given rows.
   *
   * @param index0 the starting row, inclusive
   * @param index1 the ending row, inclusive
   *
   * @return the array of the tree pathes
   */
  protected TreePath[] getPathBetweenRows(int index0, int index1)
  {
    TreeUI ui = getUI();
    if (ui == null)
      return null;
    int minIndex = Math.min(index0, index1);
    int maxIndex = Math.max(index0, index1);
    TreePath[] paths = new TreePath[maxIndex - minIndex + 1];
    for (int i = minIndex; i <= maxIndex; ++i)
      paths[i - minIndex] = ui.getPathForRow(this, i);
    return paths;
  }
  /**
   * Creates a new TreeModel object.
   *
   * @param value the values stored in the model
   */
  protected static TreeModel createTreeModel(Object value)
  {
    return new DefaultTreeModel(new DynamicUtilTreeNode(value, value));
  }
  /**
   * Return the UI associated with this JTree object.
   *
   * @return the associated TreeUI object
   */
  public TreeUI getUI()
  {
    return (TreeUI) ui;
  }
  /**
   * Sets the UI associated with this JTree object.
   *
   * @param ui the TreeUI to associate
   */
  public void setUI(TreeUI ui)
  {
    super.setUI(ui);
  }
  /**
   * This method resets the UI used to the Look and Feel defaults..
   */
  public void updateUI()
  {
    setUI((TreeUI) UIManager.getUI(this));
  }
  /**
   * This method returns the String ID of the UI class of Separator.
   *
   * @return The UI class' String ID.
   */
  public String getUIClassID()
  {
    return "TreeUI";
  }
  /**
   * Gets the AccessibleContext associated with this
   * JTree.
   *
   * @return the associated context
   */
  public AccessibleContext getAccessibleContext()
  {
    return new AccessibleJTree();
  }
  /**
   * Returns the preferred viewport size.
   *
   * @return the preferred size
   */
  public Dimension getPreferredScrollableViewportSize()
  {
    return getPreferredSize();
  }
  /**
   * Return the preferred scrolling amount (in pixels) for the given scrolling
   * direction and orientation. This method handles a partially exposed row by
   * returning the distance required to completely expose the item.
   *
   * @param visibleRect the currently visible part of the component.
   * @param orientation the scrolling orientation
   * @param direction the scrolling direction (negative - up, positive -down).
   *          The values greater than one means that more mouse wheel or similar
   *          events were generated, and hence it is better to scroll the longer
   *          distance.
   * @author Audrius Meskauskas (audriusa@bioinformatics.org)
   */
  public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation,
                                        int direction)
  {
    int delta = 0;
    // Round so that the top would start from the row boundary
    if (orientation == SwingConstants.VERTICAL)
      {
        int row = getClosestRowForLocation(0, visibleRect.y);
        if (row != -1)
          {
            Rectangle b = getRowBounds(row);
            if (b.y != visibleRect.y)
              {
                if (direction < 0)
                  delta = Math.max(0, visibleRect.y - b.y);
                else
                  delta = b.y + b.height - visibleRect.y;
              }
            else
              {
                if (direction < 0)
                  {
                    if (row != 0)
                      {
                        b = getRowBounds(row - 1);
                        delta = b.height;
                      }
                  }
                else
                  delta = b.height;
              }
          }
      }
    else
      // The RI always  returns 4 for HORIZONTAL scrolling.
      delta = 4;
    return delta;
  }
  public int getScrollableBlockIncrement(Rectangle visibleRect,
                                         int orientation, int direction)
  {
    int block;
    if (orientation == SwingConstants.VERTICAL)
      block = visibleRect.height;
    else
      block = visibleRect.width;
    return block;
  }
  public boolean getScrollableTracksViewportHeight()
  {
    if (getParent() instanceof JViewport)
      return ((JViewport) getParent()).getHeight() > getPreferredSize().height;
    return false;
  }
  public boolean getScrollableTracksViewportWidth()
  {
    if (getParent() instanceof JViewport)
      return ((JViewport) getParent()).getWidth() > getPreferredSize().width;
    return false;
  }
  /**
   * Adds a TreeExpansionListener object to the tree.
   *
   * @param listener the listener to add
   */
  public void addTreeExpansionListener(TreeExpansionListener listener)
  {
    listenerList.add(TreeExpansionListener.class, listener);
  }
  /**
   * Removes a TreeExpansionListener object from the tree.
   *
   * @param listener the listener to remove
   */
  public void removeTreeExpansionListener(TreeExpansionListener listener)
  {
    listenerList.remove(TreeExpansionListener.class, listener);
  }
  /**
   * Returns all added TreeExpansionListener objects.
   *
   * @return an array of listeners
   */
  public TreeExpansionListener[] getTreeExpansionListeners()
  {
    return (TreeExpansionListener[]) getListeners(TreeExpansionListener.class);
  }
  /**
   * Notifies all listeners that the tree was collapsed.
   *
   * @param path the path to the node that was collapsed
   */
  public void fireTreeCollapsed(TreePath path)
  {
    TreeExpansionEvent event = new TreeExpansionEvent(this, path);
    TreeExpansionListener[] listeners = getTreeExpansionListeners();
    for (int index = 0; index < listeners.length; ++index)
      listeners[index].treeCollapsed(event);
  }
  /**
   * Notifies all listeners that the tree was expanded.
   *
   * @param path the path to the node that was expanded
   */
  public void fireTreeExpanded(TreePath path)
  {
    TreeExpansionEvent event = new TreeExpansionEvent(this, path);
    TreeExpansionListener[] listeners = getTreeExpansionListeners();
    for (int index = 0; index < listeners.length; ++index)
      listeners[index].treeExpanded(event);
  }
  /**
   * Adds a TreeSelctionListener object to the tree.
   *
   * @param listener the listener to add
   */
  public void addTreeSelectionListener(TreeSelectionListener listener)
  {
    listenerList.add(TreeSelectionListener.class, listener);
  }
  /**
   * Removes a TreeSelectionListener object from the tree.
   *
   * @param listener the listener to remove
   */
  public void removeTreeSelectionListener(TreeSelectionListener listener)
  {
    listenerList.remove(TreeSelectionListener.class, listener);
  }
  /**
   * Returns all added TreeSelectionListener objects.
   *
   * @return an array of listeners
   */
  public TreeSelectionListener[] getTreeSelectionListeners()
  {
    return (TreeSelectionListener[])
    getListeners(TreeSelectionListener.class);
  }
  /**
   * Notifies all listeners when the selection of the tree changed.
   *
   * @param event the event to send
   */
  protected void fireValueChanged(TreeSelectionEvent event)
  {
    TreeSelectionListener[] listeners = getTreeSelectionListeners();
    for (int index = 0; index < listeners.length; ++index)
      listeners[index].valueChanged(event);
  }
  /**
   * Adds a TreeWillExpandListener object to the tree.
   *
   * @param listener the listener to add
   */
  public void addTreeWillExpandListener(TreeWillExpandListener listener)
  {
    listenerList.add(TreeWillExpandListener.class, listener);
  }
  /**
   * Removes a TreeWillExpandListener object from the tree.
   *
   * @param listener the listener to remove
   */
  public void removeTreeWillExpandListener(TreeWillExpandListener listener)
  {
    listenerList.remove(TreeWillExpandListener.class, listener);
  }
  /**
   * Returns all added TreeWillExpandListener objects.
   *
   * @return an array of listeners
   */
  public TreeWillExpandListener[] getTreeWillExpandListeners()
  {
    return (TreeWillExpandListener[])
    getListeners(TreeWillExpandListener.class);
  }
  /**
   * Notifies all listeners that the tree will collapse.
   *
   * @param path the path to the node that will collapse
   */
  public void fireTreeWillCollapse(TreePath path) throws ExpandVetoException
  {
    TreeExpansionEvent event = new TreeExpansionEvent(this, path);
    TreeWillExpandListener[] listeners = getTreeWillExpandListeners();
    for (int index = 0; index < listeners.length; ++index)
      listeners[index].treeWillCollapse(event);
  }
  /**
   * Notifies all listeners that the tree will expand.
   *
   * @param path the path to the node that will expand
   */
  public void fireTreeWillExpand(TreePath path) throws ExpandVetoException
  {
    TreeExpansionEvent event = new TreeExpansionEvent(this, path);
    TreeWillExpandListener[] listeners = getTreeWillExpandListeners();
    for (int index = 0; index < listeners.length; ++index)
      listeners[index].treeWillExpand(event);
  }
  /**
   * Returns the model of this JTree object.
   *
   * @return the associated TreeModel
   */
  public TreeModel getModel()
  {
    return treeModel;
  }
  /**
   * Sets the model to use in JTree.
   *
   * @param model the TreeModel to use
   */
  public void setModel(TreeModel model)
  {
    if (treeModel == model)
      return;
    // Remove listeners from old model.
    if (treeModel != null && treeModelListener != null)
      treeModel.removeTreeModelListener(treeModelListener);
    // add treeModelListener to the new model
    if (treeModelListener == null)
      treeModelListener = createTreeModelListener();
    if (model != null) // as setModel(null) is allowed
      model.addTreeModelListener(treeModelListener);
    TreeModel oldValue = treeModel;
    treeModel = model;
    clearToggledPaths();
    if (treeModel != null)
      {
        if (treeModelListener == null)
          treeModelListener = createTreeModelListener();
        if (treeModelListener != null)
          treeModel.addTreeModelListener(treeModelListener);
        Object root = treeModel.getRoot();
        if (root != null && !treeModel.isLeaf(root))
          {
            nodeStates.put(new TreePath(root), Boolean.TRUE);
          }
      }
    firePropertyChange(TREE_MODEL_PROPERTY, oldValue, model);
  }
  /**
   * Checks if this JTree object is editable.
   *
   * @return true if this tree object is editable,
   *         false otherwise
   */
  public boolean isEditable()
  {
    return editable;
  }
  /**
   * Sets the editable property.
   *
   * @param flag true to make this tree object editable,
   *        false otherwise
   */
  public void setEditable(boolean flag)
  {
    if (editable == flag)
      return;
    boolean oldValue = editable;
    editable = flag;
    firePropertyChange(EDITABLE_PROPERTY, oldValue, editable);
  }
  /**
   * Checks if the root element is visible.
   *
   * @return true if the root element is visible,
   *         false otherwise
   */
  public boolean isRootVisible()
  {
    return rootVisible;
  }
  public void setRootVisible(boolean flag)
  {
    if (rootVisible == flag)
      return;
    // If the root is currently selected, unselect it
    if (rootVisible && !flag)
      {
        TreeSelectionModel model = getSelectionModel();
        // The root is always shown in the first row
        TreePath rootPath = getPathForRow(0);
        model.removeSelectionPath(rootPath);
      }
    boolean oldValue = rootVisible;
    rootVisible = flag;
    firePropertyChange(ROOT_VISIBLE_PROPERTY, oldValue, flag);
  }
  public boolean getShowsRootHandles()
  {
    return showsRootHandles;
  }
  public void setShowsRootHandles(boolean flag)
  {
    clientShowsRootHandlesSet = true;
    if (showsRootHandles == flag)
      return;
    boolean oldValue = showsRootHandles;
    showsRootHandles = flag;
    firePropertyChange(SHOWS_ROOT_HANDLES_PROPERTY, oldValue, flag);
  }
  public TreeCellEditor getCellEditor()
  {
    return cellEditor;
  }
  public void setCellEditor(TreeCellEditor editor)
  {
    if (cellEditor == editor)
      return;
    TreeCellEditor oldValue = cellEditor;
    cellEditor = editor;
    firePropertyChange(CELL_EDITOR_PROPERTY, oldValue, editor);
  }
  public TreeCellRenderer getCellRenderer()
  {
    return cellRenderer;
  }
  public void setCellRenderer(TreeCellRenderer newRenderer)
  {
    if (cellRenderer == newRenderer)
      return;
    TreeCellRenderer oldValue = cellRenderer;
    cellRenderer = newRenderer;
    firePropertyChange(CELL_RENDERER_PROPERTY, oldValue, newRenderer);
  }
  public TreeSelectionModel getSelectionModel()
  {
    return selectionModel;
  }
  public void setSelectionModel(TreeSelectionModel model)
  {
    if (selectionModel == model)
      return;
    if( model == null )
      model = EmptySelectionModel.sharedInstance();
    if (selectionModel != null)
      selectionModel.removeTreeSelectionListener(selectionRedirector);
    TreeSelectionModel oldValue = selectionModel;
    selectionModel = model;
    selectionModel.addTreeSelectionListener(selectionRedirector);
    firePropertyChange(SELECTION_MODEL_PROPERTY, oldValue, model);
    revalidate();
    repaint();
  }
  public int getVisibleRowCount()
  {
    return visibleRowCount;
  }
  public void setVisibleRowCount(int rows)
  {
    if (visibleRowCount == rows)
      return;
    int oldValue = visibleRowCount;
    visibleRowCount = rows;
    firePropertyChange(VISIBLE_ROW_COUNT_PROPERTY, oldValue, rows);
  }
  public boolean isLargeModel()
  {
    return largeModel;
  }
  public void setLargeModel(boolean large)
  {
    if (largeModel == large)
      return;
    boolean oldValue = largeModel;
    largeModel = large;
    firePropertyChange(LARGE_MODEL_PROPERTY, oldValue, large);
  }
  public int getRowHeight()
  {
    return rowHeight;
  }
  public void setRowHeight(int height)
  {
    clientRowHeightSet = true;
    if (rowHeight == height)
      return;
    int oldValue = rowHeight;
    rowHeight = height;
    firePropertyChange(ROW_HEIGHT_PROPERTY, oldValue, height);
  }
  public boolean isFixedRowHeight()
  {
    return rowHeight > 0;
  }
  public boolean getInvokesStopCellEditing()
  {
    return invokesStopCellEditing;
  }
  public void setInvokesStopCellEditing(boolean invoke)
  {
    if (invokesStopCellEditing == invoke)
      return;
    boolean oldValue = invokesStopCellEditing;
    invokesStopCellEditing = invoke;
    firePropertyChange(INVOKES_STOP_CELL_EDITING_PROPERTY,
                       oldValue, invoke);
  }
  /**
   * @since 1.3
   */
  public int getToggleClickCount()
  {
    return toggleClickCount;
  }
  /**
   * @since 1.3
   */
  public void setToggleClickCount(int count)
  {
    if (toggleClickCount == count)
      return;
    int oldValue = toggleClickCount;
    toggleClickCount = count;
    firePropertyChange(TOGGLE_CLICK_COUNT_PROPERTY, oldValue, count);
  }
  public void scrollPathToVisible(TreePath path)
  {
    if (path == null)
      return;
    Rectangle rect = getPathBounds(path);
    scrollRectToVisible(rect);
  }
  public void scrollRowToVisible(int row)
  {
    scrollPathToVisible(getPathForRow(row));
  }
  public boolean getScrollsOnExpand()
  {
    return scrollsOnExpand;
  }
  public void setScrollsOnExpand(boolean scroll)
  {
    clientScrollsOnExpandSet = true;
    if (scrollsOnExpand == scroll)
      return;
    boolean oldValue = scrollsOnExpand;
    scrollsOnExpand = scroll;
    firePropertyChange(SCROLLS_ON_EXPAND_PROPERTY, oldValue, scroll);
  }
  public void setSelectionPath(TreePath path)
  {
    clearSelectionPathStates();
    selectionModel.setSelectionPath(path);
  }
  public void setSelectionPaths(TreePath[] paths)
  {
    clearSelectionPathStates();
    selectionModel.setSelectionPaths(paths);
  }
  /**
   * This method, and all calls to it, should be removed once the
   * DefaultTreeModel fires events properly.  Maintenance of the nodeStates
   * table should really be done in the TreeModelHandler.
   */
  private void clearSelectionPathStates()
  {
    TreePath[] oldPaths = selectionModel.getSelectionPaths();
    if (oldPaths != null)
      for (int i = 0; i < oldPaths.length; i++)
        nodeStates.remove(oldPaths[i]);
  }
  public void setSelectionRow(int row)
  {
    TreePath path = getPathForRow(row);
    if (path != null)
      setSelectionPath(path);
  }
  public void setSelectionRows(int[] rows)
  {
    // Make sure we have an UI so getPathForRow() does not return null.
    if (rows == null || getUI() == null)
      return;
    TreePath[] paths = new TreePath[rows.length];
    for (int i = rows.length - 1; i >= 0; --i)
      paths[i] = getPathForRow(rows[i]);
    setSelectionPaths(paths);
  }
  public void setSelectionInterval(int index0, int index1)
  {
    TreePath[] paths = getPathBetweenRows(index0, index1);
    if (paths != null)
      setSelectionPaths(paths);
  }
  public void addSelectionPath(TreePath path)
  {
    selectionModel.addSelectionPath(path);
  }
  public void addSelectionPaths(TreePath[] paths)
  {
    selectionModel.addSelectionPaths(paths);
  }
  public void addSelectionRow(int row)
  {
    TreePath path = getPathForRow(row);
    if (path != null)
      selectionModel.addSelectionPath(path);
  }
  public void addSelectionRows(int[] rows)
  {
    // Make sure we have an UI so getPathForRow() does not return null.
    if (rows == null || getUI() == null)
      return;
    TreePath[] paths = new TreePath[rows.length];
    for (int i = rows.length - 1; i >= 0; --i)
      paths[i] = getPathForRow(rows[i]);
    addSelectionPaths(paths);
  }
  /**
   * Select all rows between the two given indexes, inclusive. The method
   * will not select the inner leaves and braches of the currently collapsed
   * nodes in this interval.
   *
   * @param index0 the starting row, inclusive
   * @param index1 the ending row, inclusive
   */
  public void addSelectionInterval(int index0, int index1)
  {
    TreePath[] paths = getPathBetweenRows(index0, index1);
    if (paths != null)
      addSelectionPaths(paths);
  }
  public void removeSelectionPath(TreePath path)
  {
    clearSelectionPathStates();
    selectionModel.removeSelectionPath(path);
  }
  public void removeSelectionPaths(TreePath[] paths)
  {
    clearSelectionPathStates();
    selectionModel.removeSelectionPaths(paths);
  }
  public void removeSelectionRow(int row)
  {
    TreePath path = getPathForRow(row);
    if (path != null)
      removeSelectionPath(path);
  }
  public void removeSelectionRows(int[] rows)
  {
    if (rows == null || getUI() == null)
      return;
    TreePath[] paths = new TreePath[rows.length];
    for (int i = rows.length - 1; i >= 0; --i)
      paths[i] = getPathForRow(rows[i]);
    removeSelectionPaths(paths);
  }
  public void removeSelectionInterval(int index0, int index1)
  {
    TreePath[] paths = getPathBetweenRows(index0, index1);
    if (paths != null)
      removeSelectionPaths(paths);
  }
  public void clearSelection()
  {
    selectionModel.clearSelection();
    setLeadSelectionPath(null);
  }
  public TreePath getLeadSelectionPath()
  {
    if (selectionModel == null)
      return null;
    else
      return selectionModel.getLeadSelectionPath();
  }
  /**
   * @since 1.3
   */
  public void setLeadSelectionPath(TreePath path)
  {
    if (selectionModel != null)
      {
        TreePath oldValue = selectionModel.getLeadSelectionPath();
        if (path == oldValue || path != null && path.equals(oldValue))
          return;
        // Repaint the previous and current rows with the lead selection path.
        if (path != null)
          {
            repaint(getPathBounds(path));
            selectionModel.addSelectionPath(path);
          }
        if (oldValue != null)
          repaint(getPathBounds(oldValue));
        firePropertyChange(LEAD_SELECTION_PATH_PROPERTY, oldValue, path);
      }
  }
  /**
   * @since 1.3
   */
  public TreePath getAnchorSelectionPath()
  {
    return anchorSelectionPath;
  }
  /**
   * @since 1.3
   */
  public void setAnchorSelectionPath(TreePath path)
  {
    if (anchorSelectionPath == path)
      return;
    TreePath oldValue = anchorSelectionPath;
    anchorSelectionPath = path;
    firePropertyChange(ANCHOR_SELECTION_PATH_PROPERTY, oldValue, path);
  }
  public int getLeadSelectionRow()
  {
    return selectionModel.getLeadSelectionRow();
  }
  public int getMaxSelectionRow()
  {
    return selectionModel.getMaxSelectionRow();
  }
  public int getMinSelectionRow()
  {
    return selectionModel.getMinSelectionRow();
  }
  public int getSelectionCount()
  {
    return selectionModel.getSelectionCount();
  }
  public TreePath getSelectionPath()
  {
    return selectionModel.getSelectionPath();
  }
  public TreePath[] getSelectionPaths()
  {
    return selectionModel.getSelectionPaths();
  }
  public int[] getSelectionRows()
  {
    return selectionModel.getSelectionRows();
  }
  public boolean isPathSelected(TreePath path)
  {
    return selectionModel.isPathSelected(path);
  }
  /**
   * Returns true when the specified row is selected,
   * false otherwise. This call is delegated to the
   * {@link TreeSelectionModel#isRowSelected(int)} method.
   *
   * @param row the row to check
   *
   * @return true when the specified row is selected,
   *         false otherwise
   */
  public boolean isRowSelected(int row)
  {
    return selectionModel.isRowSelected(row);
  }
  public boolean isSelectionEmpty()
  {
    return selectionModel.isSelectionEmpty();
  }
  /**
   * Return the value of the dragEnabled property.
   *
   * @return the value
   *
   * @since 1.4
   */
  public boolean getDragEnabled()
  {
    return dragEnabled;
  }
  /**
   * Set the dragEnabled property.
   *
   * @param enabled new value
   *
   * @since 1.4
   */
  public void setDragEnabled(boolean enabled)
  {
    dragEnabled = enabled;
  }
  public int getRowCount()
  {
    TreeUI ui = getUI();
    if (ui != null)
      return ui.getRowCount(this);
    return 0;
  }
  public void collapsePath(TreePath path)
  {
    try
      {
        fireTreeWillCollapse(path);
      }
    catch (ExpandVetoException ev)
      {
        // We do nothing if attempt has been vetoed.
      }
    setExpandedState(path, false);
    fireTreeCollapsed(path);
  }
  public void collapseRow(int row)
  {
    if (row < 0 || row >= getRowCount())
      return;
    TreePath path = getPathForRow(row);
    if (path != null)
      collapsePath(path);
  }
  public void expandPath(TreePath path)
  {
    // Don't expand if path is null
    // or is already expanded.
    if (path == null || isExpanded(path))
      return;
    try
      {
        fireTreeWillExpand(path);
      }
    catch (ExpandVetoException ev)
      {
        // We do nothing if attempt has been vetoed.
      }
    setExpandedState(path, true);
    fireTreeExpanded(path);
  }
  public void expandRow(int row)
  {
    if (row < 0 || row >= getRowCount())
      return;
    TreePath path = getPathForRow(row);
    if (path != null)
      expandPath(path);
  }
  public boolean isCollapsed(TreePath path)
  {
    return !isExpanded(path);
  }
  public boolean isCollapsed(int row)
  {
    if (row < 0 || row >= getRowCount())
      return false;
    TreePath path = getPathForRow(row);
    if (path != null)
      return isCollapsed(path);
    return false;
  }
  public boolean isExpanded(TreePath path)
  {
    if (path == null)
      return false;
    Object state = nodeStates.get(path);
    if ((state == null) || (state != EXPANDED))
      return false;
    TreePath parent = path.getParentPath();
    if (parent != null)
      return isExpanded(parent);
    return true;
  }
  public boolean isExpanded(int row)
  {
    if (row < 0 || row >= getRowCount())
      return false;
    TreePath path = getPathForRow(row);
    if (path != null)
      return isExpanded(path);
    return false;
  }
  /**
   * @since 1.3
   */
  public boolean getExpandsSelectedPaths()
  {
    return expandsSelectedPaths;
  }
  /**
   * @since 1.3
   */
  public void setExpandsSelectedPaths(boolean flag)
  {
    if (expandsSelectedPaths == flag)
      return;
    boolean oldValue = expandsSelectedPaths;
    expandsSelectedPaths = flag;
    firePropertyChange(EXPANDS_SELECTED_PATHS_PROPERTY, oldValue, flag);
  }
  public Rectangle getPathBounds(TreePath path)
  {
    TreeUI ui = getUI();
    if (ui == null)
      return null;
    return ui.getPathBounds(this, path);
  }
  public Rectangle getRowBounds(int row)
  {
    TreePath path = getPathForRow(row);
    if (path != null)
      return getPathBounds(path);
    return null;
  }
  public boolean isEditing()
  {
    TreeUI ui = getUI();
    if (ui != null)
      return ui.isEditing(this);
    return false;
  }
  public boolean stopEditing()
  {
    TreeUI ui = getUI();
    if (isEditing())
      if (ui != null)
        return ui.stopEditing(this);
    return false;
  }
  public void cancelEditing()
  {
    TreeUI ui = getUI();
    if (isEditing())
      if (ui != null)
        ui.cancelEditing(this);
  }
  public void startEditingAtPath(TreePath path)
  {
    TreeUI ui = getUI();
    if (ui != null)
      ui.startEditingAtPath(this, path);
  }
  public TreePath getEditingPath()
  {
    TreeUI ui = getUI();
    if (ui != null)
      return ui.getEditingPath(this);
    return null;
  }
  public TreePath getPathForLocation(int x, int y)
  {
    TreePath path = getClosestPathForLocation(x, y);
    if (path != null)
      {
        Rectangle rect = getPathBounds(path);
        if ((rect != null) && rect.contains(x, y))
          return path;
      }
    return null;
  }
  public int getRowForLocation(int x, int y)
  {
    TreePath path = getPathForLocation(x, y);
    if (path != null)
      return getRowForPath(path);
    return -1;
  }
  public TreePath getClosestPathForLocation(int x, int y)
  {
    TreeUI ui = getUI();
    if (ui != null)
      return ui.getClosestPathForLocation(this, x, y);
    return null;
  }
  public int getClosestRowForLocation(int x, int y)
  {
    TreePath path = getClosestPathForLocation(x, y);
    if (path != null)
      return getRowForPath(path);
    return -1;
  }
  public Object getLastSelectedPathComponent()
  {
    TreePath path = getSelectionPath();
    if (path != null)
      return path.getLastPathComponent();
    return null;
  }
  private void doExpandParents(TreePath path, boolean state)
  {
    TreePath parent = path.getParentPath();
    if (!isExpanded(parent) && parent != null)
      doExpandParents(parent, false);
    nodeStates.put(path, state ? EXPANDED : COLLAPSED);
  }
  protected void setExpandedState(TreePath path, boolean state)
  {
    if (path == null)
      return;
    doExpandParents(path, state);
  }
  protected void clearToggledPaths()
  {
    nodeStates.clear();
  }
  protected Enumerationvalue.toString() and
   * ignores all other parameters. Subclass this method to control the
   * conversion.
   *
   * @param value the value that is converted to a String
   * @param selected indicates if that value is selected or not
   * @param expanded indicates if that value is expanded or not
   * @param leaf indicates if that value is a leaf node or not
   * @param row the row of the node
   * @param hasFocus indicates if that node has focus or not
   */
  public String convertValueToText(Object value, boolean selected,
                                   boolean expanded, boolean leaf, int row, boolean hasFocus)
  {
    return value.toString();
  }
  /**
   * A String representation of this JTree. This is intended to be used for
   * debugging. The returned string may be empty but may not be
   * null.
   *
   * @return a String representation of this JTree
   */
  protected String paramString()
  {
    // TODO: this is completely legal, but it would possibly be nice
    // to return some more content, like the tree structure, some properties
    // etc ...
    return "";
  }
  /**
   * Returns all TreePath objects which are a descendants of the given path
   * and are exapanded at the moment of the execution of this method. If the
   * state of any node is beeing toggled while this method is executing this
   * change may be left unaccounted.
   *
   * @param path The parent of this request
   *
   * @return An Enumeration containing TreePath objects
   */
  public EnumerationstartingRow that starts with prefix.
   * Searching is done in the direction specified by bias.
   *
   * @param prefix the prefix to search for in the cell values
   * @param startingRow the index of the row where to start searching from
   * @param bias the search direction, either {@link Position.Bias#Forward} or
   *        {@link Position.Bias#Backward}
   *
   * @return the path to the found element or -1 if no such element has been
   *         found
   *
   * @throws IllegalArgumentException if prefix is null or
   *         startingRow is not valid
   *
   * @since 1.4
   */
  public TreePath getNextMatch(String prefix, int startingRow,
                               Position.Bias bias)
  {
    if (prefix == null)
      throw new IllegalArgumentException("The argument 'prefix' must not be"
                                         + " null.");
    if (startingRow < 0)
      throw new IllegalArgumentException("The argument 'startingRow' must not"
                                         + " be less than zero.");
    int size = getRowCount();
    if (startingRow > size)
      throw new IllegalArgumentException("The argument 'startingRow' must not"
                                         + " be greater than the number of"
                                         + " elements in the TreeModel.");
    TreePath foundPath = null;
    if (bias == Position.Bias.Forward)
      {
        for (int i = startingRow; i < size; i++)
          {
            TreePath path = getPathForRow(i);
            Object o = path.getLastPathComponent();
            // FIXME: in the following call to convertValueToText the
            // last argument (hasFocus) should be done right.
            String item = convertValueToText(o, isRowSelected(i),
                                             isExpanded(i), treeModel.isLeaf(o),
                                             i, false);
            if (item.startsWith(prefix))
              {
                foundPath = path;
                break;
              }
          }
      }
    else
      {
        for (int i = startingRow; i >= 0; i--)
          {
            TreePath path = getPathForRow(i);
            Object o = path.getLastPathComponent();
            // FIXME: in the following call to convertValueToText the
            // last argument (hasFocus) should be done right.
            String item = convertValueToText(o, isRowSelected(i),
                                             isExpanded(i), treeModel.isLeaf(o), i, false);
            if (item.startsWith(prefix))
              {
                foundPath = path;
                break;
              }
          }
      }
    return foundPath;
  }
  /**
   * Removes any paths in the current set of selected paths that are
   * descendants of path. If includePath is set
   * to true and path itself is selected, then
   * it will be removed too.
   *
   * @param path the path from which selected descendants are to be removed
   * @param includeSelected if true then path itself
   *        will also be remove if it's selected
   *
   * @return true if something has been removed,
   *         false otherwise
   *
   * @since 1.3
   */
  protected boolean removeDescendantSelectedPaths(TreePath path,
                                                  boolean includeSelected)
  {
    boolean removedSomething = false;
    TreePath[] selected = getSelectionPaths();
    for (int index = 0; index < selected.length; index++)
      {
        if ((selected[index] == path && includeSelected)
            || (selected[index].isDescendant(path)))
          {
            removeSelectionPath(selected[index]);
            removedSomething = true;
          }
      }
    return removedSomething;
  }
  /**
   * Removes any descendants of the TreePaths in toRemove that have been
   * expanded.
   *
   * @param toRemove - Enumeration of TreePaths that need to be removed from
   * cache of toggled tree paths.
   */
  protected void removeDescendantToggledPaths(Enumeration* Sent when the tree has changed enough that we need to resize the bounds, * but not enough that we need to remove the expanded node set (e.g nodes were * expanded or collapsed, or nodes were inserted into the tree). You should * never have to invoke this, the UI will invoke this as it needs to. *
*
   * If the tree uses {@link DefaultTreeModel}, you must call
   * {@link DefaultTreeModel#reload(TreeNode)} or
   * {@link DefaultTreeModel#reload()} after adding or removing nodes. Following
   * the official Java 1.5 API standard, just calling treeDidChange, repaint()
   * or revalidate() does not update the tree appearance properly.
   *
   * @see DefaultTreeModel#reload()
   */
  public void treeDidChange()
  {
    repaint();
  }
  /**
   * Helper method for
   * {@link LookAndFeel#installProperty(JComponent, String, Object)}.
   *
   * @param propertyName the name of the property
   * @param value the value of the property
   *
   * @throws IllegalArgumentException if the specified property cannot be set
   *         by this method
   * @throws ClassCastException if the property value does not match the
   *         property type
   * @throws NullPointerException if c or
   *         propertyValue is null
   */
  void setUIProperty(String propertyName, Object value)
  {
    if (propertyName.equals("rowHeight"))
      {
        if (! clientRowHeightSet)
          {
            setRowHeight(((Integer) value).intValue());
            clientRowHeightSet = false;
          }
      }
    else if (propertyName.equals("scrollsOnExpand"))
      {
        if (! clientScrollsOnExpandSet)
          {
            setScrollsOnExpand(((Boolean) value).booleanValue());
            clientScrollsOnExpandSet = false;
          }
      }
    else if (propertyName.equals("showsRootHandles"))
      {
        if (! clientShowsRootHandlesSet)
          {
            setShowsRootHandles(((Boolean) value).booleanValue());
            clientShowsRootHandlesSet = false;
          }
      }
    else
      {
        super.setUIProperty(propertyName, value);
      }
  }
}