001 /*
002 * Copyright 2002-2008 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005 * use this file except in compliance with the License. You may obtain a copy of
006 * the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013 * License for the specific language governing permissions and limitations under
014 * the License.
015 */
016 package org.springframework.richclient.table;
017
018 import java.awt.EventQueue;
019 import java.util.ArrayList;
020 import java.util.Arrays;
021 import java.util.Collections;
022 import java.util.Comparator;
023 import java.util.List;
024 import java.util.Map;
025
026 import javax.swing.SwingUtilities;
027 import javax.swing.event.TableModelEvent;
028 import javax.swing.table.TableModel;
029
030 import org.springframework.core.style.StylerUtils;
031 import org.springframework.util.Assert;
032 import org.springframework.util.comparator.NullSafeComparator;
033
034 /**
035 * A sorter for TableModels. The sorter has a model (conforming to TableModel)
036 * and itself implements TableModel. TableSorter does not store or copy the
037 * model in the TableModel, instead it maintains an array of integers which it
038 * keeps the same size as the number of rows in its model. When the model
039 * changes it notifies the sorter that something has changed eg. "rowsAdded" so
040 * that its internal array of integers can be reallocated. As requests are made
041 * of the sorter (like getValueAt(row, col) it redirects them to its model via
042 * the mapping array. That way the TableSorter appears to hold another copy of
043 * the table with the rows in a different order. The sorting algorthm used is
044 * stable which means that it does not move around rows when its comparison
045 * function returns 0 to denote that they are equivalent.
046 */
047 public class ShuttleSortableTableModel extends AbstractTableModelFilter implements SortableTableModel {
048 private static final Comparator OBJECT_COMPARATOR = new NullSafeComparator(ToStringComparator.INSTANCE, true);
049
050 private static final Comparator COMPARABLE_COMPARATOR = NullSafeComparator.NULLS_LOW;
051
052 private Comparator[] columnComparators;
053
054 private List columnsToSort = new ArrayList(4);
055
056 private int[] indexes;
057
058 private int compares;
059
060 private boolean autoSortEnabled = true;
061
062 private Runnable notifyTableRunnable = new Runnable() {
063 public void run() {
064 fireTableDataChanged();
065 }
066 };
067
068 public ShuttleSortableTableModel(TableModel model) {
069 super(model);
070 allocateIndexes();
071 resetComparators();
072 }
073
074 private void allocateIndexes() {
075 int rowCount = filteredModel.getRowCount();
076 // Set up a new array of indexes with the right number of elements
077 // for the new model model.
078 indexes = new int[rowCount];
079 // Initialise with the identity mapping.
080 for (int row = 0; row < rowCount; row++) {
081 indexes[row] = row;
082 }
083 }
084
085 public void resetComparators() {
086 resetComparators(Collections.EMPTY_MAP);
087 }
088
089 /**
090 * Reset the <code>columnComparartos</code>.<br>
091 * Useful when the columns of the model were changed, and by consequence
092 * their comparators.
093 * @param comparators - map with comparators where the key is the column of
094 * the comparator.
095 */
096 public void resetComparators(Map comparators) {
097 int colCount = filteredModel.getColumnCount();
098 columnComparators = new Comparator[colCount];
099
100 Comparator newComparator;
101 Class clazz;
102 for (int i = 0; i < columnComparators.length; i++) {
103 newComparator = (Comparator) comparators.get(Integer.valueOf(i));
104 if (newComparator != null)
105 setComparator(i, newComparator);
106 else {
107 clazz = filteredModel.getColumnClass(i);
108 if (clazz == Object.class || !Comparable.class.isAssignableFrom(clazz))
109 columnComparators[i] = OBJECT_COMPARATOR;
110 }
111 }
112 }
113
114 public boolean isCellEditable(int row, int column) {
115 return filteredModel.isCellEditable(indexes[row], column);
116 }
117
118 public boolean isAutoSortEnabled() {
119 return autoSortEnabled;
120 }
121
122 public void setAutoSortEnabled(boolean autoSortEnabled) {
123 this.autoSortEnabled = autoSortEnabled;
124 }
125
126 public Comparator getComparator(int columnIndex) {
127 return this.columnComparators[columnIndex];
128 }
129
130 public void setComparator(int columnIndex, Comparator comparator) {
131 Assert.notNull(comparator);
132 this.columnComparators[columnIndex] = comparator;
133 }
134
135 // The mapping only affects the contents of the model rows.
136 // Pass all requests to these rows through the mapping array: "indexes".
137 public Object getValueAt(int row, int column) {
138 return filteredModel.getValueAt(indexes[row], column);
139 }
140
141 public void setValueAt(Object value, int row, int column) {
142 filteredModel.setValueAt(value, indexes[row], column);
143 }
144
145 public void sortByColumn(ColumnToSort columnToSort) {
146 columnsToSort.clear();
147 columnsToSort.add(columnToSort);
148 sort();
149 SwingUtilities.invokeLater(notifyTableRunnable);
150 }
151
152 public void sortByColumns(ColumnToSort[] columnsToSort) {
153 this.columnsToSort = Arrays.asList(columnsToSort);
154 sort();
155 notifyTableChanged();
156 }
157
158 public int[] sortByColumns(ColumnToSort[] columnsToSort, int[] preSortSelectedRows) {
159 int[] modelIndexes = new int[preSortSelectedRows.length];
160 if (logger.isDebugEnabled()) {
161 logger.debug("Selected row indexes before sort" + StylerUtils.style(preSortSelectedRows));
162 }
163 for (int i = 0; i < preSortSelectedRows.length; i++) {
164 modelIndexes[i] = convertSortedIndexToDataIndex(preSortSelectedRows[i]);
165 }
166 this.columnsToSort = Arrays.asList(columnsToSort);
167 sort();
168 int[] postSortSelectedRows = new int[modelIndexes.length];
169 for (int i = 0; i < modelIndexes.length; i++) {
170 postSortSelectedRows[i] = convertModelToRowIndex(modelIndexes[i]);
171 }
172 if (logger.isDebugEnabled()) {
173 logger.debug("Selected row indexes after sort" + StylerUtils.style(postSortSelectedRows));
174 }
175 notifyTableChanged();
176 return postSortSelectedRows;
177 }
178
179 protected void notifyTableChanged() {
180 if (!EventQueue.isDispatchThread()) {
181 SwingUtilities.invokeLater(notifyTableRunnable);
182 }
183 else {
184 notifyTableRunnable.run();
185 }
186 }
187
188 public int convertSortedIndexToDataIndex(int index) {
189 return indexes[index];
190 }
191
192 public int[] convertSortedIndexesToDataIndexes(int[] indexes) {
193 int[] converted = new int[indexes.length];
194 for (int i = 0; i < indexes.length; i++) {
195 converted[i] = convertSortedIndexToDataIndex(indexes[i]);
196 }
197 return converted;
198 }
199
200 /* this linear search is a bit slow -- we'll need to optimize later */
201 public int convertModelToRowIndex(int index) {
202 for (int i = 0; i < indexes.length; i++) {
203 if (index == indexes[i]) {
204 return i;
205 }
206 }
207 return 0;
208 }
209
210 public int[] convertDataIndexesToSortedIndexes(int[] indexes) {
211 int[] converted = new int[indexes.length];
212 for (int i = 0; i < indexes.length; i++) {
213 converted[i] = convertModelToRowIndex(indexes[i]);
214 }
215 return converted;
216 }
217
218 private void sort() {
219 if (columnsToSort.size() > 0) {
220 checkModel();
221 compares = 0;
222 doShuttleSort((int[]) indexes.clone(), indexes, 0, indexes.length);
223 }
224 }
225
226 private void checkModel() {
227 if (indexes.length != filteredModel.getRowCount()) {
228 throw new IllegalStateException("Sorter not informed of a change in model.");
229 }
230 }
231
232 // This is a home-grown implementation which we have not had time
233 // to research - it may perform poorly in some circumstances. It
234 // requires twice the space of an in-place algorithm and makes
235 // NlogN assigments shuttling the values between the two
236 // arrays. The number of compares appears to vary between N-1 and
237 // NlogN depending on the initial order but the main reason for
238 // using it here is that, unlike qsort, it is stable.
239 private void doShuttleSort(int from[], int to[], int low, int high) {
240 if (high - low < 2) {
241 return;
242 }
243 int middle = (low + high) / 2;
244 doShuttleSort(to, from, low, middle);
245 doShuttleSort(to, from, middle, high);
246
247 int p = low;
248 int q = middle;
249
250 /*
251 * This is an optional short-cut; at each recursive call, check to see
252 * if the elements in this subset are already ordered. If so, no further
253 * comparisons are needed; the sub-array can just be copied. The array
254 * must be copied rather than assigned otherwise sister calls in the
255 * recursion might get out of sinc. When the number of elements is three
256 * they are partitioned so that the first set, [low, mid), has one
257 * element and and the second, [mid, high), has two. We skip the
258 * optimisation when the number of elements is three or less as the
259 * first compare in the normal merge will produce the same sequence of
260 * steps. This optimisation seems to be worthwhile for partially ordered
261 * lists but some analysis is needed to find out how the performance
262 * drops to Nlog(N) as the initial
263 */
264 if (high - low >= 4 && compare(from[middle - 1], from[middle]) <= 0) {
265 for (int i = low; i < high; i++) {
266 to[i] = from[i];
267 }
268 return;
269 }
270
271 // A normal merge.
272 for (int i = low; i < high; i++) {
273 if (q >= high || (p < middle && compare(from[p], from[q]) <= 0)) {
274 to[i] = from[p++];
275 }
276 else {
277 to[i] = from[q++];
278 }
279 }
280 }
281
282 public int compare(int row1, int row2) {
283 compares++;
284 for (int level = 0; level < columnsToSort.size(); level++) {
285 ColumnToSort column = (ColumnToSort) columnsToSort.get(level);
286 int result = compareRowsByColumn(row1, row2, column.getColumnIndex());
287 if (result != 0) {
288 return column.getSortOrder() == SortOrder.ASCENDING ? result : -result;
289 }
290 }
291 return 0;
292 }
293
294 private int compareRowsByColumn(int row1, int row2, int column) {
295 Object o1 = filteredModel.getValueAt(row1, column);
296 Object o2 = filteredModel.getValueAt(row2, column);
297
298 Comparator comparator = columnComparators[column];
299 if (comparator != null) {
300 return comparator.compare(o1, o2);
301 }
302
303 return COMPARABLE_COMPARATOR.compare(o1, o2);
304 }
305
306 public void tableChanged(final TableModelEvent e) {
307 if (e.getType() == TableModelEvent.INSERT) {
308 if (autoSortEnabled) {
309 reallocateIndexesOnInsert(e.getFirstRow(), e.getLastRow());
310 sort();
311 final int[] insertedRows = new int[e.getLastRow() - e.getFirstRow() + 1];
312 int row = e.getFirstRow();
313 for (int i = 0; i < insertedRows.length; i++) {
314 insertedRows[i] = convertModelToRowIndex(row++);
315 }
316 for (int i = 0; i < insertedRows.length; i++) {
317 fireTableRowsInserted(insertedRows[i], insertedRows[i]);
318 }
319 }
320 else {
321 reallocateIndexesOnInsert(e.getFirstRow(), e.getLastRow());
322 super.tableChanged(e);
323 }
324 }
325 else if (e.getType() == TableModelEvent.DELETE) {
326 final int[] deletedRows = new int[e.getLastRow() - e.getFirstRow() + 1];
327 int row = e.getFirstRow();
328 for (int i = 0; i < deletedRows.length; i++) {
329 deletedRows[i] = convertModelToRowIndex(row);
330 row++;
331 }
332 allocateIndexes();
333 for (int i = 0; i < deletedRows.length; i++) {
334 fireTableRowsDeleted(deletedRows[i], deletedRows[i]);
335 }
336 sort();
337 }
338 else if (e.getType() == TableModelEvent.UPDATE) {
339 allocateIndexes();
340 sort();
341 fireTableDataChanged();
342 }
343 else {
344 logger.warn("Doing an unknown table change type: " + e.getType());
345 allocateIndexes();
346 sort();
347 super.tableChanged(e);
348 }
349 }
350
351 private void reallocateIndexesOnInsert(int firstRow, int lastRow) {
352 int rowCount = filteredModel.getRowCount();
353 int[] newIndexes = new int[rowCount];
354 for (int row = 0; row < indexes.length; row++) {
355 newIndexes[row] = indexes[row];
356 }
357 for (int row = firstRow; row <= lastRow; row++) {
358 newIndexes[row] = row;
359 }
360 indexes = newIndexes;
361 }
362
363 }