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 }