001    /* DefaultTreeSelectionModel.java 
002       Copyright (C) 2002, 2004, 2005, 2006 Free Software Foundation, Inc.
003       
004    This file is part of GNU Classpath.
005    
006    GNU Classpath is free software; you can redistribute it and/or modify
007    it under the terms of the GNU General Public License as published by
008    the Free Software Foundation; either version 2, or (at your option)
009    any later version.
010    
011    GNU Classpath is distributed in the hope that it will be useful, but
012    WITHOUT ANY WARRANTY; without even the implied warranty of
013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014    General Public License for more details.
015    
016    You should have received a copy of the GNU General Public License
017    along with GNU Classpath; see the file COPYING.  If not, write to the
018    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019    02110-1301 USA.
020    
021    Linking this library statically or dynamically with other modules is
022    making a combined work based on this library.  Thus, the terms and
023    conditions of the GNU General Public License cover the whole
024    combination.
025    
026    As a special exception, the copyright holders of this library give you
027    permission to link this library with independent modules to produce an
028    executable, regardless of the license terms of these independent
029    modules, and to copy and distribute the resulting executable under
030    terms of your choice, provided that you also meet, for each linked
031    independent module, the terms and conditions of the license of that
032    module.  An independent module is a module which is not derived from
033    or based on this library.  If you modify this library, you may extend
034    this exception to your version of the library, but you are not
035    obligated to do so.  If you do not wish to do so, delete this
036    exception statement from your version. */
037    
038    
039    package javax.swing.tree;
040    
041    import gnu.java.lang.CPStringBuilder;
042    
043    import java.beans.PropertyChangeListener;
044    import java.io.IOException;
045    import java.io.ObjectInputStream;
046    import java.io.ObjectOutputStream;
047    import java.io.Serializable;
048    import java.util.Arrays;
049    import java.util.BitSet;
050    import java.util.EventListener;
051    import java.util.HashSet;
052    import java.util.Iterator;
053    import java.util.Vector;
054    
055    import javax.swing.DefaultListSelectionModel;
056    import javax.swing.event.EventListenerList;
057    import javax.swing.event.SwingPropertyChangeSupport;
058    import javax.swing.event.TreeSelectionEvent;
059    import javax.swing.event.TreeSelectionListener;
060    
061    /**
062     * The implementation of the default tree selection model. The installed
063     * listeners are notified about the path and not the row changes. If you
064     * specifically need to track the row changes, register the listener for the
065     * expansion events.
066     * 
067     * @author Andrew Selkirk
068     * @author Audrius Meskauskas
069     */
070    public class DefaultTreeSelectionModel
071        implements Cloneable, Serializable, TreeSelectionModel
072    {
073    
074      /**
075       * According to the API docs, the method
076       * {@link DefaultTreeSelectionModel#notifyPathChange} should
077       * expect instances of a class PathPlaceHolder in the Vector parameter.
078       * This seems to be a non-public class, so I can only make guesses about the
079       * use of it.
080       */
081      private static class PathPlaceHolder
082      {
083        /**
084         * The path that we wrap.
085         */
086        TreePath path;
087    
088        /**
089         * Indicates if the path is new or already in the selection.
090         */
091        boolean isNew;
092    
093        /**
094         * Creates a new instance.
095         *
096         * @param p the path to wrap
097         * @param n if the path is new or already in the selection
098         */
099        PathPlaceHolder(TreePath p, boolean n)
100        {
101          path = p;
102          isNew = n;
103        }
104      }
105    
106      /**
107       * Use serialVersionUID for interoperability.
108       */
109      static final long serialVersionUID = 3288129636638950196L;
110    
111      /**
112       * The name of the selection mode property.
113       */
114      public static final String SELECTION_MODE_PROPERTY = "selectionMode";
115    
116      /**
117       * Our Swing property change support.
118       */
119      protected SwingPropertyChangeSupport changeSupport;
120    
121      /**
122       * The current selection.
123       */
124      protected TreePath[] selection;
125    
126      /**
127       * Our TreeSelectionListeners.
128       */
129      protected EventListenerList listenerList;
130    
131      /**
132       * The current RowMapper.
133       */
134      protected transient RowMapper rowMapper;
135    
136      /**
137       * The current listSelectionModel.
138       */
139      protected DefaultListSelectionModel listSelectionModel;
140    
141      /**
142       * The current selection mode.
143       */
144      protected int selectionMode;
145    
146      /**
147       * The path that has been added last.
148       */
149      protected TreePath leadPath;
150    
151      /**
152       * The index of the last added path.
153       */
154      protected int leadIndex;
155    
156      /**
157       * The row of the last added path according to the RowMapper.
158       */
159      protected int leadRow = -1;
160    
161      /**
162       * A supporting datastructure that is used in addSelectionPaths() and
163       * removeSelectionPaths(). It contains currently selected paths.
164       *
165       * @see #addSelectionPaths(TreePath[])
166       * @see #removeSelectionPaths(TreePath[])
167       * @see #setSelectionPaths(TreePath[])
168       */
169      private transient HashSet<TreePath> selectedPaths;
170    
171      /**
172       * A supporting datastructure that is used in addSelectionPaths() and
173       * removeSelectionPaths(). It contains the paths that are added or removed.
174       *
175       * @see #addSelectionPaths(TreePath[])
176       * @see #removeSelectionPaths(TreePath[])
177       * @see #setSelectionPaths(TreePath[])
178       */
179      private transient HashSet<TreePath> tmpPaths;
180    
181      /**
182       * Constructs a new DefaultTreeSelectionModel.
183       */
184      public DefaultTreeSelectionModel()
185      {
186        setSelectionMode(DISCONTIGUOUS_TREE_SELECTION);
187        listSelectionModel = new DefaultListSelectionModel();
188        listenerList = new EventListenerList();
189        leadIndex = -1;
190        tmpPaths = new HashSet<TreePath>();
191        selectedPaths = new HashSet<TreePath>();
192      }
193    
194      /**
195       * Creates a clone of this DefaultTreeSelectionModel with the same selection.
196       * The cloned instance will have the same registered listeners, the listeners
197       * themselves will not be cloned. The selection will be cloned.
198       * 
199       * @exception CloneNotSupportedException should not be thrown here
200       * @return a copy of this DefaultTreeSelectionModel
201       */
202      public Object clone() throws CloneNotSupportedException
203      {
204        DefaultTreeSelectionModel cloned = 
205          (DefaultTreeSelectionModel) super.clone();
206        cloned.changeSupport = null;
207        cloned.selection = (TreePath[]) selection.clone();
208        cloned.listenerList = new EventListenerList();
209        cloned.listSelectionModel =
210          (DefaultListSelectionModel) listSelectionModel.clone();
211        cloned.selectedPaths = new HashSet<TreePath>();
212        cloned.tmpPaths = new HashSet<TreePath>();
213    
214        return cloned;
215      }
216    
217      /**
218       * Returns a string that shows this object's properties.
219       * The returned string lists the selected tree rows, if any.
220       * 
221       * @return a string that shows this object's properties
222       */
223      public String toString() 
224      {
225        if (isSelectionEmpty())
226          return "[selection empty]";
227        else
228          {
229            CPStringBuilder b = new CPStringBuilder("selected rows: [");
230            for (int i = 0; i < selection.length; i++)
231              {
232                b.append(getRow(selection[i]));
233                b.append(' ');
234              }
235            b.append(", lead " + getLeadSelectionRow());
236            return b.toString();
237          }
238      }
239    
240      /**
241       * writeObject
242       * 
243       * @param value0 TODO
244       * @exception IOException TODO
245       */
246      private void writeObject(ObjectOutputStream value0) throws IOException
247      {
248        // TODO
249      }
250    
251      /**
252       * readObject
253       * 
254       * @param value0 TODO
255       * @exception IOException TODO
256       * @exception ClassNotFoundException TODO
257       */
258      private void readObject(ObjectInputStream value0) throws IOException,
259          ClassNotFoundException
260      {
261        // TODO
262      }
263    
264      /**
265       * Sets the RowMapper that should be used to map between paths and their rows.
266       * 
267       * @param mapper the RowMapper to set
268       * @see RowMapper
269       */
270      public void setRowMapper(RowMapper mapper)
271      {
272        rowMapper = mapper;
273        resetRowSelection();
274      }
275    
276      /**
277       * Returns the RowMapper that is currently used to map between paths and their
278       * rows.
279       * 
280       * @return the current RowMapper
281       * @see RowMapper
282       */
283      public RowMapper getRowMapper()
284      {
285        return rowMapper;
286      }
287    
288      /**
289       * Sets the current selection mode. Possible values are
290       * {@link #SINGLE_TREE_SELECTION}, {@link #CONTIGUOUS_TREE_SELECTION} and
291       * {@link #DISCONTIGUOUS_TREE_SELECTION}.
292       * 
293       * @param mode the selection mode to be set
294       * @see #getSelectionMode
295       * @see #SINGLE_TREE_SELECTION
296       * @see #CONTIGUOUS_TREE_SELECTION
297       * @see #DISCONTIGUOUS_TREE_SELECTION
298       */
299      public void setSelectionMode(int mode)
300      {
301        int oldMode = selectionMode;
302        selectionMode = mode;
303        // Make sure we have a valid selection mode.
304        if (selectionMode != SINGLE_TREE_SELECTION
305            && selectionMode != CONTIGUOUS_TREE_SELECTION
306            && selectionMode != DISCONTIGUOUS_TREE_SELECTION)
307          selectionMode = DISCONTIGUOUS_TREE_SELECTION;
308    
309        // Fire property change event.
310        if (oldMode != selectionMode && changeSupport != null)
311          changeSupport.firePropertyChange(SELECTION_MODE_PROPERTY, oldMode,
312                                           selectionMode);
313      }
314    
315      /**
316       * Returns the current selection mode.
317       * 
318       * @return the current selection mode
319       * @see #setSelectionMode
320       * @see #SINGLE_TREE_SELECTION
321       * @see #CONTIGUOUS_TREE_SELECTION
322       * @see #DISCONTIGUOUS_TREE_SELECTION
323       */
324      public int getSelectionMode()
325      {
326        return selectionMode;
327      }
328    
329      /**
330       * Sets this path as the only selection. If this changes the selection the
331       * registered TreeSelectionListeners are notified.
332       * 
333       * @param path the path to set as selection
334       */
335      public void setSelectionPath(TreePath path)
336      {
337        TreePath[] paths = null;
338        if (path != null)
339          paths = new TreePath[]{ path };
340        setSelectionPaths(paths);
341      }
342      
343      /**
344       * Get the number of the tree row for the given path.
345       * 
346       * @param path the tree path
347       * @return the tree row for this path or -1 if the path is not visible.
348       */
349      int getRow(TreePath path)
350      {
351        RowMapper mapper = getRowMapper();
352    
353        if (mapper instanceof AbstractLayoutCache)
354          {
355            // The absolute majority of cases, unless the TreeUI is very
356            // seriously rewritten
357            AbstractLayoutCache ama = (AbstractLayoutCache) mapper;
358            return ama.getRowForPath(path);
359          }
360        else if (mapper != null)
361          {
362            // Generic non optimized implementation.
363            int[] rows = mapper.getRowsForPaths(new TreePath[] { path });
364            if (rows.length == 0)
365              return - 1;
366            else
367              return rows[0];
368          }
369        return -1;
370      }
371    
372      /**
373       * Sets the paths as selection. This method checks for duplicates and removes
374       * them. If this changes the selection the registered TreeSelectionListeners
375       * are notified.
376       * 
377       * @param paths the paths to set as selection
378       */
379      public void setSelectionPaths(TreePath[] paths)
380      {
381        int oldLength = 0;
382        if (selection != null)
383          oldLength = selection.length;
384        int newLength = 0;
385        if (paths != null)
386          newLength = paths.length;
387        if (newLength > 0 || oldLength > 0)
388          {
389            // For SINGLE_TREE_SELECTION and for CONTIGUOUS_TREE_SELECTION with
390            // a non-contiguous path, we only allow the first path element.
391            if ((selectionMode == SINGLE_TREE_SELECTION && newLength > 1)
392                || (selectionMode == CONTIGUOUS_TREE_SELECTION && newLength > 0
393                    && ! arePathsContiguous(paths)))
394              {
395                paths = new TreePath[] { paths[0] };
396                newLength = 1;
397              }
398            // Find new paths.
399            Vector<PathPlaceHolder> changedPaths = null;
400            tmpPaths.clear();
401            int validPaths = 0;
402            TreePath oldLeadPath = leadPath;
403            for (int i = 0; i < newLength; i++)
404              {
405                if (paths[i] != null && ! tmpPaths.contains(paths[i]))
406                  {
407                    validPaths++;
408                    tmpPaths.add(paths[i]);
409                    if (! selectedPaths.contains(paths[i]))
410                      {
411                        if (changedPaths == null)
412                          changedPaths = new Vector<PathPlaceHolder>();
413                        changedPaths.add(new PathPlaceHolder(paths[i], true));
414                      }
415                    leadPath = paths[i];
416                  }
417              }
418            // Put together the new selection.
419            TreePath[] newSelection = null;
420            if (validPaths != 0)
421              {
422                if (validPaths != newLength)
423                  {
424                    // Some of the paths are already selected, put together
425                    // the new selection carefully.
426                    newSelection = new TreePath[validPaths];
427                    Iterator<TreePath> newPaths = tmpPaths.iterator();
428                    validPaths = 0;
429                    for (int i = 0; newPaths.hasNext(); i++)
430                      newSelection[i] = newPaths.next();
431                  }
432                else
433                  {
434                    newSelection = new TreePath[paths.length];
435                    System.arraycopy(paths, 0, newSelection, 0, paths.length);
436                  }
437              }
438    
439            // Find paths that have been selected, but are no more.
440            for (int i = 0; i < oldLength; i++)
441              {
442                if (selection[i] != null && ! tmpPaths.contains(selection[i]))
443                  {
444                    if (changedPaths == null)
445                      changedPaths = new Vector<PathPlaceHolder>();
446                    changedPaths.add(new PathPlaceHolder(selection[i], false));
447                  }
448              }
449    
450            // Perform changes and notification.
451            selection = newSelection;
452            HashSet<TreePath> tmp = selectedPaths;
453            selectedPaths = tmpPaths;
454            tmpPaths = tmp;
455            tmpPaths.clear();
456    
457            // Not necessary, but required according to the specs and to tests.
458            if (selection != null)
459              insureUniqueness();
460            updateLeadIndex();
461            resetRowSelection();
462            if (changedPaths != null && changedPaths.size() > 0)
463              notifyPathChange(changedPaths, oldLeadPath);
464          }
465      }
466    
467      /**
468       * Adds a path to the list of selected paths. This method checks if the path
469       * is already selected and doesn't add the same path twice. If this changes
470       * the selection the registered TreeSelectionListeners are notified.
471       * 
472       * The lead path is changed to the added path. This also happen if the 
473       * passed path was already selected before.
474       * 
475       * @param path the path to add to the selection
476       */
477      public void addSelectionPath(TreePath path)
478      {
479        if (path != null)
480          {
481            TreePath[] add = new TreePath[]{ path };
482            addSelectionPaths(add);
483          }
484      }
485    
486      /**
487       * Adds the paths to the list of selected paths. This method checks if the
488       * paths are already selected and doesn't add the same path twice. If this
489       * changes the selection the registered TreeSelectionListeners are notified.
490       * 
491       * @param paths the paths to add to the selection
492       */
493      public void addSelectionPaths(TreePath[] paths)
494      {
495        int length = paths != null ? paths.length : 0;
496        if (length > 0)
497          {
498            if (selectionMode == SINGLE_TREE_SELECTION)
499              setSelectionPaths(paths);
500            else if (selectionMode == CONTIGUOUS_TREE_SELECTION
501                     &&  ! canPathsBeAdded(paths))
502              {
503                if (arePathsContiguous(paths))
504                  setSelectionPaths(paths);
505                else
506                  setSelectionPaths(new TreePath[] { paths[0] });
507              }
508            else
509              {
510                Vector<PathPlaceHolder> changedPaths = null;
511                tmpPaths.clear();
512                int validPaths = 0;
513                TreePath oldLeadPath = leadPath;
514                int oldPaths = 0;
515                if (selection != null)
516                  oldPaths = selection.length;
517                int i;
518                for (i = 0; i < length; i++)
519                  {
520                    if (paths[i] != null)
521                      {
522                        if (! selectedPaths.contains(paths[i]))
523                          {
524                            validPaths++;
525                            if (changedPaths == null)
526                              changedPaths = new Vector<PathPlaceHolder>();
527                            changedPaths.add(new PathPlaceHolder(paths[i], true));
528                            selectedPaths.add(paths[i]);
529                            tmpPaths.add(paths[i]);
530                          }
531                        leadPath = paths[i];
532                      }
533                  }
534                if (validPaths > 0)
535                  {
536                    TreePath[] newSelection = new TreePath[oldPaths + validPaths];
537                    if (oldPaths > 0)
538                      System.arraycopy(selection, 0, newSelection, 0, oldPaths);
539                    if (validPaths != paths.length)
540                      {
541                        // Some of the paths are already selected, put together
542                        // the new selection carefully.
543                        Iterator<TreePath> newPaths = tmpPaths.iterator();
544                        i = oldPaths;
545                        while (newPaths.hasNext())
546                          {
547                            newSelection[i] = newPaths.next();
548                            i++;
549                          }
550                      }
551                    else
552                      System.arraycopy(paths, 0, newSelection, oldPaths,
553                                       validPaths);
554                    selection = newSelection;
555                    insureUniqueness();
556                    updateLeadIndex();
557                    resetRowSelection();
558                    if (changedPaths != null && changedPaths.size() > 0)
559                      notifyPathChange(changedPaths, oldLeadPath);
560                  }
561                else
562                  leadPath = oldLeadPath;
563                tmpPaths.clear();
564              }
565          }
566      }
567    
568      /**
569       * Removes the path from the selection. If this changes the selection the
570       * registered TreeSelectionListeners are notified.
571       * 
572       * @param path the path to remove
573       */
574      public void removeSelectionPath(TreePath path)
575      {
576        if (path != null)
577          removeSelectionPaths(new TreePath[]{ path });
578      }
579    
580      /**
581       * Removes the paths from the selection. If this changes the selection the
582       * registered TreeSelectionListeners are notified.
583       * 
584       * @param paths the paths to remove
585       */
586      public void removeSelectionPaths(TreePath[] paths)
587      {
588        if (paths != null && selection != null && paths.length > 0)
589          {
590            if (! canPathsBeRemoved(paths))
591              clearSelection();
592            else
593              {
594                Vector<PathPlaceHolder> pathsToRemove = null;
595                for (int i = paths.length - 1; i >= 0; i--)
596                  {
597                    if (paths[i] != null && selectedPaths.contains(paths[i]))
598                      {
599                        if (pathsToRemove == null)
600                          pathsToRemove = new Vector<PathPlaceHolder>();
601                        selectedPaths.remove(paths[i]);
602                        pathsToRemove.add(new PathPlaceHolder(paths[i],
603                                                              false));
604                      }
605                  }
606                if (pathsToRemove != null)
607                  {
608                    int numRemove = pathsToRemove.size();
609                    TreePath oldLead = leadPath;
610                    if (numRemove == selection.length)
611                      selection = null;
612                    else
613                      {
614                        selection = new TreePath[selection.length - numRemove];
615                        Iterator<TreePath> keep = selectedPaths.iterator();
616                        for (int valid = 0; keep.hasNext(); valid++)
617                          selection[valid] = keep.next();
618                      }
619                    // Update lead path.
620                    if (leadPath != null && ! selectedPaths.contains(leadPath))
621                      {
622                        if (selection != null)
623                          leadPath = selection[selection.length - 1];
624                        else
625                          leadPath = null;
626                      }
627                    else if (selection != null)
628                      leadPath = selection[selection.length - 1];
629                    else
630                      leadPath = null;
631                    updateLeadIndex();
632                    resetRowSelection();
633                    notifyPathChange(pathsToRemove, oldLead);
634                  }
635              }
636          }
637      }
638    
639      /**
640       * Returns the first path in the selection. This is especially useful when the
641       * selectionMode is {@link #SINGLE_TREE_SELECTION}.
642       * 
643       * @return the first path in the selection
644       */
645      public TreePath getSelectionPath()
646      {
647        if ((selection == null) || (selection.length == 0))
648          return null;
649        else
650          return selection[0];
651      }
652    
653      /**
654       * Returns the complete selection.
655       * 
656       * @return the complete selection
657       */
658      public TreePath[] getSelectionPaths()
659      {
660        return selection;
661      }
662    
663      /**
664       * Returns the number of paths in the selection.
665       * 
666       * @return the number of paths in the selection
667       */
668      public int getSelectionCount()
669      {
670        if (selection == null)
671          return 0;
672        else
673          return selection.length;
674      }
675    
676      /**
677       * Checks if a given path is in the selection.
678       * 
679       * @param path the path to check
680       * @return <code>true</code> if the path is in the selection,
681       *         <code>false</code> otherwise
682       */
683      public boolean isPathSelected(TreePath path)
684      {
685        if (selection == null)
686          return false;
687    
688        for (int i = 0; i < selection.length; i++)
689          {
690            if (selection[i].equals(path))
691              return true;
692          }
693        return false;
694      }
695    
696      /**
697       * Checks if the selection is empty.
698       * 
699       * @return <code>true</code> if the selection is empty, <code>false</code>
700       *         otherwise
701       */
702      public boolean isSelectionEmpty()
703      {
704        return (selection == null) || (selection.length == 0);
705      }
706    
707      /**
708       * Removes all paths from the selection. Fire the unselection event.
709       */
710      public void clearSelection()
711      {
712        if (selection != null)
713          {
714            int selectionLength = selection.length;
715            boolean[] news = new boolean[selectionLength];
716            Arrays.fill(news, false);
717            TreeSelectionEvent event = new TreeSelectionEvent(this, selection,
718                                                              news, leadPath,
719                                                              null);
720            leadPath = null;
721            leadIndex = 0;
722            leadRow = 0;
723            selectedPaths.clear();
724            selection = null;
725            resetRowSelection();
726            fireValueChanged(event);
727          }
728      }
729    
730      /**
731       * Adds a <code>TreeSelectionListener</code> object to this model.
732       * 
733       * @param listener the listener to add
734       */
735      public void addTreeSelectionListener(TreeSelectionListener listener)
736      {
737        listenerList.add(TreeSelectionListener.class, listener);
738      }
739    
740      /**
741       * Removes a <code>TreeSelectionListener</code> object from this model.
742       * 
743       * @param listener the listener to remove
744       */
745      public void removeTreeSelectionListener(TreeSelectionListener listener)
746      {
747        listenerList.remove(TreeSelectionListener.class, listener);
748      }
749    
750      /**
751       * Returns all <code>TreeSelectionListener</code> added to this model.
752       * 
753       * @return an array of listeners
754       * @since 1.4
755       */
756      public TreeSelectionListener[] getTreeSelectionListeners()
757      {
758        return (TreeSelectionListener[]) getListeners(TreeSelectionListener.class);
759      }
760    
761      /**
762       * fireValueChanged
763       * 
764       * @param event the event to fire.
765       */
766      protected void fireValueChanged(TreeSelectionEvent event)
767      {
768        TreeSelectionListener[] listeners = getTreeSelectionListeners();
769    
770        for (int i = 0; i < listeners.length; ++i)
771          listeners[i].valueChanged(event);
772      }
773    
774      /**
775       * Returns all added listeners of a special type.
776       * 
777       * @param listenerType the listener type
778       * @return an array of listeners
779       * @since 1.3
780       */
781      public <T extends EventListener> T[] getListeners(Class<T> listenerType)
782      {
783        return listenerList.getListeners(listenerType);
784      }
785    
786      /**
787       * Returns the currently selected rows.
788       * 
789       * @return the currently selected rows
790       */
791      public int[] getSelectionRows()
792      {
793        int[] rows = null;
794        if (rowMapper != null && selection != null)
795          {
796            rows = rowMapper.getRowsForPaths(selection);
797            if (rows != null)
798              {
799                // Find invisible rows.
800                int invisible = 0;
801                for (int i = rows.length - 1; i >= 0; i--)
802                  {
803                    if (rows[i] == -1)
804                      invisible++;
805                    
806                  }
807                // Clean up invisible rows.
808                if (invisible > 0)
809                  {
810                    if (invisible == rows.length)
811                      rows = null;
812                    else
813                      {
814                        int[] newRows = new int[rows.length - invisible];
815                        int visCount = 0;
816                        for (int i = rows.length - 1; i >= 0; i--)
817                          {
818                            if (rows[i] != -1)
819                              {
820                                newRows[visCount] = rows[i];
821                                visCount++;
822                              }
823                          }
824                        rows = newRows;
825                      }
826                  }
827              }
828          }
829        return rows;
830      }
831    
832      /**
833       * Returns the smallest row index from the selection.
834       * 
835       * @return the smallest row index from the selection
836       */
837      public int getMinSelectionRow()
838      {
839        return listSelectionModel.getMinSelectionIndex();
840      }
841    
842      /**
843       * Returns the largest row index from the selection.
844       * 
845       * @return the largest row index from the selection
846       */
847      public int getMaxSelectionRow()
848      {
849        return listSelectionModel.getMaxSelectionIndex();
850      }
851    
852      /**
853       * Checks if a particular row is selected.
854       * 
855       * @param row the index of the row to check
856       * @return <code>true</code> if the row is in this selection,
857       *         <code>false</code> otherwise
858       * @throws NullPointerException if the row mapper is not set (can only happen
859       *           if the user has plugged in the custom incorrect TreeUI
860       *           implementation.
861       */
862      public boolean isRowSelected(int row)
863      {
864        return listSelectionModel.isSelectedIndex(row);
865      }
866    
867      /**
868       * Updates the mappings from TreePaths to row indices.
869       */
870      public void resetRowSelection()
871      {
872        listSelectionModel.clearSelection();
873        if (selection != null && rowMapper != null)
874          {
875            int[] rows = rowMapper.getRowsForPaths(selection);
876            // Update list selection model.
877            for (int i = 0; i < rows.length; i++)
878              {
879                int row = rows[i];
880                if (row != -1)
881                  listSelectionModel.addSelectionInterval(row, row);
882              }
883            // Update lead selection.
884            if (leadIndex != -1 && rows != null)
885              leadRow = rows[leadIndex];
886            else if (leadPath != null)
887              {
888                TreePath[] tmp = new TreePath[]{ leadPath };
889                rows = rowMapper.getRowsForPaths(tmp);
890                leadRow = rows != null ? rows[0] : -1;
891              }
892            else
893              leadRow = -1;
894            insureRowContinuity();
895          }
896        else
897          leadRow = -1;
898      }
899    
900      /**
901       * getLeadSelectionRow
902       * 
903       * @return int
904       */
905      public int getLeadSelectionRow()
906      {
907        return leadRow;
908      }
909    
910      /**
911       * getLeadSelectionPath
912       * 
913       * @return TreePath
914       */
915      public TreePath getLeadSelectionPath()
916      {
917        return leadPath;
918      }
919    
920      /**
921       * Adds a <code>PropertyChangeListener</code> object to this model.
922       * 
923       * @param listener the listener to add.
924       */
925      public void addPropertyChangeListener(PropertyChangeListener listener)
926      {
927        if (changeSupport == null)
928          changeSupport = new SwingPropertyChangeSupport(this);
929        changeSupport.addPropertyChangeListener(listener);
930      }
931    
932      /**
933       * Removes a <code>PropertyChangeListener</code> object from this model.
934       * 
935       * @param listener the listener to remove.
936       */
937      public void removePropertyChangeListener(PropertyChangeListener listener)
938      {
939        if (changeSupport != null)
940          changeSupport.removePropertyChangeListener(listener);
941      }
942    
943      /**
944       * Returns all added <code>PropertyChangeListener</code> objects.
945       * 
946       * @return an array of listeners.
947       * @since 1.4
948       */
949      public PropertyChangeListener[] getPropertyChangeListeners()
950      {
951        PropertyChangeListener[] listeners = null;
952        if (changeSupport != null)
953          listeners = changeSupport.getPropertyChangeListeners();
954        else
955          listeners = new PropertyChangeListener[0];
956        return listeners;
957      }
958    
959      /**
960       * Makes sure the currently selected paths are valid according to the current
961       * selectionMode. If the selectionMode is set to
962       * {@link #CONTIGUOUS_TREE_SELECTION} and the selection isn't contiguous then
963       * the selection is reset to the first set of contguous paths. If the
964       * selectionMode is set to {@link #SINGLE_TREE_SELECTION} and the selection
965       * has more than one path, the selection is reset to the contain only the
966       * first path.
967       */
968      protected void insureRowContinuity()
969      {
970        if (selectionMode == CONTIGUOUS_TREE_SELECTION && selection != null
971            && rowMapper != null)
972          {
973            int min = listSelectionModel.getMinSelectionIndex();
974            if (min != -1)
975              {
976                int max = listSelectionModel.getMaxSelectionIndex();
977                for (int i = min; i <= max; i++)
978                  {
979                    if (! listSelectionModel.isSelectedIndex(i))
980                      {
981                        if (i == min)
982                          clearSelection();
983                        else
984                          {
985                            TreePath[] newSelection = new TreePath[i - min];
986                            int[] rows = rowMapper.getRowsForPaths(selection);
987                            for (int j = 0; j < rows.length; j++)
988                              {
989                                if (rows[j] < i)
990                                  newSelection[rows[j] - min] = selection[j];
991                              }
992                            setSelectionPaths(newSelection);
993                            break;
994                          }
995                      }
996                  }
997              }
998          }
999        else if (selectionMode == SINGLE_TREE_SELECTION && selection != null
1000            && selection.length > 1)
1001          setSelectionPath(selection[0]);
1002      }
1003      
1004      /**
1005       * Returns <code>true</code> if the paths are contiguous (take subsequent
1006       * rows in the diplayed tree view. The method returns <code>true</code> if
1007       * we have no RowMapper assigned.
1008       * 
1009       * @param paths the paths to check for continuity
1010       * @return <code>true</code> if the paths are contiguous or we have no
1011       *         RowMapper assigned
1012       */
1013      protected boolean arePathsContiguous(TreePath[] paths)
1014      {
1015        if (rowMapper == null || paths.length < 2)
1016          return true;
1017    
1018        int length = paths.length;
1019        TreePath[] tmp = new TreePath[1];
1020        tmp[0] = paths[0];
1021        int min = rowMapper.getRowsForPaths(tmp)[0];
1022        BitSet selected = new BitSet();
1023        int valid = 0;
1024        for (int i = 0; i < length; i++)
1025          {
1026            if (paths[i] != null)
1027              {
1028                tmp[0] = paths[i];
1029                int[] rows = rowMapper.getRowsForPaths(tmp);
1030                if (rows == null)
1031                  return false; // No row mapping yet, can't be selected.
1032                int row = rows[0];
1033                if (row == -1 || row < (min - length) || row > (min + length))
1034                  return false; // Not contiguous.
1035                min = Math.min(min, row);
1036                if (! selected.get(row))
1037                  {
1038                    selected.set(row);
1039                    valid++;
1040                  }
1041                
1042              }
1043          }
1044        int max = valid + min;
1045        for (int i = min; i < max; i++)
1046          if (! selected.get(i))
1047            return false; // Not contiguous.
1048        return true;
1049      }
1050    
1051      /**
1052       * Checks if the paths can be added. This returns <code>true</code> if:
1053       * <ul>
1054       * <li><code>paths</code> is <code>null</code> or empty</li>
1055       * <li>we have no RowMapper assigned</li>
1056       * <li>nothing is currently selected</li>
1057       * <li>selectionMode is {@link #DISCONTIGUOUS_TREE_SELECTION}</li>
1058       * <li>adding the paths to the selection still results in a contiguous set of
1059       * paths</li>
1060       * 
1061       * @param paths the paths to check
1062       * @return <code>true</code> if the paths can be added with respect to the
1063       *         selectionMode
1064       */
1065      protected boolean canPathsBeAdded(TreePath[] paths)
1066      {
1067        if (paths == null || paths.length == 0 || rowMapper == null
1068            || selection == null || selectionMode == DISCONTIGUOUS_TREE_SELECTION)
1069          return true;
1070    
1071        BitSet selected = new BitSet();
1072        int min = listSelectionModel.getMinSelectionIndex();
1073        int max = listSelectionModel.getMaxSelectionIndex();
1074        TreePath[] tmp = new TreePath[1];
1075        if (min != -1)
1076          {
1077            // Set the bitmask of selected elements.
1078            for (int i = min; i <= max; i++)
1079              selected.set(i);
1080          }
1081        else
1082          {
1083            tmp[0] = paths[0];
1084            min = rowMapper.getRowsForPaths(tmp)[0];
1085            max = min;
1086          }
1087        // Mark new paths as selected.
1088        for (int i = paths.length - 1; i >= 0; i--)
1089          {
1090            if (paths[i] != null)
1091              {
1092                tmp[0] = paths[i];
1093                int[] rows = rowMapper.getRowsForPaths(tmp);
1094                if (rows == null)
1095                  return false; // Now row mapping yet, can't be selected.
1096                int row = rows[0];
1097                if (row == -1)
1098                  return false; // Now row mapping yet, can't be selected.
1099                min = Math.min(min, row);
1100                max = Math.max(max, row);
1101                selected.set(row);
1102              }
1103          }
1104        // Now look if the new selection would be contiguous.
1105        for (int i = min; i <= max; i++)
1106          if (! selected.get(i))
1107            return false;
1108        return true;
1109      }
1110      
1111      /**
1112       * Checks if the paths can be removed without breaking the continuity of the
1113       * selection according to selectionMode.
1114       * 
1115       * @param paths the paths to check
1116       * @return <code>true</code> if the paths can be removed with respect to the
1117       *         selectionMode
1118       */
1119      protected boolean canPathsBeRemoved(TreePath[] paths)
1120      {
1121        if (rowMapper == null || isSelectionEmpty()
1122            || selectionMode == DISCONTIGUOUS_TREE_SELECTION)
1123          return true;
1124        
1125        HashSet<TreePath> set = new HashSet<TreePath>();
1126        for (int i = 0; i < selection.length; i++)
1127          set.add(selection[i]);
1128        
1129        for (int i = 0; i < paths.length; i++)
1130          set.remove(paths[i]);
1131        
1132        TreePath[] remaining = new TreePath[set.size()];
1133        Iterator<TreePath> iter = set.iterator();
1134        
1135        for (int i = 0; i < remaining.length; i++)
1136          remaining[i] = iter.next();
1137        
1138        return arePathsContiguous(remaining);
1139      }
1140    
1141      /**
1142       * Notify the installed listeners that the given patches have changed. This
1143       * method will call listeners if invoked, but it is not called from the
1144       * implementation of this class.
1145       * 
1146       * @param vPaths the vector of the changed patches
1147       * @param oldLeadSelection the old selection index
1148       */
1149      protected void notifyPathChange(Vector<PathPlaceHolder> vPaths,
1150                                      TreePath oldLeadSelection)
1151      {
1152    
1153        int numChangedPaths = vPaths.size();
1154        boolean[] news = new boolean[numChangedPaths];
1155        TreePath[] paths = new TreePath[numChangedPaths];
1156        for (int i = 0; i < numChangedPaths; i++)
1157          {
1158            PathPlaceHolder p = vPaths.get(i);
1159            news[i] = p.isNew;
1160            paths[i] = p.path;
1161          }
1162    
1163        TreeSelectionEvent event = new TreeSelectionEvent(this, paths, news,
1164                                                          oldLeadSelection,
1165                                                          leadPath);
1166        fireValueChanged(event);
1167      }
1168    
1169      /**
1170       * Updates the lead selection row number after changing the lead selection
1171       * path.
1172       */
1173      protected void updateLeadIndex()
1174      {
1175        leadIndex = -1;
1176        if (leadPath != null)
1177          {
1178            leadRow = -1;
1179            if (selection == null)
1180              leadPath = null;
1181            else
1182              {
1183                for (int i = selection.length - 1; i >= 0 && leadIndex == -1; i--)
1184                  {
1185                    if (selection[i] == leadPath)
1186                      leadIndex = i;
1187                  }
1188              }
1189          }
1190      }
1191    
1192      /**
1193       * This method exists due historical reasons and returns without action
1194       * (unless overridden). For compatibility with the applications that override
1195       * it, it is still called from the {@link #setSelectionPaths(TreePath[])} and
1196       * {@link #addSelectionPaths(TreePath[])}.
1197       */
1198      protected void insureUniqueness()
1199      {
1200        // Following the API 1.4, the method should return without action.
1201      }
1202    }