View Javadoc

1   package org.lcsim.plugin.browser.sort;
2   
3   import java.util.Comparator;
4   
5   import javax.swing.event.EventListenerList;
6   import javax.swing.event.TableModelEvent;
7   import javax.swing.event.TableModelListener;
8   import javax.swing.table.TableModel;
9   
10  /**
11   * Converts any TableModel to a SortableTableModel.
12   * @author Tony Johnson
13   * @version $Id: DefaultSortableTableModel.java,v 1.4 2007/06/25 17:47:27 tonyj Exp $
14   */
15  public class DefaultSortableTableModel implements SortableTableModel
16  {
17     private Comparator comparator = new DefaultComparator();
18     private TableModel source;
19     private EventListenerList listeners = new EventListenerList();
20     private TableModelListener internalListener = new InternalTableModelListener();
21     private int sortColumn = UNSORTED;
22     private String sortColumnName;
23     private boolean ascending = true;
24     private int[] rowMap;
25     private int[] reverseMap;
26     boolean reverseMapValid = false;
27     private int nRows;
28     
29     /**
30      * Creates a new instance of DefaultTableSorter
31      * @param source The table model to be converted.
32      */
33     public DefaultSortableTableModel(TableModel source)
34     {
35        this.source = source;
36     }
37     private int mapFromSorted(int rowIndex)
38     {
39        return rowMap == null ? rowIndex : rowMap[rowIndex];
40     }
41     private int mapToSorted(int rowIndex)
42     {
43        if (rowMap != null && !reverseMapValid)
44        {
45           if (reverseMap == null || reverseMap.length < nRows)
46           {
47              reverseMap = new int[rowMap.length];
48           }
49           for (int i=0; i<nRows; i++) reverseMap[rowMap[i]] = i;
50           reverseMapValid = true;
51        }
52        return rowMap == null ? rowIndex : reverseMap[rowIndex];
53     }
54     
55     public void addTableModelListener(TableModelListener l)
56     {
57        if (listeners.getListenerCount() == 0) 
58        {
59           // If we have not been listening for changes to model, we must assume
60           // it has totally changed!
61           dataChanged();
62           source.addTableModelListener(internalListener);
63        }
64        listeners.add(TableModelListener.class, l);
65     }
66     
67     public Class getColumnClass(int columnIndex)
68     {
69        return source.getColumnClass(columnIndex);
70     }
71     
72     public int getColumnCount()
73     {
74        return source.getColumnCount();
75     }
76     
77     public String getColumnName(int columnIndex)
78     {
79        return source.getColumnName(columnIndex);
80     }
81     
82     public int getRowCount()
83     {
84        return source.getRowCount();
85     }
86     
87     public Object getValueAt(int rowIndex, int columnIndex)
88     {
89        return source.getValueAt(mapFromSorted(rowIndex),columnIndex);
90     }
91     
92     public boolean isCellEditable(int rowIndex, int columnIndex)
93     {
94        return source.isCellEditable(mapFromSorted(rowIndex),columnIndex);
95     }
96     
97     public void removeTableModelListener(TableModelListener l)
98     {
99        listeners.remove(TableModelListener.class, l);
100       if (listeners.getListenerCount() == 0) source.removeTableModelListener(internalListener);
101    }
102       
103    public void sort(int column, boolean ascending)
104    {
105       if (column == UNSORTED)
106       {
107          if (this.sortColumn != UNSORTED)
108          {
109             rowMap = null;
110             reverseMap = null;
111             reverseMapValid = false;
112             this.sortColumn = column;
113             this.sortColumnName = null;
114             TableModelEvent ee = new TableModelEvent(this,0,source.getRowCount()-1,TableModelEvent.ALL_COLUMNS);
115             fireTableChanged(ee);
116          }
117       }
118       else if (column == this.sortColumn)
119       {
120          if (ascending != this.ascending)
121          {
122             this.ascending = ascending;
123             for (int i=0; i<nRows/2; i++) swap(i,nRows-1-i);
124             reverseMapValid = false;
125             TableModelEvent ee = new TableModelEvent(this,0,nRows-1,TableModelEvent.ALL_COLUMNS);
126             fireTableChanged(ee);
127          }
128       }
129       else
130       {
131          if (rowMap == null)
132          {
133             nRows = source.getRowCount();
134             rowMap = new int[nRows+10]; // Leave a little room for expansion
135             for (int i=0; i<nRows; i++) rowMap[i] = i;
136          }
137          this.sortColumn = column;
138          this.sortColumnName = getColumnName(column);
139          this.ascending = ascending;
140          reverseMapValid = false;
141          sort1(0,nRows);
142          TableModelEvent ee = new TableModelEvent(this,0,nRows-1,TableModelEvent.ALL_COLUMNS);
143          fireTableChanged(ee);
144       }
145    }
146    private void reSort()
147    {
148       reverseMapValid = false;
149       sort1(0,nRows);
150       TableModelEvent ee = new TableModelEvent(this,0,nRows-1,TableModelEvent.ALL_COLUMNS);
151       fireTableChanged(ee);
152    }
153    private void dataChanged()
154    {
155       // Entire data was changed, including (perhaps) number of rows
156       nRows = source.getRowCount();
157       if (sortColumn != UNSORTED)
158       {
159          rowMap = new int[nRows+10]; // Leave a little room for expansion
160          for (int i=0; i<nRows; i++) rowMap[i] = i;
161          reverseMapValid = false;
162          sort1(0,nRows);
163       }
164       TableModelEvent ee = new TableModelEvent(this,0,Integer.MAX_VALUE,TableModelEvent.ALL_COLUMNS);
165       fireTableChanged(ee);
166    }
167    // Insert a newly added source row in a sorted list
168    private int rowWasInserted(int row)
169    {
170       int insertPoint;
171       if (sortColumn != UNSORTED)
172       {
173          if (nRows >= rowMap.length)
174          {
175             int[] newMap = new int[nRows+10];
176             System.arraycopy(rowMap,0,newMap,0,rowMap.length);
177             rowMap = newMap;
178          }
179          // Add new row to the end to begin with
180          for (int i=0; i<nRows; i++)
181          {
182             if (rowMap[i] >= row) rowMap[i]++;
183          }
184          rowMap[nRows] = row;
185          // Do a binary search to find out where the new row should go
186          insertPoint = binarySearch(nRows,0,nRows);
187          if (insertPoint != nRows)
188          {
189             System.arraycopy(rowMap,insertPoint, rowMap, insertPoint+1,nRows-insertPoint);
190             rowMap[insertPoint] = row;
191          }
192       }
193       else
194       {
195          insertPoint = row;
196       }
197       nRows++;
198       reverseMapValid = false;
199       return insertPoint;
200    }
201    private int binarySearch(int newRow, int start, int end)
202    {
203       if (start-end < 5)
204       {
205          for (int i=start; i<end; i++)
206          {
207             if (compare(newRow,i) <= 0 ) return i;
208          }
209          return end;
210       }
211       int mid = end-start >> 1;
212       int result = compare(newRow,mid);
213       if      (result == 0) return mid;
214       else if (result  > 0) return binarySearch(newRow,mid,end);
215       else                  return binarySearch(newRow,start,mid);
216    }
217    private int rowWasDeleted(int row)
218    {
219       int sortedRow;
220       if (sortColumn != UNSORTED)
221       {
222          sortedRow = mapToSorted(row);
223          System.arraycopy(rowMap,sortedRow+1,rowMap,sortedRow,nRows-sortedRow-1);
224          nRows--;
225          for (int i=0; i<nRows; i++)
226          {
227             if (rowMap[i] > row) rowMap[i]--;
228          }
229       }
230       else 
231       {
232          sortedRow = row;
233       }
234       reverseMapValid = false;
235       return sortedRow;
236    }
237    
238    public void setValueAt(Object aValue, int rowIndex, int columnIndex)
239    {
240       source.setValueAt(aValue, mapFromSorted(rowIndex), columnIndex);
241    }
242    
243    private Object get(int index)
244    {
245       return source.getValueAt(mapFromSorted(index), sortColumn);
246    }
247    private int compare(int i, int j)
248    {
249       return comparator.compare(get(i),get(j)) * (ascending ? 1 : -1);
250    }
251    private int compare(Object o, int j)
252    {
253       return comparator.compare(o,get(j)) * (ascending ? 1 : -1);
254    }
255    private void swap(int i, int j)
256    {
257       int tmp = rowMap[i];
258       rowMap[i] = rowMap[j];
259       rowMap[j] = tmp;
260    }
261    private int med3(int a, int b, int c)
262    {
263       return compare(a,b)<0 ?
264          compare(b,c)<0 ? b : compare(a,c)<0 ? c : a :
265             compare(b,c)>0 ? b : compare(a,c)>0 ? c : a;
266    }
267    private void vecswap(int a, int b, int n)
268    {
269       for (int i=0; i<n; i++, a++, b++)
270          swap(a, b);
271    }
272    private void sort1(int off, int len)
273    {
274       // Insertion sort on smallest arrays
275       if (len < 7)
276       {
277          for (int i=off; i<len+off; i++)
278             for (int j=i; j>off && compare(j-1,j)>0; j--)
279                swap(j, j-1);
280          return;
281       }
282       
283       // Choose a partition element, v
284       int m = off + (len >> 1);       // Small arrays, middle element
285       if (len > 7)
286       {
287          int l = off;
288          int n = off + len - 1;
289          if (len > 40) // Big arrays, pseudomedian of 9
290          {
291             int s = len/8;
292             l = med3(l,     l+s, l+2*s);
293             m = med3(m-s,   m,   m+s);
294             n = med3(n-2*s, n-s, n);
295          }
296          m = med3(l, m, n); // Mid-size, med of 3
297       }
298       Object v = get(m);
299       
300       // Establish Invariant: v* (<v)* (>v)* v*
301       int a = off, b = a, c = off + len - 1, d = c;
302       int comp;
303       while(true)
304       {
305          while (b <= c && (comp = compare(v,b)) >= 0)
306          {
307             if (comp == 0) swap(a++, b);
308             b++;
309          }
310          while (c >= b && (comp = compare(v,c)) <= 0)
311          {
312             if (comp == 0) swap(c, d--);
313             c--;
314          }
315          if (b > c) break;
316          swap(b++, c--);
317       }
318       
319       // Swap partition elements back to middle
320       int s, n = off + len;
321       s = Math.min(a-off, b-a  );  vecswap(off, b-s, s);
322       s = Math.min(d-c,   n-d-1);  vecswap(b,   n-s, s);
323       
324       // Recursively sort non-partition-elements
325       if ((s = b-a) > 1) sort1(off, s);
326       if ((s = d-c) > 1) sort1(n-s, s);
327    }
328    
329    /**
330     * Notifies all listeners of a change to the sorted TableModel.
331     * @param event The event to be sent to the listeners.
332     */
333    protected void fireTableChanged(TableModelEvent event)
334    {
335       TableModelListener[] l = (TableModelListener[]) listeners.getListeners(TableModelListener.class);
336       for (int i=0; i<l.length; i++)
337       {
338          l[i].tableChanged(event);
339       }
340    }
341 
342    public boolean isSortAscending()
343    {
344       return ascending;
345    }
346 
347    public int getSortOnColumn()
348    {
349       return sortColumn;
350    }
351    private class InternalTableModelListener implements TableModelListener
352    {
353       public void tableChanged(TableModelEvent e)
354       {
355          int column = e.getColumn();
356          int first = e.getFirstRow();
357          int last = e.getLastRow();
358          int type = e.getType();
359          if (sortColumn == UNSORTED)
360          {
361             TableModelEvent ee = new TableModelEvent(DefaultSortableTableModel.this,first,last,column,type);
362             fireTableChanged(ee);
363          }
364          else if (first == TableModelEvent.HEADER_ROW)
365          {
366             // one or more entire columns was added, removed or changed -- deal with it
367             if (type == TableModelEvent.DELETE)
368             {
369                if (column < sortColumn) sortColumn--;
370                else if (column == sortColumn)
371                {
372                   sort(UNSORTED,true);
373                }
374             }
375             else if (type == TableModelEvent.INSERT)
376             {
377                if (column <= sortColumn && sortColumn != UNSORTED) sortColumn++;
378             }
379             else if (type == TableModelEvent.UPDATE)
380             {
381                if (column == sortColumn) 
382                {
383                   reSort();
384                }
385                else if (column == TableModelEvent.ALL_COLUMNS)
386                {
387                   // Assume worst case, columns and data and nRows changed
388                   sortColumn = UNSORTED;
389                   reverseMapValid = false;
390                   rowMap = null;
391                   nRows = source.getRowCount();
392                   // See if a column with the same name exists
393                   if (sortColumnName != null)
394                   {
395                      int n = getColumnCount();
396                      for (int i=0; i<n; i++)
397                      {
398                         if (sortColumnName.equals(getColumnName(i)))
399                         {
400                            sort(i,ascending);
401                         }
402                      }
403                   }
404                }
405             }
406             TableModelEvent ee = new TableModelEvent(DefaultSortableTableModel.this,first,last,column,type);
407             fireTableChanged(ee);
408          }
409          else if (column == TableModelEvent.ALL_COLUMNS)
410          {
411             // one or more rows were added, removed or changed -- deal with it
412             
413             if (type == TableModelEvent.DELETE)
414             {
415                for (int i=first; i<=last; i++)
416                {
417                   int sortedRow = rowWasDeleted(first); // Note, always first because previous first was deleted
418                   TableModelEvent ee = new TableModelEvent(DefaultSortableTableModel.this,sortedRow,sortedRow,column,type);
419                   fireTableChanged(ee);
420                }
421             }
422             else if (type == TableModelEvent.INSERT)
423             {
424                for (int i=first; i<=last; i++)
425                {
426                   int sortedRow = rowWasInserted(i);
427                   TableModelEvent ee = new TableModelEvent(DefaultSortableTableModel.this,sortedRow,sortedRow,column,type);
428                   fireTableChanged(ee);
429                }
430             }
431             else if (type == TableModelEvent.UPDATE)
432             {
433                if (Integer.MAX_VALUE == last)
434                {
435                   dataChanged();
436                }
437                else
438                {
439                   for (int i=first; i<=last; i++)
440                   {
441                      int oldRow = rowWasDeleted(i);
442                      int newRow = rowWasInserted(i);
443                      if (oldRow == newRow)
444                      {
445                         TableModelEvent ee = new TableModelEvent(DefaultSortableTableModel.this,newRow,newRow,column,type);
446                         fireTableChanged(ee);
447                      }
448                      else
449                      {
450                         TableModelEvent ee1 = new TableModelEvent(DefaultSortableTableModel.this,oldRow,oldRow,TableModelEvent.ALL_COLUMNS,TableModelEvent.DELETE);
451                         fireTableChanged(ee1);
452                         TableModelEvent ee2 = new TableModelEvent(DefaultSortableTableModel.this,newRow,newRow,TableModelEvent.ALL_COLUMNS,TableModelEvent.INSERT);
453                         fireTableChanged(ee2);
454                      }
455                   }
456                }
457             }
458          }
459          else if (type == TableModelEvent.UPDATE)
460          {
461             if (column == sortColumn)
462             {
463                for (int i=first; i<=last; i++)
464                {
465                   int oldRow = rowWasDeleted(i);
466                   int newRow = rowWasInserted(i);
467                   if (oldRow == newRow)
468                   {
469                      TableModelEvent ee = new TableModelEvent(DefaultSortableTableModel.this,newRow,newRow,column,type);
470                      fireTableChanged(ee);
471                   }
472                   else
473                   {
474                      TableModelEvent ee1 = new TableModelEvent(DefaultSortableTableModel.this,oldRow,oldRow,TableModelEvent.ALL_COLUMNS,TableModelEvent.DELETE);
475                      fireTableChanged(ee1);
476                      TableModelEvent ee2 = new TableModelEvent(DefaultSortableTableModel.this,newRow,newRow,TableModelEvent.ALL_COLUMNS,TableModelEvent.INSERT);
477                      fireTableChanged(ee2);
478                   }
479                }
480             }
481             else
482             {
483                for (int i=first; i<=last; i++)
484                {
485                   int sortedRow = mapToSorted(i);
486                   TableModelEvent ee = new TableModelEvent(DefaultSortableTableModel.this,sortedRow,sortedRow,column,type);
487                   fireTableChanged(ee);
488                }
489             }
490          }
491          else throw new UnsupportedOperationException("An unsupported TableModelEvent was found: "+e);
492       }
493    }
494    private class DefaultComparator implements Comparator
495    {
496       public int compare(Object o1, Object o2)
497       {
498          if ((o1 instanceof Comparable) && (o2 instanceof Comparable))
499             return ((Comparable) o1).compareTo((Comparable) o2);
500          else return String.valueOf(o1).compareTo(String.valueOf(o2));
501       }
502    }
503 }