OpenConcerto

Dépôt officiel du code source de l'ERP OpenConcerto
sonarqube

svn://code.openconcerto.org/openconcerto

Rev

Rev 80 | Rev 174 | Go to most recent revision | Show entire file | Regard whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 80 Rev 156
Line 35... Line 35...
35
import java.util.Map;
35
import java.util.Map;
36
 
36
 
37
import javax.swing.Icon;
37
import javax.swing.Icon;
38
import javax.swing.JLabel;
38
import javax.swing.JLabel;
39
import javax.swing.JTable;
39
import javax.swing.JTable;
-
 
40
import javax.swing.SwingConstants;
-
 
41
import javax.swing.SwingUtilities;
40
import javax.swing.event.TableModelEvent;
42
import javax.swing.event.TableModelEvent;
41
import javax.swing.event.TableModelListener;
43
import javax.swing.event.TableModelListener;
42
import javax.swing.table.AbstractTableModel;
44
import javax.swing.table.AbstractTableModel;
43
import javax.swing.table.JTableHeader;
45
import javax.swing.table.JTableHeader;
44
import javax.swing.table.TableCellRenderer;
46
import javax.swing.table.TableCellRenderer;
45
import javax.swing.table.TableColumnModel;
47
import javax.swing.table.TableColumnModel;
46
import javax.swing.table.TableModel;
48
import javax.swing.table.TableModel;
47
 
49
 
-
 
50
import net.jcip.annotations.Immutable;
-
 
51
 
48
/**
52
/**
49
 * TableSorter is a decorator for TableModels; adding sorting functionality to a supplied
53
 * TableSorter is a decorator for TableModels; adding sorting functionality to a supplied
50
 * TableModel. TableSorter does not store or copy the data in its TableModel; instead it maintains a
54
 * TableModel. TableSorter does not store or copy the data in its TableModel; instead it maintains a
51
 * map from the row indexes of the view to the row indexes of the model. As requests are made of the
55
 * map from the row indexes of the view to the row indexes of the model. As requests are made of the
52
 * sorter (like getValueAt(row, col)) they are passed to the underlying model after the row numbers
56
 * sorter (like getValueAt(row, col)) they are passed to the underlying model after the row numbers
53
 * have been translated via the internal mapping array. This way, the TableSorter appears to hold
57
 * have been translated via the internal mapping array. This way, the TableSorter appears to hold
54
 * another copy of the table with the rows in a different order. <p/>TableSorter registers itself as
58
 * another copy of the table with the rows in a different order.
-
 
59
 * <p/>
55
 * a listener to the underlying model, just as the JTable itself would. Events recieved from the
60
 * TableSorter registers itself as a listener to the underlying model, just as the JTable itself
56
 * model are examined, sometimes manipulated (typically widened), and then passed on to the
61
 * would. Events recieved from the model are examined, sometimes manipulated (typically widened),
57
 * TableSorter's listeners (typically the JTable). If a change to the model has invalidated the
62
 * and then passed on to the TableSorter's listeners (typically the JTable). If a change to the
58
 * order of TableSorter's rows, a note of this is made and the sorter will resort the rows the next
63
 * model has invalidated the order of TableSorter's rows, a note of this is made and the sorter will
59
 * time a value is requested. <p/>When the tableHeader property is set, either by using the
64
 * resort the rows the next time a value is requested.
-
 
65
 * <p/>
60
 * setTableHeader() method or the two argument constructor, the table header may be used as a
66
 * When the tableHeader property is set, either by using the setTableHeader() method or the two
61
 * complete UI for TableSorter. The default renderer of the tableHeader is decorated with a renderer
67
 * argument constructor, the table header may be used as a complete UI for TableSorter. The default
62
 * that indicates the sorting status of each column. In addition, a mouse listener is installed with
68
 * renderer of the tableHeader is decorated with a renderer that indicates the sorting status of
63
 * the following behavior:
69
 * each column. In addition, a mouse listener is installed with the following behavior:
64
 * <ul>
70
 * <ul>
65
 * <li>Mouse-click: Clears the sorting status of all other columns and advances the sorting status
71
 * <li>Mouse-click: Clears the sorting status of all other columns and advances the sorting status
66
 * of that column through three values: {NOT_SORTED, ASCENDING, DESCENDING} (then back to NOT_SORTED
72
 * of that column through three values: {NOT_SORTED, ASCENDING, DESCENDING} (then back to NOT_SORTED
67
 * again).
73
 * again).
68
 * <li>SHIFT-mouse-click: Clears the sorting status of all other columns and cycles the sorting
74
 * <li>SHIFT-mouse-click: Clears the sorting status of all other columns and cycles the sorting
Line 70... Line 76...
70
 * DESCENDING, ASCENDING}.
76
 * DESCENDING, ASCENDING}.
71
 * <li>CONTROL-mouse-click and CONTROL-SHIFT-mouse-click: as above except that the changes to the
77
 * <li>CONTROL-mouse-click and CONTROL-SHIFT-mouse-click: as above except that the changes to the
72
 * column do not cancel the statuses of columns that are already sorting - giving a way to initiate
78
 * column do not cancel the statuses of columns that are already sorting - giving a way to initiate
73
 * a compound sort.
79
 * a compound sort.
74
 * </ul>
80
 * </ul>
-
 
81
 * <p/>
75
 * <p/>This is a long overdue rewrite of a class of the same name that first appeared in the swing
82
 * This is a long overdue rewrite of a class of the same name that first appeared in the swing table
76
 * table demos in 1997.
83
 * demos in 1997.
77
 * 
84
 * 
78
 * @author Philip Milne
85
 * @author Philip Milne
79
 * @author Brendon McLean
86
 * @author Brendon McLean
80
 * @author Dan van Enckevort
87
 * @author Dan van Enckevort
81
 * @author Parwinder Sekhon
88
 * @author Parwinder Sekhon
Line 90... Line 97...
90
    public static final int ASCENDING = 1;
97
    public static final int ASCENDING = 1;
91
 
98
 
92
    private static Directive EMPTY_DIRECTIVE = new Directive(-1, NOT_SORTED);
99
    private static Directive EMPTY_DIRECTIVE = new Directive(-1, NOT_SORTED);
93
 
100
 
94
    public static final Comparator COMPARABLE_COMAPRATOR = new Comparator() {
101
    public static final Comparator COMPARABLE_COMAPRATOR = new Comparator() {
-
 
102
        @Override
95
        public int compare(Object o1, Object o2) {
103
        public int compare(Object o1, Object o2) {
96
            return ((Comparable) o1).compareTo(o2);
104
            return ((Comparable) o1).compareTo(o2);
97
        }
105
        }
98
    };
106
    };
99
    public static final Comparator LEXICAL_COMPARATOR = new Comparator() {
107
    public static final Comparator<Object> LEXICAL_COMPARATOR = new Comparator<Object>() {
-
 
108
        @Override
100
        public int compare(Object o1, Object o2) {
109
        public int compare(Object o1, Object o2) {
101
            return o1.toString().compareTo(o2.toString());
110
            return o1.toString().compareTo(o2.toString());
102
        }
111
        }
103
    };
112
    };
104
 
113
 
Line 106... Line 115...
106
    private int[] modelToView;
115
    private int[] modelToView;
107
 
116
 
108
    private JTableHeader tableHeader;
117
    private JTableHeader tableHeader;
109
    private MouseListener mouseListener;
118
    private MouseListener mouseListener;
110
    private TableModelListener tableModelListener;
119
    private TableModelListener tableModelListener;
111
    private Map columnComparators = new HashMap();
120
    private Map<Class<?>, Comparator<?>> columnComparators = new HashMap<>();
112
    private List sortingColumns = new ArrayList();
121
    private final List<Directive> sortingColumns = new ArrayList<>();
113
 
122
 
114
    private boolean enabled;
123
    private boolean enabled;
115
    private boolean sorting;
124
    private boolean sorting;
116
    private final PropertyChangeSupport supp;
125
    private final PropertyChangeSupport supp;
117
 
126
 
Line 142... Line 151...
142
    public TableModel getTableModel() {
151
    public TableModel getTableModel() {
143
        return tableModel;
152
        return tableModel;
144
    }
153
    }
145
 
154
 
146
    public void setTableModel(TableModel tableModel) {
155
    public void setTableModel(TableModel tableModel) {
-
 
156
        if (!SwingUtilities.isEventDispatchThread()) {
-
 
157
            throw new IllegalStateException("must be called from EDT");
-
 
158
        }
147
        if (this.tableModel != null) {
159
        if (this.tableModel != null) {
148
            this.tableModel.removeTableModelListener(tableModelListener);
160
            this.tableModel.removeTableModelListener(tableModelListener);
149
        }
161
        }
150
 
162
 
151
        final TableModel old = this.tableModel;
163
        final TableModel old = this.tableModel;
Line 186... Line 198...
186
    public JTableHeader getTableHeader() {
198
    public JTableHeader getTableHeader() {
187
        return tableHeader;
199
        return tableHeader;
188
    }
200
    }
189
 
201
 
190
    public void setTableHeader(JTableHeader tableHeader) {
202
    public void setTableHeader(JTableHeader tableHeader) {
-
 
203
        if (!SwingUtilities.isEventDispatchThread()) {
-
 
204
            throw new IllegalStateException("must be called from EDT");
-
 
205
        }
191
        if (this.tableHeader != null) {
206
        if (this.tableHeader != null) {
192
            this.tableHeader.removeMouseListener(mouseListener);
207
            this.tableHeader.removeMouseListener(mouseListener);
193
            TableCellRenderer defaultRenderer = this.tableHeader.getDefaultRenderer();
208
            TableCellRenderer defaultRenderer = this.tableHeader.getDefaultRenderer();
194
            if (defaultRenderer instanceof SortableHeaderRenderer) {
209
            if (defaultRenderer instanceof SortableHeaderRenderer) {
195
                this.tableHeader.setDefaultRenderer(((SortableHeaderRenderer) defaultRenderer).tableCellRenderer);
210
                this.tableHeader.setDefaultRenderer(((SortableHeaderRenderer) defaultRenderer).tableCellRenderer);
Line 205... Line 220...
205
 
220
 
206
    public boolean isSorting() {
221
    public boolean isSorting() {
207
        return sortingColumns.size() != 0;
222
        return sortingColumns.size() != 0;
208
    }
223
    }
209
 
224
 
-
 
225
    public final List<Directive> getSortingColumns() {
-
 
226
        return Collections.unmodifiableList(this.sortingColumns);
-
 
227
    }
-
 
228
 
-
 
229
    public void setSortingColumns(List<Directive> sortingColumns) {
-
 
230
        if (!SwingUtilities.isEventDispatchThread()) {
-
 
231
            throw new IllegalStateException("must be called from EDT");
-
 
232
        }
-
 
233
        this.sortingColumns.clear();
-
 
234
        this.sortingColumns.addAll(sortingColumns);
-
 
235
        sortingStatusChanged();
-
 
236
    }
-
 
237
 
210
    private Directive getDirective(int column) {
238
    private Directive getDirective(int column) {
211
        for (int i = 0; i < sortingColumns.size(); i++) {
239
        for (int i = 0; i < sortingColumns.size(); i++) {
212
            Directive directive = (Directive) sortingColumns.get(i);
240
            Directive directive = sortingColumns.get(i);
213
            if (directive.column == column) {
241
            if (directive.column == column) {
214
                return directive;
242
                return directive;
215
            }
243
            }
216
        }
244
        }
217
        return EMPTY_DIRECTIVE;
245
        return EMPTY_DIRECTIVE;
Line 232... Line 260...
232
            this.supp.firePropertyChange("sorting", old, this.sorting);
260
            this.supp.firePropertyChange("sorting", old, this.sorting);
233
        }
261
        }
234
    }
262
    }
235
 
263
 
236
    private synchronized void sortingStatusChanged() {
264
    private synchronized void sortingStatusChanged() {
-
 
265
        if (!SwingUtilities.isEventDispatchThread()) {
-
 
266
            throw new IllegalStateException("must be called from EDT");
-
 
267
        }
237
        this.sortingStatusChanged(true);
268
        this.sortingStatusChanged(true);
238
    }
269
    }
239
 
270
 
240
    private synchronized void sortingStatusChanged(final boolean fire) {
271
    private synchronized void sortingStatusChanged(final boolean fire) {
241
        clearSortingState();
272
        clearSortingState();
Line 269... Line 300...
269
    private void cancelSorting(final boolean fire) {
300
    private void cancelSorting(final boolean fire) {
270
        sortingColumns.clear();
301
        sortingColumns.clear();
271
        sortingStatusChanged(fire);
302
        sortingStatusChanged(fire);
272
    }
303
    }
273
 
304
 
274
    public void setColumnComparator(Class type, Comparator comparator) {
305
    public void setColumnComparator(Class<?> type, Comparator<?> comparator) {
275
        if (comparator == null) {
306
        if (comparator == null) {
276
            columnComparators.remove(type);
307
            columnComparators.remove(type);
277
        } else {
308
        } else {
278
            columnComparators.put(type, comparator);
309
            columnComparators.put(type, comparator);
279
        }
310
        }
280
    }
311
    }
281
 
312
 
282
    protected Comparator getComparator(int column) {
313
    protected Comparator getComparator(int column) {
283
        Class columnType = tableModel.getColumnClass(column);
314
        Class<?> columnType = tableModel.getColumnClass(column);
284
        Comparator comparator = (Comparator) columnComparators.get(columnType);
315
        Comparator<?> comparator = columnComparators.get(columnType);
285
        if (comparator != null) {
316
        if (comparator != null) {
286
            return comparator;
317
            return comparator;
287
        }
318
        }
288
        if (Comparable.class.isAssignableFrom(columnType)) {
319
        if (Comparable.class.isAssignableFrom(columnType)) {
289
            return COMPARABLE_COMAPRATOR;
320
            return COMPARABLE_COMAPRATOR;
290
        }
321
        }
291
        return LEXICAL_COMPARATOR;
322
        return LEXICAL_COMPARATOR;
292
    }
323
    }
293
 
324
 
294
    private Row[] getViewToModel() {
325
    private Row[] getViewToModel() {
-
 
326
        if (!SwingUtilities.isEventDispatchThread()) {
-
 
327
            throw new IllegalStateException("must be called from EDT");
-
 
328
        }
295
        if (viewToModel == null) {
329
        if (viewToModel == null) {
296
            int tableModelRowCount = tableModel.getRowCount();
330
            int tableModelRowCount = tableModel.getRowCount();
297
            viewToModel = new Row[tableModelRowCount];
331
            viewToModel = new Row[tableModelRowCount];
298
            for (int row = 0; row < tableModelRowCount; row++) {
332
            for (int row = 0; row < tableModelRowCount; row++) {
299
                viewToModel[row] = new Row(row);
333
                viewToModel[row] = new Row(row);
Line 316... Line 350...
316
    public int modelIndex(int viewIndex) {
350
    public int modelIndex(int viewIndex) {
317
        return getViewToModel()[viewIndex].modelIndex;
351
        return getViewToModel()[viewIndex].modelIndex;
318
    }
352
    }
319
 
353
 
320
    private int[] getModelToView() {
354
    private int[] getModelToView() {
-
 
355
        if (!SwingUtilities.isEventDispatchThread()) {
-
 
356
            throw new IllegalStateException("must be called from EDT");
-
 
357
        }
321
        if (modelToView == null) {
358
        if (modelToView == null) {
322
            int n = getViewToModel().length;
359
            int n = getViewToModel().length;
323
            modelToView = new int[n];
360
            modelToView = new int[n];
324
            for (int i = 0; i < n; i++) {
361
            for (int i = 0; i < n; i++) {
325
                modelToView[modelIndex(i)] = i;
362
                modelToView[modelIndex(i)] = i;
Line 332... Line 369...
332
        return getModelToView()[modelIndex];
369
        return getModelToView()[modelIndex];
333
    }
370
    }
334
 
371
 
335
    // TableModel interface methods
372
    // TableModel interface methods
336
 
373
 
-
 
374
    @Override
337
    public int getRowCount() {
375
    public int getRowCount() {
-
 
376
        if (!SwingUtilities.isEventDispatchThread()) {
-
 
377
            throw new IllegalStateException("must be called from EDT");
-
 
378
        }
338
        return (tableModel == null) ? 0 : tableModel.getRowCount();
379
        return (tableModel == null) ? 0 : tableModel.getRowCount();
339
    }
380
    }
340
 
381
 
-
 
382
    @Override
341
    public int getColumnCount() {
383
    public int getColumnCount() {
-
 
384
        if (!SwingUtilities.isEventDispatchThread()) {
-
 
385
            throw new IllegalStateException("must be called from EDT");
-
 
386
        }
342
        return (tableModel == null) ? 0 : tableModel.getColumnCount();
387
        return (tableModel == null) ? 0 : tableModel.getColumnCount();
343
    }
388
    }
344
 
389
 
-
 
390
    @Override
345
    public String getColumnName(int column) {
391
    public String getColumnName(int column) {
346
        return tableModel.getColumnName(column);
392
        return tableModel.getColumnName(column);
347
    }
393
    }
348
 
394
 
-
 
395
    @Override
349
    public Class getColumnClass(int column) {
396
    public Class<?> getColumnClass(int column) {
350
        return tableModel.getColumnClass(column);
397
        return tableModel.getColumnClass(column);
351
    }
398
    }
352
 
399
 
-
 
400
    @Override
353
    public boolean isCellEditable(int row, int column) {
401
    public boolean isCellEditable(int row, int column) {
354
        return tableModel.isCellEditable(modelIndex(row), column);
402
        return tableModel.isCellEditable(modelIndex(row), column);
355
    }
403
    }
356
 
404
 
-
 
405
    @Override
357
    public Object getValueAt(int row, int column) {
406
    public Object getValueAt(int row, int column) {
-
 
407
        if (!SwingUtilities.isEventDispatchThread()) {
-
 
408
            throw new IllegalStateException("must be called from EDT");
-
 
409
        }
358
        return tableModel.getValueAt(modelIndex(row), column);
410
        return tableModel.getValueAt(modelIndex(row), column);
359
    }
411
    }
360
 
412
 
-
 
413
    @Override
361
    public void setValueAt(Object aValue, int row, int column) {
414
    public void setValueAt(Object aValue, int row, int column) {
-
 
415
        if (!SwingUtilities.isEventDispatchThread()) {
-
 
416
            throw new IllegalStateException("must be called from EDT");
-
 
417
        }
362
        tableModel.setValueAt(aValue, modelIndex(row), column);
418
        tableModel.setValueAt(aValue, modelIndex(row), column);
363
    }
419
    }
364
 
420
 
365
    public void addPropertyChangeListener(PropertyChangeListener l) {
421
    public void addPropertyChangeListener(PropertyChangeListener l) {
366
        this.supp.addPropertyChangeListener(l);
422
        this.supp.addPropertyChangeListener(l);
Line 374... Line 430...
374
        this.supp.removePropertyChangeListener(l);
430
        this.supp.removePropertyChangeListener(l);
375
    }
431
    }
376
 
432
 
377
    // Helper classes
433
    // Helper classes
378
 
434
 
379
    private class Row implements Comparable {
435
    private class Row implements Comparable<Row> {
380
        private int modelIndex;
436
        private int modelIndex;
381
 
437
 
382
        public Row(int index) {
438
        public Row(int index) {
383
            this.modelIndex = index;
439
            this.modelIndex = index;
384
        }
440
        }
385
 
441
 
-
 
442
        @Override
386
        public int compareTo(Object o) {
443
        public int compareTo(Row o) {
387
            int row1 = modelIndex;
444
            int row1 = modelIndex;
388
            int row2 = ((Row) o).modelIndex;
445
            int row2 = o.modelIndex;
389
 
446
 
390
            for (Iterator it = sortingColumns.iterator(); it.hasNext();) {
447
            for (Iterator<Directive> it = sortingColumns.iterator(); it.hasNext();) {
391
                Directive directive = (Directive) it.next();
448
                Directive directive = it.next();
392
                int column = directive.column;
449
                int column = directive.column;
393
                Object o1 = tableModel.getValueAt(row1, column);
450
                Object o1 = tableModel.getValueAt(row1, column);
394
                Object o2 = tableModel.getValueAt(row2, column);
451
                Object o2 = tableModel.getValueAt(row2, column);
395
 
452
 
396
                int comparison = 0;
453
                int comparison = 0;
Line 411... Line 468...
411
            return 0;
468
            return 0;
412
        }
469
        }
413
    }
470
    }
414
 
471
 
415
    private class TableModelHandler implements TableModelListener {
472
    private class TableModelHandler implements TableModelListener {
-
 
473
        @Override
416
        public void tableChanged(TableModelEvent e) {
474
        public void tableChanged(TableModelEvent e) {
417
            // If we're not sorting by anything, just pass the event along.
475
            // If we're not sorting by anything, just pass the event along.
418
            if (!isSorting()) {
476
            if (!isSorting()) {
419
                clearSortingState();
477
                clearSortingState();
420
                fireTableChanged(e);
478
                fireTableChanged(e);
Line 485... Line 543...
485
 
543
 
486
        return currentL.equals(newL);
544
        return currentL.equals(newL);
487
    }
545
    }
488
 
546
 
489
    private class MouseHandler extends MouseAdapter {
547
    private class MouseHandler extends MouseAdapter {
-
 
548
        @Override
490
        public void mouseClicked(MouseEvent e) {
549
        public void mouseClicked(MouseEvent e) {
491
            JTableHeader h = (JTableHeader) e.getSource();
550
            JTableHeader h = (JTableHeader) e.getSource();
492
            TableColumnModel columnModel = h.getColumnModel();
551
            TableColumnModel columnModel = h.getColumnModel();
493
            int viewColumn = columnModel.getColumnIndexAtX(e.getX());
552
            int viewColumn = columnModel.getColumnIndexAtX(e.getX());
494
            // don't continue if we can't find the column
553
            // don't continue if we can't find the column
Line 521... Line 580...
521
            this.descending = descending;
580
            this.descending = descending;
522
            this.size = size;
581
            this.size = size;
523
            this.priority = priority;
582
            this.priority = priority;
524
        }
583
        }
525
 
584
 
-
 
585
        @Override
526
        public void paintIcon(Component c, Graphics g, int x, int y) {
586
        public void paintIcon(Component c, Graphics g, int x, int y) {
527
            Color color = c == null ? Color.GRAY : c.getBackground();
587
            Color color = c == null ? Color.GRAY : c.getBackground();
528
            // In a compound sort, make each succesive triangle 20%
588
            // In a compound sort, make each succesive triangle 20%
529
            // smaller than the previous one.
589
            // smaller than the previous one.
530
            int dx = (int) (size / 2 * Math.pow(0.8, priority));
590
            int dx = (int) (size / 2 * Math.pow(0.8, priority));
Line 554... Line 614...
554
 
614
 
555
            g.setColor(color);
615
            g.setColor(color);
556
            g.translate(-x, -y);
616
            g.translate(-x, -y);
557
        }
617
        }
558
 
618
 
-
 
619
        @Override
559
        public int getIconWidth() {
620
        public int getIconWidth() {
560
            return size;
621
            return size;
561
        }
622
        }
562
 
623
 
-
 
624
        @Override
563
        public int getIconHeight() {
625
        public int getIconHeight() {
564
            return size;
626
            return size;
565
        }
627
        }
566
    }
628
    }
567
 
629
 
Line 570... Line 632...
570
 
632
 
571
        public SortableHeaderRenderer(TableCellRenderer tableCellRenderer) {
633
        public SortableHeaderRenderer(TableCellRenderer tableCellRenderer) {
572
            this.tableCellRenderer = tableCellRenderer;
634
            this.tableCellRenderer = tableCellRenderer;
573
        }
635
        }
574
 
636
 
-
 
637
        @Override
575
        public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
638
        public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
576
            Component c = tableCellRenderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
639
            Component c = tableCellRenderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
577
            if (c instanceof JLabel) {
640
            if (c instanceof JLabel) {
578
                JLabel l = (JLabel) c;
641
                JLabel l = (JLabel) c;
579
                l.setHorizontalTextPosition(JLabel.LEFT);
642
                l.setHorizontalTextPosition(SwingConstants.LEFT);
580
                int modelColumn = table.convertColumnIndexToModel(column);
643
                int modelColumn = table.convertColumnIndexToModel(column);
581
                l.setIcon(getHeaderRendererIcon(modelColumn, l.getFont().getSize()));
644
                l.setIcon(getHeaderRendererIcon(modelColumn, l.getFont().getSize()));
582
            }
645
            }
583
            return c;
646
            return c;
584
        }
647
        }
585
    }
648
    }
586
 
649
 
-
 
650
    @Immutable
587
    private static class Directive {
651
    public static class Directive {
588
        private int column;
652
        private final int column;
589
        private int direction;
653
        private final int direction;
590
 
654
 
591
        public Directive(int column, int direction) {
655
        public Directive(int column, int direction) {
592
            this.column = column;
656
            this.column = column;
593
            this.direction = direction;
657
            this.direction = direction;
594
        }
658
        }
-
 
659
 
-
 
660
        public final int getColumn() {
-
 
661
            return this.column;
-
 
662
        }
-
 
663
 
-
 
664
        public final int getDirection() {
-
 
665
            return this.direction;
-
 
666
        }
-
 
667
 
-
 
668
        @Override
-
 
669
        public String toString() {
-
 
670
            return this.getClass().getSimpleName() + " " + this.getDirection() + " on column " + this.getColumn();
-
 
671
        }
595
    }
672
    }
596
}
673
}