KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > javax > swing > DefaultRowSorter


1 /*
2  * @(#)DefaultRowSorter.java 1.20 06/06/23
3  *
4  * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
5  * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
6  */

7 package javax.swing;
8
9 import java.text.Collator JavaDoc;
10 import java.util.ArrayList JavaDoc;
11 import java.util.Arrays JavaDoc;
12 import java.util.Collections JavaDoc;
13 import java.util.Comparator JavaDoc;
14 import java.util.List JavaDoc;
15 import javax.swing.SortOrder JavaDoc;
16
17 /**
18  * An implementation of <code>RowSorter</code> that provides sorting and
19  * filtering around a grid-based data model.
20  * Beyond creating and installing a <code>RowSorter</code>, you very rarely
21  * need to interact with one directly. Refer to
22  * {@link javax.swing.table.TableRowSorter TableRowSorter} for a concrete
23  * implementation of <code>RowSorter</code> for <code>JTable</code>.
24  * <p>
25  * Sorting is done based on the current <code>SortKey</code>s, in order.
26  * If two objects are equal (the <code>Comparator</code> for the
27  * column returns 0) the next <code>SortKey</code> is used. If no
28  * <code>SortKey</code>s remain or the order is <code>UNSORTED</code>, then
29  * the order of the rows in the model is used.
30  * <p>
31  * Sorting of each column is done by way of a <code>Comparator</code>
32  * that you can specify using the <code>setComparator</code> method.
33  * If a <code>Comparator</code> has not been specified, the
34  * <code>Comparator</code> returned by
35  * <code>Collator.getInstance()</code> is used on the results of
36  * calling <code>toString</code> on the underlying objects. The
37  * <code>Comparator</code> is never passed <code>null</code>. A
38  * <code>null</code> value is treated as occuring before a
39  * non-<code>null</code> value, and two <code>null</code> values are
40  * considered equal.
41  * <p>
42  * If you specify a <code>Comparator</code> that casts its argument to
43  * a type other than that provided by the model, a
44  * <code>ClassCastException</code> will be thrown when the data is sorted.
45  * <p>
46  * In addition to sorting, <code>DefaultRowSorter</code> provides the
47  * ability to filter rows. Filtering is done by way of a
48  * <code>RowFilter</code> that is specified using the
49  * <code>setRowFilter</code> method. If no filter has been specified all
50  * rows are included.
51  * <p>
52  * By default, rows are in unsorted order (the same as the model) and
53  * every column is sortable. The default <code>Comparator</code>s are
54  * documented in the subclasses (for example, {@link
55  * javax.swing.table.TableRowSorter TableRowSorter}).
56  * <p>
57  * If the underlying model structure changes (the
58  * <code>modelStructureChanged</code> method is invoked) the following
59  * are reset to their default values: <code>Comparator</code>s by
60  * column, current sort order, and whether each column is sortable. To
61  * find the default <code>Comparator</code>s, see the concrete
62  * implementation (for example, {@link
63  * javax.swing.table.TableRowSorter TableRowSorter}). The default
64  * sort order is unsorted (the same as the model), and columns are
65  * sortable by default.
66  * <p>
67  * If the underlying model structure changes (the
68  * <code>modelStructureChanged</code> method is invoked) the following
69  * are reset to their default values: <code>Comparator</code>s by column,
70  * current sort order and whether a column is sortable.
71  * <p>
72  * <code>DefaultRowSorter</code> is an abstract class. Concrete
73  * subclasses must provide access to the underlying data by invoking
74  * {@code setModelWrapper}. The {@code setModelWrapper} method
75  * <b>must</b> be invoked soon after the constructor is
76  * called, ideally from within the subclass's constructor.
77  * Undefined behavior will result if you use a {@code
78  * DefaultRowSorter} without specifying a {@code ModelWrapper}.
79  * <p>
80  * <code>DefaultRowSorter</code> has two formal type parameters. The
81  * first type parameter corresponds to the class of the model, for example
82  * <code>DefaultTableModel</code>. The second type parameter
83  * corresponds to the class of the identifier passed to the
84  * <code>RowFilter</code>. Refer to <code>TableRowSorter</code> and
85  * <code>RowFilter</code> for more details on the type parameters.
86  *
87  * @param <M> the type of the model
88  * @param <I> the type of the identifier passed to the <code>RowFilter</code>
89  * @version 1.20 06/23/06
90  * @see javax.swing.table.TableRowSorter
91  * @see javax.swing.table.DefaultTableModel
92  * @see java.text.Collator
93  * @since 1.6
94  */

95 public abstract class DefaultRowSorter<M, I> extends RowSorter JavaDoc<M> {
96     /**
97      * Whether or not we resort on TableModelEvent.UPDATEs.
98      */

99     private boolean sortsOnUpdates;
100
101     /**
102      * View (JTable) -> model.
103      */

104     private Row[] viewToModel;
105
106     /**
107      * model -> view (JTable)
108      */

109     private int[] modelToView;
110
111     /**
112      * Comparators specified by column.
113      */

114     private Comparator JavaDoc[] comparators;
115
116     /**
117      * Whether or not the specified column is sortable, by column.
118      */

119     private boolean[] isSortable;
120
121     /**
122      * Cached SortKeys for the current sort.
123      */

124     private SortKey[] cachedSortKeys;
125
126     /**
127      * Cached comparators for the current sort
128      */

129     private Comparator JavaDoc[] sortComparators;
130
131     /**
132      * Developer supplied Filter.
133      */

134     private RowFilter JavaDoc<? super M,? super I> filter;
135
136     /**
137      * Value passed to the filter. The same instance is passed to the
138      * filter for different rows.
139      */

140     private FilterEntry filterEntry;
141
142     /**
143      * The sort keys.
144      */

145     private List JavaDoc<SortKey> sortKeys;
146
147     /**
148      * Whether or not to use getStringValueAt. This is indexed by column.
149      */

150     private boolean[] useToString;
151
152     /**
153      * Indicates the contents are sorted. This is used if
154      * getSortsOnUpdates is false and an update event is received.
155      */

156     private boolean sorted;
157
158     /**
159      * Maximum number of sort keys.
160      */

161     private int maxSortKeys;
162
163     /**
164      * Provides access to the data we're sorting/filtering.
165      */

166     private ModelWrapper<M,I> modelWrapper;
167
168     /**
169      * Size of the model. This is used to enforce error checking within
170      * the table changed notification methods (such as rowsInserted).
171      */

172     private int modelRowCount;
173
174
175     /**
176      * Creates an empty <code>DefaultRowSorter</code>.
177      */

178     public DefaultRowSorter() {
179         sortKeys = Collections.emptyList();
180         maxSortKeys = 3;
181     }
182
183     /**
184      * Sets the model wrapper providing the data that is being sorted and
185      * filtered.
186      *
187      * @param modelWrapper the model wrapper responsible for providing the
188      * data that gets sorted and filtered
189      * @throws IllegalArgumentException if {@code modelWrapper} is
190      * {@code null}
191      */

192     protected final void setModelWrapper(ModelWrapper<M,I> modelWrapper) {
193         if (modelWrapper == null) {
194             throw new IllegalArgumentException JavaDoc(
195                 "modelWrapper most be non-null");
196         }
197         ModelWrapper<M,I> last = this.modelWrapper;
198         this.modelWrapper = modelWrapper;
199         if (last != null) {
200             modelStructureChanged();
201         } else {
202             // If last is null, we're in the constructor. If we're in
203
// the constructor we don't want to call to overridable methods.
204
modelRowCount = getModelWrapper().getRowCount();
205         }
206     }
207
208     /**
209      * Returns the model wrapper providing the data that is being sorted and
210      * filtered.
211      *
212      * @return the model wrapper responsible for providing the data that
213      * gets sorted and filtered
214      */

215     protected final ModelWrapper<M,I> getModelWrapper() {
216         return modelWrapper;
217     }
218
219     /**
220      * Returns the underlying model.
221      *
222      * @return the underlying model
223      */

224     public final M getModel() {
225         return getModelWrapper().getModel();
226     }
227
228     /**
229      * Sets whether or not the specified column is sortable. The specified
230      * value is only checked when <code>toggleSortOrder</code> is invoked.
231      * It is still possible to sort on a column that has been marked as
232      * unsortable by directly setting the sort keys. The default is
233      * true.
234      *
235      * @param column the column to enable or disable sorting on, in terms
236      * of the underlying model
237      * @param sortable whether or not the specified column is sortable
238      * @throws IndexOutOfBoundsException if <code>column</code> is outside
239      * the range of the model
240      * @see #toggleSortOrder
241      * @see #setSortKeys
242      */

243     public void setSortable(int column, boolean sortable) {
244         checkColumn(column);
245         if (isSortable == null) {
246             isSortable = new boolean[getModelWrapper().getColumnCount()];
247             for (int i = isSortable.length - 1; i >= 0; i--) {
248                 isSortable[i] = true;
249             }
250         }
251         isSortable[column] = sortable;
252     }
253
254     /**
255      * Returns true if the specified column is sortable; otherwise, false.
256      *
257      * @param column the column to check sorting for, in terms of the
258      * underlying model
259      * @return true if the column is sortable
260      * @throws IndexOutOfBoundsException if column is outside
261      * the range of the underlying model
262      */

263     public boolean isSortable(int column) {
264         checkColumn(column);
265         return (isSortable == null) ? true : isSortable[column];
266     }
267
268     /**
269      * Sets the sort keys. This creates a copy of the supplied
270      * {@code List}; subsequent changes to the supplied
271      * {@code List} do not effect this {@code DefaultRowSorter}.
272      * If the sort keys have changed this triggers a sort.
273      *
274      * @param sortKeys the new <code>SortKeys</code>; <code>null</code>
275      * is a shorthand for specifying an empty list,
276      * indicating that the view should be unsorted
277      * @throws IllegalArgumentException if any of the values in
278      * <code>sortKeys</code> are null or have a column index outside
279      * the range of the model
280      */

281     public void setSortKeys(List JavaDoc<? extends SortKey> sortKeys) {
282         List JavaDoc<SortKey> old = this.sortKeys;
283         if (sortKeys != null && sortKeys.size() > 0) {
284             int max = getModelWrapper().getColumnCount();
285             for (SortKey key : sortKeys) {
286                 if (key == null || key.getColumn() < 0 ||
287                         key.getColumn() >= max) {
288                     throw new IllegalArgumentException JavaDoc("Invalid SortKey");
289                 }
290             }
291             this.sortKeys = Collections.unmodifiableList(
292                     new ArrayList JavaDoc<SortKey>(sortKeys));
293         }
294         else {
295             this.sortKeys = Collections.emptyList();
296         }
297         if (!this.sortKeys.equals(old)) {
298             fireSortOrderChanged();
299             if (viewToModel == null) {
300                 // Currently unsorted, use sort so that internal fields
301
// are correctly set.
302
sort();
303             } else {
304                 sortExistingData();
305             }
306         }
307     }
308
309     /**
310      * Returns the current sort keys. This returns an unmodifiable
311      * {@code non-null List}. If you need to change the sort keys,
312      * make a copy of the returned {@code List}, mutate the copy
313      * and invoke {@code setSortKeys} with the new list.
314      *
315      * @return the current sort order
316      */

317     public List JavaDoc<? extends SortKey> getSortKeys() {
318         return sortKeys;
319     }
320
321     /**
322      * Sets the maximum number of sort keys. The number of sort keys
323      * determines how equal values are resolved when sorting. For
324      * example, assume a table row sorter is created and
325      * <code>setMaxSortKeys(2)</code> is invoked on it. The user
326      * clicks the header for column 1, causing the table rows to be
327      * sorted based on the items in column 1. Next, the user clicks
328      * the header for column 2, causing the table to be sorted based
329      * on the items in column 2; if any items in column 2 are equal,
330      * then those particular rows are ordered based on the items in
331      * column 1. In this case, we say that the rows are primarily
332      * sorted on column 2, and secondarily on column 1. If the user
333      * then clicks the header for column 3, then the items are
334      * primarily sorted on column 3 and secondarily sorted on column
335      * 2. Because the maximum number of sort keys has been set to 2
336      * with <code>setMaxSortKeys</code>, column 1 no longer has an
337      * effect on the order.
338      * <p>
339      * The maximum number of sort keys is enforced by
340      * <code>toggleSortOrder</code>. You can specify more sort
341      * keys by invoking <code>setSortKeys</code> directly and they will
342      * all be honored. However if <code>toggleSortOrder</code> is subsequently
343      * invoked the maximum number of sort keys will be enforced.
344      * The default value is 3.
345      *
346      * @param max the maximum number of sort keys
347      * @throws IllegalArgumentException if <code>max</code> &lt; 1
348      */

349     public void setMaxSortKeys(int max) {
350         if (max < 1) {
351             throw new IllegalArgumentException JavaDoc("Invalid max");
352         }
353         maxSortKeys = max;
354     }
355
356     /**
357      * Returns the maximum number of sort keys.
358      *
359      * @return the maximum number of sort keys
360      */

361     public int getMaxSortKeys() {
362         return maxSortKeys;
363     }
364
365     /**
366      * If true, specifies that a sort should happen when the underlying
367      * model is updated (<code>rowsUpdated</code> is invoked). For
368      * example, if this is true and the user edits an entry the
369      * location of that item in the view may change. The default is
370      * false.
371      *
372      * @param sortsOnUpdates whether or not to sort on update events
373      */

374     public void setSortsOnUpdates(boolean sortsOnUpdates) {
375         this.sortsOnUpdates = sortsOnUpdates;
376     }
377
378     /**
379      * Returns true if a sort should happen when the underlying
380      * model is updated; otherwise, returns false.
381      *
382      * @return whether or not to sort when the model is updated
383      */

384     public boolean getSortsOnUpdates() {
385         return sortsOnUpdates;
386     }
387
388     /**
389      * Sets the filter that determines which rows, if any, should be
390      * hidden from the view. The filter is applied before sorting. A value
391      * of <code>null</code> indicates all values from the model should be
392      * included.
393      * <p>
394      * <code>RowFilter</code>'s <code>include</code> method is passed an
395      * <code>Entry</code> that wraps the underlying model. The number
396      * of columns in the <code>Entry</code> corresponds to the
397      * number of columns in the <code>ModelWrapper</code>. The identifier
398      * comes from the <code>ModelWrapper</code> as well.
399      * <p>
400      * This method triggers a sort.
401      *
402      * @param filter the filter used to determine what entries should be
403      * included
404      */

405     public void setRowFilter(RowFilter JavaDoc<? super M,? super I> filter) {
406         this.filter = filter;
407         sort();
408     }
409
410     /**
411      * Returns the filter that determines which rows, if any, should
412      * be hidden from view.
413      *
414      * @return the filter
415      */

416     public RowFilter JavaDoc<? super M,? super I> getRowFilter() {
417         return filter;
418     }
419
420     /**
421      * Reverses the sort order from ascending to descending (or
422      * descending to ascending) if the specified column is already the
423      * primary sorted column; otherwise, makes the specified column
424      * the primary sorted column, with an ascending sort order. If
425      * the specified column is not sortable, this method has no
426      * effect.
427      *
428      * @param column index of the column to make the primary sorted column,
429      * in terms of the underlying model
430      * @throws IndexOutOfBoundsException {@inheritDoc}
431      * @see #setSortable(int,boolean)
432      * @see #setMaxSortKeys(int)
433      */

434     public void toggleSortOrder(int column) {
435         checkColumn(column);
436         if (isSortable(column)) {
437             List JavaDoc<SortKey> keys = new ArrayList JavaDoc<SortKey>(getSortKeys());
438             SortKey sortKey;
439             int sortIndex;
440             for (sortIndex = keys.size() - 1; sortIndex >= 0; sortIndex--) {
441                 if (keys.get(sortIndex).getColumn() == column) {
442                     break;
443                 }
444             }
445             if (sortIndex == -1) {
446                 // Key doesn't exist
447
sortKey = new SortKey(column, SortOrder.ASCENDING);
448                 keys.add(0, sortKey);
449             }
450             else if (sortIndex == 0) {
451                 // It's the primary sorting key, toggle it
452
keys.set(0, toggle(keys.get(0)));
453             }
454             else {
455                 // It's not the first, but was sorted on, remove old
456
// entry, insert as first with ascending.
457
keys.remove(sortIndex);
458                 keys.add(0, new SortKey(column, SortOrder.ASCENDING));
459             }
460             if (keys.size() > getMaxSortKeys()) {
461                 keys = keys.subList(0, getMaxSortKeys());
462             }
463             setSortKeys(keys);
464         }
465     }
466
467     private SortKey toggle(SortKey key) {
468         if (key.getSortOrder() == SortOrder.ASCENDING) {
469             return new SortKey(key.getColumn(), SortOrder.DESCENDING);
470         }
471         return new SortKey(key.getColumn(), SortOrder.ASCENDING);
472     }
473
474     /**
475      * {@inheritDoc}
476      *
477      * @throws IndexOutOfBoundsException {@inheritDoc}
478      */

479     public int convertRowIndexToView(int index) {
480         if (modelToView == null) {
481             if (index < 0 || index >= getModelWrapper().getRowCount()) {
482                 throw new IndexOutOfBoundsException JavaDoc("Invalid index");
483             }
484             return index;
485         }
486         return modelToView[index];
487     }
488
489     /**
490      * {@inheritDoc}
491      *
492      * @throws IndexOutOfBoundsException {@inheritDoc}
493      */

494     public int convertRowIndexToModel(int index) {
495         if (viewToModel == null) {
496             if (index < 0 || index >= getModelWrapper().getRowCount()) {
497                 throw new IndexOutOfBoundsException JavaDoc("Invalid index");
498             }
499             return index;
500         }
501         return viewToModel[index].modelIndex;
502     }
503
504     private boolean isUnsorted() {
505         List JavaDoc<? extends SortKey> keys = getSortKeys();
506         int keySize = keys.size();
507         return (keySize == 0 || keys.get(0).getSortOrder() ==
508                 SortOrder.UNSORTED);
509     }
510
511     /**
512      * Sorts the existing filtered data. This should only be used if
513      * the filter hasn't changed.
514      */

515     private void sortExistingData() {
516         int[] lastViewToModel = getViewToModelAsInts(viewToModel);
517
518         updateUseToString();
519         cacheSortKeys(getSortKeys());
520
521         if (isUnsorted()) {
522             if (getRowFilter() == null) {
523                 viewToModel = null;
524                 modelToView = null;
525             } else {
526                 int included = 0;
527                 for (int i = 0; i < modelToView.length; i++) {
528                     if (modelToView[i] != -1) {
529                         viewToModel[included].modelIndex = i;
530                         modelToView[i] = included++;
531                     }
532                 }
533             }
534         } else {
535             // sort the data
536
Arrays.sort(viewToModel);
537
538             // Update the modelToView array
539
setModelToViewFromViewToModel(false);
540         }
541         fireRowSorterChanged(lastViewToModel);
542     }
543
544     /**
545      * Sorts and filters the rows in the view based on the sort keys
546      * of the columns currently being sorted and the filter, if any,
547      * associated with this sorter. An empty <code>sortKeys</code> list
548      * indicates that the view should unsorted, the same as the model.
549      *
550      * @see #setRowFilter
551      * @see #setSortKeys
552      */

553     public void sort() {
554         sorted = true;
555         int[] lastViewToModel = getViewToModelAsInts(viewToModel);
556         updateUseToString();
557         if (isUnsorted()) {
558             // Unsorted
559
cachedSortKeys = new SortKey[0];
560             if (getRowFilter() == null) {
561                 // No filter & unsorted
562
if (viewToModel != null) {
563                     // sorted -> unsorted
564
viewToModel = null;
565                     modelToView = null;
566                 }
567                 else {
568                     // unsorted -> unsorted
569
// No need to do anything.
570
return;
571                 }
572             }
573             else {
574                 // There is filter, reset mappings
575
initializeFilteredMapping();
576             }
577         }
578         else {
579             cacheSortKeys(getSortKeys());
580
581             if (getRowFilter() != null) {
582                 initializeFilteredMapping();
583             }
584             else {
585                 createModelToView(getModelWrapper().getRowCount());
586                 createViewToModel(getModelWrapper().getRowCount());
587             }
588
589             // sort them
590
Arrays.sort(viewToModel);
591
592             // Update the modelToView array
593
setModelToViewFromViewToModel(false);
594         }
595         fireRowSorterChanged(lastViewToModel);
596     }
597
598     /**
599      * Updates the useToString mapping before a sort.
600      */

601     private void updateUseToString() {
602         int i = getModelWrapper().getColumnCount();
603         if (useToString == null || useToString.length != i) {
604             useToString = new boolean[i];
605         }
606         for (--i; i >= 0; i--) {
607             useToString[i] = useToString(i);
608         }
609     }
610
611     /**
612      * Resets the viewToModel and modelToView mappings based on
613      * the current Filter.
614      */

615     private void initializeFilteredMapping() {
616         int rowCount = getModelWrapper().getRowCount();
617         int i, j;
618         int excludedCount = 0;
619
620         // Update model -> view
621
createModelToView(rowCount);
622         for (i = 0; i < rowCount; i++) {
623             if (include(i)) {
624                 modelToView[i] = i - excludedCount;
625             }
626             else {
627                 modelToView[i] = -1;
628                 excludedCount++;
629             }
630         }
631
632         // Update view -> model
633
createViewToModel(rowCount - excludedCount);
634         for (i = 0, j = 0; i < rowCount; i++) {
635             if (modelToView[i] != -1) {
636                 viewToModel[j++].modelIndex = i;
637             }
638         }
639     }
640
641     /**
642      * Makes sure the modelToView array is of size rowCount.
643      */

644     private void createModelToView(int rowCount) {
645         if (modelToView == null || modelToView.length != rowCount) {
646             modelToView = new int[rowCount];
647         }
648     }
649
650     /**
651      * Resets the viewToModel array to be of size rowCount.
652      */

653     private void createViewToModel(int rowCount) {
654         int recreateFrom = 0;
655         if (viewToModel != null) {
656             recreateFrom = Math.min(rowCount, viewToModel.length);
657             if (viewToModel.length != rowCount) {
658                 Row[] oldViewToModel = viewToModel;
659                 viewToModel = new Row[rowCount];
660                 System.arraycopy(oldViewToModel, 0, viewToModel,
661                                  0, recreateFrom);
662             }
663         }
664         else {
665             viewToModel = new Row[rowCount];
666         }
667         int i;
668         for (i = 0; i < recreateFrom; i++) {
669             viewToModel[i].modelIndex = i;
670         }
671         for (i = recreateFrom; i < rowCount; i++) {
672             viewToModel[i] = new Row(this, i);
673         }
674     }
675
676     /**
677      * Caches the sort keys before a sort.
678      */

679     private void cacheSortKeys(List JavaDoc<? extends SortKey> keys) {
680         int keySize = keys.size();
681         sortComparators = new Comparator JavaDoc[keySize];
682         for (int i = 0; i < keySize; i++) {
683             sortComparators[i] = getComparator0(keys.get(i).getColumn());
684         }
685         cachedSortKeys = keys.toArray(new SortKey[keySize]);
686     }
687
688     /**
689      * Returns whether or not to convert the value to a string before
690      * doing comparisons when sorting. If true
691      * <code>ModelWrapper.getStringValueAt</code> will be used, otherwise
692      * <code>ModelWrapper.getValueAt</code> will be used. It is up to
693      * subclasses, such as <code>TableRowSorter</code>, to honor this value
694      * in their <code>ModelWrapper</code> implementation.
695      *
696      * @param column the index of the column to test, in terms of the
697      * underlying model
698      * @throws IndexOutOfBoundsException if <code>column</code> is not valid
699      */

700     protected boolean useToString(int column) {
701         return (getComparator(column) == null);
702     }
703
704     /**
705      * Refreshes the modelToView mapping from that of viewToModel.
706      * If <code>unsetFirst</code> is true, all indices in modelToView are
707      * first set to -1.
708      */

709     private void setModelToViewFromViewToModel(boolean unsetFirst) {
710         int i;
711         if (unsetFirst) {
712             for (i = modelToView.length - 1; i >= 0; i--) {
713                 modelToView[i] = -1;
714             }
715         }
716         for (i = viewToModel.length - 1; i >= 0; i--) {
717             modelToView[viewToModel[i].modelIndex] = i;
718         }
719     }
720
721     private int[] getViewToModelAsInts(Row[] viewToModel) {
722         if (viewToModel != null) {
723             int[] viewToModelI = new int[viewToModel.length];
724             for (int i = viewToModel.length - 1; i >= 0; i--) {
725                 viewToModelI[i] = viewToModel[i].modelIndex;
726             }
727             return viewToModelI;
728         }
729         return new int[0];
730     }
731
732     /**
733      * Sets the <code>Comparator</code> to use when sorting the specified
734      * column. This does not trigger a sort. If you want to sort after
735      * setting the comparator you need to explicitly invoke <code>sort</code>.
736      *
737      * @param column the index of the column the <code>Comparator</code> is
738      * to be used for, in terms of the underlying model
739      * @param comparator the <code>Comparator</code> to use
740      * @throws IndexOutOfBoundsException if <code>column</code> is outside
741      * the range of the underlying model
742      */

743     public void setComparator(int column, Comparator JavaDoc<?> comparator) {
744         checkColumn(column);
745         if (comparators == null) {
746             comparators = new Comparator JavaDoc[getModelWrapper().getColumnCount()];
747         }
748         comparators[column] = comparator;
749     }
750
751     /**
752      * Returns the <code>Comparator</code> for the specified
753      * column. This will return <code>null</code> if a <code>Comparator</code>
754      * has not been specified for the column.
755      *
756      * @param column the column to fetch the <code>Comparator</code> for, in
757      * terms of the underlying model
758      * @return the <code>Comparator</code> for the specified column
759      * @throws IndexOutOfBoundsException if column is outside
760      * the range of the underlying model
761      */

762     public Comparator JavaDoc<?> getComparator(int column) {
763         checkColumn(column);
764         if (comparators != null) {
765             return comparators[column];
766         }
767         return null;
768     }
769
770     // Returns the Comparator to use during sorting. Where as
771
// getComparator() may return null, this will never return null.
772
private Comparator JavaDoc getComparator0(int column) {
773         Comparator JavaDoc comparator = getComparator(column);
774         if (comparator != null) {
775             return comparator;
776         }
777         // This should be ok as useToString(column) should have returned
778
// true in this case.
779
return Collator.getInstance();
780     }
781
782     private RowFilter.Entry JavaDoc<M,I> getFilterEntry(int modelIndex) {
783         if (filterEntry == null) {
784             filterEntry = new FilterEntry();
785         }
786         filterEntry.modelIndex = modelIndex;
787         return filterEntry;
788     }
789
790     /**
791      * {@inheritDoc}
792      */

793     public int getViewRowCount() {
794         if (viewToModel != null) {
795             // When filtering this may differ from getModelWrapper().getRowCount()
796
return viewToModel.length;
797         }
798         return getModelWrapper().getRowCount();
799     }
800
801     /**
802      * {@inheritDoc}
803      */

804     public int getModelRowCount() {
805         return getModelWrapper().getRowCount();
806     }
807
808     private void allChanged() {
809         modelToView = null;
810         viewToModel = null;
811         comparators = null;
812         isSortable = null;
813         if (isUnsorted()) {
814             // Keys are already empty, to force a resort we have to
815
// call sort
816
sort();
817         } else {
818             setSortKeys(null);
819         }
820     }
821
822     /**
823      * {@inheritDoc}
824      */

825     public void modelStructureChanged() {
826         allChanged();
827         modelRowCount = getModelWrapper().getRowCount();
828     }
829
830     /**
831      * {@inheritDoc}
832      */

833     public void allRowsChanged() {
834         modelRowCount = getModelWrapper().getRowCount();
835         sort();
836     }
837
838     /**
839      * {@inheritDoc}
840      *
841      * @throws IndexOutOfBoundsException {@inheritDoc}
842      */

843     public void rowsInserted(int firstRow, int endRow) {
844         checkAgainstModel(firstRow, endRow);
845         int newModelRowCount = getModelWrapper().getRowCount();
846         if (endRow >= newModelRowCount) {
847             throw new IndexOutOfBoundsException JavaDoc("Invalid range");
848         }
849         modelRowCount = newModelRowCount;
850         if (shouldOptimizeChange(firstRow, endRow)) {
851             rowsInserted0(firstRow, endRow);
852         }
853     }
854
855     /**
856      * {@inheritDoc}
857      *
858      * @throws IndexOutOfBoundsException {@inheritDoc}
859      */

860     public void rowsDeleted(int firstRow, int endRow) {
861         checkAgainstModel(firstRow, endRow);
862         if (firstRow >= modelRowCount || endRow >= modelRowCount) {
863             throw new IndexOutOfBoundsException JavaDoc("Invalid range");
864         }
865         modelRowCount = getModelWrapper().getRowCount();
866         if (shouldOptimizeChange(firstRow, endRow)) {
867             rowsDeleted0(firstRow, endRow);
868         }
869     }
870
871     /**
872      * {@inheritDoc}
873      *
874      * @throws IndexOutOfBoundsException {@inheritDoc}
875      */

876     public void rowsUpdated(int firstRow, int endRow) {
877         checkAgainstModel(firstRow, endRow);
878         if (firstRow >= modelRowCount || endRow >= modelRowCount) {
879             throw new IndexOutOfBoundsException JavaDoc("Invalid range");
880         }
881         if (getSortsOnUpdates()) {
882             if (shouldOptimizeChange(firstRow, endRow)) {
883                 rowsUpdated0(firstRow, endRow);
884             }
885         }
886         else {
887             sorted = false;
888         }
889     }
890
891     /**
892      * {@inheritDoc}
893      *
894      * @throws IndexOutOfBoundsException {@inheritDoc}
895      */

896     public void rowsUpdated(int firstRow, int endRow, int column) {
897         checkColumn(column);
898         rowsUpdated(firstRow, endRow);
899     }
900
901     private void checkAgainstModel(int firstRow, int endRow) {
902         if (firstRow > endRow || firstRow < 0 || endRow < 0 ||
903                 firstRow > modelRowCount) {
904             throw new IndexOutOfBoundsException JavaDoc("Invalid range");
905         }
906     }
907
908     /**
909      * Returns true if the specified row should be included.
910      */

911     private boolean include(int row) {
912         RowFilter JavaDoc<? super M, ? super I> filter = getRowFilter();
913         if (filter != null) {
914             return filter.include(getFilterEntry(row));
915         }
916         // null filter, always include the row.
917
return true;
918     }
919
920     @SuppressWarnings JavaDoc("unchecked")
921     private int compare(int model1, int model2) {
922         int column;
923         SortOrder JavaDoc sortOrder;
924         Object JavaDoc v1, v2;
925         int result;
926             
927         for (int counter = 0; counter < cachedSortKeys.length; counter++) {
928             column = cachedSortKeys[counter].getColumn();
929             sortOrder = cachedSortKeys[counter].getSortOrder();
930             if (sortOrder == SortOrder.UNSORTED) {
931                 result = model1 - model2;
932             } else {
933                 // v1 != null && v2 != null
934
if (useToString[column]) {
935                     v1 = getModelWrapper().getStringValueAt(model1, column);
936                     v2 = getModelWrapper().getStringValueAt(model2, column);
937                 } else {
938                     v1 = getModelWrapper().getValueAt(model1, column);
939                     v2 = getModelWrapper().getValueAt(model2, column);
940                 }
941                 // Treat nulls as < then non-null
942
if (v1 == null) {
943                     if (v2 == null) {
944                         result = 0;
945                     } else {
946                         result = -1;
947                     }
948                 } else if (v2 == null) {
949                     result = 1;
950                 } else {
951                 &nb