PanelTable.java

1
/*******************************************************************************
2
 * Copyhacked (H) 2012-2025.
3
 * This program and the accompanying materials
4
 * are made available under no term at all, use it like
5
 * you want, but share and discuss it
6
 * every time possible with every body.
7
 * 
8
 * Contributors:
9
 *      ron190 at ymail dot com - initial implementation
10
 ******************************************************************************/
11
package com.jsql.view.swing.table;
12
13
import com.jsql.util.LogLevelUtil;
14
import com.jsql.view.swing.popupmenu.JPopupMenuTable;
15
import com.jsql.view.swing.text.JTextFieldPlaceholder;
16
import com.jsql.view.swing.util.UiStringUtil;
17
import org.apache.commons.lang3.StringUtils;
18
import org.apache.logging.log4j.LogManager;
19
import org.apache.logging.log4j.Logger;
20
21
import javax.swing.*;
22
import javax.swing.event.DocumentEvent;
23
import javax.swing.event.DocumentListener;
24
import javax.swing.table.DefaultTableCellRenderer;
25
import javax.swing.table.TableCellRenderer;
26
import javax.swing.table.TableModel;
27
import javax.swing.table.TableRowSorter;
28
import java.awt.*;
29
import java.awt.event.*;
30
import java.util.Comparator;
31
import java.util.HashSet;
32
import java.util.Set;
33
import java.util.regex.Pattern;
34
35
/**
36
 * Display a table for database values. Add keyboard shortcut, mouse icon, text
37
 * and header formatting.
38
 */
39
public class PanelTable extends JPanel {
40
    
41
    /**
42
     * Log4j logger sent to view.
43
     */
44
    private static final Logger LOGGER = LogManager.getRootLogger();
45
    
46
    /**
47
     * Table to display in the panel.
48
     */
49
    private final JTable tableValues;
50
51
    /**
52
     * Create a panel containing a table to display injection values.
53
     * 
54
     * @param data Array 2D with injection table data
55
     * @param columnNames Names of columns from database
56
     */
57
    public PanelTable(String[][] data, String[] columnNames) {
58
        super(new BorderLayout());
59
60
        this.tableValues = new JTable(data, columnNames) {
61
            @Override
62
            public boolean isCellEditable(int row, int column) {
63 1 1. isCellEditable : replaced boolean return with true for com/jsql/view/swing/table/PanelTable$1::isCellEditable → NO_COVERAGE
                return false;
64
            }
65
        };
66 1 1. <init> : removed call to javax/swing/JTable::setAutoResizeMode → NO_COVERAGE
        this.tableValues.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
67 1 1. <init> : removed call to javax/swing/JTable::setSelectionMode → NO_COVERAGE
        this.tableValues.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
68 1 1. <init> : removed call to javax/swing/JTable::setColumnSelectionAllowed → NO_COVERAGE
        this.tableValues.setColumnSelectionAllowed(true);
69 1 1. <init> : removed call to javax/swing/JTable::setRowHeight → NO_COVERAGE
        this.tableValues.setRowHeight(20);
70 1 1. <init> : removed call to javax/swing/JTable::setRowSelectionAllowed → NO_COVERAGE
        this.tableValues.setRowSelectionAllowed(true);
71 1 1. <init> : removed call to javax/swing/JTable::setCellSelectionEnabled → NO_COVERAGE
        this.tableValues.setCellSelectionEnabled(true);
72
73 1 1. <init> : removed call to com/jsql/view/swing/table/PanelTable::initRenderer → NO_COVERAGE
        this.initRenderer();
74
75 1 1. <init> : removed call to javax/swing/table/JTableHeader::setReorderingAllowed → NO_COVERAGE
        this.tableValues.getTableHeader().setReorderingAllowed(false);
76
77 1 1. <init> : removed call to com/jsql/view/swing/table/PanelTable::initMouseEvent → NO_COVERAGE
        this.initMouseEvent();
78 1 1. <init> : removed call to com/jsql/view/swing/table/PanelTable::initTabShortcut → NO_COVERAGE
        this.initTabShortcut();
79
80
        var columnAdjuster = new AdjusterTableColumn(this.tableValues);
81 1 1. <init> : removed call to com/jsql/view/swing/table/AdjusterTableColumn::adjustColumns → NO_COVERAGE
        columnAdjuster.adjustColumns();
82
83
        final TableRowSorter<TableModel> rowSorter = new TableRowSorter<>(this.tableValues.getModel());
84 1 1. <init> : removed call to javax/swing/JTable::setRowSorter → NO_COVERAGE
        this.tableValues.setRowSorter(rowSorter);
85 1 1. <init> : removed call to com/jsql/view/swing/table/PanelTable::initTableScroller → NO_COVERAGE
        this.initTableScroller();
86 1 1. <init> : removed call to com/jsql/view/swing/table/PanelTable::initPanelSearch → NO_COVERAGE
        this.initPanelSearch(rowSorter);
87
88
        Comparator<Object> comparatorNumeric = new ComparatorColumn<>();
89 2 1. <init> : negated conditional → NO_COVERAGE
2. <init> : changed conditional boundary → NO_COVERAGE
        for (var i = 0 ; i < this.tableValues.getColumnCount() ; i++) {
90 1 1. <init> : removed call to javax/swing/table/TableRowSorter::setComparator → NO_COVERAGE
            rowSorter.setComparator(i, comparatorNumeric);
91
        }
92
    }
93
94
    private void initMouseEvent() {
95 1 1. initMouseEvent : removed call to javax/swing/JTable::setDragEnabled → NO_COVERAGE
        this.tableValues.setDragEnabled(true);
96 1 1. initMouseEvent : removed call to javax/swing/JTable::addMouseListener → NO_COVERAGE
        this.tableValues.addMouseListener(new MouseAdapter() {
97
            @Override
98
            public void mousePressed(MouseEvent e) {
99
                PanelTable.this.tableValues.requestFocusInWindow();
100 1 1. mousePressed : negated conditional → NO_COVERAGE
                if (SwingUtilities.isRightMouseButton(e)) {
101
                    // Keep selection when multiple cells are selected,
102
                    // move focus only
103
                    var p = e.getPoint();
104
                    var rowNumber = PanelTable.this.tableValues.rowAtPoint(p);
105
                    var colNumber = PanelTable.this.tableValues.columnAtPoint(p);
106
                    DefaultListSelectionModel modelRow = (DefaultListSelectionModel) PanelTable.this.tableValues.getSelectionModel();
107
                    DefaultListSelectionModel modelColumn = (DefaultListSelectionModel) PanelTable.this.tableValues.getColumnModel().getSelectionModel();
108 1 1. mousePressed : removed call to javax/swing/DefaultListSelectionModel::moveLeadSelectionIndex → NO_COVERAGE
                    modelRow.moveLeadSelectionIndex(rowNumber);
109 1 1. mousePressed : removed call to javax/swing/DefaultListSelectionModel::moveLeadSelectionIndex → NO_COVERAGE
                    modelColumn.moveLeadSelectionIndex(colNumber);
110
                }
111
            }
112
        });
113
    }
114
115
    private void initRenderer() {
116
        final TableCellRenderer cellRendererHeader = this.tableValues.getTableHeader().getDefaultRenderer();
117 1 1. initRenderer : removed call to javax/swing/table/JTableHeader::setDefaultRenderer → NO_COVERAGE
        this.tableValues.getTableHeader().setDefaultRenderer(
118 1 1. lambda$initRenderer$0 : replaced return value with null for com/jsql/view/swing/table/PanelTable::lambda$initRenderer$0 → NO_COVERAGE
            (JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) -> cellRendererHeader.getTableCellRendererComponent(
119
                table,
120
                UiStringUtil.detectUtf8HtmlNoWrap(StringUtils.SPACE + value + StringUtils.SPACE),
121
                isSelected,
122
                hasFocus,
123
                row,
124
                column
125
            )
126
        );
127
128
        final var cellRendererDefault = new DefaultTableCellRenderer();
129 1 1. initRenderer : removed call to javax/swing/JTable::setDefaultRenderer → NO_COVERAGE
        this.tableValues.setDefaultRenderer(
130
            this.tableValues.getColumnClass(2),
131
            (JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) -> {
132
                // Prepare cell value to be utf8 inspected
133 1 1. lambda$initRenderer$1 : negated conditional → NO_COVERAGE
                String cellValue = value != null ? value.toString() : StringUtils.EMPTY;
134
                // Fix #90481: NullPointerException on getTableCellRendererComponent()
135
                // Fix #96072: IllegalArgumentException on getTableCellRendererComponent()
136
                try {
137 1 1. lambda$initRenderer$1 : replaced return value with null for com/jsql/view/swing/table/PanelTable::lambda$initRenderer$1 → NO_COVERAGE
                    return cellRendererDefault.getTableCellRendererComponent(
138
                        table, UiStringUtil.detectUtf8HtmlNoWrap(cellValue), isSelected, hasFocus, row, column
139
                    );
140
                } catch (IllegalArgumentException | NullPointerException e) {
141
                    LOGGER.log(LogLevelUtil.CONSOLE_JAVA, e, e);
142
                    return null;
143
                }
144
            }
145
        );
146
    }
147
148
    private void initTableScroller() {
149
        var scroller = new JScrollPane(this.tableValues);
150
        var tableFixedColumn = new FixedColumnTable();
151 1 1. initTableScroller : removed call to com/jsql/view/swing/table/FixedColumnTable::fixColumnSize → NO_COVERAGE
        tableFixedColumn.fixColumnSize(2, scroller);
152 1 1. initTableScroller : removed call to com/jsql/view/swing/table/PanelTable::add → NO_COVERAGE
        this.add(scroller, BorderLayout.CENTER);
153
    }
154
155
    private void initPanelSearch(final TableRowSorter<TableModel> rowSorter) {
156
        final var panelSearch = new JPanel(new BorderLayout());
157
158
        final JTextField textFilter = new JTextFieldPlaceholder("Find in table");
159 1 1. initPanelSearch : removed call to javax/swing/JPanel::add → NO_COVERAGE
        panelSearch.add(textFilter, BorderLayout.CENTER);
160
161
        Action actionShowSearchTable = new ActionShowSearch(panelSearch, textFilter);
162
        String keySearch = "search";
163 1 1. initPanelSearch : removed call to javax/swing/ActionMap::put → NO_COVERAGE
        this.tableValues.getActionMap().put(keySearch, actionShowSearchTable);
164 1 1. initPanelSearch : removed call to javax/swing/InputMap::put → NO_COVERAGE
        this.tableValues.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_F, InputEvent.CTRL_DOWN_MASK), keySearch);
165
166
        Action actionCloseSearch = new ActionCloseSearch(textFilter, panelSearch, this);
167
        String keyClose = "close";
168 1 1. initPanelSearch : removed call to javax/swing/ActionMap::put → NO_COVERAGE
        textFilter.getActionMap().put(keyClose, actionCloseSearch);
169 1 1. initPanelSearch : removed call to javax/swing/InputMap::put → NO_COVERAGE
        textFilter.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), keyClose);
170
171
        // Fix #43974: PatternSyntaxException on regexFilter() => Pattern.quote()
172 1 1. initPanelSearch : removed call to javax/swing/text/Document::addDocumentListener → NO_COVERAGE
        textFilter.getDocument().addDocumentListener(new DocumentListener() {
173
            private void insertUpdateFixed() {
174
                String text = textFilter.getText();
175 1 1. insertUpdateFixed : negated conditional → NO_COVERAGE
                if (text.trim().isEmpty()) {
176 1 1. insertUpdateFixed : removed call to javax/swing/table/TableRowSorter::setRowFilter → NO_COVERAGE
                    rowSorter.setRowFilter(null);
177
                } else {
178 1 1. insertUpdateFixed : removed call to javax/swing/table/TableRowSorter::setRowFilter → NO_COVERAGE
                    rowSorter.setRowFilter(RowFilter.regexFilter("(?i)" + Pattern.quote(text)));
179
                }
180
            }
181
            @Override
182
            public void insertUpdate(DocumentEvent e) {
183 1 1. insertUpdate : removed call to com/jsql/view/swing/table/PanelTable$3::insertUpdateFixed → NO_COVERAGE
                this.insertUpdateFixed();
184
            }
185
            @Override
186
            public void removeUpdate(DocumentEvent e) {
187 1 1. removeUpdate : removed call to com/jsql/view/swing/table/PanelTable$3::insertUpdateFixed → NO_COVERAGE
                this.insertUpdateFixed();
188
            }
189
            @Override
190
            public void changedUpdate(DocumentEvent e) {
191
                throw new UnsupportedOperationException("Not supported yet.");
192
            }
193
        });
194
195 1 1. initPanelSearch : removed call to javax/swing/JTable::setComponentPopupMenu → NO_COVERAGE
        this.tableValues.setComponentPopupMenu(new JPopupMenuTable(this.tableValues, actionShowSearchTable));
196
        
197
        JButton buttonCloseSearch = new ButtonClose();
198 1 1. initPanelSearch : removed call to javax/swing/JButton::addActionListener → NO_COVERAGE
        buttonCloseSearch.addActionListener(actionCloseSearch);
199 1 1. initPanelSearch : removed call to javax/swing/JPanel::add → NO_COVERAGE
        panelSearch.add(buttonCloseSearch, BorderLayout.EAST);
200 1 1. initPanelSearch : removed call to javax/swing/JPanel::setVisible → NO_COVERAGE
        panelSearch.setVisible(false);
201 1 1. initPanelSearch : removed call to com/jsql/view/swing/table/PanelTable::add → NO_COVERAGE
        this.add(panelSearch, BorderLayout.SOUTH);
202
    }
203
204
    private void initTabShortcut() {
205 1 1. initTabShortcut : removed call to javax/swing/InputMap::put → NO_COVERAGE
        this.tableValues.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(
206
            KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0),
207
            null
208
        );
209 1 1. initTabShortcut : removed call to javax/swing/InputMap::put → NO_COVERAGE
        this.tableValues.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(
210
            KeyStroke.getKeyStroke(KeyEvent.VK_TAB, InputEvent.SHIFT_DOWN_MASK),
211
            null
212
        );
213
214
        Set<AWTKeyStroke> forward = new HashSet<>(
215
            this.tableValues.getFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS)
216
        );
217
        forward.add(KeyStroke.getKeyStroke("TAB"));
218 1 1. initTabShortcut : removed call to javax/swing/JTable::setFocusTraversalKeys → NO_COVERAGE
        this.tableValues.setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, forward);
219
        
220
        Set<AWTKeyStroke> backward = new HashSet<>(
221
            this.tableValues.getFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS)
222
        );
223
        backward.add(KeyStroke.getKeyStroke("shift TAB"));
224 1 1. initTabShortcut : removed call to javax/swing/JTable::setFocusTraversalKeys → NO_COVERAGE
        this.tableValues.setFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, backward);
225
    }
226
227
    /**
228
     * Select every cell.
229
     */
230
    public void selectTable() {
231 1 1. selectTable : removed call to javax/swing/JTable::selectAll → NO_COVERAGE
        this.tableValues.selectAll();
232
    }
233
234
    /**
235
     * Perform copy event on current table.
236
     */
237
    public void copyTable() {
238
        var actionEvent = new ActionEvent(this.tableValues, ActionEvent.ACTION_PERFORMED, "copy");
239 1 1. copyTable : removed call to javax/swing/Action::actionPerformed → NO_COVERAGE
        this.tableValues.getActionMap().get(actionEvent.getActionCommand()).actionPerformed(actionEvent);
240
    }
241
242
    
243
    // Getter and setter
244
    
245
    public JTable getTableValues() {
246 1 1. getTableValues : replaced return value with null for com/jsql/view/swing/table/PanelTable::getTableValues → NO_COVERAGE
        return this.tableValues;
247
    }
248
}

Mutations

63

1.1
Location : isCellEditable
Killed by : none
replaced boolean return with true for com/jsql/view/swing/table/PanelTable$1::isCellEditable → NO_COVERAGE

66

1.1
Location : <init>
Killed by : none
removed call to javax/swing/JTable::setAutoResizeMode → NO_COVERAGE

67

1.1
Location : <init>
Killed by : none
removed call to javax/swing/JTable::setSelectionMode → NO_COVERAGE

68

1.1
Location : <init>
Killed by : none
removed call to javax/swing/JTable::setColumnSelectionAllowed → NO_COVERAGE

69

1.1
Location : <init>
Killed by : none
removed call to javax/swing/JTable::setRowHeight → NO_COVERAGE

70

1.1
Location : <init>
Killed by : none
removed call to javax/swing/JTable::setRowSelectionAllowed → NO_COVERAGE

71

1.1
Location : <init>
Killed by : none
removed call to javax/swing/JTable::setCellSelectionEnabled → NO_COVERAGE

73

1.1
Location : <init>
Killed by : none
removed call to com/jsql/view/swing/table/PanelTable::initRenderer → NO_COVERAGE

75

1.1
Location : <init>
Killed by : none
removed call to javax/swing/table/JTableHeader::setReorderingAllowed → NO_COVERAGE

77

1.1
Location : <init>
Killed by : none
removed call to com/jsql/view/swing/table/PanelTable::initMouseEvent → NO_COVERAGE

78

1.1
Location : <init>
Killed by : none
removed call to com/jsql/view/swing/table/PanelTable::initTabShortcut → NO_COVERAGE

81

1.1
Location : <init>
Killed by : none
removed call to com/jsql/view/swing/table/AdjusterTableColumn::adjustColumns → NO_COVERAGE

84

1.1
Location : <init>
Killed by : none
removed call to javax/swing/JTable::setRowSorter → NO_COVERAGE

85

1.1
Location : <init>
Killed by : none
removed call to com/jsql/view/swing/table/PanelTable::initTableScroller → NO_COVERAGE

86

1.1
Location : <init>
Killed by : none
removed call to com/jsql/view/swing/table/PanelTable::initPanelSearch → NO_COVERAGE

89

1.1
Location : <init>
Killed by : none
negated conditional → NO_COVERAGE

2.2
Location : <init>
Killed by : none
changed conditional boundary → NO_COVERAGE

90

1.1
Location : <init>
Killed by : none
removed call to javax/swing/table/TableRowSorter::setComparator → NO_COVERAGE

95

1.1
Location : initMouseEvent
Killed by : none
removed call to javax/swing/JTable::setDragEnabled → NO_COVERAGE

96

1.1
Location : initMouseEvent
Killed by : none
removed call to javax/swing/JTable::addMouseListener → NO_COVERAGE

100

1.1
Location : mousePressed
Killed by : none
negated conditional → NO_COVERAGE

108

1.1
Location : mousePressed
Killed by : none
removed call to javax/swing/DefaultListSelectionModel::moveLeadSelectionIndex → NO_COVERAGE

109

1.1
Location : mousePressed
Killed by : none
removed call to javax/swing/DefaultListSelectionModel::moveLeadSelectionIndex → NO_COVERAGE

117

1.1
Location : initRenderer
Killed by : none
removed call to javax/swing/table/JTableHeader::setDefaultRenderer → NO_COVERAGE

118

1.1
Location : lambda$initRenderer$0
Killed by : none
replaced return value with null for com/jsql/view/swing/table/PanelTable::lambda$initRenderer$0 → NO_COVERAGE

129

1.1
Location : initRenderer
Killed by : none
removed call to javax/swing/JTable::setDefaultRenderer → NO_COVERAGE

133

1.1
Location : lambda$initRenderer$1
Killed by : none
negated conditional → NO_COVERAGE

137

1.1
Location : lambda$initRenderer$1
Killed by : none
replaced return value with null for com/jsql/view/swing/table/PanelTable::lambda$initRenderer$1 → NO_COVERAGE

151

1.1
Location : initTableScroller
Killed by : none
removed call to com/jsql/view/swing/table/FixedColumnTable::fixColumnSize → NO_COVERAGE

152

1.1
Location : initTableScroller
Killed by : none
removed call to com/jsql/view/swing/table/PanelTable::add → NO_COVERAGE

159

1.1
Location : initPanelSearch
Killed by : none
removed call to javax/swing/JPanel::add → NO_COVERAGE

163

1.1
Location : initPanelSearch
Killed by : none
removed call to javax/swing/ActionMap::put → NO_COVERAGE

164

1.1
Location : initPanelSearch
Killed by : none
removed call to javax/swing/InputMap::put → NO_COVERAGE

168

1.1
Location : initPanelSearch
Killed by : none
removed call to javax/swing/ActionMap::put → NO_COVERAGE

169

1.1
Location : initPanelSearch
Killed by : none
removed call to javax/swing/InputMap::put → NO_COVERAGE

172

1.1
Location : initPanelSearch
Killed by : none
removed call to javax/swing/text/Document::addDocumentListener → NO_COVERAGE

175

1.1
Location : insertUpdateFixed
Killed by : none
negated conditional → NO_COVERAGE

176

1.1
Location : insertUpdateFixed
Killed by : none
removed call to javax/swing/table/TableRowSorter::setRowFilter → NO_COVERAGE

178

1.1
Location : insertUpdateFixed
Killed by : none
removed call to javax/swing/table/TableRowSorter::setRowFilter → NO_COVERAGE

183

1.1
Location : insertUpdate
Killed by : none
removed call to com/jsql/view/swing/table/PanelTable$3::insertUpdateFixed → NO_COVERAGE

187

1.1
Location : removeUpdate
Killed by : none
removed call to com/jsql/view/swing/table/PanelTable$3::insertUpdateFixed → NO_COVERAGE

195

1.1
Location : initPanelSearch
Killed by : none
removed call to javax/swing/JTable::setComponentPopupMenu → NO_COVERAGE

198

1.1
Location : initPanelSearch
Killed by : none
removed call to javax/swing/JButton::addActionListener → NO_COVERAGE

199

1.1
Location : initPanelSearch
Killed by : none
removed call to javax/swing/JPanel::add → NO_COVERAGE

200

1.1
Location : initPanelSearch
Killed by : none
removed call to javax/swing/JPanel::setVisible → NO_COVERAGE

201

1.1
Location : initPanelSearch
Killed by : none
removed call to com/jsql/view/swing/table/PanelTable::add → NO_COVERAGE

205

1.1
Location : initTabShortcut
Killed by : none
removed call to javax/swing/InputMap::put → NO_COVERAGE

209

1.1
Location : initTabShortcut
Killed by : none
removed call to javax/swing/InputMap::put → NO_COVERAGE

218

1.1
Location : initTabShortcut
Killed by : none
removed call to javax/swing/JTable::setFocusTraversalKeys → NO_COVERAGE

224

1.1
Location : initTabShortcut
Killed by : none
removed call to javax/swing/JTable::setFocusTraversalKeys → NO_COVERAGE

231

1.1
Location : selectTable
Killed by : none
removed call to javax/swing/JTable::selectAll → NO_COVERAGE

239

1.1
Location : copyTable
Killed by : none
removed call to javax/swing/Action::actionPerformed → NO_COVERAGE

246

1.1
Location : getTableValues
Killed by : none
replaced return value with null for com/jsql/view/swing/table/PanelTable::getTableValues → NO_COVERAGE

Active mutators

Tests examined


Report generated by PIT 1.19.1