EMMA Coverage Report (generated Mon Mar 20 21:34:30 PST 2006)
[all classes][com.moesol.ui.log.manager]

COVERAGE SUMMARY FOR SOURCE FILE [TableSorter.java]

nameclass, %method, %block, %line, %
TableSorter.java0%   (0/9)0%   (0/45)0%   (0/850)0%   (0/194)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class TableSorter0%   (0/1)0%   (0/28)0%   (0/409)0%   (0/99)
<static initializer> 0%   (0/1)0%   (0/15)0%   (0/3)
TableSorter (): void 0%   (0/1)0%   (0/27)0%   (0/6)
TableSorter (TableModel): void 0%   (0/1)0%   (0/6)0%   (0/3)
TableSorter (TableModel, JTableHeader): void 0%   (0/1)0%   (0/9)0%   (0/4)
cancelSorting (): void 0%   (0/1)0%   (0/6)0%   (0/3)
clearSortingState (): void 0%   (0/1)0%   (0/7)0%   (0/3)
getColumnClass (int): Class 0%   (0/1)0%   (0/5)0%   (0/1)
getColumnCount (): int 0%   (0/1)0%   (0/9)0%   (0/1)
getColumnName (int): String 0%   (0/1)0%   (0/5)0%   (0/1)
getComparator (int): Comparator 0%   (0/1)0%   (0/30)0%   (0/7)
getDirective (int): TableSorter$Directive 0%   (0/1)0%   (0/23)0%   (0/5)
getHeaderRendererIcon (int, int): Icon 0%   (0/1)0%   (0/25)0%   (0/4)
getModelToView (): int [] 0%   (0/1)0%   (0/28)0%   (0/6)
getRowCount (): int 0%   (0/1)0%   (0/9)0%   (0/1)
getSortingStatus (int): int 0%   (0/1)0%   (0/5)0%   (0/1)
getTableHeader (): JTableHeader 0%   (0/1)0%   (0/3)0%   (0/1)
getTableModel (): TableModel 0%   (0/1)0%   (0/3)0%   (0/1)
getValueAt (int, int): Object 0%   (0/1)0%   (0/8)0%   (0/1)
getViewToModel (): TableSorter$Row [] 0%   (0/1)0%   (0/36)0%   (0/8)
isCellEditable (int, int): boolean 0%   (0/1)0%   (0/8)0%   (0/1)
isSorting (): boolean 0%   (0/1)0%   (0/8)0%   (0/1)
modelIndex (int): int 0%   (0/1)0%   (0/6)0%   (0/1)
setColumnComparator (Class, Comparator): void 0%   (0/1)0%   (0/15)0%   (0/4)
setSortingStatus (int, int): void 0%   (0/1)0%   (0/26)0%   (0/7)
setTableHeader (JTableHeader): void 0%   (0/1)0%   (0/43)0%   (0/10)
setTableModel (TableModel): void 0%   (0/1)0%   (0/24)0%   (0/8)
setValueAt (Object, int, int): void 0%   (0/1)0%   (0/9)0%   (0/2)
sortingStatusChanged (): void 0%   (0/1)0%   (0/11)0%   (0/5)
     
class TableSorter$10%   (0/1)0%   (0/2)0%   (0/8)0%   (0/2)
TableSorter$1 (): void 0%   (0/1)0%   (0/3)0%   (0/1)
compare (Object, Object): int 0%   (0/1)0%   (0/5)0%   (0/1)
     
class TableSorter$20%   (0/1)0%   (0/2)0%   (0/9)0%   (0/2)
TableSorter$2 (): void 0%   (0/1)0%   (0/3)0%   (0/1)
compare (Object, Object): int 0%   (0/1)0%   (0/6)0%   (0/1)
     
class TableSorter$Arrow0%   (0/1)0%   (0/4)0%   (0/148)0%   (0/26)
TableSorter$Arrow (boolean, int, int): void 0%   (0/1)0%   (0/12)0%   (0/5)
getIconHeight (): int 0%   (0/1)0%   (0/3)0%   (0/1)
getIconWidth (): int 0%   (0/1)0%   (0/3)0%   (0/1)
paintIcon (Component, Graphics, int, int): void 0%   (0/1)0%   (0/130)0%   (0/19)
     
class TableSorter$Directive0%   (0/1)0%   (0/1)0%   (0/9)0%   (0/4)
TableSorter$Directive (int, int): void 0%   (0/1)0%   (0/9)0%   (0/4)
     
class TableSorter$MouseHandler0%   (0/1)0%   (0/2)0%   (0/60)0%   (0/13)
TableSorter$MouseHandler (TableSorter): void 0%   (0/1)0%   (0/6)0%   (0/1)
mouseClicked (MouseEvent): void 0%   (0/1)0%   (0/54)0%   (0/12)
     
class TableSorter$Row0%   (0/1)0%   (0/2)0%   (0/86)0%   (0/21)
TableSorter$Row (TableSorter, int): void 0%   (0/1)0%   (0/9)0%   (0/3)
compareTo (Object): int 0%   (0/1)0%   (0/77)0%   (0/18)
     
class TableSorter$SortableHeaderRenderer0%   (0/1)0%   (0/2)0%   (0/43)0%   (0/10)
TableSorter$SortableHeaderRenderer (TableSorter, TableCellRenderer): void 0%   (0/1)0%   (0/9)0%   (0/3)
getTableCellRendererComponent (JTable, Object, boolean, boolean, int, int): C... 0%   (0/1)0%   (0/34)0%   (0/7)
     
class TableSorter$TableModelHandler0%   (0/1)0%   (0/2)0%   (0/78)0%   (0/17)
TableSorter$TableModelHandler (TableSorter): void 0%   (0/1)0%   (0/6)0%   (0/1)
tableChanged (TableModelEvent): void 0%   (0/1)0%   (0/72)0%   (0/16)

1/*
2 * $Id: TableSorter.java,v 1.1 2005/08/04 18:58:14 hastings Exp $
3 * 
4 * Source: Java Tutorial, How to Use Tables, updated February, 2004
5 */
6package com.moesol.ui.log.manager;
7 
8import java.awt.*;
9import java.awt.event.*;
10import java.util.*;
11import java.util.List;
12 
13import javax.swing.*;
14import javax.swing.event.TableModelEvent;
15import javax.swing.event.TableModelListener;
16import javax.swing.table.*;
17 
18/**
19 * TableSorter is a decorator for TableModels; adding sorting
20 * functionality to a supplied TableModel. TableSorter does
21 * not store or copy the data in its TableModel; instead it maintains
22 * a map from the row indexes of the view to the row indexes of the
23 * model. As requests are made of the sorter (like getValueAt(row, col))
24 * they are passed to the underlying model after the row numbers
25 * have been translated via the internal mapping array. This way,
26 * the TableSorter appears to hold another copy of the table
27 * with the rows in a different order.
28 * <p/>
29 * TableSorter registers itself as a listener to the underlying model,
30 * just as the JTable itself would. Events recieved from the model
31 * are examined, sometimes manipulated (typically widened), and then
32 * passed on to the TableSorter's listeners (typically the JTable).
33 * If a change to the model has invalidated the order of TableSorter's
34 * rows, a note of this is made and the sorter will resort the
35 * rows the next time a value is requested.
36 * <p/>
37 * When the tableHeader property is set, either by using the
38 * setTableHeader() method or the two argument constructor, the
39 * table header may be used as a complete UI for TableSorter.
40 * The default renderer of the tableHeader is decorated with a renderer
41 * that indicates the sorting status of each column. In addition,
42 * a mouse listener is installed with the following behavior:
43 * <ul>
44 * <li>
45 * Mouse-click: Clears the sorting status of all other columns
46 * and advances the sorting status of that column through three
47 * values: {NOT_SORTED, ASCENDING, DESCENDING} (then back to
48 * NOT_SORTED again).
49 * <li>
50 * SHIFT-mouse-click: Clears the sorting status of all other columns
51 * and cycles the sorting status of the column through the same
52 * three values, in the opposite order: {NOT_SORTED, DESCENDING, ASCENDING}.
53 * <li>
54 * CONTROL-mouse-click and CONTROL-SHIFT-mouse-click: as above except
55 * that the changes to the column do not cancel the statuses of columns
56 * that are already sorting - giving a way to initiate a compound
57 * sort.
58 * </ul>
59 * <p/>
60 * This is a long overdue rewrite of a class of the same name that
61 * first appeared in the swing table demos in 1997.
62 * 
63 * @author Philip Milne
64 * @author Brendon McLean 
65 * @author Dan van Enckevort
66 * @author Parwinder Sekhon
67 * @version 2.0 02/27/04
68 */
69 
70public class TableSorter extends AbstractTableModel {
71        private static final long serialVersionUID = 4454925515216678869L;
72 
73        protected TableModel tableModel;
74 
75    public static final int DESCENDING = -1;
76    public static final int NOT_SORTED = 0;
77    public static final int ASCENDING = 1;
78 
79    private static Directive EMPTY_DIRECTIVE = new Directive(-1, NOT_SORTED);
80 
81    public static final Comparator COMPARABLE_COMAPRATOR = new Comparator() {
82        public int compare(Object o1, Object o2) {
83            return ((Comparable) o1).compareTo(o2);
84        }
85    };
86    public static final Comparator LEXICAL_COMPARATOR = new Comparator() {
87        public int compare(Object o1, Object o2) {
88            return o1.toString().compareTo(o2.toString());
89        }
90    };
91 
92    private Row[] viewToModel;
93    private int[] modelToView;
94 
95    private JTableHeader tableHeader;
96    private MouseListener mouseListener;
97    private TableModelListener tableModelListener;
98    private Map columnComparators = new HashMap();
99    private List sortingColumns = new ArrayList();
100 
101    public TableSorter() {
102        this.mouseListener = new MouseHandler();
103        this.tableModelListener = new TableModelHandler();
104    }
105 
106    public TableSorter(TableModel tableModel) {
107        this();
108        setTableModel(tableModel);
109    }
110 
111    public TableSorter(TableModel tableModel, JTableHeader tableHeader) {
112        this();
113        setTableHeader(tableHeader);
114        setTableModel(tableModel);
115    }
116 
117    private void clearSortingState() {
118        viewToModel = null;
119        modelToView = null;
120    }
121 
122    public TableModel getTableModel() {
123        return tableModel;
124    }
125 
126    public void setTableModel(TableModel tableModel) {
127        if (this.tableModel != null) {
128            this.tableModel.removeTableModelListener(tableModelListener);
129        }
130 
131        this.tableModel = tableModel;
132        if (this.tableModel != null) {
133            this.tableModel.addTableModelListener(tableModelListener);
134        }
135 
136        clearSortingState();
137        fireTableStructureChanged();
138    }
139 
140    public JTableHeader getTableHeader() {
141        return tableHeader;
142    }
143 
144    public void setTableHeader(JTableHeader tableHeader) {
145        if (this.tableHeader != null) {
146            this.tableHeader.removeMouseListener(mouseListener);
147            TableCellRenderer defaultRenderer = this.tableHeader.getDefaultRenderer();
148            if (defaultRenderer instanceof SortableHeaderRenderer) {
149                this.tableHeader.setDefaultRenderer(((SortableHeaderRenderer) defaultRenderer).tableCellRenderer);
150            }
151        }
152        this.tableHeader = tableHeader;
153        if (this.tableHeader != null) {
154            this.tableHeader.addMouseListener(mouseListener);
155            this.tableHeader.setDefaultRenderer(
156                    new SortableHeaderRenderer(this.tableHeader.getDefaultRenderer()));
157        }
158    }
159 
160    public boolean isSorting() {
161        return sortingColumns.size() != 0;
162    }
163 
164    private Directive getDirective(int column) {
165        for (int i = 0; i < sortingColumns.size(); i++) {
166            Directive directive = (Directive)sortingColumns.get(i);
167            if (directive.column == column) {
168                return directive;
169            }
170        }
171        return EMPTY_DIRECTIVE;
172    }
173 
174    public int getSortingStatus(int column) {
175        return getDirective(column).direction;
176    }
177 
178    private void sortingStatusChanged() {
179        clearSortingState();
180        fireTableDataChanged();
181        if (tableHeader != null) {
182            tableHeader.repaint();
183        }
184    }
185 
186    public void setSortingStatus(int column, int status) {
187        Directive directive = getDirective(column);
188        if (directive != EMPTY_DIRECTIVE) {
189            sortingColumns.remove(directive);
190        }
191        if (status != NOT_SORTED) {
192            sortingColumns.add(new Directive(column, status));
193        }
194        sortingStatusChanged();
195    }
196 
197    protected Icon getHeaderRendererIcon(int column, int size) {
198        Directive directive = getDirective(column);
199        if (directive == EMPTY_DIRECTIVE) {
200            return null;
201        }
202        return new Arrow(directive.direction == DESCENDING, size, sortingColumns.indexOf(directive));
203    }
204 
205    private void cancelSorting() {
206        sortingColumns.clear();
207        sortingStatusChanged();
208    }
209 
210    public void setColumnComparator(Class type, Comparator comparator) {
211        if (comparator == null) {
212            columnComparators.remove(type);
213        } else {
214            columnComparators.put(type, comparator);
215        }
216    }
217 
218    protected Comparator getComparator(int column) {
219        Class columnType = tableModel.getColumnClass(column);
220        Comparator comparator = (Comparator) columnComparators.get(columnType);
221        if (comparator != null) {
222            return comparator;
223        }
224        if (Comparable.class.isAssignableFrom(columnType)) {
225            return COMPARABLE_COMAPRATOR;
226        }
227        return LEXICAL_COMPARATOR;
228    }
229 
230    private Row[] getViewToModel() {
231        if (viewToModel == null) {
232            int tableModelRowCount = tableModel.getRowCount();
233            viewToModel = new Row[tableModelRowCount];
234            for (int row = 0; row < tableModelRowCount; row++) {
235                viewToModel[row] = new Row(row);
236            }
237 
238            if (isSorting()) {
239                Arrays.sort(viewToModel);
240            }
241        }
242        return viewToModel;
243    }
244 
245    public int modelIndex(int viewIndex) {
246        return getViewToModel()[viewIndex].modelIndex;
247    }
248 
249    private int[] getModelToView() {
250        if (modelToView == null) {
251            int n = getViewToModel().length;
252            modelToView = new int[n];
253            for (int i = 0; i < n; i++) {
254                modelToView[modelIndex(i)] = i;
255            }
256        }
257        return modelToView;
258    }
259 
260    // TableModel interface methods 
261 
262    public int getRowCount() {
263        return (tableModel == null) ? 0 : tableModel.getRowCount();
264    }
265 
266    public int getColumnCount() {
267        return (tableModel == null) ? 0 : tableModel.getColumnCount();
268    }
269 
270    public String getColumnName(int column) {
271        return tableModel.getColumnName(column);
272    }
273 
274    public Class getColumnClass(int column) {
275        return tableModel.getColumnClass(column);
276    }
277 
278    public boolean isCellEditable(int row, int column) {
279        return tableModel.isCellEditable(modelIndex(row), column);
280    }
281 
282    public Object getValueAt(int row, int column) {
283        return tableModel.getValueAt(modelIndex(row), column);
284    }
285 
286    public void setValueAt(Object aValue, int row, int column) {
287        tableModel.setValueAt(aValue, modelIndex(row), column);
288    }
289 
290    // Helper classes
291    
292    private class Row implements Comparable {
293        private int modelIndex;
294 
295        public Row(int index) {
296            this.modelIndex = index;
297        }
298 
299        public int compareTo(Object o) {
300            int row1 = modelIndex;
301            int row2 = ((Row) o).modelIndex;
302 
303            for (Iterator it = sortingColumns.iterator(); it.hasNext();) {
304                Directive directive = (Directive) it.next();
305                int column = directive.column;
306                Object o1 = tableModel.getValueAt(row1, column);
307                Object o2 = tableModel.getValueAt(row2, column);
308 
309                int comparison = 0;
310                // Define null less than everything, except null.
311                if (o1 == null && o2 == null) {
312                    comparison = 0;
313                } else if (o1 == null) {
314                    comparison = -1;
315                } else if (o2 == null) {
316                    comparison = 1;
317                } else {
318                    comparison = getComparator(column).compare(o1, o2);
319                }
320                if (comparison != 0) {
321                    return directive.direction == DESCENDING ? -comparison : comparison;
322                }
323            }
324            return 0;
325        }
326    }
327 
328    private class TableModelHandler implements TableModelListener {
329        public void tableChanged(TableModelEvent e) {
330            // If we're not sorting by anything, just pass the event along.             
331            if (!isSorting()) {
332                clearSortingState();
333                fireTableChanged(e);
334                return;
335            }
336                
337            // If the table structure has changed, cancel the sorting; the             
338            // sorting columns may have been either moved or deleted from             
339            // the model. 
340            if (e.getFirstRow() == TableModelEvent.HEADER_ROW) {
341                cancelSorting();
342                fireTableChanged(e);
343                return;
344            }
345 
346            // We can map a cell event through to the view without widening             
347            // when the following conditions apply: 
348            // 
349            // a) all the changes are on one row (e.getFirstRow() == e.getLastRow()) and, 
350            // b) all the changes are in one column (column != TableModelEvent.ALL_COLUMNS) and,
351            // c) we are not sorting on that column (getSortingStatus(column) == NOT_SORTED) and, 
352            // d) a reverse lookup will not trigger a sort (modelToView != null)
353            //
354            // Note: INSERT and DELETE events fail this test as they have column == ALL_COLUMNS.
355            // 
356            // The last check, for (modelToView != null) is to see if modelToView 
357            // is already allocated. If we don't do this check; sorting can become 
358            // a performance bottleneck for applications where cells  
359            // change rapidly in different parts of the table. If cells 
360            // change alternately in the sorting column and then outside of             
361            // it this class can end up re-sorting on alternate cell updates - 
362            // which can be a performance problem for large tables. The last 
363            // clause avoids this problem. 
364            int column = e.getColumn();
365            if (e.getFirstRow() == e.getLastRow()
366                    && column != TableModelEvent.ALL_COLUMNS
367                    && getSortingStatus(column) == NOT_SORTED
368                    && modelToView != null) {
369                int viewIndex = getModelToView()[e.getFirstRow()];
370                fireTableChanged(new TableModelEvent(TableSorter.this, 
371                                                     viewIndex, viewIndex, 
372                                                     column, e.getType()));
373                return;
374            }
375 
376            // Something has happened to the data that may have invalidated the row order. 
377            clearSortingState();
378            fireTableDataChanged();
379            return;
380        }
381    }
382 
383    private class MouseHandler extends MouseAdapter {
384        public void mouseClicked(MouseEvent e) {
385            JTableHeader h = (JTableHeader) e.getSource();
386            TableColumnModel columnModel = h.getColumnModel();
387            int viewColumn = columnModel.getColumnIndexAtX(e.getX());
388            int column = columnModel.getColumn(viewColumn).getModelIndex();
389            if (column != -1) {
390                int status = getSortingStatus(column);
391                if (!e.isControlDown()) {
392                    cancelSorting();
393                }
394                // Cycle the sorting states through {NOT_SORTED, ASCENDING, DESCENDING} or 
395                // {NOT_SORTED, DESCENDING, ASCENDING} depending on whether shift is pressed. 
396                status = status + (e.isShiftDown() ? -1 : 1);
397                status = (status + 4) % 3 - 1; // signed mod, returning {-1, 0, 1}
398                setSortingStatus(column, status);
399            }
400        }
401    }
402 
403    private static class Arrow implements Icon {
404        private boolean descending;
405        private int size;
406        private int priority;
407 
408        public Arrow(boolean descending, int size, int priority) {
409            this.descending = descending;
410            this.size = size;
411            this.priority = priority;
412        }
413 
414        public void paintIcon(Component c, Graphics g, int x, int y) {
415            Color color = c == null ? Color.GRAY : c.getBackground();             
416            // In a compound sort, make each succesive triangle 20% 
417            // smaller than the previous one. 
418            int dx = (int)(size/2*Math.pow(0.8, priority));
419            int dy = descending ? dx : -dx;
420            // Align icon (roughly) with font baseline. 
421            y = y + 5*size/6 + (descending ? -dy : 0);
422            int shift = descending ? 1 : -1;
423            g.translate(x, y);
424 
425            // Right diagonal. 
426            g.setColor(color.darker());
427            g.drawLine(dx / 2, dy, 0, 0);
428            g.drawLine(dx / 2, dy + shift, 0, shift);
429            
430            // Left diagonal. 
431            g.setColor(color.brighter());
432            g.drawLine(dx / 2, dy, dx, 0);
433            g.drawLine(dx / 2, dy + shift, dx, shift);
434            
435            // Horizontal line. 
436            if (descending) {
437                g.setColor(color.darker().darker());
438            } else {
439                g.setColor(color.brighter().brighter());
440            }
441            g.drawLine(dx, 0, 0, 0);
442 
443            g.setColor(color);
444            g.translate(-x, -y);
445        }
446 
447        public int getIconWidth() {
448            return size;
449        }
450 
451        public int getIconHeight() {
452            return size;
453        }
454    }
455 
456    private class SortableHeaderRenderer implements TableCellRenderer {
457        private TableCellRenderer tableCellRenderer;
458 
459        public SortableHeaderRenderer(TableCellRenderer tableCellRenderer) {
460            this.tableCellRenderer = tableCellRenderer;
461        }
462 
463        public Component getTableCellRendererComponent(JTable table, 
464                                                       Object value,
465                                                       boolean isSelected, 
466                                                       boolean hasFocus,
467                                                       int row, 
468                                                       int column) {
469            Component c = tableCellRenderer.getTableCellRendererComponent(table, 
470                    value, isSelected, hasFocus, row, column);
471            if (c instanceof JLabel) {
472                JLabel l = (JLabel) c;
473                l.setHorizontalTextPosition(JLabel.LEFT);
474                int modelColumn = table.convertColumnIndexToModel(column);
475                l.setIcon(getHeaderRendererIcon(modelColumn, l.getFont().getSize()));
476            }
477            return c;
478        }
479    }
480 
481    private static class Directive {
482        private int column;
483        private int direction;
484 
485        public Directive(int column, int direction) {
486            this.column = column;
487            this.direction = direction;
488        }
489    }
490}

[all classes][com.moesol.ui.log.manager]
EMMA 2.0.5312 (C) Vladimir Roubtsov