1 /* 2 * Copyright (c) 2005, 2014, 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 * @return true if values are to be converted to strings before doing 716 * comparisons when sorting 717 * @throws IndexOutOfBoundsException if <code>column</code> is not valid 718 */ 719 protected boolean useToString(int column) { 720 return (getComparator(column) == null); 721 } 722 723 /** 724 * Refreshes the modelToView mapping from that of viewToModel. 725 * If <code>unsetFirst</code> is true, all indices in modelToView are 726 * first set to -1. 727 */ 728 private void setModelToViewFromViewToModel(boolean unsetFirst) { 729 int i; 730 if (unsetFirst) { 731 for (i = modelToView.length - 1; i >= 0; i--) { 732 modelToView[i] = -1; 733 } 734 } 735 for (i = viewToModel.length - 1; i >= 0; i--) { 736 modelToView[viewToModel[i].modelIndex] = i; 737 } 738 } 739 740 private int[] getViewToModelAsInts(Row[] viewToModel) { 741 if (viewToModel != null) { 742 int[] viewToModelI = new int[viewToModel.length]; 743 for (int i = viewToModel.length - 1; i >= 0; i--) { 744 viewToModelI[i] = viewToModel[i].modelIndex; 745 } 746 return viewToModelI; 747 } 748 return new int[0]; 749 } 750 751 /** 752 * Sets the <code>Comparator</code> to use when sorting the specified 753 * column. This does not trigger a sort. If you want to sort after 754 * setting the comparator you need to explicitly invoke <code>sort</code>. 755 * 756 * @param column the index of the column the <code>Comparator</code> is 757 * to be used for, in terms of the underlying model 758 * @param comparator the <code>Comparator</code> to use 759 * @throws IndexOutOfBoundsException if <code>column</code> is outside 760 * the range of the underlying model 761 */ 762 public void setComparator(int column, Comparator<?> comparator) { 763 checkColumn(column); 764 if (comparators == null) { 765 comparators = new Comparator<?>[getModelWrapper().getColumnCount()]; 766 } 767 comparators[column] = comparator; 768 } 769 770 /** 771 * Returns the <code>Comparator</code> for the specified 772 * column. This will return <code>null</code> if a <code>Comparator</code> 773 * has not been specified for the column. 774 * 775 * @param column the column to fetch the <code>Comparator</code> for, in 776 * terms of the underlying model 777 * @return the <code>Comparator</code> for the specified column 778 * @throws IndexOutOfBoundsException if column is outside 779 * the range of the underlying model 780 */ 781 public Comparator<?> getComparator(int column) { 782 checkColumn(column); 783 if (comparators != null) { 784 return comparators[column]; 785 } 786 return null; 787 } 788 789 // Returns the Comparator to use during sorting. Where as 790 // getComparator() may return null, this will never return null. 791 private Comparator<?> getComparator0(int column) { 792 Comparator<?> comparator = getComparator(column); 793 if (comparator != null) { 794 return comparator; 795 } 796 // This should be ok as useToString(column) should have returned 797 // true in this case. 798 return Collator.getInstance(); 799 } 800 801 private RowFilter.Entry<M,I> getFilterEntry(int modelIndex) { 802 if (filterEntry == null) { 803 filterEntry = new FilterEntry(); 804 } 805 filterEntry.modelIndex = modelIndex; 806 return filterEntry; 807 } 808 809 /** 810 * {@inheritDoc} 811 */ 812 public int getViewRowCount() { 813 if (viewToModel != null) { 814 // When filtering this may differ from getModelWrapper().getRowCount() 815 return viewToModel.length; 816 } 817 return getModelWrapper().getRowCount(); 818 } 819 820 /** 821 * {@inheritDoc} 822 */ 823 public int getModelRowCount() { 824 return getModelWrapper().getRowCount(); 825 } 826 827 private void allChanged() { 828 modelToView = null; 829 viewToModel = null; 830 comparators = null; 831 isSortable = null; 832 if (isUnsorted()) { 833 // Keys are already empty, to force a resort we have to 834 // call sort 835 sort(); 836 } else { 837 setSortKeys(null); 838 } 839 } 840 841 /** 842 * {@inheritDoc} 843 */ 844 public void modelStructureChanged() { 845 allChanged(); 846 modelRowCount = getModelWrapper().getRowCount(); 847 } 848 849 /** 850 * {@inheritDoc} 851 */ 852 public void allRowsChanged() { 853 modelRowCount = getModelWrapper().getRowCount(); 854 sort(); 855 } 856 857 /** 858 * {@inheritDoc} 859 * 860 * @throws IndexOutOfBoundsException {@inheritDoc} 861 */ 862 public void rowsInserted(int firstRow, int endRow) { 863 checkAgainstModel(firstRow, endRow); 864 int newModelRowCount = getModelWrapper().getRowCount(); 865 if (endRow >= newModelRowCount) { 866 throw new IndexOutOfBoundsException("Invalid range"); 867 } 868 modelRowCount = newModelRowCount; 869 if (shouldOptimizeChange(firstRow, endRow)) { 870 rowsInserted0(firstRow, endRow); 871 } 872 } 873 874 /** 875 * {@inheritDoc} 876 * 877 * @throws IndexOutOfBoundsException {@inheritDoc} 878 */ 879 public void rowsDeleted(int firstRow, int endRow) { 880 checkAgainstModel(firstRow, endRow); 881 if (firstRow >= modelRowCount || endRow >= modelRowCount) { 882 throw new IndexOutOfBoundsException("Invalid range"); 883 } 884 modelRowCount = getModelWrapper().getRowCount(); 885 if (shouldOptimizeChange(firstRow, endRow)) { 886 rowsDeleted0(firstRow, endRow); 887 } 888 } 889 890 /** 891 * {@inheritDoc} 892 * 893 * @throws IndexOutOfBoundsException {@inheritDoc} 894 */ 895 public void rowsUpdated(int firstRow, int endRow) { 896 checkAgainstModel(firstRow, endRow); 897 if (firstRow >= modelRowCount || endRow >= modelRowCount) { 898 throw new IndexOutOfBoundsException("Invalid range"); 899 } 900 if (getSortsOnUpdates()) { 901 if (shouldOptimizeChange(firstRow, endRow)) { 902 rowsUpdated0(firstRow, endRow); 903 } 904 } 905 else { 906 sorted = false; 907 } 908 } 909 910 /** 911 * {@inheritDoc} 912 * 913 * @throws IndexOutOfBoundsException {@inheritDoc} 914 */ 915 public void rowsUpdated(int firstRow, int endRow, int column) { 916 checkColumn(column); 917 rowsUpdated(firstRow, endRow); 918 } 919 920 private void checkAgainstModel(int firstRow, int endRow) { 921 if (firstRow > endRow || firstRow < 0 || endRow < 0 || 922 firstRow > modelRowCount) { 923 throw new IndexOutOfBoundsException("Invalid range"); 924 } 925 } 926 927 /** 928 * Returns true if the specified row should be included. 929 */ 930 private boolean include(int row) { 931 RowFilter<? super M, ? super I> filter = getRowFilter(); 932 if (filter != null) { 933 return filter.include(getFilterEntry(row)); 934 } 935 // null filter, always include the row. 936 return true; 937 } 938 939 @SuppressWarnings("unchecked") 940 private int compare(int model1, int model2) { 941 int column; 942 SortOrder sortOrder; 943 Object v1, v2; 944 int result; 945 946 for (int counter = 0; counter < cachedSortKeys.length; counter++) { 947 column = cachedSortKeys[counter].getColumn(); 948 sortOrder = cachedSortKeys[counter].getSortOrder(); 949 if (sortOrder == SortOrder.UNSORTED) { 950 result = model1 - model2; 951 } else { 952 // v1 != null && v2 != null 953 if (useToString[column]) { 954 v1 = getModelWrapper().getStringValueAt(model1, column); 955 v2 = getModelWrapper().getStringValueAt(model2, column); 956 } else { 957 v1 = getModelWrapper().getValueAt(model1, column); 958 v2 = getModelWrapper().getValueAt(model2, column); 959 } 960 // Treat nulls as < then non-null 961 if (v1 == null) { 962 if (v2 == null) { 963 result = 0; 964 } else { 965 result = -1; 966 } 967 } else if (v2 == null) { 968 result = 1; 969 } else { 970 Comparator<Object> c = 971 (Comparator<Object>)sortComparators[counter]; 972 result = c.compare(v1, v2); 973 } 974 if (sortOrder == SortOrder.DESCENDING) { 975 result *= -1; 976 } 977 } 978 if (result != 0) { 979 return result; 980 } 981 } 982 // If we get here, they're equal. Fallback to model order. 983 return model1 - model2; 984 } 985 986 /** 987 * Whether not we are filtering/sorting. 988 */ 989 private boolean isTransformed() { 990 return (viewToModel != null); 991 } 992 993 /** 994 * Insets new set of entries. 995 * 996 * @param toAdd the Rows to add, sorted 997 * @param current the array to insert the items into 998 */ 999 private void insertInOrder(List<Row> toAdd, Row[] current) { 1000 int last = 0; 1001 int index; 1002 int max = toAdd.size(); 1003 for (int i = 0; i < max; i++) { 1004 index = Arrays.binarySearch(current, toAdd.get(i)); 1005 if (index < 0) { 1006 index = -1 - index; 1007 } 1008 System.arraycopy(current, last, 1009 viewToModel, last + i, index - last); 1010 viewToModel[index + i] = toAdd.get(i); 1011 last = index; 1012 } 1013 System.arraycopy(current, last, viewToModel, last + max, 1014 current.length - last); 1015 } 1016 1017 /** 1018 * Returns true if we should try and optimize the processing of the 1019 * <code>TableModelEvent</code>. If this returns false, assume the 1020 * event was dealt with and no further processing needs to happen. 1021 */ 1022 private boolean shouldOptimizeChange(int firstRow, int lastRow) { 1023 if (!isTransformed()) { 1024 // Not transformed, nothing to do. 1025 return false; 1026 } 1027 if (!sorted || (lastRow - firstRow) > viewToModel.length / 10) { 1028 // We either weren't sorted, or to much changed, sort it all 1029 sort(); 1030 return false; 1031 } 1032 return true; 1033 } 1034 1035 private void rowsInserted0(int firstRow, int lastRow) { 1036 int[] oldViewToModel = getViewToModelAsInts(viewToModel); 1037 int i; 1038 int delta = (lastRow - firstRow) + 1; 1039 List<Row> added = new ArrayList<Row>(delta); 1040 1041 // Build the list of Rows to add into added 1042 for (i = firstRow; i <= lastRow; i++) { 1043 if (include(i)) { 1044 added.add(new Row(this, i)); 1045 } 1046 } 1047 1048 // Adjust the model index of rows after the effected region 1049 int viewIndex; 1050 for (i = modelToView.length - 1; i >= firstRow; i--) { 1051 viewIndex = modelToView[i]; 1052 if (viewIndex != -1) { 1053 viewToModel[viewIndex].modelIndex += delta; 1054 } 1055 } 1056 1057 // Insert newly added rows into viewToModel 1058 if (added.size() > 0) { 1059 Collections.sort(added); 1060 Row[] lastViewToModel = viewToModel; 1061 viewToModel = new Row[viewToModel.length + added.size()]; 1062 insertInOrder(added, lastViewToModel); 1063 } 1064 1065 // Update modelToView 1066 createModelToView(getModelWrapper().getRowCount()); 1067 setModelToViewFromViewToModel(true); 1068 1069 // Notify of change 1070 fireRowSorterChanged(oldViewToModel); 1071 } 1072 1073 private void rowsDeleted0(int firstRow, int lastRow) { 1074 int[] oldViewToModel = getViewToModelAsInts(viewToModel); 1075 int removedFromView = 0; 1076 int i; 1077 int viewIndex; 1078 1079 // Figure out how many visible rows are going to be effected. 1080 for (i = firstRow; i <= lastRow; i++) { 1081 viewIndex = modelToView[i]; 1082 if (viewIndex != -1) { 1083 removedFromView++; 1084 viewToModel[viewIndex] = null; 1085 } 1086 } 1087 1088 // Update the model index of rows after the effected region 1089 int delta = lastRow - firstRow + 1; 1090 for (i = modelToView.length - 1; i > lastRow; i--) { 1091 viewIndex = modelToView[i]; 1092 if (viewIndex != -1) { 1093 viewToModel[viewIndex].modelIndex -= delta; 1094 } 1095 } 1096 1097 // Then patch up the viewToModel array 1098 if (removedFromView > 0) { 1099 Row[] newViewToModel = new Row[viewToModel.length - 1100 removedFromView]; 1101 int newIndex = 0; 1102 int last = 0; 1103 for (i = 0; i < viewToModel.length; i++) { 1104 if (viewToModel[i] == null) { 1105 System.arraycopy(viewToModel, last, 1106 newViewToModel, newIndex, i - last); 1107 newIndex += (i - last); 1108 last = i + 1; 1109 } 1110 } 1111 System.arraycopy(viewToModel, last, 1112 newViewToModel, newIndex, viewToModel.length - last); 1113 viewToModel = newViewToModel; 1114 } 1115 1116 // Update the modelToView mapping 1117 createModelToView(getModelWrapper().getRowCount()); 1118 setModelToViewFromViewToModel(true); 1119 1120 // And notify of change 1121 fireRowSorterChanged(oldViewToModel); 1122 } 1123 1124 private void rowsUpdated0(int firstRow, int lastRow) { 1125 int[] oldViewToModel = getViewToModelAsInts(viewToModel); 1126 int i, j; 1127 int delta = lastRow - firstRow + 1; 1128 int modelIndex; 1129 int last; 1130 int index; 1131 1132 if (getRowFilter() == null) { 1133 // Sorting only: 1134 1135 // Remove the effected rows 1136 Row[] updated = new Row[delta]; 1137 for (j = 0, i = firstRow; i <= lastRow; i++, j++) { 1138 updated[j] = viewToModel[modelToView[i]]; 1139 } 1140 1141 // Sort the update rows 1142 Arrays.sort(updated); 1143 1144 // Build the intermediary array: the array of 1145 // viewToModel without the effected rows. 1146 Row[] intermediary = new Row[viewToModel.length - delta]; 1147 for (i = 0, j = 0; i < viewToModel.length; i++) { 1148 modelIndex = viewToModel[i].modelIndex; 1149 if (modelIndex < firstRow || modelIndex > lastRow) { 1150 intermediary[j++] = viewToModel[i]; 1151 } 1152 } 1153 1154 // Build the new viewToModel 1155 insertInOrder(Arrays.asList(updated), intermediary); 1156 1157 // Update modelToView 1158 setModelToViewFromViewToModel(false); 1159 } 1160 else { 1161 // Sorting & filtering. 1162 1163 // Remove the effected rows, adding them to updated and setting 1164 // modelToView to -2 for any rows that were not filtered out 1165 List<Row> updated = new ArrayList<Row>(delta); 1166 int newlyVisible = 0; 1167 int newlyHidden = 0; 1168 int effected = 0; 1169 for (i = firstRow; i <= lastRow; i++) { 1170 if (modelToView[i] == -1) { 1171 // This row was filtered out 1172 if (include(i)) { 1173 // No longer filtered 1174 updated.add(new Row(this, i)); 1175 newlyVisible++; 1176 } 1177 } 1178 else { 1179 // This row was visible, make sure it should still be 1180 // visible. 1181 if (!include(i)) { 1182 newlyHidden++; 1183 } 1184 else { 1185 updated.add(viewToModel[modelToView[i]]); 1186 } 1187 modelToView[i] = -2; 1188 effected++; 1189 } 1190 } 1191 1192 // Sort the updated rows 1193 Collections.sort(updated); 1194 1195 // Build the intermediary array: the array of 1196 // viewToModel without the updated rows. 1197 Row[] intermediary = new Row[viewToModel.length - effected]; 1198 for (i = 0, j = 0; i < viewToModel.length; i++) { 1199 modelIndex = viewToModel[i].modelIndex; 1200 if (modelToView[modelIndex] != -2) { 1201 intermediary[j++] = viewToModel[i]; 1202 } 1203 } 1204 1205 // Recreate viewToModel, if necessary 1206 if (newlyVisible != newlyHidden) { 1207 viewToModel = new Row[viewToModel.length + newlyVisible - 1208 newlyHidden]; 1209 } 1210 1211 // Rebuild the new viewToModel array 1212 insertInOrder(updated, intermediary); 1213 1214 // Update modelToView 1215 setModelToViewFromViewToModel(true); 1216 } 1217 // And finally fire a sort event. 1218 fireRowSorterChanged(oldViewToModel); 1219 } 1220 1221 private void checkColumn(int column) { 1222 if (column < 0 || column >= getModelWrapper().getColumnCount()) { 1223 throw new IndexOutOfBoundsException( 1224 "column beyond range of TableModel"); 1225 } 1226 } 1227 1228 1229 /** 1230 * <code>DefaultRowSorter.ModelWrapper</code> is responsible for providing 1231 * the data that gets sorted by <code>DefaultRowSorter</code>. You 1232 * normally do not interact directly with <code>ModelWrapper</code>. 1233 * Subclasses of <code>DefaultRowSorter</code> provide an 1234 * implementation of <code>ModelWrapper</code> wrapping another model. 1235 * For example, 1236 * <code>TableRowSorter</code> provides a <code>ModelWrapper</code> that 1237 * wraps a <code>TableModel</code>. 1238 * <p> 1239 * <code>ModelWrapper</code> makes a distinction between values as 1240 * <code>Object</code>s and <code>String</code>s. This allows 1241 * implementations to provide a custom string 1242 * converter to be used instead of invoking <code>toString</code> on the 1243 * object. 1244 * 1245 * @param <M> the type of the underlying model 1246 * @param <I> the identifier supplied to the filter 1247 * @since 1.6 1248 * @see RowFilter 1249 * @see RowFilter.Entry 1250 */ 1251 protected abstract static class ModelWrapper<M,I> { 1252 /** 1253 * Creates a new <code>ModelWrapper</code>. 1254 */ 1255 protected ModelWrapper() { 1256 } 1257 1258 /** 1259 * Returns the underlying model that this <code>Model</code> is 1260 * wrapping. 1261 * 1262 * @return the underlying model 1263 */ 1264 public abstract M getModel(); 1265 1266 /** 1267 * Returns the number of columns in the model. 1268 * 1269 * @return the number of columns in the model 1270 */ 1271 public abstract int getColumnCount(); 1272 1273 /** 1274 * Returns the number of rows in the model. 1275 * 1276 * @return the number of rows in the model 1277 */ 1278 public abstract int getRowCount(); 1279 1280 /** 1281 * Returns the value at the specified index. 1282 * 1283 * @param row the row index 1284 * @param column the column index 1285 * @return the value at the specified index 1286 * @throws IndexOutOfBoundsException if the indices are outside 1287 * the range of the model 1288 */ 1289 public abstract Object getValueAt(int row, int column); 1290 1291 /** 1292 * Returns the value as a <code>String</code> at the specified 1293 * index. This implementation uses <code>toString</code> on 1294 * the result from <code>getValueAt</code> (making sure 1295 * to return an empty string for null values). Subclasses that 1296 * override this method should never return null. 1297 * 1298 * @param row the row index 1299 * @param column the column index 1300 * @return the value at the specified index as a <code>String</code> 1301 * @throws IndexOutOfBoundsException if the indices are outside 1302 * the range of the model 1303 */ 1304 public String getStringValueAt(int row, int column) { 1305 Object o = getValueAt(row, column); 1306 if (o == null) { 1307 return ""; 1308 } 1309 String string = o.toString(); 1310 if (string == null) { 1311 return ""; 1312 } 1313 return string; 1314 } 1315 1316 /** 1317 * Returns the identifier for the specified row. The return value 1318 * of this is used as the identifier for the 1319 * <code>RowFilter.Entry</code> that is passed to the 1320 * <code>RowFilter</code>. 1321 * 1322 * @param row the row to return the identifier for, in terms of 1323 * the underlying model 1324 * @return the identifier 1325 * @see RowFilter.Entry#getIdentifier 1326 */ 1327 public abstract I getIdentifier(int row); 1328 } 1329 1330 1331 /** 1332 * RowFilter.Entry implementation that delegates to the ModelWrapper. 1333 * getFilterEntry(int) creates the single instance of this that is 1334 * passed to the Filter. Only call getFilterEntry(int) to get 1335 * the instance. 1336 */ 1337 private class FilterEntry extends RowFilter.Entry<M,I> { 1338 /** 1339 * The index into the model, set in getFilterEntry 1340 */ 1341 int modelIndex; 1342 1343 public M getModel() { 1344 return getModelWrapper().getModel(); 1345 } 1346 1347 public int getValueCount() { 1348 return getModelWrapper().getColumnCount(); 1349 } 1350 1351 public Object getValue(int index) { 1352 return getModelWrapper().getValueAt(modelIndex, index); 1353 } 1354 1355 public String getStringValue(int index) { 1356 return getModelWrapper().getStringValueAt(modelIndex, index); 1357 } 1358 1359 public I getIdentifier() { 1360 return getModelWrapper().getIdentifier(modelIndex); 1361 } 1362 } 1363 1364 1365 /** 1366 * Row is used to handle the actual sorting by way of Comparable. It 1367 * will use the sortKeys to do the actual comparison. 1368 */ 1369 // NOTE: this class is static so that it can be placed in an array 1370 private static class Row implements Comparable<Row> { 1371 private DefaultRowSorter<?, ?> sorter; 1372 int modelIndex; 1373 1374 public Row(DefaultRowSorter<?, ?> sorter, int index) { 1375 this.sorter = sorter; 1376 modelIndex = index; 1377 } 1378 1379 public int compareTo(Row o) { 1380 return sorter.compare(modelIndex, o.modelIndex); 1381 } 1382 } 1383 }