KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > ejtools > swing > table > TableModelSorter


1 /*
2  * EJTools, the Enterprise Java Tools
3  *
4  * Distributable under LGPL license.
5  * See terms of license at www.gnu.org.
6  */

7 package org.ejtools.swing.table;
8
9 import java.awt.event.InputEvent JavaDoc;
10 import java.awt.event.MouseAdapter JavaDoc;
11 import java.awt.event.MouseEvent JavaDoc;
12 import java.util.Date JavaDoc;
13 import java.util.Vector JavaDoc;
14
15 import javax.swing.JTable JavaDoc;
16 import javax.swing.event.TableModelEvent JavaDoc;
17 import javax.swing.table.JTableHeader JavaDoc;
18 import javax.swing.table.TableColumnModel JavaDoc;
19 import javax.swing.table.TableModel JavaDoc;
20
21 /**
22  * @author Laurent Etiemble
23  * @version $Revision: 1.8 $
24  */

25 public class TableModelSorter extends TableModelIndexed
26 {
27    /** Description of the Field */
28    protected boolean ascending = true;
29    /** Description of the Field */
30    protected int column = -1;
31    /** Description of the Field */
32    protected int compares;
33    /** Description of the Field */
34    protected Vector JavaDoc sortingColumns = new Vector JavaDoc();
35
36
37    /**
38     * Constructor for TableModelSorter.
39     *
40     * @param model
41     */

42    public TableModelSorter(TableModel JavaDoc model)
43    {
44       super(model);
45       this.reallocateIndexes();
46    }
47
48
49    /**
50     * Add a mouse listener to the Table to trigger a table sort when a column
51     * heading is clicked in the JTable.
52     *
53     * @param table The feature to be added to the MouseListenerToHeaderInTable
54     * attribute
55     */

56    public void addMouseListenerToHeaderInTable(JTable JavaDoc table)
57    {
58       final TableModelSorter tableSorter = this;
59       final JTable JavaDoc tableView = table;
60       tableView.setColumnSelectionAllowed(false);
61       MouseAdapter JavaDoc listMouseListener =
62          new MouseAdapter JavaDoc()
63          {
64             public void mouseClicked(MouseEvent JavaDoc e)
65             {
66                TableColumnModel JavaDoc columnModel = tableView.getColumnModel();
67                int viewColumn = columnModel.getColumnIndexAtX(e.getX());
68                int column = tableView.convertColumnIndexToModel(viewColumn);
69                if (e.getClickCount() == 1 && column != -1)
70                {
71                   int shiftPressed = e.getModifiers() & InputEvent.SHIFT_MASK;
72                   boolean ascending = (shiftPressed == 0);
73                   tableSorter.sortByColumn(column, ascending);
74                }
75             }
76          };
77       JTableHeader JavaDoc th = tableView.getTableHeader();
78       th.addMouseListener(listMouseListener);
79    }
80
81
82    /** Description of the Method */
83    public void checkModel()
84    {
85       if (indexes.length != model.getRowCount())
86       {
87          System.err.println("Sorter not informed of a change in model.");
88       }
89    }
90
91
92    /**
93     * Description of the Method
94     *
95     * @param row1 Description of the Parameter
96     * @param row2 Description of the Parameter
97     * @return Description of the Return Value
98     */

99    public int compare(int row1, int row2)
100    {
101       this.compares++;
102       for (int level = 0; level < this.sortingColumns.size(); level++)
103       {
104          Integer JavaDoc column = (Integer JavaDoc) this.sortingColumns.elementAt(level);
105          int result = this.compareRowsByColumn(row1, row2, column.intValue());
106          if (result != 0)
107          {
108             return this.ascending ? result : -result;
109          }
110       }
111       return 0;
112    }
113
114
115
116    /**
117     * Description of the Method
118     *
119     * @param row1 Description of the Parameter
120     * @param row2 Description of the Parameter
121     * @param column Description of the Parameter
122     * @return Description of the Return Value
123     */

124    public int compareRowsByColumn(int row1, int row2, int column)
125    {
126       Class JavaDoc type = this.model.getColumnClass(column);
127       TableModel JavaDoc data = this.model;
128
129       // Check for nulls.
130
Object JavaDoc o1 = data.getValueAt(row1, column);
131       Object JavaDoc o2 = data.getValueAt(row2, column);
132
133       // If both values are null, return 0.
134
if (o1 == null && o2 == null)
135       {
136          return 0;
137       }
138       else if (o1 == null)
139       {
140          // Define null less than everything.
141
return -1;
142       }
143       else if (o2 == null)
144       {
145          return 1;
146       }
147
148       /*
149        * We copy all returned values from the getValue call in case
150        * an optimised model is reusing one object to return many
151        * values. The Number subclasses in the JDK are immutable and
152        * so will not be used in this way but other subclasses of
153        * Number might want to do this to save space and avoid
154        * unnecessary heap allocation.
155        */

156       if (Comparable JavaDoc.class.isAssignableFrom(type))
157       {
158          Comparable JavaDoc n1 = (Comparable JavaDoc) data.getValueAt(row1, column);
159          Comparable JavaDoc n2 = (Comparable JavaDoc) data.getValueAt(row2, column);
160
161          int result = n1.compareTo(n2);
162
163          if (result < 0)
164          {
165             return -1;
166          }
167          else if (result > 0)
168          {
169             return 1;
170          }
171          else
172          {
173             return 0;
174          }
175       }
176       else if (Number JavaDoc.class.isAssignableFrom(type))
177       {
178          Number JavaDoc n1 = (Number JavaDoc) data.getValueAt(row1, column);
179          double d1 = n1.doubleValue();
180          Number JavaDoc n2 = (Number JavaDoc) data.getValueAt(row2, column);
181          double d2 = n2.doubleValue();
182
183          if (d1 < d2)
184          {
185             return -1;
186          }
187          else if (d1 > d2)
188          {
189             return 1;
190          }
191          else
192          {
193             return 0;
194          }
195       }
196       else if (type == Date JavaDoc.class)
197       {
198          Date JavaDoc d1 = (Date JavaDoc) data.getValueAt(row1, column);
199          long n1 = d1.getTime();
200          Date JavaDoc d2 = (Date JavaDoc) data.getValueAt(row2, column);
201          long n2 = d2.getTime();
202
203          if (n1 < n2)
204          {
205             return -1;
206          }
207          else if (n1 > n2)
208          {
209             return 1;
210          }
211          else
212          {
213             return 0;
214          }
215       }
216       else if (type == String JavaDoc.class)
217       {
218          String JavaDoc s1 = (String JavaDoc) data.getValueAt(row1, column);
219          String JavaDoc s2 = (String JavaDoc) data.getValueAt(row2, column);
220          int result = s1.compareTo(s2);
221
222          if (result < 0)
223          {
224             return -1;
225          }
226          else if (result > 0)
227          {
228             return 1;
229          }
230          else
231          {
232             return 0;
233          }
234       }
235       else if (type == Boolean JavaDoc.class)
236       {
237          Boolean JavaDoc bool1 = (Boolean JavaDoc) data.getValueAt(row1, column);
238          boolean b1 = bool1.booleanValue();
239          Boolean JavaDoc bool2 = (Boolean JavaDoc) data.getValueAt(row2, column);
240          boolean b2 = bool2.booleanValue();
241
242          if (b1 == b2)
243          {
244             return 0;
245          }
246          else if (b1)
247          {
248             // Define false < true
249
return 1;
250          }
251          else
252          {
253             return -1;
254          }
255       }
256       else
257       {
258          String JavaDoc comp1 = o1.toString();
259          String JavaDoc comp2 = o2.toString();
260
261          int result = comp1.compareTo(comp2);
262
263          if (result < 0)
264          {
265             return -1;
266          }
267          else if (result > 0)
268          {
269             return 1;
270          }
271          else
272          {
273             return 0;
274          }
275       }
276    }
277
278
279    /**
280     * Gets the column attribute of the TableModelSorter object
281     *
282     * @return The column value
283     */

284    public int getColumn()
285    {
286       return this.column;
287    }
288
289
290    /**
291     * Gets the valueAt attribute of the TableModelSorter object
292     *
293     * @param aRow Description of the Parameter
294     * @param aColumn Description of the Parameter
295     * @return The valueAt value
296     */

297    public Object JavaDoc getValueAt(int aRow, int aColumn)
298    {
299       this.checkModel();
300       return super.getValueAt(aRow, aColumn);
301    }
302
303
304    /**
305     * Gets the ascending attribute of the TableModelSorter object
306     *
307     * @return The ascending value
308     */

309    public boolean isAscending()
310    {
311       return this.ascending;
312    }
313
314
315    /** Description of the Method */
316    public void n2sort()
317    {
318       for (int i = 0; i < getRowCount(); i++)
319       {
320          for (int j = i + 1; j < getRowCount(); j++)
321          {
322             if (compare(indexes[i], indexes[j]) == -1)
323             {
324                this.swap(i, j);
325             }
326          }
327       }
328    }
329
330
331
332    /** Description of the Method */
333    public void reallocateIndexes()
334    {
335       int rowCount = model.getRowCount();
336       this.indexes = new int[rowCount];
337
338       // Initialise with the identity mapping.
339
for (int row = 0; row < rowCount; row++)
340       {
341          indexes[row] = row;
342       }
343    }
344
345
346    /**
347     * Sets the valueAt attribute of the TableModelSorter object
348     *
349     * @param aValue The new valueAt value
350     * @param aRow The new valueAt value
351     * @param aColumn The new valueAt value
352     */

353    public void setValueAt(Object JavaDoc aValue, int aRow, int aColumn)
354    {
355       this.checkModel();
356       super.setValueAt(aValue, aRow, aColumn);
357    }
358
359
360
361    /**
362     * This is a home-grown implementation which we have not had time to research
363     * - it may perform poorly in some circumstances. It requires twice the space
364     * of an in-place algorithm and makes NlogN assigments shuttling the values
365     * between the two arrays. The number of compares appears to vary between N-1
366     * and NlogN depending on the initial order but the main reason for using it
367     * here is that, unlike qsort, it is stable.
368     *
369     * @param from Description of the Parameter
370     * @param to Description of the Parameter
371     * @param low Description of the Parameter
372     * @param high Description of the Parameter
373     */

374    public void shuttlesort(int from[], int to[], int low, int high)
375    {
376       if (high - low < 2)
377       {
378          return;
379       }
380       int middle = (low + high) / 2;
381
382       this.shuttlesort(to, from, low, middle);
383       this.shuttlesort(to, from, middle, high);
384
385       int p = low;
386       int q = middle;
387
388       /*
389        * This is an optional short-cut; at each recursive call,
390        * check to see if the elements in this subset are already
391        * ordered. If so, no further comparisons are needed; the
392        * sub-array can just be copied. The array must be copied rather
393        * than assigned otherwise sister calls in the recursion might
394        * get out of sinc. When the number of elements is three they
395        * are partitioned so that the first set, [low, mid), has one
396        * element and and the second, [mid, high), has two. We skip the
397        * optimisation when the number of elements is three or less as
398        * the first compare in the normal merge will produce the same
399        * sequence of steps. This optimisation seems to be worthwhile
400        * for partially ordered lists but some analysis is needed to
401        * find out how the performance drops to Nlog(N) as the initial
402        * order diminishes - it may drop very quickly.
403        */

404       if (high - low >= 4 && this.compare(from[middle - 1], from[middle]) <= 0)
405       {
406          for (int i = low; i < high; i++)
407          {
408             to[i] = from[i];
409          }
410          return;
411       }
412
413       // A normal merge.
414
for (int i = low; i < high; i++)
415       {
416          if (q >= high || (p < middle && this.compare(from[p], from[q]) <= 0))
417          {
418             to[i] = from[p++];
419          }
420          else
421          {
422             to[i] = from[q++];
423          }
424       }
425    }
426
427
428    /**
429     * Description of the Method
430     *
431     * @param sender Description of the Parameter
432     */

433    public void sort(Object JavaDoc sender)
434    {
435       this.checkModel();
436
437       this.compares = 0;
438       // n2sort();
439
// qsort(0, indexes.length-1);
440
this.shuttlesort((int[]) indexes.clone(), indexes, 0, indexes.length);
441       //System.out.println("Compares: "+compares);
442
}
443
444
445
446    /**
447     * Description of the Method
448     *
449     * @param column Description of the Parameter
450     */

451    public void sortByColumn(int column)
452    {
453       this.sortByColumn(column, true);
454    }
455
456
457    /**
458     * Description of the Method
459     *
460     * @param column Description of the Parameter
461     * @param ascending Description of the Parameter
462     */

463    public void sortByColumn(int column, boolean ascending)
464    {
465       this.ascending = ascending;
466       this.sortingColumns.removeAllElements();
467       this.sortingColumns.addElement(new Integer JavaDoc(column));
468       this.sort(this);
469       super.tableChanged(new TableModelEvent JavaDoc(this));
470    }
471
472
473    /**
474     * Description of the Method
475     *
476     * @param event Description of the Parameter
477     */

478    public void tableChanged(TableModelEvent JavaDoc event)
479    {
480       this.reallocateIndexes();
481 // if (column >= 0)
482
// {
483
// this.sortByColumn(column, ascending);
484
// }
485
super.tableChanged(event);
486    }
487 }
488
Popular Tags