View Javadoc
1 /***
2 * A sorter for TableModels. The sorter has a model (conforming to TableModel)
3 * and itself implements TableModel. TableSorter does not store or copy
4 * the data in the TableModel, instead it maintains an array of
5 * integers which it keeps the same size as the number of rows in its
6 * model. When the model changes it notifies the sorter that something
7 * has changed eg. "rowsAdded" so that its internal array of integers
8 * can be reallocated. As requests are made of the sorter (like
9 * getValueAt(row, col) it redirects them to its model via the mapping
10 * array. That way the TableSorter appears to hold another copy of the table
11 * with the rows in a different order. The sorting algorthm used is stable
12 * which means that it does not move around rows when its comparison
13 * function returns 0 to denote that they are equivalent.
14 */
15
16 package dptool.config;
17
18 import java.util.*;
19
20 import javax.swing.table.TableModel;
21 import javax.swing.event.TableModelEvent;
22
23 // Imports for picking up mouse events from the JTable.
24
25 import java.awt.event.MouseAdapter;
26 import java.awt.event.MouseEvent;
27 import java.awt.event.InputEvent;
28 import javax.swing.JTable;
29 import javax.swing.table.JTableHeader;
30 import javax.swing.table.TableColumnModel;
31
32 /***
33 * Describe class <code>TableSorter</code> here.
34 *
35 * @version 1.5 12/17/97
36 * @author Philip Milne
37 */
38 public class TableSorter extends TableMap {
39
40 int indexes[];
41 Vector sortingColumns = new Vector();
42 boolean ascending = true;
43 int compares;
44
45 public TableSorter() {
46 indexes = new int[0]; // for consistency
47 }
48
49 public TableSorter(TableModel model) {
50 setModel(model);
51 }
52
53 public void setModel(TableModel model) {
54 super.setModel(model);
55 reallocateIndexes();
56 }
57
58 public int compareRowsByColumn(int row1, int row2, int column) {
59 Class type = model.getColumnClass(column);
60 TableModel data = model;
61
62 // Check for nulls.
63
64 Object o1 = data.getValueAt(row1, column);
65 Object o2 = data.getValueAt(row2, column);
66
67 // If both values are null, return 0.
68 if (o1 == null && o2 == null) {
69 return 0;
70 } else if (o1 == null) { // Define null less than everything.
71 return -1;
72 } else if (o2 == null) {
73 return 1;
74 }
75
76 /*
77 * We copy all returned values from the getValue call in case
78 * an optimised model is reusing one object to return many
79 * values. The Number subclasses in the JDK are immutable and
80 * so will not be used in this way but other subclasses of
81 * Number might want to do this to save space and avoid
82 * unnecessary heap allocation.
83 */
84
85 if (type.getSuperclass() == java.lang.Number.class) {
86 Number n1 = (Number)data.getValueAt(row1, column);
87 double d1 = n1.doubleValue();
88 Number n2 = (Number)data.getValueAt(row2, column);
89 double d2 = n2.doubleValue();
90
91 if (d1 < d2) {
92 return -1;
93 } else if (d1 > d2) {
94 return 1;
95 } else {
96 return 0;
97 }
98 } else if (type == java.util.Date.class) {
99 Date d1 = (Date)data.getValueAt(row1, column);
100 long n1 = d1.getTime();
101 Date d2 = (Date)data.getValueAt(row2, column);
102 long n2 = d2.getTime();
103
104 if (n1 < n2) {
105 return -1;
106 } else if (n1 > n2) {
107 return 1;
108 } else {
109 return 0;
110 }
111 } else if (type == String.class) {
112 String s1 = (String)data.getValueAt(row1, column);
113 String s2 = (String)data.getValueAt(row2, column);
114 int result = s1.compareTo(s2);
115
116 if (result < 0) {
117 return -1;
118 } else if (result > 0) {
119 return 1;
120 } else {
121 return 0;
122 }
123 } else if (type == Boolean.class) {
124 Boolean bool1 = (Boolean)data.getValueAt(row1, column);
125 boolean b1 = bool1.booleanValue();
126 Boolean bool2 = (Boolean)data.getValueAt(row2, column);
127 boolean b2 = bool2.booleanValue();
128
129 if (b1 == b2) {
130 return 0;
131 } else if (b1) { // Define false < true
132 return 1;
133 } else {
134 return -1;
135 }
136 } else {
137 Object v1 = data.getValueAt(row1, column);
138 String s1 = v1.toString();
139 Object v2 = data.getValueAt(row2, column);
140 String s2 = v2.toString();
141 int result = s1.compareTo(s2);
142
143 if (result < 0) {
144 return -1;
145 } else if (result > 0) {
146 return 1;
147 } else {
148 return 0;
149 }
150 }
151 }
152
153 public int compare(int row1, int row2) {
154 compares++;
155 for (int level = 0; level < sortingColumns.size(); level++) {
156 Integer column = (Integer)sortingColumns.elementAt(level);
157 int result = compareRowsByColumn(row1, row2, column.intValue());
158 if (result != 0) {
159 return ascending ? result : -result;
160 }
161 }
162 return 0;
163 }
164
165 public void reallocateIndexes() {
166 int rowCount = model.getRowCount();
167
168 // Set up a new array of indexes with the right number of elements
169 // for the new data model.
170 indexes = new int[rowCount];
171
172 // Initialise with the identity mapping.
173 for (int row = 0; row < rowCount; row++) {
174 indexes[row] = row;
175 }
176 }
177
178 public void tableChanged(TableModelEvent e) {
179 //System.out.println("Sorter: tableChanged");
180 reallocateIndexes();
181
182 super.tableChanged(e);
183 }
184
185 public void checkModel() {
186 if (indexes.length != model.getRowCount()) {
187 System.err.println("Sorter not informed of a change in model.");
188 }
189 }
190
191 public void sort(Object sender) {
192 checkModel();
193
194 compares = 0;
195 // n2sort();
196 // qsort(0, indexes.length-1);
197 shuttlesort((int[])indexes.clone(), indexes, 0, indexes.length);
198 //System.out.println("Compares: "+compares);
199 }
200
201 public void n2sort() {
202 for (int i = 0; i < getRowCount(); i++) {
203 for (int j = i+1; j < getRowCount(); j++) {
204 if (compare(indexes[i], indexes[j]) == -1) {
205 swap(i, j);
206 }
207 }
208 }
209 }
210
211 // This is a home-grown implementation which we have not had time
212 // to research - it may perform poorly in some circumstances. It
213 // requires twice the space of an in-place algorithm and makes
214 // NlogN assigments shuttling the values between the two
215 // arrays. The number of compares appears to vary between N-1 and
216 // NlogN depending on the initial order but the main reason for
217 // using it here is that, unlike qsort, it is stable.
218 public void shuttlesort(int from[], int to[], int low, int high) {
219 if (high - low < 2) {
220 return;
221 }
222 int middle = (low + high)/2;
223 shuttlesort(to, from, low, middle);
224 shuttlesort(to, from, middle, high);
225
226 int p = low;
227 int q = middle;
228
229 /* This is an optional short-cut; at each recursive call,
230 check to see if the elements in this subset are already
231 ordered. If so, no further comparisons are needed; the
232 sub-array can just be copied. The array must be copied rather
233 than assigned otherwise sister calls in the recursion might
234 get out of sinc. When the number of elements is three they
235 are partitioned so that the first set, [low, mid), has one
236 element and and the second, [mid, high), has two. We skip the
237 optimisation when the number of elements is three or less as
238 the first compare in the normal merge will produce the same
239 sequence of steps. This optimisation seems to be worthwhile
240 for partially ordered lists but some analysis is needed to
241 find out how the performance drops to Nlog(N) as the initial
242 order diminishes - it may drop very quickly. */
243
244 if (high - low >= 4 && compare(from[middle-1], from[middle]) <= 0) {
245 for (int i = low; i < high; i++) {
246 to[i] = from[i];
247 }
248 return;
249 }
250
251 // A normal merge.
252
253 for (int i = low; i < high; i++) {
254 if (q >= high || (p < middle && compare(from[p], from[q]) <= 0)) {
255 to[i] = from[p++];
256 }
257 else {
258 to[i] = from[q++];
259 }
260 }
261 }
262
263 public void swap(int i, int j) {
264 int tmp = indexes[i];
265 indexes[i] = indexes[j];
266 indexes[j] = tmp;
267 }
268
269 // The mapping only affects the contents of the data rows.
270 // Pass all requests to these rows through the mapping array: "indexes".
271
272 public Object getValueAt(int aRow, int aColumn) {
273 checkModel();
274 return model.getValueAt(indexes[aRow], aColumn);
275 }
276
277 public void setValueAt(Object aValue, int aRow, int aColumn) {
278 checkModel();
279 model.setValueAt(aValue, indexes[aRow], aColumn);
280 }
281
282 public void sortByColumn(int column) {
283 sortByColumn(column, true);
284 }
285
286 public void sortByColumn(int column, boolean ascending) {
287 this.ascending = ascending;
288 sortingColumns.removeAllElements();
289 sortingColumns.addElement(new Integer(column));
290 sort(this);
291 super.tableChanged(new TableModelEvent(this));
292 }
293
294 // There is no-where else to put this.
295 // Add a mouse listener to the Table to trigger a table sort
296 // when a column heading is clicked in the JTable.
297 public void addMouseListenerToHeaderInTable(JTable table) {
298 final TableSorter sorter = this;
299 final JTable tableView = table;
300 tableView.setColumnSelectionAllowed(false);
301 MouseAdapter listMouseListener = new MouseAdapter() {
302 public void mouseClicked(MouseEvent e) {
303 TableColumnModel columnModel = tableView.getColumnModel();
304 int viewColumn = columnModel.getColumnIndexAtX(e.getX());
305 int column = tableView.convertColumnIndexToModel(viewColumn);
306 if (e.getClickCount() == 1 && column != -1) {
307 //System.out.println("Sorting ...");
308 int shiftPressed = e.getModifiers()&InputEvent.SHIFT_MASK;
309 boolean ascending = (shiftPressed == 0);
310 sorter.sortByColumn(column, ascending);
311 }
312 }
313 };
314 JTableHeader th = tableView.getTableHeader();
315 th.addMouseListener(listMouseListener);
316 }
317 }
This page was automatically generated by Maven