1 /*
   2  * Copyright (c) 2005, 2006, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package javax.swing;
  26 
  27 import javax.swing.SortOrder;
  28 import javax.swing.event.*;
  29 import java.util.*;
  30 
  31 /**
  32  * {@code RowSorter} provides the basis for sorting and filtering.
  33  * Beyond creating and installing a {@code RowSorter}, you very rarely
  34  * need to interact with one directly.  Refer to
  35  * {@link javax.swing.table.TableRowSorter TableRowSorter} for a concrete
  36  * implementation of {@code RowSorter} for {@code JTable}.
  37  * <p>
  38  * {@code RowSorter}'s primary role is to provide a mapping between
  39  * two coordinate systems: that of the view (for example a
  40  * {@code JTable}) and that of the underlying data source, typically a
  41  * model.
  42  * <p>
  43  * The view invokes the following methods on the {@code RowSorter}:
  44  * <ul>
  45  * <li>{@code toggleSortOrder} — The view invokes this when the
  46  *     appropriate user gesture has occurred to trigger a sort.  For example,
  47  *     the user clicked a column header in a table.
  48  * <li>One of the model change methods — The view invokes a model
  49  *     change method when the underlying model
  50  *     has changed.  There may be order dependencies in how the events are
  51  *     delivered, so a {@code RowSorter} should not update its mapping
  52  *     until one of these methods is invoked.
  53  * </ul>
  54  * Because the view makes extensive use of  the
  55  * {@code convertRowIndexToModel},
  56  * {@code convertRowIndexToView} and {@code getViewRowCount} methods,
  57  * these methods need to be fast.
  58  * <p>
  59  * {@code RowSorter} provides notification of changes by way of
  60  * {@code RowSorterListener}.  Two types of notification are sent:
  61  * <ul>
  62  * <li>{@code RowSorterEvent.Type.SORT_ORDER_CHANGED} — notifies
  63  *     listeners that the sort order has changed.  This is typically followed
  64  *     by a notification that the sort has changed.
  65  * <li>{@code RowSorterEvent.Type.SORTED} — notifies listeners that
  66  *     the mapping maintained by the {@code RowSorter} has changed in
  67  *     some way.
  68  * </ul>
  69  * {@code RowSorter} implementations typically don't have a one-to-one
  70  * mapping with the underlying model, but they can.
  71  * For example, if a database does the sorting,
  72  * {@code toggleSortOrder} might call through to the database
  73  * (on a background thread), and override the mapping methods to return the
  74  * argument that is passed in.
  75  * <p>
  76  * Concrete implementations of {@code RowSorter}
  77  * need to reference a model such as {@code TableModel} or
  78  * {@code ListModel}.  The view classes, such as
  79  * {@code JTable} and {@code JList}, will also have a
  80  * reference to the model.  To avoid ordering dependencies,
  81  * {@code RowSorter} implementations should not install a
  82  * listener on the model.  Instead the view class will call into the
  83  * {@code RowSorter} when the model changes.  For
  84  * example, if a row is updated in a {@code TableModel}
  85  * {@code JTable} invokes {@code rowsUpdated}.
  86  * When the model changes, the view may call into any of the following methods:
  87  * {@code modelStructureChanged}, {@code allRowsChanged},
  88  * {@code rowsInserted}, {@code rowsDeleted} and
  89  * {@code rowsUpdated}.
  90  *
  91  * @param <M> the type of the underlying model
  92  * @see javax.swing.table.TableRowSorter
  93  * @since 1.6
  94  */
  95 public abstract class RowSorter<M> {
  96     private EventListenerList listenerList = new EventListenerList();
  97 
  98     /**
  99      * Creates a {@code RowSorter}.
 100      */
 101     public RowSorter() {
 102     }
 103 
 104     /**
 105      * Returns the underlying model.
 106      *
 107      * @return the underlying model
 108      */
 109     public abstract M getModel();
 110 
 111     /**
 112      * Reverses the sort order of the specified column.  It is up to
 113      * subclasses to provide the exact behavior when invoked.  Typically
 114      * this will reverse the sort order from ascending to descending (or
 115      * descending to ascending) if the specified column is already the
 116      * primary sorted column; otherwise, makes the specified column
 117      * the primary sorted column, with an ascending sort order.  If
 118      * the specified column is not sortable, this method has no
 119      * effect.
 120      * <p>
 121      * If this results in changing the sort order and sorting, the
 122      * appropriate {@code RowSorterListener} notification will be
 123      * sent.
 124      *
 125      * @param column the column to toggle the sort ordering of, in
 126      *        terms of the underlying model
 127      * @throws IndexOutOfBoundsException if column is outside the range of
 128      *         the underlying model
 129      */
 130     public abstract void toggleSortOrder(int column);
 131 
 132     /**
 133      * Returns the location of {@code index} in terms of the
 134      * underlying model.  That is, for the row {@code index} in
 135      * the coordinates of the view this returns the row index in terms
 136      * of the underlying model.
 137      *
 138      * @param index the row index in terms of the underlying view
 139      * @return row index in terms of the view
 140      * @throws IndexOutOfBoundsException if {@code index} is outside the
 141      *         range of the view
 142      */
 143     public abstract int convertRowIndexToModel(int index);
 144 
 145     /**
 146      * Returns the location of {@code index} in terms of the
 147      * view.  That is, for the row {@code index} in the
 148      * coordinates of the underlying model this returns the row index
 149      * in terms of the view.
 150      *
 151      * @param index the row index in terms of the underlying model
 152      * @return row index in terms of the view, or -1 if index has been
 153      *         filtered out of the view
 154      * @throws IndexOutOfBoundsException if {@code index} is outside
 155      *         the range of the model
 156      */
 157     public abstract int convertRowIndexToView(int index);
 158 
 159     /**
 160      * Sets the current sort keys.
 161      *
 162      * @param keys the new {@code SortKeys}; {@code null}
 163      *        is a shorthand for specifying an empty list,
 164      *        indicating that the view should be unsorted
 165      */
 166     public abstract void setSortKeys(List<? extends SortKey> keys);
 167 
 168     /**
 169      * Returns the current sort keys.  This must return a {@code
 170      * non-null List} and may return an unmodifiable {@code List}. If
 171      * you need to change the sort keys, make a copy of the returned
 172      * {@code List}, mutate the copy and invoke {@code setSortKeys}
 173      * with the new list.
 174      *
 175      * @return the current sort order
 176      */
 177     public abstract List<? extends SortKey> getSortKeys();
 178 
 179     /**
 180      * Returns the number of rows in the view.  If the contents have
 181      * been filtered this might differ from the row count of the
 182      * underlying model.
 183      *
 184      * @return number of rows in the view
 185      * @see #getModelRowCount
 186      */
 187     public abstract int getViewRowCount();
 188 
 189     /**
 190      * Returns the number of rows in the underlying model.
 191      *
 192      * @return number of rows in the underlying model
 193      * @see #getViewRowCount
 194      */
 195     public abstract int getModelRowCount();
 196 
 197     /**
 198      * Invoked when the underlying model structure has completely
 199      * changed.  For example, if the number of columns in a
 200      * {@code TableModel} changed, this method would be invoked.
 201      * <p>
 202      * You normally do not call this method.  This method is public
 203      * to allow view classes to call it.
 204      */
 205     public abstract void modelStructureChanged();
 206 
 207     /**
 208      * Invoked when the contents of the underlying model have
 209      * completely changed. The structure of the table is the same,
 210      * only the contents have changed. This is typically sent when it
 211      * is too expensive to characterize the change in terms of the
 212      * other methods.
 213      * <p>
 214      * You normally do not call this method.  This method is public
 215      * to allow view classes to call it.
 216      */
 217     public abstract void allRowsChanged();
 218 
 219     /**
 220      * Invoked when rows have been inserted into the underlying model
 221      * in the specified range (inclusive).
 222      * <p>
 223      * The arguments give the indices of the effected range.
 224      * The first argument is in terms of the model before the change, and
 225      * must be less than or equal to the size of the model before the change.
 226      * The second argument is in terms of the model after the change and must
 227      * be less than the size of the model after the change. For example,
 228      * if you have a 5-row model and add 3 items to the end of the model
 229      * the indices are 5, 7.
 230      * <p>
 231      * You normally do not call this method.  This method is public
 232      * to allow view classes to call it.
 233      *
 234      * @param firstRow the first row
 235      * @param endRow the last row
 236      * @throws IndexOutOfBoundsException if either argument is invalid, or
 237      *         {@code firstRow > endRow}
 238      */
 239     public abstract void rowsInserted(int firstRow, int endRow);
 240 
 241     /**
 242      * Invoked when rows have been deleted from the underlying model
 243      * in the specified range (inclusive).
 244      * <p>
 245      * The arguments give the indices of the effected range and
 246      * are in terms of the model <b>before</b> the change.
 247      * For example, if you have a 5-row model and delete 3 items from the end
 248      * of the model the indices are 2, 4.
 249      * <p>
 250      * You normally do not call this method.  This method is public
 251      * to allow view classes to call it.
 252      *
 253      * @param firstRow the first row
 254      * @param endRow the last row
 255      * @throws IndexOutOfBoundsException if either argument is outside
 256      *         the range of the model before the change, or
 257      *         {@code firstRow > endRow}
 258      */
 259     public abstract void rowsDeleted(int firstRow, int endRow);
 260 
 261     /**
 262      * Invoked when rows have been changed in the underlying model
 263      * between the specified range (inclusive).
 264      * <p>
 265      * You normally do not call this method.  This method is public
 266      * to allow view classes to call it.
 267      *
 268      * @param firstRow the first row, in terms of the underlying model
 269      * @param endRow the last row, in terms of the underlying model
 270      * @throws IndexOutOfBoundsException if either argument is outside
 271      *         the range of the underlying model, or
 272      *         {@code firstRow > endRow}
 273      */
 274     public abstract void rowsUpdated(int firstRow, int endRow);
 275 
 276     /**
 277      * Invoked when the column in the rows have been updated in
 278      * the underlying model between the specified range.
 279      * <p>
 280      * You normally do not call this method.  This method is public
 281      * to allow view classes to call it.
 282      *
 283      * @param firstRow the first row, in terms of the underlying model
 284      * @param endRow the last row, in terms of the underlying model
 285      * @param column the column that has changed, in terms of the underlying
 286      *        model
 287      * @throws IndexOutOfBoundsException if either argument is outside
 288      *         the range of the underlying model after the change,
 289      *         {@code firstRow > endRow}, or
 290      *         {@code column} is outside the range of the underlying
 291      *          model
 292      */
 293     public abstract void rowsUpdated(int firstRow, int endRow, int column);
 294 
 295     /**
 296      * Adds a {@code RowSorterListener} to receive notification
 297      * about this {@code RowSorter}.  If the same
 298      * listener is added more than once it will receive multiple
 299      * notifications.  If {@code l} is {@code null} nothing
 300      * is done.
 301      *
 302      * @param l the {@code RowSorterListener}
 303      */
 304     public void addRowSorterListener(RowSorterListener l) {
 305         listenerList.add(RowSorterListener.class, l);
 306     }
 307 
 308     /**
 309      * Removes a {@code RowSorterListener}.  If
 310      * {@code l} is {@code null} nothing is done.
 311      *
 312      * @param l the {@code RowSorterListener}
 313      */
 314     public void removeRowSorterListener(RowSorterListener l) {
 315         listenerList.remove(RowSorterListener.class, l);
 316     }
 317 
 318     /**
 319      * Notifies listener that the sort order has changed.
 320      */
 321     protected void fireSortOrderChanged() {
 322         fireRowSorterChanged(new RowSorterEvent(this));
 323     }
 324 
 325     /**
 326      * Notifies listener that the mapping has changed.
 327      *
 328      * @param lastRowIndexToModel the mapping from model indices to
 329      *        view indices prior to the sort, may be {@code null}
 330      */
 331     protected void fireRowSorterChanged(int[] lastRowIndexToModel) {
 332         fireRowSorterChanged(new RowSorterEvent(this,
 333                 RowSorterEvent.Type.SORTED, lastRowIndexToModel));
 334     }
 335 
 336     void fireRowSorterChanged(RowSorterEvent event) {
 337         Object[] listeners = listenerList.getListenerList();
 338         for (int i = listeners.length - 2; i >= 0; i -= 2) {
 339             if (listeners[i] == RowSorterListener.class) {
 340                 ((RowSorterListener)listeners[i + 1]).
 341                         sorterChanged(event);
 342             }
 343         }
 344     }
 345 
 346     /**
 347      * SortKey describes the sort order for a particular column.  The
 348      * column index is in terms of the underlying model, which may differ
 349      * from that of the view.
 350      *
 351      * @since 1.6
 352      */
 353     public static class SortKey {
 354         private int column;
 355         private SortOrder sortOrder;
 356 
 357         /**
 358          * Creates a {@code SortKey} for the specified column with
 359          * the specified sort order.
 360          *
 361          * @param column index of the column, in terms of the model
 362          * @param sortOrder the sorter order
 363          * @throws IllegalArgumentException if {@code sortOrder} is
 364          *         {@code null}
 365          */
 366         public SortKey(int column, SortOrder sortOrder) {
 367             if (sortOrder == null) {
 368                 throw new IllegalArgumentException(
 369                         "sort order must be non-null");
 370             }
 371             this.column = column;
 372             this.sortOrder = sortOrder;
 373         }
 374 
 375         /**
 376          * Returns the index of the column.
 377          *
 378          * @return index of column
 379          */
 380         public final int getColumn() {
 381             return column;
 382         }
 383 
 384         /**
 385          * Returns the sort order of the column.
 386          *
 387          * @return the sort order of the column
 388          */
 389         public final SortOrder getSortOrder() {
 390             return sortOrder;
 391         }
 392 
 393         /**
 394          * Returns the hash code for this {@code SortKey}.
 395          *
 396          * @return hash code
 397          */
 398         public int hashCode() {
 399             int result = 17;
 400             result = 37 * result + column;
 401             result = 37 * result + sortOrder.hashCode();
 402             return result;
 403         }
 404 
 405         /**
 406          * Returns true if this object equals the specified object.
 407          * If the specified object is a {@code SortKey} and
 408          * references the same column and sort order, the two objects
 409          * are equal.
 410          *
 411          * @param o the object to compare to
 412          * @return true if {@code o} is equal to this {@code SortKey}
 413          */
 414         public boolean equals(Object o) {
 415             if (o == this) {
 416                 return true;
 417             }
 418             if (o instanceof SortKey) {
 419                 return (((SortKey)o).column == column &&
 420                         ((SortKey)o).sortOrder == sortOrder);
 421             }
 422             return false;
 423         }
 424     }
 425 }
--- EOF ---