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