View Javadoc
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  //https://www.zhihu.com/question/50890909
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 }