1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2000 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  *
26  * ident	"%Z%%M%	%I%	%E% SMI"
27  */
28 package com.sun.dhcpmgr.ui;
29 
30 /*
31  * @(#)TableSorter.java 1.5 97/12/17
32  */
33 
34 /**
35  * A sorter for TableModels. The sorter has a model (conforming to TableModel)
36  * and itself implements TableModel. TableSorter does not store or copy
37  * the data in the TableModel, instead it maintains an array of
38  * integers which it keeps the same size as the number of rows in its
39  * model. When the model changes it notifies the sorter that something
40  * has changed eg. "rowsAdded" so that its internal array of integers
41  * can be reallocated. As requests are made of the sorter (like
42  * getValueAt(row, col) it redirects them to its model via the mapping
43  * array. That way the TableSorter appears to hold another copy of the table
44  * with the rows in a different order. The sorting algorthm used is stable
45  * which means that it does not move around rows when its comparison
46  * function returns 0 to denote that they are equivalent.
47  *
48  * @version 1.5 12/17/97
49  * @author Philip Milne
50  */
51 
52 import java.util.*;
53 
54 import javax.swing.table.TableModel;
55 import javax.swing.event.TableModelEvent;
56 
57 import com.sun.dhcpmgr.data.IPAddress;
58 
59 // Imports for picking up mouse events from the JTable.
60 
61 import java.awt.event.MouseAdapter;
62 import java.awt.event.MouseEvent;
63 import java.awt.event.InputEvent;
64 import java.awt.event.ActionEvent;
65 import java.awt.event.ActionListener;
66 import javax.swing.JTable;
67 import javax.swing.table.JTableHeader;
68 import javax.swing.table.TableColumn;
69 import javax.swing.table.TableColumnModel;
70 
71 public class TableSorter extends TableMap
72 {
73     int             indexes[] = new int[0];
74     Vector          sortingColumns = new Vector();
75     boolean         ascending = true;
76     int compares;
77     Vector listeners = new Vector();
78 
TableSorter()79     public TableSorter() {
80         indexes = new int[0]; // For consistency.
81     }
82 
TableSorter(TableModel model)83     public TableSorter(TableModel model) {
84         setModel(model);
85     }
86 
setModel(TableModel model)87     public void setModel(TableModel model) {
88         super.setModel(model);
89         reallocateIndexes();
90     }
91 
compareRowsByColumn(int row1, int row2, int column)92     public int compareRowsByColumn(int row1, int row2, int column) {
93         Class type = model.getColumnClass(column);
94         TableModel data = model;
95 
96         // Check for nulls
97 
98         Object o1 = data.getValueAt(row1, column);
99         Object o2 = data.getValueAt(row2, column);
100 
101         // If both values are null return 0
102         if (o1 == null && o2 == null) {
103             return 0;
104         } else if (o1 == null) { // Define null less than everything.
105             return -1;
106         } else if (o2 == null) {
107             return 1;
108         }
109 
110 	/*
111  	 * We copy all returned values from the getValue call in case
112 	 * an optimised model is reusing one object to return many values.
113 	 * The Number subclasses in the JDK are immutable and so will not be
114 	 * used in this way but other subclasses of Number might want to do
115 	 * this to save space and avoid unnecessary heap allocation.
116 	 */
117         if (type.getSuperclass() == java.lang.Number.class) {
118                 Number n1 = (Number)data.getValueAt(row1, column);
119                 double d1 = n1.doubleValue();
120                 Number n2 = (Number)data.getValueAt(row2, column);
121                 double d2 = n2.doubleValue();
122 
123                 if (d1 < d2)
124                     return -1;
125                 else if (d1 > d2)
126                     return 1;
127                 else
128                     return 0;
129         } else if (type == java.util.Date.class) {
130                 Date d1 = (Date)data.getValueAt(row1, column);
131                 long n1 = d1.getTime();
132                 Date d2 = (Date)data.getValueAt(row2, column);
133                 long n2 = d2.getTime();
134 
135 		// Handle negatives specially
136 		if (n1 < 0) {
137 		    return 1;
138 		} else if (n2 < 0) {
139 		    return -1;
140 		}
141                 if (n1 < n2)
142                     return -1;
143                 else if (n1 > n2)
144                     return 1;
145                 else
146 		    return 0;
147         } else if (type == String.class) {
148                 String s1 = (String)data.getValueAt(row1, column);
149                 String s2    = (String)data.getValueAt(row2, column);
150                 int result = s1.compareTo(s2);
151 
152                 if (result < 0)
153                     return -1;
154                 else if (result > 0)
155                     return 1;
156                 else return 0;
157         } else if (type == Boolean.class) {
158                 Boolean bool1 = (Boolean)data.getValueAt(row1, column);
159                 boolean b1 = bool1.booleanValue();
160                 Boolean bool2 = (Boolean)data.getValueAt(row2, column);
161                 boolean b2 = bool2.booleanValue();
162 
163                 if (b1 == b2)
164                     return 0;
165                 else if (b1) // Define false < true
166                     return 1;
167                 else
168                     return -1;
169         } else if (type == IPAddress.class) {
170 		IPAddress addr1 = (IPAddress)data.getValueAt(row1, column);
171 		IPAddress addr2 = (IPAddress)data.getValueAt(row2, column);
172 		byte [] a1 = addr1.getAddress();
173 		byte [] a2 = addr2.getAddress();
174 		int c1, c2;
175 		for (int i = 0; i < a1.length; ++i) {
176 		    /*
177 		     * Promote and mask because bytes are signed and 128-255
178 		     * will be done wrong
179 		     */
180 		    c1 = a1[i] & 0xff;
181 		    c2 = a2[i] & 0xff;
182 		    if (c1 < c2) {
183 			return -1;
184 		    } else if (c1 > c2) {
185 			return 1;
186 		    }
187 		}
188 		return 0;
189 	} else {
190                 Object v1 = data.getValueAt(row1, column);
191                 String s1 = v1.toString();
192                 Object v2 = data.getValueAt(row2, column);
193                 String s2 = v2.toString();
194                 int result = s1.compareTo(s2);
195 
196                 if (result < 0)
197                     return -1;
198                 else if (result > 0)
199                     return 1;
200                 else return 0;
201         }
202     }
203 
compare(int row1, int row2)204     public int compare(int row1, int row2) {
205         compares++;
206         for (int level = 0; level < sortingColumns.size(); level++) {
207                 Integer column = (Integer)sortingColumns.elementAt(level);
208                 int result = compareRowsByColumn(row1, row2, column.intValue());
209                 if (result != 0)
210                     return ascending ? result : -result;
211         }
212         return 0;
213     }
214 
reallocateIndexes()215     public void  reallocateIndexes() {
216         int rowCount = model.getRowCount();
217 
218         // Set up a new array of indexes with the right number of elements
219         // for the new data model.
220         indexes = new int[rowCount];
221 
222         // Initialise with the identity mapping.
223         for (int row = 0; row < rowCount; row++)
224             indexes[row] = row;
225     }
226 
tableChanged(TableModelEvent e)227     public void tableChanged(TableModelEvent e) {
228         // System.out.println("Sorter: tableChanged");
229         reallocateIndexes();
230 	sort(this);
231         super.tableChanged(e);
232     }
233 
checkModel()234     public void checkModel() {
235         if (indexes.length != model.getRowCount()) {
236             System.err.println("Sorter not informed of a change in model.");
237         }
238     }
239 
sort(Object sender)240     public void  sort(Object sender) {
241         checkModel();
242 
243         compares = 0;
244         // n2sort();
245         // qsort(0, indexes.length-1);
246         shuttlesort((int[])indexes.clone(), indexes, 0, indexes.length);
247         // System.out.println("Compares: "+compares);
248     }
249 
n2sort()250     public void n2sort() {
251         for (int i = 0; i < getRowCount(); i++) {
252             for (int j = i+1; j < getRowCount(); j++) {
253                 if (compare(indexes[i], indexes[j]) == -1) {
254                     swap(i, j);
255                 }
256             }
257         }
258     }
259 
260     /*
261      * This is a home-grown implementation which we have not had time
262      * to research - it may perform poorly in some circumstances. It
263      * requires twice the space of an in-place algorithm and makes
264      * NlogN assigments shuttling the values between the two
265      * arrays. The number of compares appears to vary between N-1 and
266      * NlogN depending on the initial order but the main reason for
267      * using it here is that, unlike qsort, it is stable.
268      */
shuttlesort(int from[], int to[], int low, int high)269     public void shuttlesort(int from[], int to[], int low, int high) {
270         if (high - low < 2) {
271             return;
272         }
273         int middle = (low + high)/2;
274         shuttlesort(to, from, low, middle);
275         shuttlesort(to, from, middle, high);
276 
277         int p = low;
278         int q = middle;
279 
280         /*
281 	 * This is an optional short-cut; at each recursive call,
282          * check to see if the elements in this subset are already
283          * ordered.  If so, no further comparisons are needed; the
284          * sub-array can just be copied.  The array must be copied rather
285          * than assigned otherwise sister calls in the recursion might
286          * get out of sinc.  When the number of elements is three they
287          * are partitioned so that the first set, [low, mid), has one
288          * element and and the second, [mid, high), has two. We skip the
289          * optimisation when the number of elements is three or less as
290          * the first compare in the normal merge will produce the same
291          * sequence of steps. This optimisation seems to be worthwhile
292          * for partially ordered lists but some analysis is needed to
293          * find out how the performance drops to Nlog(N) as the initial
294          * order diminishes - it may drop very quickly.
295 	 */
296 
297         if (high - low >= 4 && compare(from[middle-1], from[middle]) <= 0) {
298             for (int i = low; i < high; i++) {
299                 to[i] = from[i];
300             }
301             return;
302         }
303 
304         // A normal merge.
305 
306         for (int i = low; i < high; i++) {
307             if (q >= high || (p < middle && compare(from[p], from[q]) <= 0)) {
308                 to[i] = from[p++];
309             } else {
310                 to[i] = from[q++];
311             }
312         }
313     }
314 
swap(int i, int j)315     public void swap(int i, int j) {
316         int tmp = indexes[i];
317         indexes[i] = indexes[j];
318         indexes[j] = tmp;
319     }
320 
321     /*
322      * The mapping only affects the contents of the data rows.
323      * Pass all requests to these rows through the mapping array: "indexes".
324      */
325 
getValueAt(int aRow, int aColumn)326     public Object getValueAt(int aRow, int aColumn)
327     {
328         checkModel();
329         return model.getValueAt(indexes[aRow], aColumn);
330     }
331 
mapRowAt(int aRow)332     public int mapRowAt(int aRow) {
333 	checkModel();
334 	if (aRow < indexes.length) {
335 	    return indexes[aRow];
336 	} else {
337 	    return -1;
338 	}
339     }
340 
setValueAt(Object aValue, int aRow, int aColumn)341     public void setValueAt(Object aValue, int aRow, int aColumn) {
342         checkModel();
343         model.setValueAt(aValue, indexes[aRow], aColumn);
344     }
345 
sortByColumn(int column)346     public void sortByColumn(int column) {
347 	// Re-sort on this column, but don't change sort order
348         sortByColumn(column, this.ascending);
349     }
350 
sortByColumn(int column, boolean ascending)351     public void sortByColumn(int column, boolean ascending) {
352         this.ascending = ascending;
353         sortingColumns.removeAllElements();
354         sortingColumns.addElement(new Integer(column));
355         sort(this);
356         super.tableChanged(new TableModelEvent(this));
357 	fireActionPerformed();
358     }
359 
360     /*
361      * There is no-where else to put this.
362      * Add a mouse listener to the Table to trigger a table sort
363      * when a column heading is clicked in the JTable.
364      */
addMouseListenerToHeaderInTable(JTable table)365     public void addMouseListenerToHeaderInTable(JTable table) {
366         final TableSorter sorter = this;
367         final JTable tableView = table;
368         tableView.setColumnSelectionAllowed(false);
369         MouseAdapter listMouseListener = new MouseAdapter() {
370             public void mouseClicked(MouseEvent e) {
371                 TableColumnModel columnModel = tableView.getColumnModel();
372                 int viewColumn = columnModel.getColumnIndexAtX(e.getX());
373                 int column = tableView.convertColumnIndexToModel(viewColumn);
374                 if (e.getClickCount() == 1 && column != -1) {
375                     // System.out.println("Sorting ...");
376                     int shiftPressed = e.getModifiers()&InputEvent.SHIFT_MASK;
377                     boolean ascending = (shiftPressed == 0);
378                     sorter.sortByColumn(column, ascending);
379                 }
380              }
381          };
382         JTableHeader th = tableView.getTableHeader();
383         th.addMouseListener(listMouseListener);
384     }
385 
386     // Allow others to be notified when re-sorting is done
addActionListener(ActionListener l)387     public void addActionListener(ActionListener l) {
388 	listeners.addElement(l);
389     }
390 
391     // Take me off the notify list
removeActionListener(ActionListener l)392     public void removeActionListener(ActionListener l) {
393 	listeners.removeElement(l);
394     }
395 
396     /*
397      * Notify listeners of sort events; we just use ActionEvent as it's a
398      * good all-purpose event
399      */
fireActionPerformed()400     protected void fireActionPerformed() {
401 	ActionEvent e = new ActionEvent(this, ActionEvent.ACTION_PERFORMED,
402 	    sortingColumns.firstElement().toString());
403 	Enumeration en = listeners.elements();
404 	while (en.hasMoreElements()) {
405 	    ActionListener l = (ActionListener)en.nextElement();
406 	    l.actionPerformed(e);
407 	}
408     }
409 }
410