KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > mc4j > console > swing > table > TableSorter


1 /*
2  * Copyright 2002-2004 Greg Hinkle
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16
17 package org.mc4j.console.swing.table;
18
19
20 import java.awt.event.MouseAdapter JavaDoc;
21 import java.awt.event.MouseEvent JavaDoc;
22 import java.util.Date JavaDoc;
23 import java.util.Vector JavaDoc;
24
25 import javax.swing.JTable JavaDoc;
26 import javax.swing.event.TableModelEvent JavaDoc;
27 import javax.swing.table.JTableHeader JavaDoc;
28 import javax.swing.table.TableColumnModel JavaDoc;
29 import javax.swing.table.TableModel JavaDoc;
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.5 12/17/97
46  * @author Philip Milne
47  */

48 public class TableSorter extends TableMap {
49     int indexes[];
50     Vector JavaDoc sortingColumns = new Vector JavaDoc();
51     boolean ascending = true;
52     int compares;
53
54     int currentSortColumn;
55
56     public TableSorter() {
57         indexes = new int[0]; // for consistency
58
}
59
60     public TableSorter(TableModel JavaDoc model) {
61         setModel(model);
62     }
63
64     public void setModel(TableModel JavaDoc model) {
65         super.setModel(model);
66         reallocateIndexes();
67     }
68
69     public int getCurrentSortColumn() {
70         return currentSortColumn;
71     }
72
73     public void setCurrentSortColumn(int currentSortColumn) {
74         this.currentSortColumn = currentSortColumn;
75     }
76
77     public boolean isAscending() {
78         return ascending;
79     }
80
81     public int compareRowsByColumn(int row1, int row2, int column) {
82         Class JavaDoc type = model.getColumnClass(column);
83         TableModel JavaDoc data = model;
84
85         // Check for nulls.
86

87         Object JavaDoc o1 = data.getValueAt(row1, column);
88         Object JavaDoc o2 = data.getValueAt(row2, column);
89
90         // If both values are null, return 0.
91
if (o1 == null && o2 == null) {
92             return 0;
93         } else if (o1 == null) { // Define null less than everything.
94
return -1;
95         } else if (o2 == null) {
96             return 1;
97         }
98
99         /*
100          * We copy all returned values from the getValue call in case
101          * an optimised model is reusing one object to return many
102          * values. The Number subclasses in the JDK are immutable and
103          * so will not be used in this way but other subclasses of
104          * Number might want to do this to save space and avoid
105          * unnecessary heap allocation.
106          */

107
108         if (type.getSuperclass() == java.lang.Number JavaDoc.class) {
109             Number JavaDoc n1 = (Number JavaDoc)data.getValueAt(row1, column);
110             double d1 = n1.doubleValue();
111             Number JavaDoc n2 = (Number JavaDoc)data.getValueAt(row2, column);
112             double d2 = n2.doubleValue();
113
114             if (d1 < d2) {
115                 return -1;
116             } else if (d1 > d2) {
117                 return 1;
118             } else {
119                 return 0;
120             }
121         } else if (type == java.util.Date JavaDoc.class) {
122             Date JavaDoc d1 = (Date JavaDoc)data.getValueAt(row1, column);
123             long n1 = d1.getTime();
124             Date JavaDoc d2 = (Date JavaDoc)data.getValueAt(row2, column);
125             long n2 = d2.getTime();
126
127             if (n1 < n2) {
128                 return -1;
129             } else if (n1 > n2) {
130                 return 1;
131             } else {
132                 return 0;
133             }
134         } else if (type == String JavaDoc.class) {
135             String JavaDoc s1 = (String JavaDoc)data.getValueAt(row1, column);
136             String JavaDoc s2 = (String JavaDoc)data.getValueAt(row2, column);
137             int result = s1.compareTo(s2);
138
139             if (result < 0) {
140                 return -1;
141             } else if (result > 0) {
142                 return 1;
143             } else {
144                 return 0;
145             }
146         } else if (type == Boolean JavaDoc.class) {
147             Boolean JavaDoc bool1 = (Boolean JavaDoc)data.getValueAt(row1, column);
148             boolean b1 = bool1.booleanValue();
149             Boolean JavaDoc bool2 = (Boolean JavaDoc)data.getValueAt(row2, column);
150             boolean b2 = bool2.booleanValue();
151
152             if (b1 == b2) {
153                 return 0;
154             } else if (b1) { // Define false < true
155
return 1;
156             } else {
157                 return -1;
158             }
159         } else {
160             Object JavaDoc v1 = data.getValueAt(row1, column);
161             String JavaDoc s1 = v1.toString();
162             Object JavaDoc v2 = data.getValueAt(row2, column);
163             String JavaDoc s2 = v2.toString();
164             int result = s1.compareTo(s2);
165
166             if (result < 0) {
167                 return -1;
168             } else if (result > 0) {
169                 return 1;
170             } else {
171                 return 0;
172             }
173         }
174     }
175
176     public int compare(int row1, int row2) {
177         compares++;
178         for (int level = 0; level < sortingColumns.size(); level++) {
179             Integer JavaDoc column = (Integer JavaDoc)sortingColumns.elementAt(level);
180             int result = compareRowsByColumn(row1, row2, column.intValue());
181             if (result != 0) {
182                 return ascending ? result : -result;
183             }
184         }
185         return 0;
186     }
187
188     public void reallocateIndexes() {
189         int rowCount = model.getRowCount();
190
191         // Set up a new array of indexes with the right number of elements
192
// for the new data model.
193
indexes = new int[rowCount];
194
195         // Initialise with the identity mapping.
196
for (int row = 0; row < rowCount; row++) {
197             indexes[row] = row;
198         }
199     }
200
201     public void tableChanged(TableModelEvent JavaDoc e) {
202         //System.out.println("Sorter: tableChanged");
203
reallocateIndexes();
204         sort(this);
205
206         super.tableChanged(e);
207     }
208
209     public void checkModel() {
210         if (indexes.length != model.getRowCount()) {
211             System.err.println("Sorter not informed of a change in model.");
212         }
213     }
214
215     public void sort(Object JavaDoc sender) {
216         checkModel();
217
218         compares = 0;
219         // n2sort();
220
// qsort(0, indexes.length-1);
221
shuttlesort((int[])indexes.clone(), indexes, 0, indexes.length);
222         //System.out.println("Compares: "+compares);
223
}
224
225     public void n2sort() {
226         for (int i = 0; i < getRowCount(); i++) {
227             for (int j = i+1; j < getRowCount(); j++) {
228                 if (compare(indexes[i], indexes[j]) == -1) {
229                     swap(i, j);
230                 }
231             }
232         }
233     }
234
235     // This is a home-grown implementation which we have not had time
236
// to research - it may perform poorly in some circumstances. It
237
// requires twice the space of an in-place algorithm and makes
238
// NlogN assigments shuttling the values between the two
239
// arrays. The number of compares appears to vary between N-1 and
240
// NlogN depending on the initial order but the main reason for
241
// using it here is that, unlike qsort, it is stable.
242
public void shuttlesort(int from[], int to[], int low, int high) {
243         if (high - low < 2) {
244             return;
245         }
246         int middle = (low + high)/2;
247         shuttlesort(to, from, low, middle);
248         shuttlesort(to, from, middle, high);
249
250         int p = low;
251         int q = middle;
252
253         /* This is an optional short-cut; at each recursive call,
254         check to see if the elements in this subset are already
255         ordered. If so, no further comparisons are needed; the
256         sub-array can just be copied. The array must be copied rather
257         than assigned otherwise sister calls in the recursion might
258         get out of sinc. When the number of elements is three they
259         are partitioned so that the first set, [low, mid), has one
260         element and and the second, [mid, high), has two. We skip the
261         optimisation when the number of elements is three or less as
262         the first compare in the normal merge will produce the same
263         sequence of steps. This optimisation seems to be worthwhile
264         for partially ordered lists but some analysis is needed to
265         find out how the performance drops to Nlog(N) as the initial
266         order diminishes - it may drop very quickly. */

267
268         if (high - low >= 4 && compare(from[middle-1], from[middle]) <= 0) {
269             for (int i = low; i < high; i++) {
270                 to[i] = from[i];
271             }
272             return;
273         }
274
275         // A normal merge.
276

277         for (int i = low; i < high; i++) {
278             if (q >= high || (p < middle && compare(from[p], from[q]) <= 0)) {
279                 to[i] = from[p++];
280             }
281             else {
282                 to[i] = from[q++];
283             }
284         }
285     }
286
287     public void swap(int i, int j) {
288         int tmp = indexes[i];
289         indexes[i] = indexes[j];
290         indexes[j] = tmp;
291     }
292
293     // The mapping only affects the contents of the data rows.
294
// Pass all requests to these rows through the mapping array: "indexes".
295

296     public Object JavaDoc getValueAt(int aRow, int aColumn) {
297         checkModel();
298         return model.getValueAt(indexes[aRow], aColumn);
299     }
300
301     public int translateRow(int row) {
302         return indexes[row];
303     }
304
305     public void setValueAt(Object JavaDoc aValue, int aRow, int aColumn) {
306         checkModel();
307         model.setValueAt(aValue, indexes[aRow], aColumn);
308     }
309
310     public void sortByColumn(int column) {
311         sortByColumn(column, true);
312     }
313
314     public void sortByColumn(int column, boolean ascending) {
315
316         setCurrentSortColumn(column);
317         this.ascending = ascending;
318         sortingColumns.removeAllElements();
319         sortingColumns.addElement(new Integer JavaDoc(column));
320         sort(this);
321         super.tableChanged(new TableModelEvent JavaDoc(this));
322
323     }
324
325     // There is no-where else to put this.
326
// Add a mouse listener to the Table to trigger a table sort
327
// when a column heading is clicked in the JTable.
328
public void addMouseListenerToHeaderInTable(JTable JavaDoc table) {
329         final TableSorter sorter = this;
330         final JTable JavaDoc tableView = table;
331         tableView.setColumnSelectionAllowed(false);
332         MouseAdapter JavaDoc listMouseListener = new MouseAdapter JavaDoc() {
333             public void mouseClicked(MouseEvent JavaDoc e) {
334                 TableColumnModel JavaDoc columnModel = tableView.getColumnModel();
335                 int viewColumn = columnModel.getColumnIndexAtX(e.getX());
336                 int column = tableView.convertColumnIndexToModel(viewColumn);
337                 if (e.getClickCount() == 1 && column != -1) {
338                     //System.out.println("Sorting ...");
339
//int shiftPressed = e.getModifiers()&InputEvent.SHIFT_MASK;
340
boolean ascending = sorter.isAscending();
341                     if (currentSortColumn == column) {
342                         ascending = !ascending;
343                     }
344
345                     sorter.sortByColumn(column, ascending);
346                     // Repaint the header with the new sort info
347
tableView.getTableHeader().repaint();
348                 }
349             }
350         };
351         JTableHeader JavaDoc th = tableView.getTableHeader();
352         th.addMouseListener(listMouseListener);
353     }
354 }
355
Popular Tags