1 /*****************************************************************************/ 2 /* Software Testing Automation Framework (STAF) */ 3 /* (C) Copyright IBM Corp. 2002 */ 4 /* */ 5 /* This software is licensed under the Eclipse Public License (EPL) V1.0. */ 6 /*****************************************************************************/ 7 8 package com.ibm.staf.service.stax; 9 10 import java.util.*; 11 import javax.swing.table.TableModel; 12 import javax.swing.event.TableModelEvent; 13 import java.awt.event.MouseAdapter; 14 import java.awt.event.MouseEvent; 15 import java.awt.event.InputEvent; 16 import javax.swing.JTable; 17 import javax.swing.table.JTableHeader; 18 import javax.swing.table.TableColumnModel; 19 20 public class STAXMonitorTableSorter extends STAXMonitorTableMap 21 { 22 int indexes[]; 23 Vector<Integer> sortingColumns = new Vector<Integer>(); 24 boolean ascending = true; 25 int compares; 26 int sortColumn = -1; // by default, don't sort on any column 27 String sortColumnHeader = ""; 28 String fontName = "Dialog"; 29 STAXMonitorTableSorter()30 public STAXMonitorTableSorter() 31 { 32 indexes = new int[0]; // for consistency 33 } 34 STAXMonitorTableSorter(STAXMonitorTableModel model)35 public STAXMonitorTableSorter(STAXMonitorTableModel model) 36 { 37 setModel(model); 38 } 39 STAXMonitorTableSorter(STAXMonitorTableModel model, int column)40 public STAXMonitorTableSorter(STAXMonitorTableModel model, int column) 41 { 42 setModel(model); 43 sortColumn = column; 44 } 45 STAXMonitorTableSorter(STAXMonitorTableModel model, int column, boolean ascending)46 public STAXMonitorTableSorter(STAXMonitorTableModel model, int column, 47 boolean ascending) 48 { 49 setModel(model); 50 sortColumn = column; 51 this.ascending = ascending; 52 } 53 STAXMonitorTableSorter(STAXMonitorTableModel model, int column, String theFontName)54 public STAXMonitorTableSorter(STAXMonitorTableModel model, int column, 55 String theFontName) 56 { 57 setModel(model); 58 sortColumn = column; 59 fontName = theFontName; 60 } 61 STAXMonitorTableSorter(STAXMonitorTableModel model, int column, boolean ascending, String theFontName)62 public STAXMonitorTableSorter(STAXMonitorTableModel model, int column, 63 boolean ascending, String theFontName) 64 { 65 setModel(model); 66 sortColumn = column; 67 fontName = theFontName; 68 this.ascending = ascending; 69 } 70 setModel(STAXMonitorTableModel model)71 public void setModel(STAXMonitorTableModel model) 72 { 73 super.setModel(model); 74 reallocateIndexes(); 75 } 76 compareRowsByColumn(int row1, int row2, int column)77 public int compareRowsByColumn(int row1, int row2, int column) 78 { 79 Class type = model.getColumnClass(column); 80 TableModel data = model; 81 82 // Check for nulls. 83 84 Object o1 = data.getValueAt(row1, column); 85 Object o2 = data.getValueAt(row2, column); 86 87 // If both values are null, return 0. 88 if (o1 == null && o2 == null) 89 { 90 return 0; 91 } 92 else if (o1 == null) 93 { // Define null less than everything. 94 return -1; 95 } 96 else if (o2 == null) 97 { 98 return 1; 99 } 100 101 /* 102 * We copy all returned values from the getValue call in case 103 * an optimised model is reusing one object to return many 104 * values. The Number subclasses in the JDK are immutable and 105 * so will not be used in this way but other subclasses of 106 * Number might want to do this to save space and avoid 107 * unnecessary heap allocation. 108 */ 109 110 if (type.getSuperclass() == java.lang.Number.class) 111 { 112 Number n1 = (Number)data.getValueAt(row1, column); 113 double d1 = n1.doubleValue(); 114 Number n2 = (Number)data.getValueAt(row2, column); 115 double d2 = n2.doubleValue(); 116 117 if (d1 < d2) 118 { 119 return -1; 120 } 121 else if (d1 > d2) 122 { 123 return 1; 124 } 125 else 126 { 127 return 0; 128 } 129 } 130 else if (type == java.util.Date.class) 131 { 132 Date d1 = (Date)data.getValueAt(row1, column); 133 long n1 = d1.getTime(); 134 Date d2 = (Date)data.getValueAt(row2, column); 135 long n2 = d2.getTime(); 136 137 if (n1 < n2) 138 { 139 return -1; 140 } 141 else if (n1 > n2) 142 { 143 return 1; 144 } 145 else 146 { 147 return 0; 148 } 149 } 150 else if (type == String.class) 151 { 152 String s1 = (String)data.getValueAt(row1, column); 153 String s2 = (String)data.getValueAt(row2, column); 154 int result = s1.compareTo(s2); 155 156 if (result < 0) 157 { 158 return -1; 159 } 160 else if (result > 0) 161 { 162 return 1; 163 } 164 else 165 { 166 return 0; 167 } 168 } 169 else if (type == Boolean.class) 170 { 171 Boolean bool1 = (Boolean)data.getValueAt(row1, column); 172 boolean b1 = bool1.booleanValue(); 173 Boolean bool2 = (Boolean)data.getValueAt(row2, column); 174 boolean b2 = bool2.booleanValue(); 175 176 if (b1 == b2) 177 { 178 return 0; 179 } 180 else if (b1) 181 { // Define false < true 182 return 1; 183 } 184 else 185 { 186 return -1; 187 } 188 } 189 else 190 { 191 Object v1 = data.getValueAt(row1, column); 192 String s1 = v1.toString(); 193 Object v2 = data.getValueAt(row2, column); 194 String s2 = v2.toString(); 195 int result = s1.compareTo(s2); 196 197 if (result < 0) 198 { 199 return -1; 200 } 201 else if (result > 0) 202 { 203 return 1; 204 } 205 else 206 { 207 return 0; 208 } 209 } 210 } 211 compare(int row1, int row2)212 public int compare(int row1, int row2) 213 { 214 compares++; 215 for (int level = 0; level < sortingColumns.size(); level++) 216 { 217 Integer column = sortingColumns.elementAt(level); 218 int result = compareRowsByColumn(row1, row2, column.intValue()); 219 if (result != 0) 220 { 221 return ascending ? result : -result; 222 } 223 } 224 return 0; 225 } 226 reallocateIndexes()227 public void reallocateIndexes() 228 { 229 int rowCount = model.getRowCount(); 230 231 // Set up a new array of indexes with the right number of elements 232 // for the new data model. 233 indexes = new int[rowCount]; 234 235 // Initialise with the identity mapping. 236 for (int row = 0; row < rowCount; row++) 237 { 238 indexes[row] = row; 239 } 240 } 241 sortTable()242 public void sortTable() 243 { 244 reallocateIndexes(); 245 246 if (sortColumn >= 0) 247 { 248 sortByColumn(sortColumn, ascending); 249 } 250 } 251 tableChanged(TableModelEvent e)252 public void tableChanged(TableModelEvent e) 253 { 254 if (e.getType() != TableModelEvent.UPDATE) 255 { 256 reallocateIndexes(); 257 258 if (sortColumn >= 0) 259 { 260 sortByColumn(sortColumn); 261 } 262 263 super.tableChanged(e); 264 } 265 } 266 checkModel()267 public void checkModel() 268 { 269 if (indexes.length != model.getRowCount()) 270 { 271 System.err.println("Sorter not informed of a change in model."); 272 } 273 } 274 sort(Object sender)275 public void sort(Object sender) 276 { 277 checkModel(); 278 279 compares = 0; 280 // n2sort(); 281 // qsort(0, indexes.length-1); 282 shuttlesort((int[])indexes.clone(), indexes, 0, indexes.length); 283 //System.out.println("Compares: "+compares); 284 } 285 n2sort()286 public void n2sort() 287 { 288 for (int i = 0; i < getRowCount(); i++) 289 { 290 for (int j = i+1; j < getRowCount(); j++) 291 { 292 if (compare(indexes[i], indexes[j]) == -1) 293 { 294 swap(i, j); 295 } 296 } 297 } 298 } 299 300 // This is a home-grown implementation which we have not had time 301 // to research - it may perform poorly in some circumstances. It 302 // requires twice the space of an in-place algorithm and makes 303 // NlogN assigments shuttling the values between the two 304 // arrays. The number of compares appears to vary between N-1 and 305 // NlogN depending on the initial order but the main reason for 306 // using it here is that, unlike qsort, it is stable. shuttlesort(int from[], int to[], int low, int high)307 public void shuttlesort(int from[], int to[], int low, int high) 308 { 309 if (high - low < 2) 310 { 311 return; 312 } 313 314 int middle = (low + high)/2; 315 shuttlesort(to, from, low, middle); 316 shuttlesort(to, from, middle, high); 317 318 int p = low; 319 int q = middle; 320 321 /* This is an optional short-cut; at each recursive call, 322 check to see if the elements in this subset are already 323 ordered. If so, no further comparisons are needed; the 324 sub-array can just be copied. The array must be copied rather 325 than assigned otherwise sister calls in the recursion might 326 get out of sinc. When the number of elements is three they 327 are partitioned so that the first set, [low, mid), has one 328 element and and the second, [mid, high), has two. We skip the 329 optimisation when the number of elements is three or less as 330 the first compare in the normal merge will produce the same 331 sequence of steps. This optimisation seems to be worthwhile 332 for partially ordered lists but some analysis is needed to 333 find out how the performance drops to Nlog(N) as the initial 334 order diminishes - it may drop very quickly. */ 335 336 if (high - low >= 4 && compare(from[middle-1], from[middle]) <= 0) 337 { 338 for (int i = low; i < high; i++) 339 { 340 to[i] = from[i]; 341 } 342 return; 343 } 344 345 // A normal merge. 346 347 for (int i = low; i < high; i++) 348 { 349 if (q >= high || (p < middle && compare(from[p], from[q]) <= 0)) 350 { 351 to[i] = from[p++]; 352 } 353 else 354 { 355 to[i] = from[q++]; 356 } 357 } 358 } 359 swap(int i, int j)360 public void swap(int i, int j) 361 { 362 int tmp = indexes[i]; 363 indexes[i] = indexes[j]; 364 indexes[j] = tmp; 365 } 366 367 // The mapping only affects the contents of the data rows. 368 // Pass all requests to these rows through the mapping array: "indexes". 369 getValueAt(int aRow, int aColumn)370 public Object getValueAt(int aRow, int aColumn) 371 { 372 checkModel(); 373 return model.getValueAt(indexes[aRow], aColumn); 374 } 375 setValueAt(Object aValue, int aRow, int aColumn)376 public void setValueAt(Object aValue, int aRow, int aColumn) 377 { 378 checkModel(); 379 model.setValueAt(aValue, indexes[aRow], aColumn); 380 381 fireTableChanged(new TableModelEvent(this, aRow, aRow, aColumn)); 382 } 383 sortByColumn(int column)384 public void sortByColumn(int column) 385 { 386 sortByColumn(column, this.ascending); 387 } 388 sortByColumn(int column, boolean ascending)389 public void sortByColumn(int column, boolean ascending) 390 { 391 this.ascending = ascending; 392 sortingColumns.removeAllElements(); 393 sortingColumns.addElement(new Integer(column)); 394 sortColumn = column; 395 String sortColumnHeader = this.getColumnName(column); 396 this.sortColumnHeader = sortColumnHeader; 397 sort(this); 398 super.tableChanged(new TableModelEvent(this)); 399 } 400 getSortColumn()401 public int getSortColumn() 402 { 403 return sortColumn; 404 } 405 getSortColumnHeader()406 public String getSortColumnHeader() 407 { 408 return sortColumnHeader; 409 } 410 isAscending()411 public boolean isAscending() 412 { 413 return ascending; 414 } 415 map(int row)416 public int map(int row) 417 { 418 // XXX: is there a better way to find the sorted index? 419 Vector<Integer> rowVector = new Vector<Integer>(); 420 for (int i = 0; i < indexes.length; i++) 421 { 422 rowVector.addElement(new Integer(indexes[i])); 423 } 424 return rowVector.indexOf(new Integer(row)); 425 } 426 addMouseListenerToHeaderInTable(JTable table, int column)427 public void addMouseListenerToHeaderInTable(JTable table, int column) 428 { 429 final STAXMonitorTableSorter sorter = this; 430 final JTable tableView = table; 431 final int multiLineColumn = column; 432 tableView.setColumnSelectionAllowed(false); 433 MouseAdapter listMouseListener = new MouseAdapter() 434 { 435 public void mouseClicked(MouseEvent e) 436 { 437 synchronized (tableView) 438 { 439 TableColumnModel columnModel = tableView.getColumnModel(); 440 int viewColumn = columnModel.getColumnIndexAtX(e.getX()); 441 int column = 442 tableView.convertColumnIndexToModel(viewColumn); 443 444 if (e.getClickCount() == 1 && column != -1) 445 { 446 int shiftPressed = 447 e.getModifiers()&InputEvent.SHIFT_MASK; 448 boolean ascending = (shiftPressed == 0); 449 sorter.sortByColumn(column, ascending); 450 451 tableView.setModel(sorter); 452 453 int multiLineViewColumn = 454 tableView.convertColumnIndexToView(multiLineColumn); 455 456 if (multiLineViewColumn == -1) 457 { 458 multiLineViewColumn = 0; 459 } 460 461 STAXMonitorUtil.updateRowHeights(tableView, 462 multiLineViewColumn, fontName); 463 } 464 } 465 } 466 }; 467 JTableHeader th = tableView.getTableHeader(); 468 th.addMouseListener(listMouseListener); 469 } 470 }