View Javadoc

1   package groovy.inspect.swingui;
2   
3   /*
4    * Copyright (c) 2003 Sun Microsystems, Inc. All  Rights Reserved.
5    *
6    * Redistribution and use in source and binary forms, with or without
7    * modification, are permitted provided that the following conditions
8    * are met:
9    *
10   * -Redistributions of source code must retain the above copyright
11   *  notice, this list of conditions and the following disclaimer.
12   *
13   * -Redistribution in binary form must reproduct the above copyright
14   *  notice, this list of conditions and the following disclaimer in
15   *  the documentation and/or other materials provided with the distribution.
16   *
17   * Neither the name of Sun Microsystems, Inc. or the names of contributors
18   * may be used to endorse or promote products derived from this software
19   * without specific prior written permission.
20   *
21   * This software is provided "AS IS," without a warranty of any kind. ALL
22   * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
23   * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
24   * OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS SHALL NOT
25   * BE LIABLE FOR ANY DAMAGES OR LIABILITIES SUFFERED BY LICENSEE AS A RESULT
26   * OF OR RELATING TO USE, MODIFICATION OR DISTRIBUTION OF THE SOFTWARE OR ITS
27   * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST
28   * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL,
29   * INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY
30   * OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE SOFTWARE, EVEN
31   * IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
32   *
33   * You acknowledge that Software is not designed, licensed or intended for
34   * use in the design, construction, operation or maintenance of any nuclear
35   * facility.
36   */
37  
38  /*
39   * @(#)TableSorter.java	1.12 03/01/23
40   */
41  
42  /***
43   * A sorter for TableModels. The sorter has a model (conforming to TableModel)
44   * and itself implements TableModel. TableSorter does not store or copy
45   * the data in the TableModel, instead it maintains an array of
46   * integers which it keeps the same size as the number of rows in its
47   * model. When the model changes it notifies the sorter that something
48   * has changed eg. "rowsAdded" so that its internal array of integers
49   * can be reallocated. As requests are made of the sorter (like
50   * getValueAt(row, col) it redirects them to its model via the mapping
51   * array. That way the TableSorter appears to hold another copy of the table
52   * with the rows in a different order. The sorting algorthm used is stable
53   * which means that it does not move around rows when its comparison
54   * function returns 0 to denote that they are equivalent.
55   *
56   * @version 1.12 01/23/03
57   * @author Philip Milne
58   * @author Minimal adjustments by Dierk Koenig, June 2005
59   */
60  
61  import java.awt.event.MouseAdapter;
62  import java.awt.event.MouseEvent;
63  import java.util.Date;
64  import java.util.Vector;
65  
66  import javax.swing.JTable;
67  import javax.swing.event.TableModelEvent;
68  import javax.swing.table.JTableHeader;
69  import javax.swing.table.TableColumnModel;
70  import javax.swing.table.TableModel;
71  
72  public class TableSorter extends TableMap
73  {
74      int             indexes[];
75      Vector          sortingColumns = new Vector();
76      boolean         ascending = true;
77      int             lastSortedColumn = -1;
78  
79      public TableSorter()
80      {
81          indexes = new int[0]; // For consistency.
82      }
83  
84      public TableSorter(TableModel model)
85      {
86          setModel(model);
87      }
88  
89      public void setModel(TableModel model) {
90          super.setModel(model);
91          reallocateIndexes();
92      }
93  
94      public int compareRowsByColumn(int row1, int row2, int column)
95      {
96          Class type = model.getColumnClass(column);
97          TableModel data = model;
98  
99          // Check for nulls
100 
101         Object o1 = data.getValueAt(row1, column);
102         Object o2 = data.getValueAt(row2, column);
103 
104         // If both values are null return 0
105         if (o1 == null && o2 == null) {
106             return 0;
107         }
108         else if (o1 == null) { // Define null less than everything.
109             return -1;
110         }
111         else if (o2 == null) {
112             return 1;
113         }
114 
115 /* We copy all returned values from the getValue call in case
116 an optimised model is reusing one object to return many values.
117 The Number subclasses in the JDK are immutable and so will not be used in
118 this way but other subclasses of Number might want to do this to save
119 space and avoid unnecessary heap allocation.
120 */
121         if (type.getSuperclass() == java.lang.Number.class)
122             {
123                 Number n1 = (Number)data.getValueAt(row1, column);
124                 double d1 = n1.doubleValue();
125                 Number n2 = (Number)data.getValueAt(row2, column);
126                 double d2 = n2.doubleValue();
127 
128                 if (d1 < d2)
129                     return -1;
130                 else if (d1 > d2)
131                     return 1;
132                 else
133                     return 0;
134             }
135         else if (type == java.util.Date.class)
136             {
137                 Date d1 = (Date)data.getValueAt(row1, column);
138                 long n1 = d1.getTime();
139                 Date d2 = (Date)data.getValueAt(row2, column);
140                 long n2 = d2.getTime();
141 
142                 if (n1 < n2)
143                     return -1;
144                 else if (n1 > n2)
145                     return 1;
146                 else return 0;
147             }
148         else if (type == String.class)
149             {
150                 String s1 = (String)data.getValueAt(row1, column);
151                 String s2    = (String)data.getValueAt(row2, column);
152                 int result = s1.compareTo(s2);
153 
154                 if (result < 0)
155                     return -1;
156                 else if (result > 0)
157                     return 1;
158                 else return 0;
159             }
160         else if (type == Boolean.class)
161             {
162                 Boolean bool1 = (Boolean)data.getValueAt(row1, column);
163                 boolean b1 = bool1.booleanValue();
164                 Boolean bool2 = (Boolean)data.getValueAt(row2, column);
165                 boolean b2 = bool2.booleanValue();
166 
167                 if (b1 == b2)
168                     return 0;
169                 else if (b1) // Define false < true
170                     return 1;
171                 else
172                     return -1;
173             }
174         else
175             {
176                 Object v1 = data.getValueAt(row1, column);
177                 String s1 = v1.toString();
178                 Object v2 = data.getValueAt(row2, column);
179                 String s2 = v2.toString();
180                 int result = s1.compareTo(s2);
181 
182                 if (result < 0)
183                     return -1;
184                 else if (result > 0)
185                     return 1;
186                 else return 0;
187             }
188     }
189 
190     public int compare(int row1, int row2)
191     {
192         for(int level = 0; level < sortingColumns.size(); level++)
193             {
194                 Integer column = (Integer)sortingColumns.elementAt(level);
195                 int result = compareRowsByColumn(row1, row2, column.intValue());
196                 if (result != 0)
197                     return ascending ? result : -result;
198             }
199         return 0;
200     }
201 
202     public void  reallocateIndexes()
203     {
204         int rowCount = model.getRowCount();
205 
206         // Set up a new array of indexes with the right number of elements
207         // for the new data model.
208         indexes = new int[rowCount];
209 
210         // Initialise with the identity mapping.
211         for(int row = 0; row < rowCount; row++)
212             indexes[row] = row;
213     }
214 
215     public void tableChanged(TableModelEvent e)
216     {
217 	System.out.println("Sorter: tableChanged");
218         reallocateIndexes();
219 
220         super.tableChanged(e);
221     }
222 
223     public void checkModel()
224     {
225         if (indexes.length != model.getRowCount()) {
226             System.err.println("Sorter not informed of a change in model.");
227         }
228     }
229 
230     public void  sort(Object sender)
231     {
232         checkModel();
233         shuttlesort((int[])indexes.clone(), indexes, 0, indexes.length);
234     }
235 
236     public void n2sort() {
237         for(int i = 0; i < getRowCount(); i++) {
238             for(int j = i+1; j < getRowCount(); j++) {
239                 if (compare(indexes[i], indexes[j]) == -1) {
240                     swap(i, j);
241                 }
242             }
243         }
244     }
245 
246     // This is a home-grown implementation which we have not had time
247     // to research - it may perform poorly in some circumstances. It
248     // requires twice the space of an in-place algorithm and makes
249     // NlogN assigments shuttling the values between the two
250     // arrays. The number of compares appears to vary between N-1 and
251     // NlogN depending on the initial order but the main reason for
252     // using it here is that, unlike qsort, it is stable.
253     public void shuttlesort(int from[], int to[], int low, int high) {
254         if (high - low < 2) {
255             return;
256         }
257         int middle = (low + high)/2;
258         shuttlesort(to, from, low, middle);
259         shuttlesort(to, from, middle, high);
260 
261         int p = low;
262         int q = middle;
263 
264         /* This is an optional short-cut; at each recursive call,
265         check to see if the elements in this subset are already
266         ordered.  If so, no further comparisons are needed; the
267         sub-array can just be copied.  The array must be copied rather
268         than assigned otherwise sister calls in the recursion might
269         get out of sinc.  When the number of elements is three they
270         are partitioned so that the first set, [low, mid), has one
271         element and and the second, [mid, high), has two. We skip the
272         optimisation when the number of elements is three or less as
273         the first compare in the normal merge will produce the same
274         sequence of steps. This optimisation seems to be worthwhile
275         for partially ordered lists but some analysis is needed to
276         find out how the performance drops to Nlog(N) as the initial
277         order diminishes - it may drop very quickly.  */
278 
279         if (high - low >= 4 && compare(from[middle-1], from[middle]) <= 0) {
280             for (int i = low; i < high; i++) {
281                 to[i] = from[i];
282             }
283             return;
284         }
285 
286         // A normal merge.
287 
288         for(int i = low; i < high; i++) {
289             if (q >= high || (p < middle && compare(from[p], from[q]) <= 0)) {
290                 to[i] = from[p++];
291             }
292             else {
293                 to[i] = from[q++];
294             }
295         }
296     }
297 
298     public void swap(int i, int j) {
299         int tmp = indexes[i];
300         indexes[i] = indexes[j];
301         indexes[j] = tmp;
302     }
303 
304     // The mapping only affects the contents of the data rows.
305     // Pass all requests to these rows through the mapping array: "indexes".
306 
307     public Object getValueAt(int aRow, int aColumn)
308     {
309         checkModel();
310         return model.getValueAt(indexes[aRow], aColumn);
311     }
312 
313     public void setValueAt(Object aValue, int aRow, int aColumn)
314     {
315         checkModel();
316         model.setValueAt(aValue, indexes[aRow], aColumn);
317     }
318 
319     public void sortByColumn(int column) {
320         sortByColumn(column, true);
321     }
322 
323     public void sortByColumn(int column, boolean ascending) {
324         this.ascending = ascending;
325         sortingColumns.removeAllElements();
326         sortingColumns.addElement(new Integer(column));
327         sort(this);
328         super.tableChanged(new TableModelEvent(this));
329     }
330 
331     // There is no-where else to put this.
332     // Add a mouse listener to the Table to trigger a table sort
333     // when a column heading is clicked in the JTable.
334     public void addMouseListenerToHeaderInTable(JTable table) {
335         final TableSorter sorter = this;
336         final JTable tableView = table;
337         tableView.setColumnSelectionAllowed(false);
338         MouseAdapter listMouseListener = new MouseAdapter() {
339             public void mouseClicked(MouseEvent e) {
340                 TableColumnModel columnModel = tableView.getColumnModel();
341                 int viewColumn = columnModel.getColumnIndexAtX(e.getX());                
342                 int column = tableView.convertColumnIndexToModel(viewColumn);
343                 if(e.getClickCount() == 1 && column != -1) {
344                     if (lastSortedColumn == column) ascending = !ascending;
345                     sorter.sortByColumn(column, ascending);
346                     lastSortedColumn = column;
347                 }
348              }
349          };
350         JTableHeader th = tableView.getTableHeader();
351         th.addMouseListener(listMouseListener);
352     }
353 
354 
355 
356 }
357