1 /* 2 * Copyright (c) 2005, 2013, 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 java.text.Collator; 28 import java.util.ArrayList; 29 import java.util.Arrays; 30 import java.util.Collections; 31 import java.util.Comparator; 32 import java.util.List; 33 import javax.swing.SortOrder; 34 35 /** 36 * An implementation of <code>RowSorter</code> that provides sorting and 37 * filtering around a grid-based data model. 38 * Beyond creating and installing a <code>RowSorter</code>, you very rarely 39 * need to interact with one directly. Refer to 40 * {@link javax.swing.table.TableRowSorter TableRowSorter} for a concrete 41 * implementation of <code>RowSorter</code> for <code>JTable</code>. 42 * <p> 43 * Sorting is done based on the current <code>SortKey</code>s, in order. 44 * If two objects are equal (the <code>Comparator</code> for the 45 * column returns 0) the next <code>SortKey</code> is used. If no 46 * <code>SortKey</code>s remain or the order is <code>UNSORTED</code>, then 47 * the order of the rows in the model is used. 48 * <p> 49 * Sorting of each column is done by way of a <code>Comparator</code> 50 * that you can specify using the <code>setComparator</code> method. 51 * If a <code>Comparator</code> has not been specified, the 52 * <code>Comparator</code> returned by 53 * <code>Collator.getInstance()</code> is used on the results of 54 * calling <code>toString</code> on the underlying objects. The 55 * <code>Comparator</code> is never passed <code>null</code>. A 56 * <code>null</code> value is treated as occurring before a 57 * non-<code>null</code> value, and two <code>null</code> values are 58 * considered equal. 59 * <p> 60 * If you specify a <code>Comparator</code> that casts its argument to 61 * a type other than that provided by the model, a 62 * <code>ClassCastException</code> will be thrown when the data is sorted. 63 * <p> 64 * In addition to sorting, <code>DefaultRowSorter</code> provides the 65 * ability to filter rows. Filtering is done by way of a 66 * <code>RowFilter</code> that is specified using the 67 * <code>setRowFilter</code> method. If no filter has been specified all 68 * rows are included. 69 * <p> 70 * By default, rows are in unsorted order (the same as the model) and 71 * every column is sortable. The default <code>Comparator</code>s are 72 * documented in the subclasses (for example, {@link 73 * javax.swing.table.TableRowSorter TableRowSorter}). 74 * <p> 75 * If the underlying model structure changes (the 76 * <code>modelStructureChanged</code> method is invoked) the following 77 * are reset to their default values: <code>Comparator</code>s by 78 * column, current sort order, and whether each column is sortable. To 79 * find the default <code>Comparator</code>s, see the concrete 80 * implementation (for example, {@link 81 * javax.swing.table.TableRowSorter TableRowSorter}). The default 82 * sort order is unsorted (the same as the model), and columns are 83 * sortable by default. 84 * <p> 85 * If the underlying model structure changes (the 86 * <code>modelStructureChanged</code> method is invoked) the following 87 * are reset to their default values: <code>Comparator</code>s by column, 88 * current sort order and whether a column is sortable. 89 * <p> 90 * <code>DefaultRowSorter</code> is an abstract class. Concrete 91 * subclasses must provide access to the underlying data by invoking 92 * {@code setModelWrapper}. The {@code setModelWrapper} method 93 * <b>must</b> be invoked soon after the constructor is 94 * called, ideally from within the subclass's constructor. 95 * Undefined behavior will result if you use a {@code 96 * DefaultRowSorter} without specifying a {@code ModelWrapper}. 97 * <p> 98 * <code>DefaultRowSorter</code> has two formal type parameters. The 99 * first type parameter corresponds to the class of the model, for example 100 * <code>DefaultTableModel</code>. The second type parameter 101 * corresponds to the class of the identifier passed to the 102 * <code>RowFilter</code>. Refer to <code>TableRowSorter</code> and 103 * <code>RowFilter</code> for more details on the type parameters. 104 * 105 * @param <M> the type of the model 106 * @param <I> the type of the identifier passed to the <code>RowFilter</code> 107 * @see javax.swing.table.TableRowSorter 108 * @see javax.swing.table.DefaultTableModel 109 * @see java.text.Collator 110 * @since 1.6 111 */ 112 public abstract class DefaultRowSorter<M, I> extends RowSorter<M> { 113 /** 114 * Whether or not we resort on TableModelEvent.UPDATEs. 115 */ 116 private boolean sortsOnUpdates; 117 118 /** 119 * View (JTable) -> model. 120 */ 121 private Row[] viewToModel; 122 123 /** 124 * model -> view (JTable) 125 */ 126 private int[] modelToView; 127 128 /** 129 * Comparators specified by column. 130 */ 131 private Comparator<?>[] comparators; 132 133 /** 134 * Whether or not the specified column is sortable, by column. 135 */ 136 private boolean[] isSortable; 137 138 /** 139 * Cached SortKeys for the current sort. 140 */ 141 private SortKey[] cachedSortKeys; 142 143 /** 144 * Cached comparators for the current sort 145 */ 146 private Comparator<?>[] sortComparators; 147 148 /** 149 * Developer supplied Filter. 150 */ 151 private RowFilter<? super M,? super I> filter; 152 153 /** 154 * Value passed to the filter. The same instance is passed to the 155 * filter for different rows. 156 */ 157 private FilterEntry filterEntry; 158 159 /** 160 * The sort keys. 161 */ 162 private List<SortKey> sortKeys; 163 164 /** 165 * Whether or not to use getStringValueAt. This is indexed by column. 166 */ 167 private boolean[] useToString; 168 169 /** 170 * Indicates the contents are sorted. This is used if 171 * getSortsOnUpdates is false and an update event is received. 172 */ 173 private boolean sorted; 174 175 /** 176 * Maximum number of sort keys. 177 */ 178 private int maxSortKeys; 179 180 /** 181 * Provides access to the data we're sorting/filtering. 182 */ 183 private ModelWrapper<M,I> modelWrapper; 184 185 /** 186 * Size of the model. This is used to enforce error checking within 187 * the table changed notification methods (such as rowsInserted). 188 */ 189 private int modelRowCount; 190 191 192 /** 193 * Creates an empty <code>DefaultRowSorter</code>. 194 */ 195 public DefaultRowSorter() { 196 sortKeys = Collections.emptyList(); 197 maxSortKeys = 3; 198 } 199 200 /** 201 * Sets the model wrapper providing the data that is being sorted and 202 * filtered. 203 * 204 * @param modelWrapper the model wrapper responsible for providing the 205 * data that gets sorted and filtered 206 * @throws IllegalArgumentException if {@code modelWrapper} is 207 * {@code null} 208 */ 209 protected final void setModelWrapper(ModelWrapper<M,I> modelWrapper) { 210 if (modelWrapper == null) { 211 throw new IllegalArgumentException( 212 "modelWrapper most be non-null"); 213 } 214 ModelWrapper<M,I> last = this.modelWrapper; 215 this.modelWrapper = modelWrapper; 216 if (last != null) { 217 modelStructureChanged(); 218 } else { 219 // If last is null, we're in the constructor. If we're in 220 // the constructor we don't want to call to overridable methods. 221 modelRowCount = getModelWrapper().getRowCount(); 222 } 223 } 224 225 /** 226 * Returns the model wrapper providing the data that is being sorted and 227 * filtered. 228 * 229 * @return the model wrapper responsible for providing the data that 230 * gets sorted and filtered 231 */ 232 protected final ModelWrapper<M,I> getModelWrapper() { 233 return modelWrapper; 234 } 235 236 /** 237 * Returns the underlying model. 238 * 239 * @return the underlying model 240 */ 241 public final M getModel() { 242 return getModelWrapper().getModel(); 243 } 244 245 /** 246 * Sets whether or not the specified column is sortable. The specified 247 * value is only checked when <code>toggleSortOrder</code> is invoked. 248 * It is still possible to sort on a column that has been marked as 249 * unsortable by directly setting the sort keys. The default is 250 * true. 251 * 252 * @param column the column to enable or disable sorting on, in terms 253 * of the underlying model 254 * @param sortable whether or not the specified column is sortable 255 * @throws IndexOutOfBoundsException if <code>column</code> is outside 256 * the range of the model 257 * @see #toggleSortOrder 258 * @see #setSortKeys 259 */ 260 public void setSortable(int column, boolean sortable) { 261 checkColumn(column); 262 if (isSortable == null) { 263 isSortable = new boolean[getModelWrapper().getColumnCount()]; 264 for (int i = isSortable.length - 1; i >= 0; i--) { 265 isSortable[i] = true; 266 } 267 } 268 isSortable[column] = sortable; 269 } 270 271 /** 272 * Returns true if the specified column is sortable; otherwise, false. 273 * 274 * @param column the column to check sorting for, in terms of the 275 * underlying model 276 * @return true if the column is sortable 277 * @throws IndexOutOfBoundsException if column is outside 278 * the range of the underlying model 279 */ 280 public boolean isSortable(int column) { 281 checkColumn(column); 282 return (isSortable == null) ? true : isSortable[column]; 283 } 284 285 /** 286 * Sets the sort keys. This creates a copy of the supplied 287 * {@code List}; subsequent changes to the supplied 288 * {@code List} do not effect this {@code DefaultRowSorter}. 289 * If the sort keys have changed this triggers a sort. 290 * 291 * @param sortKeys the new <code>SortKeys</code>; <code>null</code> 292 * is a shorthand for specifying an empty list, 293 * indicating that the view should be unsorted 294 * @throws IllegalArgumentException if any of the values in 295 * <code>sortKeys</code> are null or have a column index outside 296 * the range of the model 297 */ 298 public void setSortKeys(List<? extends SortKey> sortKeys) { 299 List<SortKey> old = this.sortKeys; 300 if (sortKeys != null && sortKeys.size() > 0) { 301 int max = getModelWrapper().getColumnCount(); 302 for (SortKey key : sortKeys) { 303 if (key == null || key.getColumn() < 0 || 304 key.getColumn() >= max) { 305 throw new IllegalArgumentException("Invalid SortKey"); 306 } 307 } 308 this.sortKeys = Collections.unmodifiableList( 309 new ArrayList<SortKey>(sortKeys)); 310 } 311 else { 312 this.sortKeys = Collections.emptyList(); 313 } 314 if (!this.sortKeys.equals(old)) { 315 fireSortOrderChanged(); 316 if (viewToModel == null) { 317 // Currently unsorted, use sort so that internal fields 318 // are correctly set. 319 sort(); 320 } else { 321 sortExistingData(); 322 } 323 } 324 } 325 326 /** 327 * Returns the current sort keys. This returns an unmodifiable 328 * {@code non-null List}. If you need to change the sort keys, 329 * make a copy of the returned {@code List}, mutate the copy 330 * and invoke {@code setSortKeys} with the new list. 331 * 332 * @return the current sort order 333 */ 334 public List<? extends SortKey> getSortKeys() { 335 return sortKeys; 336 } 337 338 /** 339 * Sets the maximum number of sort keys. The number of sort keys 340 * determines how equal values are resolved when sorting. For 341 * example, assume a table row sorter is created and 342 * <code>setMaxSortKeys(2)</code> is invoked on it. The user 343 * clicks the header for column 1, causing the table rows to be 344 * sorted based on the items in column 1. Next, the user clicks 345 * the header for column 2, causing the table to be sorted based 346 * on the items in column 2; if any items in column 2 are equal, 347 * then those particular rows are ordered based on the items in 348 * column 1. In this case, we say that the rows are primarily 349 * sorted on column 2, and secondarily on column 1. If the user 350 * then clicks the header for column 3, then the items are 351 * primarily sorted on column 3 and secondarily sorted on column 352 * 2. Because the maximum number of sort keys has been set to 2 353 * with <code>setMaxSortKeys</code>, column 1 no longer has an 354 * effect on the order. 355 * <p> 356 * The maximum number of sort keys is enforced by 357 * <code>toggleSortOrder</code>. You can specify more sort 358 * keys by invoking <code>setSortKeys</code> directly and they will 359 * all be honored. However if <code>toggleSortOrder</code> is subsequently 360 * invoked the maximum number of sort keys will be enforced. 361 * The default value is 3. 362 * 363 * @param max the maximum number of sort keys 364 * @throws IllegalArgumentException if <code>max</code> < 1 365 */ 366 public void setMaxSortKeys(int max) { 367 if (max < 1) { 368 throw new IllegalArgumentException("Invalid max"); 369 } 370 maxSortKeys = max; 371 } 372 373 /** 374 * Returns the maximum number of sort keys. 375 * 376 * @return the maximum number of sort keys 377 */ 378 public int getMaxSortKeys() { 379 return maxSortKeys; 380 } 381 382 /** 383 * If true, specifies that a sort should happen when the underlying 384 * model is updated (<code>rowsUpdated</code> is invoked). For 385 * example, if this is true and the user edits an entry the 386 * location of that item in the view may change. The default is 387 * false. 388 * 389 * @param sortsOnUpdates whether or not to sort on update events 390 */ 391 public void setSortsOnUpdates(boolean sortsOnUpdates) { 392 this.sortsOnUpdates = sortsOnUpdates; 393 } 394 395 /** 396 * Returns true if a sort should happen when the underlying 397 * model is updated; otherwise, returns false. 398 * 399 * @return whether or not to sort when the model is updated 400 */ 401 public boolean getSortsOnUpdates() { 402 return sortsOnUpdates; 403 } 404 405 /** 406 * Sets the filter that determines which rows, if any, should be 407 * hidden from the view. The filter is applied before sorting. A value 408 * of <code>null</code> indicates all values from the model should be 409 * included. 410 * <p> 411 * <code>RowFilter</code>'s <code>include</code> method is passed an 412 * <code>Entry</code> that wraps the underlying model. The number 413 * of columns in the <code>Entry</code> corresponds to the 414 * number of columns in the <code>ModelWrapper</code>. The identifier 415 * comes from the <code>ModelWrapper</code> as well. 416 * <p> 417 * This method triggers a sort. 418 * 419 * @param filter the filter used to determine what entries should be 420 * included 421 */ 422 public void setRowFilter(RowFilter<? super M,? super I> filter) { 423 this.filter = filter; 424 sort(); 425 } 426 427 /** 428 * Returns the filter that determines which rows, if any, should 429 * be hidden from view. 430 * 431 * @return the filter 432 */ 433 public RowFilter<? super M,? super I> getRowFilter() { 434 return filter; 435 } 436 437 /** 438 * Reverses the sort order from ascending to descending (or 439 * descending to ascending) if the specified column is already the 440 * primary sorted column; otherwise, makes the specified column 441 * the primary sorted column, with an ascending sort order. If 442 * the specified column is not sortable, this method has no 443 * effect. 444 * 445 * @param column index of the column to make the primary sorted column, 446 * in terms of the underlying model 447 * @throws IndexOutOfBoundsException {@inheritDoc} 448 * @see #setSortable(int,boolean) 449 * @see #setMaxSortKeys(int) 450 */ 451 public void toggleSortOrder(int column) { 452 checkColumn(column); 453 if (isSortable(column)) { 454 List<SortKey> keys = new ArrayList<SortKey>(getSortKeys()); 455 SortKey sortKey; 456 int sortIndex; 457 for (sortIndex = keys.size() - 1; sortIndex >= 0; sortIndex--) { 458 if (keys.get(sortIndex).getColumn() == column) { 459 break; 460 } 461 } 462 if (sortIndex == -1) { 463 // Key doesn't exist 464 sortKey = new SortKey(column, SortOrder.ASCENDING); 465 keys.add(0, sortKey); 466 } 467 else if (sortIndex == 0) { 468 // It's the primary sorting key, toggle it 469 keys.set(0, toggle(keys.get(0))); 470 } 471 else { 472 // It's not the first, but was sorted on, remove old 473 // entry, insert as first with ascending. 474 keys.remove(sortIndex); 475 keys.add(0, new SortKey(column, SortOrder.ASCENDING)); 476 } 477 if (keys.size() > getMaxSortKeys()) { 478 keys = keys.subList(0, getMaxSortKeys()); 479 } 480 setSortKeys(keys); 481 } 482 } 483 484 private SortKey toggle(SortKey key) { 485 if (key.getSortOrder() == SortOrder.ASCENDING) { 486 return new SortKey(key.getColumn(), SortOrder.DESCENDING); 487 } 488 return new SortKey(key.getColumn(), SortOrder.ASCENDING); 489 } 490 491 /** 492 * {@inheritDoc} 493 * 494 * @throws IndexOutOfBoundsException {@inheritDoc} 495 */ 496 public int convertRowIndexToView(int index) { 497 if (modelToView == null) { 498 if (index < 0 || index >= getModelWrapper().getRowCount()) { 499 throw new IndexOutOfBoundsException("Invalid index"); 500 } 501 return index; 502 } 503 return modelToView[index]; 504 } 505 506 /** 507 * {@inheritDoc} 508 * 509 * @throws IndexOutOfBoundsException {@inheritDoc} 510 */ 511 public int convertRowIndexToModel(int index) { 512 if (viewToModel == null) { 513 if (index < 0 || index >= getModelWrapper().getRowCount()) { 514 throw new IndexOutOfBoundsException("Invalid index"); 515 } 516 return index; 517 } 518 return viewToModel[index].modelIndex; 519 } 520 521 private boolean isUnsorted() { 522 List<? extends SortKey> keys = getSortKeys(); 523 int keySize = keys.size(); 524 return (keySize == 0 || keys.get(0).getSortOrder() == 525 SortOrder.UNSORTED); 526 } 527 528 /** 529 * Sorts the existing filtered data. This should only be used if 530 * the filter hasn't changed. 531 */ 532 private void sortExistingData() { 533 int[] lastViewToModel = getViewToModelAsInts(viewToModel); 534 535 updateUseToString(); 536 cacheSortKeys(getSortKeys()); 537 538 if (isUnsorted()) { 539 if (getRowFilter() == null) { 540 viewToModel = null; 541 modelToView = null; 542 } else { 543 int included = 0; 544 for (int i = 0; i < modelToView.length; i++) { 545 if (modelToView[i] != -1) { 546 viewToModel[included].modelIndex = i; 547 modelToView[i] = included++; 548 } 549 } 550 } 551 } else { 552 // sort the data 553 Arrays.sort(viewToModel); 554 555 // Update the modelToView array 556 setModelToViewFromViewToModel(false); 557 } 558 fireRowSorterChanged(lastViewToModel); 559 } 560 561 /** 562 * Sorts and filters the rows in the view based on the sort keys 563 * of the columns currently being sorted and the filter, if any, 564 * associated with this sorter. An empty <code>sortKeys</code> list 565 * indicates that the view should unsorted, the same as the model. 566 * 567 * @see #setRowFilter 568 * @see #setSortKeys 569 */ 570 public void sort() { 571 sorted = true; 572 int[] lastViewToModel = getViewToModelAsInts(viewToModel); 573 updateUseToString(); 574 if (isUnsorted()) { 575 // Unsorted 576 cachedSortKeys = new SortKey[0]; 577 if (getRowFilter() == null) { 578 // No filter & unsorted 579 if (viewToModel != null) { 580 // sorted -> unsorted 581 viewToModel = null; 582 modelToView = null; 583 } 584 else { 585 // unsorted -> unsorted 586 // No need to do anything. 587 return; 588 } 589 } 590 else { 591 // There is filter, reset mappings 592 initializeFilteredMapping(); 593 } 594 } 595 else { 596 cacheSortKeys(getSortKeys()); 597 598 if (getRowFilter() != null) { 599 initializeFilteredMapping(); 600 } 601 else { 602 createModelToView(getModelWrapper().getRowCount()); 603 createViewToModel(getModelWrapper().getRowCount()); 604 } 605 606 // sort them 607 Arrays.sort(viewToModel); 608 609 // Update the modelToView array 610 setModelToViewFromViewToModel(false); 611 } 612 fireRowSorterChanged(lastViewToModel); 613 } 614 615 /** 616 * Updates the useToString mapping before a sort. 617 */ 618 private void updateUseToString() { 619 int i = getModelWrapper().getColumnCount(); 620 if (useToString == null || useToString.length != i) { 621 useToString = new boolean[i]; 622 } 623 for (--i; i >= 0; i--) { 624 useToString[i] = useToString(i); 625 } 626 } 627 628 /** 629 * Resets the viewToModel and modelToView mappings based on 630 * the current Filter. 631 */ 632 private void initializeFilteredMapping() { 633 int rowCount = getModelWrapper().getRowCount(); 634 int i, j; 635 int excludedCount = 0; 636 637 // Update model -> view 638 createModelToView(rowCount); 639 for (i = 0; i < rowCount; i++) { 640 if (include(i)) { 641 modelToView[i] = i - excludedCount; 642 } 643 else { 644 modelToView[i] = -1; 645 excludedCount++; 646 } 647 } 648 649 // Update view -> model 650 createViewToModel(rowCount - excludedCount); 651 for (i = 0, j = 0; i < rowCount; i++) { 652 if (modelToView[i] != -1) { 653 viewToModel[j++].modelIndex = i; 654 } 655 } 656 } 657 658 /** 659 * Makes sure the modelToView array is of size rowCount. 660 */ 661 private void createModelToView(int rowCount) { 662 if (modelToView == null || modelToView.length != rowCount) { 663 modelToView = new int[rowCount]; 664 } 665 } 666 667 /** 668 * Resets the viewToModel array to be of size rowCount. 669 */ 670 private void createViewToModel(int rowCount) { 671 int recreateFrom = 0; 672 if (viewToModel != null) { 673 recreateFrom = Math.min(rowCount, viewToModel.length); 674 if (viewToModel.length != rowCount) { 675 Row[] oldViewToModel = viewToModel; 676 viewToModel = new Row[rowCount]; 677 System.arraycopy(oldViewToModel, 0, viewToModel, 678 0, recreateFrom); 679 } 680 } 681 else { 682 viewToModel = new Row[rowCount]; 683 } 684 int i; 685 for (i = 0; i < recreateFrom; i++) { 686 viewToModel[i].modelIndex = i; 687 } 688 for (i = recreateFrom; i < rowCount; i++) { 689 viewToModel[i] = new Row(this, i); 690 } 691 } 692 693 /** 694 * Caches the sort keys before a sort. 695 */ 696 private void cacheSortKeys(List<? extends SortKey> keys) { 697 int keySize = keys.size(); 698 sortComparators = new Comparator<?>[keySize]; 699 for (int i = 0; i < keySize; i++) { 700 sortComparators[i] = getComparator0(keys.get(i).getColumn()); 701 } 702 cachedSortKeys = keys.toArray(new SortKey[keySize]); 703 } 704 705 /** 706 * Returns whether or not to convert the value to a string before 707 * doing comparisons when sorting. If true 708 * <code>ModelWrapper.getStringValueAt</code> will be used, otherwise 709 * <code>ModelWrapper.getValueAt</code> will be used. It is up to 710 * subclasses, such as <code>TableRowSorter</code>, to honor this value 711 * in their <code>ModelWrapper</code> implementation. 712 * 713 * @param column the index of the column to test, in terms of the 714 * underlying model 715 * @throws IndexOutOfBoundsException if <code>column</code> is not valid 716 */ 717 protected boolean useToString(int column) { 718 return (getComparator(column) == null); 719 } 720 721 /** 722 * Refreshes the modelToView mapping from that of viewToModel. 723 * If <code>unsetFirst</code> is true, all indices in modelToView are 724 * first set to -1. 725 */ 726 private void setModelToViewFromViewToModel(boolean unsetFirst) { 727 int i; 728 if (unsetFirst) { 729 for (i = modelToView.length - 1; i >= 0; i--) { 730 modelToView[i] = -1; 731 } 732 } 733 for (i = viewToModel.length - 1; i >= 0; i--) { 734 modelToView[viewToModel[i].modelIndex] = i; 735 } 736 } 737 738 private int[] getViewToModelAsInts(Row[] viewToModel) { 739 if (viewToModel != null) { 740 int[] viewToModelI = new int[viewToModel.length]; 741 for (int i = viewToModel.length - 1; i >= 0; i--) { 742 viewToModelI[i] = viewToModel[i].modelIndex; 743 } 744 return viewToModelI; 745 } 746 return new int[0]; 747 } 748 749 /** 750 * Sets the <code>Comparator</code> to use when sorting the specified 751 * column. This does not trigger a sort. If you want to sort after 752 * setting the comparator you need to explicitly invoke <code>sort</code>. 753 * 754 * @param column the index of the column the <code>Comparator</code> is 755 * to be used for, in terms of the underlying model 756 * @param comparator the <code>Comparator</code> to use 757 * @throws IndexOutOfBoundsException if <code>column</code> is outside 758 * the range of the underlying model 759 */ 760 public void setComparator(int column, Comparator<?> comparator) { 761 checkColumn(column); 762 if (comparators == null) { 763 comparators = new Comparator<?>[getModelWrapper().getColumnCount()]; 764 } 765 comparators[column] = comparator; 766 } 767 768 /** 769 * Returns the <code>Comparator</code> for the specified 770 * column. This will return <code>null</code> if a <code>Comparator</code> 771 * has not been specified for the column. 772 * 773 * @param column the column to fetch the <code>Comparator</code> for, in 774 * terms of the underlying model 775 * @return the <code>Comparator</code> for the specified column 776 * @throws IndexOutOfBoundsException if column is outside 777 * the range of the underlying model 778 */ 779 public Comparator<?> getComparator(int column) { 780 checkColumn(column); 781 if (comparators != null) { 782 return comparators[column]; 783 } 784 return null; 785 } 786 787 // Returns the Comparator to use during sorting. Where as 788 // getComparator() may return null, this will never return null. 789 private Comparator<?> getComparator0(int column) { 790 Comparator<?> comparator = getComparator(column); 791 if (comparator != null) { 792 return comparator; 793 } 794 // This should be ok as useToString(column) should have returned 795 // true in this case. 796 return Collator.getInstance(); 797 } 798 799 private RowFilter.Entry<M,I> getFilterEntry(int modelIndex) { 800 if (filterEntry == null) { 801 filterEntry = new FilterEntry(); 802 } 803 filterEntry.modelIndex = modelIndex; 804 return filterEntry; 805 } 806 807 /** 808 * {@inheritDoc} 809 */ 810 public int getViewRowCount() { 811 if (viewToModel != null) { 812 // When filtering this may differ from getModelWrapper().getRowCount() 813 return viewToModel.length; 814 } 815 return getModelWrapper().getRowCount(); 816 } 817 818 /** 819 * {@inheritDoc} 820 */ 821 public int getModelRowCount() { 822 return getModelWrapper().getRowCount(); 823 } 824 825 private void allChanged() { 826 modelToView = null; 827 viewToModel = null; 828 comparators = null; 829 isSortable = null; 830 if (isUnsorted()) { 831 // Keys are already empty, to force a resort we have to 832 // call sort 833 sort(); 834 } else { 835 setSortKeys(null); 836 } 837 } 838 839 /** 840 * {@inheritDoc} 841 */ 842 public void modelStructureChanged() { 843 allChanged(); 844 modelRowCount = getModelWrapper().getRowCount(); 845 } 846 847 /** 848 * {@inheritDoc} 849 */ 850 public void allRowsChanged() { 851 modelRowCount = getModelWrapper().getRowCount(); 852 sort(); 853 } 854 855 /** 856 * {@inheritDoc} 857 * 858 * @throws IndexOutOfBoundsException {@inheritDoc} 859 */ 860 public void rowsInserted(int firstRow, int endRow) { 861 checkAgainstModel(firstRow, endRow); 862 int newModelRowCount = getModelWrapper().getRowCount(); 863 if (endRow >= newModelRowCount) { 864 throw new IndexOutOfBoundsException("Invalid range"); 865 } 866 modelRowCount = newModelRowCount; 867 if (shouldOptimizeChange(firstRow, endRow)) { 868 rowsInserted0(firstRow, endRow); 869 } 870 } 871 872 /** 873 * {@inheritDoc} 874 * 875 * @throws IndexOutOfBoundsException {@inheritDoc} 876 */ 877 public void rowsDeleted(int firstRow, int endRow) { 878 checkAgainstModel(firstRow, endRow); 879 if (firstRow >= modelRowCount || endRow >= modelRowCount) { 880 throw new IndexOutOfBoundsException("Invalid range"); 881 } 882 modelRowCount = getModelWrapper().getRowCount(); 883 if (shouldOptimizeChange(firstRow, endRow)) { 884 rowsDeleted0(firstRow, endRow); 885 } 886 } 887 888 /** 889 * {@inheritDoc} 890 * 891 * @throws IndexOutOfBoundsException {@inheritDoc} 892 */ 893 public void rowsUpdated(int firstRow, int endRow) { 894 checkAgainstModel(firstRow, endRow); 895 if (firstRow >= modelRowCount || endRow >= modelRowCount) { 896 throw new IndexOutOfBoundsException("Invalid range"); 897 } 898 if (getSortsOnUpdates()) { 899 if (shouldOptimizeChange(firstRow, endRow)) { 900 rowsUpdated0(firstRow, endRow); 901 } 902 } 903 else { 904 sorted = false; 905 } 906 } 907 908 /** 909 * {@inheritDoc} 910 * 911 * @throws IndexOutOfBoundsException {@inheritDoc} 912 */ 913 public void rowsUpdated(int firstRow, int endRow, int column) { 914 checkColumn(column); 915 rowsUpdated(firstRow, endRow); 916 } 917 918 private void checkAgainstModel(int firstRow, int endRow) { 919 if (firstRow > endRow || firstRow < 0 || endRow < 0 || 920 firstRow > modelRowCount) { 921 throw new IndexOutOfBoundsException("Invalid range"); 922 } 923 } 924 925 /** 926 * Returns true if the specified row should be included. 927 */ 928 private boolean include(int row) { 929 RowFilter<? super M, ? super I> filter = getRowFilter(); 930 if (filter != null) { 931 return filter.include(getFilterEntry(row)); 932 } 933 // null filter, always include the row. 934 return true; 935 } 936 937 @SuppressWarnings("unchecked") 938 private int compare(int model1, int model2) { 939 int column; 940 SortOrder sortOrder; 941 Object v1, v2; 942 int result; 943 944 for (int counter = 0; counter < cachedSortKeys.length; counter++) { 945 column = cachedSortKeys[counter].getColumn(); 946 sortOrder = cachedSortKeys[counter].getSortOrder(); 947 if (sortOrder == SortOrder.UNSORTED) { 948 result = model1 - model2; 949 } else { 950 // v1 != null && v2 != null 951 if (useToString[column]) { 952 v1 = getModelWrapper().getStringValueAt(model1, column); 953 v2 = getModelWrapper().getStringValueAt(model2, column); 954 } else { 955 v1 = getModelWrapper().getValueAt(model1, column); 956 v2 = getModelWrapper().getValueAt(model2, column); 957 } 958 // Treat nulls as < then non-null 959 if (v1 == null) { 960 if (v2 == null) { 961 result = 0; 962 } else { 963 result = -1; 964 } 965 } else if (v2 == null) { 966 result = 1; 967 } else { 968 Comparator<Object> c = 969 (Comparator<Object>)sortComparators[counter]; 970 result = c.compare(v1, v2); 971 } 972 if (sortOrder == SortOrder.DESCENDING) { 973 result *= -1; 974 } 975 } 976 if (result != 0) { 977 return result; 978 } 979 } 980 // If we get here, they're equal. Fallback to model order. 981 return model1 - model2; 982 } 983 984 /** 985 * Whether not we are filtering/sorting. 986 */ 987 private boolean isTransformed() { 988 return (viewToModel != null); 989 } 990 991 /** 992 * Insets new set of entries. 993 * 994 * @param toAdd the Rows to add, sorted 995 * @param current the array to insert the items into 996 */ 997 private void insertInOrder(List<Row> toAdd, Row[] current) { 998 int last = 0; 999 int index; 1000 int max = toAdd.size(); 1001 for (int i = 0; i < max; i++) { 1002 index = Arrays.binarySearch(current, toAdd.get(i)); 1003 if (index < 0) { 1004 index = -1 - index; 1005 } 1006 System.arraycopy(current, last, 1007 viewToModel, last + i, index - last); 1008 viewToModel[index + i] = toAdd.get(i); 1009 last = index; 1010 } 1011 System.arraycopy(current, last, viewToModel, last + max, 1012 current.length - last); 1013 } 1014 1015 /** 1016 * Returns true if we should try and optimize the processing of the 1017 * <code>TableModelEvent</code>. If this returns false, assume the 1018 * event was dealt with and no further processing needs to happen. 1019 */ 1020 private boolean shouldOptimizeChange(int firstRow, int lastRow) { 1021 if (!isTransformed()) { 1022 // Not transformed, nothing to do. 1023 return false; 1024 } 1025 if (!sorted || (lastRow - firstRow) > viewToModel.length / 10) { 1026 // We either weren't sorted, or to much changed, sort it all 1027 sort(); 1028 return false; 1029 } 1030 return true; 1031 } 1032 1033 private void rowsInserted0(int firstRow, int lastRow) { 1034 int[] oldViewToModel = getViewToModelAsInts(viewToModel); 1035 int i; 1036 int delta = (lastRow - firstRow) + 1; 1037 List<Row> added = new ArrayList<Row>(delta); 1038 1039 // Build the list of Rows to add into added 1040 for (i = firstRow; i <= lastRow; i++) { 1041 if (include(i)) { 1042 added.add(new Row(this, i)); 1043 } 1044 } 1045 1046 // Adjust the model index of rows after the effected region 1047 int viewIndex; 1048 for (i = modelToView.length - 1; i >= firstRow; i--) { 1049 viewIndex = modelToView[i]; 1050 if (viewIndex != -1) { 1051 viewToModel[viewIndex].modelIndex += delta; 1052 } 1053 } 1054 1055 // Insert newly added rows into viewToModel 1056 if (added.size() > 0) { 1057 Collections.sort(added); 1058 Row[] lastViewToModel = viewToModel; 1059 viewToModel = new Row[viewToModel.length + added.size()]; 1060 insertInOrder(added, lastViewToModel); 1061 } 1062 1063 // Update modelToView 1064 createModelToView(getModelWrapper().getRowCount()); 1065 setModelToViewFromViewToModel(true); 1066 1067 // Notify of change 1068 fireRowSorterChanged(oldViewToModel); 1069 } 1070 1071 private void rowsDeleted0(int firstRow, int lastRow) { 1072 int[] oldViewToModel = getViewToModelAsInts(viewToModel); 1073 int removedFromView = 0; 1074 int i; 1075 int viewIndex; 1076 1077 // Figure out how many visible rows are going to be effected. 1078 for (i = firstRow; i <= lastRow; i++) { 1079 viewIndex = modelToView[i]; 1080 if (viewIndex != -1) { 1081 removedFromView++; 1082 viewToModel[viewIndex] = null; 1083 } 1084 } 1085 1086 // Update the model index of rows after the effected region 1087 int delta = lastRow - firstRow + 1; 1088 for (i = modelToView.length - 1; i > lastRow; i--) { 1089 viewIndex = modelToView[i]; 1090 if (viewIndex != -1) { 1091 viewToModel[viewIndex].modelIndex -= delta; 1092 } 1093 } 1094 1095 // Then patch up the viewToModel array 1096 if (removedFromView > 0) { 1097 Row[] newViewToModel = new Row[viewToModel.length - 1098 removedFromView]; 1099 int newIndex = 0; 1100 int last = 0; 1101 for (i = 0; i < viewToModel.length; i++) { 1102 if (viewToModel[i] == null) { 1103 System.arraycopy(viewToModel, last, 1104 newViewToModel, newIndex, i - last); 1105 newIndex += (i - last); 1106 last = i + 1; 1107 } 1108 } 1109 System.arraycopy(viewToModel, last, 1110 newViewToModel, newIndex, viewToModel.length - last); 1111 viewToModel = newViewToModel; 1112 } 1113 1114 // Update the modelToView mapping 1115 createModelToView(getModelWrapper().getRowCount()); 1116 setModelToViewFromViewToModel(true); 1117 1118 // And notify of change 1119 fireRowSorterChanged(oldViewToModel); 1120 } 1121 1122 private void rowsUpdated0(int firstRow, int lastRow) { 1123 int[] oldViewToModel = getViewToModelAsInts(viewToModel); 1124 int i, j; 1125 int delta = lastRow - firstRow + 1; 1126 int modelIndex; 1127 int last; 1128 int index; 1129 1130 if (getRowFilter() == null) { 1131 // Sorting only: 1132 1133 // Remove the effected rows 1134 Row[] updated = new Row[delta]; 1135 for (j = 0, i = firstRow; i <= lastRow; i++, j++) { 1136 updated[j] = viewToModel[modelToView[i]]; 1137 } 1138 1139 // Sort the update rows 1140 Arrays.sort(updated); 1141 1142 // Build the intermediary array: the array of 1143 // viewToModel without the effected rows. 1144 Row[] intermediary = new Row[viewToModel.length - delta]; 1145 for (i = 0, j = 0; i < viewToModel.length; i++) { 1146 modelIndex = viewToModel[i].modelIndex; 1147 if (modelIndex < firstRow || modelIndex > lastRow) { 1148 intermediary[j++] = viewToModel[i]; 1149 } 1150 } 1151 1152 // Build the new viewToModel 1153 insertInOrder(Arrays.asList(updated), intermediary); 1154 1155 // Update modelToView 1156 setModelToViewFromViewToModel(false); 1157 } 1158 else { 1159 // Sorting & filtering. 1160 1161 // Remove the effected rows, adding them to updated and setting 1162 // modelToView to -2 for any rows that were not filtered out 1163 List<Row> updated = new ArrayList<Row>(delta); 1164 int newlyVisible = 0; 1165 int newlyHidden = 0; 1166 int effected = 0; 1167 for (i = firstRow; i <= lastRow; i++) { 1168 if (modelToView[i] == -1) { 1169 // This row was filtered out 1170 if (include(i)) { 1171 // No longer filtered 1172 updated.add(new Row(this, i)); 1173 newlyVisible++; 1174 } 1175 } 1176 else { 1177 // This row was visible, make sure it should still be 1178 // visible. 1179 if (!include(i)) { 1180 newlyHidden++; 1181 } 1182 else { 1183 updated.add(viewToModel[modelToView[i]]); 1184 } 1185 modelToView[i] = -2; 1186 effected++; 1187 } 1188 } 1189 1190 // Sort the updated rows 1191 Collections.sort(updated); 1192 1193 // Build the intermediary array: the array of 1194 // viewToModel without the updated rows. 1195 Row[] intermediary = new Row[viewToModel.length - effected]; 1196 for (i = 0, j = 0; i < viewToModel.length; i++) { 1197 modelIndex = viewToModel[i].modelIndex; 1198 if (modelToView[modelIndex] != -2) { 1199 intermediary[j++] = viewToModel[i]; 1200 } 1201 } 1202 1203 // Recreate viewToModel, if necessary 1204 if (newlyVisible != newlyHidden) { 1205 viewToModel = new Row[viewToModel.length + newlyVisible - 1206 newlyHidden]; 1207 } 1208 1209 // Rebuild the new viewToModel array 1210 insertInOrder(updated, intermediary); 1211 1212 // Update modelToView 1213 setModelToViewFromViewToModel(true); 1214 } 1215 // And finally fire a sort event. 1216 fireRowSorterChanged(oldViewToModel); 1217 } 1218 1219 private void checkColumn(int column) { 1220 if (column < 0 || column >= getModelWrapper().getColumnCount()) { 1221 throw new IndexOutOfBoundsException( 1222 "column beyond range of TableModel"); 1223 } 1224 } 1225 1226 1227 /** 1228 * <code>DefaultRowSorter.ModelWrapper</code> is responsible for providing 1229 * the data that gets sorted by <code>DefaultRowSorter</code>. You 1230 * normally do not interact directly with <code>ModelWrapper</code>. 1231 * Subclasses of <code>DefaultRowSorter</code> provide an 1232 * implementation of <code>ModelWrapper</code> wrapping another model. 1233 * For example, 1234 * <code>TableRowSorter</code> provides a <code>ModelWrapper</code> that 1235 * wraps a <code>TableModel</code>. 1236 * <p> 1237 * <code>ModelWrapper</code> makes a distinction between values as 1238 * <code>Object</code>s and <code>String</code>s. This allows 1239 * implementations to provide a custom string 1240 * converter to be used instead of invoking <code>toString</code> on the 1241 * object. 1242 * 1243 * @param <M> the type of the underlying model 1244 * @param <I> the identifier supplied to the filter 1245 * @since 1.6 1246 * @see RowFilter 1247 * @see RowFilter.Entry 1248 */ 1249 protected abstract static class ModelWrapper<M,I> { 1250 /** 1251 * Creates a new <code>ModelWrapper</code>. 1252 */ 1253 protected ModelWrapper() { 1254 } 1255 1256 /** 1257 * Returns the underlying model that this <code>Model</code> is 1258 * wrapping. 1259 * 1260 * @return the underlying model 1261 */ 1262 public abstract M getModel(); 1263 1264 /** 1265 * Returns the number of columns in the model. 1266 * 1267 * @return the number of columns in the model 1268 */ 1269 public abstract int getColumnCount(); 1270 1271 /** 1272 * Returns the number of rows in the model. 1273 * 1274 * @return the number of rows in the model 1275 */ 1276 public abstract int getRowCount(); 1277 1278 /** 1279 * Returns the value at the specified index. 1280 * 1281 * @param row the row index 1282 * @param column the column index 1283 * @return the value at the specified index 1284 * @throws IndexOutOfBoundsException if the indices are outside 1285 * the range of the model 1286 */ 1287 public abstract Object getValueAt(int row, int column); 1288 1289 /** 1290 * Returns the value as a <code>String</code> at the specified 1291 * index. This implementation uses <code>toString</code> on 1292 * the result from <code>getValueAt</code> (making sure 1293 * to return an empty string for null values). Subclasses that 1294 * override this method should never return null. 1295 * 1296 * @param row the row index 1297 * @param column the column index 1298 * @return the value at the specified index as a <code>String</code> 1299 * @throws IndexOutOfBoundsException if the indices are outside 1300 * the range of the model 1301 */ 1302 public String getStringValueAt(int row, int column) { 1303 Object o = getValueAt(row, column); 1304 if (o == null) { 1305 return ""; 1306 } 1307 String string = o.toString(); 1308 if (string == null) { 1309 return ""; 1310 } 1311 return string; 1312 } 1313 1314 /** 1315 * Returns the identifier for the specified row. The return value 1316 * of this is used as the identifier for the 1317 * <code>RowFilter.Entry</code> that is passed to the 1318 * <code>RowFilter</code>. 1319 * 1320 * @param row the row to return the identifier for, in terms of 1321 * the underlying model 1322 * @return the identifier 1323 * @see RowFilter.Entry#getIdentifier 1324 */ 1325 public abstract I getIdentifier(int row); 1326 } 1327 1328 1329 /** 1330 * RowFilter.Entry implementation that delegates to the ModelWrapper. 1331 * getFilterEntry(int) creates the single instance of this that is 1332 * passed to the Filter. Only call getFilterEntry(int) to get 1333 * the instance. 1334 */ 1335 private class FilterEntry extends RowFilter.Entry<M,I> { 1336 /** 1337 * The index into the model, set in getFilterEntry 1338 */ 1339 int modelIndex; 1340 1341 public M getModel() { 1342 return getModelWrapper().getModel(); 1343 } 1344 1345 public int getValueCount() { 1346 return getModelWrapper().getColumnCount(); 1347 } 1348 1349 public Object getValue(int index) { 1350 return getModelWrapper().getValueAt(modelIndex, index); 1351 } 1352 1353 public String getStringValue(int index) { 1354 return getModelWrapper().getStringValueAt(modelIndex, index); 1355 } 1356 1357 public I getIdentifier() { 1358 return getModelWrapper().getIdentifier(modelIndex); 1359 } 1360 } 1361 1362 1363 /** 1364 * Row is used to handle the actual sorting by way of Comparable. It 1365 * will use the sortKeys to do the actual comparison. 1366 */ 1367 // NOTE: this class is static so that it can be placed in an array 1368 private static class Row implements Comparable<Row> { 1369 private DefaultRowSorter<?, ?> sorter; 1370 int modelIndex; 1371 1372 public Row(DefaultRowSorter<?, ?> sorter, int index) { 1373 this.sorter = sorter; 1374 modelIndex = index; 1375 } 1376 1377 public int compareTo(Row o) { 1378 return sorter.compare(modelIndex, o.modelIndex); 1379 } 1380 } 1381 }