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