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

Mutations

60

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

63

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

64

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

65

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

66

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

67

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

68

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

70

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

72

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

74

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

75

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

78

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

81

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

82

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

83

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

86

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

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

87

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

92

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

93

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

97

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

105

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

106

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

114

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

115

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

126

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

130

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

134

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

148

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

149

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

156

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

160

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

161

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

165

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

166

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

169

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

172

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

173

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

175

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

180

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

184

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

192

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

195

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

196

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

197

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

198

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

202

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

206

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

215

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

221

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

228

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

236

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

243

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