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} that provides sorting 35 * and filtering using a {@code TableModel}. 36 * The following example shows adding sorting to a {@code JTable}: 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}'s row-based methods and {@code JTable}'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} 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} 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} will return 66 * -1 for locations that are not visible in the view. 67 * <p> 68 * {@code TableRowSorter} uses {@code Comparator}s for doing 69 * comparisons. The following defines how a {@code Comparator} is 70 * chosen for a column: 71 * <ol> 72 * <li>If a {@code Comparator} has been specified for the column by the 73 * {@code setComparator} method, use it. 74 * <li>If the column class as returned by {@code getColumnClass} is 75 * {@code String}, use the {@code Comparator} returned by 76 * {@code Collator.getInstance()}. 77 * <li>If the column class implements {@code Comparable}, use a 78 * {@code Comparator} that invokes the {@code compareTo} 79 * method. 80 * <li>If a {@code TableStringConverter} has been specified, use it 81 * to convert the values to {@code String}s and then use the 82 * {@code Comparator} returned by {@code Collator.getInstance()}. 83 * <li>Otherwise use the {@code Comparator} returned by 84 * {@code Collator.getInstance()} on the results from 85 * calling {@code toString} on the objects. 86 * </ol> 87 * <p> 88 * In addition to sorting {@code TableRowSorter} provides the ability 89 * to filter. A filter is specified using the {@code setFilter} 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} method is invoked) the following 102 * are reset to their default values: {@code Comparator}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} 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} for an example 111 * of this. 112 * <p> 113 * <b>WARNING:</b> {@code DefaultTableModel} returns a column 114 * class of {@code Object}. As such all comparisons will 115 * be done using {@code toString}. This may be unnecessarily 116 * expensive. If the column only contains one type of value, such as 117 * an {@code Integer}, you should override {@code getColumnClass} and 118 * return the appropriate {@code Class}. 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} 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} with an empty model. 150 */ 151 public TableRowSorter() { 152 this(null); 153 } 154 155 /** 156 * Creates a {@code TableRowSorter} using {@code model} 157 * as the underlying {@code TableModel}. 158 * 159 * @param model the underlying {@code TableModel} to use, 160 * {@code null} is treated as an empty model 161 */ 162 public TableRowSorter(M model) { 163 setModel(model); 164 } 165 166 /** 167 * Sets the {@code TableModel} to use as the underlying model 168 * for this {@code TableRowSorter}. A value of {@code null} 169 * can be used to set an empty model. 170 * 171 * @param model the underlying model to use, or {@code null} 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} this 181 * is used to convert any object values, that do not have a 182 * registered {@code Comparator}, 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} for the specified 203 * column. If a {@code Comparator} has not been specified using 204 * the {@code setComparator} method a {@code Comparator} 205 * will be returned based on the column class 206 * ({@code TableModel.getColumnClass}) of the specified column. 207 * If the column class is {@code String}, 208 * {@code Collator.getInstance} is returned. If the 209 * column class implements {@code Comparable} a private 210 * {@code Comparator} is returned that invokes the 211 * {@code compareTo} method. Otherwise 212 * {@code Collator.getInstance} 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 } --- EOF ---