net.sf.cuf.model.ui.CufTableRowSorter Maven / Gradle / Ivy
package net.sf.cuf.model.ui;
import javax.swing.JTable;
import javax.swing.SortOrder;
import javax.swing.table.TableModel;
import javax.swing.table.TableRowSorter;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
/**
* RowSorter that allows switching to {@link SortOrder#UNSORTED} in addition what a
* {@link TableRowSorter}, already does, like taking into consideration filters like CoderazziFilter.
*
* The public methods are based on the now deprecated NewTableSorter, which was a TableModel, to make migration to this sorter easier.
*
* In contrast to the NewTableSorter, selection is not lost/mistaken when sorting and using filter at the same time.
*/
public class CufTableRowSorter extends TableRowSorter
{
/** Standard comparator used for classes that implements Comparable interface */
public static final Comparator> COMPARABLE_COMPARATOR = new ComparableComparator();
/** You can sort by max 3 columns */
private final static int MAX_SORT_KEYS = 3;
/**
* "-1" safe helper method to convert from a view index to a model index.
* @param pTable the table, must not be null
* @param pViewIndex -1 or the view index
* @return -1 or the model index
*/
public static int convertRowIndexToModel(JTable pTable, int pViewIndex)
{
if (pViewIndex<0)
return pViewIndex;
else if (pViewIndex >= pTable.getRowCount())
return -1; // map bad indices to -1
else
return pTable.convertRowIndexToModel(pViewIndex);
}
/**
* "-1" safe helper method to convert from a model index to a view index.
* @param pTable the table, must not be null
* @param pModelIndex -1 or the model index
* @return -1 or the view index
*/
public static int convertRowIndexToView(JTable pTable, int pModelIndex)
{
if (pModelIndex<0)
return pModelIndex;
else if (pModelIndex >= pTable.getModel().getRowCount())
return -1; // map bad indices to -1
else
return pTable.convertRowIndexToView(pModelIndex);
}
/**
* Creates a CufTableRowSorter
using pModel
* as the underlying TableModel
. We assume that all columns are sortable.
*
* @param pModel the underlying TableModel
to use,
* null
is treated as an empty model
*/
public CufTableRowSorter(M pModel)
{
super(pModel);
for (int i=0; i< getColumnCount(); i++)
{
setSortable(i, true);
}
setMaxSortKeys(MAX_SORT_KEYS);
}
/**
* Sets comparator of given column in table
* @param pColumn column to be set with comparator
* @param pComparator comparator
*/
public void setColumnComparator(final int pColumn, final Comparator> pComparator)
{
setComparator(pColumn, pComparator);
}
/**
* Adds given comparator to all columns of particular type (class)
* @param pType column data type (class)
* @param pComparator comparator
*/
public void setColumnComparator(final Class> pType, final Comparator> pComparator)
{
for(int pColumn=0; pColumn< getColumnCount(); pColumn++)
{
if (getColumnClass(pColumn) == pType)
{
setComparator(pColumn, pComparator);
}
}
}
/**
* Check if the given cell is editable.
* @param pRow the row index
* @param pColumn the column index
* @return true if the cell can be edited
*/
public boolean isCellEditable(final int pRow, final int pColumn)
{
return getModelWrapper().getModel().isCellEditable(pRow, pColumn);
}
/**
* Get the value of the given cell.
* @param pRow the row index
* @param pColumn the column index
* @return the current value of the cell
*/
public Object getValueAt(final int pRow, final int pColumn)
{
return getModelWrapper().getValueAt(pRow, pColumn);
}
/**
* Sets value on given cell.
* @param pValue value to set
* @param pRow row index
* @param pColumn column index
*/
public void setValueAt(final Object pValue, final int pRow, final int pColumn)
{
getModelWrapper().getModel().setValueAt(pValue, pRow, pColumn);
}
/**
* Forces initial sorting (ASCENDING) of given column
* @param pColumnForInitialSorting column ID
*/
public void forceInitialSorting(int pColumnForInitialSorting)
{
toggleSortOrder(pColumnForInitialSorting);
}
/**
* Checks sorting type applied to given Column
* @param pColumn column index
* @return soring type
*/
public SortOrder getSortingStatus(final int pColumn)
{
checkColumn(pColumn);
final List sortKeys = new ArrayList<>(getSortKeys());
for(SortKey sortKey : sortKeys)
{
if(sortKey.getColumn() == pColumn)
{
return sortKey.getSortOrder();
}
}
return SortOrder.UNSORTED;
}
/**
* Removes all applied sorting.
*/
public void clearSortingState()
{
setSortKeys(Collections.emptyList());
}
/**
* Checks if any sorting type (other than UNSORTED) is applied to the table
* @return true if any sorting type (other than UNSORTED) was applied, false otherwise
*/
public boolean isSorting()
{
return !getSortKeys().stream().allMatch(key -> key.getSortOrder() == SortOrder.UNSORTED);
}
/**
* Overrides sorting for given column, or add new one
* @param pColumn column index
* @param pSortOrder new sort order for given column
*/
public void setSortingStatus(final int pColumn, final SortOrder pSortOrder)
{
final List sortKeys = new ArrayList<>(getSortKeys());
SortKey newSortKey = new SortKey(pColumn, pSortOrder);
updateSortKeys(sortKeys, newSortKey);
setSortKeys(sortKeys);
}
/**
* Updates given list of SortKeys:
* - if {@param pNewSortKey} exists in given list {@param pSortKeys} by the means of
* column ID, then its replaced (removed, and added at the end)
* - if {@param pNewSortKey} does NOT exists in given list {@param pSortKeys}
* by the means of column ID, then its added
* @param pSortKeys current sorting
* @param pNewSortKey new sorting of particular column
*/
private void updateSortKeys(List pSortKeys, SortKey pNewSortKey)
{
Iterator sortKeyIterator = pSortKeys.iterator();
while(sortKeyIterator.hasNext())
{
final SortKey sortKey = sortKeyIterator.next();
if(sortKey.getColumn() == pNewSortKey.getColumn())
{
sortKeyIterator.remove();
break;
}
}
pSortKeys.add(pNewSortKey);
}
/**
* We need to override {@link javax.swing.DefaultRowSorter#toggleSortOrder}
* in order to 'toggle' in a different way:
* ASCENDING - DESCENDING - UNSORTED
* By default, no UNSORTED is considered
*
* @param pColumn index of the column to make the primary sorted column,
* in terms of the underlying model
*/
@Override
public void toggleSortOrder(int pColumn)
{
checkColumn(pColumn);
if (isSortable(pColumn))
{
List keys = new ArrayList<>(getSortKeys());
SortKey sortKey;
int sortIndex;
for (sortIndex = keys.size() - 1; sortIndex >= 0; sortIndex--)
{
if (keys.get(sortIndex).getColumn() == pColumn)
{
break;
}
}
if (sortIndex == -1)
{
// Key doesn't exist
sortKey = new SortKey(pColumn, SortOrder.ASCENDING);
keys.add(0, sortKey);
}
else if (sortIndex == 0)
{
// It's the primary sorting key, toggle it
keys.set(0, toggle(keys.get(0)));
}
else
{
// It's not the first, but was sorted on, remove old
// entry, insert as first with ascending.
keys.remove(sortIndex);
keys.add(0, new SortKey(pColumn, SortOrder.ASCENDING));
}
if (keys.size() > getMaxSortKeys())
{
keys = keys.subList(0, getMaxSortKeys());
}
setSortKeys(keys);
}
}
/**
* Modified javax.swing.DefaultRowSorter#toggle(SortKey) method using also UNSORTED type
* @param key sort key to toggle
* @return toggled sort key ASCENDING -> DESCENDING -> UNSORTED
*/
private SortKey toggle(SortKey key)
{
if (key.getSortOrder() == SortOrder.ASCENDING)
{
return new SortKey(key.getColumn(), SortOrder.DESCENDING);
}
else if (key.getSortOrder() == SortOrder.DESCENDING)
{
return new SortKey(key.getColumn(), SortOrder.UNSORTED);
}
return new SortKey(key.getColumn(), SortOrder.ASCENDING);
}
/**
* @return the number of (filtered) rows
*/
public int getRowCount()
{
return (getModelWrapper() == null) ? 0 : getModelWrapper().getRowCount();
}
/**
* @return the number of (filtered) columns
*/
public int getColumnCount()
{
return (getModelWrapper() == null) ? 0 : getModelWrapper().getColumnCount();
}
/**
* Get the column name.
* @param pColumn the column index
* @return the name of the column
*/
public String getColumnName(final int pColumn)
{
checkColumn(pColumn);
return getModelWrapper().getModel().getColumnName(pColumn);
}
/**
* Get the column class.
* @param pColumn the column index
* @return the class of the column
*/
public Class> getColumnClass(final int pColumn)
{
checkColumn(pColumn);
return getModelWrapper().getModel().getColumnClass(pColumn);
}
/**
* @return the underlying table model
*/
public TableModel getTableModel()
{
return getModel();
}
/**
* Change the table model
* @param pTableModel the new table model
*/
public void setTableModel(final M pTableModel)
{
setModel(pTableModel);
}
/**
* Check if the column is valid
* @param pColumn column index to check.
* @throws IndexOutOfBoundsException if the column is invalid
*/
private void checkColumn(int pColumn)
{
if (getModelWrapper() == null)
{
throw new IllegalStateException("Model not initialised");
}
if (pColumn < 0 || pColumn >= getModelWrapper().getColumnCount())
{
throw new IndexOutOfBoundsException("column "+pColumn+" beyond range of TableModel "+getModelWrapper().getColumnCount());
}
}
/**
* Adapted standard {@link TableRowSorter#getComparator} method in order to handle
* comparison of arrays, as well as supporting a default Collator.
*
* The default comparator is a Collator, which is meant for String class.
* Because there could be a mismatch - xml table definition will say a column is a String,
* but in model (ValueModel) it is something else (e.g. Long) - then brute force casting from Collator won't work.
* That's why we need to get rid of Collator and use more generic comparator based on Comparable interface.
*
* @param pColumn the column to fetch the Comparator
for, in
* terms of the underlying model
* @return comparator
*/
@Override
public Comparator> getComparator(int pColumn)
{
Comparator> comparator = super.getComparator(pColumn);
// if comparator was set, and it's not default Collator - return it
if (comparator != null && !(comparator instanceof Collator))
{
return comparator;
}
Class> columnClass = getModel().getColumnClass(pColumn);
// standard case - make use of comparable interface
if (Comparable.class.isAssignableFrom(columnClass))
{
return COMPARABLE_COMPARATOR;
}
// handle arrays otherwise
return LEXICAL_COMPARATOR;
}
/*
* Helper classes
*/
/**
* Static Lexical-Comparator with a special handling of String[]'s.
*/
public static final Comparator> LEXICAL_COMPARATOR = (pO1, pO2) -> {
// compare the first element of each array
if (pO1 instanceof String[] && pO2 instanceof String[])
{
String[] s1 = (String[]) pO1;
String[] s2 = (String[]) pO2;
if (s1.length > 0 && s2.length > 0)
{
return s1[0].compareTo(s2[0]);
}
}
String s1 = pO1.toString();
String s2 = pO2.toString();
//noinspection StringEquality
if (s1 == s2)
{
return 0;
}
if (s1 == null)
{
return -1;
}
if (s2 == null)
{
return 1;
}
return s1.compareTo(s2);
};
private static class ComparableComparator implements Comparator>
{
@Override
public int compare(Comparable o1, Comparable o2)
{
//noinspection unchecked
return o1.compareTo(o2);
}
}
}