1 /*
   2  * Copyright (c) 1997, 2018, 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 java.beans.PropertyChangeListener;
  29 import java.io.*;
  30 import java.util.ArrayList;
  31 import java.util.BitSet;
  32 import java.util.Enumeration;
  33 import java.util.EventListener;
  34 import java.util.Hashtable;
  35 import java.util.List;
  36 import java.util.Vector;
  37 import javax.swing.event.*;
  38 import javax.swing.DefaultListSelectionModel;
  39 
  40 /**
  41  * Default implementation of TreeSelectionModel.  Listeners are notified
  42  * whenever
  43  * the paths in the selection change, not the rows. In order
  44  * to be able to track row changes you may wish to become a listener
  45  * for expansion events on the tree and test for changes from there.
  46  * <p>resetRowSelection is called from any of the methods that update
  47  * the selected paths. If you subclass any of these methods to
  48  * filter what is allowed to be selected, be sure and message
  49  * <code>resetRowSelection</code> if you do not message super.
  50  *
  51  * <strong>Warning:</strong>
  52  * Serialized objects of this class will not be compatible with
  53  * future Swing releases. The current serialization support is
  54  * appropriate for short term storage or RMI between applications running
  55  * the same version of Swing.  As of 1.4, support for long term storage
  56  * of all JavaBeans&trade;
  57  * has been added to the <code>java.beans</code> package.
  58  * Please see {@link java.beans.XMLEncoder}.
  59  *
  60  * @see javax.swing.JTree
  61  *
  62  * @author Scott Violet
  63  */
  64 @SuppressWarnings("serial")
  65 public class DefaultTreeSelectionModel implements Cloneable, Serializable, TreeSelectionModel
  66 {
  67     /** Property name for selectionMode. */
  68     public static final String          SELECTION_MODE_PROPERTY = "selectionMode";
  69 
  70     /** Used to messaged registered listeners. */
  71     protected SwingPropertyChangeSupport     changeSupport;
  72 
  73     /** Paths that are currently selected.  Will be null if nothing is
  74       * currently selected. */
  75     protected TreePath[]                selection;
  76 
  77     /** Event listener list. */
  78     protected EventListenerList   listenerList = new EventListenerList();
  79 
  80     /** Provides a row for a given path. */
  81     protected transient RowMapper               rowMapper;
  82 
  83     /** Handles maintaining the list selection model. The RowMapper is used
  84      * to map from a TreePath to a row, and the value is then placed here. */
  85     protected DefaultListSelectionModel     listSelectionModel;
  86 
  87     /** Mode for the selection, will be either SINGLE_TREE_SELECTION,
  88      * CONTIGUOUS_TREE_SELECTION or DISCONTIGUOUS_TREE_SELECTION.
  89      */
  90     protected int                           selectionMode;
  91 
  92     /** Last path that was added. */
  93     protected TreePath                      leadPath;
  94     /** Index of the lead path in selection. */
  95     protected int                           leadIndex;
  96     /** Lead row. */
  97     protected int                           leadRow;
  98 
  99     /** Used to make sure the paths are unique, will contain all the paths
 100      * in <code>selection</code>.
 101      */
 102     private Hashtable<TreePath, Boolean>    uniquePaths;
 103     private Hashtable<TreePath, Boolean>    lastPaths;
 104     private TreePath[]                      tempPaths;
 105 
 106 
 107     /**
 108      * Creates a new instance of DefaultTreeSelectionModel that is
 109      * empty, with a selection mode of DISCONTIGUOUS_TREE_SELECTION.
 110      */
 111     public DefaultTreeSelectionModel() {
 112         listSelectionModel = new DefaultListSelectionModel();
 113         selectionMode = DISCONTIGUOUS_TREE_SELECTION;
 114         leadIndex = leadRow = -1;
 115         uniquePaths = new Hashtable<TreePath, Boolean>();
 116         lastPaths = new Hashtable<TreePath, Boolean>();
 117         tempPaths = new TreePath[1];
 118     }
 119 
 120     /**
 121      * Sets the RowMapper instance. This instance is used to determine
 122      * the row for a particular TreePath.
 123      */
 124     public void setRowMapper(RowMapper newMapper) {
 125         rowMapper = newMapper;
 126         resetRowSelection();
 127     }
 128 
 129     /**
 130      * Returns the RowMapper instance that is able to map a TreePath to a
 131      * row.
 132      */
 133     public RowMapper getRowMapper() {
 134         return rowMapper;
 135     }
 136 
 137     /**
 138      * Sets the selection model, which must be one of SINGLE_TREE_SELECTION,
 139      * CONTIGUOUS_TREE_SELECTION or DISCONTIGUOUS_TREE_SELECTION. If mode
 140      * is not one of the defined value,
 141      * <code>DISCONTIGUOUS_TREE_SELECTION</code> is assumed.
 142      * <p>This may change the selection if the current selection is not valid
 143      * for the new mode. For example, if three TreePaths are
 144      * selected when the mode is changed to <code>SINGLE_TREE_SELECTION</code>,
 145      * only one TreePath will remain selected. It is up to the particular
 146      * implementation to decide what TreePath remains selected.
 147      * <p>
 148      * Setting the mode to something other than the defined types will
 149      * result in the mode becoming <code>DISCONTIGUOUS_TREE_SELECTION</code>.
 150      */
 151     public void setSelectionMode(int mode) {
 152         int            oldMode = selectionMode;
 153 
 154         selectionMode = validateSelectionMode(mode);
 155         if(oldMode != selectionMode && changeSupport != null)
 156             changeSupport.firePropertyChange(SELECTION_MODE_PROPERTY,
 157                                              Integer.valueOf(oldMode),
 158                                              Integer.valueOf(selectionMode));
 159     }
 160 
 161     private static int validateSelectionMode(int mode) {
 162         return (mode != TreeSelectionModel.SINGLE_TREE_SELECTION
 163                 && mode != TreeSelectionModel.CONTIGUOUS_TREE_SELECTION
 164                 && mode != TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION)
 165                 ? TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION : mode;
 166     }
 167 
 168     /**
 169      * Returns the selection mode, one of <code>SINGLE_TREE_SELECTION</code>,
 170      * <code>DISCONTIGUOUS_TREE_SELECTION</code> or
 171      * <code>CONTIGUOUS_TREE_SELECTION</code>.
 172      */
 173     public int getSelectionMode() {
 174         return selectionMode;
 175     }
 176 
 177     /**
 178       * Sets the selection to path. If this represents a change, then
 179       * the TreeSelectionListeners are notified. If <code>path</code> is
 180       * null, this has the same effect as invoking <code>clearSelection</code>.
 181       *
 182       * @param path new path to select
 183       */
 184     public void setSelectionPath(TreePath path) {
 185         if(path == null)
 186             setSelectionPaths(null);
 187         else {
 188             TreePath[]          newPaths = new TreePath[1];
 189 
 190             newPaths[0] = path;
 191             setSelectionPaths(newPaths);
 192         }
 193     }
 194 
 195     /**
 196      * Sets the selection. Whether the supplied paths are taken as the
 197      * new selection depends upon the selection mode. If the supplied
 198      * array is {@code null}, or empty, the selection is cleared. If
 199      * the selection mode is {@code SINGLE_TREE_SELECTION}, only the
 200      * first path in {@code pPaths} is used. If the selection
 201      * mode is {@code CONTIGUOUS_TREE_SELECTION} and the supplied paths
 202      * are not contiguous, then only the first path in {@code pPaths} is
 203      * used. If the selection mode is
 204      * {@code DISCONTIGUOUS_TREE_SELECTION}, then all paths are used.
 205      * <p>
 206      * All {@code null} paths in {@code pPaths} are ignored.
 207      * <p>
 208      * If this represents a change, all registered {@code
 209      * TreeSelectionListener}s are notified.
 210      * <p>
 211      * The lead path is set to the last unique path.
 212      * <p>
 213      * The paths returned from {@code getSelectionPaths} are in the same
 214      * order as those supplied to this method.
 215      *
 216      * @param pPaths the new selection
 217      */
 218     public void setSelectionPaths(TreePath[] pPaths) {
 219         int            newCount, newCounter, oldCount, oldCounter;
 220         TreePath[]     paths = pPaths;
 221 
 222         if(paths == null)
 223             newCount = 0;
 224         else
 225             newCount = paths.length;
 226         if(selection == null)
 227             oldCount = 0;
 228         else
 229             oldCount = selection.length;
 230         if((newCount + oldCount) != 0) {
 231             if(selectionMode == TreeSelectionModel.SINGLE_TREE_SELECTION) {
 232                 /* If single selection and more than one path, only allow
 233                    first. */
 234                 if(newCount > 1) {
 235                     paths = new TreePath[1];
 236                     paths[0] = pPaths[0];
 237                     newCount = 1;
 238                 }
 239             }
 240             else if(selectionMode ==
 241                     TreeSelectionModel.CONTIGUOUS_TREE_SELECTION) {
 242                 /* If contiguous selection and paths aren't contiguous,
 243                    only select the first path item. */
 244                 if(newCount > 0 && !arePathsContiguous(paths)) {
 245                     paths = new TreePath[1];
 246                     paths[0] = pPaths[0];
 247                     newCount = 1;
 248                 }
 249             }
 250 
 251             TreePath         beginLeadPath = leadPath;
 252             Vector<PathPlaceHolder> cPaths = new Vector<PathPlaceHolder>(newCount + oldCount);
 253             List<TreePath> newSelectionAsList =
 254                     new ArrayList<TreePath>(newCount);
 255 
 256             lastPaths.clear();
 257             leadPath = null;
 258             /* Find the paths that are new. */
 259             for(newCounter = 0; newCounter < newCount; newCounter++) {
 260                 TreePath path = paths[newCounter];
 261                 if (path != null && lastPaths.get(path) == null) {
 262                     lastPaths.put(path, Boolean.TRUE);
 263                     if (uniquePaths.get(path) == null) {
 264                         cPaths.addElement(new PathPlaceHolder(path, true));
 265                     }
 266                     leadPath = path;
 267                     newSelectionAsList.add(path);
 268                 }
 269             }
 270 
 271             TreePath[] newSelection = newSelectionAsList.toArray(
 272                     new TreePath[newSelectionAsList.size()]);
 273 
 274             /* Get the paths that were selected but no longer selected. */
 275             for(oldCounter = 0; oldCounter < oldCount; oldCounter++)
 276                 if(selection[oldCounter] != null &&
 277                     lastPaths.get(selection[oldCounter]) == null)
 278                     cPaths.addElement(new PathPlaceHolder
 279                                       (selection[oldCounter], false));
 280 
 281             selection = newSelection;
 282 
 283             Hashtable<TreePath, Boolean>  tempHT = uniquePaths;
 284 
 285             uniquePaths = lastPaths;
 286             lastPaths = tempHT;
 287             lastPaths.clear();
 288 
 289             // No reason to do this now, but will still call it.
 290             insureUniqueness();
 291 
 292             updateLeadIndex();
 293 
 294             resetRowSelection();
 295             /* Notify of the change. */
 296             if(cPaths.size() > 0)
 297                 notifyPathChange(cPaths, beginLeadPath);
 298         }
 299     }
 300 
 301     /**
 302       * Adds path to the current selection. If path is not currently
 303       * in the selection the TreeSelectionListeners are notified. This has
 304       * no effect if <code>path</code> is null.
 305       *
 306       * @param path the new path to add to the current selection
 307       */
 308     public void addSelectionPath(TreePath path) {
 309         if(path != null) {
 310             TreePath[]            toAdd = new TreePath[1];
 311 
 312             toAdd[0] = path;
 313             addSelectionPaths(toAdd);
 314         }
 315     }
 316 
 317     /**
 318       * Adds paths to the current selection. If any of the paths in
 319       * paths are not currently in the selection the TreeSelectionListeners
 320       * are notified. This has
 321       * no effect if <code>paths</code> is null.
 322       * <p>The lead path is set to the last element in <code>paths</code>.
 323       * <p>If the selection mode is <code>CONTIGUOUS_TREE_SELECTION</code>,
 324       * and adding the new paths would make the selection discontiguous.
 325       * Then two things can result: if the TreePaths in <code>paths</code>
 326       * are contiguous, then the selection becomes these TreePaths,
 327       * otherwise the TreePaths aren't contiguous and the selection becomes
 328       * the first TreePath in <code>paths</code>.
 329       *
 330       * @param paths the new path to add to the current selection
 331       */
 332     public void addSelectionPaths(TreePath[] paths) {
 333         int       newPathLength = ((paths == null) ? 0 : paths.length);
 334 
 335         if(newPathLength > 0) {
 336             if(selectionMode == TreeSelectionModel.SINGLE_TREE_SELECTION) {
 337                 setSelectionPaths(paths);
 338             }
 339             else if(selectionMode == TreeSelectionModel.
 340                     CONTIGUOUS_TREE_SELECTION && !canPathsBeAdded(paths)) {
 341                 if(arePathsContiguous(paths)) {
 342                     setSelectionPaths(paths);
 343                 }
 344                 else {
 345                     TreePath[]          newPaths = new TreePath[1];
 346 
 347                     newPaths[0] = paths[0];
 348                     setSelectionPaths(newPaths);
 349                 }
 350             }
 351             else {
 352                 int               counter, validCount;
 353                 int               oldCount;
 354                 TreePath          beginLeadPath = leadPath;
 355                 Vector<PathPlaceHolder>  cPaths = null;
 356 
 357                 if(selection == null)
 358                     oldCount = 0;
 359                 else
 360                     oldCount = selection.length;
 361                 /* Determine the paths that aren't currently in the
 362                    selection. */
 363                 lastPaths.clear();
 364                 for(counter = 0, validCount = 0; counter < newPathLength;
 365                     counter++) {
 366                     if(paths[counter] != null) {
 367                         if (uniquePaths.get(paths[counter]) == null) {
 368                             validCount++;
 369                             if(cPaths == null)
 370                                 cPaths = new Vector<PathPlaceHolder>();
 371                             cPaths.addElement(new PathPlaceHolder
 372                                               (paths[counter], true));
 373                             uniquePaths.put(paths[counter], Boolean.TRUE);
 374                             lastPaths.put(paths[counter], Boolean.TRUE);
 375                         }
 376                         leadPath = paths[counter];
 377                     }
 378                 }
 379 
 380                 if(leadPath == null) {
 381                     leadPath = beginLeadPath;
 382                 }
 383 
 384                 if(validCount > 0) {
 385                     TreePath[]         newSelection = new TreePath[oldCount +
 386                                                                   validCount];
 387 
 388                     /* And build the new selection. */
 389                     if(oldCount > 0)
 390                         System.arraycopy(selection, 0, newSelection, 0,
 391                                          oldCount);
 392                     if(validCount != paths.length) {
 393                         /* Some of the paths in paths are already in
 394                            the selection. */
 395                         Enumeration<TreePath> newPaths = lastPaths.keys();
 396 
 397                         counter = oldCount;
 398                         while (newPaths.hasMoreElements()) {
 399                             newSelection[counter++] = newPaths.nextElement();
 400                         }
 401                     }
 402                     else {
 403                         System.arraycopy(paths, 0, newSelection, oldCount,
 404                                          validCount);
 405                     }
 406 
 407                     selection = newSelection;
 408 
 409                     insureUniqueness();
 410 
 411                     updateLeadIndex();
 412 
 413                     resetRowSelection();
 414 
 415                     notifyPathChange(cPaths, beginLeadPath);
 416                 }
 417                 else
 418                     leadPath = beginLeadPath;
 419                 lastPaths.clear();
 420             }
 421         }
 422     }
 423 
 424     /**
 425       * Removes path from the selection. If path is in the selection
 426       * The TreeSelectionListeners are notified. This has no effect if
 427       * <code>path</code> is null.
 428       *
 429       * @param path the path to remove from the selection
 430       */
 431     public void removeSelectionPath(TreePath path) {
 432         if(path != null) {
 433             TreePath[]             rPath = new TreePath[1];
 434 
 435             rPath[0] = path;
 436             removeSelectionPaths(rPath);
 437         }
 438     }
 439 
 440     /**
 441       * Removes paths from the selection.  If any of the paths in paths
 442       * are in the selection the TreeSelectionListeners are notified.
 443       * This has no effect if <code>paths</code> is null.
 444       *
 445       * @param paths the paths to remove from the selection
 446       */
 447     public void removeSelectionPaths(TreePath[] paths) {
 448         if (paths != null && selection != null && paths.length > 0) {
 449             if(!canPathsBeRemoved(paths)) {
 450                 /* Could probably do something more interesting here! */
 451                 clearSelection();
 452             }
 453             else {
 454                 Vector<PathPlaceHolder> pathsToRemove = null;
 455 
 456                 /* Find the paths that can be removed. */
 457                 for (int removeCounter = paths.length - 1; removeCounter >= 0;
 458                      removeCounter--) {
 459                     if(paths[removeCounter] != null) {
 460                         if (uniquePaths.get(paths[removeCounter]) != null) {
 461                             if(pathsToRemove == null)
 462                                 pathsToRemove = new Vector<PathPlaceHolder>(paths.length);
 463                             uniquePaths.remove(paths[removeCounter]);
 464                             pathsToRemove.addElement(new PathPlaceHolder
 465                                          (paths[removeCounter], false));
 466                         }
 467                     }
 468                 }
 469                 if(pathsToRemove != null) {
 470                     int         removeCount = pathsToRemove.size();
 471                     TreePath    beginLeadPath = leadPath;
 472 
 473                     if(removeCount == selection.length) {
 474                         selection = null;
 475                     }
 476                     else {
 477                         Enumeration<TreePath> pEnum = uniquePaths.keys();
 478                         int                  validCount = 0;
 479 
 480                         selection = new TreePath[selection.length -
 481                                                 removeCount];
 482                         while (pEnum.hasMoreElements()) {
 483                             selection[validCount++] = pEnum.nextElement();
 484                         }
 485                     }
 486                     if (leadPath != null &&
 487                         uniquePaths.get(leadPath) == null) {
 488                         if (selection != null) {
 489                             leadPath = selection[selection.length - 1];
 490                         }
 491                         else {
 492                             leadPath = null;
 493                         }
 494                     }
 495                     else if (selection != null) {
 496                         leadPath = selection[selection.length - 1];
 497                     }
 498                     else {
 499                         leadPath = null;
 500                     }
 501                     updateLeadIndex();
 502 
 503                     resetRowSelection();
 504 
 505                     notifyPathChange(pathsToRemove, beginLeadPath);
 506                 }
 507             }
 508         }
 509     }
 510 
 511     /**
 512       * Returns the first path in the selection. This is useful if there
 513       * if only one item currently selected.
 514       */
 515     public TreePath getSelectionPath() {
 516         if (selection != null && selection.length > 0) {
 517             return selection[0];
 518         }
 519         return null;
 520     }
 521 
 522     /**
 523       * Returns the selection.
 524       *
 525       * @return the selection
 526       */
 527     public TreePath[] getSelectionPaths() {
 528         if(selection != null) {
 529             int                 pathSize = selection.length;
 530             TreePath[]          result = new TreePath[pathSize];
 531 
 532             System.arraycopy(selection, 0, result, 0, pathSize);
 533             return result;
 534         }
 535         return new TreePath[0];
 536     }
 537 
 538     /**
 539      * Returns the number of paths that are selected.
 540      */
 541     public int getSelectionCount() {
 542         return (selection == null) ? 0 : selection.length;
 543     }
 544 
 545     /**
 546       * Returns true if the path, <code>path</code>,
 547       * is in the current selection.
 548       */
 549     public boolean isPathSelected(TreePath path) {
 550         return (path != null) ? (uniquePaths.get(path) != null) : false;
 551     }
 552 
 553     /**
 554       * Returns true if the selection is currently empty.
 555       */
 556     public boolean isSelectionEmpty() {
 557         return (selection == null || selection.length == 0);
 558     }
 559 
 560     /**
 561       * Empties the current selection.  If this represents a change in the
 562       * current selection, the selection listeners are notified.
 563       */
 564     public void clearSelection() {
 565         if (selection != null && selection.length > 0) {
 566             int                    selSize = selection.length;
 567             boolean[]              newness = new boolean[selSize];
 568 
 569             for(int counter = 0; counter < selSize; counter++)
 570                 newness[counter] = false;
 571 
 572             TreeSelectionEvent     event = new TreeSelectionEvent
 573                 (this, selection, newness, leadPath, null);
 574 
 575             leadPath = null;
 576             leadIndex = leadRow = -1;
 577             uniquePaths.clear();
 578             selection = null;
 579             resetRowSelection();
 580             fireValueChanged(event);
 581         }
 582     }
 583 
 584     /**
 585       * Adds x to the list of listeners that are notified each time the
 586       * set of selected TreePaths changes.
 587       *
 588       * @param x the new listener to be added
 589       */
 590     public void addTreeSelectionListener(TreeSelectionListener x) {
 591         listenerList.add(TreeSelectionListener.class, x);
 592     }
 593 
 594     /**
 595       * Removes x from the list of listeners that are notified each time
 596       * the set of selected TreePaths changes.
 597       *
 598       * @param x the listener to remove
 599       */
 600     public void removeTreeSelectionListener(TreeSelectionListener x) {
 601         listenerList.remove(TreeSelectionListener.class, x);
 602     }
 603 
 604     /**
 605      * Returns an array of all the tree selection listeners
 606      * registered on this model.
 607      *
 608      * @return all of this model's <code>TreeSelectionListener</code>s
 609      *         or an empty
 610      *         array if no tree selection listeners are currently registered
 611      *
 612      * @see #addTreeSelectionListener
 613      * @see #removeTreeSelectionListener
 614      *
 615      * @since 1.4
 616      */
 617     public TreeSelectionListener[] getTreeSelectionListeners() {
 618         return listenerList.getListeners(TreeSelectionListener.class);
 619     }
 620 
 621     /**
 622      * Notifies all listeners that are registered for
 623      * tree selection events on this object.
 624      *
 625      * @param e the event that characterizes the change
 626      *
 627      * @see #addTreeSelectionListener
 628      * @see EventListenerList
 629      */
 630     protected void fireValueChanged(TreeSelectionEvent e) {
 631         // Guaranteed to return a non-null array
 632         Object[] listeners = listenerList.getListenerList();
 633         // TreeSelectionEvent e = null;
 634         // Process the listeners last to first, notifying
 635         // those that are interested in this event
 636         for (int i = listeners.length-2; i>=0; i-=2) {
 637             if (listeners[i]==TreeSelectionListener.class) {
 638                 // Lazily create the event:
 639                 // if (e == null)
 640                 // e = new ListSelectionEvent(this, firstIndex, lastIndex);
 641                 ((TreeSelectionListener)listeners[i+1]).valueChanged(e);
 642             }
 643         }
 644     }
 645 
 646     /**
 647      * Returns an array of all the objects currently registered
 648      * as <code><em>Foo</em>Listener</code>s
 649      * upon this model.
 650      * <code><em>Foo</em>Listener</code>s are registered using the
 651      * <code>add<em>Foo</em>Listener</code> method.
 652      *
 653      * <p>
 654      *
 655      * You can specify the <code>listenerType</code> argument
 656      * with a class literal,
 657      * such as
 658      * <code><em>Foo</em>Listener.class</code>.
 659      * For example, you can query a
 660      * <code>DefaultTreeSelectionModel</code> <code>m</code>
 661      * for its tree selection listeners with the following code:
 662      *
 663      * <pre>TreeSelectionListener[] tsls = (TreeSelectionListener[])(m.getListeners(TreeSelectionListener.class));</pre>
 664      *
 665      * If no such listeners exist, this method returns an empty array.
 666      *
 667      * @param <T> the listener type
 668      * @param listenerType the type of listeners requested
 669      * @return an array of all objects registered as
 670      *          <code><em>Foo</em>Listener</code>s on this component,
 671      *          or an empty array if no such
 672      *          listeners have been added
 673      * @exception ClassCastException if <code>listenerType</code>
 674      *          doesn't specify a class or interface that implements
 675      *          <code>java.util.EventListener</code>
 676      *
 677      * @see #getTreeSelectionListeners
 678      * @see #getPropertyChangeListeners
 679      *
 680      * @since 1.3
 681      */
 682     public <T extends EventListener> T[] getListeners(Class<T> listenerType) {
 683         return listenerList.getListeners(listenerType);
 684     }
 685 
 686     /**
 687      * Returns the selection in terms of rows. There is not
 688      * necessarily a one-to-one mapping between the {@code TreePath}s
 689      * returned from {@code getSelectionPaths} and this method. In
 690      * particular, if a {@code TreePath} is not viewable (the {@code
 691      * RowMapper} returns {@code -1} for the row corresponding to the
 692      * {@code TreePath}), then the corresponding row is not included
 693      * in the returned array. For example, if the selection consists
 694      * of two paths, {@code A} and {@code B}, with {@code A} at row
 695      * {@code 10}, and {@code B} not currently viewable, then this method
 696      * returns an array with the single entry {@code 10}.
 697      *
 698      * @return the selection in terms of rows
 699      */
 700     public int[] getSelectionRows() {
 701         // This is currently rather expensive.  Needs
 702         // to be better support from ListSelectionModel to speed this up.
 703         if (rowMapper != null && selection != null && selection.length > 0) {
 704             int[]      rows = rowMapper.getRowsForPaths(selection);
 705 
 706             if (rows != null) {
 707                 int       invisCount = 0;
 708 
 709                 for (int counter = rows.length - 1; counter >= 0; counter--) {
 710                     if (rows[counter] == -1) {
 711                         invisCount++;
 712                     }
 713                 }
 714                 if (invisCount > 0) {
 715                     if (invisCount == rows.length) {
 716                         rows = null;
 717                     }
 718                     else {
 719                         int[]    tempRows = new int[rows.length - invisCount];
 720 
 721                         for (int counter = rows.length - 1, visCounter = 0;
 722                              counter >= 0; counter--) {
 723                             if (rows[counter] != -1) {
 724                                 tempRows[visCounter++] = rows[counter];
 725                             }
 726                         }
 727                         rows = tempRows;
 728                     }
 729                 }
 730             }
 731             return rows;
 732         }
 733         return new int[0];
 734     }
 735 
 736     /**
 737      * Returns the smallest value obtained from the RowMapper for the
 738      * current set of selected TreePaths. If nothing is selected,
 739      * or there is no RowMapper, this will return -1.
 740       */
 741     public int getMinSelectionRow() {
 742         return listSelectionModel.getMinSelectionIndex();
 743     }
 744 
 745     /**
 746      * Returns the largest value obtained from the RowMapper for the
 747      * current set of selected TreePaths. If nothing is selected,
 748      * or there is no RowMapper, this will return -1.
 749       */
 750     public int getMaxSelectionRow() {
 751         return listSelectionModel.getMaxSelectionIndex();
 752     }
 753 
 754     /**
 755       * Returns true if the row identified by <code>row</code> is selected.
 756       */
 757     public boolean isRowSelected(int row) {
 758         return listSelectionModel.isSelectedIndex(row);
 759     }
 760 
 761     /**
 762      * Updates this object's mapping from TreePath to rows. This should
 763      * be invoked when the mapping from TreePaths to integers has changed
 764      * (for example, a node has been expanded).
 765      * <p>You do not normally have to call this, JTree and its associated
 766      * Listeners will invoke this for you. If you are implementing your own
 767      * View class, then you will have to invoke this.
 768      * <p>This will invoke <code>insureRowContinuity</code> to make sure
 769      * the currently selected TreePaths are still valid based on the
 770      * selection mode.
 771      */
 772     public void resetRowSelection() {
 773         listSelectionModel.clearSelection();
 774         if(selection != null && rowMapper != null) {
 775             int               aRow;
 776             int               validCount = 0;
 777             int[]             rows = rowMapper.getRowsForPaths(selection);
 778 
 779             for(int counter = 0, maxCounter = selection.length;
 780                 counter < maxCounter; counter++) {
 781                 aRow = rows[counter];
 782                 if(aRow != -1) {
 783                     listSelectionModel.addSelectionInterval(aRow, aRow);
 784                 }
 785             }
 786             if(leadIndex != -1 && rows != null) {
 787                 leadRow = rows[leadIndex];
 788             }
 789             else if (leadPath != null) {
 790                 // Lead selection path doesn't have to be in the selection.
 791                 tempPaths[0] = leadPath;
 792                 rows = rowMapper.getRowsForPaths(tempPaths);
 793                 leadRow = (rows != null) ? rows[0] : -1;
 794             }
 795             else {
 796                 leadRow = -1;
 797             }
 798             insureRowContinuity();
 799 
 800         }
 801         else
 802             leadRow = -1;
 803     }
 804 
 805     /**
 806      * Returns the lead selection index. That is the last index that was
 807      * added.
 808      */
 809     public int getLeadSelectionRow() {
 810         return leadRow;
 811     }
 812 
 813     /**
 814      * Returns the last path that was added. This may differ from the
 815      * leadSelectionPath property maintained by the JTree.
 816      */
 817     public TreePath getLeadSelectionPath() {
 818         return leadPath;
 819     }
 820 
 821     /**
 822      * Adds a PropertyChangeListener to the listener list.
 823      * The listener is registered for all properties.
 824      * <p>
 825      * A PropertyChangeEvent will get fired when the selection mode
 826      * changes.
 827      *
 828      * @param listener  the PropertyChangeListener to be added
 829      */
 830     public synchronized void addPropertyChangeListener(
 831                                 PropertyChangeListener listener) {
 832         if (changeSupport == null) {
 833             changeSupport = new SwingPropertyChangeSupport(this);
 834         }
 835         changeSupport.addPropertyChangeListener(listener);
 836     }
 837 
 838     /**
 839      * Removes a PropertyChangeListener from the listener list.
 840      * This removes a PropertyChangeListener that was registered
 841      * for all properties.
 842      *
 843      * @param listener  the PropertyChangeListener to be removed
 844      */
 845 
 846     public synchronized void removePropertyChangeListener(
 847                                 PropertyChangeListener listener) {
 848         if (changeSupport == null) {
 849             return;
 850         }
 851         changeSupport.removePropertyChangeListener(listener);
 852     }
 853 
 854     /**
 855      * Returns an array of all the property change listeners
 856      * registered on this <code>DefaultTreeSelectionModel</code>.
 857      *
 858      * @return all of this model's <code>PropertyChangeListener</code>s
 859      *         or an empty
 860      *         array if no property change listeners are currently registered
 861      *
 862      * @see #addPropertyChangeListener
 863      * @see #removePropertyChangeListener
 864      *
 865      * @since 1.4
 866      */
 867     public PropertyChangeListener[] getPropertyChangeListeners() {
 868         if (changeSupport == null) {
 869             return new PropertyChangeListener[0];
 870         }
 871         return changeSupport.getPropertyChangeListeners();
 872     }
 873 
 874     /**
 875      * Makes sure the currently selected <code>TreePath</code>s are valid
 876      * for the current selection mode.
 877      * If the selection mode is <code>CONTIGUOUS_TREE_SELECTION</code>
 878      * and a <code>RowMapper</code> exists, this will make sure all
 879      * the rows are contiguous, that is, when sorted all the rows are
 880      * in order with no gaps.
 881      * If the selection isn't contiguous, the selection is
 882      * reset to contain the first set, when sorted, of contiguous rows.
 883      * <p>
 884      * If the selection mode is <code>SINGLE_TREE_SELECTION</code> and
 885      * more than one TreePath is selected, the selection is reset to
 886      * contain the first path currently selected.
 887      */
 888     protected void insureRowContinuity() {
 889         if(selectionMode == TreeSelectionModel.CONTIGUOUS_TREE_SELECTION &&
 890            selection != null && rowMapper != null) {
 891             DefaultListSelectionModel lModel = listSelectionModel;
 892             int                       min = lModel.getMinSelectionIndex();
 893 
 894             if(min != -1) {
 895                 for(int counter = min,
 896                         maxCounter = lModel.getMaxSelectionIndex();
 897                         counter <= maxCounter; counter++) {
 898                     if(!lModel.isSelectedIndex(counter)) {
 899                         if(counter == min) {
 900                             clearSelection();
 901                         }
 902                         else {
 903                             TreePath[] newSel = new TreePath[counter - min];
 904                             int[] selectionIndex = rowMapper.getRowsForPaths(selection);
 905                             // find the actual selection pathes corresponded to the
 906                             // rows of the new selection
 907                             for (int i = 0; i < selectionIndex.length; i++) {
 908                                 if (selectionIndex[i]<counter) {
 909                                     newSel[selectionIndex[i]-min] = selection[i];
 910                                 }
 911                             }
 912                             setSelectionPaths(newSel);
 913                             break;
 914                         }
 915                     }
 916                 }
 917             }
 918         }
 919         else if(selectionMode == TreeSelectionModel.SINGLE_TREE_SELECTION &&
 920                 selection != null && selection.length > 1) {
 921             setSelectionPath(selection[0]);
 922         }
 923     }
 924 
 925     /**
 926      * Returns true if the paths are contiguous,
 927      * or this object has no RowMapper.
 928      *
 929      * @param paths array of paths to check
 930      * @return      whether the paths are contiguous, or this object has no RowMapper
 931      */
 932     protected boolean arePathsContiguous(TreePath[] paths) {
 933         if(rowMapper == null || paths.length < 2)
 934             return true;
 935         else {
 936             BitSet                             bitSet = new BitSet(32);
 937             int                                anIndex, counter, min;
 938             int                                pathCount = paths.length;
 939             int                                validCount = 0;
 940             TreePath[]                         tempPath = new TreePath[1];
 941 
 942             tempPath[0] = paths[0];
 943             min = rowMapper.getRowsForPaths(tempPath)[0];
 944             for(counter = 0; counter < pathCount; counter++) {
 945                 if(paths[counter] != null) {
 946                     tempPath[0] = paths[counter];
 947                     int[] rows = rowMapper.getRowsForPaths(tempPath);
 948                     if (rows == null) {
 949                         return false;
 950                     }
 951                     anIndex = rows[0];
 952                     if(anIndex == -1 || anIndex < (min - pathCount) ||
 953                        anIndex > (min + pathCount))
 954                         return false;
 955                     if(anIndex < min)
 956                         min = anIndex;
 957                     if(!bitSet.get(anIndex)) {
 958                         bitSet.set(anIndex);
 959                         validCount++;
 960                     }
 961                 }
 962             }
 963             int          maxCounter = validCount + min;
 964 
 965             for(counter = min; counter < maxCounter; counter++)
 966                 if(!bitSet.get(counter))
 967                     return false;
 968         }
 969         return true;
 970     }
 971 
 972     /**
 973      * Used to test if a particular set of <code>TreePath</code>s can
 974      * be added. This will return true if <code>paths</code> is null (or
 975      * empty), or this object has no RowMapper, or nothing is currently selected,
 976      * or the selection mode is <code>DISCONTIGUOUS_TREE_SELECTION</code>, or
 977      * adding the paths to the current selection still results in a
 978      * contiguous set of <code>TreePath</code>s.
 979      *
 980      * @param paths array of {@code TreePaths} to check
 981      * @return      whether the particular set of {@code TreePaths} can be added
 982      */
 983     protected boolean canPathsBeAdded(TreePath[] paths) {
 984         if(paths == null || paths.length == 0 || rowMapper == null ||
 985            selection == null || selectionMode ==
 986            TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION)
 987             return true;
 988         else {
 989             BitSet                       bitSet = new BitSet();
 990             DefaultListSelectionModel    lModel = listSelectionModel;
 991             int                          anIndex;
 992             int                          counter;
 993             int                          min = lModel.getMinSelectionIndex();
 994             int                          max = lModel.getMaxSelectionIndex();
 995             TreePath[]                   tempPath = new TreePath[1];
 996 
 997             if(min != -1) {
 998                 for(counter = min; counter <= max; counter++) {
 999                     if(lModel.isSelectedIndex(counter))
1000                         bitSet.set(counter);
1001                 }
1002             }
1003             else {
1004                 tempPath[0] = paths[0];
1005                 min = max = rowMapper.getRowsForPaths(tempPath)[0];
1006             }
1007             for(counter = paths.length - 1; counter >= 0; counter--) {
1008                 if(paths[counter] != null) {
1009                     tempPath[0] = paths[counter];
1010                     int[]   rows = rowMapper.getRowsForPaths(tempPath);
1011                     if (rows == null) {
1012                         return false;
1013                     }
1014                     anIndex = rows[0];
1015                     min = Math.min(anIndex, min);
1016                     max = Math.max(anIndex, max);
1017                     if(anIndex == -1)
1018                         return false;
1019                     bitSet.set(anIndex);
1020                 }
1021             }
1022             for(counter = min; counter <= max; counter++)
1023                 if(!bitSet.get(counter))
1024                     return false;
1025         }
1026         return true;
1027     }
1028 
1029     /**
1030      * Returns true if the paths can be removed without breaking the
1031      * continuity of the model.
1032      * This is rather expensive.
1033      *
1034      * @param paths array of {@code TreePath} to check
1035      * @return      whether the paths can be removed without breaking the
1036      *              continuity of the model
1037      */
1038     protected boolean canPathsBeRemoved(TreePath[] paths) {
1039         if(rowMapper == null || selection == null ||
1040            selectionMode == TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION)
1041             return true;
1042         else {
1043             BitSet               bitSet = new BitSet();
1044             int                  counter;
1045             int                  pathCount = paths.length;
1046             int                  anIndex;
1047             int                  min = -1;
1048             int                  validCount = 0;
1049             TreePath[]           tempPath = new TreePath[1];
1050             int[]                rows;
1051 
1052             /* Determine the rows for the removed entries. */
1053             lastPaths.clear();
1054             for (counter = 0; counter < pathCount; counter++) {
1055                 if (paths[counter] != null) {
1056                     lastPaths.put(paths[counter], Boolean.TRUE);
1057                 }
1058             }
1059             for(counter = selection.length - 1; counter >= 0; counter--) {
1060                 if(lastPaths.get(selection[counter]) == null) {
1061                     tempPath[0] = selection[counter];
1062                     rows = rowMapper.getRowsForPaths(tempPath);
1063                     if(rows != null && rows[0] != -1 && !bitSet.get(rows[0])) {
1064                         validCount++;
1065                         if(min == -1)
1066                             min = rows[0];
1067                         else
1068                             min = Math.min(min, rows[0]);
1069                         bitSet.set(rows[0]);
1070                     }
1071                 }
1072             }
1073             lastPaths.clear();
1074             /* Make sure they are contiguous. */
1075             if(validCount > 1) {
1076                 for(counter = min + validCount - 1; counter >= min;
1077                     counter--)
1078                     if(!bitSet.get(counter))
1079                         return false;
1080             }
1081         }
1082         return true;
1083     }
1084 
1085     /**
1086      * Notifies listeners of a change in path. changePaths should contain
1087      * instances of PathPlaceHolder.
1088      *
1089      * @deprecated As of JDK version 1.7
1090      *
1091      * @param changedPaths      the vector of the changed paths
1092      * @param oldLeadSelection  the old selection path
1093      */
1094     @Deprecated
1095     protected void notifyPathChange(Vector<?> changedPaths,
1096                                     TreePath oldLeadSelection) {
1097         int                    cPathCount = changedPaths.size();
1098         boolean[]              newness = new boolean[cPathCount];
1099         TreePath[]            paths = new TreePath[cPathCount];
1100         PathPlaceHolder        placeholder;
1101 
1102         for(int counter = 0; counter < cPathCount; counter++) {
1103             placeholder = (PathPlaceHolder) changedPaths.elementAt(counter);
1104             newness[counter] = placeholder.isNew;
1105             paths[counter] = placeholder.path;
1106         }
1107 
1108         TreeSelectionEvent     event = new TreeSelectionEvent
1109                           (this, paths, newness, oldLeadSelection, leadPath);
1110 
1111         fireValueChanged(event);
1112     }
1113 
1114     /**
1115      * Updates the leadIndex instance variable.
1116      */
1117     protected void updateLeadIndex() {
1118         if(leadPath != null) {
1119             if(selection == null) {
1120                 leadPath = null;
1121                 leadIndex = leadRow = -1;
1122             }
1123             else {
1124                 leadRow = leadIndex = -1;
1125                 for(int counter = selection.length - 1; counter >= 0;
1126                     counter--) {
1127                     // Can use == here since we know leadPath came from
1128                     // selection
1129                     if(selection[counter] == leadPath) {
1130                         leadIndex = counter;
1131                         break;
1132                     }
1133                 }
1134             }
1135         }
1136         else {
1137             leadIndex = -1;
1138         }
1139     }
1140 
1141     /**
1142      * This method is obsolete and its implementation is now a noop.  It's
1143      * still called by setSelectionPaths and addSelectionPaths, but only
1144      * for backwards compatibility.
1145      */
1146     protected void insureUniqueness() {
1147     }
1148 
1149 
1150     /**
1151      * Returns a string that displays and identifies this
1152      * object's properties.
1153      *
1154      * @return a String representation of this object
1155      */
1156     public String toString() {
1157         int                selCount = getSelectionCount();
1158         StringBuilder      sb = new StringBuilder();
1159         int[]              rows;
1160 
1161         if(rowMapper != null)
1162             rows = rowMapper.getRowsForPaths(selection);
1163         else
1164             rows = null;
1165         sb.append(getClass().getName() + " " + hashCode() + " [ ");
1166         for(int counter = 0; counter < selCount; counter++) {
1167             if(rows != null)
1168                 sb.append(selection[counter].toString() + "@" +
1169                           Integer.toString(rows[counter])+ " ");
1170             else
1171                 sb.append(selection[counter].toString() + " ");
1172         }
1173         sb.append("]");
1174         return sb.toString();
1175     }
1176 
1177     /**
1178      * Returns a clone of this object with the same selection.
1179      * This method does not duplicate
1180      * selection listeners and property listeners.
1181      *
1182      * @exception CloneNotSupportedException never thrown by instances of
1183      *                                       this class
1184      */
1185     public Object clone() throws CloneNotSupportedException {
1186         DefaultTreeSelectionModel        clone = (DefaultTreeSelectionModel)
1187                             super.clone();
1188 
1189         clone.changeSupport = null;
1190         if(selection != null) {
1191             int              selLength = selection.length;
1192 
1193             clone.selection = new TreePath[selLength];
1194             System.arraycopy(selection, 0, clone.selection, 0, selLength);
1195         }
1196         clone.listenerList = new EventListenerList();
1197         clone.listSelectionModel = (DefaultListSelectionModel)
1198             listSelectionModel.clone();
1199         clone.uniquePaths = new Hashtable<TreePath, Boolean>();
1200         clone.lastPaths = new Hashtable<TreePath, Boolean>();
1201         clone.tempPaths = new TreePath[1];
1202         return clone;
1203     }
1204 
1205     // Serialization support.
1206     private void writeObject(ObjectOutputStream s) throws IOException {
1207         Object[]             tValues;
1208 
1209         s.defaultWriteObject();
1210         // Save the rowMapper, if it implements Serializable
1211         if(rowMapper != null && rowMapper instanceof Serializable) {
1212             tValues = new Object[2];
1213             tValues[0] = "rowMapper";
1214             tValues[1] = rowMapper;
1215         }
1216         else
1217             tValues = new Object[0];
1218         s.writeObject(tValues);
1219     }
1220 
1221 
1222     private void readObject(ObjectInputStream s)
1223         throws IOException, ClassNotFoundException {
1224         ObjectInputStream.GetField f = s.readFields();
1225 
1226         changeSupport = (SwingPropertyChangeSupport) f.get("changeSupport", null);
1227         selection = (TreePath[]) f.get("selection", null);
1228         EventListenerList newListenerList = (EventListenerList) f.get("listenerList", null);
1229         if (newListenerList == null) {
1230             throw new InvalidObjectException("Null listenerList");
1231         }
1232         listenerList = newListenerList;
1233         listSelectionModel = (DefaultListSelectionModel) f.get("listSelectionModel", null);
1234         selectionMode = validateSelectionMode(f.get("selectionMode", 0));
1235         leadPath = (TreePath) f.get("leadPath", null);
1236         leadIndex = f.get("leadIndex", 0);
1237         leadRow = f.get("leadRow", 0);
1238         @SuppressWarnings("unchecked")
1239         Hashtable<TreePath, Boolean> newUniquePaths =
1240                 (Hashtable<TreePath, Boolean>) f.get("uniquePaths", null);
1241         uniquePaths = newUniquePaths;
1242         @SuppressWarnings("unchecked")
1243         Hashtable<TreePath, Boolean> newLastPaths =
1244                 (Hashtable<TreePath, Boolean>) f.get("lastPaths", null);
1245         lastPaths = newLastPaths;
1246         tempPaths = (TreePath[]) f.get("tempPaths", null);
1247 
1248         Object[]      tValues;
1249         tValues = (Object[])s.readObject();
1250 
1251         if (tValues.length > 0 && tValues[0].equals("rowMapper")) {
1252             RowMapper newRowMapper = (RowMapper) tValues[1];
1253             if (newRowMapper == null) {
1254                 throw new InvalidObjectException("Null newRowMapper");
1255             }
1256             rowMapper = newRowMapper;
1257         }
1258     }
1259 }
1260 
1261 /**
1262  * Holds a path and whether or not it is new.
1263  */
1264 class PathPlaceHolder {
1265     protected boolean             isNew;
1266     protected TreePath           path;
1267 
1268     PathPlaceHolder(TreePath path, boolean isNew) {
1269         this.path = path;
1270         this.isNew = isNew;
1271     }
1272 }