1 /*
2 * Copyright (c) 2005, 2016, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25 package javax.swing;
26
27 import java.text.Collator;
28 import java.util.ArrayList;
29 import java.util.Arrays;
30 import java.util.Collections;
31 import java.util.Comparator;
32 import java.util.List;
33
34 /**
35 * An implementation of <code>RowSorter</code> that provides sorting and
36 * filtering around a grid-based data model.
37 * Beyond creating and installing a <code>RowSorter</code>, you very rarely
38 * need to interact with one directly. Refer to
39 * {@link javax.swing.table.TableRowSorter TableRowSorter} for a concrete
40 * implementation of <code>RowSorter</code> for <code>JTable</code>.
41 * <p>
42 * Sorting is done based on the current <code>SortKey</code>s, in order.
43 * If two objects are equal (the <code>Comparator</code> for the
44 * column returns 0) the next <code>SortKey</code> is used. If no
45 * <code>SortKey</code>s remain or the order is <code>UNSORTED</code>, then
46 * the order of the rows in the model is used.
47 * <p>
48 * Sorting of each column is done by way of a <code>Comparator</code>
49 * that you can specify using the <code>setComparator</code> method.
50 * If a <code>Comparator</code> has not been specified, the
51 * <code>Comparator</code> returned by
52 * <code>Collator.getInstance()</code> is used on the results of
53 * calling <code>toString</code> on the underlying objects. The
54 * <code>Comparator</code> is never passed <code>null</code>. A
55 * <code>null</code> value is treated as occurring before a
56 * non-<code>null</code> value, and two <code>null</code> values are
57 * considered equal.
58 * <p>
59 * If you specify a <code>Comparator</code> that casts its argument to
60 * a type other than that provided by the model, a
61 * <code>ClassCastException</code> will be thrown when the data is sorted.
62 * <p>
63 * In addition to sorting, <code>DefaultRowSorter</code> provides the
64 * ability to filter rows. Filtering is done by way of a
65 * <code>RowFilter</code> that is specified using the
66 * <code>setRowFilter</code> method. If no filter has been specified all
67 * rows are included.
68 * <p>
69 * By default, rows are in unsorted order (the same as the model) and
70 * every column is sortable. The default <code>Comparator</code>s are
71 * documented in the subclasses (for example, {@link
72 * javax.swing.table.TableRowSorter TableRowSorter}).
73 * <p>
74 * If the underlying model structure changes (the
75 * <code>modelStructureChanged</code> method is invoked) the following
76 * are reset to their default values: <code>Comparator</code>s by
77 * column, current sort order, and whether each column is sortable. To
78 * find the default <code>Comparator</code>s, see the concrete
79 * implementation (for example, {@link
80 * javax.swing.table.TableRowSorter TableRowSorter}). The default
81 * sort order is unsorted (the same as the model), and columns are
82 * sortable by default.
83 * <p>
84 * If the underlying model structure changes (the
85 * <code>modelStructureChanged</code> method is invoked) the following
86 * are reset to their default values: <code>Comparator</code>s by column,
87 * current sort order and whether a column is sortable.
88 * <p>
89 * <code>DefaultRowSorter</code> is an abstract class. Concrete
90 * subclasses must provide access to the underlying data by invoking
91 * {@code setModelWrapper}. The {@code setModelWrapper} method
92 * <b>must</b> be invoked soon after the constructor is
93 * called, ideally from within the subclass's constructor.
94 * Undefined behavior will result if you use a {@code
95 * DefaultRowSorter} without specifying a {@code ModelWrapper}.
96 * <p>
97 * <code>DefaultRowSorter</code> has two formal type parameters. The
98 * first type parameter corresponds to the class of the model, for example
99 * <code>DefaultTableModel</code>. The second type parameter
100 * corresponds to the class of the identifier passed to the
101 * <code>RowFilter</code>. Refer to <code>TableRowSorter</code> and
102 * <code>RowFilter</code> for more details on the type parameters.
103 *
104 * @param <M> the type of the model
105 * @param <I> the type of the identifier passed to the <code>RowFilter</code>
106 * @see javax.swing.table.TableRowSorter
107 * @see javax.swing.table.DefaultTableModel
108 * @see java.text.Collator
109 * @since 1.6
110 */
111 public abstract class DefaultRowSorter<M, I> extends RowSorter<M> {
112 /**
113 * Whether or not we resort on TableModelEvent.UPDATEs.
114 */
115 private boolean sortsOnUpdates;
116
117 /**
118 * View (JTable) -> model.
119 */
120 private Row[] viewToModel;
121
122 /**
123 * model -> view (JTable)
124 */
125 private int[] modelToView;
126
127 /**
128 * Comparators specified by column.
129 */
130 private Comparator<?>[] comparators;
131
132 /**
133 * Whether or not the specified column is sortable, by column.
134 */
135 private boolean[] isSortable;
136
137 /**
138 * Cached SortKeys for the current sort.
139 */
140 private SortKey[] cachedSortKeys;
141
142 /**
143 * Cached comparators for the current sort
144 */
145 private Comparator<?>[] sortComparators;
146
147 /**
148 * Developer supplied Filter.
149 */
150 private RowFilter<? super M,? super I> filter;
151
152 /**
153 * Value passed to the filter. The same instance is passed to the
154 * filter for different rows.
155 */
156 private FilterEntry filterEntry;
157
158 /**
159 * The sort keys.
160 */
161 private List<SortKey> sortKeys;
162
163 /**
164 * Whether or not to use getStringValueAt. This is indexed by column.
165 */
166 private boolean[] useToString;
167
168 /**
169 * Indicates the contents are sorted. This is used if
170 * getSortsOnUpdates is false and an update event is received.
171 */
172 private boolean sorted;
173
174 /**
175 * Maximum number of sort keys.
176 */
177 private int maxSortKeys;
178
179 /**
180 * Provides access to the data we're sorting/filtering.
181 */
182 private ModelWrapper<M,I> modelWrapper;
183
184 /**
185 * Size of the model. This is used to enforce error checking within
186 * the table changed notification methods (such as rowsInserted).
187 */
188 private int modelRowCount;
189
190
191 /**
192 * Creates an empty <code>DefaultRowSorter</code>.
193 */
194 public DefaultRowSorter() {
195 sortKeys = Collections.emptyList();
196 maxSortKeys = 3;
197 }
198
199 /**
200 * Sets the model wrapper providing the data that is being sorted and
201 * filtered.
202 *
203 * @param modelWrapper the model wrapper responsible for providing the
204 * data that gets sorted and filtered
205 * @throws IllegalArgumentException if {@code modelWrapper} is
206 * {@code null}
207 */
208 protected final void setModelWrapper(ModelWrapper<M,I> modelWrapper) {
209 if (modelWrapper == null) {
210 throw new IllegalArgumentException(
211 "modelWrapper most be non-null");
212 }
213 ModelWrapper<M,I> last = this.modelWrapper;
214 this.modelWrapper = modelWrapper;
215 if (last != null) {
216 modelStructureChanged();
217 } else {
218 // If last is null, we're in the constructor. If we're in
219 // the constructor we don't want to call to overridable methods.
220 modelRowCount = getModelWrapper().getRowCount();
221 }
222 }
223
224 /**
225 * Returns the model wrapper providing the data that is being sorted and
226 * filtered.
227 *
228 * @return the model wrapper responsible for providing the data that
229 * gets sorted and filtered
230 */
231 protected final ModelWrapper<M,I> getModelWrapper() {
232 return modelWrapper;
233 }
234
235 /**
236 * Returns the underlying model.
237 *
238 * @return the underlying model
239 */
240 public final M getModel() {
241 return getModelWrapper().getModel();
242 }
243
244 /**
245 * Sets whether or not the specified column is sortable. The specified
246 * value is only checked when <code>toggleSortOrder</code> is invoked.
247 * It is still possible to sort on a column that has been marked as
248 * unsortable by directly setting the sort keys. The default is
249 * true.
250 *
251 * @param column the column to enable or disable sorting on, in terms
252 * of the underlying model
253 * @param sortable whether or not the specified column is sortable
254 * @throws IndexOutOfBoundsException if <code>column</code> is outside
255 * the range of the model
256 * @see #toggleSortOrder
257 * @see #setSortKeys
258 */
259 public void setSortable(int column, boolean sortable) {
260 checkColumn(column);
261 if (isSortable == null) {
262 isSortable = new boolean[getModelWrapper().getColumnCount()];
263 for (int i = isSortable.length - 1; i >= 0; i--) {
264 isSortable[i] = true;
265 }
266 }
267 isSortable[column] = sortable;
268 }
269
270 /**
271 * Returns true if the specified column is sortable; otherwise, false.
272 *
273 * @param column the column to check sorting for, in terms of the
274 * underlying model
275 * @return true if the column is sortable
276 * @throws IndexOutOfBoundsException if column is outside
277 * the range of the underlying model
278 */
279 public boolean isSortable(int column) {
280 checkColumn(column);
281 return (isSortable == null) ? true : isSortable[column];
282 }
283
284 /**
285 * Sets the sort keys. This creates a copy of the supplied
286 * {@code List}; subsequent changes to the supplied
287 * {@code List} do not effect this {@code DefaultRowSorter}.
288 * If the sort keys have changed this triggers a sort.
289 *
290 * @param sortKeys the new <code>SortKeys</code>; <code>null</code>
291 * is a shorthand for specifying an empty list,
292 * indicating that the view should be unsorted
293 * @throws IllegalArgumentException if any of the values in
294 * <code>sortKeys</code> are null or have a column index outside
295 * the range of the model
296 */
297 public void setSortKeys(List<? extends SortKey> sortKeys) {
298 List<SortKey> old = this.sortKeys;
299 if (sortKeys != null && sortKeys.size() > 0) {
300 int max = getModelWrapper().getColumnCount();
301 for (SortKey key : sortKeys) {
302 if (key == null || key.getColumn() < 0 ||
303 key.getColumn() >= max) {
304 throw new IllegalArgumentException("Invalid SortKey");
305 }
306 }
307 this.sortKeys = Collections.unmodifiableList(
308 new ArrayList<SortKey>(sortKeys));
309 }
310 else {
311 this.sortKeys = Collections.emptyList();
312 }
313 if (!this.sortKeys.equals(old)) {
314 fireSortOrderChanged();
315 if (viewToModel == null) {
316 // Currently unsorted, use sort so that internal fields
317 // are correctly set.
318 sort();
319 } else {
320 sortExistingData();
321 }
322 }
323 }
324
325 /**
326 * Returns the current sort keys. This returns an unmodifiable
327 * {@code non-null List}. If you need to change the sort keys,
328 * make a copy of the returned {@code List}, mutate the copy
329 * and invoke {@code setSortKeys} with the new list.
330 *
331 * @return the current sort order
332 */
333 public List<? extends SortKey> getSortKeys() {
334 return sortKeys;
335 }
336
337 /**
338 * Sets the maximum number of sort keys. The number of sort keys
339 * determines how equal values are resolved when sorting. For
340 * example, assume a table row sorter is created and
341 * <code>setMaxSortKeys(2)</code> is invoked on it. The user
342 * clicks the header for column 1, causing the table rows to be
343 * sorted based on the items in column 1. Next, the user clicks
344 * the header for column 2, causing the table to be sorted based
345 * on the items in column 2; if any items in column 2 are equal,
346 * then those particular rows are ordered based on the items in
347 * column 1. In this case, we say that the rows are primarily
348 * sorted on column 2, and secondarily on column 1. If the user
349 * then clicks the header for column 3, then the items are
350 * primarily sorted on column 3 and secondarily sorted on column
351 * 2. Because the maximum number of sort keys has been set to 2
352 * with <code>setMaxSortKeys</code>, column 1 no longer has an
353 * effect on the order.
354 * <p>
355 * The maximum number of sort keys is enforced by
356 * <code>toggleSortOrder</code>. You can specify more sort
357 * keys by invoking <code>setSortKeys</code> directly and they will
358 * all be honored. However if <code>toggleSortOrder</code> is subsequently
359 * invoked the maximum number of sort keys will be enforced.
360 * The default value is 3.
361 *
362 * @param max the maximum number of sort keys
363 * @throws IllegalArgumentException if <code>max</code> < 1
364 */
365 public void setMaxSortKeys(int max) {
366 if (max < 1) {
367 throw new IllegalArgumentException("Invalid max");
368 }
369 maxSortKeys = max;
370 }
371
372 /**
373 * Returns the maximum number of sort keys.
374 *
375 * @return the maximum number of sort keys
376 */
377 public int getMaxSortKeys() {
378 return maxSortKeys;
379 }
380
381 /**
382 * If true, specifies that a sort should happen when the underlying
383 * model is updated (<code>rowsUpdated</code> is invoked). For
384 * example, if this is true and the user edits an entry the
385 * location of that item in the view may change. The default is
386 * false.
387 *
388 * @param sortsOnUpdates whether or not to sort on update events
389 */
390 public void setSortsOnUpdates(boolean sortsOnUpdates) {
391 this.sortsOnUpdates = sortsOnUpdates;
392 }
393
394 /**
395 * Returns true if a sort should happen when the underlying
396 * model is updated; otherwise, returns false.
397 *
398 * @return whether or not to sort when the model is updated
399 */
400 public boolean getSortsOnUpdates() {
401 return sortsOnUpdates;
402 }
403
404 /**
405 * Sets the filter that determines which rows, if any, should be
406 * hidden from the view. The filter is applied before sorting. A value
407 * of <code>null</code> indicates all values from the model should be
408 * included.
409 * <p>
410 * <code>RowFilter</code>'s <code>include</code> method is passed an
411 * <code>Entry</code> that wraps the underlying model. The number
412 * of columns in the <code>Entry</code> corresponds to the
413 * number of columns in the <code>ModelWrapper</code>. The identifier
414 * comes from the <code>ModelWrapper</code> as well.
415 * <p>
416 * This method triggers a sort.
417 *
418 * @param filter the filter used to determine what entries should be
419 * included
420 */
421 public void setRowFilter(RowFilter<? super M,? super I> filter) {
422 this.filter = filter;
423 sort();
424 }
425
426 /**
427 * Returns the filter that determines which rows, if any, should
428 * be hidden from view.
429 *
430 * @return the filter
431 */
432 public RowFilter<? super M,? super I> getRowFilter() {
433 return filter;
434 }
435
436 /**
437 * Reverses the sort order from ascending to descending (or
438 * descending to ascending) if the specified column is already the
439 * primary sorted column; otherwise, makes the specified column
440 * the primary sorted column, with an ascending sort order. If
441 * the specified column is not sortable, this method has no
442 * effect.
443 *
444 * @param column index of the column to make the primary sorted column,
445 * in terms of the underlying model
446 * @throws IndexOutOfBoundsException {@inheritDoc}
447 * @see #setSortable(int,boolean)
448 * @see #setMaxSortKeys(int)
449 */
450 public void toggleSortOrder(int column) {
451 checkColumn(column);
452 if (isSortable(column)) {
453 List<SortKey> keys = new ArrayList<SortKey>(getSortKeys());
454 SortKey sortKey;
455 int sortIndex;
456 for (sortIndex = keys.size() - 1; sortIndex >= 0; sortIndex--) {
457 if (keys.get(sortIndex).getColumn() == column) {
458 break;
459 }
460 }
461 if (sortIndex == -1) {
462 // Key doesn't exist
463 sortKey = new SortKey(column, SortOrder.ASCENDING);
464 keys.add(0, sortKey);
465 }
466 else if (sortIndex == 0) {
467 // It's the primary sorting key, toggle it
468 keys.set(0, toggle(keys.get(0)));
469 }
470 else {
471 // It's not the first, but was sorted on, remove old
472 // entry, insert as first with ascending.
473 keys.remove(sortIndex);
474 keys.add(0, new SortKey(column, SortOrder.ASCENDING));
475 }
476 if (keys.size() > getMaxSortKeys()) {
477 keys = keys.subList(0, getMaxSortKeys());
478 }
479 setSortKeys(keys);
480 }
481 }
482
483 private SortKey toggle(SortKey key) {
484 if (key.getSortOrder() == SortOrder.ASCENDING) {
485 return new SortKey(key.getColumn(), SortOrder.DESCENDING);
486 }
487 return new SortKey(key.getColumn(), SortOrder.ASCENDING);
488 }
489
490 /**
491 * {@inheritDoc}
492 *
493 * @throws IndexOutOfBoundsException {@inheritDoc}
494 */
495 public int convertRowIndexToView(int index) {
496 if (modelToView == null) {
497 if (index < 0 || index >= modelRowCount) {
498 throw new IndexOutOfBoundsException("Invalid index");
499 }
500 return index;
501 }
502 return modelToView[index];
503 }
504
505 /**
506 * {@inheritDoc}
507 *
508 * @throws IndexOutOfBoundsException {@inheritDoc}
509 */
510 public int convertRowIndexToModel(int index) {
511 if (viewToModel == null) {
512 if (index < 0 || index >= modelRowCount) {
513 throw new IndexOutOfBoundsException("Invalid index");
514 }
515 return index;
516 }
517 return viewToModel[index].modelIndex;
518 }
519
520 private boolean isUnsorted() {
521 List<? extends SortKey> keys = getSortKeys();
522 int keySize = keys.size();
523 return (keySize == 0 || keys.get(0).getSortOrder() ==
524 SortOrder.UNSORTED);
525 }
526
527 /**
528 * Sorts the existing filtered data. This should only be used if
529 * the filter hasn't changed.
530 */
531 private void sortExistingData() {
532 int[] lastViewToModel = getViewToModelAsInts(viewToModel);
533
534 updateUseToString();
535 cacheSortKeys(getSortKeys());
536
537 if (isUnsorted()) {
538 if (getRowFilter() == null) {
539 viewToModel = null;
540 modelToView = null;
541 } else {
542 int included = 0;
543 for (int i = 0; i < modelToView.length; i++) {
544 if (modelToView[i] != -1) {
545 viewToModel[included].modelIndex = i;
546 modelToView[i] = included++;
547 }
548 }
549 }
550 } else {
551 // sort the data
552 Arrays.sort(viewToModel);
553
554 // Update the modelToView array
555 setModelToViewFromViewToModel(false);
556 }
557 fireRowSorterChanged(lastViewToModel);
558 }
559
560 /**
561 * Sorts and filters the rows in the view based on the sort keys
562 * of the columns currently being sorted and the filter, if any,
563 * associated with this sorter. An empty <code>sortKeys</code> list
564 * indicates that the view should unsorted, the same as the model.
565 *
566 * @see #setRowFilter
567 * @see #setSortKeys
568 */
569 public void sort() {
570 sorted = true;
571 int[] lastViewToModel = getViewToModelAsInts(viewToModel);
572 updateUseToString();
573 if (isUnsorted()) {
574 // Unsorted
575 cachedSortKeys = new SortKey[0];
576 if (getRowFilter() == null) {
577 // No filter & unsorted
578 if (viewToModel != null) {
579 // sorted -> unsorted
580 viewToModel = null;
581 modelToView = null;
582 }
583 else {
584 // unsorted -> unsorted
585 // No need to do anything.
586 return;
587 }
588 }
589 else {
590 // There is filter, reset mappings
591 initializeFilteredMapping();
592 }
593 }
594 else {
595 cacheSortKeys(getSortKeys());
596
597 if (getRowFilter() != null) {
598 initializeFilteredMapping();
599 }
600 else {
601 createModelToView(getModelWrapper().getRowCount());
602 createViewToModel(getModelWrapper().getRowCount());
603 }
604
605 // sort them
606 Arrays.sort(viewToModel);
607
608 // Update the modelToView array
609 setModelToViewFromViewToModel(false);
610 }
611 fireRowSorterChanged(lastViewToModel);
612 }
613
614 /**
615 * Updates the useToString mapping before a sort.
616 */
617 private void updateUseToString() {
618 int i = getModelWrapper().getColumnCount();
619 if (useToString == null || useToString.length != i) {
620 useToString = new boolean[i];
621 }
622 for (--i; i >= 0; i--) {
623 useToString[i] = useToString(i);
624 }
625 }
626
627 /**
628 * Resets the viewToModel and modelToView mappings based on
629 * the current Filter.
630 */
631 private void initializeFilteredMapping() {
632 int rowCount = getModelWrapper().getRowCount();
633 int i, j;
634 int excludedCount = 0;
635
636 // Update model -> view
637 createModelToView(rowCount);
638 for (i = 0; i < rowCount; i++) {
639 if (include(i)) {
640 modelToView[i] = i - excludedCount;
641 }
642 else {
643 modelToView[i] = -1;
644 excludedCount++;
645 }
646 }
647
648 // Update view -> model
649 createViewToModel(rowCount - excludedCount);
650 for (i = 0, j = 0; i < rowCount; i++) {
651 if (modelToView[i] != -1) {
652 viewToModel[j++].modelIndex = i;
653 }
654 }
655 }
656
657 /**
658 * Makes sure the modelToView array is of size rowCount.
659 */
660 private void createModelToView(int rowCount) {
661 if (modelToView == null || modelToView.length != rowCount) {
662 modelToView = new int[rowCount];
663 }
664 }
665
666 /**
667 * Resets the viewToModel array to be of size rowCount.
668 */
669 private void createViewToModel(int rowCount) {
670 int recreateFrom = 0;
671 if (viewToModel != null) {
672 recreateFrom = Math.min(rowCount, viewToModel.length);
673 if (viewToModel.length != rowCount) {
674 Row[] oldViewToModel = viewToModel;
675 viewToModel = new Row[rowCount];
676 System.arraycopy(oldViewToModel, 0, viewToModel,
677 0, recreateFrom);
678 }
679 }
680 else {
681 viewToModel = new Row[rowCount];
682 }
683 int i;
684 for (i = 0; i < recreateFrom; i++) {
685 viewToModel[i].modelIndex = i;
686 }
687 for (i = recreateFrom; i < rowCount; i++) {
688 viewToModel[i] = new Row(this, i);
689 }
690 }
691
692 /**
693 * Caches the sort keys before a sort.
694 */
695 private void cacheSortKeys(List<? extends SortKey> keys) {
696 int keySize = keys.size();
697 sortComparators = new Comparator<?>[keySize];
698 for (int i = 0; i < keySize; i++) {
699 sortComparators[i] = getComparator0(keys.get(i).getColumn());
700 }
701 cachedSortKeys = keys.toArray(new SortKey[keySize]);
702 }
703
704 /**
705 * Returns whether or not to convert the value to a string before
706 * doing comparisons when sorting. If true
707 * <code>ModelWrapper.getStringValueAt</code> will be used, otherwise
708 * <code>ModelWrapper.getValueAt</code> will be used. It is up to
709 * subclasses, such as <code>TableRowSorter</code>, to honor this value
710 * in their <code>ModelWrapper</code> implementation.
711 *
712 * @param column the index of the column to test, in terms of the
713 * underlying model
714 * @return true if values are to be converted to strings before doing
715 * comparisons when sorting
716 * @throws IndexOutOfBoundsException if <code>column</code> is not valid
717 */
718 protected boolean useToString(int column) {
719 return (getComparator(column) == null);
720 }
721
722 /**
723 * Refreshes the modelToView mapping from that of viewToModel.
724 * If <code>unsetFirst</code> is true, all indices in modelToView are
725 * first set to -1.
726 */
727 private void setModelToViewFromViewToModel(boolean unsetFirst) {
728 int i;
729 if (unsetFirst) {
730 for (i = modelToView.length - 1; i >= 0; i--) {
731 modelToView[i] = -1;
732 }
733 }
734 for (i = viewToModel.length - 1; i >= 0; i--) {
735 modelToView[viewToModel[i].modelIndex] = i;
736 }
737 }
738
739 private int[] getViewToModelAsInts(Row[] viewToModel) {
740 if (viewToModel != null) {
741 int[] viewToModelI = new int[viewToModel.length];
742 for (int i = viewToModel.length - 1; i >= 0; i--) {
743 viewToModelI[i] = viewToModel[i].modelIndex;
744 }
745 return viewToModelI;
746 }
747 return new int[0];
748 }
749
750 /**
751 * Sets the <code>Comparator</code> to use when sorting the specified
752 * column. This does not trigger a sort. If you want to sort after
753 * setting the comparator you need to explicitly invoke <code>sort</code>.
754 *
755 * @param column the index of the column the <code>Comparator</code> is
756 * to be used for, in terms of the underlying model
757 * @param comparator the <code>Comparator</code> to use
758 * @throws IndexOutOfBoundsException if <code>column</code> is outside
759 * the range of the underlying model
760 */
761 public void setComparator(int column, Comparator<?> comparator) {
762 checkColumn(column);
763 if (comparators == null) {
764 comparators = new Comparator<?>[getModelWrapper().getColumnCount()];
765 }
766 comparators[column] = comparator;
767 }
768
769 /**
770 * Returns the <code>Comparator</code> for the specified
771 * column. This will return <code>null</code> if a <code>Comparator</code>
772 * has not been specified for the column.
773 *
774 * @param column the column to fetch the <code>Comparator</code> for, in
775 * terms of the underlying model
776 * @return the <code>Comparator</code> for the specified column
777 * @throws IndexOutOfBoundsException if column is outside
778 * the range of the underlying model
779 */
780 public Comparator<?> getComparator(int column) {
781 checkColumn(column);
782 if (comparators != null) {
783 return comparators[column];
784 }
785 return null;
786 }
787
788 // Returns the Comparator to use during sorting. Where as
789 // getComparator() may return null, this will never return null.
790 private Comparator<?> getComparator0(int column) {
791 Comparator<?> comparator = getComparator(column);
792 if (comparator != null) {
793 return comparator;
794 }
795 // This should be ok as useToString(column) should have returned
796 // true in this case.
797 return Collator.getInstance();
798 }
799
800 private RowFilter.Entry<M,I> getFilterEntry(int modelIndex) {
801 if (filterEntry == null) {
802 filterEntry = new FilterEntry();
803 }
804 filterEntry.modelIndex = modelIndex;
805 return filterEntry;
806 }
807
808 /**
809 * {@inheritDoc}
810 */
811 public int getViewRowCount() {
812 if (viewToModel != null) {
813 // When filtering this may differ from getModelWrapper().getRowCount()
814 return viewToModel.length;
815 }
816 return Math.max(getModelWrapper().getRowCount(), modelRowCount);
817 }
818
819 /**
820 * {@inheritDoc}
821 */
822 public int getModelRowCount() {
823 return getModelWrapper().getRowCount();
824 }
825
826 private void allChanged() {
827 modelToView = null;
828 viewToModel = null;
829 comparators = null;
830 isSortable = null;
831 if (isUnsorted()) {
832 // Keys are already empty, to force a resort we have to
833 // call sort
834 sort();
835 } else {
836 setSortKeys(null);
837 }
838 }
839
840 /**
841 * {@inheritDoc}
842 */
843 public void modelStructureChanged() {
844 allChanged();
845 modelRowCount = getModelWrapper().getRowCount();
846 }
847
848 /**
849 * {@inheritDoc}
850 */
851 public void allRowsChanged() {
852 modelRowCount = getModelWrapper().getRowCount();
853 sort();
854 }
855
856 /**
857 * {@inheritDoc}
858 *
859 * @throws IndexOutOfBoundsException {@inheritDoc}
860 */
861 public void rowsInserted(int firstRow, int endRow) {
862 checkAgainstModel(firstRow, endRow);
863 int newModelRowCount = getModelWrapper().getRowCount();
864 if (endRow >= newModelRowCount) {
865 throw new IndexOutOfBoundsException("Invalid range");
866 }
867 modelRowCount = newModelRowCount;
868 if (shouldOptimizeChange(firstRow, endRow)) {
869 rowsInserted0(firstRow, endRow);
870 }
871 }
872
873 /**
874 * {@inheritDoc}
875 *
876 * @throws IndexOutOfBoundsException {@inheritDoc}
877 */
878 public void rowsDeleted(int firstRow, int endRow) {
879 checkAgainstModel(firstRow, endRow);
880 if (firstRow >= modelRowCount || endRow >= modelRowCount) {
881 throw new IndexOutOfBoundsException("Invalid range");
882 }
883 modelRowCount = getModelWrapper().getRowCount();
884 if (shouldOptimizeChange(firstRow, endRow)) {
885 rowsDeleted0(firstRow, endRow);
886 }
887 }
888
889 /**
890 * {@inheritDoc}
891 *
892 * @throws IndexOutOfBoundsException {@inheritDoc}
893 */
894 public void rowsUpdated(int firstRow, int endRow) {
895 checkAgainstModel(firstRow, endRow);
896 if (firstRow >= modelRowCount || endRow >= modelRowCount) {
897 throw new IndexOutOfBoundsException("Invalid range");
898 }
899 if (getSortsOnUpdates()) {
900 if (shouldOptimizeChange(firstRow, endRow)) {
901 rowsUpdated0(firstRow, endRow);
902 }
903 }
904 else {
905 sorted = false;
906 }
907 }
908
909 /**
910 * {@inheritDoc}
911 *
912 * @throws IndexOutOfBoundsException {@inheritDoc}
913 */
914 public void rowsUpdated(int firstRow, int endRow, int column) {
915 checkColumn(column);
916 rowsUpdated(firstRow, endRow);
917 }
918
919 private void checkAgainstModel(int firstRow, int endRow) {
920 if (firstRow > endRow || firstRow < 0 || endRow < 0 ||
921 firstRow > modelRowCount) {
922 throw new IndexOutOfBoundsException("Invalid range");
923 }
924 }
925
926 /**
927 * Returns true if the specified row should be included.
928 */
929 private boolean include(int row) {
930 RowFilter<? super M, ? super I> filter = getRowFilter();
931 if (filter != null) {
932 return filter.include(getFilterEntry(row));
933 }
934 // null filter, always include the row.
935 return true;
936 }
937
938 @SuppressWarnings("unchecked")
939 private int compare(int model1, int model2) {
940 int column;
941 SortOrder sortOrder;
942 Object v1, v2;
943 int result;
944
945 for (int counter = 0; counter < cachedSortKeys.length; counter++) {
946 column = cachedSortKeys[counter].getColumn();
947 sortOrder = cachedSortKeys[counter].getSortOrder();
948 if (sortOrder == SortOrder.UNSORTED) {
949 result = model1 - model2;
950 } else {
951 // v1 != null && v2 != null
952 if (useToString[column]) {
953 v1 = getModelWrapper().getStringValueAt(model1, column);
954 v2 = getModelWrapper().getStringValueAt(model2, column);
955 } else {
956 v1 = getModelWrapper().getValueAt(model1, column);
957 v2 = getModelWrapper().getValueAt(model2, column);
958 }
959 // Treat nulls as < then non-null
960 if (v1 == null) {
961 if (v2 == null) {
962 result = 0;
963 } else {
964 result = -1;
965 }
966 } else if (v2 == null) {
967 result = 1;
968 } else {
969 Comparator<Object> c =
970 (Comparator<Object>)sortComparators[counter];
971 result = c.compare(v1, v2);
972 }
973 if (sortOrder == SortOrder.DESCENDING) {
974 result *= -1;
975 }
976 }
977 if (result != 0) {
978 return result;
979 }
980 }
981 // If we get here, they're equal. Fallback to model order.
982 return model1 - model2;
983 }
984
985 /**
986 * Whether not we are filtering/sorting.
987 */
988 private boolean isTransformed() {
989 return (viewToModel != null);
990 }
991
992 /**
993 * Insets new set of entries.
994 *
995 * @param toAdd the Rows to add, sorted
996 * @param current the array to insert the items into
997 */
998 private void insertInOrder(List<Row> toAdd, Row[] current) {
999 int last = 0;
1000 int index;
1001 int max = toAdd.size();
1002 for (int i = 0; i < max; i++) {
1003 index = Arrays.binarySearch(current, toAdd.get(i));
1004 if (index < 0) {
1005 index = -1 - index;
1006 }
1007 System.arraycopy(current, last,
1008 viewToModel, last + i, index - last);
1009 viewToModel[index + i] = toAdd.get(i);
1010 last = index;
1011 }
1012 System.arraycopy(current, last, viewToModel, last + max,
1013 current.length - last);
1014 }
1015
1016 /**
1017 * Returns true if we should try and optimize the processing of the
1018 * <code>TableModelEvent</code>. If this returns false, assume the
1019 * event was dealt with and no further processing needs to happen.
1020 */
1021 private boolean shouldOptimizeChange(int firstRow, int lastRow) {
1022 if (!isTransformed()) {
1023 // Not transformed, nothing to do.
1024 return false;
1025 }
1026 if (!sorted || (lastRow - firstRow) > viewToModel.length / 10) {
1027 // We either weren't sorted, or to much changed, sort it all
1028 sort();
1029 return false;
1030 }
1031 return true;
1032 }
1033
1034 private void rowsInserted0(int firstRow, int lastRow) {
1035 int[] oldViewToModel = getViewToModelAsInts(viewToModel);
1036 int i;
1037 int delta = (lastRow - firstRow) + 1;
1038 List<Row> added = new ArrayList<Row>(delta);
1039
1040 // Build the list of Rows to add into added
1041 for (i = firstRow; i <= lastRow; i++) {
1042 if (include(i)) {
1043 added.add(new Row(this, i));
1044 }
1045 }
1046
1047 // Adjust the model index of rows after the effected region
1048 int viewIndex;
1049 for (i = modelToView.length - 1; i >= firstRow; i--) {
1050 viewIndex = modelToView[i];
1051 if (viewIndex != -1) {
1052 viewToModel[viewIndex].modelIndex += delta;
1053 }
1054 }
1055
1056 // Insert newly added rows into viewToModel
1057 if (added.size() > 0) {
1058 Collections.sort(added);
1059 Row[] lastViewToModel = viewToModel;
1060 viewToModel = new Row[viewToModel.length + added.size()];
1061 insertInOrder(added, lastViewToModel);
1062 }
1063
1064 // Update modelToView
1065 createModelToView(getModelWrapper().getRowCount());
1066 setModelToViewFromViewToModel(true);
1067
1068 // Notify of change
1069 fireRowSorterChanged(oldViewToModel);
1070 }
1071
1072 private void rowsDeleted0(int firstRow, int lastRow) {
1073 int[] oldViewToModel = getViewToModelAsInts(viewToModel);
1074 int removedFromView = 0;
1075 int i;
1076 int viewIndex;
1077
1078 // Figure out how many visible rows are going to be effected.
1079 for (i = firstRow; i <= lastRow; i++) {
1080 viewIndex = modelToView[i];
1081 if (viewIndex != -1) {
1082 removedFromView++;
1083 viewToModel[viewIndex] = null;
1084 }
1085 }
1086
1087 // Update the model index of rows after the effected region
1088 int delta = lastRow - firstRow + 1;
1089 for (i = modelToView.length - 1; i > lastRow; i--) {
1090 viewIndex = modelToView[i];
1091 if (viewIndex != -1) {
1092 viewToModel[viewIndex].modelIndex -= delta;
1093 }
1094 }
1095
1096 // Then patch up the viewToModel array
1097 if (removedFromView > 0) {
1098 Row[] newViewToModel = new Row[viewToModel.length -
1099 removedFromView];
1100 int newIndex = 0;
1101 int last = 0;
1102 for (i = 0; i < viewToModel.length; i++) {
1103 if (viewToModel[i] == null) {
1104 System.arraycopy(viewToModel, last,
1105 newViewToModel, newIndex, i - last);
1106 newIndex += (i - last);
1107 last = i + 1;
1108 }
1109 }
1110 System.arraycopy(viewToModel, last,
1111 newViewToModel, newIndex, viewToModel.length - last);
1112 viewToModel = newViewToModel;
1113 }
1114
1115 // Update the modelToView mapping
1116 createModelToView(getModelWrapper().getRowCount());
1117 setModelToViewFromViewToModel(true);
1118
1119 // And notify of change
1120 fireRowSorterChanged(oldViewToModel);
1121 }
1122
1123 private void rowsUpdated0(int firstRow, int lastRow) {
1124 int[] oldViewToModel = getViewToModelAsInts(viewToModel);
1125 int i, j;
1126 int delta = lastRow - firstRow + 1;
1127 int modelIndex;
1128 int last;
1129 int index;
1130
1131 if (getRowFilter() == null) {
1132 // Sorting only:
1133
1134 // Remove the effected rows
1135 Row[] updated = new Row[delta];
1136 for (j = 0, i = firstRow; i <= lastRow; i++, j++) {
1137 updated[j] = viewToModel[modelToView[i]];
1138 }
1139
1140 // Sort the update rows
1141 Arrays.sort(updated);
1142
1143 // Build the intermediary array: the array of
1144 // viewToModel without the effected rows.
1145 Row[] intermediary = new Row[viewToModel.length - delta];
1146 for (i = 0, j = 0; i < viewToModel.length; i++) {
1147 modelIndex = viewToModel[i].modelIndex;
1148 if (modelIndex < firstRow || modelIndex > lastRow) {
1149 intermediary[j++] = viewToModel[i];
1150 }
1151 }
1152
1153 // Build the new viewToModel
1154 insertInOrder(Arrays.asList(updated), intermediary);
1155
1156 // Update modelToView
1157 setModelToViewFromViewToModel(false);
1158 }
1159 else {
1160 // Sorting & filtering.
1161
1162 // Remove the effected rows, adding them to updated and setting
1163 // modelToView to -2 for any rows that were not filtered out
1164 List<Row> updated = new ArrayList<Row>(delta);
1165 int newlyVisible = 0;
1166 int newlyHidden = 0;
1167 int effected = 0;
1168 for (i = firstRow; i <= lastRow; i++) {
1169 if (modelToView[i] == -1) {
1170 // This row was filtered out
1171 if (include(i)) {
1172 // No longer filtered
1173 updated.add(new Row(this, i));
1174 newlyVisible++;
1175 }
1176 }
1177 else {
1178 // This row was visible, make sure it should still be
1179 // visible.
1180 if (!include(i)) {
1181 newlyHidden++;
1182 }
1183 else {
1184 updated.add(viewToModel[modelToView[i]]);
1185 }
1186 modelToView[i] = -2;
1187 effected++;
1188 }
1189 }
1190
1191 // Sort the updated rows
1192 Collections.sort(updated);
1193
1194 // Build the intermediary array: the array of
1195 // viewToModel without the updated rows.
1196 Row[] intermediary = new Row[viewToModel.length - effected];
1197 for (i = 0, j = 0; i < viewToModel.length; i++) {
1198 modelIndex = viewToModel[i].modelIndex;
1199 if (modelToView[modelIndex] != -2) {
1200 intermediary[j++] = viewToModel[i];
1201 }
1202 }
1203
1204 // Recreate viewToModel, if necessary
1205 if (newlyVisible != newlyHidden) {
1206 viewToModel = new Row[viewToModel.length + newlyVisible -
1207 newlyHidden];
1208 }
1209
1210 // Rebuild the new viewToModel array
1211 insertInOrder(updated, intermediary);
1212
1213 // Update modelToView
1214 setModelToViewFromViewToModel(true);
1215 }
1216 // And finally fire a sort event.
1217 fireRowSorterChanged(oldViewToModel);
1218 }
1219
1220 private void checkColumn(int column) {
1221 if (column < 0 || column >= getModelWrapper().getColumnCount()) {
1222 throw new IndexOutOfBoundsException(
1223 "column beyond range of TableModel");
1224 }
1225 }
1226
1227
1228 /**
1229 * <code>DefaultRowSorter.ModelWrapper</code> is responsible for providing
1230 * the data that gets sorted by <code>DefaultRowSorter</code>. You
1231 * normally do not interact directly with <code>ModelWrapper</code>.
1232 * Subclasses of <code>DefaultRowSorter</code> provide an
1233 * implementation of <code>ModelWrapper</code> wrapping another model.
1234 * For example,
1235 * <code>TableRowSorter</code> provides a <code>ModelWrapper</code> that
1236 * wraps a <code>TableModel</code>.
1237 * <p>
1238 * <code>ModelWrapper</code> makes a distinction between values as
1239 * <code>Object</code>s and <code>String</code>s. This allows
1240 * implementations to provide a custom string
1241 * converter to be used instead of invoking <code>toString</code> on the
1242 * object.
1243 *
1244 * @param <M> the type of the underlying model
1245 * @param <I> the identifier supplied to the filter
1246 * @since 1.6
1247 * @see RowFilter
1248 * @see RowFilter.Entry
1249 */
1250 protected abstract static class ModelWrapper<M,I> {
1251 /**
1252 * Creates a new <code>ModelWrapper</code>.
1253 */
1254 protected ModelWrapper() {
1255 }
1256
1257 /**
1258 * Returns the underlying model that this <code>Model</code> is
1259 * wrapping.
1260 *
1261 * @return the underlying model
1262 */
1263 public abstract M getModel();
1264
1265 /**
1266 * Returns the number of columns in the model.
1267 *
1268 * @return the number of columns in the model
1269 */
1270 public abstract int getColumnCount();
1271
1272 /**
1273 * Returns the number of rows in the model.
1274 *
1275 * @return the number of rows in the model
1276 */
1277 public abstract int getRowCount();
1278
1279 /**
1280 * Returns the value at the specified index.
1281 *
1282 * @param row the row index
1283 * @param column the column index
1284 * @return the value at the specified index
1285 * @throws IndexOutOfBoundsException if the indices are outside
1286 * the range of the model
1287 */
1288 public abstract Object getValueAt(int row, int column);
1289
1290 /**
1291 * Returns the value as a <code>String</code> at the specified
1292 * index. This implementation uses <code>toString</code> on
1293 * the result from <code>getValueAt</code> (making sure
1294 * to return an empty string for null values). Subclasses that
1295 * override this method should never return null.
1296 *
1297 * @param row the row index
1298 * @param column the column index
1299 * @return the value at the specified index as a <code>String</code>
1300 * @throws IndexOutOfBoundsException if the indices are outside
1301 * the range of the model
1302 */
1303 public String getStringValueAt(int row, int column) {
1304 Object o = getValueAt(row, column);
1305 if (o == null) {
1306 return "";
1307 }
1308 String string = o.toString();
1309 if (string == null) {
1310 return "";
1311 }
1312 return string;
1313 }
1314
1315 /**
1316 * Returns the identifier for the specified row. The return value
1317 * of this is used as the identifier for the
1318 * <code>RowFilter.Entry</code> that is passed to the
1319 * <code>RowFilter</code>.
1320 *
1321 * @param row the row to return the identifier for, in terms of
1322 * the underlying model
1323 * @return the identifier
1324 * @see RowFilter.Entry#getIdentifier
1325 */
1326 public abstract I getIdentifier(int row);
1327 }
1328
1329
1330 /**
1331 * RowFilter.Entry implementation that delegates to the ModelWrapper.
1332 * getFilterEntry(int) creates the single instance of this that is
1333 * passed to the Filter. Only call getFilterEntry(int) to get
1334 * the instance.
1335 */
1336 private class FilterEntry extends RowFilter.Entry<M,I> {
1337 /**
1338 * The index into the model, set in getFilterEntry
1339 */
1340 int modelIndex;
1341
1342 public M getModel() {
1343 return getModelWrapper().getModel();
1344 }
1345
1346 public int getValueCount() {
1347 return getModelWrapper().getColumnCount();
1348 }
1349
1350 public Object getValue(int index) {
1351 return getModelWrapper().getValueAt(modelIndex, index);
1352 }
1353
1354 public String getStringValue(int index) {
1355 return getModelWrapper().getStringValueAt(modelIndex, index);
1356 }
1357
1358 public I getIdentifier() {
1359 return getModelWrapper().getIdentifier(modelIndex);
1360 }
1361 }
1362
1363
1364 /**
1365 * Row is used to handle the actual sorting by way of Comparable. It
1366 * will use the sortKeys to do the actual comparison.
1367 */
1368 // NOTE: this class is static so that it can be placed in an array
1369 private static class Row implements Comparable<Row> {
1370 private DefaultRowSorter<?, ?> sorter;
1371 int modelIndex;
1372
1373 public Row(DefaultRowSorter<?, ?> sorter, int index) {
1374 this.sorter = sorter;
1375 modelIndex = index;
1376 }
1377
1378 public int compareTo(Row o) {
1379 return sorter.compare(modelIndex, o.modelIndex);
1380 }
1381 }
1382 }