Home » openjdk-7 » javax » swing » [javadoc | source]

    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> &lt; 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   }

Home » openjdk-7 » javax » swing » [javadoc | source]