1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31 /***
32 * A sorter for TableModels. The sorter has a model (conforming to TableModel)
33 * and itself implements TableModel. TableSorter does not store or copy
34 * the data in the TableModel, instead it maintains an array of
35 * integers which it keeps the same size as the number of rows in its
36 * model. When the model changes it notifies the sorter that something
37 * has changed eg. "rowsAdded" so that its internal array of integers
38 * can be reallocated. As requests are made of the sorter (like
39 * getValueAt(row, col) it redirects them to its model via the mapping
40 * array. That way the TableSorter appears to hold another copy of the table
41 * with the rows in a different order. The sorting algorthm used is stable
42 * which means that it does not move around rows when its comparison
43 * function returns 0 to denote that they are equivalent.
44 *
45 * @version 1.8 04/23/99
46 * @author Philip Milne
47 */
48 package org.xnap.commons.gui.table;
49
50 import java.util.ArrayList;
51 import javax.swing.event.TableModelEvent;
52 import javax.swing.event.TableModelListener;
53 import javax.swing.table.AbstractTableModel;
54 import javax.swing.table.TableModel;
55
56 public class TableSorter extends AbstractTableModel implements SortableModel
57 {
58
59 protected int indexes[] = new int[0];
60 protected int revIndexes[] = new int[0];
61 protected ArrayList<Integer> sortingColumns = new ArrayList<Integer>();
62 protected Order sortOrder = Order.UNSORTED;
63 /***
64 * Counts number of compares.
65 */
66 protected int compares;
67 protected int lastSortedColumn = -1;
68 protected boolean maintainSortOrder;
69 private TableModel tableModel;
70 private TableModelListener tableModelListener;
71
72 public TableSorter()
73 {
74 this.tableModelListener = new TableModelHandler();
75 }
76
77 public TableSorter(TableModel tableModel)
78 {
79 this();
80
81 setTableModel(tableModel);
82 }
83
84 public TableModel getTableModel() {
85 return tableModel;
86 }
87
88
89
90 public int getRowCount() {
91 return (tableModel == null) ? 0 : tableModel.getRowCount();
92 }
93
94 public int getColumnCount() {
95 return (tableModel == null) ? 0 : tableModel.getColumnCount();
96 }
97
98 public String getColumnName(int column) {
99 return tableModel.getColumnName(column);
100 }
101
102 public Class<?> getColumnClass(int column) {
103 return tableModel.getColumnClass(column);
104 }
105
106 public boolean isCellEditable(int row, int column) {
107 return tableModel.isCellEditable(mapToIndex(row), column);
108 }
109
110 public Object getValueAt(int row, int column) {
111 synchronized (indexes) {
112 return tableModel.getValueAt(mapToIndex(row), column);
113 }
114 }
115
116 public void setValueAt(Object aValue, int row, int column) {
117 synchronized (indexes) {
118 tableModel.setValueAt(aValue, mapToIndex(row), column);
119 }
120 }
121
122 public int getSortedColumn()
123 {
124 return lastSortedColumn;
125 }
126
127 public Order getSortOrder()
128 {
129 return sortOrder;
130 }
131
132 /***
133 * Returns the mapped row index.
134 */
135 public int mapToIndex(int i)
136 {
137 return indexes[i];
138 }
139
140 public void setMaintainSortOrder(boolean newValue)
141 {
142 maintainSortOrder = newValue;
143 }
144
145 public void setSortOrder(Order newValue)
146 {
147 if (newValue == null) {
148 throw new IllegalArgumentException();
149 }
150
151 sortOrder = newValue;
152 }
153
154 public void setTableModel(TableModel tableModel) {
155 if (this.tableModel != null) {
156 this.tableModel.removeTableModelListener(tableModelListener);
157 }
158
159 this.tableModel = tableModel;
160 if (this.tableModel != null) {
161 this.tableModel.addTableModelListener(tableModelListener);
162 }
163
164 reallocateIndexes();
165 fireTableStructureChanged();
166 }
167
168 /***
169 * Sorts the table.
170 *
171 * @return true, if table is sorted ascending; false, if descending
172 */
173 public Order sortByColumn(int column, Order sortOrder, boolean revert)
174 {
175
176 if (column < 0 || column >= getColumnCount()) {
177 throw new IllegalArgumentException("Column is invalid");
178 }
179
180 sortingColumns.clear();
181 sortingColumns.add(new Integer(column));
182
183 setSortOrder(sortOrder);
184
185 if (!sort() && revert) {
186 setSortOrder(sortOrder.next());
187 sort();
188 }
189 lastSortedColumn = column;
190
191 return getSortOrder();
192 }
193
194 public void resort()
195 {
196 if (lastSortedColumn != -1) {
197 sortByColumn(lastSortedColumn, getSortOrder(), false);
198 }
199 }
200
201 /***
202 * Compares two rows.
203 */
204 protected int compare(int row1, int row2)
205 {
206 compares++;
207 for(int level = 0; level < sortingColumns.size(); level++) {
208 int column = sortingColumns.get(level);
209 int result = compareRowsByColumn(row1, row2, column);
210 if (result != 0) {
211 return (sortOrder == Order.ASCENDING) ? result : -result;
212 }
213 }
214 return 0;
215 }
216
217 /***
218 * Compares two rows by a column.
219 */
220 @SuppressWarnings("unchecked")
221 protected int compareRowsByColumn(int row1, int row2, int column)
222 {
223 Class type = getColumnClass(column);
224
225
226 Object o1 = tableModel.getValueAt(row1, column);
227 Object o2 = tableModel.getValueAt(row2, column);
228
229
230
231 if (o1 == null && o2 == null) {
232 return 0;
233 } else if (o1 == null) {
234 return -1;
235 } else if (o2 == null) {
236 return 1;
237 }
238
239
240
241
242
243
244
245 if (type == String.class) {
246 String s1 = (String)o1;
247 String s2 = (String)o2;
248 int result = s1.compareToIgnoreCase(s2);
249
250
251 if (s1.length() == 0 ^ s2.length() == 0) {
252 result = -result;
253 }
254
255 return result;
256 }
257 else if (o1 instanceof Comparable) {
258 return ((Comparable)o1).compareTo(o2);
259 }
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276 else {
277
278 return o1.toString().compareTo(o2.toString());
279 }
280 }
281
282 protected void reallocateIndexes(TableModelEvent e)
283 {
284 int rowCount = getRowCount();
285
286 if (rowCount == indexes.length)
287 return;
288
289
290
291 int newIndexes[] = new int[rowCount];
292 int newRevIndexes[] = new int[rowCount];
293
294 synchronized (indexes) {
295 int row = 0;
296
297 if (e != null && e.getType() == TableModelEvent.DELETE) {
298
299
300
301 int skipped = 0;
302 for(; row < indexes.length; row++) {
303 int dec = 0;
304 boolean skip = false;
305 for (int i = e.getFirstRow(); i <= e.getLastRow(); i++) {
306 if (i < indexes[row])
307 dec++;
308 else if (i == indexes[row])
309 skip = true;
310 }
311 if (skip) {
312 skipped++;
313 continue;
314 }
315
316 newIndexes[row - skipped] = indexes[row] - dec;
317 newRevIndexes[indexes[row] - dec] = row - skipped;
318 }
319 }
320 else if (e != null && (e.getType() == TableModelEvent.UPDATE
321 && e.getLastRow() >= indexes.length)) {
322
323 for (int i = 0; i < rowCount; i++) {
324 newIndexes[i] = i;
325 newRevIndexes[i] = i;
326 }
327 }
328 else {
329
330
331 for(; row < indexes.length && row < rowCount; row++) {
332 newIndexes[row] = indexes[row];
333 newRevIndexes[row] = revIndexes[row];
334 }
335 for(; row < rowCount; row++) {
336 newIndexes[row] = row;
337 newRevIndexes[row] = row;
338 }
339
340 }
341
342 indexes = newIndexes;
343 revIndexes = newRevIndexes;
344 }
345 }
346
347 protected void reallocateIndexes()
348 {
349 reallocateIndexes(null);
350 }
351
352 /***
353 * Returns false if nothing has changed.
354 */
355 protected boolean sort()
356 {
357 synchronized (indexes) {
358 if (sortOrder == Order.UNSORTED) {
359 for(int i = 0; i < indexes.length; i++) {
360 indexes[i] = i;
361 revIndexes[i] = i;
362 }
363 return false;
364 }
365
366 compares = 0;
367
368
369
370 int[] oldIndexes = new int[indexes.length];
371 System.arraycopy(indexes, 0, oldIndexes, 0, indexes.length);
372
373 if (shuttlesort(oldIndexes, indexes, 0, indexes.length)) {
374
375
376
377
378
379
380 for(int i = 0; i < indexes.length; i++) {
381 revIndexes[indexes[i]] = i;
382 }
383
384 fireTableChanged(new TableModelEvent(this));
385
386 return true;
387 }
388 else {
389 return false;
390 }
391 }
392 }
393
394 /***
395 * Returns false if nothing has changed.
396 */
397
398
399
400
401
402
403
404 public boolean shuttlesort(int from[], int to[], int low, int high)
405 {
406 if (high - low < 2) {
407 return false;
408 }
409
410 boolean changed = false;
411 int middle = (low + high) / 2;
412 changed |= shuttlesort(to, from, low, middle);
413 changed |= shuttlesort(to, from, middle, high);
414
415 int p = low;
416 int q = middle;
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433 if (high - low >= 4) {
434 if (compare(from[middle-1], from[middle]) <= 0) {
435 for (int i = low; i < high; i++) {
436 to[i] = from[i];
437 }
438 return changed;
439 } else {
440
441 if (compare(from[high-1], from[low]) < 0) {
442 int i = low;
443 for( ; q < high; q++) {
444 to[i++] = from[q];
445 }
446 for( ; i < high; i++) {
447 to[i] = from[p++];
448 }
449 return changed;
450 }
451 }
452 }
453
454
455 for (int i = low; i < high; i++) {
456 if (q >= high || (p < middle && compare(from[p], from[q]) <= 0)) {
457 to[i] = from[p++];
458 }
459 else {
460 changed |= (p < middle);
461 to[i] = from[q++];
462 }
463 }
464
465 return changed;
466 }
467
468 private class TableModelHandler implements TableModelListener
469 {
470 /***
471 * We need to mangle these events to fire the right indicies.
472 */
473 public void tableChanged(TableModelEvent e)
474 {
475 reallocateIndexes(e);
476
477 if (maintainSortOrder) {
478
479 if (sort()) {
480
481 return;
482 }
483 }
484
485 if (e.getType() == TableModelEvent.DELETE
486 || (e.getType() == TableModelEvent.UPDATE &&
487 e.getLastRow() >= revIndexes.length)) {
488
489 fireTableChanged(new TableModelEvent(TableSorter.this));
490 }
491 else {
492 for (int i = e.getFirstRow(); i <= e.getLastRow(); i++) {
493 TableModelEvent t = new TableModelEvent
494 (TableSorter.this, revIndexes[i], revIndexes[i], e.getColumn(),
495 e.getType());
496 fireTableChanged(t);
497 }
498 }
499 }
500 }
501
502 public boolean getMaintainSortOrder()
503 {
504 return maintainSortOrder;
505 }
506
507 }