View Javadoc

1   /*
2    * @(#)TableSorter.java	1.8 99/04/23
3    *
4    * Copyright (c) 1997-1999 by Sun Microsystems, Inc. All Rights Reserved.
5    *
6    * Sun grants you ("Licensee") a non-exclusive, royalty free, license to use,
7    * modify and redistribute this software in source and binary code form,
8    * provided that i) this copyright notice and license appear on all copies of
9    * the software; and ii) Licensee does not utilize the software in a manner
10   * which is disparaging to Sun.
11   *
12   * This software is provided "AS IS," without a warranty of any kind. ALL
13   * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING ANY
14   * IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR
15   * NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS SHALL NOT BE
16   * LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING
17   * OR DISTRIBUTING THE SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS
18   * LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT,
19   * INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER
20   * CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF
21   * OR INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
22   * POSSIBILITY OF SUCH DAMAGES.
23   *
24   * This software is not designed or intended for use in on-line control of
25   * aircraft, air traffic, aircraft navigation or aircraft communications; or in
26   * the design, construction, operation or maintenance of any nuclear
27   * facility. Licensee represents and warrants that it will not use or
28   * redistribute the Software for such purposes.
29   */
30  
31  /***
32   * A sorter for TableModels. The sorter has a model (conforming to TableModel)
33   * and itself implements TableModel. TableSorter does not store or copy
34   * the data in the TableModel, instead it maintains an array of
35   * integers which it keeps the same size as the number of rows in its
36   * model. When the model changes it notifies the sorter that something
37   * has changed eg. "rowsAdded" so that its internal array of integers
38   * can be reallocated. As requests are made of the sorter (like
39   * getValueAt(row, col) it redirects them to its model via the mapping
40   * array. That way the TableSorter appears to hold another copy of the table
41   * with the rows in a different order. The sorting algorthm used is stable
42   * which means that it does not move around rows when its comparison
43   * function returns 0 to denote that they are equivalent.
44   *
45   * @version 1.8 04/23/99
46   * @author Philip Milne
47   */
48  package org.xnap.gui.table;
49  
50  import java.util.Vector;
51  
52  import javax.swing.event.TableModelEvent;
53  import javax.swing.table.AbstractTableModel;
54  
55  public abstract class AbstractSortableTableModel 
56      extends AbstractTableModel implements SortableModel
57  {
58  
59      //--- Data field(s) ---
60  
61      protected int indexes[] = new int[0];
62      protected int revIndexes[] = new int[0];
63      /***
64       * All columns to be sorted by.
65       */
66      protected Vector sortingColumns = new Vector();
67      protected boolean ascending = true;
68      /***
69       * Counts number of compares.
70       */
71      protected int compares;
72      protected int lastSortedColumn = -1;
73      protected boolean maintainSortOrder;
74  
75      //--- Constructor(s) ---
76  	
77      public AbstractSortableTableModel(boolean maintainSortOrder)
78      {
79  		this.maintainSortOrder = maintainSortOrder;
80      }
81      
82      public AbstractSortableTableModel()
83      {
84  		this(false);
85      }
86  
87      //--- Method(s) ---
88  
89      /***
90       * We need to mangle these events to fire the right indicies.
91       */
92      public void fireTableChanged(TableModelEvent e) 
93      {
94  		reallocateIndexes(e);
95  
96  		if (maintainSortOrder) {
97  			// this will reset the user selection
98  			if (sort()) {
99  				// sort has already notified the super class
100 				return;
101 			}
102 		}
103 
104 		if (e.getType() == TableModelEvent.DELETE
105 			|| (e.getType() == TableModelEvent.UPDATE && 
106 				e.getLastRow() >= revIndexes.length)) {
107 			// we don't know anymore which rows have been deleted
108 			super.fireTableChanged(new TableModelEvent(this));
109 		}
110 		else {
111 			for (int i = e.getFirstRow(); i <= e.getLastRow(); i++) {
112 				TableModelEvent t = new TableModelEvent
113 					(this, revIndexes[i], revIndexes[i], e.getColumn(),
114 					 e.getType());
115 				super.fireTableChanged(t);
116 			}
117 		}
118     }
119 
120     public abstract Object get(int aRow, int aColumn);
121 
122     public int getSortedColumn()
123     {
124 		return lastSortedColumn;
125     }
126 
127     /***
128      * The mapping only affects the contents of the data rows.
129      * Pass all requests to these rows through the mapping array: "indexes".
130      */
131     public Object getValueAt(int aRow, int aColumn) 
132     {
133         synchronized (indexes) {
134             return get(indexes[aRow], aColumn);
135         }
136     }
137 
138     public boolean isSortedAscending()
139     {
140 		return ascending;
141     }
142 
143     public boolean isCellEditable(int row, int column)
144     {
145 		return false;
146     }
147 
148     /***
149      * Returns the mapped row index.
150      *
151      * @deprecated
152      */
153     public int mapToDtmIndex(int i) 
154     {
155         return mapToIndex(i);
156     }
157 
158     /***
159      * Returns the mapped row index.
160      */
161     public int mapToIndex(int i) 
162     {
163         return indexes[i];
164     }
165 
166     public void set(Object aValue, int aRow, int aColumn)
167     {
168     }
169 
170     public void setMaintainSortOrder(boolean newValue)
171     {
172 		maintainSortOrder = newValue;
173     }
174 
175     public void setSortedAscending(boolean newValue)
176     {
177 		ascending = newValue;
178     }
179 
180     public void setValueAt(Object aValue, int aRow, int aColumn) 
181     {
182         synchronized (indexes) {
183             set(aValue, indexes[aRow], aColumn);
184         }
185     }
186 
187     /***
188      * Sorts the table.
189 	 *
190 	 * @return true, if table is sorted ascending; false, if descending
191      */
192     public boolean sortByColumn(int column, boolean ascending, boolean revert) 
193     {
194 		// add column
195 		if (column < 0 || column >= getColumnCount()) {
196 			throw new IndexOutOfBoundsException("Column is invalid");
197 		}
198 
199 		sortingColumns.clear();
200 		sortingColumns.add(new Integer(column));
201 
202 		this.ascending = ascending;
203 
204 		if (!sort() && revert) {
205 			this.ascending = !ascending;
206 			sort();
207 		}
208 		lastSortedColumn = column;
209 
210 		return ascending;
211     }
212 
213     public void resort()
214     {
215 		if (lastSortedColumn != -1) {
216 			sortByColumn(lastSortedColumn, ascending, false);
217 		}
218     }
219 
220     /***
221      * Compares two rows.
222      */
223     protected int compare(int row1, int row2) 
224     {
225         compares++;
226         for(int level = 0; level < sortingColumns.size(); level++) {
227             Integer column = (Integer)sortingColumns.elementAt(level);
228             int result = compareRowsByColumn(row1, row2, column.intValue());
229             if (result != 0)
230                 return ascending ? result : -result;
231         }
232         return 0;
233     }
234 
235     /***
236      * Compares two rows by a column.
237      */
238     protected int compareRowsByColumn(int row1, int row2, int column) 
239     {
240         Class type = getColumnClass(column);
241 
242         // check for nulls
243         Object o1 = get(row1, column);
244         Object o2 = get(row2, column);
245 
246         // If both values are null return 0
247 		// define: null is less than anything
248         if (o1 == null && o2 == null) {
249             return 0;
250         } else if (o1 == null) { 
251             return -1;
252         } else if (o2 == null) {
253             return 1;
254         }
255 
256         /* We copy all returned values from the getValue call in case
257 		   an optimised model is reusing one object to return many values.  The
258 		   Number subclasses in the JDK are immutable and so will not be used in
259 		   this way but other subclasses of Number might want to do this to save
260 		   space and avoid unnecessary heap allocation.
261 		*/
262 		if (type.equals(String.class)) {
263             String s1  = (String)o1;
264             String s2  = (String)o2;
265 			int result = s1.compareToIgnoreCase(s2);
266 
267 			// empty strings come last
268 			if (s1.length() == 0 ^ s2.length() == 0) {
269 				result = -result;
270 			}
271 
272 			return result;
273 		}
274 		else if (o1 instanceof Comparable) {
275 			return ((Comparable)o1).compareTo(o2);
276 		}
277 		else if (type == Boolean.class) {
278             boolean b1 = ((Boolean)o1).booleanValue();
279             boolean b2 = ((Boolean)o2).booleanValue();
280 	    
281 			// define: false < true
282             if (b1 == b2) {
283                 return 0;
284             } else if (b1) {
285                 return 1;
286             } else {
287                 return -1;
288             }
289         } 
290 		else {
291 			// now what?
292 			return o1.toString().compareTo(o2.toString());
293         }
294     }
295 
296     protected void reallocateIndexes(TableModelEvent e) 
297     {
298         int rowCount = getRowCount();
299 
300         if (rowCount == indexes.length)
301             return;
302 
303         // Set up a new array of indexes with the right number of elements
304         // for the new data model.
305         int newIndexes[] = new int[rowCount];
306         int newRevIndexes[] = new int[rowCount];
307 
308         synchronized (indexes) {
309             int row = 0;
310 
311 			if (e != null && e.getType() == TableModelEvent.DELETE) {
312 				// we need to remove the deleted lines from the index
313 				// and decrease all row numbers by the appropriate
314 				// amount
315 				int skipped = 0;
316 				for(; row < indexes.length; row++) {
317 					int dec = 0;
318 					boolean skip = false;
319 					for (int i = e.getFirstRow(); i <= e.getLastRow(); i++) {
320 						if (i < indexes[row])
321 							dec++;
322 						else if (i == indexes[row])
323 							skip = true;
324 					}
325 					if (skip) {
326 						skipped++;
327 						continue;
328 					}
329 		    
330 					newIndexes[row - skipped] = indexes[row] - dec;
331 					newRevIndexes[indexes[row] - dec] = row - skipped;
332 				}
333 			} 
334 			else if (e != null && (e.getType() == TableModelEvent.UPDATE
335 								   && e.getLastRow() >= indexes.length)) {
336 				// start from scratch
337 				for (int i = 0; i < rowCount; i++) {
338 					newIndexes[i] = i;
339 					newRevIndexes[i] = i;
340 				}
341 			}
342 			else {
343 				// Add the new rows to the end of the map, but leave the
344 				// beginning intact.
345 				for(; row < indexes.length && row < rowCount; row++) {
346 					newIndexes[row] = indexes[row];
347 					newRevIndexes[row] = revIndexes[row];
348 				}
349 				for(; row < rowCount; row++) {
350 					newIndexes[row] = row;
351 					newRevIndexes[row] = row;
352 				}
353 		
354 			}
355 	    
356 			indexes = newIndexes;
357 			revIndexes = newRevIndexes;
358 		}
359     }
360 
361     protected void reallocateIndexes() 
362     {
363 		reallocateIndexes(null);
364     }
365     
366     /***
367      * Returns false if nothing has changed.
368      */
369     protected boolean sort() 
370     {
371         synchronized (indexes) {
372 			compares = 0;
373 			//long startTime = System.currentTimeMillis();
374 
375 			// copy indexes for sorting
376 			int[] oldIndexes = new int[indexes.length];
377 			System.arraycopy(indexes, 0, oldIndexes, 0, indexes.length);
378 	    
379 			if (shuttlesort(oldIndexes, indexes, 0, indexes.length)) {
380 	    
381 				//long elapsedTime = System.currentTimeMillis() - startTime;
382 				//System.out.println("sorted with " + compares 
383 				//+ " compares in " + elapsedTime + " ms.");
384 		
385 				// update reverse indexes
386 				for(int i = 0; i < indexes.length; i++) {
387 					revIndexes[indexes[i]] = i;
388 				}
389 		
390 				super.fireTableChanged(new TableModelEvent(this));
391 
392 				return true;
393 			}
394 			else {
395 				return false;
396 			}
397 		}
398     }
399 
400     /***
401      * Returns false if nothing has changed.
402      */
403     // This is a home-grown implementation which we have not had time
404     // to research - it may perform poorly in some circumstances. It
405     // requires twice the space of an in-place algorithm and makes
406     // NlogN assigments shuttling the values between the two
407     // arrays. The number of compares appears to vary between N-1 and
408     // NlogN depending on the initial order but the main reason for
409     // using it here is that, unlike qsort, it is stable.
410     public boolean shuttlesort(int from[], int to[], int low, int high) 
411     {
412         if (high - low < 2) {
413             return false;
414         }
415 
416 		boolean changed = false;
417         int middle = (low + high) / 2;
418         changed |= shuttlesort(to, from, low, middle);
419         changed |= shuttlesort(to, from, middle, high);
420 
421         int p = low;
422         int q = middle;
423 
424         /* This is an optional short-cut; at each recursive call,
425 		   check to see if the elements in this subset are already
426 		   ordered.  If so, no further comparisons are needed; the
427 		   sub-array can just be copied.  The array must be copied rather
428 		   than assigned otherwise sister calls in the recursion might
429 		   get out of sinc.  When the number of elements is three they
430 		   are partitioned so that the first set, [low, mid), has one
431 		   element and and the second, [mid, high), has two. We skip the
432 		   optimisation when the number of elements is three or less as
433 		   the first compare in the normal merge will produce the same
434 		   sequence of steps. This optimisation seems to be worthwhile
435 		   for partially ordered lists but some analysis is needed to
436 		   find out how the performance drops to Nlog(N) as the initial
437 		   order diminishes - it may drop very quickly.  */
438 
439         if (high - low >= 4) {
440             if (compare(from[middle-1], from[middle]) <= 0) {
441                 for (int i = low; i < high; i++) {
442                     to[i] = from[i];
443                 }
444                 return changed;
445             } else {
446                 // an optimisation for lists with reverse order -- j.a.
447                 if (compare(from[high-1], from[low]) < 0) {
448                     int i = low;
449                     for( ; q < high; q++) {
450                         to[i++] = from[q];
451                     }
452                     for( ; i < high; i++) {
453                         to[i] = from[p++];
454                     }
455                     return changed;
456                 }
457             }
458         }
459 
460         // standard merge
461         for (int i = low; i < high; i++) {
462             if (q >= high || (p < middle && compare(from[p], from[q]) <= 0)) {
463                 to[i] = from[p++];
464             } 
465 			else {
466 				changed |= (p < middle);
467                 to[i] = from[q++];
468             }
469         }
470 
471 		return changed;
472     }
473 
474     /*
475      * Alternative sort algorithm.
476      *
477      */
478     public void n2sort() {
479         for(int i = 0; i < getRowCount(); i++) {
480             for(int j = i+1; j < getRowCount(); j++) {
481                 if (compare(indexes[i], indexes[j]) == -1) {
482                     swap(i, j);
483                 }
484             }
485         }
486     }
487 
488     protected void swap(int i, int j) {
489         int tmp = indexes[i];
490         indexes[i] = indexes[j];
491         indexes[j] = tmp;
492     }
493 
494 }