001    package groovy.inspect.swingui;
002    
003    /*
004     * Copyright (c) 2003 Sun Microsystems, Inc. All  Rights Reserved.
005     *
006     * Redistribution and use in source and binary forms, with or without
007     * modification, are permitted provided that the following conditions
008     * are met:
009     *
010     * -Redistributions of source code must retain the above copyright
011     *  notice, this list of conditions and the following disclaimer.
012     *
013     * -Redistribution in binary form must reproduct the above copyright
014     *  notice, this list of conditions and the following disclaimer in
015     *  the documentation and/or other materials provided with the distribution.
016     *
017     * Neither the name of Sun Microsystems, Inc. or the names of contributors
018     * may be used to endorse or promote products derived from this software
019     * without specific prior written permission.
020     *
021     * This software is provided "AS IS," without a warranty of any kind. ALL
022     * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
023     * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
024     * OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS SHALL NOT
025     * BE LIABLE FOR ANY DAMAGES OR LIABILITIES SUFFERED BY LICENSEE AS A RESULT
026     * OF OR RELATING TO USE, MODIFICATION OR DISTRIBUTION OF THE SOFTWARE OR ITS
027     * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST
028     * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL,
029     * INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY
030     * OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE SOFTWARE, EVEN
031     * IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
032     *
033     * You acknowledge that Software is not designed, licensed or intended for
034     * use in the design, construction, operation or maintenance of any nuclear
035     * facility.
036     */
037    
038    /*
039     * @(#)TableSorter.java 1.12 03/01/23
040     */
041    
042    /**
043     * A sorter for TableModels. The sorter has a model (conforming to TableModel)
044     * and itself implements TableModel. TableSorter does not store or copy
045     * the data in the TableModel, instead it maintains an array of
046     * integers which it keeps the same size as the number of rows in its
047     * model. When the model changes it notifies the sorter that something
048     * has changed eg. "rowsAdded" so that its internal array of integers
049     * can be reallocated. As requests are made of the sorter (like
050     * getValueAt(row, col) it redirects them to its model via the mapping
051     * array. That way the TableSorter appears to hold another copy of the table
052     * with the rows in a different order. The sorting algorthm used is stable
053     * which means that it does not move around rows when its comparison
054     * function returns 0 to denote that they are equivalent.
055     *
056     * @version 1.12 01/23/03
057     * @author Philip Milne
058     * @author Minimal adjustments by Dierk Koenig, June 2005
059     */
060    
061    import java.awt.event.MouseAdapter;
062    import java.awt.event.MouseEvent;
063    import java.util.Date;
064    import java.util.Vector;
065    
066    import javax.swing.JTable;
067    import javax.swing.event.TableModelEvent;
068    import javax.swing.table.JTableHeader;
069    import javax.swing.table.TableColumnModel;
070    import javax.swing.table.TableModel;
071    
072    public class TableSorter extends TableMap
073    {
074        int             indexes[];
075        Vector          sortingColumns = new Vector();
076        boolean         ascending = true;
077        int             lastSortedColumn = -1;
078    
079        public TableSorter()
080        {
081            indexes = new int[0]; // For consistency.
082        }
083    
084        public TableSorter(TableModel model)
085        {
086            setModel(model);
087        }
088    
089        public void setModel(TableModel model) {
090            super.setModel(model);
091            reallocateIndexes();
092        }
093    
094        public int compareRowsByColumn(int row1, int row2, int column)
095        {
096            Class type = model.getColumnClass(column);
097            TableModel data = model;
098    
099            // 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