View Javadoc

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         // If the model has not been sorted since the last update.
114         // Do as if it was not sorted to avoid errors.
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     // Taken From the Java Tutorial.
168     // This is a home-grown implementation which we have not had time
169     // to research - it may perform poorly in some circumstances. It
170     // requires twice the space of an in-place algorithm and makes
171     // NlogN assigments shuttling the values between the two
172     // arrays. The number of compares appears to vary between N-1 and
173     // NlogN depending on the initial order but the main reason for
174     // using it here is that, unlike qsort, it is stable.
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          * This is an optional short-cut; at each recursive call, check to see
188          * if the elements in this subset are already ordered. If so, no further
189          * comparisons are needed; the sub-array can just be copied. The array
190          * must be copied rather than assigned otherwise sister calls in the
191          * recursion might get out of sinc. When the number of elements is three
192          * they are partitioned so that the first set, [low, mid), has one
193          * element and and the second, [mid, high), has two. We skip the
194          * optimisation when the number of elements is three or less as the
195          * first compare in the normal merge will produce the same sequence of
196          * steps. This optimisation seems to be worthwhile for partially ordered
197          * lists but some analysis is needed to find out how the performance
198          * drops to Nlog(N) as the initial order diminishes - it may drop very
199          * quickly.
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         // A normal merge.
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                 // both are equal to null.
271                 result = 0;
272             } else if (o1 == null) {
273                 // Define null less than everything.
274                 result = -1;
275             } else if (o2 == null) {
276                 result = 1;
277             } else if (Comparable.class.isAssignableFrom(type)) {
278                 // column is comparable, use that property.
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                     // Define false < true
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         // First convert original interval to discrete sorted indices.
341         // Build array to convert original indices to sorted indices
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         //Then build array of discrete values corresponding to the parameter
350         // interval
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         // Then convert discrete sorted indices to intervals.
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 }