FileDocCategorySizeDatePackage
DefaultRowSorter.javaAPI DocJava SE 6 API46560Tue Jun 10 00:26:36 BST 2008javax.swing

DefaultRowSorter

public abstract class DefaultRowSorter extends RowSorter
An implementation of RowSorter that provides sorting and filtering around a grid-based data model. Beyond creating and installing a RowSorter, you very rarely need to interact with one directly. Refer to {@link javax.swing.table.TableRowSorter TableRowSorter} for a concrete implementation of RowSorter for JTable.

Sorting is done based on the current SortKeys, in order. If two objects are equal (the Comparator for the column returns 0) the next SortKey is used. If no SortKeys remain or the order is UNSORTED, then the order of the rows in the model is used.

Sorting of each column is done by way of a Comparator that you can specify using the setComparator method. If a Comparator has not been specified, the Comparator returned by Collator.getInstance() is used on the results of calling toString on the underlying objects. The Comparator is never passed null. A null value is treated as occuring before a non-null value, and two null values are considered equal.

If you specify a Comparator that casts its argument to a type other than that provided by the model, a ClassCastException will be thrown when the data is sorted.

In addition to sorting, DefaultRowSorter provides the ability to filter rows. Filtering is done by way of a RowFilter that is specified using the setRowFilter method. If no filter has been specified all rows are included.

By default, rows are in unsorted order (the same as the model) and every column is sortable. The default Comparators are documented in the subclasses (for example, {@link javax.swing.table.TableRowSorter TableRowSorter}).

If the underlying model structure changes (the modelStructureChanged method is invoked) the following are reset to their default values: Comparators by column, current sort order, and whether each column is sortable. To find the default Comparators, see the concrete implementation (for example, {@link javax.swing.table.TableRowSorter TableRowSorter}). The default sort order is unsorted (the same as the model), and columns are sortable by default.

If the underlying model structure changes (the modelStructureChanged method is invoked) the following are reset to their default values: Comparators by column, current sort order and whether a column is sortable.

DefaultRowSorter is an abstract class. Concrete subclasses must provide access to the underlying data by invoking {@code setModelWrapper}. The {@code setModelWrapper} method must be invoked soon after the constructor is called, ideally from within the subclass's constructor. Undefined behavior will result if you use a {@code DefaultRowSorter} without specifying a {@code ModelWrapper}.

DefaultRowSorter has two formal type parameters. The first type parameter corresponds to the class of the model, for example DefaultTableModel. The second type parameter corresponds to the class of the identifier passed to the RowFilter. Refer to TableRowSorter and RowFilter for more details on the type parameters.

param
the type of the model
param
the type of the identifier passed to the RowFilter
version
1.20 06/23/06
see
javax.swing.table.TableRowSorter
see
javax.swing.table.DefaultTableModel
see
java.text.Collator
since
1.6

Fields Summary
private boolean
sortsOnUpdates
Whether or not we resort on TableModelEvent.UPDATEs.
private Row[]
viewToModel
View (JTable) -> model.
private int[]
modelToView
model -> view (JTable)
private Comparator[]
comparators
Comparators specified by column.
private boolean[]
isSortable
Whether or not the specified column is sortable, by column.
private SortKey[]
cachedSortKeys
Cached SortKeys for the current sort.
private Comparator[]
sortComparators
Cached comparators for the current sort
private RowFilter
filter
Developer supplied Filter.
private FilterEntry
filterEntry
Value passed to the filter. The same instance is passed to the filter for different rows.
private List
sortKeys
The sort keys.
private boolean[]
useToString
Whether or not to use getStringValueAt. This is indexed by column.
private boolean
sorted
Indicates the contents are sorted. This is used if getSortsOnUpdates is false and an update event is received.
private int
maxSortKeys
Maximum number of sort keys.
private ModelWrapper
modelWrapper
Provides access to the data we're sorting/filtering.
private int
modelRowCount
Size of the model. This is used to enforce error checking within the table changed notification methods (such as rowsInserted).
Constructors Summary
public DefaultRowSorter()
Creates an empty DefaultRowSorter.

        sortKeys = Collections.emptyList();
        maxSortKeys = 3;
    
Methods Summary
private voidallChanged()

        modelToView = null;
        viewToModel = null;
        comparators = null;
        isSortable = null;
        if (isUnsorted()) {
            // Keys are already empty, to force a resort we have to
            // call sort
            sort();
        } else {
            setSortKeys(null);
        }
    
public voidallRowsChanged()
{@inheritDoc}

        modelRowCount = getModelWrapper().getRowCount();
        sort();
    
private voidcacheSortKeys(java.util.List keys)
Caches the sort keys before a sort.

        int keySize = keys.size();
        sortComparators = new Comparator[keySize];
        for (int i = 0; i < keySize; i++) {
            sortComparators[i] = getComparator0(keys.get(i).getColumn());
        }
        cachedSortKeys = keys.toArray(new SortKey[keySize]);
    
private voidcheckAgainstModel(int firstRow, int endRow)

        if (firstRow > endRow || firstRow < 0 || endRow < 0 ||
                firstRow > modelRowCount) {
            throw new IndexOutOfBoundsException("Invalid range");
        }
    
private voidcheckColumn(int column)

        if (column < 0 || column >= getModelWrapper().getColumnCount()) {
            throw new IndexOutOfBoundsException(
                    "column beyond range of TableModel");
        }
    
private intcompare(int model1, int model2)

        int column;
        SortOrder sortOrder;
        Object v1, v2;
        int result;
            
        for (int counter = 0; counter < cachedSortKeys.length; counter++) {
            column = cachedSortKeys[counter].getColumn();
            sortOrder = cachedSortKeys[counter].getSortOrder();
            if (sortOrder == SortOrder.UNSORTED) {
                result = model1 - model2;
            } else {
                // v1 != null && v2 != null
                if (useToString[column]) {
                    v1 = getModelWrapper().getStringValueAt(model1, column);
                    v2 = getModelWrapper().getStringValueAt(model2, column);
                } else {
                    v1 = getModelWrapper().getValueAt(model1, column);
                    v2 = getModelWrapper().getValueAt(model2, column);
                }
                // Treat nulls as < then non-null
                if (v1 == null) {
                    if (v2 == null) {
                        result = 0;
                    } else {
                        result = -1;
                    }
                } else if (v2 == null) {
                    result = 1;
                } else {
                    result = sortComparators[counter].compare(v1, v2);
                }
                if (sortOrder == SortOrder.DESCENDING) {
                    result *= -1;
                }
            }
            if (result != 0) {
                return result;
            }
        }
        // If we get here, they're equal. Fallback to model order.
        return model1 - model2;
    
public intconvertRowIndexToModel(int index)
{@inheritDoc}

throws
IndexOutOfBoundsException {@inheritDoc}

        if (viewToModel == null) {
            if (index < 0 || index >= getModelWrapper().getRowCount()) {
                throw new IndexOutOfBoundsException("Invalid index");
            }
            return index;
        }
        return viewToModel[index].modelIndex;
    
public intconvertRowIndexToView(int index)
{@inheritDoc}

throws
IndexOutOfBoundsException {@inheritDoc}

        if (modelToView == null) {
            if (index < 0 || index >= getModelWrapper().getRowCount()) {
                throw new IndexOutOfBoundsException("Invalid index");
            }
            return index;
        }
        return modelToView[index];
    
private voidcreateModelToView(int rowCount)
Makes sure the modelToView array is of size rowCount.

        if (modelToView == null || modelToView.length != rowCount) {
            modelToView = new int[rowCount];
        }
    
private voidcreateViewToModel(int rowCount)
Resets the viewToModel array to be of size rowCount.

        int recreateFrom = 0;
        if (viewToModel != null) {
            recreateFrom = Math.min(rowCount, viewToModel.length);
            if (viewToModel.length != rowCount) {
                Row[] oldViewToModel = viewToModel;
                viewToModel = new Row[rowCount];
                System.arraycopy(oldViewToModel, 0, viewToModel,
                                 0, recreateFrom);
            }
        }
        else {
            viewToModel = new Row[rowCount];
        }
        int i;
        for (i = 0; i < recreateFrom; i++) {
            viewToModel[i].modelIndex = i;
        }
        for (i = recreateFrom; i < rowCount; i++) {
            viewToModel[i] = new Row(this, i);
        }
    
public java.util.ComparatorgetComparator(int column)
Returns the Comparator for the specified column. This will return null if a Comparator has not been specified for the column.

param
column the column to fetch the Comparator for, in terms of the underlying model
return
the Comparator for the specified column
throws
IndexOutOfBoundsException if column is outside the range of the underlying model

        checkColumn(column);
        if (comparators != null) {
            return comparators[column];
        }
        return null;
    
private java.util.ComparatorgetComparator0(int column)

        Comparator comparator = getComparator(column);
        if (comparator != null) {
            return comparator;
        }
        // This should be ok as useToString(column) should have returned
        // true in this case.
        return Collator.getInstance();
    
private javax.swing.RowFilter$EntrygetFilterEntry(int modelIndex)

        if (filterEntry == null) {
            filterEntry = new FilterEntry();
        }
        filterEntry.modelIndex = modelIndex;
        return filterEntry;
    
public intgetMaxSortKeys()
Returns the maximum number of sort keys.

return
the maximum number of sort keys

        return maxSortKeys;
    
public final MgetModel()
Returns the underlying model.

return
the underlying model

        return getModelWrapper().getModel();
    
public intgetModelRowCount()
{@inheritDoc}

        return getModelWrapper().getRowCount();
    
protected final javax.swing.DefaultRowSorter$ModelWrappergetModelWrapper()
Returns the model wrapper providing the data that is being sorted and filtered.

return
the model wrapper responsible for providing the data that gets sorted and filtered

        return modelWrapper;
    
public javax.swing.RowFiltergetRowFilter()
Returns the filter that determines which rows, if any, should be hidden from view.

return
the filter

        return filter;
    
public java.util.ListgetSortKeys()
Returns the current sort keys. This returns an unmodifiable {@code non-null List}. If you need to change the sort keys, make a copy of the returned {@code List}, mutate the copy and invoke {@code setSortKeys} with the new list.

return
the current sort order

        return sortKeys;
    
public booleangetSortsOnUpdates()
Returns true if a sort should happen when the underlying model is updated; otherwise, returns false.

return
whether or not to sort when the model is updated

        return sortsOnUpdates;
    
public intgetViewRowCount()
{@inheritDoc}

        if (viewToModel != null) {
            // When filtering this may differ from getModelWrapper().getRowCount()
            return viewToModel.length;
        }
        return getModelWrapper().getRowCount();
    
private int[]getViewToModelAsInts(javax.swing.DefaultRowSorter$Row[] viewToModel)

        if (viewToModel != null) {
            int[] viewToModelI = new int[viewToModel.length];
            for (int i = viewToModel.length - 1; i >= 0; i--) {
                viewToModelI[i] = viewToModel[i].modelIndex;
            }
            return viewToModelI;
        }
        return new int[0];
    
private booleaninclude(int row)
Returns true if the specified row should be included.

        RowFilter<? super M, ? super I> filter = getRowFilter();
        if (filter != null) {
            return filter.include(getFilterEntry(row));
        }
        // null filter, always include the row.
        return true;
    
private voidinitializeFilteredMapping()
Resets the viewToModel and modelToView mappings based on the current Filter.

        int rowCount = getModelWrapper().getRowCount();
        int i, j;
        int excludedCount = 0;

        // Update model -> view
        createModelToView(rowCount);
        for (i = 0; i < rowCount; i++) {
            if (include(i)) {
                modelToView[i] = i - excludedCount;
            }
            else {
                modelToView[i] = -1;
                excludedCount++;
            }
        }

        // Update view -> model
        createViewToModel(rowCount - excludedCount);
        for (i = 0, j = 0; i < rowCount; i++) {
            if (modelToView[i] != -1) {
                viewToModel[j++].modelIndex = i;
            }
        }
    
private voidinsertInOrder(java.util.List toAdd, javax.swing.DefaultRowSorter$Row[] current)
Insets new set of entries.

param
toAdd the Rows to add, sorted
param
current the array to insert the items into

        int last = 0;
        int index;
        int max = toAdd.size();
        for (int i = 0; i < max; i++) {
            index = Arrays.binarySearch(current, toAdd.get(i));
            if (index < 0) {
                index = -1 - index;
            }
            System.arraycopy(current, last,
                             viewToModel, last + i, index - last);
            viewToModel[index + i] = toAdd.get(i);
            last = index;
        }
        System.arraycopy(current, last, viewToModel, last + max,
                         current.length - last);
    
public booleanisSortable(int column)
Returns true if the specified column is sortable; otherwise, false.

param
column the column to check sorting for, in terms of the underlying model
return
true if the column is sortable
throws
IndexOutOfBoundsException if column is outside the range of the underlying model

        checkColumn(column);
        return (isSortable == null) ? true : isSortable[column];
    
private booleanisTransformed()
Whether not we are filtering/sorting.

        return (viewToModel != null);
    
private booleanisUnsorted()

        List<? extends SortKey> keys = getSortKeys();
        int keySize = keys.size();
        return (keySize == 0 || keys.get(0).getSortOrder() ==
                SortOrder.UNSORTED);
    
public voidmodelStructureChanged()
{@inheritDoc}

        allChanged();
        modelRowCount = getModelWrapper().getRowCount();
    
public voidrowsDeleted(int firstRow, int endRow)
{@inheritDoc}

throws
IndexOutOfBoundsException {@inheritDoc}

        checkAgainstModel(firstRow, endRow);
        if (firstRow >= modelRowCount || endRow >= modelRowCount) {
            throw new IndexOutOfBoundsException("Invalid range");
        }
        modelRowCount = getModelWrapper().getRowCount();
        if (shouldOptimizeChange(firstRow, endRow)) {
            rowsDeleted0(firstRow, endRow);
        }
    
private voidrowsDeleted0(int firstRow, int lastRow)

        int[] oldViewToModel = getViewToModelAsInts(viewToModel);
        int removedFromView = 0;
        int i;
        int viewIndex;

        // Figure out how many visible rows are going to be effected.
        for (i = firstRow; i <= lastRow; i++) {
            viewIndex = modelToView[i];
            if (viewIndex != -1) {
                removedFromView++;
                viewToModel[viewIndex] = null;
            }
        }

        // Update the model index of rows after the effected region
        int delta = lastRow - firstRow + 1;
        for (i = modelToView.length - 1; i > lastRow; i--) {
            viewIndex = modelToView[i];
            if (viewIndex != -1) {
                viewToModel[viewIndex].modelIndex -= delta;
            }
        }

        // Then patch up the viewToModel array
        if (removedFromView > 0) {
            Row[] newViewToModel = new Row[viewToModel.length -
                                           removedFromView];
            int newIndex = 0;
            int last = 0;
            for (i = 0; i < viewToModel.length; i++) {
                if (viewToModel[i] == null) {
                    System.arraycopy(viewToModel, last,
                                     newViewToModel, newIndex, i - last);
                    newIndex += (i - last);
                    last = i + 1;
                }
            }
            System.arraycopy(viewToModel, last,
                    newViewToModel, newIndex, viewToModel.length - last);
            viewToModel = newViewToModel;
        }

        // Update the modelToView mapping
        createModelToView(getModelWrapper().getRowCount());
        setModelToViewFromViewToModel(true);

        // And notify of change
        fireRowSorterChanged(oldViewToModel);
    
public voidrowsInserted(int firstRow, int endRow)
{@inheritDoc}

throws
IndexOutOfBoundsException {@inheritDoc}

        checkAgainstModel(firstRow, endRow);
        int newModelRowCount = getModelWrapper().getRowCount();
        if (endRow >= newModelRowCount) {
            throw new IndexOutOfBoundsException("Invalid range");
        }
        modelRowCount = newModelRowCount;
        if (shouldOptimizeChange(firstRow, endRow)) {
            rowsInserted0(firstRow, endRow);
        }
    
private voidrowsInserted0(int firstRow, int lastRow)

        int[] oldViewToModel = getViewToModelAsInts(viewToModel);
        int i;
        int delta = (lastRow - firstRow) + 1;
        List<Row> added = new ArrayList<Row>(delta);

        // Build the list of Rows to add into added
        for (i = firstRow; i <= lastRow; i++) {
            if (include(i)) {
                added.add(new Row(this, i));
            }
        }

        // Adjust the model index of rows after the effected region
        int viewIndex;
        for (i = modelToView.length - 1; i >= firstRow; i--) {
            viewIndex = modelToView[i];
            if (viewIndex != -1) {
                viewToModel[viewIndex].modelIndex += delta;
            }
        }

        // Insert newly added rows into viewToModel
        if (added.size() > 0) {
            Collections.sort(added);
            Row[] lastViewToModel = viewToModel;
            viewToModel = new Row[viewToModel.length + added.size()];
            insertInOrder(added, lastViewToModel);
        }

        // Update modelToView
        createModelToView(getModelWrapper().getRowCount());
        setModelToViewFromViewToModel(true);

        // Notify of change
        fireRowSorterChanged(oldViewToModel);
    
public voidrowsUpdated(int firstRow, int endRow)
{@inheritDoc}

throws
IndexOutOfBoundsException {@inheritDoc}

        checkAgainstModel(firstRow, endRow);
        if (firstRow >= modelRowCount || endRow >= modelRowCount) {
            throw new IndexOutOfBoundsException("Invalid range");
        }
        if (getSortsOnUpdates()) {
            if (shouldOptimizeChange(firstRow, endRow)) {
                rowsUpdated0(firstRow, endRow);
            }
        }
        else {
            sorted = false;
        }
    
public voidrowsUpdated(int firstRow, int endRow, int column)
{@inheritDoc}

throws
IndexOutOfBoundsException {@inheritDoc}

        checkColumn(column);
        rowsUpdated(firstRow, endRow);
    
private voidrowsUpdated0(int firstRow, int lastRow)

        int[] oldViewToModel = getViewToModelAsInts(viewToModel);
        int i, j;
        int delta = lastRow - firstRow + 1;
        int modelIndex;
        int last;
        int index;

        if (getRowFilter() == null) {
            // Sorting only:

            // Remove the effected rows
            Row[] updated = new Row[delta];
            for (j = 0, i = firstRow; i <= lastRow; i++, j++) {
                updated[j] = viewToModel[modelToView[i]];
            }

            // Sort the update rows
            Arrays.sort(updated);

            // Build the intermediary array: the array of
            // viewToModel without the effected rows.
            Row[] intermediary = new Row[viewToModel.length - delta];
            for (i = 0, j = 0; i < viewToModel.length; i++) {
                modelIndex = viewToModel[i].modelIndex;
                if (modelIndex < firstRow || modelIndex > lastRow) {
                    intermediary[j++] = viewToModel[i];
                }
            }

            // Build the new viewToModel
            insertInOrder(Arrays.asList(updated), intermediary);

            // Update modelToView
            setModelToViewFromViewToModel(false);
        }
        else {
            // Sorting & filtering.

            // Remove the effected rows, adding them to updated and setting
            // modelToView to -2 for any rows that were not filtered out
            List<Row> updated = new ArrayList<Row>(delta);
            int newlyVisible = 0;
            int newlyHidden = 0;
            int effected = 0;
            for (i = firstRow; i <= lastRow; i++) {
                if (modelToView[i] == -1) {
                    // This row was filtered out
                    if (include(i)) {
                        // No longer filtered
                        updated.add(new Row(this, i));
                        newlyVisible++;
                    }
                }
                else {
                    // This row was visible, make sure it should still be
                    // visible.
                    if (!include(i)) {
                        newlyHidden++;
                    }
                    else {
                        updated.add(viewToModel[modelToView[i]]);
                    }
                    modelToView[i] = -2;
                    effected++;
                }
            }

            // Sort the updated rows
            Collections.sort(updated);

            // Build the intermediary array: the array of
            // viewToModel without the updated rows.
            Row[] intermediary = new Row[viewToModel.length - effected];
            for (i = 0, j = 0; i < viewToModel.length; i++) {
                modelIndex = viewToModel[i].modelIndex;
                if (modelToView[modelIndex] != -2) {
                    intermediary[j++] = viewToModel[i];
                }
            }

            // Recreate viewToModel, if necessary
            if (newlyVisible != newlyHidden) {
                viewToModel = new Row[viewToModel.length + newlyVisible -
                                      newlyHidden];
            }

            // Rebuild the new viewToModel array
            insertInOrder(updated, intermediary);

            // Update modelToView
            setModelToViewFromViewToModel(true);
        }
        // And finally fire a sort event.
        fireRowSorterChanged(oldViewToModel);
    
public voidsetComparator(int column, java.util.Comparator comparator)
Sets the Comparator to use when sorting the specified column. This does not trigger a sort. If you want to sort after setting the comparator you need to explicitly invoke sort.

param
column the index of the column the Comparator is to be used for, in terms of the underlying model
param
comparator the Comparator to use
throws
IndexOutOfBoundsException if column is outside the range of the underlying model

        checkColumn(column);
        if (comparators == null) {
            comparators = new Comparator[getModelWrapper().getColumnCount()];
        }
        comparators[column] = comparator;
    
public voidsetMaxSortKeys(int max)
Sets the maximum number of sort keys. The number of sort keys determines how equal values are resolved when sorting. For example, assume a table row sorter is created and setMaxSortKeys(2) is invoked on it. The user clicks the header for column 1, causing the table rows to be sorted based on the items in column 1. Next, the user clicks the header for column 2, causing the table to be sorted based on the items in column 2; if any items in column 2 are equal, then those particular rows are ordered based on the items in column 1. In this case, we say that the rows are primarily sorted on column 2, and secondarily on column 1. If the user then clicks the header for column 3, then the items are primarily sorted on column 3 and secondarily sorted on column 2. Because the maximum number of sort keys has been set to 2 with setMaxSortKeys, column 1 no longer has an effect on the order.

The maximum number of sort keys is enforced by toggleSortOrder. You can specify more sort keys by invoking setSortKeys directly and they will all be honored. However if toggleSortOrder is subsequently invoked the maximum number of sort keys will be enforced. The default value is 3.

param
max the maximum number of sort keys
throws
IllegalArgumentException if max < 1

        if (max < 1) {
            throw new IllegalArgumentException("Invalid max");
        }
        maxSortKeys = max;
    
private voidsetModelToViewFromViewToModel(boolean unsetFirst)
Refreshes the modelToView mapping from that of viewToModel. If unsetFirst is true, all indices in modelToView are first set to -1.

        int i;
        if (unsetFirst) {
            for (i = modelToView.length - 1; i >= 0; i--) {
                modelToView[i] = -1;
            }
        }
        for (i = viewToModel.length - 1; i >= 0; i--) {
            modelToView[viewToModel[i].modelIndex] = i;
        }
    
protected final voidsetModelWrapper(javax.swing.DefaultRowSorter$ModelWrapper modelWrapper)
Sets the model wrapper providing the data that is being sorted and filtered.

param
modelWrapper the model wrapper responsible for providing the data that gets sorted and filtered
throws
IllegalArgumentException if {@code modelWrapper} is {@code null}

        if (modelWrapper == null) {
            throw new IllegalArgumentException(
                "modelWrapper most be non-null");
        }
        ModelWrapper<M,I> last = this.modelWrapper;
        this.modelWrapper = modelWrapper;
        if (last != null) {
            modelStructureChanged();
        } else {
            // If last is null, we're in the constructor. If we're in
            // the constructor we don't want to call to overridable methods.
            modelRowCount = getModelWrapper().getRowCount();
        }
    
public voidsetRowFilter(javax.swing.RowFilter filter)
Sets the filter that determines which rows, if any, should be hidden from the view. The filter is applied before sorting. A value of null indicates all values from the model should be included.

RowFilter's include method is passed an Entry that wraps the underlying model. The number of columns in the Entry corresponds to the number of columns in the ModelWrapper. The identifier comes from the ModelWrapper as well.

This method triggers a sort.

param
filter the filter used to determine what entries should be included

        this.filter = filter;
        sort();
    
public voidsetSortKeys(java.util.List sortKeys)
Sets the sort keys. This creates a copy of the supplied {@code List}; subsequent changes to the supplied {@code List} do not effect this {@code DefaultRowSorter}. If the sort keys have changed this triggers a sort.

param
sortKeys the new SortKeys; null is a shorthand for specifying an empty list, indicating that the view should be unsorted
throws
IllegalArgumentException if any of the values in sortKeys are null or have a column index outside the range of the model

        List<SortKey> old = this.sortKeys;
        if (sortKeys != null && sortKeys.size() > 0) {
            int max = getModelWrapper().getColumnCount();
            for (SortKey key : sortKeys) {
                if (key == null || key.getColumn() < 0 ||
                        key.getColumn() >= max) {
                    throw new IllegalArgumentException("Invalid SortKey");
                }
            }
            this.sortKeys = Collections.unmodifiableList(
                    new ArrayList<SortKey>(sortKeys));
        }
        else {
            this.sortKeys = Collections.emptyList();
        }
        if (!this.sortKeys.equals(old)) {
            fireSortOrderChanged();
            if (viewToModel == null) {
                // Currently unsorted, use sort so that internal fields
                // are correctly set.
                sort();
            } else {
                sortExistingData();
            }
        }
    
public voidsetSortable(int column, boolean sortable)
Sets whether or not the specified column is sortable. The specified value is only checked when toggleSortOrder is invoked. It is still possible to sort on a column that has been marked as unsortable by directly setting the sort keys. The default is true.

param
column the column to enable or disable sorting on, in terms of the underlying model
param
sortable whether or not the specified column is sortable
throws
IndexOutOfBoundsException if column is outside the range of the model
see
#toggleSortOrder
see
#setSortKeys

        checkColumn(column);
        if (isSortable == null) {
            isSortable = new boolean[getModelWrapper().getColumnCount()];
            for (int i = isSortable.length - 1; i >= 0; i--) {
                isSortable[i] = true;
            }
        }
        isSortable[column] = sortable;
    
public voidsetSortsOnUpdates(boolean sortsOnUpdates)
If true, specifies that a sort should happen when the underlying model is updated (rowsUpdated is invoked). For example, if this is true and the user edits an entry the location of that item in the view may change. The default is false.

param
sortsOnUpdates whether or not to sort on update events

        this.sortsOnUpdates = sortsOnUpdates;
    
private booleanshouldOptimizeChange(int firstRow, int lastRow)
Returns true if we should try and optimize the processing of the TableModelEvent. If this returns false, assume the event was dealt with and no further processing needs to happen.

        if (!isTransformed()) {
            // Not transformed, nothing to do.
            return false;
        }
        if (!sorted || (lastRow - firstRow) > viewToModel.length / 10) {
            // We either weren't sorted, or to much changed, sort it all
            sort();
            return false;
        }
        return true;
    
public voidsort()
Sorts and filters the rows in the view based on the sort keys of the columns currently being sorted and the filter, if any, associated with this sorter. An empty sortKeys list indicates that the view should unsorted, the same as the model.

see
#setRowFilter
see
#setSortKeys

        sorted = true;
        int[] lastViewToModel = getViewToModelAsInts(viewToModel);
        updateUseToString();
        if (isUnsorted()) {
            // Unsorted
            cachedSortKeys = new SortKey[0];
            if (getRowFilter() == null) {
                // No filter & unsorted
                if (viewToModel != null) {
                    // sorted -> unsorted
                    viewToModel = null;
                    modelToView = null;
                }
                else {
                    // unsorted -> unsorted
                    // No need to do anything.
                    return;
                }
            }
            else {
                // There is filter, reset mappings
                initializeFilteredMapping();
            }
        }
        else {
            cacheSortKeys(getSortKeys());

            if (getRowFilter() != null) {
                initializeFilteredMapping();
            }
            else {
                createModelToView(getModelWrapper().getRowCount());
                createViewToModel(getModelWrapper().getRowCount());
            }

            // sort them
            Arrays.sort(viewToModel);

            // Update the modelToView array
            setModelToViewFromViewToModel(false);
        }
        fireRowSorterChanged(lastViewToModel);
    
private voidsortExistingData()
Sorts the existing filtered data. This should only be used if the filter hasn't changed.

        int[] lastViewToModel = getViewToModelAsInts(viewToModel);

        updateUseToString();
        cacheSortKeys(getSortKeys());

        if (isUnsorted()) {
            if (getRowFilter() == null) {
                viewToModel = null;
                modelToView = null;
            } else {
                int included = 0;
                for (int i = 0; i < modelToView.length; i++) {
                    if (modelToView[i] != -1) {
                        viewToModel[included].modelIndex = i;
                        modelToView[i] = included++;
                    }
                }
            }
        } else {
            // sort the data
            Arrays.sort(viewToModel);

            // Update the modelToView array
            setModelToViewFromViewToModel(false);
        }
        fireRowSorterChanged(lastViewToModel);
    
private SortKeytoggle(SortKey key)

        if (key.getSortOrder() == SortOrder.ASCENDING) {
            return new SortKey(key.getColumn(), SortOrder.DESCENDING);
        }
        return new SortKey(key.getColumn(), SortOrder.ASCENDING);
    
public voidtoggleSortOrder(int column)
Reverses the sort order from ascending to descending (or descending to ascending) if the specified column is already the primary sorted column; otherwise, makes the specified column the primary sorted column, with an ascending sort order. If the specified column is not sortable, this method has no effect.

param
column index of the column to make the primary sorted column, in terms of the underlying model
throws
IndexOutOfBoundsException {@inheritDoc}
see
#setSortable(int,boolean)
see
#setMaxSortKeys(int)

        checkColumn(column);
        if (isSortable(column)) {
            List<SortKey> keys = new ArrayList<SortKey>(getSortKeys());
            SortKey sortKey;
            int sortIndex;
            for (sortIndex = keys.size() - 1; sortIndex >= 0; sortIndex--) {
                if (keys.get(sortIndex).getColumn() == column) {
                    break;
                }
            }
            if (sortIndex == -1) {
                // Key doesn't exist
                sortKey = new SortKey(column, 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(column, SortOrder.ASCENDING));
            }
            if (keys.size() > getMaxSortKeys()) {
                keys = keys.subList(0, getMaxSortKeys());
            }
            setSortKeys(keys);
        }
    
private voidupdateUseToString()
Updates the useToString mapping before a sort.

        int i = getModelWrapper().getColumnCount();
        if (useToString == null || useToString.length != i) {
            useToString = new boolean[i];
        }
        for (--i; i >= 0; i--) {
            useToString[i] = useToString(i);
        }
    
protected booleanuseToString(int column)
Returns whether or not to convert the value to a string before doing comparisons when sorting. If true ModelWrapper.getStringValueAt will be used, otherwise ModelWrapper.getValueAt will be used. It is up to subclasses, such as TableRowSorter, to honor this value in their ModelWrapper implementation.

param
column the index of the column to test, in terms of the underlying model
throws
IndexOutOfBoundsException if column is not valid

        return (getComparator(column) == null);