BatchRenamePanel.java

package edu.jiangxin.apktoolbox.file.batchrename;

import edu.jiangxin.apktoolbox.swing.extend.listener.SelectDirectoryListener;
import edu.jiangxin.apktoolbox.swing.extend.EasyPanel;
import edu.jiangxin.apktoolbox.utils.Constants;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collection;
import java.util.Set;
import java.util.TreeSet;

//https://www.zhihu.com/question/50890909
public class BatchRenamePanel extends EasyPanel {
    private static final String STRING_TYPE_PARENT_NAME = "使用父目录名";

    private static final String STRING_TYPE_PARENT_PATH = "使用目录路径";

    private static final String STRING_TYPE_SPECIALISE_STRING = "使用指定字符";

    private JPanel warningPanel;

    private JPanel sourcePanel;

    private JPanel rulePanel;

    private JPanel operationPanel;

    private JPanel statusPanel;

    private JTextField sourceTextField;
    private JTextField suffixTextField;
    private JCheckBox recursiveCheckBox;
    private JRadioButton ruleRadioButton1;
    private JRadioButton ruleRadioButton2;
    private JRadioButton ruleRadioButton3;
    private JRadioButton ruleRadioButton4;
    private JRadioButton ruleRadioButton5;
    private JRadioButton ruleRadioButton6;
    private JTextField textField21;
    private JSpinner spinner21;
    private JCheckBox checkBox31;
    private JCheckBox checkBox32;
    private JCheckBox checkBox33;
    private JTextField textField31;
    private JTextField textField32;
    private JSpinner spinner31;
    private JSpinner spinner32;
    private JSpinner spinner41;
    private JComboBox<String> comboBoxStringType;
    private JTextField textField43;
    private JSpinner spinner51;
    private JSpinner spinner52;
    private JTextField textField61;
    private JTextField textField62;
    private JTextField currentTextField;

    public BatchRenamePanel() throws HeadlessException {
        super();
    }

    @Override
    public void initUI() {
        BoxLayout boxLayout = new BoxLayout(this, BoxLayout.Y_AXIS);
        setLayout(boxLayout);

        createWarningPanel();
        add(warningPanel);
        add(Box.createVerticalStrut(Constants.DEFAULT_Y_BORDER));

        createSourcePanel();
        add(sourcePanel);
        add(Box.createVerticalStrut(Constants.DEFAULT_Y_BORDER));

        createRulePanel();
        add(rulePanel);
        add(Box.createVerticalStrut(Constants.DEFAULT_Y_BORDER));

        createOperationPanel();
        add(operationPanel);
        add(Box.createVerticalStrut(Constants.DEFAULT_Y_BORDER));

        createStatusPanel();
        add(statusPanel);
    }

    private void createWarningPanel() {
        warningPanel = new JPanel();
        warningPanel.setLayout(new BoxLayout(warningPanel, BoxLayout.X_AXIS));

        JTextArea warningTextArea = new JTextArea();
        warningTextArea.setText("It is dangerous to operate on the original files! \nPlease back up at first and check the result carefully at end!");
        warningTextArea.setForeground(Color.RED);
        warningTextArea.setFont(new Font("宋体", Font.BOLD, 20));
        warningTextArea.setBorder(null);
        warningTextArea.setEditable(false);

        warningPanel.add(warningTextArea);
        warningPanel.add(Box.createHorizontalGlue());
    }

    private void createSourcePanel() {
        sourcePanel = new JPanel();
        sourcePanel.setBorder(BorderFactory.createTitledBorder("1. 源文件"));
        sourcePanel.setLayout(new BorderLayout());

        JPanel secondLevelPanel = new JPanel();
        secondLevelPanel.setLayout(new BoxLayout(secondLevelPanel, BoxLayout.Y_AXIS));
        sourcePanel.add(secondLevelPanel);

        JPanel thirdLevelPanel1 = new JPanel();
        thirdLevelPanel1.setLayout(new BoxLayout(thirdLevelPanel1, BoxLayout.X_AXIS));

        JPanel thirdLevelPanel2 = new JPanel();
        thirdLevelPanel2.setLayout(new BoxLayout(thirdLevelPanel2, BoxLayout.X_AXIS));

        secondLevelPanel.add(thirdLevelPanel1);
        secondLevelPanel.add(Box.createHorizontalStrut(Constants.DEFAULT_X_BORDER));
        secondLevelPanel.add(thirdLevelPanel2);

        JLabel label = new JLabel("源文件所在目录:");
        sourceTextField = new JTextField();
        JButton button = new JButton("选择");
        button.addActionListener(new SelectDirectoryListener("Select a directory", sourceTextField));
        thirdLevelPanel1.add(label);
        thirdLevelPanel1.add(Box.createHorizontalStrut(Constants.DEFAULT_X_BORDER));
        thirdLevelPanel1.add(sourceTextField);
        thirdLevelPanel1.add(Box.createHorizontalStrut(Constants.DEFAULT_X_BORDER));
        thirdLevelPanel1.add(button);

        JLabel label2 = new JLabel("后缀:");
        suffixTextField = new JTextField();
        suffixTextField.setToolTipText("an array of extensions, ex. java,xml,mp4. If this parameter is empty, all files are returned.");
        recursiveCheckBox = new JCheckBox("包括子目录");

        thirdLevelPanel2.add(label2);
        thirdLevelPanel2.add(Box.createHorizontalStrut(Constants.DEFAULT_X_BORDER));
        thirdLevelPanel2.add(suffixTextField);
        thirdLevelPanel2.add(Box.createHorizontalStrut(Constants.DEFAULT_X_BORDER));
        thirdLevelPanel2.add(recursiveCheckBox);
    }

    private void createRulePanel() {
        rulePanel = new JPanel();
        rulePanel.setBorder(BorderFactory.createTitledBorder("3. 更名规则"));
        rulePanel.setLayout(new BorderLayout());

        JPanel secondLevelPanel = new JPanel();
        secondLevelPanel.setLayout(new BoxLayout(secondLevelPanel, BoxLayout.Y_AXIS));
        rulePanel.add(secondLevelPanel);

        JPanel thirdLevelPanel1 = new JPanel();
        thirdLevelPanel1.setLayout(new FlowLayout(FlowLayout.LEFT,10,3));

        JPanel thirdLevelPanel2 = new JPanel();
        thirdLevelPanel2.setLayout(new FlowLayout(FlowLayout.LEFT,10,3));

        JPanel thirdLevelPanel3 = new JPanel();
        thirdLevelPanel3.setLayout(new FlowLayout(FlowLayout.LEFT,10,3));

        JPanel thirdLevelPanel4 = new JPanel();
        thirdLevelPanel4.setLayout(new FlowLayout(FlowLayout.LEFT,10,3));

        JPanel thirdLevelPanel5 = new JPanel();
        thirdLevelPanel5.setLayout(new FlowLayout(FlowLayout.LEFT,10,3));

        JPanel thirdLevelPanel6 = new JPanel();
        thirdLevelPanel6.setLayout(new FlowLayout(FlowLayout.LEFT,10,3));

        secondLevelPanel.add(thirdLevelPanel1);
        secondLevelPanel.add(Box.createHorizontalStrut(Constants.DEFAULT_X_BORDER));
        secondLevelPanel.add(thirdLevelPanel2);
        secondLevelPanel.add(Box.createHorizontalStrut(Constants.DEFAULT_X_BORDER));
        secondLevelPanel.add(thirdLevelPanel3);
        secondLevelPanel.add(Box.createHorizontalStrut(Constants.DEFAULT_X_BORDER));
        secondLevelPanel.add(thirdLevelPanel4);
        secondLevelPanel.add(Box.createHorizontalStrut(Constants.DEFAULT_X_BORDER));
        secondLevelPanel.add(thirdLevelPanel5);
        secondLevelPanel.add(Box.createHorizontalStrut(Constants.DEFAULT_X_BORDER));
        secondLevelPanel.add(thirdLevelPanel6);

        ButtonGroup buttonGroup = new ButtonGroup();

        {
            ruleRadioButton1 = new JRadioButton("只在文件重名时才更名");
            ruleRadioButton1.setSelected(true);
            thirdLevelPanel1.add(ruleRadioButton1);
            buttonGroup.add(ruleRadioButton1);
        }

        {
            ruleRadioButton2 = new JRadioButton("将前缀{String}后的数字改为{Int}位");
            thirdLevelPanel2.add(ruleRadioButton2);
            buttonGroup.add(ruleRadioButton2);

            JLabel label21 = new JLabel("String");
            textField21 = new JTextField("");
            JLabel label22 = new JLabel("Int");
            spinner21 = new JSpinner();
            spinner21.setValue(4);
            thirdLevelPanel2.add(label21);
            thirdLevelPanel2.add(textField21);
            thirdLevelPanel2.add(label22);
            thirdLevelPanel2.add(spinner21);
        }

        {
            ruleRadioButton3 = new JRadioButton("重编号所有文件");
            thirdLevelPanel3.add(ruleRadioButton3);
            buttonGroup.add(ruleRadioButton3);

            checkBox31 = new JCheckBox("编号先按原文件名排序");
            checkBox32 = new JCheckBox("按末尾数字排序");
            checkBox33 = new JCheckBox("用目录名为前缀");
            JLabel label31 = new JLabel("分隔字符");
            textField31 = new JTextField("_");
            JLabel label32 = new JLabel("前缀");
            textField32 = new JTextField("File");
            JLabel label33 = new JLabel("起始号码");
            spinner31 = new JSpinner();
            spinner31.setValue(1);
            JLabel label34 = new JLabel("号码位数");
            spinner32 = new JSpinner();
            spinner32.setValue(3);
            thirdLevelPanel3.add(checkBox31);
            thirdLevelPanel3.add(checkBox32);
            thirdLevelPanel3.add(checkBox33);
            thirdLevelPanel3.add(label31);
            thirdLevelPanel3.add(textField31);
            thirdLevelPanel3.add(label32);
            thirdLevelPanel3.add(textField32);
            thirdLevelPanel3.add(label33);
            thirdLevelPanel3.add(spinner31);
            thirdLevelPanel3.add(label34);
            thirdLevelPanel3.add(spinner32);
        }

        {
            ruleRadioButton4 = new JRadioButton("在第{Int}个字符后添加{String}");
            thirdLevelPanel4.add(ruleRadioButton4);
            buttonGroup.add(ruleRadioButton4);

            JLabel stringType = new JLabel("字符串类型");

            comboBoxStringType = new JComboBox<>();
            comboBoxStringType.addItem(STRING_TYPE_PARENT_NAME);
            comboBoxStringType.addItem(STRING_TYPE_PARENT_PATH);
            comboBoxStringType.addItem(STRING_TYPE_SPECIALISE_STRING);
            comboBoxStringType.setSelectedItem(STRING_TYPE_SPECIALISE_STRING);
            comboBoxStringType.addItemListener(e -> textField43.setEditable(e.getItem().equals(STRING_TYPE_SPECIALISE_STRING)));

            JLabel label41 = new JLabel("Int");
            spinner41 = new JSpinner();
            spinner41.setValue(0);

            JLabel label43 = new JLabel("String");
            textField43 = new JTextField("");

            thirdLevelPanel4.add(label41);
            thirdLevelPanel4.add(spinner41);
            thirdLevelPanel4.add(label43);
            thirdLevelPanel4.add(textField43);
            thirdLevelPanel4.add(stringType);
            thirdLevelPanel4.add(comboBoxStringType);
        }

        {
            ruleRadioButton5 = new JRadioButton("删除自第{Int:1}个字符开始的{Int:2}个字符");
            thirdLevelPanel5.add(ruleRadioButton5);
            buttonGroup.add(ruleRadioButton5);

            JLabel label51 = new JLabel("Int:1");
            spinner51 = new JSpinner();
            spinner51.setValue(1);
            JLabel label52 = new JLabel("Int:2");
            spinner52 = new JSpinner();
            spinner52.setValue(1);
            thirdLevelPanel5.add(label51);
            thirdLevelPanel5.add(spinner51);
            thirdLevelPanel5.add(label52);
            thirdLevelPanel5.add(spinner52);
        }

        {
            ruleRadioButton6 = new JRadioButton("将文件名中出现的字符串{String:1}替换为{String:2}");
            thirdLevelPanel6.add(ruleRadioButton6);
            buttonGroup.add(ruleRadioButton6);

            JLabel label61 = new JLabel("String:1");
            textField61 = new JTextField("");
            JLabel label62 = new JLabel("String:2");
            textField62 = new JTextField("");
            thirdLevelPanel6.add(label61);
            thirdLevelPanel6.add(textField61);
            thirdLevelPanel6.add(label62);
            thirdLevelPanel6.add(textField62);
        }
    }

    private void createOperationPanel() {
        operationPanel = new JPanel();
        operationPanel.setBorder(BorderFactory.createTitledBorder("6. 操作"));
        operationPanel.setLayout(new BorderLayout());

        JPanel secondLevelPanel = new JPanel();
        secondLevelPanel.setLayout(new BoxLayout(secondLevelPanel, BoxLayout.X_AXIS));
        operationPanel.add(secondLevelPanel);

        JButton button = new JButton("开始");
        button.addActionListener(new StartButtonActionListener());
        secondLevelPanel.add(button);
        secondLevelPanel.add(Box.createHorizontalGlue());
    }

    private void createStatusPanel() {
        statusPanel = new JPanel();
        statusPanel.setBorder(BorderFactory.createTitledBorder("正在处理"));
        statusPanel.setLayout(new BorderLayout());

        JPanel secondLevelPanel = new JPanel();
        secondLevelPanel.setLayout(new BoxLayout(secondLevelPanel, BoxLayout.Y_AXIS));
        statusPanel.add(secondLevelPanel);

        currentTextField = new JTextField();
        secondLevelPanel.add(currentTextField);
    }

    private final class StartButtonActionListener implements ActionListener {
        @Override
        public void actionPerformed(ActionEvent e) {
            String sourceString = sourceTextField.getText();
            if (StringUtils.isEmpty(sourceString)) {
                JOptionPane.showMessageDialog(BatchRenamePanel.this, "sourceString is empty", "ERROR",
                        JOptionPane.ERROR_MESSAGE);
                return;
            }
            File sourceFile = new File(sourceString);
            if (!sourceFile.exists() || !sourceFile.isDirectory()) {
                JOptionPane.showMessageDialog(BatchRenamePanel.this, "sourceFile does not exist or not directory", "ERROR",
                        JOptionPane.ERROR_MESSAGE);
                return;
            }
            String[] extensions = null;
            if (StringUtils.isNotEmpty(suffixTextField.getText())) {
                extensions = suffixTextField.getText().split(",");
            }
            Collection<File> fileCollection = FileUtils.listFiles(sourceFile, extensions, recursiveCheckBox.isSelected());
            Set<File> fileSet = new TreeSet<>(fileCollection);
            if (fileSet.isEmpty()) {
                JOptionPane.showMessageDialog(BatchRenamePanel.this, "fileSet is empty", "ERROR",
                        JOptionPane.ERROR_MESSAGE);
                return;
            }
            for (File file : fileSet) {
                renameSingleFile(file);
            }
        }
    }

    private void renameSingleFile(File file) {
        String sourceFile = FilenameUtils.normalizeNoEndSeparator(file.getAbsolutePath());
        String sourceFileName = FilenameUtils.getName(sourceFile);
        String currentDir = FilenameUtils.getPath(sourceFile);
        String targetFileName = "";
        if (ruleRadioButton1.isSelected()) {
            logger.info("unimplemented");
        } else if (ruleRadioButton2.isSelected()) {
            targetFileName = getNewFileNameWhen2Selected(sourceFileName);
        } else if (ruleRadioButton3.isSelected()) {
            targetFileName = getNewFileNameWhen3Selected();
        } else if (ruleRadioButton4.isSelected()) {
            targetFileName = getNewFileNameWhen4Selected(file);
        } else if (ruleRadioButton5.isSelected()) {
            targetFileName = getNewFileNameWhen5Selected(sourceFileName);
        } else if (ruleRadioButton6.isSelected()) {
            targetFileName = getNewFileNameWhen6Selected(sourceFileName);
        } else {
            logger.error("ruleRadioButton select status error");
            return;
        }

        try {
            Path sourcePath = new File(sourceFile).toPath();
            Files.move(sourcePath, sourcePath.resolveSibling(targetFileName));
            StringBuilder sb = new StringBuilder();
            sb.append(currentDir).append(" [").append(sourceFileName).append("]->[").append(targetFileName).append("]");
            currentTextField.setText(sb.toString());
            logger.info(sb.toString());
        } catch (IOException e) {
            logger.error("copy file failed: IOException. current: " + sourceFile);
        }
    }

    private String getNewFileNameWhen2Selected(String oldFileName) {
        String prefix = textField21.getText();
        String sourceFileNameWithoutSuffix = FilenameUtils.getBaseName(oldFileName);
        String serialNumberStr = StringUtils.substringAfter(sourceFileNameWithoutSuffix, prefix);
        int serialNumber = Integer.parseInt(serialNumberStr);
        int digit = (Integer) spinner21.getValue();
        String format = "%0" + digit + "d";
        return oldFileName.replaceAll(serialNumberStr, String.format(format, serialNumber));
    }

    private String getNewFileNameWhen3Selected() {
        String separator = textField31.getText();
        String prefix = textField32.getText();
        int start = (Integer) spinner31.getValue();
        int digit = (Integer) spinner32.getValue();
        boolean isCheckBox31Selected = checkBox31.isSelected();
        boolean isCheckBox32Selected = checkBox32.isSelected();
        boolean isCheckBox33Selected = checkBox33.isSelected();
        logger.info("unimplemented: separator: {}, prefix: {}, start: {}, digit: {}", separator, prefix, start, digit);
        logger.info("unimplemented: isCheckBox31Selected: {}, isCheckBox32Selected: {}, isCheckBox33Selected: {}",
                isCheckBox31Selected, isCheckBox32Selected, isCheckBox33Selected);
        return "";
    }

    private String getNewFileNameWhen4Selected(File file) {
        String sourceFile = FilenameUtils.normalizeNoEndSeparator(file.getAbsolutePath());
        String sourceFileName = FilenameUtils.getName(sourceFile);
        int index = (Integer) spinner41.getValue();
        String insertString = "";
        switch ((String) comboBoxStringType.getSelectedItem()) {
            case STRING_TYPE_PARENT_PATH:
                break;
            case STRING_TYPE_PARENT_NAME:
                String parentFile = FilenameUtils.normalizeNoEndSeparator(file.getParentFile().getAbsolutePath());
                insertString = FilenameUtils.getName(parentFile);
                break;
            case STRING_TYPE_SPECIALISE_STRING:
                insertString = textField43.getText();
                break;
        }
        return StringUtils.substring(sourceFileName, 0, index) + insertString + StringUtils.substring(sourceFileName, index);
    }

    private String getNewFileNameWhen5Selected(String oldFileName) {
        int start = (Integer) spinner51.getValue();
        int length = (Integer) spinner52.getValue();
        return StringUtils.substring(oldFileName, 0, start - 1) + StringUtils.substring(oldFileName, start + length - 1);
    }

    private String getNewFileNameWhen6Selected(String oldFileName) {
        return StringUtils.replace(oldFileName, textField61.getText(), textField62.getText());
    }
}