1 package edu.jiangxin.apktoolbox.file.batchrename;
2
3 import edu.jiangxin.apktoolbox.swing.extend.listener.SelectDirectoryListener;
4 import edu.jiangxin.apktoolbox.swing.extend.EasyPanel;
5 import edu.jiangxin.apktoolbox.utils.Constants;
6 import org.apache.commons.io.FileUtils;
7 import org.apache.commons.io.FilenameUtils;
8 import org.apache.commons.lang3.StringUtils;
9 import org.apache.commons.lang3.Strings;
10
11 import javax.swing.*;
12 import java.awt.*;
13 import java.awt.event.ActionEvent;
14 import java.awt.event.ActionListener;
15 import java.io.File;
16 import java.io.IOException;
17 import java.io.Serial;
18 import java.nio.file.Files;
19 import java.nio.file.Path;
20 import java.util.Collection;
21 import java.util.Set;
22 import java.util.TreeSet;
23
24
25 public class BatchRenamePanel extends EasyPanel {
26 @Serial
27 private static final long serialVersionUID = 1L;
28
29 private static final String STRING_TYPE_PARENT_NAME = "使用父目录名";
30
31 private static final String STRING_TYPE_PARENT_PATH = "使用目录路径";
32
33 private static final String STRING_TYPE_SPECIALISE_STRING = "使用指定字符";
34
35 private JPanel warningPanel;
36
37 private JPanel sourcePanel;
38
39 private JPanel rulePanel;
40
41 private JPanel operationPanel;
42
43 private JPanel statusPanel;
44
45 private JTextField sourceTextField;
46 private JTextField suffixTextField;
47 private JCheckBox recursiveCheckBox;
48 private JRadioButton ruleRadioButton1;
49 private JRadioButton ruleRadioButton2;
50 private JRadioButton ruleRadioButton3;
51 private JRadioButton ruleRadioButton4;
52 private JRadioButton ruleRadioButton5;
53 private JRadioButton ruleRadioButton6;
54 private JTextField textField21;
55 private JSpinner spinner21;
56 private JCheckBox checkBox31;
57 private JCheckBox checkBox32;
58 private JCheckBox checkBox33;
59 private JTextField textField31;
60 private JTextField textField32;
61 private JSpinner spinner31;
62 private JSpinner spinner32;
63 private JSpinner spinner41;
64 private JComboBox<String> comboBoxStringType;
65 private JTextField textField43;
66 private JSpinner spinner51;
67 private JSpinner spinner52;
68 private JTextField textField61;
69 private JTextField textField62;
70 private JTextField currentTextField;
71
72 public BatchRenamePanel() throws HeadlessException {
73 super();
74 }
75
76 @Override
77 public void initUI() {
78 BoxLayout boxLayout = new BoxLayout(this, BoxLayout.Y_AXIS);
79 setLayout(boxLayout);
80
81 createWarningPanel();
82 add(warningPanel);
83 add(Box.createVerticalStrut(Constants.DEFAULT_Y_BORDER));
84
85 createSourcePanel();
86 add(sourcePanel);
87 add(Box.createVerticalStrut(Constants.DEFAULT_Y_BORDER));
88
89 createRulePanel();
90 add(rulePanel);
91 add(Box.createVerticalStrut(Constants.DEFAULT_Y_BORDER));
92
93 createOperationPanel();
94 add(operationPanel);
95 add(Box.createVerticalStrut(Constants.DEFAULT_Y_BORDER));
96
97 createStatusPanel();
98 add(statusPanel);
99 }
100
101 private void createWarningPanel() {
102 warningPanel = new JPanel();
103 warningPanel.setLayout(new BoxLayout(warningPanel, BoxLayout.X_AXIS));
104
105 JTextArea warningTextArea = new JTextArea();
106 warningTextArea.setText("It is dangerous to operate on the original files! \nPlease back up at first and check the result carefully at end!");
107 warningTextArea.setForeground(Color.RED);
108 warningTextArea.setFont(new Font("宋体", Font.BOLD, 20));
109 warningTextArea.setBorder(null);
110 warningTextArea.setEditable(false);
111
112 warningPanel.add(warningTextArea);
113 warningPanel.add(Box.createHorizontalGlue());
114 }
115
116 private void createSourcePanel() {
117 sourcePanel = new JPanel();
118 sourcePanel.setBorder(BorderFactory.createTitledBorder("1. 源文件"));
119 sourcePanel.setLayout(new BorderLayout());
120
121 JPanel secondLevelPanel = new JPanel();
122 secondLevelPanel.setLayout(new BoxLayout(secondLevelPanel, BoxLayout.Y_AXIS));
123 sourcePanel.add(secondLevelPanel);
124
125 JPanel thirdLevelPanel1 = new JPanel();
126 thirdLevelPanel1.setLayout(new BoxLayout(thirdLevelPanel1, BoxLayout.X_AXIS));
127
128 JPanel thirdLevelPanel2 = new JPanel();
129 thirdLevelPanel2.setLayout(new BoxLayout(thirdLevelPanel2, BoxLayout.X_AXIS));
130
131 secondLevelPanel.add(thirdLevelPanel1);
132 secondLevelPanel.add(Box.createHorizontalStrut(Constants.DEFAULT_X_BORDER));
133 secondLevelPanel.add(thirdLevelPanel2);
134
135 JLabel label = new JLabel("源文件所在目录:");
136 sourceTextField = new JTextField();
137 JButton button = new JButton("选择");
138 button.addActionListener(new SelectDirectoryListener("Select a directory", sourceTextField));
139 thirdLevelPanel1.add(label);
140 thirdLevelPanel1.add(Box.createHorizontalStrut(Constants.DEFAULT_X_BORDER));
141 thirdLevelPanel1.add(sourceTextField);
142 thirdLevelPanel1.add(Box.createHorizontalStrut(Constants.DEFAULT_X_BORDER));
143 thirdLevelPanel1.add(button);
144
145 JLabel label2 = new JLabel("后缀:");
146 suffixTextField = new JTextField();
147 suffixTextField.setToolTipText("an array of extensions, ex. java,xml,mp4. If this parameter is empty, all files are returned.");
148 recursiveCheckBox = new JCheckBox("包括子目录");
149
150 thirdLevelPanel2.add(label2);
151 thirdLevelPanel2.add(Box.createHorizontalStrut(Constants.DEFAULT_X_BORDER));
152 thirdLevelPanel2.add(suffixTextField);
153 thirdLevelPanel2.add(Box.createHorizontalStrut(Constants.DEFAULT_X_BORDER));
154 thirdLevelPanel2.add(recursiveCheckBox);
155 }
156
157 private void createRulePanel() {
158 rulePanel = new JPanel();
159 rulePanel.setBorder(BorderFactory.createTitledBorder("3. 更名规则"));
160 rulePanel.setLayout(new BorderLayout());
161
162 JPanel secondLevelPanel = new JPanel();
163 secondLevelPanel.setLayout(new BoxLayout(secondLevelPanel, BoxLayout.Y_AXIS));
164 rulePanel.add(secondLevelPanel);
165
166 JPanel thirdLevelPanel1 = new JPanel();
167 thirdLevelPanel1.setLayout(new FlowLayout(FlowLayout.LEFT,10,3));
168
169 JPanel thirdLevelPanel2 = new JPanel();
170 thirdLevelPanel2.setLayout(new FlowLayout(FlowLayout.LEFT,10,3));
171
172 JPanel thirdLevelPanel3 = new JPanel();
173 thirdLevelPanel3.setLayout(new FlowLayout(FlowLayout.LEFT,10,3));
174
175 JPanel thirdLevelPanel4 = new JPanel();
176 thirdLevelPanel4.setLayout(new FlowLayout(FlowLayout.LEFT,10,3));
177
178 JPanel thirdLevelPanel5 = new JPanel();
179 thirdLevelPanel5.setLayout(new FlowLayout(FlowLayout.LEFT,10,3));
180
181 JPanel thirdLevelPanel6 = new JPanel();
182 thirdLevelPanel6.setLayout(new FlowLayout(FlowLayout.LEFT,10,3));
183
184 secondLevelPanel.add(thirdLevelPanel1);
185 secondLevelPanel.add(Box.createHorizontalStrut(Constants.DEFAULT_X_BORDER));
186 secondLevelPanel.add(thirdLevelPanel2);
187 secondLevelPanel.add(Box.createHorizontalStrut(Constants.DEFAULT_X_BORDER));
188 secondLevelPanel.add(thirdLevelPanel3);
189 secondLevelPanel.add(Box.createHorizontalStrut(Constants.DEFAULT_X_BORDER));
190 secondLevelPanel.add(thirdLevelPanel4);
191 secondLevelPanel.add(Box.createHorizontalStrut(Constants.DEFAULT_X_BORDER));
192 secondLevelPanel.add(thirdLevelPanel5);
193 secondLevelPanel.add(Box.createHorizontalStrut(Constants.DEFAULT_X_BORDER));
194 secondLevelPanel.add(thirdLevelPanel6);
195
196 ButtonGroup buttonGroup = new ButtonGroup();
197
198 {
199 ruleRadioButton1 = new JRadioButton("只在文件重名时才更名");
200 ruleRadioButton1.setSelected(true);
201 thirdLevelPanel1.add(ruleRadioButton1);
202 buttonGroup.add(ruleRadioButton1);
203 }
204
205 {
206 ruleRadioButton2 = new JRadioButton("将前缀{String}后的数字改为{Int}位");
207 thirdLevelPanel2.add(ruleRadioButton2);
208 buttonGroup.add(ruleRadioButton2);
209
210 JLabel label21 = new JLabel("String");
211 textField21 = new JTextField("");
212 JLabel label22 = new JLabel("Int");
213 spinner21 = new JSpinner();
214 spinner21.setValue(4);
215 thirdLevelPanel2.add(label21);
216 thirdLevelPanel2.add(textField21);
217 thirdLevelPanel2.add(label22);
218 thirdLevelPanel2.add(spinner21);
219 }
220
221 {
222 ruleRadioButton3 = new JRadioButton("重编号所有文件");
223 thirdLevelPanel3.add(ruleRadioButton3);
224 buttonGroup.add(ruleRadioButton3);
225
226 checkBox31 = new JCheckBox("编号先按原文件名排序");
227 checkBox32 = new JCheckBox("按末尾数字排序");
228 checkBox33 = new JCheckBox("用目录名为前缀");
229 JLabel label31 = new JLabel("分隔字符");
230 textField31 = new JTextField("_");
231 JLabel label32 = new JLabel("前缀");
232 textField32 = new JTextField("File");
233 JLabel label33 = new JLabel("起始号码");
234 spinner31 = new JSpinner();
235 spinner31.setValue(1);
236 JLabel label34 = new JLabel("号码位数");
237 spinner32 = new JSpinner();
238 spinner32.setValue(3);
239 thirdLevelPanel3.add(checkBox31);
240 thirdLevelPanel3.add(checkBox32);
241 thirdLevelPanel3.add(checkBox33);
242 thirdLevelPanel3.add(label31);
243 thirdLevelPanel3.add(textField31);
244 thirdLevelPanel3.add(label32);
245 thirdLevelPanel3.add(textField32);
246 thirdLevelPanel3.add(label33);
247 thirdLevelPanel3.add(spinner31);
248 thirdLevelPanel3.add(label34);
249 thirdLevelPanel3.add(spinner32);
250 }
251
252 {
253 ruleRadioButton4 = new JRadioButton("在第{Int}个字符后添加{String}");
254 thirdLevelPanel4.add(ruleRadioButton4);
255 buttonGroup.add(ruleRadioButton4);
256
257 JLabel stringType = new JLabel("字符串类型");
258
259 comboBoxStringType = new JComboBox<>();
260 comboBoxStringType.addItem(STRING_TYPE_PARENT_NAME);
261 comboBoxStringType.addItem(STRING_TYPE_PARENT_PATH);
262 comboBoxStringType.addItem(STRING_TYPE_SPECIALISE_STRING);
263 comboBoxStringType.setSelectedItem(STRING_TYPE_SPECIALISE_STRING);
264 comboBoxStringType.addItemListener(e -> textField43.setEditable(e.getItem().equals(STRING_TYPE_SPECIALISE_STRING)));
265
266 JLabel label41 = new JLabel("Int");
267 spinner41 = new JSpinner();
268 spinner41.setValue(0);
269
270 JLabel label43 = new JLabel("String");
271 textField43 = new JTextField("");
272
273 thirdLevelPanel4.add(label41);
274 thirdLevelPanel4.add(spinner41);
275 thirdLevelPanel4.add(label43);
276 thirdLevelPanel4.add(textField43);
277 thirdLevelPanel4.add(stringType);
278 thirdLevelPanel4.add(comboBoxStringType);
279 }
280
281 {
282 ruleRadioButton5 = new JRadioButton("删除自第{Int:1}个字符开始的{Int:2}个字符");
283 thirdLevelPanel5.add(ruleRadioButton5);
284 buttonGroup.add(ruleRadioButton5);
285
286 JLabel label51 = new JLabel("Int:1");
287 spinner51 = new JSpinner();
288 spinner51.setValue(1);
289 JLabel label52 = new JLabel("Int:2");
290 spinner52 = new JSpinner();
291 spinner52.setValue(1);
292 thirdLevelPanel5.add(label51);
293 thirdLevelPanel5.add(spinner51);
294 thirdLevelPanel5.add(label52);
295 thirdLevelPanel5.add(spinner52);
296 }
297
298 {
299 ruleRadioButton6 = new JRadioButton("将文件名中出现的字符串{String:1}替换为{String:2}");
300 thirdLevelPanel6.add(ruleRadioButton6);
301 buttonGroup.add(ruleRadioButton6);
302
303 JLabel label61 = new JLabel("String:1");
304 textField61 = new JTextField("");
305 JLabel label62 = new JLabel("String:2");
306 textField62 = new JTextField("");
307 thirdLevelPanel6.add(label61);
308 thirdLevelPanel6.add(textField61);
309 thirdLevelPanel6.add(label62);
310 thirdLevelPanel6.add(textField62);
311 }
312 }
313
314 private void createOperationPanel() {
315 operationPanel = new JPanel();
316 operationPanel.setBorder(BorderFactory.createTitledBorder("6. 操作"));
317 operationPanel.setLayout(new BorderLayout());
318
319 JPanel secondLevelPanel = new JPanel();
320 secondLevelPanel.setLayout(new BoxLayout(secondLevelPanel, BoxLayout.X_AXIS));
321 operationPanel.add(secondLevelPanel);
322
323 JButton button = new JButton("开始");
324 button.addActionListener(new StartButtonActionListener());
325 secondLevelPanel.add(button);
326 secondLevelPanel.add(Box.createHorizontalGlue());
327 }
328
329 private void createStatusPanel() {
330 statusPanel = new JPanel();
331 statusPanel.setBorder(BorderFactory.createTitledBorder("正在处理"));
332 statusPanel.setLayout(new BorderLayout());
333
334 JPanel secondLevelPanel = new JPanel();
335 secondLevelPanel.setLayout(new BoxLayout(secondLevelPanel, BoxLayout.Y_AXIS));
336 statusPanel.add(secondLevelPanel);
337
338 currentTextField = new JTextField();
339 secondLevelPanel.add(currentTextField);
340 }
341
342 private final class StartButtonActionListener implements ActionListener {
343 @Override
344 public void actionPerformed(ActionEvent e) {
345 String sourceString = sourceTextField.getText();
346 if (StringUtils.isEmpty(sourceString)) {
347 JOptionPane.showMessageDialog(BatchRenamePanel.this, "sourceString is empty", "ERROR",
348 JOptionPane.ERROR_MESSAGE);
349 return;
350 }
351 File sourceFile = new File(sourceString);
352 if (!sourceFile.exists() || !sourceFile.isDirectory()) {
353 JOptionPane.showMessageDialog(BatchRenamePanel.this, "sourceFile does not exist or not directory", "ERROR",
354 JOptionPane.ERROR_MESSAGE);
355 return;
356 }
357 String[] extensions = null;
358 if (StringUtils.isNotEmpty(suffixTextField.getText())) {
359 extensions = suffixTextField.getText().split(",");
360 }
361 Collection<File> fileCollection = FileUtils.listFiles(sourceFile, extensions, recursiveCheckBox.isSelected());
362 Set<File> fileSet = new TreeSet<>(fileCollection);
363 if (fileSet.isEmpty()) {
364 JOptionPane.showMessageDialog(BatchRenamePanel.this, "fileSet is empty", "ERROR",
365 JOptionPane.ERROR_MESSAGE);
366 return;
367 }
368 for (File file : fileSet) {
369 renameSingleFile(file);
370 }
371 }
372 }
373
374 private void renameSingleFile(File file) {
375 String sourceFile = FilenameUtils.normalizeNoEndSeparator(file.getAbsolutePath());
376 String sourceFileName = FilenameUtils.getName(sourceFile);
377 String currentDir = FilenameUtils.getPath(sourceFile);
378 String targetFileName = "";
379 if (ruleRadioButton1.isSelected()) {
380 logger.info("unimplemented");
381 } else if (ruleRadioButton2.isSelected()) {
382 targetFileName = getNewFileNameWhen2Selected(sourceFileName);
383 } else if (ruleRadioButton3.isSelected()) {
384 targetFileName = getNewFileNameWhen3Selected();
385 } else if (ruleRadioButton4.isSelected()) {
386 targetFileName = getNewFileNameWhen4Selected(file);
387 } else if (ruleRadioButton5.isSelected()) {
388 targetFileName = getNewFileNameWhen5Selected(sourceFileName);
389 } else if (ruleRadioButton6.isSelected()) {
390 targetFileName = getNewFileNameWhen6Selected(sourceFileName);
391 } else {
392 logger.error("ruleRadioButton select status error");
393 return;
394 }
395
396 try {
397 Path sourcePath = new File(sourceFile).toPath();
398 Files.move(sourcePath, sourcePath.resolveSibling(targetFileName));
399 StringBuilder sb = new StringBuilder();
400 sb.append(currentDir).append(" [").append(sourceFileName).append("]->[").append(targetFileName).append("]");
401 currentTextField.setText(sb.toString());
402 logger.info(sb.toString());
403 } catch (IOException e) {
404 logger.error("copy file failed: IOException. current: " + sourceFile);
405 }
406 }
407
408 private String getNewFileNameWhen2Selected(String oldFileName) {
409 String prefix = textField21.getText();
410 String sourceFileNameWithoutSuffix = FilenameUtils.getBaseName(oldFileName);
411 String serialNumberStr = StringUtils.substringAfter(sourceFileNameWithoutSuffix, prefix);
412 int serialNumber = Integer.parseInt(serialNumberStr);
413 int digit = (Integer) spinner21.getValue();
414 String format = "%0" + digit + "d";
415 return oldFileName.replaceAll(serialNumberStr, String.format(format, serialNumber));
416 }
417
418 private String getNewFileNameWhen3Selected() {
419 String separator = textField31.getText();
420 String prefix = textField32.getText();
421 int start = (Integer) spinner31.getValue();
422 int digit = (Integer) spinner32.getValue();
423 boolean isCheckBox31Selected = checkBox31.isSelected();
424 boolean isCheckBox32Selected = checkBox32.isSelected();
425 boolean isCheckBox33Selected = checkBox33.isSelected();
426 logger.info("unimplemented: separator: {}, prefix: {}, start: {}, digit: {}", separator, prefix, start, digit);
427 logger.info("unimplemented: isCheckBox31Selected: {}, isCheckBox32Selected: {}, isCheckBox33Selected: {}",
428 isCheckBox31Selected, isCheckBox32Selected, isCheckBox33Selected);
429 return "";
430 }
431
432 private String getNewFileNameWhen4Selected(File file) {
433 String sourceFile = FilenameUtils.normalizeNoEndSeparator(file.getAbsolutePath());
434 String sourceFileName = FilenameUtils.getName(sourceFile);
435 int index = (Integer) spinner41.getValue();
436 String insertString = "";
437 switch ((String) comboBoxStringType.getSelectedItem()) {
438 case STRING_TYPE_PARENT_PATH:
439 break;
440 case STRING_TYPE_PARENT_NAME:
441 String parentFile = FilenameUtils.normalizeNoEndSeparator(file.getParentFile().getAbsolutePath());
442 insertString = FilenameUtils.getName(parentFile);
443 break;
444 case STRING_TYPE_SPECIALISE_STRING:
445 insertString = textField43.getText();
446 break;
447 }
448 return StringUtils.substring(sourceFileName, 0, index) + insertString + StringUtils.substring(sourceFileName, index);
449 }
450
451 private String getNewFileNameWhen5Selected(String oldFileName) {
452 int start = (Integer) spinner51.getValue();
453 int length = (Integer) spinner52.getValue();
454 return StringUtils.substring(oldFileName, 0, start - 1) + StringUtils.substring(oldFileName, start + length - 1);
455 }
456
457 private String getNewFileNameWhen6Selected(String oldFileName) {
458 return Strings.CS.replace(oldFileName, textField61.getText(), textField62.getText());
459 }
460 }