KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > objectweb > jac > aspects > gui > TableSorter


1 package org.objectweb.jac.aspects.gui;
2
3 import java.util.Date JavaDoc;
4 import java.util.HashMap JavaDoc;
5 import java.util.Vector JavaDoc;
6 import javax.swing.event.TableModelEvent JavaDoc;
7 import javax.swing.event.TableModelListener JavaDoc;
8 import javax.swing.table.AbstractTableModel JavaDoc;
9 import org.apache.log4j.Logger;
10 import org.objectweb.jac.core.Collaboration;
11 import org.objectweb.jac.core.rtti.CollectionItem;
12 import org.objectweb.jac.core.rtti.MemberItem;
13
14
15 /**
16  * A sorter for TableModels. The sorter has a model (conforming to TableModel)
17  * and itself implements TableModel. TableSorter does not store or copy
18  * the data in the TableModel, instead it maintains an array of
19  * integers which it keeps the same size as the number of rows in its
20  * model. When the model changes it notifies the sorter that something
21  * has changed eg. "rowsAdded" so that its internal array of integers
22  * can be reallocated. As requests are made of the sorter (like
23  * getValueAt(row, col) it redirects them to its model via the mapping
24  * array. That way the TableSorter appears to hold another copy of the table
25  * with the rows in a different order. The sorting algorthm used is stable
26  * which means that it does not move around rows when its comparison
27  * function returns 0 to denote that they are equivalent.
28  *
29  * @version 1.5 12/17/97
30  * @author Philip Milne
31  */

32
33 public class TableSorter extends TableMap {
34     static Logger logger = Logger.getLogger("gui.sort");
35
36     int indexes[];
37     /** list of SortCriteria */
38     Vector JavaDoc sortingColumns = new Vector JavaDoc();
39     int compares;
40    
41     public TableSorter() {
42         indexes = new int[0]; // for consistency
43
}
44    
45     public TableSorter(ExtendedTableModel model) {
46         setModel(model);
47     }
48
49     /**
50      * Gets the sort criteria for a column.
51      * @return the SortCriteria of the given column, or null.
52      */

53     public SortCriteria getSortCriteria(int column) {
54         for (int i=0; i<sortingColumns.size(); i++) {
55             if (((SortCriteria)sortingColumns.get(i)).column==column) {
56                 return (SortCriteria)sortingColumns.get(i);
57             }
58         }
59         return null;
60     }
61
62     public void setModel(ExtendedTableModel model) {
63         super.setModel(model);
64         reallocateIndexes();
65         defaultSortOrder();
66     }
67
68     /**
69      * Sets the sort column for the collection from the context or from
70      * RTTI configuration.
71      */

72     public void defaultSortOrder() {
73         // First see if there's a config in the context
74
HashMap JavaDoc map = (HashMap JavaDoc)Collaboration.get().getAttribute(GuiAC.SORT_COLUMN);
75         if (map != null) {
76             SortCriteria criteria = (SortCriteria)map.get(getCollection());
77             if (criteria != null) {
78                 logger.debug("Using sort criteria from context: "+criteria);
79                 sortByColumn(criteria.column,criteria.ascending);
80                 return;
81             }
82         }
83
84         // Then see if the collection has a default sort order
85
String JavaDoc column = GuiAC.getDefaultSortedColumn(getCollection());
86         if (column!=null) {
87             logger.debug("Using default sort order : "+column);
88             boolean ascending = true;
89             if (column.startsWith("-")) {
90                 ascending = false;
91                 column = column.substring(1);
92             } else if (column.startsWith("+")) {
93                 ascending = true;
94                 column = column.substring(1);
95             }
96             // find the column with that name
97
MemberItem[] members = getMembers();
98             for (int i=0; i<members.length; i++) {
99                 if (members[i].getName().equals(column)) {
100                     sortByColumn(i,ascending);
101                     return;
102                 }
103             }
104         }
105     }
106
107     public int compareRowsByColumn(int row1, int row2, int column) {
108         Class JavaDoc type = model.getColumnClass(column);
109         ExtendedTableModel data = model;
110
111         // Check for nulls.
112

113         Object JavaDoc o1 = data.getValueAt(row1, column);
114         Object JavaDoc o2 = data.getValueAt(row2, column);
115             
116         // If both values are null, return 0.
117
if (o1 == null && o2 == null) {
118             return 0;
119         } else if (o1 == null) { // Define null less than everything.
120
return -1;
121         } else if (o2 == null) {
122             return 1;
123         }
124
125         /*
126          * We copy all returned values from the getValue call in case
127          * an optimised model is reusing one object to return many
128          * values. The Number subclasses in the JDK are immutable and
129          * so will not be used in this way but other subclasses of
130          * Number might want to do this to save space and avoid
131          * unnecessary heap allocation.
132          */

133       
134         if (type.getSuperclass() == java.lang.Number JavaDoc.class) {
135             double d1 = ((Number JavaDoc)o1).doubleValue();
136             double d2 = ((Number JavaDoc)o2).doubleValue();
137          
138             if (d1 < d2) {
139                 return -1;
140             } else if (d1 > d2) {
141                 return 1;
142             } else {
143                 return 0;
144             }
145         } else if (type == java.util.Date JavaDoc.class) {
146             long n1 = ((Date JavaDoc)o1).getTime();
147             long n2 = ((Date JavaDoc)o2).getTime();
148          
149             if (n1 < n2) {
150                 return -1;
151             } else if (n1 > n2) {
152                 return 1;
153             } else {
154                 return 0;
155             }
156         } else if (type == String JavaDoc.class) {
157             return ((String JavaDoc)o1).compareToIgnoreCase((String JavaDoc)o2);
158         } else if (type == Boolean JavaDoc.class) {
159             boolean b1 = ((Boolean JavaDoc)o1).booleanValue();
160             boolean b2 = ((Boolean JavaDoc)o2).booleanValue();
161          
162             if (b1 == b2) {
163                 return 0;
164             } else if (b1) { // Define false < true
165
return 1;
166             } else {
167                 return -1;
168             }
169         } else {
170             return GuiAC.toString(data.getValueAt(row1, column)).
171                 compareToIgnoreCase(GuiAC.toString(data.getValueAt(row2, column)));
172         }
173     }
174    
175     public int compare(int row1, int row2) {
176         compares++;
177         for (int level=0; level<sortingColumns.size(); level++) {
178             SortCriteria criteria = (SortCriteria)sortingColumns.get(level);
179             int result = compareRowsByColumn(row1, row2, criteria.column);
180             if (result != 0) {
181                 return criteria.ascending ? result : -result;
182             }
183         }
184         return 0;
185     }
186    
187     /**
188      * Reset to default unsorted order of the model.
189      */

190     public void reallocateIndexes() {
191         int rowCount = model.getRowCount();
192       
193         logger.debug(this+".reallocateIndexes "+rowCount);
194
195         // Set up a new array of indexes with the right number of elements
196
// for the new data model.
197
indexes = new int[rowCount];
198       
199         // Initialise with the identity mapping.
200
for (int row = 0; row < rowCount; row++) {
201             indexes[row] = row;
202         }
203     }
204
205     public int getActualIndex(int row) {
206         return indexes[row];
207     }
208    
209     public void tableChanged(TableModelEvent JavaDoc e) {
210         if (e.getType()==TableModelEvent.INSERT ||
211             e.getType()==TableModelEvent.DELETE) {
212             logger.debug(this+".tableChanged "+
213                       (e.getType()==TableModelEvent.DELETE?"DELETE":"INSERT")+
214                       " "+e.getFirstRow()+"-"+e.getLastRow());
215             reallocateIndexes();
216             sort(this);
217             super.tableChanged(e);
218         } else {
219             logger.debug(this+".tableChanged UPDATE "+
220                       e.getFirstRow()+"-"+e.getLastRow());
221             reallocateIndexes();
222             sort(this);
223             super.tableChanged(e);
224         }
225     }
226    
227     public void checkModel() {
228         if (indexes.length != model.getRowCount()) {
229             logger.warn(this+" not informed of a change in model for collection "+
230                         getCollection().getName()+": "+indexes.length+"!="+model.getRowCount());
231         }
232     }
233    
234     public void sort(Object JavaDoc sender) {
235         if (sortingColumns.size()>0) {
236             logger.debug("sorting "+getCollection()+" by "+sortingColumns);
237             checkModel();
238          
239             compares = 0;
240             // n2sort();
241
// qsort(0, indexes.length-1);
242
shuttlesort((int[])indexes.clone(), indexes, 0, indexes.length);
243             //System.out.println("Compares: "+compares);
244
} else {
245             reallocateIndexes();
246         }
247     }
248
249     public void n2sort() {
250         for (int i=0; i<getRowCount(); i++) {
251             for (int j=i+1; j<getRowCount(); j++) {
252                 if (compare(indexes[i], indexes[j]) < 0) {
253                     swap(i, j);
254                 }
255             }
256         }
257     }
258    
259     // This is a home-grown implementation which we have not had time
260
// to research - it may perform poorly in some circumstances. It
261
// requires twice the space of an in-place algorithm and makes
262
// NlogN assigments shuttling the values between the two
263
// arrays. The number of compares appears to vary between N-1 and
264
// NlogN depending on the initial order but the main reason for
265
// using it here is that, unlike qsort, it is stable.
266
public void shuttlesort(int from[], int to[], int low, int high) {
267         if (high - low < 2) {
268             return;
269         }
270         int middle = (low + high)/2;
271         shuttlesort(to, from, low, middle);
272         shuttlesort(to, from, middle, high);
273       
274         int p = low;
275         int q = middle;
276       
277         /* This is an optional short-cut; at each recursive call,
278            check to see if the elements in this subset are already
279            ordered. If so, no further comparisons are needed; the
280            sub-array can just be copied. The array must be copied rather
281            than assigned otherwise sister calls in the recursion might
282            get out of sinc. When the number of elements is three they
283            are partitioned so that the first set, [low, mid), has one
284            element and and the second, [mid, high), has two. We skip the
285            optimisation when the number of elements is three or less as
286            the first compare in the normal merge will produce the same
287            sequence of steps. This optimisation seems to be worthwhile
288            for partially ordered lists but some analysis is needed to
289            find out how the performance drops to Nlog(N) as the initial
290            order diminishes - it may drop very quickly. */

291       
292         if (high - low >= 4 && compare(from[middle-1], from[middle]) <= 0) {
293             for (int i = low; i < high; i++) {
294                 to[i] = from[i];
295             }
296             return;
297         }
298       
299         // A normal merge.
300

301         for (int i = low; i < high; i++) {
302             if (q >= high || (p < middle && compare(from[p], from[q]) <= 0)) {
303                 to[i] = from[p++];
304             }
305             else {
306                 to[i] = from[q++];
307             }
308         }
309     }
310    
311     public void swap(int i, int j) {
312         int tmp = indexes[i];
313         indexes[i] = indexes[j];
314         indexes[j] = tmp;
315     }
316    
317     // The mapping only affects the contents of the data rows.
318
// Pass all requests to these rows through the mapping array: "indexes".
319

320     public Object JavaDoc getValueAt(int aRow, int aColumn) {
321         checkModel();
322         if(indexes.length>aRow) {
323             logger.debug("getValueAt("+aRow+","+aColumn+") -> "+
324                       model.getValueAt(indexes[aRow], aColumn));
325             return model.getValueAt(indexes[aRow], aColumn);
326         } else {
327             return null;
328         }
329     }
330    
331     public Object JavaDoc getObject(int row) {
332         checkModel();
333         return model.getObject(indexes[row]);
334     }
335
336     public int indexOf(Object JavaDoc object) {
337         checkModel();
338         return indexes[model.indexOf(object)];
339     }
340
341     public Object JavaDoc getObject(int row, int column) {
342         checkModel();
343         return model.getObject(indexes[row],column);
344     }
345
346     public void setValueAt(Object JavaDoc aValue, int aRow, int aColumn) {
347         checkModel();
348         model.setValueAt(aValue, indexes[aRow], aColumn);
349     }
350    
351     /**
352      * Sorts using values of a column
353      * @param column index of column to sort by
354      */

355     public void sortByColumn(int column) {
356         sortByColumn(column, true);
357     }
358
359     /**
360      * Sorts using values of a column. Reverse the order if the table
361      * was already sorted by this column.
362      * @param column index of column to sort by
363      */

364     public void toggleSortByColumn(int column) {
365         SortCriteria criteria = getSortCriteria(column);
366         SortCriteria savedCriteria;
367         if (criteria!=null) {
368             criteria.toggleAscending();
369             savedCriteria = new SortCriteria(column,criteria.isAscending());
370         } else {
371             sortingColumns.clear();
372             sortingColumns.add(new SortCriteria(column,true));
373             savedCriteria = new SortCriteria(column,true);
374         }
375         sort(this);
376         saveSortCriteria(savedCriteria);
377         super.tableChanged(new TableModelEvent JavaDoc(this));
378     }
379
380     public void sortByColumn(int column, boolean ascending) {
381         logger.debug("sortByColumn "+column+
382                      "("+(ascending?"":"-")+(column>=0?getHeaders()[column]:"none")+")");
383         sortingColumns.clear();
384
385         if (column>=0) {
386             sortingColumns.add(new SortCriteria(column,ascending));
387         }
388         sort(this);
389
390         saveSortCriteria(column>=0?new SortCriteria(column,ascending):null);
391
392         super.tableChanged(new TableModelEvent JavaDoc(this));
393     }
394
395     /**
396      * Save the sort criteria in the context
397      * @param criteria the sort criteria to save
398      */

399     protected void saveSortCriteria(SortCriteria criteria) {
400         // new Exception().printStackTrace();
401
HashMap JavaDoc map = (HashMap JavaDoc)Collaboration.get().getAttribute(GuiAC.SORT_COLUMN);
402         if (map == null) {
403             map = new HashMap JavaDoc();
404             Collaboration.get().addAttribute(GuiAC.SORT_COLUMN, map);
405         }
406         map.put(getCollection(), criteria);
407     }
408 }
409
Popular Tags