package org.freehep.swing.table;
import java.util.Comparator;
import javax.swing.event.EventListenerList;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.TableModel;
/**
* Converts any TableModel to a SortableTableModel.
* @author Tony Johnson
* @version $Id: DefaultSortableTableModel.java 12627 2007-06-10 04:50:35Z tonyj $
*/
public class DefaultSortableTableModel implements SortableTableModel
{
private Comparator comparator = new DefaultComparator();
private TableModel source;
private EventListenerList listeners = new EventListenerList();
private TableModelListener internalListener = new InternalTableModelListener();
private int sortColumn = UNSORTED;
private boolean ascending = true;
private int[] rowMap;
private int[] reverseMap;
boolean reverseMapValid = false;
private int nRows;
/**
* Creates a new instance of DefaultTableSorter
* @param source The table model to be converted.
*/
public DefaultSortableTableModel(TableModel source)
{
this.source = source;
}
private int mapFromSorted(int rowIndex)
{
return rowMap == null ? rowIndex : rowMap[rowIndex];
}
private int mapToSorted(int rowIndex)
{
if (rowMap != null && !reverseMapValid)
{
if (reverseMap == null || reverseMap.length < nRows)
{
reverseMap = new int[rowMap.length];
}
for (int i=0; i<nRows; i++) reverseMap[rowMap[i]] = i;
reverseMapValid = true;
}
return rowMap == null ? rowIndex : reverseMap[rowIndex];
}
public void addTableModelListener(TableModelListener l)
{
if (listeners.getListenerCount() == 0)
{
// If we have not been listening for changes to model, we must assume
// it has totally changed!
dataChanged();
source.addTableModelListener(internalListener);
}
listeners.add(TableModelListener.class, l);
}
public Class getColumnClass(int columnIndex)
{
return source.getColumnClass(columnIndex);
}
public int getColumnCount()
{
return source.getColumnCount();
}
public String getColumnName(int columnIndex)
{
return source.getColumnName(columnIndex);
}
public int getRowCount()
{
return source.getRowCount();
}
public Object getValueAt(int rowIndex, int columnIndex)
{
return source.getValueAt(mapFromSorted(rowIndex),columnIndex);
}
public boolean isCellEditable(int rowIndex, int columnIndex)
{
return source.isCellEditable(mapFromSorted(rowIndex),columnIndex);
}
public void removeTableModelListener(TableModelListener l)
{
listeners.remove(TableModelListener.class, l);
if (listeners.getListenerCount() == 0) source.removeTableModelListener(internalListener);
}
public void setValueAt(Object aValue, int rowIndex, int columnIndex)
{
source.setValueAt(aValue, mapFromSorted(rowIndex), columnIndex);
}
public void sort(int column, boolean ascending)
{
if (column == UNSORTED)
{
if (this.sortColumn != UNSORTED)
{
rowMap = null;
reverseMap = null;
reverseMapValid = false;
this.sortColumn = column;
TableModelEvent ee = new TableModelEvent(this,0,source.getRowCount()-1,TableModelEvent.ALL_COLUMNS);
fireTableChanged(ee);
}
}
else if (column == this.sortColumn)
{
if (ascending != this.ascending)
{
this.ascending = ascending;
for (int i=0; i<nRows/2; i++) swap(i,nRows-1-i);
reverseMapValid = false;
TableModelEvent ee = new TableModelEvent(this,0,nRows-1,TableModelEvent.ALL_COLUMNS);
fireTableChanged(ee);
}
}
else
{
if (rowMap == null)
{
nRows = source.getRowCount();
rowMap = new int[nRows+10]; // Leave a little room for expansion
for (int i=0; i<nRows; i++) rowMap[i] = i;
}
this.sortColumn = column;
this.ascending = ascending;
reverseMapValid = false;
sort1(0,nRows);
TableModelEvent ee = new TableModelEvent(this,0,nRows-1,TableModelEvent.ALL_COLUMNS);
fireTableChanged(ee);
}
}
private void reSort()
{
reverseMapValid = false;
sort1(0,nRows);
TableModelEvent ee = new TableModelEvent(this,0,nRows-1,TableModelEvent.ALL_COLUMNS);
fireTableChanged(ee);
}
private void dataChanged()
{
// Entire data was changed, including (perhaps) number of rows
nRows = source.getRowCount();
rowMap = new int[nRows+10]; // Leave a little room for expansion
for (int i=0; i<nRows; i++) rowMap[i] = i;
reverseMapValid = false;
if (sortColumn != UNSORTED) sort1(0,nRows);
TableModelEvent ee = new TableModelEvent(this,0,Integer.MAX_VALUE,TableModelEvent.ALL_COLUMNS);
fireTableChanged(ee);
}
// Insert a newly added source row in a sorted list
private int rowWasInserted(int row)
{
if (nRows == rowMap.length)
{
int[] newMap = new int[rowMap.length+10];
System.arraycopy(rowMap,0,newMap,0,rowMap.length);
rowMap = newMap;
}
// Add new row to the end to begin with
for (int i=0; i<nRows; i++)
{
if (rowMap[i] >= row) rowMap[i]++;
}
rowMap[nRows] = row;
// Do a binary search to find out where the new row should go
int insertPoint = binarySearch(nRows,0,nRows);
if (insertPoint != nRows)
{
System.arraycopy(rowMap,insertPoint, rowMap, insertPoint+1,nRows-insertPoint);
rowMap[insertPoint] = row;
}
nRows++;
reverseMapValid = false;
return insertPoint;
}
private int binarySearch(int newRow, int start, int end)
{
if (start-end < 5)
{
for (int i=start; i<end; i++)
{
if (compare(newRow,i) <= 0 ) return i;
}
return end;
}
int mid = end-start >> 1;
int result = compare(newRow,mid);
if (result == 0) return mid;
else if (result > 0) return binarySearch(newRow,mid,end);
else return binarySearch(newRow,start,mid);
}
private int rowWasDeleted(int row)
{
int sortedRow = mapToSorted(row);
System.arraycopy(rowMap,sortedRow+1,rowMap,sortedRow,nRows-sortedRow-1);
nRows--;
for (int i=0; i<nRows; i++)
{
if (rowMap[i] > row) rowMap[i]--;
}
reverseMapValid = false;
return sortedRow;
}
private Object get(int index)
{
return source.getValueAt(rowMap[index], sortColumn);
}
private int compare(int i, int j)
{
return comparator.compare(get(i),get(j)) * (ascending ? 1 : -1);
}
private int compare(Object o, int j)
{
return comparator.compare(o,get(j)) * (ascending ? 1 : -1);
}
private void swap(int i, int j)
{
int tmp = rowMap[i];
rowMap[i] = rowMap[j];
rowMap[j] = tmp;
}
private int med3(int a, int b, int c)
{
return compare(a,b)<0 ?
compare(b,c)<0 ? b : compare(a,c)<0 ? c : a :
compare(b,c)>0 ? b : compare(a,c)>0 ? c : a;
}
private void vecswap(int a, int b, int n)
{
for (int i=0; i<n; i++, a++, b++)
swap(a, b);
}
private void sort1(int off, int len)
{
// Insertion sort on smallest arrays
if (len < 7)
{
for (int i=off; i<len+off; i++)
for (int j=i; j>off && compare(j-1,j)>0; j--)
swap(j, j-1);
return;
}
// Choose a partition element, v
int m = off + (len >> 1); // Small arrays, middle element
if (len > 7)
{
int l = off;
int n = off + len - 1;
if (len > 40) // Big arrays, pseudomedian of 9
{
int s = len/8;
l = med3(l, l+s, l+2*s);
m = med3(m-s, m, m+s);
n = med3(n-2*s, n-s, n);
}
m = med3(l, m, n); // Mid-size, med of 3
}
Object v = get(m);
// Establish Invariant: v* (<v)* (>v)* v*
int a = off, b = a, c = off + len - 1, d = c;
int comp;
while(true)
{
while (b <= c && (comp = compare(v,b)) >= 0)
{
if (comp == 0) swap(a++, b);
b++;
}
while (c >= b && (comp = compare(v,c)) <= 0)
{
if (comp == 0) swap(c, d--);
c--;
}
if (b > c) break;
swap(b++, c--);
}
// Swap partition elements back to middle
int s, n = off + len;
s = Math.min(a-off, b-a ); vecswap(off, b-s, s);
s = Math.min(d-c, n-d-1); vecswap(b, n-s, s);
// Recursively sort non-partition-elements
if ((s = b-a) > 1) sort1(off, s);
if ((s = d-c) > 1) sort1(n-s, s);
}
/**
* Notifies all listeners of a change to the sorted TableModel.
* @param event The event to be sent to the listeners.
*/
protected void fireTableChanged(TableModelEvent event)
{
TableModelListener[] l = (TableModelListener[]) listeners.getListeners(TableModelListener.class);
for (int i=0; i<l.length; i++)
{
l[i].tableChanged(event);
}
}
public boolean isSortAscending()
{
return ascending;
}
public int getSortOnColumn()
{
return sortColumn;
}
private class InternalTableModelListener implements TableModelListener
{
public void tableChanged(TableModelEvent e)
{
int column = e.getColumn();
int first = e.getFirstRow();
int last = e.getLastRow();
int type = e.getType();
if (sortColumn == UNSORTED)
{
TableModelEvent ee = new TableModelEvent(DefaultSortableTableModel.this,first,last,column,type);
fireTableChanged(ee);
}
else if (first == TableModelEvent.HEADER_ROW)
{
// one or more entire columns was added, removed or changed -- deal with it
if (type == TableModelEvent.DELETE)
{
if (column < sortColumn) sortColumn--;
else if (column == sortColumn)
{
sort(UNSORTED,true);
}
}
else if (type == TableModelEvent.INSERT)
{
if (column <= sortColumn && sortColumn != UNSORTED) sortColumn++;
}
else if (type == TableModelEvent.UPDATE)
{
if (column == sortColumn || column == TableModelEvent.ALL_COLUMNS) reSort();
}
TableModelEvent ee = new TableModelEvent(DefaultSortableTableModel.this,first,last,column,type);
fireTableChanged(ee);
}
else if (column == TableModelEvent.ALL_COLUMNS)
{
// one or more rows were added, removed or changed -- deal with it
if (type == TableModelEvent.DELETE)
{
for (int i=first; i<=last; i++)
{
int sortedRow = rowWasDeleted(first); // Note, always first because previous first was deleted
TableModelEvent ee = new TableModelEvent(DefaultSortableTableModel.this,sortedRow,sortedRow,column,type);
fireTableChanged(ee);
}
}
else if (type == TableModelEvent.INSERT)
{
for (int i=first; i<=last; i++)
{
int sortedRow = rowWasInserted(i);
TableModelEvent ee = new TableModelEvent(DefaultSortableTableModel.this,sortedRow,sortedRow,column,type);
fireTableChanged(ee);
}
}
else if (type == TableModelEvent.UPDATE)
{
if (Integer.MAX_VALUE == last)
{
dataChanged();
}
else
{
for (int i=first; i<=last; i++)
{
int oldRow = rowWasDeleted(i);
int newRow = rowWasInserted(i);
if (oldRow == newRow)
{
TableModelEvent ee = new TableModelEvent(DefaultSortableTableModel.this,newRow,newRow,column,type);
fireTableChanged(ee);
}
else
{
TableModelEvent ee1 = new TableModelEvent(DefaultSortableTableModel.this,oldRow,oldRow,TableModelEvent.ALL_COLUMNS,TableModelEvent.DELETE);
fireTableChanged(ee1);
TableModelEvent ee2 = new TableModelEvent(DefaultSortableTableModel.this,newRow,newRow,TableModelEvent.ALL_COLUMNS,TableModelEvent.INSERT);
fireTableChanged(ee2);
}
}
}
}
}
else if (type == TableModelEvent.UPDATE)
{
if (column == sortColumn)
{
for (int i=first; i<=last; i++)
{
int oldRow = rowWasDeleted(i);
int newRow = rowWasInserted(i);
if (oldRow == newRow)
{
TableModelEvent ee = new TableModelEvent(DefaultSortableTableModel.this,newRow,newRow,column,type);
fireTableChanged(ee);
}
else
{
TableModelEvent ee1 = new TableModelEvent(DefaultSortableTableModel.this,oldRow,oldRow,TableModelEvent.ALL_COLUMNS,TableModelEvent.DELETE);
fireTableChanged(ee1);
TableModelEvent ee2 = new TableModelEvent(DefaultSortableTableModel.this,newRow,newRow,TableModelEvent.ALL_COLUMNS,TableModelEvent.INSERT);
fireTableChanged(ee2);
}
}
}
else
{
for (int i=first; i<=last; i++)
{
int sortedRow = mapToSorted(i);
TableModelEvent ee = new TableModelEvent(DefaultSortableTableModel.this,sortedRow,sortedRow,column,type);
fireTableChanged(ee);
}
}
}
else throw new UnsupportedOperationException("An unsupported TableModelEvent was found: "+e);
}
}
private class DefaultComparator implements Comparator
{
public int compare(Object o1, Object o2)
{
if ((o1 instanceof Comparable) && (o2 instanceof Comparable))
return ((Comparable) o1).compareTo((Comparable) o2);
else return String.valueOf(o1).compareTo(String.valueOf(o2));
}
}
}