History

My first initial commit with all of mine experimental changes.

Dmitry Kabak (userdima committed 13 years ago

Commit d37b88d1 parent 3f5b46618352 files · +2598093 −-8690Browse files

Showing the changes to java/pkg/javax/swing/DefaultRowSorter.java (1 of 18352 files). Show all

Unified · Split

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

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t