1 /*
2  * Copyright (c) 1997, 2015, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package javax.swing.tree;
27 
28 import 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      */
DefaultTreeSelectionModel()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      */
setRowMapper(RowMapper newMapper)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      */
getRowMapper()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      */
setSelectionMode(int mode)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 
validateSelectionMode(int mode)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      */
getSelectionMode()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       */
setSelectionPath(TreePath path)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      */
setSelectionPaths(TreePath[] pPaths)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       */
addSelectionPath(TreePath path)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       */
addSelectionPaths(TreePath[] paths)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       */
removeSelectionPath(TreePath path)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       */
removeSelectionPaths(TreePath[] paths)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       */
getSelectionPath()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       */
getSelectionPaths()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      */
getSelectionCount()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       */
isPathSelected(TreePath path)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       */
isSelectionEmpty()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       */
clearSelection()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       */
addTreeSelectionListener(TreeSelectionListener x)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       */
removeTreeSelectionListener(TreeSelectionListener x)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      */
getTreeSelectionListeners()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      */
fireValueChanged(TreeSelectionEvent e)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      */
getListeners(Class<T> listenerType)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      */
getSelectionRows()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       */
getMinSelectionRow()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       */
getMaxSelectionRow()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       */
isRowSelected(int row)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      */
resetRowSelection()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      */
getLeadSelectionRow()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      */
getLeadSelectionPath()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      */
addPropertyChangeListener( PropertyChangeListener listener)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 
removePropertyChangeListener( PropertyChangeListener listener)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      */
getPropertyChangeListeners()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      */
insureRowContinuity()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      */
arePathsContiguous(TreePath[] paths)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      */
canPathsBeAdded(TreePath[] paths)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      */
canPathsBeRemoved(TreePath[] paths)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
notifyPathChange(Vector<?> changedPaths, TreePath oldLeadSelection)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      */
updateLeadIndex()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      */
insureUniqueness()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      */
toString()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      */
clone()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.
writeObject(ObjectOutputStream s)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 
readObject(ObjectInputStream s)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 
PathPlaceHolder(TreePath path, boolean isNew)1268     PathPlaceHolder(TreePath path, boolean isNew) {
1269         this.path = path;
1270         this.isNew = isNew;
1271     }
1272 }
1273