Package nz.govt.natlib.meta.ui.log

Source Code of nz.govt.natlib.meta.ui.log.TableSorter

/*
*  Copyright 2006 The National Library of New Zealand
*
*  Licensed under the Apache License, Version 2.0 (the "License");
*  you may not use this file except in compliance with the License.
*  You may obtain a copy of the License at
*
*      http://www.apache.org/licenses/LICENSE-2.0
*
*  Unless required by applicable law or agreed to in writing, software
*  distributed under the License is distributed on an "AS IS" BASIS,
*  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*  See the License for the specific language governing permissions and
*  limitations under the License.
*/

package nz.govt.natlib.meta.ui.log;

/**
* A sorter for TableModels. The sorter has a model (conforming to TableModel)
* and itself implements TableModel. TableSorter does not store or copy the data
* in the TableModel, instead it maintains an array of integers which it keeps
* the same size as the number of rows in its model. When the model changes it
* notifies the sorter that something has changed eg. "rowsAdded" so that its
* internal array of integers can be reallocated. As requests are made of the
* sorter (like getValueAt(row, col) it redirects them to its model via the
* mapping array. That way the TableSorter appears to hold another copy of the
* table with the rows in a different order. The sorting algorthm used is stable
* which means that it does not move around rows when its comparison function
* returns 0 to denote that they are equivalent.
*
* @version 1.5 12/17/97
* @author Philip Milne
*/

import java.awt.event.InputEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Date;
import java.util.Vector;

import javax.swing.JTable;
import javax.swing.event.TableModelEvent;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableColumnModel;
import javax.swing.table.TableModel;

import nz.govt.natlib.meta.log.LogLevel;

public class TableSorter extends TableMap {
  int indexes[];

  Vector sortingColumns = new Vector();

  boolean ascending = true;

  int compares;

  public TableSorter() {
    indexes = new int[0]; // for consistency
  }

  public TableSorter(TableModel model) {
    setModel(model);
  }

  public void setModel(TableModel model) {
    super.setModel(model);
    reallocateIndexes();
  }

  public int compareRowsByColumn(int row1, int row2, int column) {
    Class type = model.getColumnClass(column);
    TableModel data = model;

    // Check for nulls.

    Object o1 = data.getValueAt(row1, column);
    Object o2 = data.getValueAt(row2, column);

    // If both values are null, return 0.
    if (o1 == null && o2 == null) {
      return 0;
    } else if (o1 == null) { // Define null less than everything.
      return -1;
    } else if (o2 == null) {
      return 1;
    }

    /*
     * We copy all returned values from the getValue call in case an
     * optimised model is reusing one object to return many values. The
     * Number subclasses in the JDK are immutable and so will not be used in
     * this way but other subclasses of Number might want to do this to save
     * space and avoid unnecessary heap allocation.
     */

    if (type == LogLevel.class) {
      LogLevel n1 = (LogLevel) data.getValueAt(row1, column);
      double d1 = n1.getLevel();
      LogLevel n2 = (LogLevel) data.getValueAt(row2, column);
      double d2 = n2.getLevel();

      if (d1 < d2) {
        return 1;
      } else if (d1 > d2) {
        return -1;
      } else {
        return 0;
      }
    } else if (type.getSuperclass() == java.lang.Number.class) {
      Number n1 = (Number) data.getValueAt(row1, column);
      double d1 = n1.doubleValue();
      Number n2 = (Number) data.getValueAt(row2, column);
      double d2 = n2.doubleValue();

      if (d1 < d2) {
        return -1;
      } else if (d1 > d2) {
        return 1;
      } else {
        return 0;
      }
    } else if (type == java.util.Date.class) {
      Date d1 = (Date) data.getValueAt(row1, column);
      long n1 = d1.getTime();
      Date d2 = (Date) data.getValueAt(row2, column);
      long n2 = d2.getTime();

      if (n1 < n2) {
        return -1;
      } else if (n1 > n2) {
        return 1;
      } else {
        return 0;
      }
    } else if (type == String.class) {
      String s1 = (String) data.getValueAt(row1, column);
      String s2 = (String) data.getValueAt(row2, column);
      int result = s1.compareTo(s2);

      if (result < 0) {
        return -1;
      } else if (result > 0) {
        return 1;
      } else {
        return 0;
      }
    } else if (type == Boolean.class) {
      Boolean bool1 = (Boolean) data.getValueAt(row1, column);
      boolean b1 = bool1.booleanValue();
      Boolean bool2 = (Boolean) data.getValueAt(row2, column);
      boolean b2 = bool2.booleanValue();

      if (b1 == b2) {
        return 0;
      } else if (b1) { // Define false < true
        return 1;
      } else {
        return -1;
      }
    } else {
      Object v1 = data.getValueAt(row1, column);
      String s1 = v1.toString();
      Object v2 = data.getValueAt(row2, column);
      String s2 = v2.toString();
      int result = s1.compareTo(s2);

      if (result < 0) {
        return -1;
      } else if (result > 0) {
        return 1;
      } else {
        return 0;
      }
    }
  }

  public int compare(int row1, int row2) {
    compares++;
    for (int level = 0; level < sortingColumns.size(); level++) {
      Integer column = (Integer) sortingColumns.elementAt(level);
      int result = compareRowsByColumn(row1, row2, column.intValue());
      if (result != 0) {
        return ascending ? result : -result;
      }
    }
    return 0;
  }

  public void reallocateIndexes() {
    int rowCount = model.getRowCount();

    // Set up a new array of indexes with the right number of elements
    // for the new data model.
    indexes = new int[rowCount];

    // Initialise with the identity mapping.
    for (int row = 0; row < rowCount; row++) {
      indexes[row] = row;
    }
  }

  public void tableChanged(TableModelEvent e) {
    // System.out.println("Sorter: tableChanged");
    reallocateIndexes();

    super.tableChanged(e);
  }

  public void checkModel() {
    if (indexes.length != model.getRowCount()) {
      System.err.println("Sorter not informed of a change in model.");
    }
  }

  public void sort(Object sender) {
    checkModel();

    compares = 0;
    // n2sort();
    // qsort(0, indexes.length-1);
    shuttlesort((int[]) indexes.clone(), indexes, 0, indexes.length);
    // System.out.println("Compares: "+compares);
  }

  public void n2sort() {
    for (int i = 0; i < getRowCount(); i++) {
      for (int j = i + 1; j < getRowCount(); j++) {
        if (compare(indexes[i], indexes[j]) == -1) {
          swap(i, j);
        }
      }
    }
  }

  // This is a home-grown implementation which we have not had time
  // to research - it may perform poorly in some circumstances. It
  // requires twice the space of an in-place algorithm and makes
  // NlogN assigments shuttling the values between the two
  // arrays. The number of compares appears to vary between N-1 and
  // NlogN depending on the initial order but the main reason for
  // using it here is that, unlike qsort, it is stable.
  public void shuttlesort(int from[], int to[], int low, int high) {
    if (high - low < 2) {
      return;
    }
    int middle = (low + high) / 2;
    shuttlesort(to, from, low, middle);
    shuttlesort(to, from, middle, high);

    int p = low;
    int q = middle;

    /*
     * This is an optional short-cut; at each recursive call, check to see
     * if the elements in this subset are already ordered. If so, no further
     * comparisons are needed; the sub-array can just be copied. The array
     * must be copied rather than assigned otherwise sister calls in the
     * recursion might get out of sinc. When the number of elements is three
     * they are partitioned so that the first set, [low, mid), has one
     * element and and the second, [mid, high), has two. We skip the
     * optimisation when the number of elements is three or less as the
     * first compare in the normal merge will produce the same sequence of
     * steps. This optimisation seems to be worthwhile for partially ordered
     * lists but some analysis is needed to find out how the performance
     * drops to Nlog(N) as the initial order diminishes - it may drop very
     * quickly.
     */

    if (high - low >= 4 && compare(from[middle - 1], from[middle]) <= 0) {
      for (int i = low; i < high; i++) {
        to[i] = from[i];
      }
      return;
    }

    // A normal merge.

    for (int i = low; i < high; i++) {
      if (q >= high || (p < middle && compare(from[p], from[q]) <= 0)) {
        to[i] = from[p++];
      } else {
        to[i] = from[q++];
      }
    }
  }

  public void swap(int i, int j) {
    int tmp = indexes[i];
    indexes[i] = indexes[j];
    indexes[j] = tmp;
  }

  // The mapping only affects the contents of the data rows.
  // Pass all requests to these rows through the mapping array: "indexes".

  public Object getValueAt(int aRow, int aColumn) {
    checkModel();
    return model.getValueAt(indexes[aRow], aColumn);
  }

  public void setValueAt(Object aValue, int aRow, int aColumn) {
    checkModel();
    model.setValueAt(aValue, indexes[aRow], aColumn);
  }

  public void sortByColumn(int column) {
    sortByColumn(column, true);
  }

  public void sortByColumn(int column, boolean ascending) {
    this.ascending = ascending;
    sortingColumns.removeAllElements();
    sortingColumns.addElement(new Integer(column));
    sort(this);
    super.tableChanged(new TableModelEvent(this));
  }

  // There is no-where else to put this.
  // Add a mouse listener to the Table to trigger a table sort
  // when a column heading is clicked in the JTable.
  public void addMouseListenerToHeaderInTable(JTable table) {
    final TableSorter sorter = this;
    final JTable tableView = table;
    tableView.setColumnSelectionAllowed(false);
    MouseAdapter listMouseListener = new MouseAdapter() {
      public void mouseClicked(MouseEvent e) {
        JTable table = ((JTableHeader) e.getSource()).getTable();
        table.changeSelection(table.getSelectedRow(), table
            .getSelectedColumn(), true, false);

        TableColumnModel columnModel = tableView.getColumnModel();
        int viewColumn = columnModel.getColumnIndexAtX(e.getX());
        int column = tableView.convertColumnIndexToModel(viewColumn);
        if (e.getClickCount() == 1 && column != -1) {
          // System.out.println("Sorting ...");
          int shiftPressed = e.getModifiers() & InputEvent.SHIFT_MASK;
          boolean ascending = (shiftPressed == 0);
          sorter.sortByColumn(column, ascending);
        }
      }
    };
    JTableHeader th = tableView.getTableHeader();
    th.addMouseListener(listMouseListener);
  }
}
TOP

Related Classes of nz.govt.natlib.meta.ui.log.TableSorter

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.