1 /*
   2  * Copyright (c) 1997, 2014, 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     transient protected 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 listenerType the type of listeners requested; this parameter
 668      *          should specify an interface that descends from
 669      *          <code>java.util.EventListener</code>
 670      * @return an array of all objects registered as
 671      *          <code><em>Foo</em>Listener</code>s on this component,
 672      *          or an empty array if no such
 673      *          listeners have been added
 674      * @exception ClassCastException if <code>listenerType</code>
 675      *          doesn't specify a class or interface that implements
 676      *          <code>java.util.EventListener</code>
 677      *
 678      * @see #getTreeSelectionListeners
 679      * @see #getPropertyChangeListeners
 680      *
 681      * @since 1.3
 682      */
 683     public <T extends EventListener> T[] getListeners(Class<T> listenerType) {
 684         return listenerList.getListeners(listenerType);
 685     }
 686 
 687     /**
 688      * Returns the selection in terms of rows. There is not
 689      * necessarily a one-to-one mapping between the {@code TreePath}s
 690      * returned from {@code getSelectionPaths} and this method. In
 691      * particular, if a {@code TreePath} is not viewable (the {@code
 692      * RowMapper} returns {@code -1} for the row corresponding to the
 693      * {@code TreePath}), then the corresponding row is not included
 694      * in the returned array. For example, if the selection consists
 695      * of two paths, {@code A} and {@code B}, with {@code A} at row
 696      * {@code 10}, and {@code B} not currently viewable, then this method
 697      * returns an array with the single entry {@code 10}.
 698      *
 699      * @return the selection in terms of rows
 700      */
 701     public int[] getSelectionRows() {
 702         // This is currently rather expensive.  Needs
 703         // to be better support from ListSelectionModel to speed this up.
 704         if (rowMapper != null && selection != null && selection.length > 0) {
 705             int[]      rows = rowMapper.getRowsForPaths(selection);
 706 
 707             if (rows != null) {
 708                 int       invisCount = 0;
 709 
 710                 for (int counter = rows.length - 1; counter >= 0; counter--) {
 711                     if (rows[counter] == -1) {
 712                         invisCount++;
 713                     }
 714                 }
 715                 if (invisCount > 0) {
 716                     if (invisCount == rows.length) {
 717                         rows = null;
 718                     }
 719                     else {
 720                         int[]    tempRows = new int[rows.length - invisCount];
 721 
 722                         for (int counter = rows.length - 1, visCounter = 0;
 723                              counter >= 0; counter--) {
 724                             if (rows[counter] != -1) {
 725                                 tempRows[visCounter++] = rows[counter];
 726                             }
 727                         }
 728                         rows = tempRows;
 729                     }
 730                 }
 731             }
 732             return rows;
 733         }
 734         return new int[0];
 735     }
 736 
 737     /**
 738      * Returns the smallest value obtained from the RowMapper for the
 739      * current set of selected TreePaths. If nothing is selected,
 740      * or there is no RowMapper, this will return -1.
 741       */
 742     public int getMinSelectionRow() {
 743         return listSelectionModel.getMinSelectionIndex();
 744     }
 745 
 746     /**
 747      * Returns the largest value obtained from the RowMapper for the
 748      * current set of selected TreePaths. If nothing is selected,
 749      * or there is no RowMapper, this will return -1.
 750       */
 751     public int getMaxSelectionRow() {
 752         return listSelectionModel.getMaxSelectionIndex();
 753     }
 754 
 755     /**
 756       * Returns true if the row identified by <code>row</code> is selected.
 757       */
 758     public boolean isRowSelected(int row) {
 759         return listSelectionModel.isSelectedIndex(row);
 760     }
 761 
 762     /**
 763      * Updates this object's mapping from TreePath to rows. This should
 764      * be invoked when the mapping from TreePaths to integers has changed
 765      * (for example, a node has been expanded).
 766      * <p>You do not normally have to call this, JTree and its associated
 767      * Listeners will invoke this for you. If you are implementing your own
 768      * View class, then you will have to invoke this.
 769      * <p>This will invoke <code>insureRowContinuity</code> to make sure
 770      * the currently selected TreePaths are still valid based on the
 771      * selection mode.
 772      */
 773     public void resetRowSelection() {
 774         listSelectionModel.clearSelection();
 775         if(selection != null && rowMapper != null) {
 776             int               aRow;
 777             int               validCount = 0;
 778             int[]             rows = rowMapper.getRowsForPaths(selection);
 779 
 780             for(int counter = 0, maxCounter = selection.length;
 781                 counter < maxCounter; counter++) {
 782                 aRow = rows[counter];
 783                 if(aRow != -1) {
 784                     listSelectionModel.addSelectionInterval(aRow, aRow);
 785                 }
 786             }
 787             if(leadIndex != -1 && rows != null) {
 788                 leadRow = rows[leadIndex];
 789             }
 790             else if (leadPath != null) {
 791                 // Lead selection path doesn't have to be in the selection.
 792                 tempPaths[0] = leadPath;
 793                 rows = rowMapper.getRowsForPaths(tempPaths);
 794                 leadRow = (rows != null) ? rows[0] : -1;
 795             }
 796             else {
 797                 leadRow = -1;
 798             }
 799             insureRowContinuity();
 800 
 801         }
 802         else
 803             leadRow = -1;
 804     }
 805 
 806     /**
 807      * Returns the lead selection index. That is the last index that was
 808      * added.
 809      */
 810     public int getLeadSelectionRow() {
 811         return leadRow;
 812     }
 813 
 814     /**
 815      * Returns the last path that was added. This may differ from the
 816      * leadSelectionPath property maintained by the JTree.
 817      */
 818     public TreePath getLeadSelectionPath() {
 819         return leadPath;
 820     }
 821 
 822     /**
 823      * Adds a PropertyChangeListener to the listener list.
 824      * The listener is registered for all properties.
 825      * <p>
 826      * A PropertyChangeEvent will get fired when the selection mode
 827      * changes.
 828      *
 829      * @param listener  the PropertyChangeListener to be added
 830      */
 831     public synchronized void addPropertyChangeListener(
 832                                 PropertyChangeListener listener) {
 833         if (changeSupport == null) {
 834             changeSupport = new SwingPropertyChangeSupport(this);
 835         }
 836         changeSupport.addPropertyChangeListener(listener);
 837     }
 838 
 839     /**
 840      * Removes a PropertyChangeListener from the listener list.
 841      * This removes a PropertyChangeListener that was registered
 842      * for all properties.
 843      *
 844      * @param listener  the PropertyChangeListener to be removed
 845      */
 846 
 847     public synchronized void removePropertyChangeListener(
 848                                 PropertyChangeListener listener) {
 849         if (changeSupport == null) {
 850             return;
 851         }
 852         changeSupport.removePropertyChangeListener(listener);
 853     }
 854 
 855     /**
 856      * Returns an array of all the property change listeners
 857      * registered on this <code>DefaultTreeSelectionModel</code>.
 858      *
 859      * @return all of this model's <code>PropertyChangeListener</code>s
 860      *         or an empty
 861      *         array if no property change listeners are currently registered
 862      *
 863      * @see #addPropertyChangeListener
 864      * @see #removePropertyChangeListener
 865      *
 866      * @since 1.4
 867      */
 868     public PropertyChangeListener[] getPropertyChangeListeners() {
 869         if (changeSupport == null) {
 870             return new PropertyChangeListener[0];
 871         }
 872         return changeSupport.getPropertyChangeListeners();
 873     }
 874 
 875     /**
 876      * Makes sure the currently selected <code>TreePath</code>s are valid
 877      * for the current selection mode.
 878      * If the selection mode is <code>CONTIGUOUS_TREE_SELECTION</code>
 879      * and a <code>RowMapper</code> exists, this will make sure all
 880      * the rows are contiguous, that is, when sorted all the rows are
 881      * in order with no gaps.
 882      * If the selection isn't contiguous, the selection is
 883      * reset to contain the first set, when sorted, of contiguous rows.
 884      * <p>
 885      * If the selection mode is <code>SINGLE_TREE_SELECTION</code> and
 886      * more than one TreePath is selected, the selection is reset to
 887      * contain the first path currently selected.
 888      */
 889     protected void insureRowContinuity() {
 890         if(selectionMode == TreeSelectionModel.CONTIGUOUS_TREE_SELECTION &&
 891            selection != null && rowMapper != null) {
 892             DefaultListSelectionModel lModel = listSelectionModel;
 893             int                       min = lModel.getMinSelectionIndex();
 894 
 895             if(min != -1) {
 896                 for(int counter = min,
 897                         maxCounter = lModel.getMaxSelectionIndex();
 898                         counter <= maxCounter; counter++) {
 899                     if(!lModel.isSelectedIndex(counter)) {
 900                         if(counter == min) {
 901                             clearSelection();
 902                         }
 903                         else {
 904                             TreePath[] newSel = new TreePath[counter - min];
 905                             int selectionIndex[] = rowMapper.getRowsForPaths(selection);
 906                             // find the actual selection pathes corresponded to the
 907                             // rows of the new selection
 908                             for (int i = 0; i < selectionIndex.length; i++) {
 909                                 if (selectionIndex[i]<counter) {
 910                                     newSel[selectionIndex[i]-min] = selection[i];
 911                                 }
 912                             }
 913                             setSelectionPaths(newSel);
 914                             break;
 915                         }
 916                     }
 917                 }
 918             }
 919         }
 920         else if(selectionMode == TreeSelectionModel.SINGLE_TREE_SELECTION &&
 921                 selection != null && selection.length > 1) {
 922             setSelectionPath(selection[0]);
 923         }
 924     }
 925 
 926     /**
 927      * Returns true if the paths are contiguous,
 928      * or this object has no RowMapper.
 929      *
 930      * @param paths array of paths to check
 931      * @return      whether the paths are contiguous, or this object has no RowMapper
 932      */
 933     protected boolean arePathsContiguous(TreePath[] paths) {
 934         if(rowMapper == null || paths.length < 2)
 935             return true;
 936         else {
 937             BitSet                             bitSet = new BitSet(32);
 938             int                                anIndex, counter, min;
 939             int                                pathCount = paths.length;
 940             int                                validCount = 0;
 941             TreePath[]                         tempPath = new TreePath[1];
 942 
 943             tempPath[0] = paths[0];
 944             min = rowMapper.getRowsForPaths(tempPath)[0];
 945             for(counter = 0; counter < pathCount; counter++) {
 946                 if(paths[counter] != null) {
 947                     tempPath[0] = paths[counter];
 948                     int[] rows = rowMapper.getRowsForPaths(tempPath);
 949                     if (rows == null) {
 950                         return false;
 951                     }
 952                     anIndex = rows[0];
 953                     if(anIndex == -1 || anIndex < (min - pathCount) ||
 954                        anIndex > (min + pathCount))
 955                         return false;
 956                     if(anIndex < min)
 957                         min = anIndex;
 958                     if(!bitSet.get(anIndex)) {
 959                         bitSet.set(anIndex);
 960                         validCount++;
 961                     }
 962                 }
 963             }
 964             int          maxCounter = validCount + min;
 965 
 966             for(counter = min; counter < maxCounter; counter++)
 967                 if(!bitSet.get(counter))
 968                     return false;
 969         }
 970         return true;
 971     }
 972 
 973     /**
 974      * Used to test if a particular set of <code>TreePath</code>s can
 975      * be added. This will return true if <code>paths</code> is null (or
 976      * empty), or this object has no RowMapper, or nothing is currently selected,
 977      * or the selection mode is <code>DISCONTIGUOUS_TREE_SELECTION</code>, or
 978      * adding the paths to the current selection still results in a
 979      * contiguous set of <code>TreePath</code>s.
 980      *
 981      * @param paths array of {@code TreePaths} to check
 982      * @return      whether the particular set of {@code TreePaths} can be added
 983      */
 984     protected boolean canPathsBeAdded(TreePath[] paths) {
 985         if(paths == null || paths.length == 0 || rowMapper == null ||
 986            selection == null || selectionMode ==
 987            TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION)
 988             return true;
 989         else {
 990             BitSet                       bitSet = new BitSet();
 991             DefaultListSelectionModel    lModel = listSelectionModel;
 992             int                          anIndex;
 993             int                          counter;
 994             int                          min = lModel.getMinSelectionIndex();
 995             int                          max = lModel.getMaxSelectionIndex();
 996             TreePath[]                   tempPath = new TreePath[1];
 997 
 998             if(min != -1) {
 999                 for(counter = min; counter <= max; counter++) {
1000                     if(lModel.isSelectedIndex(counter))
1001                         bitSet.set(counter);
1002                 }
1003             }
1004             else {
1005                 tempPath[0] = paths[0];
1006                 min = max = rowMapper.getRowsForPaths(tempPath)[0];
1007             }
1008             for(counter = paths.length - 1; counter >= 0; counter--) {
1009                 if(paths[counter] != null) {
1010                     tempPath[0] = paths[counter];
1011                     int[]   rows = rowMapper.getRowsForPaths(tempPath);
1012                     if (rows == null) {
1013                         return false;
1014                     }
1015                     anIndex = rows[0];
1016                     min = Math.min(anIndex, min);
1017                     max = Math.max(anIndex, max);
1018                     if(anIndex == -1)
1019                         return false;
1020                     bitSet.set(anIndex);
1021                 }
1022             }
1023             for(counter = min; counter <= max; counter++)
1024                 if(!bitSet.get(counter))
1025                     return false;
1026         }
1027         return true;
1028     }
1029 
1030     /**
1031      * Returns true if the paths can be removed without breaking the
1032      * continuity of the model.
1033      * This is rather expensive.
1034      *
1035      * @param paths array of {@code TreePath} to check
1036      * @return      whether the paths can be removed without breaking the
1037      *              continuity of the model
1038      */
1039     protected boolean canPathsBeRemoved(TreePath[] paths) {
1040         if(rowMapper == null || selection == null ||
1041            selectionMode == TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION)
1042             return true;
1043         else {
1044             BitSet               bitSet = new BitSet();
1045             int                  counter;
1046             int                  pathCount = paths.length;
1047             int                  anIndex;
1048             int                  min = -1;
1049             int                  validCount = 0;
1050             TreePath[]           tempPath = new TreePath[1];
1051             int[]                rows;
1052 
1053             /* Determine the rows for the removed entries. */
1054             lastPaths.clear();
1055             for (counter = 0; counter < pathCount; counter++) {
1056                 if (paths[counter] != null) {
1057                     lastPaths.put(paths[counter], Boolean.TRUE);
1058                 }
1059             }
1060             for(counter = selection.length - 1; counter >= 0; counter--) {
1061                 if(lastPaths.get(selection[counter]) == null) {
1062                     tempPath[0] = selection[counter];
1063                     rows = rowMapper.getRowsForPaths(tempPath);
1064                     if(rows != null && rows[0] != -1 && !bitSet.get(rows[0])) {
1065                         validCount++;
1066                         if(min == -1)
1067                             min = rows[0];
1068                         else
1069                             min = Math.min(min, rows[0]);
1070                         bitSet.set(rows[0]);
1071                     }
1072                 }
1073             }
1074             lastPaths.clear();
1075             /* Make sure they are contiguous. */
1076             if(validCount > 1) {
1077                 for(counter = min + validCount - 1; counter >= min;
1078                     counter--)
1079                     if(!bitSet.get(counter))
1080                         return false;
1081             }
1082         }
1083         return true;
1084     }
1085 
1086     /**
1087      * Notifies listeners of a change in path. changePaths should contain
1088      * instances of PathPlaceHolder.
1089      *
1090      * @deprecated As of JDK version 1.7
1091      *
1092      * @param changedPaths      the vector of the changed paths
1093      * @param oldLeadSelection  the old selection path
1094      */
1095     @Deprecated
1096     protected void notifyPathChange(Vector<?> changedPaths,
1097                                     TreePath oldLeadSelection) {
1098         int                    cPathCount = changedPaths.size();
1099         boolean[]              newness = new boolean[cPathCount];
1100         TreePath[]            paths = new TreePath[cPathCount];
1101         PathPlaceHolder        placeholder;
1102 
1103         for(int counter = 0; counter < cPathCount; counter++) {
1104             placeholder = (PathPlaceHolder) changedPaths.elementAt(counter);
1105             newness[counter] = placeholder.isNew;
1106             paths[counter] = placeholder.path;
1107         }
1108 
1109         TreeSelectionEvent     event = new TreeSelectionEvent
1110                           (this, paths, newness, oldLeadSelection, leadPath);
1111 
1112         fireValueChanged(event);
1113     }
1114 
1115     /**
1116      * Updates the leadIndex instance variable.
1117      */
1118     protected void updateLeadIndex() {
1119         if(leadPath != null) {
1120             if(selection == null) {
1121                 leadPath = null;
1122                 leadIndex = leadRow = -1;
1123             }
1124             else {
1125                 leadRow = leadIndex = -1;
1126                 for(int counter = selection.length - 1; counter >= 0;
1127                     counter--) {
1128                     // Can use == here since we know leadPath came from
1129                     // selection
1130                     if(selection[counter] == leadPath) {
1131                         leadIndex = counter;
1132                         break;
1133                     }
1134                 }
1135             }
1136         }
1137         else {
1138             leadIndex = -1;
1139         }
1140     }
1141 
1142     /**
1143      * This method is obsolete and its implementation is now a noop.  It's
1144      * still called by setSelectionPaths and addSelectionPaths, but only
1145      * for backwards compatibility.
1146      */
1147     protected void insureUniqueness() {
1148     }
1149 
1150 
1151     /**
1152      * Returns a string that displays and identifies this
1153      * object's properties.
1154      *
1155      * @return a String representation of this object
1156      */
1157     public String toString() {
1158         int                selCount = getSelectionCount();
1159         StringBuilder      sb = new StringBuilder();
1160         int[]              rows;
1161 
1162         if(rowMapper != null)
1163             rows = rowMapper.getRowsForPaths(selection);
1164         else
1165             rows = null;
1166         sb.append(getClass().getName()).append(' ').append(hashCode()).append(" [ ");
1167         for(int counter = 0; counter < selCount; counter++) {
1168             if(rows != null)
1169                 sb.append(selection[counter]).append('@')
1170                         .append(rows[counter]).append(' ');
1171             else
1172                 sb.append(selection[counter]).append(' ');
1173         }
1174         sb.append(']');
1175         return sb.toString();
1176     }
1177 
1178     /**
1179      * Returns a clone of this object with the same selection.
1180      * This method does not duplicate
1181      * selection listeners and property listeners.
1182      *
1183      * @exception CloneNotSupportedException never thrown by instances of
1184      *                                       this class
1185      */
1186     public Object clone() throws CloneNotSupportedException {
1187         DefaultTreeSelectionModel        clone = (DefaultTreeSelectionModel)
1188                             super.clone();
1189 
1190         clone.changeSupport = null;
1191         if(selection != null) {
1192             int              selLength = selection.length;
1193 
1194             clone.selection = new TreePath[selLength];
1195             System.arraycopy(selection, 0, clone.selection, 0, selLength);
1196         }
1197         clone.listenerList = new EventListenerList();
1198         clone.listSelectionModel = (DefaultListSelectionModel)
1199             listSelectionModel.clone();
1200         clone.uniquePaths = new Hashtable<TreePath, Boolean>();
1201         clone.lastPaths = new Hashtable<TreePath, Boolean>();
1202         clone.tempPaths = new TreePath[1];
1203         return clone;
1204     }
1205 
1206     // Serialization support.
1207     private void writeObject(ObjectOutputStream s) throws IOException {
1208         Object[]             tValues;
1209 
1210         s.defaultWriteObject();
1211         // Save the rowMapper, if it implements Serializable
1212         if(rowMapper != null && rowMapper instanceof Serializable) {
1213             tValues = new Object[2];
1214             tValues[0] = "rowMapper";
1215             tValues[1] = rowMapper;
1216         }
1217         else
1218             tValues = new Object[0];
1219         s.writeObject(tValues);
1220     }
1221 
1222 
1223     private void readObject(ObjectInputStream s)
1224         throws IOException, ClassNotFoundException {
1225         ObjectInputStream.GetField f = s.readFields();
1226 
1227         changeSupport = (SwingPropertyChangeSupport) f.get("changeSupport", null);
1228         selection = (TreePath[]) f.get("selection", null);
1229         EventListenerList newListenerList = (EventListenerList) f.get("listenerList", null);
1230         if (newListenerList == null) {
1231             throw new InvalidObjectException("Null listenerList");
1232         }
1233         listenerList = newListenerList;
1234         listSelectionModel = (DefaultListSelectionModel) f.get("listSelectionModel", null);
1235         selectionMode = validateSelectionMode(f.get("selectionMode", 0));
1236         leadPath = (TreePath) f.get("leadPath", null);
1237         leadIndex = f.get("leadIndex", 0);
1238         leadRow = f.get("leadRow", 0);
1239         @SuppressWarnings("unchecked")
1240         Hashtable<TreePath, Boolean> newUniquePaths =
1241                 (Hashtable<TreePath, Boolean>) f.get("uniquePaths", null);
1242         uniquePaths = newUniquePaths;
1243         @SuppressWarnings("unchecked")
1244         Hashtable<TreePath, Boolean> newLastPaths =
1245                 (Hashtable<TreePath, Boolean>) f.get("lastPaths", null);
1246         lastPaths = newLastPaths;
1247         tempPaths = (TreePath[]) f.get("tempPaths", null);
1248 
1249         Object[]      tValues;
1250         tValues = (Object[])s.readObject();
1251 
1252         if (tValues.length > 0 && tValues[0].equals("rowMapper")) {
1253             RowMapper newRowMapper = (RowMapper) tValues[1];
1254             if (newRowMapper == null) {
1255                 throw new InvalidObjectException("Null newRowMapper");
1256             }
1257             rowMapper = newRowMapper;
1258         }
1259     }
1260 }
1261 
1262 /**
1263  * Holds a path and whether or not it is new.
1264  */
1265 class PathPlaceHolder {
1266     protected boolean             isNew;
1267     protected TreePath           path;
1268 
1269     PathPlaceHolder(TreePath path, boolean isNew) {
1270         this.path = path;
1271         this.isNew = isNew;
1272     }
1273 }