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.table; 26 27 import java.text.Collator; 28 import java.util.*; 29 import javax.swing.DefaultRowSorter; 30 import javax.swing.RowFilter; 31 import javax.swing.SortOrder; 32 33 /** 34 * An implementation of <code>RowSorter</code> that provides sorting 35 * and filtering using a <code>TableModel</code>. 36 * The following example shows adding sorting to a <code>JTable</code>: 37 * <pre> 38 * TableModel myModel = createMyTableModel(); 39 * JTable table = new JTable(myModel); 40 * table.setRowSorter(new TableRowSorter(myModel)); 41 * </pre> 42 * This will do all the wiring such that when the user does the appropriate 43 * gesture, such as clicking on the column header, the table will 44 * visually sort. 45 * <p> 46 * <code>JTable</code>'s row-based methods and <code>JTable</code>'s 47 * selection model refer to the view and not the underlying 48 * model. Therefore, it is necessary to convert between the two. For 49 * example, to get the selection in terms of <code>myModel</code> 50 * you need to convert the indices: 51 * <pre> 52 * int[] selection = table.getSelectedRows(); 53 * for (int i = 0; i < selection.length; i++) { 54 * selection[i] = table.convertRowIndexToModel(selection[i]); 55 * } 56 * </pre> 57 * Similarly to select a row in <code>JTable</code> based on 58 * a coordinate from the underlying model do the inverse: 59 * <pre> 60 * table.setRowSelectionInterval(table.convertRowIndexToView(row), 61 * table.convertRowIndexToView(row)); 62 * </pre> 63 * <p> 64 * The previous example assumes you have not enabled filtering. If you 65 * have enabled filtering <code>convertRowIndexToView</code> will return 66 * -1 for locations that are not visible in the view. 67 * <p> 68 * <code>TableRowSorter</code> uses <code>Comparator</code>s for doing 69 * comparisons. The following defines how a <code>Comparator</code> is 70 * chosen for a column: 71 * <ol> 72 * <li>If a <code>Comparator</code> has been specified for the column by the 73 * <code>setComparator</code> method, use it. 74 * <li>If the column class as returned by <code>getColumnClass</code> is 75 * <code>String</code>, use the <code>Comparator</code> returned by 76 * <code>Collator.getInstance()</code>. 77 * <li>If the column class implements <code>Comparable</code>, use a 78 * <code>Comparator</code> that invokes the <code>compareTo</code> 79 * method. 80 * <li>If a <code>TableStringConverter</code> has been specified, use it 81 * to convert the values to <code>String</code>s and then use the 82 * <code>Comparator</code> returned by <code>Collator.getInstance()</code>. 83 * <li>Otherwise use the <code>Comparator</code> returned by 84 * <code>Collator.getInstance()</code> on the results from 85 * calling <code>toString</code> on the objects. 86 * </ol> 87 * <p> 88 * In addition to sorting <code>TableRowSorter</code> provides the ability 89 * to filter. A filter is specified using the <code>setFilter</code> 90 * method. The following example will only show rows containing the string 91 * "foo": 92 * <pre> 93 * TableModel myModel = createMyTableModel(); 94 * TableRowSorter sorter = new TableRowSorter(myModel); 95 * sorter.setRowFilter(RowFilter.regexFilter(".*foo.*")); 96 * JTable table = new JTable(myModel); 97 * table.setRowSorter(sorter); 98 * </pre> 99 * <p> 100 * If the underlying model structure changes (the 101 * <code>modelStructureChanged</code> method is invoked) the following 102 * are reset to their default values: <code>Comparator</code>s by 103 * column, current sort order, and whether each column is sortable. The default 104 * sort order is natural (the same as the model), and columns are 105 * sortable by default. 106 * <p> 107 * <code>TableRowSorter</code> has one formal type parameter: the type 108 * of the model. Passing in a type that corresponds exactly to your 109 * model allows you to filter based on your model without casting. 110 * Refer to the documentation of <code>RowFilter</code> for an example 111 * of this. 112 * <p> 113 * <b>WARNING:</b> <code>DefaultTableModel</code> returns a column 114 * class of <code>Object</code>. As such all comparisons will 115 * be done using <code>toString</code>. This may be unnecessarily 116 * expensive. If the column only contains one type of value, such as 117 * an <code>Integer</code>, you should override <code>getColumnClass</code> and 118 * return the appropriate <code>Class</code>. This will dramatically 119 * increase the performance of this class. 120 * 121 * @param <M> the type of the model, which must be an implementation of 122 * <code>TableModel</code> 123 * @see javax.swing.JTable 124 * @see javax.swing.RowFilter 125 * @see javax.swing.table.DefaultTableModel 126 * @see java.text.Collator 127 * @see java.util.Comparator 128 * @since 1.6 129 */ 130 public class TableRowSorter<M extends TableModel> extends DefaultRowSorter<M, Integer> { 131 /** 132 * Comparator that uses compareTo on the contents. 133 */ 134 private static final Comparator<?> COMPARABLE_COMPARATOR = 135 new ComparableComparator(); 136 137 /** 138 * Underlying model. 139 */ 140 private M tableModel; 141 142 /** 143 * For toString conversions. 144 */ 145 private TableStringConverter stringConverter; 146 147 148 /** 149 * Creates a <code>TableRowSorter</code> with an empty model. 150 */ 151 public TableRowSorter() { 152 this(null); 153 } 154 155 /** 156 * Creates a <code>TableRowSorter</code> using <code>model</code> 157 * as the underlying <code>TableModel</code>. 158 * 159 * @param model the underlying <code>TableModel</code> to use, 160 * <code>null</code> is treated as an empty model 161 */ 162 public TableRowSorter(M model) { 163 setModel(model); 164 } 165 166 /** 167 * Sets the <code>TableModel</code> to use as the underlying model 168 * for this <code>TableRowSorter</code>. A value of <code>null</code> 169 * can be used to set an empty model. 170 * 171 * @param model the underlying model to use, or <code>null</code> 172 */ 173 public void setModel(M model) { 174 tableModel = model; 175 setModelWrapper(new TableRowSorterModelWrapper()); 176 } 177 178 /** 179 * Sets the object responsible for converting values from the 180 * model to strings. If non-<code>null</code> this 181 * is used to convert any object values, that do not have a 182 * registered <code>Comparator</code>, to strings. 183 * 184 * @param stringConverter the object responsible for converting values 185 * from the model to strings 186 */ 187 public void setStringConverter(TableStringConverter stringConverter) { 188 this.stringConverter = stringConverter; 189 } 190 191 /** 192 * Returns the object responsible for converting values from the 193 * model to strings. 194 * 195 * @return object responsible for converting values to strings. 196 */ 197 public TableStringConverter getStringConverter() { 198 return stringConverter; 199 } 200 201 /** 202 * Returns the <code>Comparator</code> for the specified 203 * column. If a <code>Comparator</code> has not been specified using 204 * the <code>setComparator</code> method a <code>Comparator</code> 205 * will be returned based on the column class 206 * (<code>TableModel.getColumnClass</code>) of the specified column. 207 * If the column class is <code>String</code>, 208 * <code>Collator.getInstance</code> is returned. If the 209 * column class implements <code>Comparable</code> a private 210 * <code>Comparator</code> is returned that invokes the 211 * <code>compareTo</code> method. Otherwise 212 * <code>Collator.getInstance</code> is returned. 213 * 214 * @throws IndexOutOfBoundsException {@inheritDoc} 215 */ 216 public Comparator<?> getComparator(int column) { 217 Comparator<?> comparator = super.getComparator(column); 218 if (comparator != null) { 219 return comparator; 220 } 221 Class<?> columnClass = getModel().getColumnClass(column); 222 if (columnClass == String.class) { 223 return Collator.getInstance(); 224 } 225 if (Comparable.class.isAssignableFrom(columnClass)) { 226 return COMPARABLE_COMPARATOR; 227 } 228 return Collator.getInstance(); 229 } 230 231 /** 232 * {@inheritDoc} 233 * 234 * @throws IndexOutOfBoundsException {@inheritDoc} 235 */ 236 protected boolean useToString(int column) { 237 Comparator<?> comparator = super.getComparator(column); 238 if (comparator != null) { 239 return false; 240 } 241 Class<?> columnClass = getModel().getColumnClass(column); 242 if (columnClass == String.class) { 243 return false; 244 } 245 if (Comparable.class.isAssignableFrom(columnClass)) { 246 return false; 247 } 248 return true; 249 } 250 251 /** 252 * Implementation of DefaultRowSorter.ModelWrapper that delegates to a 253 * TableModel. 254 */ 255 private class TableRowSorterModelWrapper extends ModelWrapper<M,Integer> { 256 public M getModel() { 257 return tableModel; 258 } 259 260 public int getColumnCount() { 261 return (tableModel == null) ? 0 : tableModel.getColumnCount(); 262 } 263 264 public int getRowCount() { 265 return (tableModel == null) ? 0 : tableModel.getRowCount(); 266 } 267 268 public Object getValueAt(int row, int column) { 269 return tableModel.getValueAt(row, column); 270 } 271 272 public String getStringValueAt(int row, int column) { 273 TableStringConverter converter = getStringConverter(); 274 if (converter != null) { 275 // Use the converter 276 String value = converter.toString( 277 tableModel, row, column); 278 if (value != null) { 279 return value; 280 } 281 return ""; 282 } 283 284 // No converter, use getValueAt followed by toString 285 Object o = getValueAt(row, column); 286 if (o == null) { 287 return ""; 288 } 289 String string = o.toString(); 290 if (string == null) { 291 return ""; 292 } 293 return string; 294 } 295 296 public Integer getIdentifier(int index) { 297 return index; 298 } 299 } 300 301 302 private static class ComparableComparator implements Comparator<Object> { 303 @SuppressWarnings("unchecked") 304 public int compare(Object o1, Object o2) { 305 return ((Comparable)o1).compareTo(o2); 306 } 307 } 308 }