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 
  79     public TableSorter() {
  80         indexes = new int[0]; // For consistency.        
  81     }
  82 
  83     public TableSorter(TableModel model) {
  84         setModel(model);
  85     }
  86 
  87     public void setModel(TableModel model) {
  88         super.setModel(model); 
  89         reallocateIndexes(); 
  90     }
  91 
  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 
 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 
 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 
 227     public void tableChanged(TableModelEvent e) {
 228         // System.out.println("Sorter: tableChanged"); 
 229         reallocateIndexes();
 230         sort(this);
 231         super.tableChanged(e);
 232     }
 233 
 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 
 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 
 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      */
 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 
 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 
 326     public Object getValueAt(int aRow, int aColumn)
 327     {
 328         checkModel();
 329         return model.getValueAt(indexes[aRow], aColumn);
 330     }
 331     
 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 
 341     public void setValueAt(Object aValue, int aRow, int aColumn) {
 342         checkModel();
 343         model.setValueAt(aValue, indexes[aRow], aColumn);
 344     }
 345 
 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 
 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      */
 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
 387     public void addActionListener(ActionListener l) {
 388         listeners.addElement(l);
 389     }
 390     
 391     // Take me off the notify list
 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      */
 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 }