• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

jiangxincode / ApkToolBoxGUI / #988

09 Mar 2025 11:36AM UTC coverage: 3.134% (-0.001%) from 3.135%
#988

push

jiangxincode
Merge branch 'master' of https://github.com/jiangxincode/ApkToolBoxGUI

236 of 7531 relevant lines covered (3.13%)

0.03 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/main/java/edu/jiangxin/apktoolbox/file/duplicate/DuplicateSearchPanel.java
1
package edu.jiangxin.apktoolbox.file.duplicate;
2

3
import edu.jiangxin.apktoolbox.utils.DateUtils;
4
import edu.jiangxin.apktoolbox.swing.extend.FileListPanel;
5
import edu.jiangxin.apktoolbox.swing.extend.EasyPanel;
6
import edu.jiangxin.apktoolbox.utils.Constants;
7
import edu.jiangxin.apktoolbox.utils.FileUtils;
8
import org.apache.commons.codec.digest.DigestUtils;
9
import org.apache.commons.io.FilenameUtils;
10
import org.apache.commons.lang3.StringUtils;
11

12
import javax.swing.*;
13
import javax.swing.table.DefaultTableModel;
14
import java.awt.*;
15
import java.awt.event.ActionEvent;
16
import java.awt.event.ActionListener;
17
import java.awt.event.MouseAdapter;
18
import java.awt.event.MouseEvent;
19
import java.io.*;
20
import java.util.List;
21
import java.util.*;
22

23
public class DuplicateSearchPanel extends EasyPanel {
×
24

25
    private static final long serialVersionUID = 1L;
26

27
    private JTabbedPane tabbedPane;
28

29
    private JPanel optionPanel;
30

31
    private FileListPanel fileListPanel;
32

33
    private JCheckBox isSizeChecked;
34
    private JCheckBox isFileNameChecked;
35
    private JCheckBox isMD5Checked;
36
    private JCheckBox isModifiedTimeChecked;
37

38
    private JCheckBox isHiddenFileSearched;
39
    private JCheckBox isRecursiveSearched;
40
    private JTextField suffixTextField;
41

42
    private JPanel resultPanel;
43

44
    private JTable resultTable;
45

46
    private DefaultTableModel resultTableModel;
47

48
    private JButton searchButton;
49
    private JButton cancelButton;
50

51
    private JMenuItem openDirMenuItem;
52
    private JMenuItem deleteFileMenuItem;
53
    private JMenuItem deleteFilesInSameDirMenuItem;
54
    private JMenuItem deleteFilesInSameDirRecursiveMenuItem;
55

56
    private Thread searchThread;
57

58
    final private Map<String, List<File>> duplicateFileGroupMap = new HashMap<>();
×
59

60
    @Override
61
    public void initUI() {
62
        tabbedPane = new JTabbedPane();
×
63
        add(tabbedPane);
×
64

65
        createOptionPanel();
×
66
        tabbedPane.addTab("Option", null, optionPanel, "Show Search Options");
×
67

68
        createResultPanel();
×
69
        tabbedPane.addTab("Result", null, resultPanel, "Show Search Result");
×
70
    }
×
71

72
    private void createOptionPanel() {
73
        optionPanel = new JPanel();
×
74
        optionPanel.setLayout(new BoxLayout(optionPanel, BoxLayout.Y_AXIS));
×
75

76
        fileListPanel = new FileListPanel();
×
77

78
        JPanel checkOptionPanel = new JPanel();
×
79
        checkOptionPanel.setLayout(new BoxLayout(checkOptionPanel, BoxLayout.X_AXIS));
×
80
        checkOptionPanel.setBorder(BorderFactory.createTitledBorder("Check Options"));
×
81

82
        isSizeChecked = new JCheckBox("Size");
×
83
        isSizeChecked.setSelected(true);
×
84
        isFileNameChecked = new JCheckBox("Filename");
×
85
        isMD5Checked = new JCheckBox("MD5");
×
86
        isModifiedTimeChecked = new JCheckBox("Last Modified Time");
×
87
        checkOptionPanel.add(isSizeChecked);
×
88
        checkOptionPanel.add(Box.createHorizontalStrut(Constants.DEFAULT_X_BORDER));
×
89
        checkOptionPanel.add(isFileNameChecked);
×
90
        checkOptionPanel.add(Box.createHorizontalStrut(Constants.DEFAULT_X_BORDER));
×
91
        checkOptionPanel.add(isMD5Checked);
×
92
        checkOptionPanel.add(Box.createHorizontalStrut(Constants.DEFAULT_X_BORDER));
×
93
        checkOptionPanel.add(isModifiedTimeChecked);
×
94
        checkOptionPanel.add(Box.createHorizontalGlue());
×
95

96
        JPanel searchOptionPanel = new JPanel();
×
97
        searchOptionPanel.setLayout(new BoxLayout(searchOptionPanel, BoxLayout.X_AXIS));
×
98
        searchOptionPanel.setBorder(BorderFactory.createTitledBorder("Search Options"));
×
99

100
        isHiddenFileSearched = new JCheckBox("Hidden Files");
×
101
        isRecursiveSearched = new JCheckBox("Recursive");
×
102
        isRecursiveSearched.setSelected(true);
×
103
        JLabel suffixLabel = new JLabel("Suffix: ");
×
104
        suffixTextField = new JTextField();
×
105
        suffixTextField.setToolTipText("an array of extensions, ex. {\"java\",\"xml\"}. If this parameter is empty, all files are returned.");
×
106
        searchOptionPanel.add(isHiddenFileSearched);
×
107
        searchOptionPanel.add(Box.createHorizontalStrut(Constants.DEFAULT_X_BORDER));
×
108
        searchOptionPanel.add(isRecursiveSearched);
×
109
        searchOptionPanel.add(Box.createHorizontalStrut(Constants.DEFAULT_X_BORDER));
×
110
        searchOptionPanel.add(suffixLabel);
×
111
        searchOptionPanel.add(suffixTextField);
×
112
        searchOptionPanel.add(Box.createHorizontalGlue());
×
113

114
        JPanel operationPanel = new JPanel();
×
115
        operationPanel.setLayout(new BoxLayout(operationPanel, BoxLayout.X_AXIS));
×
116
        operationPanel.setBorder(BorderFactory.createTitledBorder("Operations"));
×
117

118
        searchButton = new JButton("Search");
×
119
        cancelButton = new JButton("Cancel");
×
120
        searchButton.addActionListener(new OperationButtonActionListener());
×
121
        cancelButton.addActionListener(new OperationButtonActionListener());
×
122
        operationPanel.add(searchButton);
×
123
        operationPanel.add(Box.createHorizontalStrut(Constants.DEFAULT_X_BORDER));
×
124
        operationPanel.add(Box.createHorizontalStrut(Constants.DEFAULT_X_BORDER));
×
125
        operationPanel.add(cancelButton);
×
126
        operationPanel.add(Box.createHorizontalGlue());
×
127

128
        optionPanel.add(fileListPanel);
×
129
        optionPanel.add(Box.createVerticalStrut(Constants.DEFAULT_Y_BORDER));
×
130
        optionPanel.add(checkOptionPanel);
×
131
        optionPanel.add(Box.createVerticalStrut(Constants.DEFAULT_Y_BORDER));
×
132
        optionPanel.add(searchOptionPanel);
×
133
        optionPanel.add(Box.createVerticalStrut(Constants.DEFAULT_Y_BORDER));
×
134
        optionPanel.add(operationPanel);
×
135
    }
×
136

137
    private void createResultPanel() {
138
        resultPanel = new JPanel();
×
139
        resultPanel.setLayout(new BoxLayout(resultPanel, BoxLayout.Y_AXIS));
×
140

141
        resultTableModel = new DuplicateFilesTableModel(new Vector<>(), DuplicateFilesConstants.COLUMN_NAMES);
×
142
        resultTable = new JTable(resultTableModel);
×
143

144
        resultTable.setDefaultRenderer(Vector.class, new DuplicateFilesTableCellRenderer());
×
145

146
        for (int i = 0; i < resultTable.getColumnCount(); i++) {
×
147
            resultTable.getColumn(resultTable.getColumnName(i)).setCellRenderer(new DuplicateFilesTableCellRenderer());
×
148
        }
149

150
        resultTable.addMouseListener(new MyMouseListener());
×
151

152
        resultTable.setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
×
153

154
        JScrollPane scrollPane = new JScrollPane(resultTable);
×
155
        resultPanel.add(scrollPane);
×
156
    }
×
157

158
    public String getComparedKey(File file) {
159
        StringBuilder sb = new StringBuilder();
×
160
        if (isSizeChecked.isSelected()) {
×
161
            sb.append("[Size][");
×
162
            sb.append(DigestUtils.md5Hex(String.valueOf(file.length())));
×
163
            sb.append("]");
×
164
        }
165
        if (isFileNameChecked.isSelected()) {
×
166
            sb.append("[Filename][");
×
167
            sb.append(DigestUtils.md5Hex(file.getName()));
×
168
            sb.append("]");
×
169
        }
170
        if (isMD5Checked.isSelected()) {
×
171
            sb.append("[MD5][");
×
172
            try (InputStream is = new FileInputStream(file)) {
×
173
                sb.append(DigestUtils.md5Hex(is));
×
174
            } catch (FileNotFoundException e) {
×
175
                logger.error("getComparedKey FileNotFoundException");
×
176
            } catch (IOException e) {
×
177
                logger.error("getComparedKey IOException");
×
178
            }
×
179
            sb.append("]");
×
180
        }
181
        if (isModifiedTimeChecked.isSelected()) {
×
182
            sb.append("[ModifiedTime][");
×
183
            sb.append(DigestUtils.md5Hex(String.valueOf(file.lastModified())));
×
184
            sb.append("]");
×
185
        }
186
        logger.info("path: " + file.getAbsolutePath() + ", key: " + sb);
×
187
        return sb.toString();
×
188
    }
189

190
    class MyMouseListener extends MouseAdapter {
×
191
        @Override
192
        public void mouseReleased(MouseEvent e) {
193
            super.mouseReleased(e);
×
194
            int r = resultTable.rowAtPoint(e.getPoint());
×
195
            if (r >= 0 && r < resultTable.getRowCount()) {
×
196
                resultTable.setRowSelectionInterval(r, r);
×
197
            } else {
198
                resultTable.clearSelection();
×
199
            }
200
            int rowIndex = resultTable.getSelectedRow();
×
201
            if (rowIndex < 0) {
×
202
                return;
×
203
            }
204
            if (e.isPopupTrigger() && e.getComponent() instanceof JTable) {
×
205
                JPopupMenu popupmenu = new JPopupMenu();
×
206
                MyMenuActionListener menuActionListener = new MyMenuActionListener();
×
207

208
                openDirMenuItem = new JMenuItem("Open parent folder of this file");
×
209
                openDirMenuItem.addActionListener(menuActionListener);
×
210
                popupmenu.add(openDirMenuItem);
×
211

212
                deleteFileMenuItem = new JMenuItem("Delete this duplicate file");
×
213
                deleteFileMenuItem.addActionListener(menuActionListener);
×
214
                popupmenu.add(deleteFileMenuItem);
×
215

216
                deleteFilesInSameDirMenuItem = new JMenuItem("Delete these duplicate files in the same directory");
×
217
                deleteFilesInSameDirMenuItem.addActionListener(menuActionListener);
×
218
                popupmenu.add(deleteFilesInSameDirMenuItem);
×
219

220
                deleteFilesInSameDirRecursiveMenuItem = new JMenuItem("Delete these duplicate files in the same directory(Recursive)");
×
221
                deleteFilesInSameDirRecursiveMenuItem.addActionListener(menuActionListener);
×
222
                popupmenu.add(deleteFilesInSameDirRecursiveMenuItem);
×
223

224
                popupmenu.show(e.getComponent(), e.getX(), e.getY());
×
225
            }
226
        }
×
227
    }
228

229
    class MyMenuActionListener implements ActionListener {
×
230
        @Override
231
        public void actionPerformed(ActionEvent actionEvent) {
232
            Object source = actionEvent.getSource();
×
233
            if (source.equals(openDirMenuItem)) {
×
234
                onOpenDir();
×
235
            } else if (source.equals(deleteFileMenuItem)) {
×
236
                onDeleteFile();
×
237
            } else if (source.equals(deleteFilesInSameDirMenuItem)) {
×
238
                onDeleteFilesInSameDir();
×
239
            } else if (source.equals(deleteFilesInSameDirRecursiveMenuItem)) {
×
240
                onDeleteFilesInSameDirRecursive();
×
241
            } else {
242
                logger.error("invalid source");
×
243
            }
244
        }
×
245

246
        private void onOpenDir() {
247
            int rowIndex = resultTable.getSelectedRow();
×
248
            String parentPath = resultTableModel.getValueAt(rowIndex, resultTable.getColumn(DuplicateFilesConstants.COLUMN_NAME_FILE_PARENT).getModelIndex()).toString();
×
249
            File parent = new File(parentPath);
×
250
            if (parent.isDirectory()) {
×
251
                try {
252
                    Desktop.getDesktop().open(parent);
×
253
                } catch (IOException e) {
×
254
                    logger.error("open parent failed: " + parent.getPath());
×
255
                }
×
256
            }
257
        }
×
258

259
        private void onDeleteFile() {
260
            int rowIndex = resultTable.getSelectedRow();
×
261
            String parentPath = resultTableModel.getValueAt(rowIndex, resultTable.getColumn(DuplicateFilesConstants.COLUMN_NAME_FILE_PARENT).getModelIndex()).toString();
×
262
            String name = resultTableModel.getValueAt(rowIndex, resultTable.getColumn(DuplicateFilesConstants.COLUMN_NAME_FILE_NAME).getModelIndex()).toString();
×
263
            File selectedFile = new File(parentPath, name);
×
264
            String key = getComparedKey(selectedFile);
×
265
            List<File> files = duplicateFileGroupMap.get(key);
×
266
            for (File file : files) {
×
267
                if (!selectedFile.equals(file)) {
×
268
                    continue;
×
269
                }
270
                files.remove(file);
×
271
                boolean isSuccessful = file.delete();
×
272
                logger.info("delete file: " + file.getAbsolutePath() + ", result: " + isSuccessful);
×
273
                break;
×
274
            }
275
            resultTableModel.setRowCount(0);
×
276
            showResult();
×
277
        }
×
278

279
        private void onDeleteFilesInSameDir() {
280
            int rowIndex = resultTable.getSelectedRow();
×
281
            String parentPath = resultTableModel.getValueAt(rowIndex, resultTable.getColumn(DuplicateFilesConstants.COLUMN_NAME_FILE_PARENT).getModelIndex()).toString();
×
282
            for (Map.Entry<String, List<File>> entry : duplicateFileGroupMap.entrySet()) {
×
283
                List<File> duplicateFileGroup = entry.getValue();
×
284
                for (File duplicateFile : duplicateFileGroup) {
×
285
                    String parentPathTmp = duplicateFile.getParent();
×
286
                    if (Objects.equals(parentPath, parentPathTmp)) {
×
287
                        duplicateFileGroup.remove(duplicateFile);
×
288
                        boolean isSuccessful = duplicateFile.delete();
×
289
                        logger.info("delete file: " + duplicateFile.getAbsolutePath() + ", result: " + isSuccessful);
×
290
                        break;
×
291
                    }
292
                }
×
293
            }
×
294
            resultTableModel.setRowCount(0);
×
295
            showResult();
×
296
        }
×
297

298
        private void onDeleteFilesInSameDirRecursive() {
299
            int rowIndex = resultTable.getSelectedRow();
×
300
            String parentPath = resultTableModel.getValueAt(rowIndex, resultTable.getColumn(DuplicateFilesConstants.COLUMN_NAME_FILE_PARENT).getModelIndex()).toString();
×
301
            for (Map.Entry<String, List<File>> entry : duplicateFileGroupMap.entrySet()) {
×
302
                List<File> duplicateFileGroup = entry.getValue();
×
303
                for (File duplicateFile : duplicateFileGroup) {
×
304
                    String parentPathTmp = duplicateFile.getParent();
×
305
                    if (Objects.equals(parentPath, parentPathTmp) || FilenameUtils.directoryContains(parentPath, parentPathTmp)) {
×
306
                        duplicateFileGroup.remove(duplicateFile);
×
307
                        boolean isSuccessful = duplicateFile.delete();
×
308
                        logger.info("delete file: " + duplicateFile.getAbsolutePath() + ", result: " + isSuccessful);
×
309
                        break;
×
310
                    }
311
                }
×
312
            }
×
313
            resultTableModel.setRowCount(0);
×
314
            showResult();
×
315
        }
×
316
    }
317

318
    class OperationButtonActionListener implements ActionListener {
×
319
        @Override
320
        public void actionPerformed(ActionEvent e) {
321
            Object source = e.getSource();
×
322
            if (source.equals(searchButton)) {
×
323
                String[] extensions = null;
×
324
                if (StringUtils.isNotEmpty(suffixTextField.getText())) {
×
325
                    extensions = suffixTextField.getText().split(",");
×
326
                }
327
                searchThread = new SearchThread(extensions, isRecursiveSearched.isSelected(), isHiddenFileSearched.isSelected(), duplicateFileGroupMap);
×
328
                searchThread.start();
×
329
            } else if (source.equals(cancelButton)) {
×
330
                if (searchThread.isAlive()) {
×
331
                    searchThread.interrupt();
×
332
                }
333
            }
334

335
        }
×
336
    }
337

338
    private void showResult() {
339
        SwingUtilities.invokeLater(() -> {
×
340
            int groupIndex = 0;
×
341
            for (Map.Entry<String, List<File>> entry : duplicateFileGroupMap.entrySet()) {
×
342
                List<File> duplicateFileGroup = entry.getValue();
×
343
                if (duplicateFileGroup.size() < 2) {
×
344
                    continue;
×
345
                }
346
                groupIndex++;
×
347
                for (File duplicateFile : duplicateFileGroup) {
×
348
                    Vector<Object> rowData = getRowVector(groupIndex, duplicateFile);
×
349
                    resultTableModel.addRow(rowData);
×
350
                }
×
351
            }
×
352
            tabbedPane.setSelectedIndex(1);
×
353
        });
×
354
    }
×
355

356
    private Vector<Object> getRowVector(int groupIndex, File file) {
357
        Vector<Object> rowData = new Vector<>();
×
358
        rowData.add(groupIndex);
×
359
        rowData.add(file.getParent());
×
360
        rowData.add(file.getName());
×
361
        rowData.add(FilenameUtils.getExtension(file.getName()));
×
362
        rowData.add(FileUtils.sizeOfInHumanFormat(file));
×
363
        rowData.add(DateUtils.millisecondToHumanFormat(file.lastModified()));
×
364
        return rowData;
×
365
    }
366

367
    class SearchThread extends Thread {
368
        private String[] extensions;
369
        private boolean isRecursiveSearched;
370
        private boolean isHiddenFileSearched;
371
        private Map<String, List<File>> duplicateFileGroupMap;
372

373
        public SearchThread(String[] extensions, boolean isRecursiveSearched, boolean isHiddenFileSearched, Map<String, List<File>> duplicateFileGroupMap) {
×
374
            super();
×
375
            this.extensions = extensions;
×
376
            this.isRecursiveSearched = isRecursiveSearched;
×
377
            this.isHiddenFileSearched = isHiddenFileSearched;
×
378
            this.duplicateFileGroupMap = duplicateFileGroupMap;
×
379
        }
×
380

381
        @Override
382
        public void run() {
383
            super.run();
×
384
            duplicateFileGroupMap.clear();
×
385
            SwingUtilities.invokeLater(() -> {
×
386
                resultTableModel.setRowCount(0);
×
387
            });
×
388
            List<File> fileList = fileListPanel.getFileList();
×
389

390
            Set<File> fileSet = new TreeSet<>(fileList);
×
391
            for (File file : fileList) {
×
392
                fileSet.addAll(org.apache.commons.io.FileUtils.listFiles(file, extensions, isRecursiveSearched));
×
393
            }
×
394

395
            for (File file : fileSet) {
×
396
                if (Thread.currentThread().isInterrupted()) {
×
397
                    break;
×
398
                }
399
                if (file.isHidden() && !isHiddenFileSearched) {
×
400
                    continue;
×
401
                }
402
                String hash = getComparedKey(file);
×
403
                List<File> list = duplicateFileGroupMap.computeIfAbsent(hash, k -> new LinkedList<>());
×
404
                list.add(file);
×
405
            }
×
406
            showResult();
×
407
        }
×
408
    }
409
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc