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+ */
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+import 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+ */
112+public 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