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