/* * Copyright (c) 2005, 2006, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package javax.swing; import javax.swing.SortOrder; import javax.swing.event.*; import java.util.*; /** * <code>RowSorter</code> provides the basis for sorting and filtering. * Beyond creating and installing a <code>RowSorter</code>, you very rarely * need to interact with one directly. Refer to * {@link javax.swing.table.TableRowSorter TableRowSorter} for a concrete * implementation of <code>RowSorter</code> for <code>JTable</code>. * <p> * <code>RowSorter</code>'s primary role is to provide a mapping between * two coordinate systems: that of the view (for example a * <code>JTable</code>) and that of the underlying data source, typically a * model. * <p> * The view invokes the following methods on the <code>RowSorter</code>: * <ul> * <li><code>toggleSortOrder</code> — The view invokes this when the * appropriate user gesture has occurred to trigger a sort. For example, * the user clicked a column header in a table. * <li>One of the model change methods — The view invokes a model * change method when the underlying model * has changed. There may be order dependencies in how the events are * delivered, so a <code>RowSorter</code> should not update its mapping * until one of these methods is invoked. * </ul> * Because the view makes extensive use of the * <code>convertRowIndexToModel</code>, * <code>convertRowIndexToView</code> and <code>getViewRowCount</code> methods, * these methods need to be fast. * <p> * <code>RowSorter</code> provides notification of changes by way of * <code>RowSorterListener</code>. Two types of notification are sent: * <ul> * <li><code>RowSorterEvent.Type.SORT_ORDER_CHANGED</code> — notifies * listeners that the sort order has changed. This is typically followed * by a notification that the sort has changed. * <li><code>RowSorterEvent.Type.SORTED</code> — notifies listeners that * the mapping maintained by the <code>RowSorter</code> has changed in * some way. * </ul> * <code>RowSorter</code> implementations typically don't have a one-to-one * mapping with the underlying model, but they can. * For example, if a database does the sorting, * <code>toggleSortOrder</code> might call through to the database * (on a background thread), and override the mapping methods to return the * argument that is passed in. * <p> * Concrete implementations of <code>RowSorter</code> * need to reference a model such as <code>TableModel</code> or * <code>ListModel</code>. The view classes, such as * <code>JTable</code> and <code>JList</code>, will also have a * reference to the model. To avoid ordering dependencies, * <code>RowSorter</code> implementations should not install a * listener on the model. Instead the view class will call into the * <code>RowSorter</code> when the model changes. For * example, if a row is updated in a <code>TableModel</code> * <code>JTable</code> invokes <code>rowsUpdated</code>. * When the model changes, the view may call into any of the following methods: * <code>modelStructureChanged</code>, <code>allRowsChanged</code>, * <code>rowsInserted</code>, <code>rowsDeleted</code> and * <code>rowsUpdated</code>. * * @param <M> the type of the underlying model * @see javax.swing.table.TableRowSorter * @since 1.6 */ public abstract class RowSorter<M> { private EventListenerList listenerList = new EventListenerList(); /** * Creates a <code>RowSorter</code>. */ public RowSorter() { } /** * Returns the underlying model. * * @return the underlying model */ public abstract M getModel(); /** * Reverses the sort order of the specified column. It is up to * subclasses to provide the exact behavior when invoked. Typically * this will reverse 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. * <p> * If this results in changing the sort order and sorting, the * appropriate <code>RowSorterListener</code> notification will be * sent. * * @param column the column to toggle the sort ordering of, in * terms of the underlying model * @throws IndexOutOfBoundsException if column is outside the range of * the underlying model */ public abstract void toggleSortOrder(int column); /** * Returns the location of <code>index</code> in terms of the * underlying model. That is, for the row <code>index</code> in * the coordinates of the view this returns the row index in terms * of the underlying model. * * @param index the row index in terms of the underlying view * @return row index in terms of the view * @throws IndexOutOfBoundsException if <code>index</code> is outside the * range of the view */ public abstract int convertRowIndexToModel(int index); /** * Returns the location of <code>index</code> in terms of the * view. That is, for the row <code>index</code> in the * coordinates of the underlying model this returns the row index * in terms of the view. * * @param index the row index in terms of the underlying model * @return row index in terms of the view, or -1 if index has been * filtered out of the view * @throws IndexOutOfBoundsException if <code>index</code> is outside * the range of the model */ public abstract int convertRowIndexToView(int index); /** * Sets the current sort keys. * * @param keys the new <code>SortKeys</code>; <code>null</code> * is a shorthand for specifying an empty list, * indicating that the view should be unsorted */ public abstract void setSortKeys(List<? extends SortKey> keys); /** * Returns the current sort keys. This must return a {@code * non-null List} and may return an unmodifiable {@code 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 */ public abstract List<? extends SortKey> getSortKeys(); /** * Returns the number of rows in the view. If the contents have * been filtered this might differ from the row count of the * underlying model. * * @return number of rows in the view * @see #getModelRowCount */ public abstract int getViewRowCount(); /** * Returns the number of rows in the underlying model. * * @return number of rows in the underlying model * @see #getViewRowCount */ public abstract int getModelRowCount(); /** * Invoked when the underlying model structure has completely * changed. For example, if the number of columns in a * <code>TableModel</code> changed, this method would be invoked. * <p> * You normally do not call this method. This method is public * to allow view classes to call it. */ public abstract void modelStructureChanged(); /** * Invoked when the contents of the underlying model have * completely changed. The structure of the table is the same, * only the contents have changed. This is typically sent when it * is too expensive to characterize the change in terms of the * other methods. * <p> * You normally do not call this method. This method is public * to allow view classes to call it. */ public abstract void allRowsChanged(); /** * Invoked when rows have been inserted into the underlying model * in the specified range (inclusive). * <p> * The arguments give the indices of the effected range. * The first argument is in terms of the model before the change, and * must be less than or equal to the size of the model before the change. * The second argument is in terms of the model after the change and must * be less than the size of the model after the change. For example, * if you have a 5-row model and add 3 items to the end of the model * the indices are 5, 7. * <p> * You normally do not call this method. This method is public * to allow view classes to call it. * * @param firstRow the first row * @param endRow the last row * @throws IndexOutOfBoundsException if either argument is invalid, or * <code>firstRow</code> > <code>endRow</code> */ public abstract void rowsInserted(int firstRow, int endRow); /** * Invoked when rows have been deleted from the underlying model * in the specified range (inclusive). * <p> * The arguments give the indices of the effected range and * are in terms of the model <b>before</b> the change. * For example, if you have a 5-row model and delete 3 items from the end * of the model the indices are 2, 4. * <p> * You normally do not call this method. This method is public * to allow view classes to call it. * * @param firstRow the first row * @param endRow the last row * @throws IndexOutOfBoundsException if either argument is outside * the range of the model before the change, or * <code>firstRow</code> > <code>endRow</code> */ public abstract void rowsDeleted(int firstRow, int endRow); /** * Invoked when rows have been changed in the underlying model * between the specified range (inclusive). * <p> * You normally do not call this method. This method is public * to allow view classes to call it. * * @param firstRow the first row, in terms of the underlying model * @param endRow the last row, in terms of the underlying model * @throws IndexOutOfBoundsException if either argument is outside * the range of the underlying model, or * <code>firstRow</code> > <code>endRow</code> */ public abstract void rowsUpdated(int firstRow, int endRow); /** * Invoked when the column in the rows have been updated in * the underlying model between the specified range. * <p> * You normally do not call this method. This method is public * to allow view classes to call it. * * @param firstRow the first row, in terms of the underlying model * @param endRow the last row, in terms of the underlying model * @param column the column that has changed, in terms of the underlying * model * @throws IndexOutOfBoundsException if either argument is outside * the range of the underlying model after the change, * <code>firstRow</code> > <code>endRow</code>, or * <code>column</code> is outside the range of the underlying * model */ public abstract void rowsUpdated(int firstRow, int endRow, int column); /** * Adds a <code>RowSorterListener</code> to receive notification * about this <code>RowSorter</code>. If the same * listener is added more than once it will receive multiple * notifications. If <code>l</code> is <code>null</code> nothing * is done. * * @param l the <code>RowSorterListener</code> */ public void addRowSorterListener(RowSorterListener l) { listenerList.add(RowSorterListener.class, l); } /** * Removes a <code>RowSorterListener</code>. If * <code>l</code> is <code>null</code> nothing is done. * * @param l the <code>RowSorterListener</code> */ public void removeRowSorterListener(RowSorterListener l) { listenerList.remove(RowSorterListener.class, l); } /** * Notifies listener that the sort order has changed. */ protected void fireSortOrderChanged() { fireRowSorterChanged(new RowSorterEvent(this)); } /** * Notifies listener that the mapping has changed. * * @param lastRowIndexToModel the mapping from model indices to * view indices prior to the sort, may be <code>null</code> */ protected void fireRowSorterChanged(int[] lastRowIndexToModel) { fireRowSorterChanged(new RowSorterEvent(this, RowSorterEvent.Type.SORTED, lastRowIndexToModel)); } void fireRowSorterChanged(RowSorterEvent event) { Object[] listeners = listenerList.getListenerList(); for (int i = listeners.length - 2; i >= 0; i -= 2) { if (listeners[i] == RowSorterListener.class) { ((RowSorterListener)listeners[i + 1]). sorterChanged(event); } } } /** * SortKey describes the sort order for a particular column. The * column index is in terms of the underlying model, which may differ * from that of the view. * * @since 1.6 */ public static class SortKey { private int column; private SortOrder sortOrder; /** * Creates a <code>SortKey</code> for the specified column with * the specified sort order. * * @param column index of the column, in terms of the model * @param sortOrder the sorter order * @throws IllegalArgumentException if <code>sortOrder</code> is * <code>null</code> */ public SortKey(int column, SortOrder sortOrder) { if (sortOrder == null) { throw new IllegalArgumentException( "sort order must be non-null"); } this.column = column; this.sortOrder = sortOrder; } /** * Returns the index of the column. * * @return index of column */ public final int getColumn() { return column; } /** * Returns the sort order of the column. * * @return the sort order of the column */ public final SortOrder getSortOrder() { return sortOrder; } /** * Returns the hash code for this <code>SortKey</code>. * * @return hash code */ public int hashCode() { int result = 17; result = 37 * result + column; result = 37 * result + sortOrder.hashCode(); return result; } /** * Returns true if this object equals the specified object. * If the specified object is a <code>SortKey</code> and * references the same column and sort order, the two objects * are equal. * * @param o the object to compare to * @return true if <code>o</code> is equal to this <code>SortKey</code> */ public boolean equals(Object o) { if (o == this) { return true; } if (o instanceof SortKey) { return (((SortKey)o).column == column && ((SortKey)o).sortOrder == sortOrder); } return false; } } }