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</code> provides the basis for sorting and filtering. 33 * Beyond creating and installing a <code>RowSorter</code>, 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</code> for <code>JTable</code>. 37 * <p> 38 * <code>RowSorter</code>'s primary role is to provide a mapping between 39 * two coordinate systems: that of the view (for example a 40 * <code>JTable</code>) and that of the underlying data source, typically a 41 * model. 42 * <p> 43 * The view invokes the following methods on the <code>RowSorter</code>: 44 * <ul> 45 * <li><code>toggleSortOrder</code> — 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</code> 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</code>, 56 * <code>convertRowIndexToView</code> and <code>getViewRowCount</code> methods, 57 * these methods need to be fast. 58 * <p> 59 * <code>RowSorter</code> provides notification of changes by way of 60 * <code>RowSorterListener</code>. Two types of notification are sent: 61 * <ul> 62 * <li><code>RowSorterEvent.Type.SORT_ORDER_CHANGED</code> — 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</code> — notifies listeners that 66 * the mapping maintained by the <code>RowSorter</code> has changed in 67 * some way. 68 * </ul> 69 * <code>RowSorter</code> 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</code> 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</code> 77 * need to reference a model such as <code>TableModel</code> or 78 * <code>ListModel</code>. The view classes, such as 79 * <code>JTable</code> and <code>JList</code>, will also have a 80 * reference to the model. To avoid ordering dependencies, 81 * <code>RowSorter</code> implementations should not install a 82 * listener on the model. Instead the view class will call into the 83 * <code>RowSorter</code> when the model changes. For 84 * example, if a row is updated in a <code>TableModel</code> 85 * <code>JTable</code> invokes <code>rowsUpdated</code>. 86 * When the model changes, the view may call into any of the following methods: 87 * <code>modelStructureChanged</code>, <code>allRowsChanged</code>, 88 * <code>rowsInserted</code>, <code>rowsDeleted</code> and 89 * <code>rowsUpdated</code>. 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</code>. 100 */ RowSorter()101 public RowSorter() { 102 } 103 104 /** 105 * Returns the underlying model. 106 * 107 * @return the underlying model 108 */ getModel()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</code> 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 */ toggleSortOrder(int column)130 public abstract void toggleSortOrder(int column); 131 132 /** 133 * Returns the location of <code>index</code> in terms of the 134 * underlying model. That is, for the row <code>index</code> 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</code> is outside the 141 * range of the view 142 */ convertRowIndexToModel(int index)143 public abstract int convertRowIndexToModel(int index); 144 145 /** 146 * Returns the location of <code>index</code> in terms of the 147 * view. That is, for the row <code>index</code> 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</code> is outside 155 * the range of the model 156 */ convertRowIndexToView(int index)157 public abstract int convertRowIndexToView(int index); 158 159 /** 160 * Sets the current sort keys. 161 * 162 * @param keys the new <code>SortKeys</code>; <code>null</code> 163 * is a shorthand for specifying an empty list, 164 * indicating that the view should be unsorted 165 */ setSortKeys(List<? extends SortKey> keys)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 */ getSortKeys()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 */ getViewRowCount()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 */ getModelRowCount()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</code> 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 */ modelStructureChanged()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 */ allRowsChanged()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</code> > <code>endRow</code> 238 */ rowsInserted(int firstRow, int endRow)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</code> > <code>endRow</code> 258 */ rowsDeleted(int firstRow, int endRow)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</code> > <code>endRow</code> 273 */ rowsUpdated(int firstRow, int endRow)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</code> > <code>endRow</code>, or 290 * <code>column</code> is outside the range of the underlying 291 * model 292 */ rowsUpdated(int firstRow, int endRow, int column)293 public abstract void rowsUpdated(int firstRow, int endRow, int column); 294 295 /** 296 * Adds a <code>RowSorterListener</code> to receive notification 297 * about this <code>RowSorter</code>. If the same 298 * listener is added more than once it will receive multiple 299 * notifications. If <code>l</code> is <code>null</code> nothing 300 * is done. 301 * 302 * @param l the <code>RowSorterListener</code> 303 */ addRowSorterListener(RowSorterListener l)304 public void addRowSorterListener(RowSorterListener l) { 305 listenerList.add(RowSorterListener.class, l); 306 } 307 308 /** 309 * Removes a <code>RowSorterListener</code>. If 310 * <code>l</code> is <code>null</code> nothing is done. 311 * 312 * @param l the <code>RowSorterListener</code> 313 */ removeRowSorterListener(RowSorterListener l)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 */ fireSortOrderChanged()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</code> 330 */ fireRowSorterChanged(int[] lastRowIndexToModel)331 protected void fireRowSorterChanged(int[] lastRowIndexToModel) { 332 fireRowSorterChanged(new RowSorterEvent(this, 333 RowSorterEvent.Type.SORTED, lastRowIndexToModel)); 334 } 335 fireRowSorterChanged(RowSorterEvent event)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</code> 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</code> is 364 * <code>null</code> 365 */ SortKey(int column, SortOrder sortOrder)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 */ getColumn()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 */ getSortOrder()389 public final SortOrder getSortOrder() { 390 return sortOrder; 391 } 392 393 /** 394 * Returns the hash code for this <code>SortKey</code>. 395 * 396 * @return hash code 397 */ hashCode()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</code> 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</code> is equal to this <code>SortKey</code> 413 */ equals(Object o)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 } 426