DnDList.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.list;
12
13
import com.jsql.util.I18nUtil;
14
import com.jsql.util.LogLevelUtil;
15
import org.apache.commons.io.FilenameUtils;
16
import org.apache.commons.lang3.StringUtils;
17
import org.apache.logging.log4j.LogManager;
18
import org.apache.logging.log4j.Logger;
19
20
import javax.swing.*;
21
import java.awt.event.*;
22
import java.io.BufferedReader;
23
import java.io.File;
24
import java.io.FileReader;
25
import java.io.IOException;
26
import java.nio.charset.StandardCharsets;
27
import java.util.ArrayList;
28
import java.util.List;
29
30
/**
31
 * A list supporting drag and drop.
32
 */
33
public class DnDList extends JList<ItemList> {
34
    
35
    private static final Logger LOGGER = LogManager.getRootLogger();
36
    
37
    /**
38
     * Model for the JList.
39
     */
40
    protected final DefaultListModel<ItemList> listModel;
41
    
42
    /**
43
     * List of default items.
44
     */
45
    private final transient List<ItemList> defaultList;
46
    
47
    /**
48
     * Create a JList decorated with drag/drop features.
49
     * @param newList List to decorate
50
     */
51
    public DnDList(List<ItemList> newList) {
52
        this.defaultList = newList;
53
        this.listModel = new DefaultListModel<>();
54
55
        for (ItemList path: newList) {
56 1 1. <init> : removed call to javax/swing/DefaultListModel::addElement → NO_COVERAGE
            this.listModel.addElement(path);
57
        }
58
59 1 1. <init> : removed call to com/jsql/view/swing/list/DnDList::setModel → NO_COVERAGE
        this.setModel(this.listModel);
60 1 1. <init> : removed call to com/jsql/view/swing/list/DnDList::initActionMap → NO_COVERAGE
        this.initActionMap();
61 1 1. <init> : removed call to com/jsql/view/swing/list/DnDList::initListener → NO_COVERAGE
        this.initListener();
62 1 1. <init> : removed call to com/jsql/view/swing/list/DnDList::setDragEnabled → NO_COVERAGE
        this.setDragEnabled(true);
63 1 1. <init> : removed call to com/jsql/view/swing/list/DnDList::setDropMode → NO_COVERAGE
        this.setDropMode(DropMode.INSERT);
64 1 1. <init> : removed call to com/jsql/view/swing/list/DnDList::setTransferHandler → NO_COVERAGE
        this.setTransferHandler(new ListTransfertHandler());  // Set Drag and Drop
65
    }
66
67
    private void initListener() {
68 1 1. initListener : removed call to com/jsql/view/swing/list/DnDList::addMouseListener → NO_COVERAGE
        this.addMouseListener(new MouseAdapterMenuAction(this));
69 1 1. initListener : removed call to com/jsql/view/swing/list/DnDList::addFocusListener → NO_COVERAGE
        this.addFocusListener(new FocusListener() {  // Allows color change when list loses/gains focus
70
            @Override
71
            public void focusLost(FocusEvent focusEvent) {
72 1 1. focusLost : removed call to com/jsql/view/swing/list/DnDList::repaint → NO_COVERAGE
                DnDList.this.repaint();
73
            }
74
            @Override
75
            public void focusGained(FocusEvent focusEvent) {
76 1 1. focusGained : removed call to com/jsql/view/swing/list/DnDList::repaint → NO_COVERAGE
                DnDList.this.repaint();
77
            }
78
        });
79 1 1. initListener : removed call to com/jsql/view/swing/list/DnDList::addKeyListener → NO_COVERAGE
        this.addKeyListener(new KeyAdapter() {  // Allows deleting values
80
            @Override
81
            public void keyPressed(KeyEvent keyEvent) {
82 1 1. keyPressed : negated conditional → NO_COVERAGE
                if (keyEvent.getKeyCode() == KeyEvent.VK_DELETE) {
83 1 1. keyPressed : removed call to com/jsql/view/swing/list/DnDList::removeSelectedItem → NO_COVERAGE
                    DnDList.this.removeSelectedItem();
84
                }
85
            }
86
        });
87
    }
88
89
    private void initActionMap() {
90
        var listActionMap = this.getActionMap();  // Transform Cut, selects next value
91 1 1. initActionMap : removed call to javax/swing/ActionMap::put → NO_COVERAGE
        listActionMap.put(TransferHandler.getCutAction().getValue(Action.NAME), new AbstractAction() {
92
            @Override
93
            public void actionPerformed(ActionEvent e) {
94 1 1. actionPerformed : negated conditional → NO_COVERAGE
                if (DnDList.this.getSelectedValuesList().isEmpty()) {
95
                    return;
96
                }
97
                
98
                List<ItemList> selectedValues = DnDList.this.getSelectedValuesList();
99
                List<ItemList> siblings = new ArrayList<>();
100
                for (ItemList value: selectedValues) {
101
                    int valueIndex = DnDList.this.listModel.indexOf(value);
102 3 1. actionPerformed : changed conditional boundary → NO_COVERAGE
2. actionPerformed : negated conditional → NO_COVERAGE
3. actionPerformed : Replaced integer subtraction with addition → NO_COVERAGE
                    if (valueIndex < DnDList.this.listModel.size() - 1) {
103 1 1. actionPerformed : Replaced integer addition with subtraction → NO_COVERAGE
                        siblings.add(DnDList.this.listModel.get(valueIndex + 1));
104 2 1. actionPerformed : negated conditional → NO_COVERAGE
2. actionPerformed : changed conditional boundary → NO_COVERAGE
                    } else if (valueIndex > 0) {
105 1 1. actionPerformed : Replaced integer subtraction with addition → NO_COVERAGE
                        siblings.add(DnDList.this.listModel.get(valueIndex - 1));
106
                    }
107
                }
108
109 1 1. actionPerformed : removed call to javax/swing/Action::actionPerformed → NO_COVERAGE
                TransferHandler.getCutAction().actionPerformed(e);
110
                
111
                for (ItemList sibling: siblings) {
112 1 1. actionPerformed : removed call to com/jsql/view/swing/list/DnDList::setSelectedValue → NO_COVERAGE
                    DnDList.this.setSelectedValue(sibling, true);
113
                }
114
            }
115
        });
116
117 1 1. initActionMap : removed call to javax/swing/ActionMap::put → NO_COVERAGE
        listActionMap.put(
118
            TransferHandler.getCopyAction().getValue(Action.NAME),
119
            TransferHandler.getCopyAction()
120
        );
121 1 1. initActionMap : removed call to javax/swing/ActionMap::put → NO_COVERAGE
        listActionMap.put(
122
            TransferHandler.getPasteAction().getValue(Action.NAME),
123
            TransferHandler.getPasteAction()
124
        );
125
    }
126
127
    /**
128
     * Delete selected items from the list.
129
     */
130
    public void removeSelectedItem() {
131 1 1. removeSelectedItem : negated conditional → NO_COVERAGE
        if (this.getSelectedValuesList().isEmpty()) {
132
            return;
133
        }
134
135
        List<ItemList> selectedValues = this.getSelectedValuesList();
136
        for (ItemList itemSelected: selectedValues) {
137
            int indexOfItemSelected = this.listModel.indexOf(itemSelected);
138
            this.listModel.removeElement(itemSelected);
139 1 1. removeSelectedItem : negated conditional → NO_COVERAGE
            if (indexOfItemSelected == this.listModel.getSize()) {
140 2 1. removeSelectedItem : Replaced integer subtraction with addition → NO_COVERAGE
2. removeSelectedItem : removed call to com/jsql/view/swing/list/DnDList::setSelectedIndex → NO_COVERAGE
                this.setSelectedIndex(indexOfItemSelected - 1);
141
            } else {
142 1 1. removeSelectedItem : removed call to com/jsql/view/swing/list/DnDList::setSelectedIndex → NO_COVERAGE
                this.setSelectedIndex(indexOfItemSelected);
143
            }
144
        }
145
        
146
        try {
147
            var rectangle = this.getCellBounds(
148
                this.getMinSelectionIndex(),
149
                this.getMaxSelectionIndex()
150
            );
151 1 1. removeSelectedItem : negated conditional → NO_COVERAGE
            if (rectangle != null) {
152 1 1. removeSelectedItem : removed call to com/jsql/view/swing/list/DnDList::scrollRectToVisible → NO_COVERAGE
                this.scrollRectToVisible(
153
                    this.getCellBounds(
154
                        this.getMinSelectionIndex(),
155
                        this.getMaxSelectionIndex()
156
                    )
157
                );
158
            }
159
        } catch (NullPointerException e) {
160
            // Report NullPointerException #1571 : manual scroll elsewhere then run action
161
            LOGGER.log(LogLevelUtil.CONSOLE_JAVA, e, e);
162
        }
163
    }
164
165
    /**
166
     * Load a file into the list (drag/drop or copy/paste).
167
     */
168
    public void dropPasteFile(final List<File> filesToImport, int position) {
169 1 1. dropPasteFile : negated conditional → NO_COVERAGE
        if (filesToImport.isEmpty()) {
170
            return;
171
        }
172
        
173
        for (File fileToImport : filesToImport) {
174
            // Report NoSuchMethodError #1617
175
            if (
176
                !FilenameUtils
177
                .getExtension(fileToImport.getPath())
178 1 1. dropPasteFile : negated conditional → NO_COVERAGE
                .matches("txt|csv|ini")
179
            ) {
180
                // Fix #42832: ClassCastException on showMessageDialog()
181
                try {
182 1 1. dropPasteFile : removed call to javax/swing/JOptionPane::showMessageDialog → NO_COVERAGE
                    JOptionPane.showMessageDialog(
183
                        this.getTopLevelAncestor(),
184
                        I18nUtil.valueByKey("LIST_IMPORT_ERROR_LABEL"),
185
                        I18nUtil.valueByKey("LIST_IMPORT_ERROR_TITLE"),
186
                        JOptionPane.ERROR_MESSAGE
187
                    );
188
                } catch (ClassCastException e) {
189
                    LOGGER.log(LogLevelUtil.CONSOLE_JAVA, e.getMessage(), e);
190
                }
191
                return;
192
            }
193
        }
194
195
        var options = new String[] {
196
            I18nUtil.valueByKey("LIST_IMPORT_CONFIRM_REPLACE"),
197
            I18nUtil.valueByKey("LIST_IMPORT_CONFIRM_ADD"),
198
            I18nUtil.valueByKey("LIST_ADD_VALUE_CANCEL")
199
        };
200
        int answer = JOptionPane.showOptionDialog(
201
            this.getTopLevelAncestor(),
202
            I18nUtil.valueByKey("LIST_IMPORT_CONFIRM_LABEL"),
203
            I18nUtil.valueByKey("LIST_IMPORT_CONFIRM_TITLE"),
204
            JOptionPane.YES_NO_CANCEL_OPTION,
205
            JOptionPane.QUESTION_MESSAGE,
206
            null,
207
            options,
208
            options[2]
209
        );
210 2 1. dropPasteFile : negated conditional → NO_COVERAGE
2. dropPasteFile : negated conditional → NO_COVERAGE
        if (answer != JOptionPane.YES_OPTION && answer != JOptionPane.NO_OPTION) {
211
            return;
212
        }
213
214
        int startPosition = position;
215 1 1. dropPasteFile : negated conditional → NO_COVERAGE
        if (answer == JOptionPane.YES_OPTION) {
216 1 1. dropPasteFile : removed call to javax/swing/DefaultListModel::clear → NO_COVERAGE
            this.listModel.clear();
217
            startPosition = 0;
218
        }
219
        int startPositionFinal = startPosition;
220 2 1. lambda$dropPasteFile$0 : removed call to com/jsql/view/swing/list/DnDList::addItems → NO_COVERAGE
2. dropPasteFile : removed call to javax/swing/SwingUtilities::invokeLater → NO_COVERAGE
        SwingUtilities.invokeLater(() -> this.addItems(filesToImport, startPositionFinal));
221
    }
222
223
    private void addItems(final List<File> filesToImport, int startPosition) {
224
        int endPosition = startPosition;
225
        for (File file: filesToImport) {
226
            endPosition = this.initItems(endPosition, file);
227
        }
228 1 1. addItems : negated conditional → NO_COVERAGE
        if (!this.listModel.isEmpty()) {
229 2 1. addItems : Replaced integer subtraction with addition → NO_COVERAGE
2. addItems : removed call to com/jsql/view/swing/list/DnDList::setSelectionInterval → NO_COVERAGE
            this.setSelectionInterval(startPosition, endPosition - 1);
230
        }
231
        
232
        try {
233 1 1. addItems : removed call to com/jsql/view/swing/list/DnDList::scrollRectToVisible → NO_COVERAGE
            this.scrollRectToVisible(
234
                this.getCellBounds(
235
                    this.getMinSelectionIndex(),
236
                    this.getMaxSelectionIndex()
237
                )
238
            );
239
        } catch (NullPointerException e) {
240
            // Report NullPointerException #1571 : manual scroll elsewhere then run action
241
            LOGGER.log(LogLevelUtil.CONSOLE_JAVA, e.getMessage(), e);
242
        }
243
    }
244
245
    private int initItems(int startPosition, File file) {
246
        int endPosition = startPosition;
247
        
248
        try (
249
            var fileReader = new FileReader(file, StandardCharsets.UTF_8);
250
            var bufferedReader = new BufferedReader(fileReader)
251
        ) {
252
            String line;
253 1 1. initItems : negated conditional → NO_COVERAGE
            while ((line = bufferedReader.readLine()) != null) {
254
                if (
255 3 1. initItems : negated conditional → NO_COVERAGE
2. initItems : changed conditional boundary → NO_COVERAGE
3. initItems : negated conditional → NO_COVERAGE
                    StringUtils.isNotEmpty(line)
256
                    // Fix Report #60
257
                    && 0 <= endPosition
258 2 1. initItems : changed conditional boundary → NO_COVERAGE
2. initItems : negated conditional → NO_COVERAGE
                    && endPosition <= this.listModel.size()
259
                ) {
260 2 1. initItems : Changed increment from 1 to -1 → NO_COVERAGE
2. initItems : removed call to com/jsql/view/swing/list/DnDList::addItem → NO_COVERAGE
                    this.addItem(endPosition++, line);
261
                }
262
            }
263
        } catch (IOException e) {
264
            LOGGER.log(LogLevelUtil.CONSOLE_JAVA, e.getMessage(), e);
265
        }
266
        
267 1 1. initItems : replaced int return with 0 for com/jsql/view/swing/list/DnDList::initItems → NO_COVERAGE
        return endPosition;
268
    }
269
    
270
    public void restore() {
271 1 1. restore : removed call to javax/swing/DefaultListModel::clear → NO_COVERAGE
        this.listModel.clear();
272
        for (ItemList path: this.defaultList) {
273 1 1. restore : removed call to javax/swing/DefaultListModel::addElement → NO_COVERAGE
            this.listModel.addElement(path);
274
        }
275
    }
276
    
277
    public void addItem(int endPosition, String line) {
278 1 1. addItem : removed call to javax/swing/DefaultListModel::add → NO_COVERAGE
        this.listModel.add(endPosition, new ItemList(line.replace("\\", "/")));
279
    }
280
}

Mutations

56

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

59

1.1
Location : <init>
Killed by : none
removed call to com/jsql/view/swing/list/DnDList::setModel → NO_COVERAGE

60

1.1
Location : <init>
Killed by : none
removed call to com/jsql/view/swing/list/DnDList::initActionMap → NO_COVERAGE

61

1.1
Location : <init>
Killed by : none
removed call to com/jsql/view/swing/list/DnDList::initListener → NO_COVERAGE

62

1.1
Location : <init>
Killed by : none
removed call to com/jsql/view/swing/list/DnDList::setDragEnabled → NO_COVERAGE

63

1.1
Location : <init>
Killed by : none
removed call to com/jsql/view/swing/list/DnDList::setDropMode → NO_COVERAGE

64

1.1
Location : <init>
Killed by : none
removed call to com/jsql/view/swing/list/DnDList::setTransferHandler → NO_COVERAGE

68

1.1
Location : initListener
Killed by : none
removed call to com/jsql/view/swing/list/DnDList::addMouseListener → NO_COVERAGE

69

1.1
Location : initListener
Killed by : none
removed call to com/jsql/view/swing/list/DnDList::addFocusListener → NO_COVERAGE

72

1.1
Location : focusLost
Killed by : none
removed call to com/jsql/view/swing/list/DnDList::repaint → NO_COVERAGE

76

1.1
Location : focusGained
Killed by : none
removed call to com/jsql/view/swing/list/DnDList::repaint → NO_COVERAGE

79

1.1
Location : initListener
Killed by : none
removed call to com/jsql/view/swing/list/DnDList::addKeyListener → NO_COVERAGE

82

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

83

1.1
Location : keyPressed
Killed by : none
removed call to com/jsql/view/swing/list/DnDList::removeSelectedItem → NO_COVERAGE

91

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

94

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

102

1.1
Location : actionPerformed
Killed by : none
changed conditional boundary → NO_COVERAGE

2.2
Location : actionPerformed
Killed by : none
negated conditional → NO_COVERAGE

3.3
Location : actionPerformed
Killed by : none
Replaced integer subtraction with addition → NO_COVERAGE

103

1.1
Location : actionPerformed
Killed by : none
Replaced integer addition with subtraction → NO_COVERAGE

104

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

2.2
Location : actionPerformed
Killed by : none
changed conditional boundary → NO_COVERAGE

105

1.1
Location : actionPerformed
Killed by : none
Replaced integer subtraction with addition → NO_COVERAGE

109

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

112

1.1
Location : actionPerformed
Killed by : none
removed call to com/jsql/view/swing/list/DnDList::setSelectedValue → NO_COVERAGE

117

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

121

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

131

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

139

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

140

1.1
Location : removeSelectedItem
Killed by : none
Replaced integer subtraction with addition → NO_COVERAGE

2.2
Location : removeSelectedItem
Killed by : none
removed call to com/jsql/view/swing/list/DnDList::setSelectedIndex → NO_COVERAGE

142

1.1
Location : removeSelectedItem
Killed by : none
removed call to com/jsql/view/swing/list/DnDList::setSelectedIndex → NO_COVERAGE

151

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

152

1.1
Location : removeSelectedItem
Killed by : none
removed call to com/jsql/view/swing/list/DnDList::scrollRectToVisible → NO_COVERAGE

169

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

178

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

182

1.1
Location : dropPasteFile
Killed by : none
removed call to javax/swing/JOptionPane::showMessageDialog → NO_COVERAGE

210

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

2.2
Location : dropPasteFile
Killed by : none
negated conditional → NO_COVERAGE

215

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

216

1.1
Location : dropPasteFile
Killed by : none
removed call to javax/swing/DefaultListModel::clear → NO_COVERAGE

220

1.1
Location : lambda$dropPasteFile$0
Killed by : none
removed call to com/jsql/view/swing/list/DnDList::addItems → NO_COVERAGE

2.2
Location : dropPasteFile
Killed by : none
removed call to javax/swing/SwingUtilities::invokeLater → NO_COVERAGE

228

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

229

1.1
Location : addItems
Killed by : none
Replaced integer subtraction with addition → NO_COVERAGE

2.2
Location : addItems
Killed by : none
removed call to com/jsql/view/swing/list/DnDList::setSelectionInterval → NO_COVERAGE

233

1.1
Location : addItems
Killed by : none
removed call to com/jsql/view/swing/list/DnDList::scrollRectToVisible → NO_COVERAGE

253

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

255

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

2.2
Location : initItems
Killed by : none
changed conditional boundary → NO_COVERAGE

3.3
Location : initItems
Killed by : none
negated conditional → NO_COVERAGE

258

1.1
Location : initItems
Killed by : none
changed conditional boundary → NO_COVERAGE

2.2
Location : initItems
Killed by : none
negated conditional → NO_COVERAGE

260

1.1
Location : initItems
Killed by : none
Changed increment from 1 to -1 → NO_COVERAGE

2.2
Location : initItems
Killed by : none
removed call to com/jsql/view/swing/list/DnDList::addItem → NO_COVERAGE

267

1.1
Location : initItems
Killed by : none
replaced int return with 0 for com/jsql/view/swing/list/DnDList::initItems → NO_COVERAGE

271

1.1
Location : restore
Killed by : none
removed call to javax/swing/DefaultListModel::clear → NO_COVERAGE

273

1.1
Location : restore
Killed by : none
removed call to javax/swing/DefaultListModel::addElement → NO_COVERAGE

278

1.1
Location : addItem
Killed by : none
removed call to javax/swing/DefaultListModel::add → NO_COVERAGE

Active mutators

Tests examined


Report generated by PIT 1.19.1