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                     result = sortComparators[counter].compare(v1, v2);
952                 }
953                 if (sortOrder == SortOrder.DESCENDING) {
954                     result *= -1;
955                 }
956             }
957             if (result != 0) {
958                 return result;
959             }
960         }
961         // If we get here, they're equal. Fallback to model order.
962
return model1 - model2;
963     }
964
965     /**
966      * Whether not we are filtering/sorting.
967      */

968     private boolean isTransformed() {
969         return (viewToModel != null);
970     }
971
972     /**
973      * Insets new set of entries.
974      *
975      * @param toAdd the Rows to add, sorted
976      * @param current the array to insert the items into
977      */

978     private void insertInOrder(List JavaDoc<Row> toAdd, Row[] current) {
979         int last = 0;
980         int index;
981         int max = toAdd.size();
982         for (int i = 0; i < max; i++) {
983             index = Arrays.binarySearch(current, toAdd.get(i));
984             if (index < 0) {
985                 index = -1 - index;
986             }
987             System.arraycopy(current, last,
988                              viewToModel, last + i, index - last);
989             viewToModel[index + i] = toAdd.get(i);
990             last = index;
991         }
992         System.arraycopy(current, last, viewToModel, last + max,
993                          current.length - last);
994     }
995
996     /**
997      * Returns true if we should try and optimize the processing of the
998      * <code>TableModelEvent</code>. If this returns false, assume the
999      * event was dealt with and no further processing needs to happen.
1000     */

1001    private boolean shouldOptimizeChange(int firstRow, int lastRow) {
1002        if (!isTransformed()) {
1003            // Not transformed, nothing to do.
1004
return false;
1005        }
1006        if (!sorted || (lastRow - firstRow) > viewToModel.length / 10) {
1007            // We either weren't sorted, or to much changed, sort it all
1008
sort();
1009            return false;
1010        }
1011        return true;
1012    }
1013
1014    private void rowsInserted0(int firstRow, int lastRow) {
1015        int[] oldViewToModel = getViewToModelAsInts(viewToModel);
1016        int i;
1017        int delta = (lastRow - firstRow) + 1;
1018        List JavaDoc<Row> added = new ArrayList JavaDoc<Row>(delta);
1019
1020        // Build the list of Rows to add into added
1021
for (i = firstRow; i <= lastRow; i++) {
1022            if (include(i)) {
1023                added.add(new Row(this, i));
1024            }
1025        }
1026
1027        // Adjust the model index of rows after the effected region
1028
int viewIndex;
1029        for (i = modelToView.length - 1; i >= firstRow; i--) {
1030            viewIndex = modelToView[i];
1031            if (viewIndex != -1) {
1032                viewToModel[viewIndex].modelIndex += delta;
1033            }
1034        }
1035
1036        // Insert newly added rows into viewToModel
1037
if (added.size() > 0) {
1038            Collections.sort(added);
1039            Row[] lastViewToModel = viewToModel;
1040            viewToModel = new Row[viewToModel.length + added.size()];
1041            insertInOrder(added, lastViewToModel);
1042        }
1043
1044        // Update modelToView
1045
createModelToView(getModelWrapper().getRowCount());
1046        setModelToViewFromViewToModel(true);
1047
1048        // Notify of change
1049
fireRowSorterChanged(oldViewToModel);
1050    }
1051
1052    private void rowsDeleted0(int firstRow, int lastRow) {
1053        int[] oldViewToModel = getViewToModelAsInts(viewToModel);
1054        int removedFromView = 0;
1055        int i;
1056        int viewIndex;
1057
1058        // Figure out how many visible rows are going to be effected.
1059
for (i = firstRow; i <= lastRow; i++) {
1060            viewIndex = modelToView[i];
1061            if (viewIndex != -1) {
1062                removedFromView++;
1063                viewToModel[viewIndex] = null;
1064            }
1065        }
1066
1067        // Update the model index of rows after the effected region
1068
int delta = lastRow - firstRow + 1;
1069        for (i = modelToView.length - 1; i > lastRow; i--) {
1070            viewIndex = modelToView[i];
1071            if (viewIndex != -1) {
1072                viewToModel[viewIndex].modelIndex -= delta;
1073            }
1074        }
1075
1076        // Then patch up the viewToModel array
1077
if (removedFromView > 0) {
1078            Row[] newViewToModel = new Row[viewToModel.length -
1079                                           removedFromView];
1080            int newIndex = 0;
1081            int last = 0;
1082            for (i = 0; i < viewToModel.length; i++) {
1083                if (viewToModel[i] == null) {
1084                    System.arraycopy(viewToModel, last,
1085                                     newViewToModel, newIndex, i - last);
1086                    newIndex += (i - last);
1087                    last = i + 1;
1088                }
1089            }
1090            System.arraycopy(viewToModel, last,
1091                    newViewToModel, newIndex, viewToModel.length - last);
1092            viewToModel = newViewToModel;
1093        }
1094
1095        // Update the modelToView mapping
1096
createModelToView(getModelWrapper().getRowCount());
1097        setModelToViewFromViewToModel(true);
1098
1099        // And notify of change
1100
fireRowSorterChanged(oldViewToModel);
1101    }
1102
1103    private void rowsUpdated0(int firstRow, int lastRow) {
1104        int[] oldViewToModel = getViewToModelAsInts(viewToModel);
1105        int i, j;
1106        int delta = lastRow - firstRow + 1;
1107        int modelIndex;
1108        int last;
1109        int index;
1110
1111        if (getRowFilter() == null) {
1112            // Sorting only:
1113

1114            // Remove the effected rows
1115
Row[] updated = new Row[delta];
1116            for (j = 0, i = firstRow; i <= lastRow; i++, j++) {
1117                updated[j] = viewToModel[modelToView[i]];
1118            }
1119
1120            // Sort the update rows
1121
Arrays.sort(updated);
1122
1123            // Build the intermediary array: the array of
1124
// viewToModel without the effected rows.
1125
Row[] intermediary = new Row[viewToModel.length - delta];
1126            for (i = 0, j = 0; i < viewToModel.length; i++) {
1127                modelIndex = viewToModel[i].modelIndex;
1128                if (modelIndex < firstRow || modelIndex > lastRow) {
1129                    intermediary[j++] = viewToModel[i];
1130                }
1131            }
1132
1133            // Build the new viewToModel
1134
insertInOrder(Arrays.asList(updated), intermediary);
1135
1136            // Update modelToView
1137
setModelToViewFromViewToModel(false);
1138        }
1139        else {
1140            // Sorting & filtering.
1141

1142            // Remove the effected rows, adding them to updated and setting
1143
// modelToView to -2 for any rows that were not filtered out
1144
List JavaDoc<Row> updated = new ArrayList JavaDoc<Row>(delta);
1145            int newlyVisible = 0;
1146            int newlyHidden = 0;
1147            int effected = 0;
1148            for (i = firstRow; i <= lastRow; i++) {
1149                if (modelToView[i] == -1) {
1150                    // This row was filtered out
1151
if (include(i)) {
1152                        // No longer filtered
1153
updated.add(new Row(this, i));
1154                        newlyVisible++;
1155                    }
1156                }
1157                else {
1158                    // This row was visible, make sure it should still be
1159
// visible.
1160
if (!include(i)) {
1161                        newlyHidden++;
1162                    }
1163                    else {
1164                        updated.add(viewToModel[modelToView[i]]);
1165                    }
1166                    modelToView[i] = -2;
1167                    effected++;
1168                }
1169            }
1170
1171            // Sort the updated rows
1172
Collections.sort(updated);
1173
1174            // Build the intermediary array: the array of
1175
// viewToModel without the updated rows.
1176
Row[] intermediary = new Row[viewToModel.length - effected];
1177            for (i = 0, j = 0; i < viewToModel.length; i++) {
1178                modelIndex = viewToModel[i].modelIndex;
1179                if (modelToView[modelIndex] != -2) {
1180                    intermediary[j++] = viewToModel[i];
1181                }
1182            }
1183
1184            // Recreate viewToModel, if necessary
1185
if (newlyVisible != newlyHidden) {
1186                viewToModel = new Row[viewToModel.length + newlyVisible -
1187                                      newlyHidden];
1188            }
1189
1190            // Rebuild the new viewToModel array
1191
insertInOrder(updated, intermediary);
1192
1193            // Update modelToView
1194
setModelToViewFromViewToModel(true);
1195        }
1196        // And finally fire a sort event.
1197
fireRowSorterChanged(oldViewToModel);
1198    }
1199
1200    private void checkColumn(int column) {
1201        if (column < 0 || column >= getModelWrapper().getColumnCount()) {
1202            throw new IndexOutOfBoundsException JavaDoc(
1203                    "column beyond range of TableModel");
1204        }
1205    }
1206
1207
1208    /**
1209     * <code>DefaultRowSorter.ModelWrapper</code> is responsible for providing
1210     * the data that gets sorted by <code>DefaultRowSorter</code>. You
1211     * normally do not interact directly with <code>ModelWrapper</code>.
1212     * Subclasses of <code>DefaultRowSorter</code> provide an
1213     * implementation of <code>ModelWrapper</code> wrapping another model.
1214     * For example,
1215     * <code>TableRowSorter</code> provides a <code>ModelWrapper</code> that
1216     * wraps a <code>TableModel</code>.
1217     * <p>
1218     * <code>ModelWrapper</code> makes a distinction between values as
1219     * <code>Object</code>s and <code>String</code>s. This allows
1220     * implementations to provide a custom string
1221     * converter to be used instead of invoking <code>toString</code> on the
1222     * object.
1223     *
1224     * @param <M> the type of the underlying model
1225     * @param <I> the identifier supplied to the filter
1226     * @since 1.6
1227     * @see RowFilter
1228     * @see RowFilter.Entry
1229     */

1230    protected abstract static class ModelWrapper<M,I> {
1231        /**
1232         * Creates a new <code>ModelWrapper</code>.
1233         */

1234        protected ModelWrapper() {
1235        }
1236
1237        /**
1238         * Returns the underlying model that this <code>Model</code> is
1239         * wrapping.
1240         *
1241         * @return the underlying model
1242         */

1243        public abstract M getModel();
1244
1245        /**
1246         * Returns the number of columns in the model.
1247         *
1248         * @return the number of columns in the model
1249         */

1250        public abstract int getColumnCount();
1251
1252        /**
1253         * Returns the number of rows in the model.
1254         *
1255         * @return the number of rows in the model
1256         */

1257        public abstract int getRowCount();
1258
1259        /**
1260         * Returns the value at the specified index.
1261         *
1262         * @param row the row index
1263         * @param column the column index
1264         * @return the value at the specified index
1265         * @throws IndexOutOfBoundsException if the indices are outside
1266         * the range of the model
1267         */

1268        public abstract Object JavaDoc getValueAt(int row, int column);
1269
1270        /**
1271         * Returns the value as a <code>String</code> at the specified
1272         * index. This implementation uses <code>toString</code> on
1273         * the result from <code>getValueAt</code> (making sure
1274         * to return an empty string for null values). Subclasses that
1275         * override this method should never return null.
1276         *
1277         * @param row the row index
1278         * @param column the column index
1279         * @return the value at the specified index as a <code>String</code>
1280         * @throws IndexOutOfBoundsException if the indices are outside
1281         * the range of the model
1282         */

1283        public String JavaDoc getStringValueAt(int row, int column) {
1284            Object JavaDoc o = getValueAt(row, column);
1285            if (o == null) {
1286                return "";
1287            }
1288            String JavaDoc string = o.toString();
1289            if (string == null) {
1290                return "";
1291            }
1292            return string;
1293        }
1294
1295        /**
1296         * Returns the identifier for the specified row. The return value
1297         * of this is used as the identifier for the
1298         * <code>RowFilter.Entry</code> that is passed to the
1299         * <code>RowFilter</code>.
1300         *
1301         * @param row the row to return the identifier for, in terms of
1302         * the underlying model
1303         * @return the identifier
1304         * @see RowFilter.Entry#getIdentifier
1305         */

1306        public abstract I getIdentifier(int row);
1307    }
1308
1309
1310    /**
1311     * RowFilter.Entry implementation that delegates to the ModelWrapper.
1312     * getFilterEntry(int) creates the single instance of this that is
1313     * passed to the Filter. Only call getFilterEntry(int) to get
1314     * the instance.
1315     */

1316    private class FilterEntry extends RowFilter.Entry JavaDoc<M,I> {
1317        /**
1318         * The index into the model, set in getFilterEntry
1319         */

1320        int modelIndex;
1321
1322        public M getModel() {
1323            return getModelWrapper().getModel();
1324        }
1325
1326        public int getValueCount() {
1327            return getModelWrapper().getColumnCount();
1328        }
1329
1330        public Object JavaDoc getValue(int index) {
1331            return getModelWrapper().getValueAt(modelIndex, index);
1332        }
1333
1334        public String JavaDoc getStringValue(int index) {
1335            return getModelWrapper().getStringValueAt(modelIndex, index);
1336        }
1337
1338        public I getIdentifier() {
1339            return getModelWrapper().getIdentifier(modelIndex);
1340        }
1341    }
1342
1343
1344    /**
1345     * Row is used to handle the actual sorting by way of Comparable. It
1346     * will use the sortKeys to do the actual comparison.
1347     */

1348    // NOTE: this class is static so that it can be placed in an array
1349
private static class Row implements Comparable JavaDoc<Row> {
1350        private DefaultRowSorter JavaDoc sorter;
1351        int modelIndex;
1352
1353        public Row(DefaultRowSorter JavaDoc sorter, int index) {
1354            this.sorter = sorter;
1355            modelIndex = index;
1356        }
1357
1358        public int compareTo(Row o) {
1359            return sorter.compare(modelIndex, o.modelIndex);
1360        }
1361    }
1362}
1363
Popular Tags