1 /***
2 * AbstractSortableTableModel.java
3 *
4 * This file is part of the creme library.
5 * The creme library intends to ease the development effort of large
6 * applications by providing easy to use support classes.
7 *
8 * Copyright (C) 2002 Denis Bregeon
9 *
10 * This library is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU Lesser General Public
12 * License as published by the Free Software Foundation; either
13 * version 2.1 of the License, or (at your option) any later version.
14 *
15 * This library is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * Lesser General Public License for more details.
19 *
20 * You should have received a copy of the GNU Lesser General Public
21 * License along with this library; if not, write to the Free Software
22 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23 *
24 * contact information: dbregeon@sourceforge.net
25 */
26 package org.jcreme.swing.table;
27
28 import java.util.Arrays;
29 import java.util.Comparator;
30 import java.util.Vector;
31
32 import javax.swing.table.AbstractTableModel;
33 import javax.swing.table.TableModel;
34
35 import org.jcreme.enumerations.SortOrder;
36
37 /***
38 * This class is an abstract implementation of the SortableTableModel interface.
39 * Some of the features are inspired from the Java Tutorial TableSorter.java
40 * sample. The concrete sub classes of this class can implement either of two
41 * schemes: the data manipulator scheme described in the tutorial, the usual
42 * scheme to create a direct subclass of this one. This class does not specify
43 * how the resort is handled. It could be through the TableChangeEvents or
44 * through any other scheme.
45 *
46 * @author $Author: dbregeon $
47 * @version $Revision: 1.1 $
48 */
49 public abstract class AbstractSortableTableModel extends AbstractTableModel
50 implements SortableTableModel {
51 /***
52 * The index in the original model.
53 */
54 private int[] sortedIndexToOriginalIndex = new int[0];
55
56 /***
57 * The columns that provide the sort feature.
58 */
59 private transient SortableTableColumn[] sortingColumns = null;
60
61 /***
62 * The comparator used to sort the columns into their order in sort.
63 */
64 private final Comparator sortableColumnComparator = new Comparator() {
65 public boolean equals(Object o) {
66 return (o == this);
67 }
68
69 public int compare(Object o1, Object o2) {
70 int result = 0;
71 if ((o1 instanceof SortableTableColumn)
72 && (o2 instanceof SortableTableColumn)) {
73 SortableTableColumn column1 = (SortableTableColumn) o1;
74 SortableTableColumn column2 = (SortableTableColumn) o2;
75 if (column1.getOrderInSort() == null) {
76 result = (column2.getOrderInSort() == null) ? 0 : -1;
77 } else if (column2.getOrderInSort() == null) {
78 result = 1;
79 } else {
80 result = ((SortableTableColumn) o1)
81 .getOrderInSort()
82 .compareTo(
83 ((SortableTableColumn) o2).getOrderInSort());
84 }
85 }
86 return result;
87 }
88 };
89
90 /***
91 * @see TableModel#getValueAt(int, int)
92 */
93 public Object getValueAt(int row, int column) {
94 return getOriginalValueAt(getOriginalRow(row), column);
95 }
96
97 /***
98 * @see TableModel#setValueAt(java.lang.Object, int, int)
99 */
100 public void setValueAt(Object value, int row, int column) {
101 setOriginalValueAt(value, getOriginalRow(row), column);
102 }
103
104 /***
105 * Enables to find a row number in the original model.
106 *
107 * @param row
108 * the row as presented after sorting.
109 * @return the row in the original (unsorted) model that corresponds to the
110 * parameter row.
111 */
112 public synchronized int getOriginalRow(int row) {
113
114
115 int result = row;
116 if (this.sortedIndexToOriginalIndex.length == getRowCount()) {
117 result = this.sortedIndexToOriginalIndex[row];
118 }
119 return result;
120 }
121
122 /***
123 * This method returns the value at the original (non sorted) row.
124 *
125 * @param row
126 * the row number in the original model.
127 * @param column
128 * the column in the model.
129 * @return the value at these coordinates in the original model.
130 */
131 protected abstract Object getOriginalValueAt(int row, int column);
132
133 /***
134 * This method enables to change the value at the original (non sorted) row.
135 *
136 * @param value
137 * the value to set these coordinates in the original model.
138 * @param row
139 * the row number in the original model.
140 * @param column
141 * the column in the model.
142 */
143 protected abstract void setOriginalValueAt(Object value, int row, int column);
144
145 /***
146 * Sorts the TableModel using the columns given in the columns parameter in
147 * the order given in columns. It also fires an event to signal the change
148 * to eventual listeners.
149 *
150 * @param columns
151 * the columns used to sort the table in the order they will be
152 * used.
153 */
154 public void sort(SortableTableColumn[] columns) {
155 synchronized (this) {
156 initConverterArray();
157 if (columns != null) {
158 this.sortingColumns = columns;
159 Arrays.sort(this.sortingColumns, this.sortableColumnComparator);
160 shuttleSort((int[]) this.sortedIndexToOriginalIndex.clone(),
161 this.sortedIndexToOriginalIndex, 0, getRowCount());
162 }
163 }
164 fireTableDataChanged();
165 }
166
167
168
169
170
171
172
173
174
175 protected void shuttleSort(int from[], int to[], int low, int high) {
176 if (high - low < 2) {
177 return;
178 }
179 int middle = (low + high) / 2;
180 shuttleSort(to, from, low, middle);
181 shuttleSort(to, from, middle, high);
182
183 int p = low;
184 int q = middle;
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202 if (high - low >= 4 && compare(from[middle - 1], from[middle]) <= 0) {
203 for (int i = low; i < high; i++) {
204 to[i] = from[i];
205 }
206 return;
207 }
208
209
210
211 for (int i = low; i < high; i++) {
212 if (q >= high || (p < middle && compare(from[p], from[q]) <= 0)) {
213 to[i] = from[p++];
214 } else {
215 to[i] = from[q++];
216 }
217 }
218 }
219
220 /***
221 * Compares two rows using the sortingColumns to determine which columns
222 * (and in which order) are to be used in the sort. Inspired From the Java
223 * Tutorial.
224 *
225 * @param row1
226 * the index of the first row.
227 * @param row2
228 * the index of the second row.
229 * @return the comparison result.
230 */
231 protected int compare(int row1, int row2) {
232 int result = 0;
233 for (int level = 0; level < this.sortingColumns.length; level++) {
234 result = compareRowsByColumn(row1, row2, level);
235 if (result != 0) {
236 break;
237 }
238 }
239 return result;
240 }
241
242 /***
243 * This method enables to compare the values of a column for two different
244 * rows.
245 *
246 * @param row1
247 * the reference row.
248 * @param row2
249 * the compared row.
250 * @param column
251 * the column of comparison.
252 * @return 0 if the values of the column for each row are equal. A negative
253 * number if the value in row2 is greater than the one in row1. A
254 * positive number if the value in row1 is greater than the value in
255 * row2.
256 */
257 public int compareRowsByColumn(int row1, int row2, int column) {
258 int result = 0;
259 if ((this.sortingColumns[column] != null)
260 && (this.sortingColumns[column].getOrder() != SortOrder.NONE)) {
261 int modelIndex = this.sortingColumns[column].getModelIndex();
262 Object o1 = getOriginalValueAt(row1, modelIndex);
263 Object o2 = getOriginalValueAt(row2, modelIndex);
264 Class type = getColumnClass(modelIndex);
265
266 if (this.sortingColumns[column].getComparator() != null) {
267 result = this.sortingColumns[column].getComparator()
268 .getComparator().compare(o1, o2);
269 } else if (o1 == null && o2 == null) {
270
271 result = 0;
272 } else if (o1 == null) {
273
274 result = -1;
275 } else if (o2 == null) {
276 result = 1;
277 } else if (Comparable.class.isAssignableFrom(type)) {
278
279 Comparable c1 = (Comparable) o1;
280 result = c1.compareTo(o2);
281 } else if (Number.class.isAssignableFrom(type)) {
282 double d1, d2;
283 d1 = ((Number) o1).doubleValue();
284 d2 = ((Number) o2).doubleValue();
285
286 if (d1 < d2) {
287 result = -1;
288 } else if (d1 > d2) {
289 result = 1;
290 } else {
291 result = 0;
292 }
293 } else if (Boolean.class.isAssignableFrom(type)) {
294 boolean b1 = ((Boolean) o1).booleanValue();
295 boolean b2 = ((Boolean) o2).booleanValue();
296
297 if (b1 == b2) {
298 result = 0;
299 } else if (b1) {
300
301 result = 1;
302 } else {
303 result = -1;
304 }
305 } else {
306 String s1 = o1.toString();
307 String s2 = o2.toString();
308 result = s1.compareTo(s2);
309 }
310 result = this.sortingColumns[column].getOrder().getOrder() * result;
311 }
312 return result;
313 }
314
315 /***
316 * This method enables to ensure that the conversion array is big enough.
317 */
318 protected synchronized void initConverterArray() {
319 if (this.sortedIndexToOriginalIndex.length != getRowCount()) {
320 this.sortedIndexToOriginalIndex = new int[getRowCount()];
321 for (int i = 0; i < this.sortedIndexToOriginalIndex.length; i++) {
322 this.sortedIndexToOriginalIndex[i] = i;
323 }
324 }
325 }
326
327 /***
328 * This method enables to transform an interval of rows in the original data
329 * into a set of intervals in the sorted data.
330 *
331 * @param originalStart
332 * the row number of the start of the interval.
333 * @param originalEnd
334 * the row number of the end of the interval.
335 * @return an array of intervals (size nx2).
336 */
337 protected int[][] originalIntervalToSortedIntervals(int originalStart,
338 int originalEnd) {
339 Vector result = new Vector();
340
341
342 int[] originalIndexToSortedIndex = null;
343 synchronized (this) {
344 originalIndexToSortedIndex = new int[this.sortedIndexToOriginalIndex.length];
345 for (int i = 0; i < this.sortedIndexToOriginalIndex.length; i++) {
346 originalIndexToSortedIndex[this.sortedIndexToOriginalIndex[i]] = i;
347 }
348 }
349
350
351 int[] values = new int[originalEnd - originalStart + 1];
352 for (int i = 0; i < values.length; i++) {
353 values[i] = originalIndexToSortedIndex[originalStart + i];
354 }
355 java.util.Arrays.sort(values);
356
357 int start = 0, end = 1;
358 int elem = values[0] + 1;
359 int maxIndex = values.length;
360 while (start < maxIndex) {
361 while ((end < maxIndex) && (values[end] == elem)) {
362 end++;
363 elem++;
364 }
365 if (end < maxIndex)
366 elem = values[end];
367 result.add(new int[] { values[start], values[end - 1] });
368 start = end;
369 }
370 return (int[][]) result.toArray(new int[result.size()][2]);
371 }
372
373 /***
374 * @see javax.swing.table.TableModel#isCellEditable(int, int)
375 */
376 public boolean isCellEditable(int rowIndex, int columnIndex) {
377 return isOriginalCellEditable(getOriginalRow(rowIndex), columnIndex);
378 }
379
380 /***
381 * This method enables to determine if the value at the original (non
382 * sorted) row can be modified.
383 *
384 * @param row
385 * the row number in the original model.
386 * @param column
387 * the column in the model.
388 * @return true if the cell at the original coordinates is editable.
389 */
390 protected abstract boolean isOriginalCellEditable(int row, int column);
391
392 /***
393 * Provides access to the columns used for sorting.
394 *
395 * @return the sortingColumns.
396 */
397 protected SortableTableColumn[] getSortingColumns() {
398 return this.sortingColumns;
399 }
400
401 }