001 /* 002 * Copyright 2002-2008 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 005 * use this file except in compliance with the License. You may obtain a copy of 006 * the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 012 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 013 * License for the specific language governing permissions and limitations under 014 * the License. 015 */ 016 package org.springframework.richclient.table; 017 018 import java.awt.EventQueue; 019 import java.util.ArrayList; 020 import java.util.Arrays; 021 import java.util.Collections; 022 import java.util.Comparator; 023 import java.util.List; 024 import java.util.Map; 025 026 import javax.swing.SwingUtilities; 027 import javax.swing.event.TableModelEvent; 028 import javax.swing.table.TableModel; 029 030 import org.springframework.core.style.StylerUtils; 031 import org.springframework.util.Assert; 032 import org.springframework.util.comparator.NullSafeComparator; 033 034 /** 035 * A sorter for TableModels. The sorter has a model (conforming to TableModel) 036 * and itself implements TableModel. TableSorter does not store or copy the 037 * model in the TableModel, instead it maintains an array of integers which it 038 * keeps the same size as the number of rows in its model. When the model 039 * changes it notifies the sorter that something has changed eg. "rowsAdded" so 040 * that its internal array of integers can be reallocated. As requests are made 041 * of the sorter (like getValueAt(row, col) it redirects them to its model via 042 * the mapping array. That way the TableSorter appears to hold another copy of 043 * the table with the rows in a different order. The sorting algorthm used is 044 * stable which means that it does not move around rows when its comparison 045 * function returns 0 to denote that they are equivalent. 046 */ 047 public class ShuttleSortableTableModel extends AbstractTableModelFilter implements SortableTableModel { 048 private static final Comparator OBJECT_COMPARATOR = new NullSafeComparator(ToStringComparator.INSTANCE, true); 049 050 private static final Comparator COMPARABLE_COMPARATOR = NullSafeComparator.NULLS_LOW; 051 052 private Comparator[] columnComparators; 053 054 private List columnsToSort = new ArrayList(4); 055 056 private int[] indexes; 057 058 private int compares; 059 060 private boolean autoSortEnabled = true; 061 062 private Runnable notifyTableRunnable = new Runnable() { 063 public void run() { 064 fireTableDataChanged(); 065 } 066 }; 067 068 public ShuttleSortableTableModel(TableModel model) { 069 super(model); 070 allocateIndexes(); 071 resetComparators(); 072 } 073 074 private void allocateIndexes() { 075 int rowCount = filteredModel.getRowCount(); 076 // Set up a new array of indexes with the right number of elements 077 // for the new model model. 078 indexes = new int[rowCount]; 079 // Initialise with the identity mapping. 080 for (int row = 0; row < rowCount; row++) { 081 indexes[row] = row; 082 } 083 } 084 085 public void resetComparators() { 086 resetComparators(Collections.EMPTY_MAP); 087 } 088 089 /** 090 * Reset the <code>columnComparartos</code>.<br> 091 * Useful when the columns of the model were changed, and by consequence 092 * their comparators. 093 * @param comparators - map with comparators where the key is the column of 094 * the comparator. 095 */ 096 public void resetComparators(Map comparators) { 097 int colCount = filteredModel.getColumnCount(); 098 columnComparators = new Comparator[colCount]; 099 100 Comparator newComparator; 101 Class clazz; 102 for (int i = 0; i < columnComparators.length; i++) { 103 newComparator = (Comparator) comparators.get(Integer.valueOf(i)); 104 if (newComparator != null) 105 setComparator(i, newComparator); 106 else { 107 clazz = filteredModel.getColumnClass(i); 108 if (clazz == Object.class || !Comparable.class.isAssignableFrom(clazz)) 109 columnComparators[i] = OBJECT_COMPARATOR; 110 } 111 } 112 } 113 114 public boolean isCellEditable(int row, int column) { 115 return filteredModel.isCellEditable(indexes[row], column); 116 } 117 118 public boolean isAutoSortEnabled() { 119 return autoSortEnabled; 120 } 121 122 public void setAutoSortEnabled(boolean autoSortEnabled) { 123 this.autoSortEnabled = autoSortEnabled; 124 } 125 126 public Comparator getComparator(int columnIndex) { 127 return this.columnComparators[columnIndex]; 128 } 129 130 public void setComparator(int columnIndex, Comparator comparator) { 131 Assert.notNull(comparator); 132 this.columnComparators[columnIndex] = comparator; 133 } 134 135 // The mapping only affects the contents of the model rows. 136 // Pass all requests to these rows through the mapping array: "indexes". 137 public Object getValueAt(int row, int column) { 138 return filteredModel.getValueAt(indexes[row], column); 139 } 140 141 public void setValueAt(Object value, int row, int column) { 142 filteredModel.setValueAt(value, indexes[row], column); 143 } 144 145 public void sortByColumn(ColumnToSort columnToSort) { 146 columnsToSort.clear(); 147 columnsToSort.add(columnToSort); 148 sort(); 149 SwingUtilities.invokeLater(notifyTableRunnable); 150 } 151 152 public void sortByColumns(ColumnToSort[] columnsToSort) { 153 this.columnsToSort = Arrays.asList(columnsToSort); 154 sort(); 155 notifyTableChanged(); 156 } 157 158 public int[] sortByColumns(ColumnToSort[] columnsToSort, int[] preSortSelectedRows) { 159 int[] modelIndexes = new int[preSortSelectedRows.length]; 160 if (logger.isDebugEnabled()) { 161 logger.debug("Selected row indexes before sort" + StylerUtils.style(preSortSelectedRows)); 162 } 163 for (int i = 0; i < preSortSelectedRows.length; i++) { 164 modelIndexes[i] = convertSortedIndexToDataIndex(preSortSelectedRows[i]); 165 } 166 this.columnsToSort = Arrays.asList(columnsToSort); 167 sort(); 168 int[] postSortSelectedRows = new int[modelIndexes.length]; 169 for (int i = 0; i < modelIndexes.length; i++) { 170 postSortSelectedRows[i] = convertModelToRowIndex(modelIndexes[i]); 171 } 172 if (logger.isDebugEnabled()) { 173 logger.debug("Selected row indexes after sort" + StylerUtils.style(postSortSelectedRows)); 174 } 175 notifyTableChanged(); 176 return postSortSelectedRows; 177 } 178 179 protected void notifyTableChanged() { 180 if (!EventQueue.isDispatchThread()) { 181 SwingUtilities.invokeLater(notifyTableRunnable); 182 } 183 else { 184 notifyTableRunnable.run(); 185 } 186 } 187 188 public int convertSortedIndexToDataIndex(int index) { 189 return indexes[index]; 190 } 191 192 public int[] convertSortedIndexesToDataIndexes(int[] indexes) { 193 int[] converted = new int[indexes.length]; 194 for (int i = 0; i < indexes.length; i++) { 195 converted[i] = convertSortedIndexToDataIndex(indexes[i]); 196 } 197 return converted; 198 } 199 200 /* this linear search is a bit slow -- we'll need to optimize later */ 201 public int convertModelToRowIndex(int index) { 202 for (int i = 0; i < indexes.length; i++) { 203 if (index == indexes[i]) { 204 return i; 205 } 206 } 207 return 0; 208 } 209 210 public int[] convertDataIndexesToSortedIndexes(int[] indexes) { 211 int[] converted = new int[indexes.length]; 212 for (int i = 0; i < indexes.length; i++) { 213 converted[i] = convertModelToRowIndex(indexes[i]); 214 } 215 return converted; 216 } 217 218 private void sort() { 219 if (columnsToSort.size() > 0) { 220 checkModel(); 221 compares = 0; 222 doShuttleSort((int[]) indexes.clone(), indexes, 0, indexes.length); 223 } 224 } 225 226 private void checkModel() { 227 if (indexes.length != filteredModel.getRowCount()) { 228 throw new IllegalStateException("Sorter not informed of a change in model."); 229 } 230 } 231 232 // This is a home-grown implementation which we have not had time 233 // to research - it may perform poorly in some circumstances. It 234 // requires twice the space of an in-place algorithm and makes 235 // NlogN assigments shuttling the values between the two 236 // arrays. The number of compares appears to vary between N-1 and 237 // NlogN depending on the initial order but the main reason for 238 // using it here is that, unlike qsort, it is stable. 239 private void doShuttleSort(int from[], int to[], int low, int high) { 240 if (high - low < 2) { 241 return; 242 } 243 int middle = (low + high) / 2; 244 doShuttleSort(to, from, low, middle); 245 doShuttleSort(to, from, middle, high); 246 247 int p = low; 248 int q = middle; 249 250 /* 251 * This is an optional short-cut; at each recursive call, check to see 252 * if the elements in this subset are already ordered. If so, no further 253 * comparisons are needed; the sub-array can just be copied. The array 254 * must be copied rather than assigned otherwise sister calls in the 255 * recursion might get out of sinc. When the number of elements is three 256 * they are partitioned so that the first set, [low, mid), has one 257 * element and and the second, [mid, high), has two. We skip the 258 * optimisation when the number of elements is three or less as the 259 * first compare in the normal merge will produce the same sequence of 260 * steps. This optimisation seems to be worthwhile for partially ordered 261 * lists but some analysis is needed to find out how the performance 262 * drops to Nlog(N) as the initial 263 */ 264 if (high - low >= 4 && compare(from[middle - 1], from[middle]) <= 0) { 265 for (int i = low; i < high; i++) { 266 to[i] = from[i]; 267 } 268 return; 269 } 270 271 // A normal merge. 272 for (int i = low; i < high; i++) { 273 if (q >= high || (p < middle && compare(from[p], from[q]) <= 0)) { 274 to[i] = from[p++]; 275 } 276 else { 277 to[i] = from[q++]; 278 } 279 } 280 } 281 282 public int compare(int row1, int row2) { 283 compares++; 284 for (int level = 0; level < columnsToSort.size(); level++) { 285 ColumnToSort column = (ColumnToSort) columnsToSort.get(level); 286 int result = compareRowsByColumn(row1, row2, column.getColumnIndex()); 287 if (result != 0) { 288 return column.getSortOrder() == SortOrder.ASCENDING ? result : -result; 289 } 290 } 291 return 0; 292 } 293 294 private int compareRowsByColumn(int row1, int row2, int column) { 295 Object o1 = filteredModel.getValueAt(row1, column); 296 Object o2 = filteredModel.getValueAt(row2, column); 297 298 Comparator comparator = columnComparators[column]; 299 if (comparator != null) { 300 return comparator.compare(o1, o2); 301 } 302 303 return COMPARABLE_COMPARATOR.compare(o1, o2); 304 } 305 306 public void tableChanged(final TableModelEvent e) { 307 if (e.getType() == TableModelEvent.INSERT) { 308 if (autoSortEnabled) { 309 reallocateIndexesOnInsert(e.getFirstRow(), e.getLastRow()); 310 sort(); 311 final int[] insertedRows = new int[e.getLastRow() - e.getFirstRow() + 1]; 312 int row = e.getFirstRow(); 313 for (int i = 0; i < insertedRows.length; i++) { 314 insertedRows[i] = convertModelToRowIndex(row++); 315 } 316 for (int i = 0; i < insertedRows.length; i++) { 317 fireTableRowsInserted(insertedRows[i], insertedRows[i]); 318 } 319 } 320 else { 321 reallocateIndexesOnInsert(e.getFirstRow(), e.getLastRow()); 322 super.tableChanged(e); 323 } 324 } 325 else if (e.getType() == TableModelEvent.DELETE) { 326 final int[] deletedRows = new int[e.getLastRow() - e.getFirstRow() + 1]; 327 int row = e.getFirstRow(); 328 for (int i = 0; i < deletedRows.length; i++) { 329 deletedRows[i] = convertModelToRowIndex(row); 330 row++; 331 } 332 allocateIndexes(); 333 for (int i = 0; i < deletedRows.length; i++) { 334 fireTableRowsDeleted(deletedRows[i], deletedRows[i]); 335 } 336 sort(); 337 } 338 else if (e.getType() == TableModelEvent.UPDATE) { 339 allocateIndexes(); 340 sort(); 341 fireTableDataChanged(); 342 } 343 else { 344 logger.warn("Doing an unknown table change type: " + e.getType()); 345 allocateIndexes(); 346 sort(); 347 super.tableChanged(e); 348 } 349 } 350 351 private void reallocateIndexesOnInsert(int firstRow, int lastRow) { 352 int rowCount = filteredModel.getRowCount(); 353 int[] newIndexes = new int[rowCount]; 354 for (int row = 0; row < indexes.length; row++) { 355 newIndexes[row] = indexes[row]; 356 } 357 for (int row = firstRow; row <= lastRow; row++) { 358 newIndexes[row] = row; 359 } 360 indexes = newIndexes; 361 } 362 363 }