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.lang3.StringUtils;
16
import org.apache.logging.log4j.LogManager;
17
import org.apache.logging.log4j.Logger;
18
19
import javax.swing.*;
20
import java.awt.event.*;
21
import java.io.BufferedReader;
22
import java.io.File;
23
import java.io.FileReader;
24
import java.io.IOException;
25
import java.nio.charset.StandardCharsets;
26
import java.nio.file.Files;
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) throws IOException {
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
            String type = Files.probeContentType(fileToImport.toPath());
176 2 1. dropPasteFile : negated conditional → NO_COVERAGE
2. dropPasteFile : negated conditional → NO_COVERAGE
            if (type != null && !type.startsWith("text")) {
177
                // Fix #42832: ClassCastException on showMessageDialog()
178
                try {
179 1 1. dropPasteFile : removed call to javax/swing/JOptionPane::showMessageDialog → NO_COVERAGE
                    JOptionPane.showMessageDialog(
180
                        this.getTopLevelAncestor(),
181
                        I18nUtil.valueByKey("LIST_IMPORT_ERROR_LABEL"),
182
                        I18nUtil.valueByKey("LIST_IMPORT_ERROR_TITLE"),
183
                        JOptionPane.ERROR_MESSAGE
184
                    );
185
                } catch (ClassCastException e) {
186
                    LOGGER.log(LogLevelUtil.CONSOLE_JAVA, e, e);
187
                }
188
                return;
189
            }
190
        }
191
192
        int answer = JOptionPane.YES_OPTION;
193 1 1. dropPasteFile : negated conditional → NO_COVERAGE
        if (!this.listModel.isEmpty()) {
194
            var options = new String[]{
195
                I18nUtil.valueByKey("LIST_IMPORT_CONFIRM_REPLACE"),
196
                I18nUtil.valueByKey("LIST_IMPORT_CONFIRM_ADD"),
197
                I18nUtil.valueByKey("LIST_ADD_VALUE_CANCEL")
198
            };
199
            answer = JOptionPane.showOptionDialog(
200
                this.getTopLevelAncestor(),
201
                I18nUtil.valueByKey("LIST_IMPORT_CONFIRM_LABEL"),
202
                I18nUtil.valueByKey("LIST_IMPORT_CONFIRM_TITLE"),
203
                JOptionPane.YES_NO_CANCEL_OPTION,
204
                JOptionPane.QUESTION_MESSAGE,
205
                null,
206
                options,
207
                options[2]
208
            );
209 2 1. dropPasteFile : negated conditional → NO_COVERAGE
2. dropPasteFile : negated conditional → NO_COVERAGE
            if (answer != JOptionPane.YES_OPTION && answer != JOptionPane.NO_OPTION) {
210
                return;
211
            }
212
        }
213
214
        int startPosition = Math.max(position, 0);  // can be -1 when empty JList
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, 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 2 1. initItems : changed conditional boundary → NO_COVERAGE
2. initItems : negated conditional → NO_COVERAGE
                    && 0 <= endPosition && endPosition <= this.listModel.size()
258
                ) {
259 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);
260
                }
261
            }
262
        } catch (IOException e) {
263
            LOGGER.log(LogLevelUtil.CONSOLE_JAVA, e, e);
264
        }
265
        
266 1 1. initItems : replaced int return with 0 for com/jsql/view/swing/list/DnDList::initItems → NO_COVERAGE
        return endPosition;
267
    }
268
    
269
    public void restore() {
270 1 1. restore : removed call to javax/swing/DefaultListModel::clear → NO_COVERAGE
        this.listModel.clear();
271
        for (ItemList path: this.defaultList) {
272 1 1. restore : removed call to javax/swing/DefaultListModel::addElement → NO_COVERAGE
            this.listModel.addElement(path);
273
        }
274
    }
275
    
276
    public void addItem(int endPosition, String line) {
277 1 1. addItem : removed call to javax/swing/DefaultListModel::add → NO_COVERAGE
        this.listModel.add(endPosition, new ItemList(line.replace("\\", "/")));
278
    }
279
}

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

176

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

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

179

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

193

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

209

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

257

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

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

259

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

266

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

270

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

272

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

277

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.22.1