I found another solution by subclassing only TableRowSorter.
From the DefaultRowSorter documentation we know :
The Comparator is never passed null
When subclassing DefaultRowSorter.ModelWrapper, we can return a special Null-Object and create a custom comparator handling that value.
Here follows my code. Probably it is not as efficient as a custom RowSorter implementation and it may still contain some bugs, I did not test everything, but for my requirements it works.
class EmptyTableRowSorter<M extends AbstracTableModel> extends TableRowSorter<M> {
private static final EmptyValue emptyValue = new EmptyValue();
public EmptyTableRowSorter(M model) {
super(model);
}
@Override
public void modelStructureChanged() {
// deletes comparators, so we must set again
super.modelStructureChanged();
M model = getModelWrapper().getModel();
for (int i = 0; i < model.getColumnCount(); i++) {
Comparator<?> comparator = this.getComparator(i);
if (comparator != null) {
Comparator wrapper = new EmptyValueComparator(comparator, this, i);
this.setComparator(i, wrapper);
}
}
}
@Override
public void setModel(M model) {
// also calls setModelWrapper method
super.setModel(model);
ModelWrapper<M, Integer> modelWrapper = getModelWrapper();
EmptyTableModelWrapper emptyTableModelWrapper = new EmptyTableModelWrapper(modelWrapper);
// calls modelStructureChanged method
setModelWrapper(emptyTableModelWrapper);
}
/**
* The DefaulRowSorter implementation does not pass null values from the table
* to the comparator.
* This implementation is a wrapper around the default ModelWrapper,
* returning a non null object for our empty row that our comparator can handle.
*/
private class EmptyTableModelWrapper extends DefaultRowSorter.ModelWrapper {
private final DefaultRowSorter.ModelWrapper modelWrapperImplementation;
public EmptyTableModelWrapper(ModelWrapper modelWrapperImplementation) {
this.modelWrapperImplementation = modelWrapperImplementation;
}
@Override
public Object getModel() {
return modelWrapperImplementation.getModel();
}
@Override
public int getColumnCount() {
return modelWrapperImplementation.getColumnCount();
}
@Override
public int getRowCount() {
return modelWrapperImplementation.getRowCount();
}
@Override
public Object getValueAt(int row, int column) {
M model = EmptyTableRowSorter.this.getModel();
// my model has the empty row always at the end,
// change this depending on your needs
int lastRow = model.getRowCount() - 1;
if (row == lastRow) {
return emptyValue;
}
return modelWrapperImplementation.getValueAt(row, column);
}
//@Override
//public String getStringValueAt(int row, int column) {
// // also override this if there is no comparator definied for a column
//}
@Override
public Object getIdentifier(int row) {
return modelWrapperImplementation.getIdentifier(row);
}
}
/**
* This is a wrapper around another comparator.
* We handle our empty value and if none, we invoke the base comparator.
*/
private class EmptyValueComparator implements Comparator {
private final Comparator defaultComparator;
private final TableRowSorter tableRowSorter;
private final int columnIndex;
public EmptyValueComparator(Comparator defaultComparator, TableRowSorter tableRowSorter, int columnIndex) {
this.defaultComparator = defaultComparator;
this.tableRowSorter = tableRowSorter;
this.columnIndex = columnIndex;
}
@Override
public int compare(Object o1, Object o2) {
if (o1 instanceof EmptyValue && o2 instanceof EmptyValue) {
return 0;
}
if (o1 instanceof EmptyValue) {
return adjustSortOrder(1);
}
if (o2 instanceof EmptyValue) {
return adjustSortOrder(-1);
}
return defaultComparator.compare(o1, o2);
}
/**
* Changes the result so that the empty row is always at the end,
* regardless of the sort order.
*/
private int adjustSortOrder(int result) {
List sortKeys = tableRowSorter.getSortKeys();
for (Object sortKeyObject : sortKeys) {
SortKey sortKey = (SortKey) sortKeyObject;
if (sortKey.getColumn() == columnIndex) {
SortOrder sortOrder = sortKey.getSortOrder();
if (sortOrder == SortOrder.DESCENDING) {
result *= -1;
}
return result;
}
}
return result;
}
}
private static class EmptyValue {}
}
Now you can enable sorting in your table.
JTable table = ...;
TableRowSorter tableRowSorter = new EmptyTableRowSorter(table.getModel());
table.setRowSorter(tableRowSorter);