Showing the changes to java/pkg/javax/swing/DefaultRowSorter.java (1 of 18352 files). Show all
Unified · Split
| @@ -0,0 +1,1379 @@ | |||
| 1 | /* | ||
| 2 | * Copyright (c) 2005, 2006, Oracle and/or its affiliates. All rights reserved. | ||
| 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | ||
| 4 | * | ||
| 5 | * This code is free software; you can redistribute it and/or modify it | ||
| 6 | * under the terms of the GNU General Public License version 2 only, as | ||
| 7 | * published by the Free Software Foundation. Oracle designates this | ||
| 8 | * particular file as subject to the "Classpath" exception as provided | ||
| 9 | * by Oracle in the LICENSE file that accompanied this code. | ||
| 10 | * | ||
| 11 | * This code is distributed in the hope that it will be useful, but WITHOUT | ||
| 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
| 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | ||
| 14 | * version 2 for more details (a copy is included in the LICENSE file that | ||
| 15 | * accompanied this code). | ||
| 16 | * | ||
| 17 | * You should have received a copy of the GNU General Public License version | ||
| 18 | * 2 along with this work; if not, write to the Free Software Foundation, | ||
| 19 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | ||
| 20 | * | ||
| 21 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | ||
| 22 | * or visit www.oracle.com if you need additional information or have any | ||
| 23 | * questions. | ||
| 24 | */ | ||
| 25 | package javax.swing; | ||
| 26 | |||
| 27 | import 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 occuring 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 | result = sortComparators[counter].compare(v1, v2); | ||
| 969 | } | ||
| 970 | if (sortOrder == SortOrder.DESCENDING) { | ||
| 971 | result *= -1; | ||
| 972 | } | ||
| 973 | } | ||
| 974 | if (result != 0) { | ||
| 975 | return result; | ||
| 976 | } | ||
| 977 | } | ||
| 978 | // If we get here, they're equal. Fallback to model order. | ||
| 979 | return model1 - model2; | ||
| 980 | } | ||
| 981 | |||
| 982 | /** | ||
| 983 | * Whether not we are filtering/sorting. | ||
| 984 | */ | ||
| 985 | private boolean isTransformed() { | ||
| 986 | return (viewToModel != null); | ||
| 987 | } | ||
| 988 | |||
| 989 | /** | ||
| 990 | * Insets new set of entries. | ||
| 991 | * | ||
| 992 | * @param toAdd the Rows to add, sorted | ||
| 993 | * @param current the array to insert the items into | ||
| 994 | */ | ||
| 995 | private void insertInOrder(List<Row> toAdd, Row[] current) { | ||
| 996 | int last = 0; | ||
| 997 | int index; | ||
| 998 | int max = toAdd.size(); | ||
| 999 | for (int i = 0; i < max; i++) { | ||
| 1000 | index = Arrays.binarySearch(current, toAdd.get(i)); | ||
| 1001 | if (index < 0) { | ||
| 1002 | index = -1 - index; | ||
| 1003 | } | ||
| 1004 | System.arraycopy(current, last, | ||
| 1005 | viewToModel, last + i, index - last); | ||
| 1006 | viewToModel[index + i] = toAdd.get(i); | ||
| 1007 | last = index; | ||
| 1008 | } | ||
| 1009 | System.arraycopy(current, last, viewToModel, last + max, | ||
| 1010 | current.length - last); | ||
| 1011 | } | ||
| 1012 | |||
| 1013 | /** | ||
| 1014 | * Returns true if we should try and optimize the processing of the | ||
| 1015 | * <code>TableModelEvent</code>. If this returns false, assume the | ||
| 1016 | * event was dealt with and no further processing needs to happen. | ||
| 1017 | */ | ||
| 1018 | private boolean shouldOptimizeChange(int firstRow, int lastRow) { | ||
| 1019 | if (!isTransformed()) { | ||
| 1020 | // Not transformed, nothing to do. | ||
| 1021 | return false; | ||
| 1022 | } | ||
| 1023 | if (!sorted || (lastRow - firstRow) > viewToModel.length / 10) { | ||
| 1024 | // We either weren't sorted, or to much changed, sort it all | ||
| 1025 | sort(); | ||
| 1026 | return false; | ||
| 1027 | } | ||
| 1028 | return true; | ||
| 1029 | } | ||
| 1030 | |||
| 1031 | private void rowsInserted0(int firstRow, int lastRow) { | ||
| 1032 | int[] oldViewToModel = getViewToModelAsInts(viewToModel); | ||
| 1033 | int i; | ||
| 1034 | int delta = (lastRow - firstRow) + 1; | ||
| 1035 | List<Row> added = new ArrayList<Row>(delta); | ||
| 1036 | |||
| 1037 | // Build the list of Rows to add into added | ||
| 1038 | for (i = firstRow; i <= lastRow; i++) { | ||
| 1039 | if (include(i)) { | ||
| 1040 | added.add(new Row(this, i)); | ||
| 1041 | } | ||
| 1042 | } | ||
| 1043 | |||
| 1044 | // Adjust the model index of rows after the effected region | ||
| 1045 | int viewIndex; | ||
| 1046 | for (i = modelToView.length - 1; i >= firstRow; i--) { | ||
| 1047 | viewIndex = modelToView[i]; | ||
| 1048 | if (viewIndex != -1) { | ||
| 1049 | viewToModel[viewIndex].modelIndex += delta; | ||
| 1050 | } | ||
| 1051 | } | ||
| 1052 | |||
| 1053 | // Insert newly added rows into viewToModel | ||
| 1054 | if (added.size() > 0) { | ||
| 1055 | Collections.sort(added); | ||
| 1056 | Row[] lastViewToModel = viewToModel; | ||
| 1057 | viewToModel = new Row[viewToModel.length + added.size()]; | ||
| 1058 | insertInOrder(added, lastViewToModel); | ||
| 1059 | } | ||
| 1060 | |||
| 1061 | // Update modelToView | ||
| 1062 | createModelToView(getModelWrapper().getRowCount()); | ||
| 1063 | setModelToViewFromViewToModel(true); | ||
| 1064 | |||
| 1065 | // Notify of change | ||
| 1066 | fireRowSorterChanged(oldViewToModel); | ||
| 1067 | } | ||
| 1068 | |||
| 1069 | private void rowsDeleted0(int firstRow, int lastRow) { | ||
| 1070 | int[] oldViewToModel = getViewToModelAsInts(viewToModel); | ||
| 1071 | int removedFromView = 0; | ||
| 1072 | int i; | ||
| 1073 | int viewIndex; | ||
| 1074 | |||
| 1075 | // Figure out how many visible rows are going to be effected. | ||
| 1076 | for (i = firstRow; i <= lastRow; i++) { | ||
| 1077 | viewIndex = modelToView[i]; | ||
| 1078 | if (viewIndex != -1) { | ||
| 1079 | removedFromView++; | ||
| 1080 | viewToModel[viewIndex] = null; | ||
| 1081 | } | ||
| 1082 | } | ||
| 1083 | |||
| 1084 | // Update the model index of rows after the effected region | ||
| 1085 | int delta = lastRow - firstRow + 1; | ||
| 1086 | for (i = modelToView.length - 1; i > lastRow; i--) { | ||
| 1087 | viewIndex = modelToView[i]; | ||
| 1088 | if (viewIndex != -1) { | ||
| 1089 | viewToModel[viewIndex].modelIndex -= delta; | ||
| 1090 | } | ||
| 1091 | } | ||
| 1092 | |||
| 1093 | // Then patch up the viewToModel array | ||
| 1094 | if (removedFromView > 0) { | ||
| 1095 | Row[] newViewToModel = new Row[viewToModel.length - | ||
| 1096 | removedFromView]; | ||
| 1097 | int newIndex = 0; | ||
| 1098 | int last = 0; | ||
| 1099 | for (i = 0; i < viewToModel.length; i++) { | ||
| 1100 | if (viewToModel[i] == null) { | ||
| 1101 | System.arraycopy(viewToModel, last, | ||
| 1102 | newViewToModel, newIndex, i - last); | ||
| 1103 | newIndex += (i - last); | ||
| 1104 | last = i + 1; | ||
| 1105 | } | ||
| 1106 | } | ||
| 1107 | System.arraycopy(viewToModel, last, | ||
| 1108 | newViewToModel, newIndex, viewToModel.length - last); | ||
| 1109 | viewToModel = newViewToModel; | ||
| 1110 | } | ||
| 1111 | |||
| 1112 | // Update the modelToView mapping | ||
| 1113 | createModelToView(getModelWrapper().getRowCount()); | ||
| 1114 | setModelToViewFromViewToModel(true); | ||
| 1115 | |||
| 1116 | // And notify of change | ||
| 1117 | fireRowSorterChanged(oldViewToModel); | ||
| 1118 | } | ||
| 1119 | |||
| 1120 | private void rowsUpdated0(int firstRow, int lastRow) { | ||
| 1121 | int[] oldViewToModel = getViewToModelAsInts(viewToModel); | ||
| 1122 | int i, j; | ||
| 1123 | int delta = lastRow - firstRow + 1; | ||
| 1124 | int modelIndex; | ||
| 1125 | int last; | ||
| 1126 | int index; | ||
| 1127 | |||
| 1128 | if (getRowFilter() == null) { | ||
| 1129 | // Sorting only: | ||
| 1130 | |||
| 1131 | // Remove the effected rows | ||
| 1132 | Row[] updated = new Row[delta]; | ||
| 1133 | for (j = 0, i = firstRow; i <= lastRow; i++, j++) { | ||
| 1134 | updated[j] = viewToModel[modelToView[i]]; | ||
| 1135 | } | ||
| 1136 | |||
| 1137 | // Sort the update rows | ||
| 1138 | Arrays.sort(updated); | ||
| 1139 | |||
| 1140 | // Build the intermediary array: the array of | ||
| 1141 | // viewToModel without the effected rows. | ||
| 1142 | Row[] intermediary = new Row[viewToModel.length - delta]; | ||
| 1143 | for (i = 0, j = 0; i < viewToModel.length; i++) { | ||
| 1144 | modelIndex = viewToModel[i].modelIndex; | ||
| 1145 | if (modelIndex < firstRow || modelIndex > lastRow) { | ||
| 1146 | intermediary[j++] = viewToModel[i]; | ||
| 1147 | } | ||
| 1148 | } | ||
| 1149 | |||
| 1150 | // Build the new viewToModel | ||
| 1151 | insertInOrder(Arrays.asList(updated), intermediary); | ||
| 1152 | |||
| 1153 | // Update modelToView | ||
| 1154 | setModelToViewFromViewToModel(false); | ||
| 1155 | } | ||
| 1156 | else { | ||
| 1157 | // Sorting & filtering. | ||
| 1158 | |||
| 1159 | // Remove the effected rows, adding them to updated and setting | ||
| 1160 | // modelToView to -2 for any rows that were not filtered out | ||
| 1161 | List<Row> updated = new ArrayList<Row>(delta); | ||
| 1162 | int newlyVisible = 0; | ||
| 1163 | int newlyHidden = 0; | ||
| 1164 | int effected = 0; | ||
| 1165 | for (i = firstRow; i <= lastRow; i++) { | ||
| 1166 | if (modelToView[i] == -1) { | ||
| 1167 | // This row was filtered out | ||
| 1168 | if (include(i)) { | ||
| 1169 | // No longer filtered | ||
| 1170 | updated.add(new Row(this, i)); | ||
| 1171 | newlyVisible++; | ||
| 1172 | } | ||
| 1173 | } | ||
| 1174 | else { | ||
| 1175 | // This row was visible, make sure it should still be | ||
| 1176 | // visible. | ||
| 1177 | if (!include(i)) { | ||
| 1178 | newlyHidden++; | ||
| 1179 | } | ||
| 1180 | else { | ||
| 1181 | updated.add(viewToModel[modelToView[i]]); | ||
| 1182 | } | ||
| 1183 | modelToView[i] = -2; | ||
| 1184 | effected++; | ||
| 1185 | } | ||
| 1186 | } | ||
| 1187 | |||
| 1188 | // Sort the updated rows | ||
| 1189 | Collections.sort(updated); | ||
| 1190 | |||
| 1191 | // Build the intermediary array: the array of | ||
| 1192 | // viewToModel without the updated rows. | ||
| 1193 | Row[] intermediary = new Row[viewToModel.length - effected]; | ||
| 1194 | for (i = 0, j = 0; i < viewToModel.length; i++) { | ||
| 1195 | modelIndex = viewToModel[i].modelIndex; | ||
| 1196 | if (modelToView[modelIndex] != -2) { | ||
| 1197 | intermediary[j++] = viewToModel[i]; | ||
| 1198 | } | ||
| 1199 | } | ||
| 1200 | |||
| 1201 | // Recreate viewToModel, if necessary | ||
| 1202 | if (newlyVisible != newlyHidden) { | ||
| 1203 | viewToModel = new Row[viewToModel.length + newlyVisible - | ||
| 1204 | newlyHidden]; | ||
| 1205 | } | ||
| 1206 | |||
| 1207 | // Rebuild the new viewToModel array | ||
| 1208 | insertInOrder(updated, intermediary); | ||
| 1209 | |||
| 1210 | // Update modelToView | ||
| 1211 | setModelToViewFromViewToModel(true); | ||
| 1212 | } | ||
| 1213 | // And finally fire a sort event. | ||
| 1214 | fireRowSorterChanged(oldViewToModel); | ||
| 1215 | } | ||
| 1216 | |||
| 1217 | private void checkColumn(int column) { | ||
| 1218 | if (column < 0 || column >= getModelWrapper().getColumnCount()) { | ||
| 1219 | throw new IndexOutOfBoundsException( | ||
| 1220 | "column beyond range of TableModel"); | ||
| 1221 | } | ||
| 1222 | } | ||
| 1223 | |||
| 1224 | |||
| 1225 | /** | ||
| 1226 | * <code>DefaultRowSorter.ModelWrapper</code> is responsible for providing | ||
| 1227 | * the data that gets sorted by <code>DefaultRowSorter</code>. You | ||
| 1228 | * normally do not interact directly with <code>ModelWrapper</code>. | ||
| 1229 | * Subclasses of <code>DefaultRowSorter</code> provide an | ||
| 1230 | * implementation of <code>ModelWrapper</code> wrapping another model. | ||
| 1231 | * For example, | ||
| 1232 | * <code>TableRowSorter</code> provides a <code>ModelWrapper</code> that | ||
| 1233 | * wraps a <code>TableModel</code>. | ||
| 1234 | * <p> | ||
| 1235 | * <code>ModelWrapper</code> makes a distinction between values as | ||
| 1236 | * <code>Object</code>s and <code>String</code>s. This allows | ||
| 1237 | * implementations to provide a custom string | ||
| 1238 | * converter to be used instead of invoking <code>toString</code> on the | ||
| 1239 | * object. | ||
| 1240 | * | ||
| 1241 | * @param <M> the type of the underlying model | ||
| 1242 | * @param <I> the identifier supplied to the filter | ||
| 1243 | * @since 1.6 | ||
| 1244 | * @see RowFilter | ||
| 1245 | * @see RowFilter.Entry | ||
| 1246 | */ | ||
| 1247 | protected abstract static class ModelWrapper<M,I> { | ||
| 1248 | /** | ||
| 1249 | * Creates a new <code>ModelWrapper</code>. | ||
| 1250 | */ | ||
| 1251 | protected ModelWrapper() { | ||
| 1252 | } | ||
| 1253 | |||
| 1254 | /** | ||
| 1255 | * Returns the underlying model that this <code>Model</code> is | ||
| 1256 | * wrapping. | ||
| 1257 | * | ||
| 1258 | * @return the underlying model | ||
| 1259 | */ | ||
| 1260 | public abstract M getModel(); | ||
| 1261 | |||
| 1262 | /** | ||
| 1263 | * Returns the number of columns in the model. | ||
| 1264 | * | ||
| 1265 | * @return the number of columns in the model | ||
| 1266 | */ | ||
| 1267 | public abstract int getColumnCount(); | ||
| 1268 | |||
| 1269 | /** | ||
| 1270 | * Returns the number of rows in the model. | ||
| 1271 | * | ||
| 1272 | * @return the number of rows in the model | ||
| 1273 | */ | ||
| 1274 | public abstract int getRowCount(); | ||
| 1275 | |||
| 1276 | /** | ||
| 1277 | * Returns the value at the specified index. | ||
| 1278 | * | ||
| 1279 | * @param row the row index | ||
| 1280 | * @param column the column index | ||
| 1281 | * @return the value at the specified index | ||
| 1282 | * @throws IndexOutOfBoundsException if the indices are outside | ||
| 1283 | * the range of the model | ||
| 1284 | */ | ||
| 1285 | public abstract Object getValueAt(int row, int column); | ||
| 1286 | |||
| 1287 | /** | ||
| 1288 | * Returns the value as a <code>String</code> at the specified | ||
| 1289 | * index. This implementation uses <code>toString</code> on | ||
| 1290 | * the result from <code>getValueAt</code> (making sure | ||
| 1291 | * to return an empty string for null values). Subclasses that | ||
| 1292 | * override this method should never return null. | ||
| 1293 | * | ||
| 1294 | * @param row the row index | ||
| 1295 | * @param column the column index | ||
| 1296 | * @return the value at the specified index as a <code>String</code> | ||
| 1297 | * @throws IndexOutOfBoundsException if the indices are outside | ||
| 1298 | * the range of the model | ||
| 1299 | */ | ||
| 1300 | public String getStringValueAt(int row, int column) { | ||
| 1301 | Object o = getValueAt(row, column); | ||
| 1302 | if (o == null) { | ||
| 1303 | return ""; | ||
| 1304 | } | ||
| 1305 | String string = o.toString(); | ||
| 1306 | if (string == null) { | ||
| 1307 | return ""; | ||
| 1308 | } | ||
| 1309 | return string; | ||
| 1310 | } | ||
| 1311 | |||
| 1312 | /** | ||
| 1313 | * Returns the identifier for the specified row. The return value | ||
| 1314 | * of this is used as the identifier for the | ||
| 1315 | * <code>RowFilter.Entry</code> that is passed to the | ||
| 1316 | * <code>RowFilter</code>. | ||
| 1317 | * | ||
| 1318 | * @param row the row to return the identifier for, in terms of | ||
| 1319 | * the underlying model | ||
| 1320 | * @return the identifier | ||
| 1321 | * @see RowFilter.Entry#getIdentifier | ||
| 1322 | */ | ||
| 1323 | public abstract I getIdentifier(int row); | ||
| 1324 | } | ||
| 1325 | |||
| 1326 | |||
| 1327 | /** | ||
| 1328 | * RowFilter.Entry implementation that delegates to the ModelWrapper. | ||
| 1329 | * getFilterEntry(int) creates the single instance of this that is | ||
| 1330 | * passed to the Filter. Only call getFilterEntry(int) to get | ||
| 1331 | * the instance. | ||
| 1332 | */ | ||
| 1333 | private class FilterEntry extends RowFilter.Entry<M,I> { | ||
| 1334 | /** | ||
| 1335 | * The index into the model, set in getFilterEntry | ||
| 1336 | */ | ||
| 1337 | int modelIndex; | ||
| 1338 | |||
| 1339 | public M getModel() { | ||
| 1340 | return getModelWrapper().getModel(); | ||
| 1341 | } | ||
| 1342 | |||
| 1343 | public int getValueCount() { | ||
| 1344 | return getModelWrapper().getColumnCount(); | ||
| 1345 | } | ||
| 1346 | |||
| 1347 | public Object getValue(int index) { | ||
| 1348 | return getModelWrapper().getValueAt(modelIndex, index); | ||
| 1349 | } | ||
| 1350 | |||
| 1351 | public String getStringValue(int index) { | ||
| 1352 | return getModelWrapper().getStringValueAt(modelIndex, index); | ||
| 1353 | } | ||
| 1354 | |||
| 1355 | public I getIdentifier() { | ||
| 1356 | return getModelWrapper().getIdentifier(modelIndex); | ||
| 1357 | } | ||
| 1358 | } | ||
| 1359 | |||
| 1360 | |||
| 1361 | /** | ||
| 1362 | * Row is used to handle the actual sorting by way of Comparable. It | ||
| 1363 | * will use the sortKeys to do the actual comparison. | ||
| 1364 | */ | ||
| 1365 | // NOTE: this class is static so that it can be placed in an array | ||
| 1366 | private static class Row implements Comparable<Row> { | ||
| 1367 | private DefaultRowSorter sorter; | ||
| 1368 | int modelIndex; | ||
| 1369 | |||
| 1370 | public Row(DefaultRowSorter sorter, int index) { | ||
| 1371 | this.sorter = sorter; | ||
| 1372 | modelIndex = index; | ||
| 1373 | } | ||
| 1374 | |||
| 1375 | public int compareTo(Row o) { | ||
| 1376 | return sorter.compare(modelIndex, o.modelIndex); | ||
| 1377 | } | ||
| 1378 | } | ||
| 1379 | } | ||