<|start_filename|>src/main/java/bs/tool/commongui/utils/LanguageUtils.java<|end_filename|>
package bs.tool.commongui.utils;

public class LanguageUtils {

    /**
     * JAVA.
     */
    public static final String CONST_JAVA = "JAVA";

    /**
     * SQL.
     */
    public static final String CONST_SQL = "SQL";

    /**
     * HTML.
     */
    public static final String CONST_HTML = "HTML";

    /**
     * XML.
     */
    public static final String CONST_XML = "XML";

    /**
     * JavaScript.
     */
    public static final String CONST_JAVASCRIPT = "JavaScript";

    /**
     * URI.
     */
    public static final String CONST_URI = "URI";

    /**
     * JSON.
     */
    public static final String CONST_JSON = "JSON";

    /**
     * CSS.
     */
    public static final String CONST_CSS = "CSS";

    /**
     * CSV.
     */
    public static final String CONST_CSV = "CSV";

}


<|start_filename|>src/main/java/bs/tool/commongui/plugins/more/CodeFormatter.java<|end_filename|>
package bs.tool.commongui.plugins.more;

import bs.tool.commongui.AbstractGuiJPanel;
import bs.tool.commongui.GuiUtils;
import bs.tool.commongui.utils.CollectionUtils;
import bs.tool.commongui.utils.SimpleMouseListener;
import com.google.gson.*;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;

/**
 * Code格式化.
 */
public class CodeFormatter extends AbstractGuiJPanel {

    private static final long serialVersionUID = 1L;

    /**
     * 文本域.
     */
    private JTextArea textArea = createJTextArea(GuiUtils.font14_un);

    /**
     * Code类型.
     */
    private String[] codeTypes = new String[]{GuiUtils.CODE_TYPE_JSON, GuiUtils.CODE_TYPE_PROPERTIES, GuiUtils.CODE_TYPE_YML,
            GuiUtils.CODE_TYPE_XML, GuiUtils.CODE_TYPE_JAVA, GuiUtils.CODE_TYPE_JS, GuiUtils.CODE_TYPE_PYTHON, GuiUtils.CODE_TYPE_SQL};

    /**
     * 当前Code类型.
     */
    private String curCodeType = codeTypes[0];

    public CodeFormatter() {
        // 边界布局
        setLayout(new BorderLayout());
        JPanel textAreaPanel = new JPanel(new GridLayout(1, 1));
        add(textAreaPanel, BorderLayout.CENTER);

        JPanel textPanel = new JPanel(new BorderLayout());
        addJLabel(textPanel, " Code: ", GuiUtils.font14b_cn, BorderLayout.WEST);
        textPanel.add(new JScrollPane(textArea), BorderLayout.CENTER);
        textAreaPanel.add(textPanel);

        // East，操作区域，使用BorderLayout布局
        JPanel actionPanel = new JPanel(new BorderLayout());
        add(actionPanel, BorderLayout.EAST);

        // 填充
        actionPanel.add(new JPanel(), BorderLayout.CENTER);

        // 放置按钮
        JPanel buttonPanel = new JPanel(new GridLayout(6, 1));
        actionPanel.add(buttonPanel, BorderLayout.SOUTH);

        JPanel codeTypePanel = new JPanel(new FlowLayout());
        addJLabel(codeTypePanel, "类型:", GuiUtils.font14b_cn);
        // 类型下拉框
        addJComboBox(codeTypePanel, codeTypes, GuiUtils.font13, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                curCodeType = ((JComboBox) event.getSource()).getSelectedItem().toString();
            }
        });
        buttonPanel.add(codeTypePanel);
        // 仅做填充
        buttonPanel.add(new JPanel());

        // 格式化
        addJButton(buttonPanel, " 格式化 ", "", GuiUtils.font14b_cn, new SimpleMouseListener() {
            @Override
            public void mouseReleased(MouseEvent event) {
                String input = textArea.getText();
                if (curCodeType.equals(GuiUtils.CODE_TYPE_JSON)) {
                    textArea.setText(prettyJson(input, false));
                } else if (curCodeType.equals(GuiUtils.CODE_TYPE_PROPERTIES)) {
                    textArea.setText(prettyProperties(input, false));
                } else {
                    showMessage("当前仅支持JSON, Properties格式化！", "提示", JOptionPane.INFORMATION_MESSAGE);
                }
            }
        });

        // 反格式化
        addJButton(buttonPanel, " 反格式化 ", "", GuiUtils.font14b_cn, new SimpleMouseListener() {
            @Override
            public void mouseReleased(MouseEvent event) {
                String input = textArea.getText();
                if (curCodeType.equals(GuiUtils.CODE_TYPE_JSON)) {
                    textArea.setText(unPrettyJson(input));
                } else {
                    showMessage("当前仅支持JSON反格式化！", "提示", JOptionPane.INFORMATION_MESSAGE);
                }
            }
        });

        // 初始化元素值
        addJButton(buttonPanel, " 初始化元素值 ", "", GuiUtils.font14b_cn, new SimpleMouseListener() {
            @Override
            public void mouseReleased(MouseEvent event) {
                String input = textArea.getText();
                if (curCodeType.equals(GuiUtils.CODE_TYPE_JSON)) {
                    textArea.setText(prettyJson(input, true));
                } else if (curCodeType.equals(GuiUtils.CODE_TYPE_PROPERTIES)) {
                    textArea.setText(prettyProperties(input, true));
                } else {
                    showMessage("当前仅支持JSON, Properties初始化元素值！", "提示", JOptionPane.INFORMATION_MESSAGE);
                }
            }
        });

        // 清空
        addJButton(buttonPanel, " 清  空 ", "", GuiUtils.font14_cn, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                textArea.setText("");
            }
        });
    }

    /**
     * 格式化json.
     *
     * @param jsonStr
     * @param reInitItem
     * @return
     */
    public String prettyJson(String jsonStr, boolean reInitItem) {
        try {
            Gson gson = getGsonBuilder()
                    .setPrettyPrinting()
                    .create();
            Object o = gson.fromJson(jsonStr, Object.class);
            if (reInitItem) {
                CollectionUtils.reInitItemValue(o);
            }
            return gson.toJson(o);
        } catch (Exception e) {
            showExceptionMessage(e);
            return jsonStr;
        }
    }

    /**
     * 格式化properties.
     *
     * @param propertiesStr
     * @param reInitItem
     * @return
     */
    public String prettyProperties(String propertiesStr, boolean reInitItem) {
        try {
            StringBuilder rps = new StringBuilder();

            List<String> l = new ArrayList<String>();
            String[] f = propertiesStr.split("\r\n");
            for (String fi : f) {
                String[] s = fi.split("\n");
                for (String si : s) {
                    String[] t = si.split("\r");
                    for (String ti : t) {
                        if (GuiUtils.trim(ti).startsWith("#")) {
                            rps.append(GuiUtils.trim(ti)).append("\n");
                        } else {
                            int idx = ti.indexOf("=");
                            if (idx > 0) {
                                rps.append(GuiUtils.trim(ti.substring(0, idx))).append("=");
                                if (!reInitItem) {
                                    rps.append(GuiUtils.trim(ti.substring(idx + 1)));
                                }
                                rps.append("\n");
                            } else {
                                rps.append(GuiUtils.trim(ti)).append("\n");
                            }
                        }
                    }
                }
            }
            return rps.toString();
        } catch (Exception e) {
            showExceptionMessage(e);
            return propertiesStr;
        }
    }

    /**
     * 反格式化json.
     *
     * @param jsonStr
     * @return
     */
    public String unPrettyJson(String jsonStr) {
        try {
            Gson gson = getGsonBuilder()
                    .create();
            return gson.toJson(gson.fromJson(jsonStr, Object.class));
        } catch (Exception e) {
            showExceptionMessage(e);
            return jsonStr;
        }
    }

    /**
     * How to prevent Gson from expressing integers as floats.
     * https://stackoverflow.com/questions/15507997/how-to-prevent-gson-from-expressing-integers-as-floats
     *
     * @return
     */
    private GsonBuilder getGsonBuilder() {
        return new GsonBuilder().
                registerTypeAdapter(Double.class, new JsonSerializer<Double>() {
                    @Override
                    public JsonElement serialize(Double src, Type typeOfSrc, JsonSerializationContext context) {
                        if (src == src.longValue()) {
                            return new JsonPrimitive(src.longValue());
                        }
                        return new JsonPrimitive(src);
                    }
                });
    }

}


<|start_filename|>src/main/java/bs/tool/commongui/plugins/more/SimpleWebview.java<|end_filename|>
package bs.tool.commongui.plugins.more;

import bs.tool.commongui.AbstractGuiJPanel;
import bs.tool.commongui.GuiUtils;
import chrriis.dj.nativeswing.swtimpl.NativeInterface;
import chrriis.dj.nativeswing.swtimpl.components.JWebBrowser;

import javax.swing.*;
import java.awt.*;

/**
 * Simple Webview.
 */
public class SimpleWebview extends AbstractGuiJPanel {

    public SimpleWebview() {
        setLayout(new BorderLayout());

        NativeInterface.open();
        JWebBrowser webBrowser = new JWebBrowser();
        webBrowser.navigate("file://" + GuiUtils.getActualPath("conf/SimpleWebview/index.html"));

        JPanel webBrowserPanel = new JPanel(new BorderLayout());
        webBrowserPanel.add(webBrowser, BorderLayout.CENTER);

        add(webBrowserPanel, BorderLayout.CENTER);
    }

}


<|start_filename|>src/main/java/bs/tool/commongui/utils/NumberUtils.java<|end_filename|>
package bs.tool.commongui.utils;

import java.math.BigDecimal;
import java.text.DecimalFormat;

public class NumberUtils {

    /**
     * 转化为百分比展示，百分比后精确到2位小数.
     *
     * @param obj
     * @return
     */
    public static String convertPercentageStr(Object obj) {
        return convertPrecision(obj, 100) + "%";
    }

    /**
     * 精确到3位精度.
     *
     * @param obj
     * @return
     */
    public static String convertPrecision(Object obj) {
        return convertPrecision(obj, 1);
    }

    /**
     * 精确到3位精度.
     *
     * @param obj
     * @param multi
     * @return
     */
    public static String convertPrecision(Object obj, int multi) {
        if (obj instanceof Double) {
            return String.format("%.3f", ((Double) obj) * multi);
        } else if (obj instanceof BigDecimal) {
            DecimalFormat format = new DecimalFormat("0.000");
            BigDecimal decimal = (BigDecimal) obj;
            if (decimal != null) {
                return format.format(decimal.multiply(new BigDecimal(multi)));
            } else {
                return "0.000";
            }
        } else {
            throw new RuntimeException("Convert precision string error, not support object type.");
        }
    }

}


<|start_filename|>src/main/java/bs/tool/commongui/plugins/FolderAndFileOperate.java<|end_filename|>
package bs.tool.commongui.plugins;

import bs.tool.commongui.AbstractGuiJPanel;
import bs.tool.commongui.GuiUtils;
import bs.tool.commongui.utils.FileUtils;
import bs.tool.commongui.utils.SimpleMouseListener;
import bs.util.io.PropertiesUtils;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.io.File;
import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.List;
import java.util.*;

/**
 * 文件(夹)操作.
 */
public class FolderAndFileOperate extends AbstractGuiJPanel {

    private static final long serialVersionUID = 1L;

    /**
     * 路径表单.
     */
    private JTextField pathTextField = new JTextField();
    /**
     * 目录选择.
     */
    private JFileChooser searchFileChooser = new JFileChooser();

    /**
     * 查找类型-文件(夹)查找.
     */
    private final String typeSearch = "文件(夹)查找";
    /**
     * 查找类型-重复文件查找.
     */
    private final String typeRepeatSearch = "重复文件查找";
    /**
     * 查找类型-同名文件查找.
     */
    private final String typeSameNameSearch = "同名文件查找";
    /**
     * 查找类型-空文件(夹)查找.
     */
    private final String typeBlankSearch = "空文件(夹)查找";
    /**
     * 查找类型.
     */
    private final String[] types = new String[]{typeSearch, typeRepeatSearch, typeSameNameSearch, typeBlankSearch};
    /**
     * 当前查找类型.
     */
    private String curType = types[0];

    /**
     * 文件类型表单.
     */
    private JTextField searchFileTypeTextField = new JTextField(39);
    /**
     * 是否相同文件类型，默认为false，用于重复及同名文件查找.
     */
    private boolean repeatSameSuffix = false;
    /**
     * 是否相同文件类型.
     */
    private JCheckBox repeatSameSuffixCheckBox = createJCheckBox("相同文件类型", false, GuiUtils.font14_cn, new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent event) {
            JCheckBox checkBox = (JCheckBox) event.getSource();
            repeatSameSuffix = checkBox.isSelected();
        }
    });

    /**
     * 操作类型-查找.
     */
    private final String action_onlySearch = "查找";
    /**
     * 操作类型-复制.
     */
    private final String action_copyFile = "复制";
    /**
     * 操作类型-剪切.
     */
    private final String action_cutFile = "剪切";
    /**
     * 操作类型-重命名.
     */
    private final String action_renameFile = "重命名";
    /**
     * 操作类型-删除.
     */
    private final String action_deleteFile = "删除";
    /**
     * 操作类型-删除重复.
     */
    private final String action_deleteRepeatFile = "删除重复";
    /**
     * 操作类型-删除空文件夹.
     */
    private final String action_deleteBlankFolder = "删除空文件夹";
    /**
     * 操作类型.
     */
    private final String[] actions = new String[]{action_onlySearch, action_copyFile, action_cutFile,
            action_renameFile, action_deleteFile, action_deleteRepeatFile, action_deleteBlankFolder};
    /**
     * 当前操作类型.
     */
    private String curAction = actions[0];
    /**
     * 操作(复制、剪切文件存放位置)表单.
     */
    private JTextField actionTextField = new JTextField(15);
    /**
     * 操作(复制、剪切文件存放位置)目录选择.
     */
    private JFileChooser actionFileChooser = new JFileChooser();
    /**
     * 操作(复制、剪切文件存放位置)目录浏览按钮.
     */
    private JButton actionChooseButton = createJButton("浏览", "", GuiUtils.font12_cn);

    /**
     * 常见文件类型.
     */
    private Map<String, String> fileTypesMap = new HashMap<String, String>();
    /**
     * 常见文件类型名称.
     */
    private String[] fileTypeNames;

    /**
     * 是否包括文件，默认为true.
     */
    private boolean containsFile = true;
    /**
     * 是否包括文件夹，默认为true.
     */
    private boolean containsFolder = true;
    /**
     * 是否包括隐藏文件(夹)，默认为true.
     */
    private boolean containsHidden = true;
    /**
     * 是否包括非隐藏文件(夹)，默认为true.
     */
    private boolean containsNotHidden = true;

    /**
     * 是否显示文件(夹)完整路径.
     */
    private boolean viewFullPathProp = true;
    /**
     * 是否显示文件大小.
     */
    private boolean viewSizeCkProp = true;
    /**
     * 显示文件大小单位，默认M.
     */
    private String viewSizeCkPropUnit = "M";
    /**
     * 是否显示修改时间.
     */
    private boolean viewModifyTimeProp = false;
    /**
     * 是否显示隐藏属性.
     */
    private boolean viewHiddenProp = false;

    /**
     * 文件大小，最小表单.
     */
    private JFormattedTextField fileSizeFromTextField = createNumberTextField();
    /**
     * 文件大小，最大表单.
     */
    private JFormattedTextField fileSizeToTextField = createNumberTextField();
    /**
     * 文件大小单位，最小下拉框.
     */
    private JComboBox fileSizeUnitFromBox = createFileSizeUnitBox(GuiUtils.font13_cn);
    /**
     * 文件大小单位，最大下拉框.
     */
    private JComboBox fileSizeUnitToBox = createFileSizeUnitBox(GuiUtils.font13_cn);

    /**
     * 修改时间，开始表单.
     */
    private JFormattedTextField modifyTimeFromTextField = createDateTextField();
    /**
     * 修改时间，结束表单.
     */
    private JFormattedTextField modifyTimeToTextField = createDateTextField();
    /**
     * 遍历目录层级，默认所有层级.
     */
    private JTextField folderHierarchyField = new JTextField(5);

    /**
     * 文件(夹)路径包含字符表单.
     */
    private JTextField filePathContainsTextField;
    /**
     * 文件(夹)路径不包含字符表单.
     */
    private JTextField filePathNotContainsTextField;
    /**
     * 文件(夹)路径是否支持正则.
     */
    private boolean filePathSupportRegex = false;

    /**
     * 文件名包含字符表单.
     */
    private JTextField fileNameContainsTextField;
    /**
     * 文件名不包含字符表单.
     */
    private JTextField fileNameNotContainsTextField;
    /**
     * 文件名是否支持正则.
     */
    private boolean fileNameSupportRegex = false;

    /**
     * 文件夹路径包含字符表单.
     */
    private JTextField folderPathContainsTextField;
    /**
     * 文件夹路径不包含字符表单.
     */
    private JTextField folderPathNotContainsTextField;
    /**
     * 文件夹路径是否支持正则.
     */
    private boolean folderPathSupportRegex = false;

    /**
     * 结果文本域.
     */
    private JTextArea resultTextArea = createJTextArea(GuiUtils.font14_un);

    /**
     * 高级条件面板.
     */
    private JPanel advanceConditionPanel;

    {
        // 常见文件类型
        String fileTypePropsPath = "conf/FolderAndFileOperate/filetype.properties";
        try {
            Map<String, String> propsMap = PropertiesUtils.getPropertiesMap(GuiUtils.getActualPath(fileTypePropsPath));
            List<String> propsSortAndNames = new ArrayList<String>();
            for (String key : propsMap.keySet()) {
                propsSortAndNames.add(key);
            }
            // 排序
            Collections.sort(propsSortAndNames);

            int expSize = propsSortAndNames.size() + 1;
            fileTypeNames = new String[expSize];
            fileTypeNames[0] = "所有";
            fileTypesMap.put(fileTypeNames[0], "");

            // 常见文件类型，按序号正排序
            for (int i = 1; i < expSize; i++) {
                String key = propsSortAndNames.get(i - 1);
                String[] keySplit = key.split("_");
                fileTypeNames[i] = keySplit.length > 1 ? key.substring(keySplit[0].length() + 1) : keySplit[0];
                fileTypesMap.put(fileTypeNames[i], propsMap.get(key));
            }
        } catch (IOException e) {
            logLoadPropertiesException(fileTypePropsPath, e);
        }
    }

    public FolderAndFileOperate() {

        // 主面板：边界布局，分North、Center两部分，North用于放置条件控件，Center是放置高级(条件)及输出
        setLayout(new BorderLayout());

        // 输入/操作
        JPanel inputPanel = new JPanel(new GridLayout(3, 1));
        add(inputPanel, BorderLayout.NORTH);

        // 目录选择/填写
        JPanel fileChooPanel = new JPanel(new BorderLayout());
        addJLabel(fileChooPanel, "  目录: ", GuiUtils.font14_cn, BorderLayout.WEST);
        JPanel pathPanel = new JPanel(new BorderLayout());
        pathPanel.add(new JPanel(), BorderLayout.NORTH);
        addJTextField(pathPanel, pathTextField, GuiUtils.font14_un, BorderLayout.CENTER);
        pathPanel.add(new JPanel(), BorderLayout.SOUTH);
        fileChooPanel.add(pathPanel, BorderLayout.CENTER);
        JPanel buttonFlowPanel = new JPanel(new FlowLayout(FlowLayout.LEADING));
        addJButton(buttonFlowPanel, "浏览", "", GuiUtils.font12_cn,
                buttonBrowseListener(searchFileChooser, pathTextField));

        // Search按钮
        addJButton(buttonFlowPanel, "查找", "", GuiUtils.font14b_cn, new SimpleMouseListener() {
            @Override
            public void mouseReleased(MouseEvent event) {
                String path = pathTextField.getText().trim();
                if (!new File(path).exists()) {
                    showMessage("查找目录不存在！", "警告", JOptionPane.WARNING_MESSAGE);
                    return;
                }
                if (curAction.equals(action_copyFile) || curAction.equals(action_cutFile)) {
                    String actionPath = actionTextField.getText().trim();
                    if (!new File(actionPath).exists()) {
                        showMessage("复制或剪切文件存放目录不存在！", "警告", JOptionPane.WARNING_MESSAGE);
                        return;
                    }
                }
                if (curAction.equals(action_renameFile) && !curType.equals(typeSearch)) {
                    showMessage(typeSearch + "才可以进行重命名操作！", "警告", JOptionPane.WARNING_MESSAGE);
                    return;
                }
                Map<String, Object> paramsMap = new HashMap<String, Object>();
                if (curType.equals(typeRepeatSearch)) {
                    paramsMap.put("type_repeatSearch", true);
                    FileUtils.searchRepeatSizeFilesMap = new HashMap<String, List<File>>(5120);
                    FileUtils.searchRepeatSameSizeFilesMap = new HashMap<String, List<File>>(512);
                    FileUtils.repeatFilesSet = new LinkedHashSet<String>(512);
                } else if (curType.equals(typeSameNameSearch)) {
                    paramsMap.put("type_sameNameSearch", true);
                    FileUtils.searchSameNameFilesMap = new HashMap<String, List<File>>(5120);
                    FileUtils.sameNameFilesSet = new LinkedHashSet<String>(512);
                } else if (curType.equals(typeBlankSearch)) {
                    paramsMap.put("type_blankSearch", true);
                }
                Long curTime = System.currentTimeMillis();
                String curFileType = searchFileTypeTextField.getText();
                paramsMap.put("searchFileType", curFileType);
                paramsMap.put("containsFile", containsFile);
                paramsMap.put("containsFolder", containsFolder);
                paramsMap.put("containsHidden", containsHidden);
                paramsMap.put("containsNotHidden", containsNotHidden);
                paramsMap.put("repeatSameSuffix", repeatSameSuffix);
                paramsMap.put("fileSizeFrom", GuiUtils.getCountFileSizeUnit(fileSizeFromTextField.getText().trim(),
                        fileSizeUnitFromBox.getSelectedItem().toString()));
                paramsMap.put("fileSizeTo", GuiUtils.getCountFileSizeUnit(fileSizeToTextField.getText().trim(),
                        fileSizeUnitToBox.getSelectedItem().toString()));
                paramsMap.put("modifyTimeFrom",
                        getLongFormatTime(modifyTimeFromTextField.getText().trim(), new SimpleDateFormat(FORMATTER_YYYYMMDDHHMMSS)));
                paramsMap.put("modifyTimeTo",
                        getLongFormatTime(modifyTimeToTextField.getText().trim(), new SimpleDateFormat(FORMATTER_YYYYMMDDHHMMSS)));
                paramsMap.put("filePathContainsText", filePathContainsTextField.getText().trim());
                paramsMap.put("filePathNotContainsText", filePathNotContainsTextField.getText().trim());
                paramsMap.put("filePathSupportRegex", filePathSupportRegex);
                paramsMap.put("fileNameContainsText", fileNameContainsTextField.getText().trim());
                paramsMap.put("fileNameNotContainsText", fileNameNotContainsTextField.getText().trim());
                paramsMap.put("fileNameSupportRegex", fileNameSupportRegex);
                paramsMap.put("folderPathContainsText", folderPathContainsTextField.getText().trim());
                paramsMap.put("folderPathNotContainsText", folderPathNotContainsTextField.getText().trim());
                paramsMap.put("folderPathSupportRegex", folderPathSupportRegex);
                paramsMap.put("folderHierarchyText", folderHierarchyField.getText().trim());
                // 最终Search到的File，当查找类型为'重复文件查找'，files最后长度为0，结果保存在FileUtils.repeatFilesSet及FileUtils.repeatFilesMap中
                List<File> files = FileUtils.getAllSubFiles(path, paramsMap);
                int cntAction = 0;
                if (curType.equals(typeRepeatSearch)) {
                    List<File> repeatFiles = null;
                    long cnt = 0;
                    int groupCnt = FileUtils.repeatFilesSet.size();
                    int sizeCnt = 0;
                    int f = 0;
                    resultTextArea
                            .append("查找方法：取出有相同大小的所有文件，比较文件大小以及前2048Byte的内容的MD5值是否一样，\n         如果两者相同，则认为重复，否则认为不重复，有较小的误差率。\n\n\n");
                    for (String prop : FileUtils.repeatFilesSet) {
                        resultTextArea.append("第" + (++f) + "组：\n");
                        repeatFiles = FileUtils.searchRepeatSameSizeFilesMap.get(prop);
                        cnt += repeatFiles.size();
                        Integer[] cntArr = printPropAndAction(curAction, repeatFiles);
                        cntAction += cntArr[0];
                        sizeCnt += cntArr[5];
                    }
                    resultTextArea.append("\n\nCount repeat group: " + groupCnt + ", files: " + cnt + ", Size: "
                            + sizeCnt + "M");
                    FileUtils.searchRepeatSizeFilesMap.clear();
                    FileUtils.searchRepeatSizeFilesMap = null;
                    FileUtils.searchRepeatSameSizeFilesMap.clear();
                    FileUtils.searchRepeatSameSizeFilesMap = null;
                    FileUtils.repeatFilesSet.clear();
                    FileUtils.repeatFilesSet = null;
                } else if (curType.equals(typeSameNameSearch)) {
                    List<File> sameNameFiles = null;
                    long cnt = 0;
                    int groupCnt = FileUtils.sameNameFilesSet.size();
                    int sizeCnt = 0;
                    int f = 0;
                    resultTextArea.append("查找方法：比较文件名及文件类型，查找相同文件名称的文件。\n\n\n");
                    for (String prop : FileUtils.sameNameFilesSet) {
                        resultTextArea.append("第" + (++f) + "组：\n");
                        sameNameFiles = FileUtils.searchSameNameFilesMap.get(prop);
                        cnt += sameNameFiles.size();
                        Integer[] cntArr = printPropAndAction(curAction, sameNameFiles);
                        cntAction += cntArr[0];
                        sizeCnt += cntArr[5];
                    }
                    resultTextArea.append("\n\nCount same name group: " + groupCnt + ", files: " + cnt + ", Size: "
                            + sizeCnt + "M");
                    FileUtils.searchSameNameFilesMap.clear();
                    FileUtils.searchSameNameFilesMap = null;
                    FileUtils.sameNameFilesSet.clear();
                    FileUtils.sameNameFilesSet = null;
                } else {
                    Integer[] cntArr = printPropAndAction(curAction, files);
                    cntAction += cntArr[0];
                    resultTextArea.append("\n\nCount: " + files.size() + ", Size: " + cntArr[5] + "M, folders: "
                            + cntArr[1] + ", files: " + cntArr[2] + ", hidden folders: " + cntArr[3]
                            + ", hidden files: " + cntArr[4]);
                }
                files.clear();
                files = null;
                if (curAction.equals(action_copyFile)) {
                    resultTextArea.append(", Count Copy files: " + cntAction);
                } else if (curAction.equals(action_cutFile)) {
                    resultTextArea.append(", Count Cut files: " + cntAction);
                } else if (curAction.equals(action_renameFile)) {
                    resultTextArea.append(", Count Rename files: " + cntAction);
                } else if (curAction.equals(action_deleteFile)) {
                    resultTextArea.append(", Count Delete files: " + cntAction);
                } else if (curAction.equals(action_deleteRepeatFile)) {
                    resultTextArea.append(", Count Delete Repeat files: " + cntAction);
                } else if (curAction.equals(action_deleteBlankFolder)) {
                    resultTextArea.append(", Count Delete Blank folders: " + cntAction);
                }
                resultTextArea.append(". Cost time:" + (System.currentTimeMillis() - curTime) / 1000.0 + "s.");

                System.gc();
            }

            @Override
            public void mousePressed(MouseEvent e) {
                resultTextArea.setText("");
            }
        });
        // 路径选择控件，仅可选择文件夹
        searchFileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
        fileChooPanel.add(buttonFlowPanel, BorderLayout.EAST);
        inputPanel.add(fileChooPanel);

        // 条件
        JPanel conditionPanel = new JPanel(new FlowLayout(FlowLayout.LEADING));
        // 查找类型
        addJLabel(conditionPanel, " 查找类型: ", GuiUtils.font14_cn);
        addJComboBox(conditionPanel, types, GuiUtils.font13_cn, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                curType = ((JComboBox) event.getSource()).getSelectedItem().toString();
                if (curType.equals(typeBlankSearch)) {
                    fileSizeFromTextField.setText("");
                    fileSizeFromTextField.setEnabled(false);
                    fileSizeUnitFromBox.setEnabled(false);
                    fileSizeToTextField.setText("");
                    fileSizeToTextField.setEnabled(false);
                    fileSizeUnitToBox.setEnabled(false);
                } else {
                    fileSizeFromTextField.setEnabled(true);
                    fileSizeUnitFromBox.setEnabled(true);
                    fileSizeToTextField.setEnabled(true);
                    fileSizeUnitToBox.setEnabled(true);
                }
                if (curType.equals(typeRepeatSearch) || curType.equals(typeSameNameSearch)) {
                    repeatSameSuffixCheckBox.setEnabled(true);
                } else {
                    repeatSameSuffixCheckBox.setEnabled(false);
                }
            }
        });

        addJLabel(conditionPanel, "   文件类型: ", GuiUtils.font14_cn);

        // 常见文件类型下拉框
        addJComboBox(conditionPanel, fileTypeNames, GuiUtils.font13_cn, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                searchFileTypeTextField.setText(fileTypesMap.get(((JComboBox) event.getSource()).getSelectedItem().toString()));
            }
        });

        addJLabel(conditionPanel, " 包括:", GuiUtils.font14_cn);
        // 是否包括文件JCheckBox
        addJCheckBox(conditionPanel, "文件", true, GuiUtils.font14_cn, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                JCheckBox checkBox = (JCheckBox) event.getSource();
                containsFile = checkBox.isSelected();
            }
        });
        // 是否包括文件夹JCheckBox
        addJCheckBox(conditionPanel, "文件夹", true, GuiUtils.font14_cn, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                JCheckBox checkBox = (JCheckBox) event.getSource();
                containsFolder = checkBox.isSelected();
            }
        });
        // 是否包括隐藏文件(夹)JCheckBox
        addJCheckBox(conditionPanel, "隐藏文件(夹)", true, GuiUtils.font14_cn, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                JCheckBox checkBox = (JCheckBox) event.getSource();
                containsHidden = checkBox.isSelected();
            }
        });
        // 是否包括隐藏文件(夹)JCheckBox
        addJCheckBox(conditionPanel, "非隐藏文件(夹)", true, GuiUtils.font14_cn, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                JCheckBox checkBox = (JCheckBox) event.getSource();
                containsNotHidden = checkBox.isSelected();
            }
        });
        inputPanel.add(conditionPanel);

        JPanel viewPropAndActionPanel = new JPanel(new FlowLayout(FlowLayout.LEADING));
        addJLabel(viewPropAndActionPanel, " 操作类型: ", GuiUtils.font14_cn);
        // 操作类型下拉框
        addJComboBox(viewPropAndActionPanel, actions, GuiUtils.font13_cn, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                curAction = ((JComboBox) event.getSource()).getSelectedItem().toString();
                if (!curAction.equals(action_onlySearch)) {
                    // 将焦点转移到下一个组件，就好像此 Component 曾是焦点所有者
                    ((JComboBox) event.getSource()).transferFocus();
                    showMessage("非查找操作务必确认不会对原文件(夹)造成意外损害！", "警告", JOptionPane.WARNING_MESSAGE);
                }
                if (curAction.equals(action_copyFile) || curAction.equals(action_cutFile)) {
                    actionTextField.setEnabled(true);
                    actionChooseButton.setEnabled(true);
                } else {
                    actionTextField.setText("");
                    actionTextField.setEnabled(false);
                    actionChooseButton.setEnabled(false);
                }
            }
        });
        addJTextField(viewPropAndActionPanel, actionTextField, GuiUtils.font14_un);
        actionTextField.setEnabled(false);
        // 路径选择控件，仅可选择文件夹
        actionFileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
        actionChooseButton.addActionListener(buttonBrowseListener(actionFileChooser, actionTextField));
        actionChooseButton.setEnabled(false);
        viewPropAndActionPanel.add(actionChooseButton);

        addJLabel(viewPropAndActionPanel, " 显示:", GuiUtils.font14_cn);
        // 是否显示文件(夹)完整路径
        addJCheckBox(viewPropAndActionPanel, "完整路径", true, GuiUtils.font14_cn, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                JCheckBox checkBox = (JCheckBox) event.getSource();
                viewFullPathProp = checkBox.isSelected();
            }
        });
        // 是否显示文件大小JCheckBox
        addJCheckBox(viewPropAndActionPanel, "大小", true, GuiUtils.font14_cn, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                JCheckBox checkBox = (JCheckBox) event.getSource();
                viewSizeCkProp = checkBox.isSelected();
            }
        });
        JComboBox viewSizeCkPropUnitBox = createFileSizeUnitBox(GuiUtils.font12_cn);
        viewSizeCkPropUnitBox.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                viewSizeCkPropUnit = ((JComboBox) event.getSource()).getSelectedItem().toString();
            }
        });
        viewPropAndActionPanel.add(viewSizeCkPropUnitBox);
        // 是否显示修改时间
        addJCheckBox(viewPropAndActionPanel, "修改时间", false, GuiUtils.font14_cn, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                JCheckBox checkBox = (JCheckBox) event.getSource();
                viewModifyTimeProp = checkBox.isSelected();
            }
        });
        // 是否显示文件大小JCheckBox
        addJCheckBox(viewPropAndActionPanel, "隐藏属性", false, GuiUtils.font14_cn, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                JCheckBox checkBox = (JCheckBox) event.getSource();
                viewHiddenProp = checkBox.isSelected();
            }
        });
        // 展开/收缩高级(条件)按钮
        addJButton(viewPropAndActionPanel, "高级", "", GuiUtils.font12_cn, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                advanceConditionPanel.setVisible(!advanceConditionPanel.isVisible());
                getContextPanel().revalidate();
            }
        });
        inputPanel.add(viewPropAndActionPanel);

        // 高级(条件)及输出面板，使用边界布局，North为高级(条件)，Center为输出
        JPanel advanceAndResultPanel = new JPanel(new BorderLayout());
        // 高级(条件
        advanceConditionPanel = new JPanel(new GridLayout(5, 1));
        advanceConditionPanel.setVisible(false);
        advanceAndResultPanel.add(advanceConditionPanel, BorderLayout.NORTH);

        // 文件类型条件
        JPanel fileTypeConditionPanel = new JPanel(new FlowLayout(FlowLayout.LEADING));
        addJLabel(fileTypeConditionPanel, " 文件类型: ", GuiUtils.font14_cn);
        fileTypeConditionPanel.add(searchFileTypeTextField);
        // 是否相同文件类型
        fileTypeConditionPanel.add(repeatSameSuffixCheckBox);
        repeatSameSuffixCheckBox.setEnabled(false);
        advanceConditionPanel.add(fileTypeConditionPanel);

        // 大小条件、时间条件
        JPanel fileSizeTimePanel = new JPanel(new FlowLayout(FlowLayout.LEADING));
        addJLabel(fileSizeTimePanel, " 文件大小: ", GuiUtils.font14_cn);
        fileSizeTimePanel.add(fileSizeFromTextField);
        fileSizeTimePanel.add(fileSizeUnitFromBox);
        addJLabel(fileSizeTimePanel, "-", GuiUtils.font14_cn);
        fileSizeTimePanel.add(fileSizeToTextField);
        fileSizeTimePanel.add(fileSizeUnitToBox);
        addJLabel(fileSizeTimePanel, "   修改时间:", GuiUtils.font14_cn);
        fileSizeTimePanel.add(modifyTimeFromTextField);
        addJLabel(fileSizeTimePanel, "--", GuiUtils.font14_cn);
        fileSizeTimePanel.add(modifyTimeToTextField);
        addJLabel(fileSizeTimePanel, "   遍历层级:", GuiUtils.font14_cn);
        addJTextField(fileSizeTimePanel, folderHierarchyField, GuiUtils.font14_cn);
        advanceConditionPanel.add(fileSizeTimePanel);

        // 文件(夹)路径包含(不包含)字符
        JPanel filePathContainsPanel = new JPanel(new FlowLayout(FlowLayout.LEADING));
        addJLabel(filePathContainsPanel, " 文件/夹路径包含:", GuiUtils.font14_cn);
        filePathContainsTextField = new JTextField(24);
        addJTextField(filePathContainsPanel, filePathContainsTextField, GuiUtils.font14_un);
        addJLabel(filePathContainsPanel, " 文件/夹路径不包含:", GuiUtils.font14_cn);
        filePathNotContainsTextField = new JTextField(24);
        addJTextField(filePathContainsPanel, filePathNotContainsTextField, GuiUtils.font14_un);
        // 是否支持正则
        addJCheckBox(filePathContainsPanel, "支持正则", false, GuiUtils.font14_cn, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                filePathSupportRegex = ((JCheckBox) event.getSource()).isSelected();
            }
        });
        advanceConditionPanel.add(filePathContainsPanel);

        // 文件名包含(不包含)字符
        JPanel fileNameContainsPanel = new JPanel(new FlowLayout(FlowLayout.LEADING));
        addJLabel(fileNameContainsPanel, " 文件名包含字符: ", GuiUtils.font14_cn);
        fileNameContainsTextField = new JTextField(24);
        addJTextField(fileNameContainsPanel, fileNameContainsTextField, GuiUtils.font14_un);
        addJLabel(fileNameContainsPanel, " 文件名不包含字符: ", GuiUtils.font14_cn);
        fileNameNotContainsTextField = new JTextField(24);
        addJTextField(fileNameContainsPanel, fileNameNotContainsTextField, GuiUtils.font14_un);
        // 是否支持正则
        addJCheckBox(fileNameContainsPanel, "支持正则", false, GuiUtils.font14_cn, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                fileNameSupportRegex = ((JCheckBox) event.getSource()).isSelected();
            }
        });
        advanceConditionPanel.add(fileNameContainsPanel);

        // 文件夹路径包含(不包含)字符
        JPanel folderPathContainsPanel = new JPanel(new FlowLayout(FlowLayout.LEADING));
        addJLabel(folderPathContainsPanel, " 文件夹路径包含: ", GuiUtils.font14_cn);
        folderPathContainsTextField = new JTextField(24);
        addJTextField(folderPathContainsPanel, folderPathContainsTextField, GuiUtils.font14_un);
        addJLabel(folderPathContainsPanel, " 文件夹路径不包含: ", GuiUtils.font14_cn);
        folderPathNotContainsTextField = new JTextField(24);
        addJTextField(folderPathContainsPanel, folderPathNotContainsTextField, GuiUtils.font14_un);
        // 是否支持正则
        addJCheckBox(folderPathContainsPanel, "支持正则", false, GuiUtils.font14_cn, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                folderPathSupportRegex = ((JCheckBox) event.getSource()).isSelected();
            }
        });
        advanceConditionPanel.add(folderPathContainsPanel);

        // 输出结果
        JPanel resultPanel = new JPanel(new BorderLayout());
        resultPanel.add(new JScrollPane(resultTextArea));
        advanceAndResultPanel.add(resultPanel, BorderLayout.CENTER);
        add(advanceAndResultPanel, BorderLayout.CENTER);
    }

    /**
     * 输出文件属性及根据操作类型操作文件，返回值表示复制文件/剪切文件/删除文件/删除文件夹的数目，查找到文件夹总数，查找到文件总数，
     * 查找到隐藏文件夹总数，查找到隐藏文件总数.
     */
    private Integer[] printPropAndAction(String action, List<File> files) {
        int cntAction = 0;
        int cntFolders = 0;
        int cntFiles = 0;
        int cntFoldersHidden = 0;
        int cntFilesHidden = 0;
        long cntLength = 0;
        // 查找重复/同名文件，并删除重复文件只保留最新一个文件
        boolean deleteRepeat = (curType.equals(typeRepeatSearch) || curType.equals(typeSameNameSearch)) && action.equals(action_deleteRepeatFile);
        if (deleteRepeat) {
            Collections.sort(files, new Comparator<File>() {
                @Override
                public int compare(File o1, File o2) {
                    return o2.lastModified() > o1.lastModified() ? 1 : -1;
                }
            });
        }
        // 文件重命名
        boolean renameFile = curType.equals(typeSearch) && curAction.equals(action_renameFile);
        Map<File, String> newNameMap = null;
        if (renameFile) {
            DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
            Map<String, Map<String, List<File>>> dirDateFiles = new HashMap<String, Map<String, List<File>>>();
            newNameMap = new HashMap<File, String>();
            for (File file : files) {
                if (!file.isDirectory()) {
                    String dir = file.getParentFile().getPath();
                    if (dirDateFiles.get(dir) == null) {
                        dirDateFiles.put(dir, new HashMap<String, List<File>>());
                    }
                    String date = dateFormat.format(new Date(file.lastModified()));
                    if (dirDateFiles.get(dir).get(date) == null) {
                        dirDateFiles.get(dir).put(date, new ArrayList<File>());
                    }
                    dirDateFiles.get(dir).get(date).add(file);
                }
            }
            for (Map.Entry<String, Map<String, List<File>>> entry : dirDateFiles.entrySet()) {
                for (Map.Entry<String, List<File>> fEntry : entry.getValue().entrySet()) {
                    List<File> fFiles = fEntry.getValue();
                    Collections.sort(fFiles, new Comparator<File>() {
                        @Override
                        public int compare(File o1, File o2) {
                            return o2.lastModified() < o1.lastModified() ? 1 : -1;
                        }
                    });
                    int fi = fFiles.size();
                    for (int i = 0; i < fi; i++) {
                        File file = fFiles.get(i);
                        int l = file.getName().lastIndexOf(".");
                        String fileType = l > 0 ? file.getName().substring(l) : "";
                        newNameMap.put(file, fEntry.getKey() + (fi == 1 ? "" : (" " + (i + 1))) + fileType);
                    }
                }
            }
        }
        int fi = 0;
        for (File file : files) {
            if (file.isDirectory()) {
                cntFolders++;
                if (file.isHidden()) {
                    cntFoldersHidden++;
                }
            } else {
                cntFiles++;
                cntLength += file.length();
                if (file.isHidden()) {
                    cntFilesHidden++;
                }
            }
            if (viewFullPathProp) {
                resultTextArea.append(file.getAbsolutePath());
            } else {
                resultTextArea.append(file.getName());
            }
            if (renameFile && !file.isDirectory()) {
                String newName = newNameMap.get(file);
                boolean rs = file.renameTo(new File(file.getParentFile().getPath() + "/" + newName));
                resultTextArea.append("   Rename: ");
                if (rs) {
                    cntAction++;
                    resultTextArea.append(newName);
                }
            }
            if (viewSizeCkProp && !file.isDirectory()) {
                resultTextArea.append("   Size: ");
                if (viewSizeCkPropUnit.equals(GuiUtils.FileSize_M)) {
                    resultTextArea.append(formatDouble3.format(file.length() / 1024.0 / 1024.0) + "M");
                } else if (viewSizeCkPropUnit.equals(GuiUtils.FileSize_KB)) {
                    resultTextArea.append(formatDouble3.format(file.length() / 1024.0) + "KB");
                } else if (viewSizeCkPropUnit.equals(GuiUtils.FileSize_Byte)) {
                    resultTextArea.append(file.length() + "Byte");
                } else if (viewSizeCkPropUnit.equals(GuiUtils.FileSize_G)) {
                    resultTextArea.append(formatDouble6.format(file.length() / 1024.0 / 1024.0 / 1024.0) + "G");
                }
            }
            if (viewModifyTimeProp) {
                resultTextArea.append("   ModifyTime: " + new SimpleDateFormat(FORMATTER_YYYYMMDDHHMMSS).format(new Date(file.lastModified())));
            }
            if (viewHiddenProp) {
                resultTextArea.append("   Hidden: " + (file.isHidden() ? "Y" : "N"));
            }
            // 删除重复文件只保留最新一个文件
            if (deleteRepeat && fi != 0) {
                file.delete();
                cntAction++;
                resultTextArea.append("   Deleted");
            }
            resultTextArea.append("\n");
            if (!action.equals(action_onlySearch)) {
                if (!file.isDirectory()) {
                    if (action.equals(action_copyFile)) {
                        // 复制文件
                        copyFile(file);
                        cntAction++;
                    } else if (action.equals(action_cutFile)) {
                        // 剪切文件，先复制文件
                        copyFile(file);
                        // 然后删除文件
                        file.delete();
                        cntAction++;
                    } else if (action.equals(action_deleteFile)) {
                        // 删除文件
                        file.delete();
                        cntAction++;
                    }
                } else if (action.equals(action_deleteBlankFolder) && file.listFiles() != null
                        && file.listFiles().length == 0) {
                    file.delete(); // 删除空文件夹
                    cntAction++;
                }
            }
            fi += 1;
        }
        return new Integer[]{cntAction, cntFolders, cntFiles, cntFoldersHidden, cntFilesHidden,
                (int) (cntLength / 1024 / 1024)};
    }

    /**
     * 复制文件.
     */
    private void copyFile(File srcFile) {
        String actionPath = actionTextField.getText().trim();
        try {
            org.apache.commons.io.FileUtils.copyFile(srcFile, new File(actionPath + "/" + srcFile.getName()));
        } catch (IOException e) {
            showExceptionMessage(e);
        }
    }
}


<|start_filename|>src/main/java/bs/tool/commongui/plugins/more/JUniversalChardet.java<|end_filename|>
package bs.tool.commongui.plugins.more;

import bs.tool.commongui.AbstractGuiJPanel;
import bs.tool.commongui.GuiUtils;
import bs.tool.commongui.utils.FileUtils;
import bs.tool.commongui.utils.SearchFileNameParams;
import bs.tool.commongui.utils.SimpleMouseListener;
import bs.util.io.PropertiesUtils;
import org.mozilla.universalchardet.UniversalDetector;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URL;
import java.util.List;
import java.util.*;

/**
 * 文本编码识别.
 */
public class JUniversalChardet extends AbstractGuiJPanel {

    private static final long serialVersionUID = 1L;

    /**
     * 探测文本/文件夹路径表单.
     */
    private JTextField detectPathTextField = new JTextField();
    /**
     * 探测文本/文件夹路径选择.
     */
    private JFileChooser detectPathChooser = new JFileChooser();

    /**
     * 探测按钮.
     */
    private JButton detectButton;

    /**
     * 取样大小表单.
     */
    private JTextField detectSizeTextField = new JTextField("51200", 7);

    /**
     * 文件名包含字符表单.
     */
    private JTextField fileNameContainsTextField;
    /**
     * 文件名不包含字符表单.
     */
    private JTextField fileNameNotContainsTextField;
    /**
     * 文件名是否支持正则.
     */
    private boolean fileNameSupportRegex = true;

    /**
     * 探测结果文本域.
     */
    private JTextArea resultTextArea = createJTextArea(GuiUtils.font14_un);

    public JUniversalChardet() {

        // 主面板：边界布局，分North、Center两部分，North用于放置输入及条件控件，Center是放置探测结果输出
        setLayout(new BorderLayout());

        // 输入及条件Panel
        JPanel inputPanel = new JPanel(new GridLayout(2, 1));

        // 探测文本/文件夹表单、探测按钮Panel
        JPanel fileChooAndDetectPanel = new JPanel(new BorderLayout());
        // 探测文本/文件夹表单
        addJLabel(fileChooAndDetectPanel, "  探测文本/文件夹/URL: ", GuiUtils.font14_cn, BorderLayout.WEST);
        JPanel fileChooPanel = new JPanel(new BorderLayout());
        fileChooPanel.add(new JPanel(), BorderLayout.NORTH);
        addJTextField(fileChooPanel, detectPathTextField, GuiUtils.font14_un, BorderLayout.CENTER);
        fileChooPanel.add(new JPanel(), BorderLayout.SOUTH);
        fileChooAndDetectPanel.add(fileChooPanel, BorderLayout.CENTER);

        JPanel buttonFlowPanel = new JPanel(new FlowLayout(FlowLayout.LEADING));
        addJButton(buttonFlowPanel, "浏览", "", GuiUtils.font12_cn,
                buttonBrowseListener(detectPathChooser, detectPathTextField));
        // 探测按钮
        detectButton = createJButton("探测", "", GuiUtils.font14b_cn);
        detectButton.addMouseListener(new SimpleMouseListener() {
            @Override
            public void mouseReleased(MouseEvent event) {
                String path = detectPathTextField.getText().trim();
                File file = new File(path);
                boolean exists = true;
                if (!file.exists()) {
                    try {
                        file = new File(FileUtils.JAVA_IO_TMPDIR + "/" + "detect-" + System.currentTimeMillis());
                        org.apache.commons.io.FileUtils.copyURLToFile(new URL(path), file);
                    } catch (IOException e) {
                        GuiUtils.log(e);
                        exists = false;
                    }
                }
                if (!exists || file == null || !file.exists()) {
                    showMessage("探测文本/文件夹/URL不存在或错误！", "警告", JOptionPane.WARNING_MESSAGE);
                    return;
                }
                Map<String, Object> paramsMap = new HashMap<String, Object>();
                paramsMap.put("fileNameContainsText", fileNameContainsTextField.getText().trim());
                paramsMap.put("fileNameNotContainsText", fileNameNotContainsTextField.getText().trim());
                paramsMap.put("fileNameSupportRegex", fileNameSupportRegex);
                detectButton.setEnabled(false);

                resultTextArea.setText("");
                try {
                    int detectLength = Integer.parseInt(detectSizeTextField.getText().trim());
                    if (!file.isDirectory()) {
                        appendDetectResult(resultTextArea, file, detectLength);
                    } else {
                        List<File> files = new ArrayList<File>();
                        FileUtils.loopDirectory(file, files, new SearchFileNameParams(paramsMap));
                        for (File fFile : files) {
                            appendDetectResult(resultTextArea, fFile, detectLength);
                        }
                        resultTextArea.append("\nCount files: " + files.size());
                    }
                } catch (NumberFormatException e) {
                    showMessage("取样大小必须是正整数！", "警告", JOptionPane.WARNING_MESSAGE);
                } catch (IOException e) {
                    showExceptionMessage(e);
                }
                detectButton.setEnabled(true);
            }

            @Override
            public void mousePressed(MouseEvent e) {
                resultTextArea.setText("");
            }
        });
        buttonFlowPanel.add(detectButton);
        // 探测路径选择控件，可选择文件/文件夹
        detectPathChooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
        fileChooAndDetectPanel.add(buttonFlowPanel, BorderLayout.EAST);
        inputPanel.add(fileChooAndDetectPanel);

        // 高级条件Panel
        JPanel advancePanel = new JPanel(new FlowLayout(FlowLayout.LEADING));
        // 取样大小
        addJLabel(advancePanel, " ", GuiUtils.font12_cn);
        addJLabel(advancePanel, "取样大小: ", GuiUtils.font14_cn);
        addJTextField(advancePanel, detectSizeTextField, GuiUtils.font14_cn);
        addJLabel(advancePanel, "Byte", GuiUtils.font14_cn);

        // 文件名包含(不包含)字符
        String confPropsFile = "conf/JUniversalChardet/searchFiletypes.properties";
        String containsFiletypes = "";
        String notContainsFiletypes = "";
        try {
            Properties confProperties = PropertiesUtils.getProperties(GuiUtils.getActualPath(confPropsFile));
            containsFiletypes = confProperties.getProperty("ContainsFiletypes").trim();
            notContainsFiletypes = confProperties.getProperty("NotContainsFiletypes").trim();
        } catch (IOException e) {
            logLoadPropertiesException(confPropsFile, e);
        }
        addJLabel(advancePanel, " 文件名包含字符: ", GuiUtils.font14_cn);
        String fileNameContains = containsFiletypes.length() > 0 ? "\\.(" + containsFiletypes.replace(",", "|") + ")$"
                : "";
        fileNameContainsTextField = new JTextField(fileNameContains, 15);
        addJTextField(advancePanel, fileNameContainsTextField, GuiUtils.font14_un);
        addJLabel(advancePanel, "  文件名不包含字符: ", GuiUtils.font14_cn);
        String fileNameNotContains = notContainsFiletypes.length() > 0 ? "\\.("
                + notContainsFiletypes.replace(",", "|") + ")$" : "";
        fileNameNotContainsTextField = new JTextField(fileNameNotContains, 15);
        addJTextField(advancePanel, fileNameNotContainsTextField, GuiUtils.font14_un);
        // 是否支持正则
        addJCheckBox(advancePanel, "支持正则", true, GuiUtils.font14_cn, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                fileNameSupportRegex = ((JCheckBox) event.getSource()).isSelected();
            }
        });
        inputPanel.add(advancePanel);

        add(inputPanel, BorderLayout.NORTH);

        // 探测结果输出
        JPanel detectLogPanel = new JPanel(new BorderLayout());
        detectLogPanel.add(new JScrollPane(resultTextArea));
        add(detectLogPanel, BorderLayout.CENTER);
    }

    /**
     * append探测文本编码结果.
     */
    private void appendDetectResult(JTextArea resultTextArea, File file, int detectLength) throws IOException {
        resultTextArea.append(file.getAbsolutePath() + "         Charset: " + detectFileCharset(file, detectLength)
                + "\n");
    }

    /**
     * 探测文本编码.
     */
    public static String detectFileCharset(File file, int detectLength) throws IOException {
        String charset = null;
        FileInputStream fis = null;
        try {
            byte[] buf = new byte[detectLength];
            fis = new FileInputStream(file);
            UniversalDetector detector = new UniversalDetector(null);
            int nread;
            while ((nread = fis.read(buf)) > 0 && !detector.isDone()) {
                detector.handleData(buf, 0, nread);
            }
            detector.dataEnd();
            charset = detector.getDetectedCharset();
            detector.reset();
        } finally {
            if (fis != null) {
                fis.close();
            }
        }
        return charset;
    }

}


<|start_filename|>src/main/java/bs/tool/commongui/plugins/TimeTool.java<|end_filename|>
package bs.tool.commongui.plugins;

import bs.tool.commongui.AbstractGuiJPanel;
import bs.tool.commongui.GuiUtils;
import bs.tool.commongui.utils.SimpleMouseListener;
import bs.tool.commongui.utils.TimeUtils;

import javax.swing.*;
import java.awt.*;
import java.awt.datatransfer.StringSelection;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * 时间工具.
 */
public class TimeTool extends AbstractGuiJPanel {

    private static final long serialVersionUID = 1L;

    /**
     * 时间格式.
     */
    private String[] timeFormatter = new String[]{TimeUtils.FORMATTER, TimeUtils.FORMATTER_MILLISECOND, TimeUtils.FORMATTER_ZH, TimeUtils.FORMATTER_YEAR, TimeUtils.FORMATTER_ZH_YEAR};

    /**
     * 当前时间格式.
     */
    private String curTimeFormatter = timeFormatter[0];

    /**
     * 时间字符表单.
     */
    private JTextField timeStrTextField = new JTextField(new SimpleDateFormat(curTimeFormatter).format(new Date()));

    /**
     * 转换.
     */
    private JButton convertButton;

    /**
     * 时间戳表单.
     */
    private JTextField timestampTextField = new JTextField(Long.toString(System.currentTimeMillis()));

    /**
     * 还原.
     */
    private JButton revertButton;

    public TimeTool() {

        // 主面板：边界布局，分North、Center两部分，North用于放置输入及条件控件，Center是放置空面面板
        setLayout(new BorderLayout());

        // 输入及条件Panel
        JPanel inputPanel = new JPanel(new GridLayout(2, 1));

        // 时间字符表单、时间格式、转换按钮Panel
        JPanel convertPanel = new JPanel(new BorderLayout());
        addJLabel(convertPanel, "  时间字符: ", GuiUtils.font14_cn, BorderLayout.WEST);
        // 时间字符表单
        addJTextField(convertPanel, timeStrTextField, GuiUtils.font14_un, BorderLayout.CENTER);

        JPanel convertFlowPanel = new JPanel(new FlowLayout(FlowLayout.LEADING));
        // 时间格式
        addJComboBox(convertFlowPanel, timeFormatter, GuiUtils.font13_cn, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                curTimeFormatter = ((JComboBox) event.getSource()).getSelectedItem().toString();
            }
        });
        // 当前时间
        addJButton(convertFlowPanel, "当前时间", "", GuiUtils.font13_cn, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                Date curDate = new Date();
                timeStrTextField.setText(new SimpleDateFormat(curTimeFormatter).format(curDate));
                timestampTextField.setText(Long.toString(curDate.getTime()));
            }
        });
        // 复制
        addJButton(convertFlowPanel, "复制", "", GuiUtils.font13_cn, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                StringSelection selection = new StringSelection(timeStrTextField.getText());
                // 获取系统剪切板，复制时间字符
                Toolkit.getDefaultToolkit().getSystemClipboard().setContents(selection, selection);
            }
        });
        // 转换按钮
        convertButton = createJButton("转换", "", GuiUtils.font14b_cn);
        convertButton.addMouseListener(new SimpleMouseListener() {
            @Override
            public void mouseReleased(MouseEvent event) {
                String timeStr = timeStrTextField.getText().trim();
                if (timeStr.length() == 0) {
                    showMessage("没有输入时间字符！", "警告", JOptionPane.WARNING_MESSAGE);
                    return;
                }
                timestampTextField.setText("");
                convertButton.setEnabled(false);
                try {
                    timestampTextField.setText(Long.toString(new SimpleDateFormat(curTimeFormatter).parse(timeStr).getTime()));
                } catch (Exception e) {
                    showExceptionMessage(e);
                }
                convertButton.setEnabled(true);
            }

            @Override
            public void mousePressed(MouseEvent e) {
                timestampTextField.setText("");
            }
        });
        convertFlowPanel.add(convertButton);
        convertPanel.add(convertFlowPanel, BorderLayout.EAST);

        // 时间戳表单、还原按钮Panel
        JPanel revertPanel = new JPanel(new BorderLayout());
        addJLabel(revertPanel, "  时 间 戳: ", GuiUtils.font14_cn, BorderLayout.WEST);
        // 时间戳表单
        addJTextField(revertPanel, timestampTextField, GuiUtils.font14_un, BorderLayout.CENTER);

        JPanel revertFlowPanel = new JPanel(new FlowLayout(FlowLayout.LEADING));
        // 复制
        addJButton(revertFlowPanel, "复制", "", GuiUtils.font13_cn, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                StringSelection selection = new StringSelection(timestampTextField.getText());
                // 获取系统剪切板，复制时间戳
                Toolkit.getDefaultToolkit().getSystemClipboard().setContents(selection, selection);
            }
        });

        revertButton = createJButton("还原", "", GuiUtils.font14b_cn);
        revertButton.addMouseListener(new SimpleMouseListener() {
            @Override
            public void mouseReleased(MouseEvent event) {
                String timestamp = timestampTextField.getText().trim();
                if (timestamp.length() == 0) {
                    showMessage("没有输入时间戳！", "警告", JOptionPane.WARNING_MESSAGE);
                    return;
                }
                timeStrTextField.setText("");
                revertButton.setEnabled(false);
                try {
                    timeStrTextField.setText(new SimpleDateFormat(curTimeFormatter).format(new Date(Long.parseLong(timestamp))));
                } catch (Exception e) {
                    showExceptionMessage(e);
                }
                revertButton.setEnabled(true);
            }

            @Override
            public void mousePressed(MouseEvent e) {
                timeStrTextField.setText("");
            }
        });
        revertFlowPanel.add(revertButton);
        revertPanel.add(revertFlowPanel, BorderLayout.EAST);

        inputPanel.add(convertPanel);
        inputPanel.add(revertPanel);

        add(inputPanel, BorderLayout.NORTH);

        // 空面面板
        JPanel resultPanel = new JPanel(new BorderLayout());
        // 空白文本域
        JTextArea resultTextArea = createJTextArea(GuiUtils.font14_un);
        resultTextArea.setEditable(false);
        resultTextArea.setText("");
        resultPanel.add(new JScrollPane(resultTextArea));
        add(resultPanel, BorderLayout.CENTER);
    }

}


<|start_filename|>src/main/java/bs/util/common/CodecUtil.java<|end_filename|>
package bs.util.common;

import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Base32;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.BinaryCodec;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.codec.net.URLCodec;

import java.io.UnsupportedEncodingException;

/**
 * commons codec util.
 *
 * @author Baishui2004
 */
public final class CodecUtil {

    /**
     * private constructor.
     */
    private CodecUtil() {
    }

    /**
     * encoding UTF-8.
     */
    public static final String ENCODING_UTF_8 = "UTF-8";
    /**
     * encoding GB18030.
     */
    public static final String ENCODING_GB18030 = "GB18030";
    /**
     * encoding GBK.
     */
    public static final String ENCODING_GBK = "GBK";
    /**
     * encoding GB2312.
     */
    public static final String ENCODING_GB2312 = "GB2312";
    /**
     * encoding Big5.
     */
    public static final String ENCODING_BIG5 = "Big5";
    /**
     * encoding ISO-8859-1.
     */
    public static final String ENCODING_ISO_8859_1 = "ISO-8859-1";
    /**
     * encoding UTF-16.
     */
    public static final String ENCODING_UTF_16 = "UTF-16";
    /**
     * encoding UTF-16BE.
     */
    public static final String ENCODING_UTF_16BE = "UTF-16BE";
    /**
     * encoding UTF-16LE.
     */
    public static final String ENCODING_UTF_16LE = "UTF-16LE";
    /**
     * default encoding.
     */
    public static final String ENCODING_DEFAULT = ENCODING_UTF_8;

    /**
     * Encode string to hex.
     *
     * @param string String
     * @return <code>String</code> hex string
     * @throws UnsupportedEncodingException unsupported encoding exception
     */
    public static String encodeHex(String string) throws UnsupportedEncodingException {
        return encodeHex(string, ENCODING_DEFAULT);
    }

    /**
     * Encode string to hex.
     *
     * @param string  String
     * @param charSet CharSet
     * @return <code>String</code> hex string
     * @throws UnsupportedEncodingException unsupported encoding exception
     */
    public static String encodeHex(String string, String charSet) throws UnsupportedEncodingException {
        if (string == null) {
            return null;
        }
        return Hex.encodeHexString(string.getBytes(charSet));
    }

    /**
     * Decode hex to string.
     *
     * @param string String
     * @return <code>String</code> string
     * @throws DecoderException             if decode error occurs
     * @throws UnsupportedEncodingException unsupported encoding exception
     */
    public static String decodeHex(String string) throws DecoderException, UnsupportedEncodingException {
        return decodeHex(string, ENCODING_DEFAULT);
    }

    /**
     * Decode hex to string.
     *
     * @param string  String
     * @param charSet CharSet
     * @return <code>String</code> string
     * @throws DecoderException             if decode error occurs
     * @throws UnsupportedEncodingException unsupported encoding exception
     */
    public static String decodeHex(String string, String charSet) throws UnsupportedEncodingException, DecoderException {
        if (string == null) {
            return null;
        }
        return new String(Hex.decodeHex(string.toCharArray()), charSet);
    }

    /**
     * Encode string to ascii.
     *
     * @param string String
     * @return <code>String</code> ascii string
     * @throws UnsupportedEncodingException unsupported encoding exception
     */
    public static String encodeAscii(String string) throws UnsupportedEncodingException {
        return encodeAscii(string, ENCODING_DEFAULT);
    }

    /**
     * Encode string to ascii.
     *
     * @param string  String
     * @param charSet CharSet
     * @return <code>String</code> ascii string
     * @throws UnsupportedEncodingException unsupported encoding exception
     */
    public static String encodeAscii(String string, String charSet) throws UnsupportedEncodingException {
        if (string == null) {
            return null;
        }
        return BinaryCodec.toAsciiString(string.getBytes(charSet));
    }

    /**
     * Decode ascii to string.
     *
     * @param string String
     * @return <code>String</code> string
     * @throws UnsupportedEncodingException unsupported encoding exception
     */
    public static String decodeAscii(String string) throws UnsupportedEncodingException {
        return decodeAscii(string, ENCODING_DEFAULT);
    }

    /**
     * Decode ascii to string.
     *
     * @param string  String
     * @param charSet CharSet
     * @return <code>String</code> string
     * @throws UnsupportedEncodingException unsupported encoding exception
     */
    public static String decodeAscii(String string, String charSet) throws UnsupportedEncodingException {
        if (string == null) {
            return null;
        }
        return new String(BinaryCodec.fromAscii(string.toCharArray()), charSet);
    }

    /**
     * Encode string for URL.
     *
     * @param string String
     * @return <code>String</code> url string
     * @throws UnsupportedEncodingException unsupported encoding exception
     */
    public static String encodeURL(String string) throws UnsupportedEncodingException {
        return encodeURL(string, ENCODING_DEFAULT);
    }

    /**
     * Encode string for URL.
     *
     * @param string  String
     * @param charSet CharSet
     * @return <code>String</code> url string
     * @throws UnsupportedEncodingException unsupported encoding exception
     */
    public static String encodeURL(String string, String charSet) throws UnsupportedEncodingException {
        if (string == null) {
            return null;
        }
        return new String(URLCodec.encodeUrl(null, string.getBytes(charSet)), charSet);
    }

    /**
     * Decode URL.
     *
     * @param string String
     * @return <code>String</code> string
     * @throws DecoderException             if decode error occurs
     * @throws UnsupportedEncodingException unsupported encoding exception
     */
    public static String decodeURL(String string) throws DecoderException, UnsupportedEncodingException {
        return decodeURL(string, ENCODING_DEFAULT);
    }

    /**
     * Decode URL.
     *
     * @param string  String
     * @param charSet CharSet
     * @return <code>String</code> string
     * @throws DecoderException             if decode error occurs
     * @throws UnsupportedEncodingException unsupported encoding exception
     */
    public static String decodeURL(String string, String charSet) throws DecoderException, UnsupportedEncodingException {
        if (string == null) {
            return null;
        }
        return new String(URLCodec.decodeUrl(string.getBytes(charSet)), charSet);
    }

    /**
     * Calculates the MD5 digest and returns the value as a 32 character hex string.
     *
     * @param string String
     * @return <code>String</code> MD5 digest as a hex string
     * @throws UnsupportedEncodingException unsupported encoding exception
     */
    public static String encryptMd5(String string) throws UnsupportedEncodingException {
        return encryptMd5(string, ENCODING_DEFAULT);
    }

    /**
     * Calculates the MD5 digest and returns the value as a 32 character hex string.
     *
     * @param string  String
     * @param charSet CharSet
     * @return <code>String</code> MD5 digest as a hex string
     * @throws UnsupportedEncodingException unsupported encoding exception
     */
    public static String encryptMd5(String string, String charSet) throws UnsupportedEncodingException {
        return DigestUtils.md5Hex(string.getBytes(charSet));
    }

    /**
     * Calculates the SHA-1 digest and returns the value as a hex string.
     *
     * @param string String
     * @return <code>String</code> SHA-1 string
     * @throws UnsupportedEncodingException unsupported encoding exception
     */
    public static String encryptSha(String string) throws UnsupportedEncodingException {
        return encryptSha(string, ENCODING_DEFAULT);
    }

    /**
     * Calculates the SHA-1 digest and returns the value as a hex string.
     *
     * @param string  String
     * @param charSet CharSet
     * @return <code>String</code> SHA-1 string
     * @throws UnsupportedEncodingException unsupported encoding exception
     */
    public static String encryptSha(String string, String charSet) throws UnsupportedEncodingException {
        if (string == null) {
            return null;
        }
        return DigestUtils.shaHex(string.getBytes(charSet));
    }

    /**
     * Calculates the SHA-256 digest and returns the value as a hex string.
     *
     * @param string String
     * @return <code>String</code> SHA-256 string
     * @throws UnsupportedEncodingException unsupported encoding exception
     */
    public static String encryptSha256(String string) throws UnsupportedEncodingException {
        return encryptSha256(string, ENCODING_DEFAULT);
    }

    /**
     * Calculates the SHA-256 digest and returns the value as a hex string.
     *
     * @param string  String
     * @param charSet CharSet
     * @return <code>String</code> SHA-256 string
     * @throws UnsupportedEncodingException unsupported encoding exception
     */
    public static String encryptSha256(String string, String charSet) throws UnsupportedEncodingException {
        if (string == null) {
            return null;
        }
        return DigestUtils.sha256Hex(string.getBytes(charSet));
    }

    /**
     * Calculates the SHA-384 digest and returns the value as a hex string.
     *
     * @param string String
     * @return <code>String</code> SHA-384 string
     * @throws UnsupportedEncodingException unsupported encoding exception
     */
    public static String encryptSha384(String string) throws UnsupportedEncodingException {
        return encryptSha384(string, ENCODING_DEFAULT);
    }

    /**
     * Calculates the SHA-384 digest and returns the value as a hex string.
     *
     * @param string  String
     * @param charSet CharSet
     * @return <code>String</code> SHA-384 string
     * @throws UnsupportedEncodingException unsupported encoding exception
     */
    public static String encryptSha384(String string, String charSet) throws UnsupportedEncodingException {
        if (string == null) {
            return null;
        }
        return DigestUtils.sha384Hex(string.getBytes(charSet));
    }

    /**
     * Calculates the SHA-512 digest and returns the value as a hex string.
     *
     * @param string String
     * @return <code>String</code> SHA-512 string
     * @throws UnsupportedEncodingException unsupported encoding exception
     */
    public static String encryptSha512(String string) throws UnsupportedEncodingException {
        return encryptSha512(string, ENCODING_DEFAULT);
    }

    /**
     * Calculates the SHA-512 digest and returns the value as a hex string.
     *
     * @param string  String
     * @param charSet CharSet
     * @return <code>String</code> SHA-512 string
     * @throws UnsupportedEncodingException unsupported encoding exception
     */
    public static String encryptSha512(String string, String charSet) throws UnsupportedEncodingException {
        if (string == null) {
            return null;
        }
        return DigestUtils.sha512Hex(string.getBytes(charSet));
    }

    /**
     * Encode string for Base32.
     *
     * @param string String
     * @return <code>String</code> Base32 string
     * @throws UnsupportedEncodingException unsupported encoding exception
     */
    public static String encodeBase32(String string) throws UnsupportedEncodingException {
        return encodeBase32(string, ENCODING_DEFAULT);
    }

    /**
     * Encode string for Base32.
     *
     * @param string  String
     * @param charSet CharSet
     * @return <code>String</code> Base32 string
     * @throws UnsupportedEncodingException unsupported encoding exception
     */
    public static String encodeBase32(String string, String charSet) throws UnsupportedEncodingException {
        if (string == null) {
            return null;
        }
        Base32 base32 = new Base32();
        return new String(base32.encode(string.getBytes(charSet)));
    }

    /**
     * Decode Base32.
     *
     * @param string String
     * @return <code>String</code> string
     * @throws UnsupportedEncodingException unsupported encoding exception
     */
    public static String decodeBase32(String string) throws UnsupportedEncodingException {
        return decodeBase32(string, ENCODING_DEFAULT);
    }

    /**
     * Decode Base32.
     *
     * @param string  String
     * @param charSet CharSet
     * @return <code>String</code> string
     * @throws UnsupportedEncodingException unsupported encoding exception
     */
    public static String decodeBase32(String string, String charSet) throws UnsupportedEncodingException {
        if (string == null) {
            return null;
        }
        Base32 base32 = new Base32();
        return new String(base32.decode(string.getBytes(charSet)), charSet);
    }

    /**
     * Encode string for Base64.
     *
     * @param string String
     * @return <code>String</code> Base64 string
     * @throws UnsupportedEncodingException unsupported encoding exception
     */
    public static String encodeBase64(String string) throws UnsupportedEncodingException {
        return encodeBase64(string, ENCODING_DEFAULT);
    }

    /**
     * Encode string for Base64.
     *
     * @param string  String
     * @param charSet CharSet
     * @return <code>String</code> Base64 string
     * @throws UnsupportedEncodingException unsupported encoding exception
     */
    public static String encodeBase64(String string, String charSet) throws UnsupportedEncodingException {
        if (string == null) {
            return null;
        }
        return new String(Base64.encodeBase64(string.getBytes(charSet)));
    }

    /**
     * Decode Base64.
     *
     * @param string String
     * @return <code>String</code> string
     * @throws UnsupportedEncodingException unsupported encoding exception
     */
    public static String decodeBase64(String string) throws UnsupportedEncodingException {
        return decodeBase64(string, ENCODING_DEFAULT);
    }

    /**
     * Decode Base64.
     *
     * @param string  String
     * @param charSet CharSet
     * @return <code>String</code> string
     * @throws UnsupportedEncodingException unsupported encoding exception
     */
    public static String decodeBase64(String string, String charSet) throws UnsupportedEncodingException {
        if (string == null) {
            return null;
        }
        return new String(Base64.decodeBase64(string.getBytes(charSet)), charSet);
    }
}

<|start_filename|>src/main/java/bs/tool/eclipse/ProjectPropertiesDeal.java<|end_filename|>
package bs.tool.eclipse;

import bs.tool.commongui.GuiUtils;

import javax.swing.*;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

/**
 * Eclipse Project Properties Deal.
 *
 * @author Baishui2004
 * @version 1.1
 * @date 2013-4-5
 */
public class ProjectPropertiesDeal implements ProjectPropertiesDealInterface {

    /**
     * Eclipse的Java Project、Dynamic Web Project或者MyEclipse的Web Project绝对路径地址.
     */
    private String projectPath;

    /**
     * 解析文件'.project'以获取Project Name, 此种方法要求'.project'文件根节点的第一个name子节点即是Project Name.
     */
    private String projectNameFile = "/.project";
    /**
     * 解析文件'.settings/org.eclipse.jdt.core.prefs'以获取compileSource以及compileTarget.
     */
    private String compilePropsFile = "/.settings/org.eclipse.jdt.core.prefs";
    /**
     * 如果没有'.settings/org.eclipse.jdt.core.prefs'文件，则解析文件'.settings/org.eclipse.wst.common.project.facet.core.xml'以"installed java facet version".
     */
    private String projectFacetCoreFile = "/.settings/org.eclipse.wst.common.project.facet.core.xml";
    /**
     * 解析文件'.classpath'以获取javaSourcesPath以及outputPath.
     */
    private String classpathFile = "/.classpath";
    /**
     * 解析文件'.settings/.jsdtscope'以获取webappPath.
     */
    private String webappPropsFile = "/.settings/.jsdtscope";

    /**
     * Project Name.
     */
    private String projectName;
    /**
     * Java Compile Source.
     */
    private String compileSource;
    /**
     * Java Compile Target.
     */
    private String compileTarget;
    /**
     * Java Sources path.
     */
    private String[] javaSourcesPath;
    /**
     * Output classes path.
     */
    private String outputPath;

    /**
     * 是否是Java Web Project.
     */
    private boolean javaWebProject;

    /**
     * Project Webapp path.
     */
    private String webappPath;

    /**
     * Main入口.
     * <p>
     * <pre>
     * 只接受传入一个参数, 即Eclipse的Java Project、Dynamic Web Project或者MyEclipse的Web Project绝对路径地址.
     * </pre>
     */
    public static void main(String[] args) throws IOException {
        if (args.length != 1) {
            throw new IllegalArgumentException("Parameters error.");
        }
        ProjectPropertiesDealInterface propertiesDeal = new ProjectPropertiesDeal();
        propertiesDeal.deal(args[0]);
    }

    /**
     * 解析属性文件获得Project相关属性.
     */
    @Override
    public void deal(String projectPath) throws IOException {

        setProjectPath(projectPath);
        if (!isJavaOrJavaWebEclipseProject(projectPath)) {
            throw new IllegalArgumentException("The Path: \'" + projectPath
                    + "\' not has a Eclipse Java Project, Dynamic Web Project or MyEclipse Web Project.");
        }

        dealProjectName();
        dealCompileSourceAndTarget();
        dealSourceAndOutput();
        if (isJavaWebProject()) {
            dealWebappPath();
        }

        print("************ Project properties Start ************");
        print("Project Path: " + projectPath);
        print("Project Name: " + getProjectName());
        print("Java Compile Source: " + getCompileSource());
        print("Java Compile Target: " + getCompileTarget());
        String[] javaSourcesPath = getJavaSourcesPath();
        if (javaSourcesPath != null) {
            for (int i = 0; i < javaSourcesPath.length; i++) {
                print("Sources Path: " + javaSourcesPath[i]);
            }
        }
        print("Output Path: " + getOutputPath());
        print("Is Java Web Project: " + isJavaWebProject());
        if (isJavaWebProject()) {
            print("Webapp Path: " + getWebappPath());
        }
        print("************ Project properties End ************\n");
    }

    /**
     * 运行日志输出文本域.
     */
    private JTextArea runLogTextArea;

    @Override
    public void setRunLogTextArea(JTextArea runLogTextArea) {
        this.runLogTextArea = runLogTextArea;
    }

    /**
     * 输出.
     */
    private void print(String log) {
        if (runLogTextArea != null) {
            runLogTextArea.append(log + "\n");
        } else {
            System.out.print(log + "\n");
        }
    }

    /**
     * 是否是Eclipse 的Java Project、Dynamic Web Project或者MyEclipse的Web Project.
     */
    @Override
    public boolean isJavaOrJavaWebEclipseProject(String projectPath) {
        if (!new File(projectPath + this.projectNameFile).exists()) {
            return false;
        } else if (!new File(projectPath + this.compilePropsFile).exists()
                && !new File(projectPath + this.projectFacetCoreFile).exists()) {
            return false;
        } else if (!new File(projectPath + this.classpathFile).exists()) {
            return false;
        }
        if (!new File(this.projectPath + this.webappPropsFile).exists()) {
            javaWebProject = false;
        } else {
            javaWebProject = true;
        }
        return true;
    }

    @Override
    public boolean isJavaWebProject() {
        return this.javaWebProject;
    }

    @Override
    public String getProjectPath() {
        return this.projectPath;
    }

    @Override
    public void setProjectPath(String projectPath) {
        this.projectPath = projectPath;
    }

    @Override
    public String getProjectName() {
        return this.projectName;
    }

    @Override
    public String getCompileSource() {
        return this.compileSource;
    }

    @Override
    public String getCompileTarget() {
        return this.compileTarget;
    }

    @Override
    public String[] getJavaSourcesPath() {
        return this.javaSourcesPath;
    }

    @Override
    public String getOutputPath() {
        return this.outputPath;
    }

    @Override
    public String getWebappPath() {
        return this.webappPath;
    }

    /**
     * 解析文件'.project'以获取Project Name, 此种方法要求'.project'文件根节点的第一个name子节点即是Project Name.
     */
    private void dealProjectName() throws FileNotFoundException {
        String xmlPath = this.projectPath + this.projectNameFile;
        parseXmlProperties(xmlPath, new XmlParse() {
            @Override
            public void parse(XMLStreamReader reader) throws XMLStreamException {
                boolean pdFlag = false;
                boolean pnFlag = false;
                while (reader.hasNext()) {
                    int i = reader.next();
                    if (i == XMLStreamConstants.START_ELEMENT) {
                        String elementName = reader.getLocalName();
                        if ("projectDescription".equals(elementName)) {
                            pdFlag = true;
                        }
                        if (pdFlag && "name".equals(elementName)) {
                            pnFlag = true;
                        }
                    }
                    if (pnFlag && reader.hasText()) {
                        projectName = reader.getText().trim();
                        break;
                    }
                }
            }
        });
    }

    /**
     * 解析文件'.settings/org.eclipse.jdt.core.prefs'以获取compileSource以及compileTarget.
     * 如果没有'.settings/org.eclipse.jdt.core.prefs'文件，则解析文件'.settings/org.eclipse.wst.common.project.facet.core.xml'以获取"installed java facet version".
     */
    private void dealCompileSourceAndTarget() throws IOException {
        String filePath = this.projectPath + this.compilePropsFile;
        if (new File(filePath).exists()) {
            Properties properties = new Properties();
            InputStream in = null;
            try {
                in = new FileInputStream(new File(filePath));
                properties.load(in);
            } finally {
                if (in != null) {
                    in.close();
                }
            }
            compileSource = properties.getProperty("org.eclipse.jdt.core.compiler.source");
            compileTarget = properties.getProperty("org.eclipse.jdt.core.compiler.compliance");
        } else {
            String xmlPath = this.projectPath + this.projectFacetCoreFile;
            parseXmlProperties(xmlPath, new XmlParse() {
                @Override
                public void parse(XMLStreamReader reader) throws XMLStreamException {
                    while (reader.hasNext()) {
                        int i = reader.next();
                        if (i == XMLStreamConstants.START_ELEMENT) {
                            String elementName = reader.getLocalName();
                            if ("installed".equals(elementName)) {
                                String kind = reader.getAttributeValue(null, "facet");
                                if ("java".equals(kind)) {
                                    compileSource = reader.getAttributeValue(null, "version");
                                    compileTarget = compileSource;
                                }
                            }
                        }
                    }
                }
            });
        }
    }

    /**
     * 解析文件'.classpath'以获取javaSourcesPath以及outputPath.
     */
    private void dealSourceAndOutput() throws FileNotFoundException {
        String xmlPath = this.projectPath + this.classpathFile;
        parseXmlProperties(xmlPath, new XmlParse() {
            @Override
            public void parse(XMLStreamReader reader) throws XMLStreamException {
                List<String> javaSourcesPaths = new ArrayList<String>();
                while (reader.hasNext()) {
                    int i = reader.next();
                    if (i == XMLStreamConstants.START_ELEMENT) {
                        String elementName = reader.getLocalName();
                        if ("classpathentry".equals(elementName)) {
                            String kind = reader.getAttributeValue(null, "kind");
                            String path = reader.getAttributeValue(null, "path");
                            if ("src".equals(kind)) {
                                javaSourcesPaths.add(path);
                            } else if ("output".equals(kind)) {
                                outputPath = path;
                            }
                        }
                    }
                }
                javaSourcesPath = new String[javaSourcesPaths.size()];
                for (int i = 0; i < javaSourcesPath.length; i++) {
                    javaSourcesPath[i] = javaSourcesPaths.get(i);
                }
            }
        });
    }

    /**
     * 解析文件'.settings/.jsdtscope'以获取webappPath.
     */
    private void dealWebappPath() throws FileNotFoundException {
        String xmlPath = this.projectPath + this.webappPropsFile;
        parseXmlProperties(xmlPath, new XmlParse() {
            @Override
            public void parse(XMLStreamReader reader) throws XMLStreamException {
                while (reader.hasNext()) {
                    int i = reader.next();
                    if (i == XMLStreamConstants.START_ELEMENT) {
                        String elementName = reader.getLocalName();
                        if ("classpathentry".equals(elementName)) {
                            String kind = reader.getAttributeValue(null, "kind");
                            if ("src".equals(kind)) {
                                webappPath = reader.getAttributeValue(null, "path");
                            }
                        }
                    }
                }
            }
        });
    }

    /**
     * 解析XML接口.
     */
    private interface XmlParse {
        void parse(XMLStreamReader reader) throws XMLStreamException;
    }

    /**
     * 解析XML.
     */
    private static void parseXmlProperties(String xmlPath, XmlParse xmlParse) throws FileNotFoundException {
        XMLInputFactory factory = XMLInputFactory.newInstance();
        InputStream stream = null;
        XMLStreamReader reader = null;
        try {
            stream = new FileInputStream(new File(xmlPath));
            reader = factory.createXMLStreamReader(stream);
            xmlParse.parse(reader);
        } catch (XMLStreamException e) {
            GuiUtils.log(e);
        } finally {
            if (stream != null) {
                try {
                    stream.close();
                } catch (IOException e) {
                    GuiUtils.log(e);
                } finally {
                    if (reader != null) {
                        try {
                            reader.close();
                        } catch (XMLStreamException e) {
                            GuiUtils.log(e);
                        }
                    }
                }
            }
        }
    }

}


<|start_filename|>src/main/java/bs/tool/commongui/plugins/InterestCalculator.java<|end_filename|>
package bs.tool.commongui.plugins;

import bs.tool.commongui.AbstractGuiJPanel;
import bs.tool.commongui.GuiUtils;
import bs.tool.commongui.utils.ExcelUtils;
import bs.tool.commongui.utils.NumberUtils;
import bs.tool.commongui.utils.SimpleMouseListener;
import bs.tool.commongui.utils.interest.InterestUtils;
import bs.tool.commongui.utils.interest.LendType;
import bs.tool.commongui.utils.interest.LoanInterestBean;
import bs.tool.commongui.utils.interest.LoanInterestUtils;
import jxl.Workbook;
import jxl.write.Label;
import jxl.write.*;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.io.File;
import java.io.FileOutputStream;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;

import static bs.tool.commongui.utils.interest.LoanInterestBean.*;

/**
 * 利息计算器.
 */
public class InterestCalculator extends AbstractGuiJPanel {

    private static final long serialVersionUID = 1L;

    /**
     * 贷款金额表单.
     */
    private JTextField loanAmountField = new JTextField("250", 5);

    /**
     * 贷款年限表单.
     */
    private JTextField loanYearField = new JTextField("30", 5);

    /**
     * 贷款利率表单.
     */
    private JTextField loanRateField = new JTextField("5.05", 5);

    /**
     * 计息方式.
     */
    private String[] lendTypes = new String[]{"", LendType.Interest.getDesc(), LendType.Principal.getDesc()};

    /**
     * 当前计息方式.
     */
    private String curLendType = lendTypes[0];
    /**
     * 左边结果文本域.
     */
    private JTextArea leftResultArea = createJTextArea(GuiUtils.font12_cn);
    /**
     * 右边结果文本域.
     */
    private JTextArea rightResultArea = createJTextArea(GuiUtils.font12_cn);

    /**
     * 贷款对比利率表单.
     */
    private JTextField compareLoanRateField = new JTextField("5.10", 5);

    /**
     * 贷款存款/理财利率表单.
     */
    private JTextField depositRateField = new JTextField("4.00", 5);

    /**
     * 提前还款金额表单.
     */
    private JTextField aheadAmountField = new JTextField("50", 5);

    /**
     * 提前还款年限表单.
     */
    private JTextField aheadYearField = new JTextField("5", 5);

    /**
     * 提前贷款后利率表单.
     */
    private JTextField aheadRateField = new JTextField("5.10", 5);

    /**
     * 提前还款后缩短年限表单.
     */
    private JTextField aheadYearReduceField = new JTextField("5", 5);

    /**
     * 分期借款金额表单.
     */
    private JTextField periodAmountField = new JTextField("10000", 5);
    /**
     * 分期期数表单.
     */
    private JTextField periodsField = new JTextField("12", 5);
    /**
     * 分期费率表单.
     */
    private JTextField periodRateField = new JTextField("0.75", 5);

    public InterestCalculator() {

        // 主面板：边界布局，North, Center两部分
        setLayout(new BorderLayout());

        JPanel topPanel = new JPanel(new BorderLayout());

        // 基础表单、计算按钮Panel
        JPanel topCenterPanel = new JPanel(new GridLayout(4, 1));
        topPanel.add(topCenterPanel, BorderLayout.CENTER);
        add(topPanel, BorderLayout.NORTH);

        JPanel calculateBaseRawPanel = new JPanel(new BorderLayout());
        JPanel calculateBasePanel = new JPanel(new FlowLayout(FlowLayout.LEADING));
        calculateBaseRawPanel.add(calculateBasePanel, BorderLayout.CENTER);
        addJLabel(calculateBasePanel, "[基础信息]", GuiUtils.font14b_cn);
        addJLabel(calculateBasePanel, "贷款金额:", GuiUtils.font13_cn);
        addJTextField(calculateBasePanel, loanAmountField, GuiUtils.font13_cn);
        addJLabel(calculateBasePanel, "万元  贷款年限:", GuiUtils.font13_cn);
        addJTextField(calculateBasePanel, loanYearField, GuiUtils.font13_cn);
        addJLabel(calculateBasePanel, "年  贷款利率:", GuiUtils.font13_cn);
        addJTextField(calculateBasePanel, loanRateField, GuiUtils.font13_cn);
        addJLabel(calculateBasePanel, "%", GuiUtils.font13_cn);
        /* 目前不使用，暂隐藏
        addJLabel(calculateBasePanel, "%  计息方式:", GuiUtils.font13_cn);
        // 计息方式
        addJComboBox(calculateBasePanel, lendTypes, GuiUtils.font13_cn, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                curLendType = ((JComboBox) event.getSource()).getSelectedItem().toString();
            }
        });
        */
        // 基础计算按钮
        JPanel calculateBaseButtonPanel = new JPanel(new FlowLayout(FlowLayout.LEADING));
        calculateBaseRawPanel.add(calculateBaseButtonPanel, BorderLayout.EAST);
        JButton calculateBaseButton = createJButton("计算", "", GuiUtils.font13_cn);
        calculateBaseButtonPanel.add(calculateBaseButton);
        calculateBaseButton.addMouseListener(new SimpleMouseListener() {
            @Override
            public void mouseReleased(MouseEvent event) {
                calculate(1, null);
            }

            @Override
            public void mousePressed(MouseEvent e) {
                leftResultArea.setText("");
                rightResultArea.setText("");
            }
        });
        topCenterPanel.add(calculateBaseRawPanel);

        // 比较信息表单、计算按钮Panel
        JPanel calculateCompareRawPanel = new JPanel(new BorderLayout());
        JPanel calculateComparePanel = new JPanel(new FlowLayout(FlowLayout.LEADING));
        calculateCompareRawPanel.add(calculateComparePanel, BorderLayout.CENTER);
        addJLabel(calculateComparePanel, "[比较信息]", GuiUtils.font14b_cn);
        addJLabel(calculateComparePanel, "对比利率:", GuiUtils.font13_cn);
        addJTextField(calculateComparePanel, compareLoanRateField, GuiUtils.font13_cn);
        addJLabel(calculateComparePanel, "%    存款利率:", GuiUtils.font13_cn);
        addJTextField(calculateComparePanel, depositRateField, GuiUtils.font13_cn);
        addJLabel(calculateComparePanel, "%", GuiUtils.font13_cn);
        addJLabel(calculateComparePanel, " ", GuiUtils.font13_cn);
        // 比较计算按钮
        JPanel calculateCompareButtonPanel = new JPanel(new FlowLayout(FlowLayout.LEADING));
        calculateCompareRawPanel.add(calculateCompareButtonPanel, BorderLayout.EAST);
        JButton calculateCompareButton = createJButton("比较", "", GuiUtils.font13_cn);
        calculateCompareButtonPanel.add(calculateCompareButton);
        calculateCompareButton.addMouseListener(new SimpleMouseListener() {
            @Override
            public void mouseReleased(MouseEvent event) {
                calculate(2, null);
            }

            @Override
            public void mousePressed(MouseEvent e) {
                leftResultArea.setText("");
                rightResultArea.setText("");
            }
        });
        topCenterPanel.add(calculateCompareRawPanel);

        // 提前还款信息表单、计算按钮Panel
        JPanel calculateAheadRawPanel = new JPanel(new BorderLayout());
        JPanel calculateAheadPanel = new JPanel(new FlowLayout(FlowLayout.LEADING));
        calculateAheadRawPanel.add(calculateAheadPanel, BorderLayout.CENTER);
        addJLabel(calculateAheadPanel, "[提前还款]", GuiUtils.font14b_cn);
        addJLabel(calculateAheadPanel, "还款金额:", GuiUtils.font13_cn);
        addJTextField(calculateAheadPanel, aheadAmountField, GuiUtils.font13_cn);
        addJLabel(calculateAheadPanel, "万元  还款期限:", GuiUtils.font13_cn);
        addJTextField(calculateAheadPanel, aheadYearField, GuiUtils.font13_cn);
        addJLabel(calculateAheadPanel, "年  还款利率:", GuiUtils.font13_cn);
        addJTextField(calculateAheadPanel, aheadRateField, GuiUtils.font13_cn);
        addJLabel(calculateAheadPanel, "%  缩短还款年限:", GuiUtils.font13_cn);
        addJTextField(calculateAheadPanel, aheadYearReduceField, GuiUtils.font13_cn);
        addJLabel(calculateAheadPanel, "年", GuiUtils.font13_cn);
        // 提前还款计算按钮
        JPanel calculateAheadButtonPanel = new JPanel(new FlowLayout(FlowLayout.LEADING));
        calculateAheadRawPanel.add(calculateAheadButtonPanel, BorderLayout.EAST);
        JButton calculateAheadButton = createJButton("计算", "", GuiUtils.font13_cn);
        calculateAheadButtonPanel.add(calculateAheadButton);
        calculateAheadButton.addMouseListener(new SimpleMouseListener() {
            @Override
            public void mouseReleased(MouseEvent event) {
                calculate(3, null);
            }

            @Override
            public void mousePressed(MouseEvent e) {
                leftResultArea.setText("");
                rightResultArea.setText("");
            }
        });
        topCenterPanel.add(calculateAheadRawPanel);

        // 分期还款表单、计算按钮Panel
        JPanel calculatePeriodRawPanel = new JPanel(new BorderLayout());
        JPanel calculatePeriodPanel = new JPanel(new FlowLayout(FlowLayout.LEADING));
        calculatePeriodRawPanel.add(calculatePeriodPanel, BorderLayout.CENTER);
        addJLabel(calculatePeriodPanel, "[分期还款]", GuiUtils.font14b_cn);
        addJLabel(calculatePeriodPanel, "借款金额:", GuiUtils.font13_cn);
        addJTextField(calculatePeriodPanel, periodAmountField, GuiUtils.font13_cn);
        addJLabel(calculatePeriodPanel, "元    分期期限:", GuiUtils.font13_cn);
        addJTextField(calculatePeriodPanel, periodsField, GuiUtils.font13_cn);
        addJLabel(calculatePeriodPanel, "期  名义费率:", GuiUtils.font13_cn);
        addJTextField(calculatePeriodPanel, periodRateField, GuiUtils.font13_cn);
        addJLabel(calculatePeriodPanel, "%", GuiUtils.font13_cn);
        // 分期真实利率计算按钮
        JPanel calculatePeriodButtonPanel = new JPanel(new FlowLayout(FlowLayout.LEADING));
        calculatePeriodRawPanel.add(calculatePeriodButtonPanel, BorderLayout.EAST);
        JButton calculatePeriodButton = createJButton("计算", "", GuiUtils.font13_cn);
        calculatePeriodButtonPanel.add(calculatePeriodButton);
        calculatePeriodButton.addMouseListener(new SimpleMouseListener() {
            @Override
            public void mouseReleased(MouseEvent event) {
                calculate(4, null);
            }

            @Override
            public void mousePressed(MouseEvent e) {
                leftResultArea.setText("");
                rightResultArea.setText("");
            }
        });
        topCenterPanel.add(calculatePeriodRawPanel);

        topPanel.add(topCenterPanel, BorderLayout.CENTER);

        // 保存计算信息为Excel
        JPanel topEastPanel = new JPanel(new BorderLayout());
        JPanel topEastNorthPanel = new JPanel(new GridLayout(1, 1));
        addJLabel(topEastNorthPanel, " ", GuiUtils.font13_cn);
        topEastPanel.add(topEastNorthPanel, BorderLayout.NORTH);
        JButton saveExcelButton = createJButton("保存为Excel", "", GuiUtils.font13_cn);
        topEastPanel.add(saveExcelButton, BorderLayout.CENTER);
        final JFileChooser excelFileChooser = createFileChooser("保存为Excel", "xls");
        saveExcelButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                int option = excelFileChooser.showSaveDialog(null);
                if (option == JFileChooser.APPROVE_OPTION) {
                    try {
                        File selectedFile = excelFileChooser.getSelectedFile();
                        if (selectedFile.getName().indexOf(".") < 0) {
                            excelFileChooser.setSelectedFile(new File(selectedFile.getAbsolutePath() + ".xls"));
                        }
                        File oFile = excelFileChooser.getSelectedFile();
                        FileOutputStream os = new FileOutputStream(oFile);
                        WritableWorkbook workbook = Workbook.createWorkbook(os);
                        workbook.createSheet("概况", 0);
                        workbook.createSheet("等额本息月还款明细", 1);
                        workbook.createSheet("等额本金月还款明细", 2);
                        calculate(1, workbook);
                        calculate(2, workbook);
                        calculate(3, workbook);
                        workbook.write();
                        workbook.close();
                        os.close();
                    } catch (Exception e) {
                        showExceptionMessage(e);
                    }
                }
            }
        });
        JPanel topEastSouthPanel = new JPanel(new GridLayout(3, 1));
        addJLabel(topEastSouthPanel, " ", GuiUtils.font13_cn);
        addJLabel(topEastSouthPanel, " ", GuiUtils.font13_cn);
        topEastPanel.add(topEastSouthPanel, BorderLayout.SOUTH);

        topPanel.add(topEastPanel, BorderLayout.EAST);

        JPanel resultPanel = new JPanel(new GridLayout(1, 2));
        resultPanel.add(new JScrollPane(leftResultArea));
        resultPanel.add(new JScrollPane(rightResultArea));

        add(resultPanel, BorderLayout.CENTER);
    }

    private List<List<String>> xlsHeader = new ArrayList<List<String>>() {{
        add(new ArrayList<String>() {{
            add("期数");
            add("还款额");
            add("本金");
            add("利息");
            add("本金占比");
            add("利息占比");
            add("累计已还本金");
            add("累计已还利息");
            add("累计已还本金占比");
            add("累计已还利息占比");
        }});
    }};

    private List<String> buildList(Object... str) {
        List<String> list = new ArrayList<String>();
        for (Object s : str) {
            list.add(s == null ? "" : s.toString());
        }
        return list;
    }

    private String appendList(List<List<String>> list, List<String> header, String indent) {
        StringBuilder sb = new StringBuilder();
        for (List<String> l : list) {
            sb.append("\n").append(indent);
            int ls = l.size();
            for (int i = 0; i < ls; i++) {
                String str = l.get(i);
                if (header != null) {
                    if (i != 0) {
                        sb.append(", ");
                    }
                    sb.append(header.get(i)).append("=");
                }
                sb.append(str);
            }
        }
        return sb.length() > 0 ? sb.substring(1) : sb.toString();
    }

    private void appendExcel(List<List<String>> list, WritableSheet sheet, WritableCellFormat cellFormat) throws WriteException {
        appendExcel(list, sheet, cellFormat, 210);
    }

    private void appendExcel(List<List<String>> list, WritableSheet sheet, WritableCellFormat cellFormat, int colWidth) throws WriteException {
        int raw = sheet.getRows();
        for (List<String> l : list) {
            int col = 0;
            for (String str : l) {
                Label lab = new Label(col, raw, str, cellFormat);
                sheet.setColumnView(col, colWidth);
                sheet.addCell(lab);
                col++;
            }
            raw++;
        }
    }

    /**
     * @param type 1 基础信息 2 比较信息 3 提前还款 4 分期还款
     */
    private void calculate(int type, WritableWorkbook workbook) {
        boolean saveToExcel = workbook != null;
        try {
            int loanAmount = Integer.parseInt(loanAmountField.getText()) * 10000;
            int loanYear = Integer.parseInt(loanYearField.getText());
            String rate = loanRateField.getText();

            LoanInterestBean bean = new LoanInterestBean(loanAmount, rate, loanYear, LendType.Interest);
            LoanInterestBean iRsBean = LoanInterestUtils.calculateByLendType(bean);
            bean.setLendType(LendType.Principal);
            LoanInterestBean pRsBean = LoanInterestUtils.calculateByLendType(bean);

            // 概况sheet
            WritableSheet sheet = null;
            if (saveToExcel) {
                sheet = workbook.getSheet(0);
            }
            WritableCellFormat headerCellFormat = ExcelUtils.getHeaderCellFormat();
            WritableCellFormat bodyCellFormat = ExcelUtils.getBodyCellFormat();

            List<List<String>> lineContents = new ArrayList<List<String>>();
            if (type == 1) {
                StringBuilder leftSb = new StringBuilder();

                lineContents.add(buildList("等额本息->基本情况："));
                lineContents.add(buildList("  利息总额=" + LoanInterestUtils.decimalFormat(iRsBean.getInterestTotalAmount()) +
                        "，每月还款=" + LoanInterestUtils.decimalFormat(iRsBean.getMonthRepayments().get(0).getAmount()) +
                        "，月利率=" + NumberUtils.convertPercentageStr(iRsBean.getLendMonthRate()) +
                        "，以分期方式计算的名义年化利率=" + NumberUtils.convertPercentageStr(iRsBean.getPeriodNominallyYearRate())));
                lineContents.add(buildList(""));

                if (saveToExcel) {
                    appendExcel(lineContents, sheet, bodyCellFormat);
                } else {
                    leftSb.append(appendList(lineContents, null, ""));
                }

                leftSb.append("\n等额本息->每月还款：\n");
                lineContents = new ArrayList<List<String>>();
                for (int i = 0; i < iRsBean.getLendMonth(); i++) {
                    MonthlyLendAmount monthlyLendAmount = iRsBean.getMonthRepayments().get(i);
                    lineContents.add(buildList(i + 1,
                            LoanInterestUtils.decimalFormat(monthlyLendAmount.getAmount()),
                            LoanInterestUtils.decimalFormat(monthlyLendAmount.getPrincipal()),
                            LoanInterestUtils.decimalFormat(monthlyLendAmount.getInterest()),
                            NumberUtils.convertPercentageStr(monthlyLendAmount.getPrincipalProportion()),
                            NumberUtils.convertPercentageStr(monthlyLendAmount.getInterestProportion()),
                            LoanInterestUtils.decimalFormat(monthlyLendAmount.getPrincipalTotal()),
                            LoanInterestUtils.decimalFormat(monthlyLendAmount.getInterestTotal()),
                            NumberUtils.convertPercentageStr(monthlyLendAmount.getPrincipalProportionTotal()),
                            NumberUtils.convertPercentageStr(monthlyLendAmount.getInterestProportionTotal())));
                }

                if (saveToExcel) {
                    appendExcel(xlsHeader, workbook.getSheet(1), headerCellFormat, 18);
                    appendExcel(lineContents, workbook.getSheet(1), bodyCellFormat, 18);
                } else {
                    leftSb.append(appendList(lineContents, xlsHeader.get(0), "  "));
                    leftResultArea.setText(leftSb.toString());
                }

                StringBuilder rightSb = new StringBuilder();
                lineContents = new ArrayList<List<String>>();
                lineContents.add(buildList("等额本金->基本情况："));
                lineContents.add(buildList("  利息总额=" + LoanInterestUtils.decimalFormat(pRsBean.getInterestTotalAmount()) +
                        "，首月还款=" + LoanInterestUtils.decimalFormat(pRsBean.getMonthRepayments().get(0).getAmount()) +
                        "，末月还款=" + LoanInterestUtils.decimalFormat(pRsBean.getMonthRepayments().get(pRsBean.getLendMonth() - 1).getAmount()) +
                        "，逐月递减=" + LoanInterestUtils.decimalFormat(pRsBean.getMonthDecrease()) +
                        "，月利率=" + NumberUtils.convertPercentageStr(pRsBean.getLendMonthRate()) +
                        "，以分期方式计算的名义年化利率=" + NumberUtils.convertPercentageStr(pRsBean.getPeriodNominallyYearRate())));
                lineContents.add(buildList(""));

                if (saveToExcel) {
                    appendExcel(lineContents, sheet, bodyCellFormat);
                } else {
                    rightSb.append(appendList(lineContents, null, ""));
                }
                rightSb.append("\n等额本金->每月还款：\n");
                lineContents = new ArrayList<List<String>>();
                for (int i = 0; i < pRsBean.getLendMonth(); i++) {
                    MonthlyLendAmount monthlyLendAmount = pRsBean.getMonthRepayments().get(i);
                    lineContents.add(buildList(i + 1,
                            LoanInterestUtils.decimalFormat(monthlyLendAmount.getAmount()),
                            LoanInterestUtils.decimalFormat(monthlyLendAmount.getPrincipal()),
                            LoanInterestUtils.decimalFormat(monthlyLendAmount.getInterest()),
                            NumberUtils.convertPercentageStr(monthlyLendAmount.getPrincipalProportion()),
                            NumberUtils.convertPercentageStr(monthlyLendAmount.getInterestProportion()),
                            LoanInterestUtils.decimalFormat(monthlyLendAmount.getPrincipalTotal()),
                            LoanInterestUtils.decimalFormat(monthlyLendAmount.getInterestTotal()),
                            NumberUtils.convertPercentageStr(monthlyLendAmount.getPrincipalProportionTotal()),
                            NumberUtils.convertPercentageStr(monthlyLendAmount.getInterestProportionTotal())));
                }
                if (saveToExcel) {
                    appendExcel(xlsHeader, workbook.getSheet(2), headerCellFormat, 18);
                    appendExcel(lineContents, workbook.getSheet(2), bodyCellFormat, 18);
                } else {
                    rightSb.append(appendList(lineContents, xlsHeader.get(0), "  "));
                    rightResultArea.setText(rightSb.toString());
                }
            } else if (type == 2) {
                // 贷款利率比较
                String compareRate = compareLoanRateField.getText();
                LoanInterestBean cBean = new LoanInterestBean(loanAmount, compareRate, loanYear, LendType.Interest);
                LoanInterestBean iCRsBean = LoanInterestUtils.calculateByLendType(cBean);
                cBean.setLendType(LendType.Principal);
                LoanInterestBean pCRsBean = LoanInterestUtils.calculateByLendType(cBean);

                StringBuilder leftSb = new StringBuilder();
                lineContents.add(buildList("不同利率对比情况->等额本息："));
                lineContents.add(buildList("  利率=" + rate + "%->" + compareRate + "%" +
                        "，以分期方式计算的名义年化利率=" + NumberUtils.convertPercentageStr(iRsBean.getPeriodNominallyYearRate()) + "->" + NumberUtils.convertPercentageStr(iCRsBean.getPeriodNominallyYearRate()) +
                        "，总利息=" + LoanInterestUtils.decimalFormat(iRsBean.getInterestTotalAmount()) + "->" + LoanInterestUtils.decimalFormat(iCRsBean.getInterestTotalAmount()) +
                        "，节省利息=" + LoanInterestUtils.decimalFormat(iRsBean.getInterestTotalAmount().subtract(iCRsBean.getInterestTotalAmount())) +
                        "，每月还款额=" + LoanInterestUtils.decimalFormat(iRsBean.getMonthRepayments().get(0).getAmount()) + "->" + LoanInterestUtils.decimalFormat(iCRsBean.getMonthRepayments().get(0).getAmount())));
                lineContents.add(buildList(""));
                lineContents.add(buildList("不同利率对比情况->等额本金："));
                lineContents.add(buildList("  利率=" + rate + "%->" + compareRate + "%" +
                        "，以分期方式计算的名义年化利率=" + NumberUtils.convertPercentageStr(pRsBean.getPeriodNominallyYearRate()) + "->" + NumberUtils.convertPercentageStr(pCRsBean.getPeriodNominallyYearRate()) +
                        "，总利息=" + LoanInterestUtils.decimalFormat(pRsBean.getInterestTotalAmount()) + "->" + LoanInterestUtils.decimalFormat(pCRsBean.getInterestTotalAmount()) +
                        "，节省利息=" + LoanInterestUtils.decimalFormat(pRsBean.getInterestTotalAmount().subtract(pCRsBean.getInterestTotalAmount())) +
                        "，首月还款额=" + LoanInterestUtils.decimalFormat(pRsBean.getMonthRepayments().get(0).getAmount()) + "->" + LoanInterestUtils.decimalFormat(pCRsBean.getMonthRepayments().get(0).getAmount()) +
                        "，末月还款额=" + LoanInterestUtils.decimalFormat(pRsBean.getMonthRepayments().get(pRsBean.getLendMonth() - 1).getAmount()) + "->" + LoanInterestUtils.decimalFormat(pCRsBean.getMonthRepayments().get(pCRsBean.getLendMonth() - 1).getAmount()) +
                        "，逐月递减=" + LoanInterestUtils.decimalFormat(pRsBean.getMonthDecrease()) + "->" + LoanInterestUtils.decimalFormat(pCRsBean.getMonthDecrease())));
                lineContents.add(buildList(""));
                if (saveToExcel) {
                    appendExcel(lineContents, sheet, bodyCellFormat);
                } else {
                    leftSb.append(appendList(lineContents, null, ""));
                    leftResultArea.setText(leftSb.toString());
                }

                // 存款复利对比
                String compoundRate = depositRateField.getText();
                ComparePrincipalInterest compare = LoanInterestUtils.comparePrincipalInterest(new ComparePrincipalInterest(pRsBean, iRsBean, compoundRate));
                int firstThan = compare.getFirstLessThanMonth();

                StringBuilder rightSb = new StringBuilder();
                lineContents = new ArrayList<List<String>>();
                lineContents.add(buildList("等额本息与等额本金对比："));
                lineContents.add(buildList("  首月还款额：等额本息" + LoanInterestUtils.decimalFormat(iRsBean.getMonthRepayments().get(0).getAmount()) + "->等额本金" + LoanInterestUtils.decimalFormat(pRsBean.getMonthRepayments().get(0).getAmount()) +
                        "，末月还款额：等额本息" + LoanInterestUtils.decimalFormat(iRsBean.getMonthRepayments().get(iRsBean.getLendMonth() - 1).getAmount()) + "->等额本金" + LoanInterestUtils.decimalFormat(pRsBean.getMonthRepayments().get(pRsBean.getLendMonth() - 1).getAmount())));
                lineContents.add(buildList("  等额本息比等额本金多还利息：" + LoanInterestUtils.decimalFormat(compare.getSavedInterest()) +
                        "，月还款额前者首次大于后者的期数：" + firstThan +
                        "，截止该期后者比前者累计多还：" + LoanInterestUtils.decimalFormat(compare.getMoreLentBefore()) +
                        "，自该期后者比前者累计少还：" + LoanInterestUtils.decimalFormat(compare.getLessLentBefore()) +
                        "，以分期方式计算的名义年化利率：" + NumberUtils.convertPercentageStr(iRsBean.getPeriodNominallyYearRate()) + "->" + NumberUtils.convertPercentageStr(pRsBean.getPeriodNominallyYearRate())));
                lineContents.add(buildList(""));
                lineContents.add(buildList("复利对比："));
                lineContents.add(buildList("  以年化" + compoundRate + "%计算，等额本息比等额本金存款复利少：" + LoanInterestUtils.decimalFormat(compare.getPrincipalFirstLessThanMonthCompoundInterest()
                        .subtract(compare.getFirstLessThanMonthCompoundTwiceInterest())) +
                        "，月还款额前者首次大于后者时的前者累积少还部分钱的复利总额：" + LoanInterestUtils.decimalFormat(compare.getFirstLessThanMonthCompoundInterest()) +
                        "，随后还款日期里该部分钱的复利总额：" + LoanInterestUtils.decimalFormat(compare.getFirstLessThanMonthCompoundTwiceInterest()) +
                        "，自该期后者比前者累计少还部分钱的复利总额：" + LoanInterestUtils.decimalFormat(compare.getPrincipalFirstLessThanMonthCompoundInterest())));
                lineContents.add(buildList(""));
                if (saveToExcel) {
                    appendExcel(lineContents, sheet, bodyCellFormat);
                } else {
                    rightSb.append(appendList(lineContents, null, ""));
                    rightResultArea.setText(rightSb.toString());
                }
            } else if (type == 3) {
                // 提前还款
                int aheadAmount = Integer.parseInt(aheadAmountField.getText()) * 10000;
                int aheadYear = Integer.parseInt(aheadYearField.getText());
                int yearReduce = Integer.parseInt(aheadYearReduceField.getText());
                String aheadRate = aheadRateField.getText();

                LendAheadOfSchedule ahead = new LendAheadOfSchedule(iRsBean, aheadYear, aheadAmount, LendType.Interest, aheadRate, yearReduce);
                ahead = LoanInterestUtils.calculateLendAhead(ahead);
                StringBuilder leftSb = new StringBuilder();
                lineContents.add(buildList("等额本息->提前还款："));
                lineContents.add(buildList("  " + aheadYear + "年已还本金：" + LoanInterestUtils.decimalFormat(ahead.getLentPrincipal()) +
                        "，已还利息：" + LoanInterestUtils.decimalFormat(ahead.getLentInterest()) +
                        "，提前" + aheadYear + "年还款" + aheadAmount + "并且缩短还款" + yearReduce + "年节省利息：" + LoanInterestUtils.decimalFormat(ahead.getSavedInterest()) +
                        "，实际总利息：" + LoanInterestUtils.decimalFormat(ahead.getLentInterest().add(ahead.getNewBean().getInterestTotalAmount()))));
                lineContents.add(buildList("  提前还款后：" +
                        "利息总额=" + LoanInterestUtils.decimalFormat(ahead.getNewBean().getInterestTotalAmount()) +
                        "，每月还款=" + LoanInterestUtils.decimalFormat(ahead.getNewBean().getMonthRepayments().get(0).getAmount()) +
                        "，月利率=" + NumberUtils.convertPercentageStr(ahead.getNewBean().getLendMonthRate()) +
                        "，以分期方式计算的名义年化利率=" + NumberUtils.convertPercentageStr(ahead.getNewBean().getPeriodNominallyYearRate())));
                if (saveToExcel) {
                    appendExcel(lineContents, sheet, bodyCellFormat);
                } else {
                    leftSb.append(appendList(lineContents, null, ""));
                    leftResultArea.setText(leftSb.toString());
                }

                ahead = new LendAheadOfSchedule(pRsBean, aheadYear, aheadAmount, LendType.Principal, aheadRate, yearReduce);
                ahead = LoanInterestUtils.calculateLendAhead(ahead);
                StringBuilder rightSb = new StringBuilder();
                lineContents = new ArrayList<List<String>>();
                if (saveToExcel) {
                    lineContents.add(buildList(""));
                    appendExcel(lineContents, sheet, bodyCellFormat);
                    lineContents = new ArrayList<List<String>>();
                }
                lineContents.add(buildList("等额本金->提前还款："));
                lineContents.add(buildList("  " + aheadYear + "年已还本金：" + LoanInterestUtils.decimalFormat(ahead.getLentPrincipal()) +
                        "，已还利息：" + LoanInterestUtils.decimalFormat(ahead.getLentInterest()) +
                        "，提前" + aheadYear + "年还款" + aheadAmount + "并且缩短还款" + yearReduce + "年节省利息：" + LoanInterestUtils.decimalFormat(ahead.getSavedInterest()) +
                        "，实际总利息：" + LoanInterestUtils.decimalFormat(ahead.getLentInterest().add(ahead.getNewBean().getInterestTotalAmount()))));
                lineContents.add(buildList("  提前还款后：" +
                        "利息总额=" + LoanInterestUtils.decimalFormat(ahead.getNewBean().getInterestTotalAmount()) +
                        "，首月还款=" + LoanInterestUtils.decimalFormat(ahead.getNewBean().getMonthRepayments().get(0).getAmount()) +
                        "，末月还款=" + LoanInterestUtils.decimalFormat(ahead.getNewBean().getMonthRepayments().get(ahead.getNewBean().getLendMonth() - 1).getAmount()) +
                        "，逐月递减=" + LoanInterestUtils.decimalFormat(ahead.getNewBean().getMonthDecrease()) +
                        "，月利率=" + NumberUtils.convertPercentageStr(ahead.getNewBean().getLendMonthRate()) +
                        "，以分期方式计算的名义年化利率=" + NumberUtils.convertPercentageStr(ahead.getNewBean().getPeriodNominallyYearRate())));
                if (saveToExcel) {
                    appendExcel(lineContents, sheet, bodyCellFormat);
                } else {
                    rightSb.append(appendList(lineContents, null, ""));
                    rightResultArea.setText(rightSb.toString());
                }
            } else if (type == 4) {
                StringBuilder leftSb = new StringBuilder();
                // 分期名义利率真实利率计算
                String declaredMonthRate = periodRateField.getText();
                int periods = Integer.parseInt(periodsField.getText());
                int principal = Integer.parseInt(periodAmountField.getText());
                // 利息
                double interest = principal * Double.parseDouble(declaredMonthRate) / 100 * periods;
                lineContents.add(buildList("分期还款->基本情况："));
                lineContents.add(buildList("  借款金额=" + principal +
                        "，总利息=" + interest +
                        "，每期还款=" + NumberUtils.convertPrecision((principal + interest) / periods)));
                // 展示利率
                BigDecimal actualRate = InterestUtils.calculatePeriodActualRate(Double.toString(Double.parseDouble(declaredMonthRate) / 100), periods);
                lineContents.add(buildList(""));
                lineContents.add(buildList("分期还款->真实利率："));
                lineContents.add(buildList("  月利率=" + NumberUtils.convertPercentageStr(actualRate) +
                        "，年化利率=" + NumberUtils.convertPercentageStr(actualRate.multiply(new BigDecimal(12)))));
                if (saveToExcel) {
                    appendExcel(lineContents, sheet, bodyCellFormat);
                } else {
                    leftSb.append(appendList(lineContents, null, ""));
                    leftResultArea.setText(leftSb.toString());
                }
            }
        } catch (Exception e) {
            showExceptionMessage(e);
        }
    }

}


<|start_filename|>src/main/java/bs/tool/commongui/utils/interest/InterestUtils.java<|end_filename|>
package bs.tool.commongui.utils.interest;

import java.math.BigDecimal;
import java.math.RoundingMode;

public class InterestUtils {

    /**
     * 精度.
     */
    public final static int SCALE = 20;

    /**
     * 每月存款变化计算复利.
     *
     * @param principal 初始本金
     * @param amount    初始月存金额
     * @param monthDiff 每月差额
     * @param rate      月利率
     * @param months    存款月数
     */
    public static BigDecimal compoundInterest(BigDecimal principal, BigDecimal amount, BigDecimal monthDiff, BigDecimal rate, int months) {
        BigDecimal all = new BigDecimal(0);
        for (int m = 0; m < months; m++) {
            // 每月本金金额逐月递减
            all = all.add(fixedPrincipalCompoundInterest(amount.subtract(monthDiff.multiply(new BigDecimal(m))), rate, months - m));
        }
        return all.add(fixedPrincipalCompoundInterest(principal, rate, months));
    }

    /**
     * 固定本金计算复利.
     *
     * @param principal 本金
     * @param rate      月利率
     * @param months    存款月数
     */
    public static BigDecimal fixedPrincipalCompoundInterest(BigDecimal principal, BigDecimal rate, int months) {
        for (int s = 0; s < months; s++) {
            principal = principal.multiply(rate.add(new BigDecimal(1)));
        }
        return principal;
    }

    /**
     * 分期真实年化利率计算.
     * 二分最大次数100次进行计算，总本金差值精度精确到0.001.
     *
     * @param declaredPeriodRate 名义每期费率
     * @param periods            期数
     * @return
     */
    public static BigDecimal calculatePeriodActualRate(String declaredPeriodRate, int periods) {
        // 假定本金10000
        BigDecimal principal = new BigDecimal(10000);
        // 平均每期本金
        BigDecimal avgPrincipal = principal.divide(new BigDecimal(periods), InterestUtils.SCALE, RoundingMode.HALF_UP);
        // 利息总额
        BigDecimal interest = principal.multiply(new BigDecimal(declaredPeriodRate)).multiply(new BigDecimal(periods));
        // 每期还款额
        BigDecimal periodAmount = (interest.add(principal)).divide(new BigDecimal(periods), InterestUtils.SCALE, RoundingMode.HALF_UP);
        BigDecimal assumeRate = new BigDecimal(0);
        BigDecimal head = new BigDecimal(0);
        BigDecimal tail = avgPrincipal;
        // 使用二分推测首期本金
        BigDecimal firstPeriodPrincipal;
        // 二分最大次数100次进行计算，总本金差值精度精确到0.001
        for (int i = 0; i < 100; i++) {
            firstPeriodPrincipal = (head.add(tail)).divide(new BigDecimal(2));
            // 以首期还款额 = 总本金 * 实际月利率 + 首期本金 计算月利率
            assumeRate = (periodAmount.subtract(firstPeriodPrincipal)).divide(principal, InterestUtils.SCALE, RoundingMode.HALF_UP);
            BigDecimal assumePrincipal = new BigDecimal(0);
            BigDecimal assumePeriodPrincipal = firstPeriodPrincipal;
            for (int p = 0; p < periods; p++) {
                assumePrincipal = assumePrincipal.add(assumePeriodPrincipal);
                assumePeriodPrincipal = periodAmount.subtract(principal.subtract(assumePrincipal).multiply(assumeRate));
            }
            double s = principal.subtract(assumePrincipal).doubleValue();
            if (s >= -0.001 && s <= 0.001) {
                // System.out.println("calculateActualRate success times: " + i + ", head: " + head + ", tail: " + tail);
                break;
            } else if (s > 0.001) {
                head = firstPeriodPrincipal;
            } else if (s < -0.001) {
                tail = firstPeriodPrincipal;
            }

        }
        return assumeRate;
    }

}


<|start_filename|>src/main/java/bs/tool/commongui/plugins/more/JNotifyVisual.java<|end_filename|>
package bs.tool.commongui.plugins.more;

import bs.org.suite.tool.JNotifyUtils;
import bs.tool.commongui.AbstractGuiJPanel;
import bs.tool.commongui.GuiUtils;
import bs.tool.commongui.utils.SimpleMouseListener;
import net.contentobjects.jnotify.JNotifyException;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * 文件(夹)变化监控，适合Windows、Linux、Mac系统.
 */
public class JNotifyVisual extends AbstractGuiJPanel {

    private static final long serialVersionUID = 1L;

    /**
     * 监控文件夹路径表单.
     */
    private JTextField monitorPathTextField = new JTextField();
    /**
     * 监控文件夹路径选择.
     */
    private JFileChooser monitorPathChooser = new JFileChooser();

    /**
     * 监控按钮.
     */
    private JButton monitorButton;
    /**
     * 停止监控按钮.
     */
    private JButton stopMonitorButton;

    /**
     * 是否保存监控日志.
     */
    private boolean saveLog = false;
    /**
     * 监控日志文件路径表单.
     */
    private JTextField monitorLogFilePathTextField = new JTextField();
    /**
     * 监控日志文件选择.
     */
    private JFileChooser monitorLogFileChooser = new JFileChooser(".");

    /**
     * 文件名包含字符表单.
     */
    private JTextField fileNameContainsTextField;
    /**
     * 文件名不包含字符表单.
     */
    private JTextField fileNameNotContainsTextField;
    /**
     * 文件名是否支持正则.
     */
    private boolean fileNameSupportRegex = false;

    /**
     * 文件夹路径包含字符表单.
     */
    private JTextField folderPathContainsTextField;
    /**
     * 文件夹路径不包含字符表单.
     */
    private JTextField folderPathNotContainsTextField;
    /**
     * 文件夹路径是否支持正则.
     */
    private boolean folderPathSupportRegex = false;

    /**
     * 记录日志文本域.
     */
    private JTextArea logTextArea = createJTextArea(GuiUtils.font14_un);

    /**
     * 高级条件面板.
     */
    private JPanel advanceConditionPanel;

    /**
     * 监控.
     */
    private JNotifyUtils notify;
    /**
     * 监控ID.
     */
    private int watchId;

    public JNotifyVisual() {

        // 主面板：边界布局，分North、Center两部分，North用于放置条件控件，Center是放置高级(条件)及监控日志输出
        setLayout(new BorderLayout());

        // 输入/操作
        JPanel inputPanel = new JPanel(new GridLayout(2, 1));
        add(inputPanel, BorderLayout.NORTH);

        // 监控文件夹选择/填写
        JPanel fileChooPanel = new JPanel(new BorderLayout());
        addJLabel(fileChooPanel, "  监控路径: ", GuiUtils.font14_cn, BorderLayout.WEST);
        JPanel pathPanel = new JPanel(new BorderLayout());
        pathPanel.add(new JPanel(), BorderLayout.NORTH);
        addJTextField(pathPanel, monitorPathTextField, GuiUtils.font14_un, BorderLayout.CENTER);
        pathPanel.add(new JPanel(), BorderLayout.SOUTH);
        fileChooPanel.add(pathPanel, BorderLayout.CENTER);
        JPanel buttonFlowPanel = new JPanel(new FlowLayout(FlowLayout.LEADING));
        addJButton(buttonFlowPanel, "浏览", "", GuiUtils.font12_cn,
                buttonBrowseListener(monitorPathChooser, monitorPathTextField));
        // 监控按钮
        monitorButton = createJButton("监控", "", GuiUtils.font14b_cn);
        monitorButton.addMouseListener(new SimpleMouseListener() {
            @Override
            public void mouseReleased(MouseEvent event) {
                String path = monitorPathTextField.getText().trim();
                if (!new File(path).exists()) {
                    showMessage("监控路径不存在！", "警告", JOptionPane.WARNING_MESSAGE);
                    return;
                } else if (!new File(path).isDirectory()) {
                    showMessage("只能监控文件夹！", "警告", JOptionPane.WARNING_MESSAGE);
                    return;
                }
                Map<String, Object> paramsMap = new HashMap<String, Object>();
                paramsMap.put("fileNameContainsText", fileNameContainsTextField.getText().trim());
                paramsMap.put("fileNameNotContainsText", fileNameNotContainsTextField.getText().trim());
                paramsMap.put("fileNameSupportRegex", fileNameSupportRegex);
                paramsMap.put("folderPathContainsText", folderPathContainsTextField.getText().trim());
                paramsMap.put("folderPathNotContainsText", folderPathNotContainsTextField.getText().trim());
                paramsMap.put("folderPathSupportRegex", folderPathSupportRegex);
                if (saveLog) {
                    String monitorLogFilePath = monitorLogFilePathTextField.getText().trim();
                    if (monitorLogFilePath.length() == 0) {
                        showMessage("必须选择监控日志路径！", "警告", JOptionPane.WARNING_MESSAGE);
                        return;
                    }
                    notify = new JNotifyUtils(logTextArea, monitorLogFilePath);
                } else {
                    notify = new JNotifyUtils(logTextArea);
                }
                try {
                    watchId = notify.startMonitor(path, paramsMap);
                    monitorButton.setEnabled(false);
                    stopMonitorButton.setEnabled(true);
                } catch (JNotifyException e) {
                    showExceptionMessage(e);
                } catch (IOException e) {
                    showExceptionMessage(e);
                }
            }

            @Override
            public void mousePressed(MouseEvent e) {
                logTextArea.setText("");
            }
        });
        buttonFlowPanel.add(monitorButton);
        // 监控路径选择控件，仅可选择文件夹
        monitorPathChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
        // 停止监控按钮
        stopMonitorButton = createJButton("停止监控", "", GuiUtils.font14b_cn);
        stopMonitorButton.setEnabled(false);
        stopMonitorButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                try {
                    notify.stopMonitor(watchId);
                    monitorButton.setEnabled(true);
                    stopMonitorButton.setEnabled(false);
                } catch (JNotifyException e) {
                    showExceptionMessage(e);
                }
            }
        });
        buttonFlowPanel.add(stopMonitorButton);
        fileChooPanel.add(buttonFlowPanel, BorderLayout.EAST);
        inputPanel.add(fileChooPanel);

        // 监控日志路径填写
        JPanel logChooPanel = new JPanel(new BorderLayout());
        JPanel logBorderPanel = new JPanel(new BorderLayout());
        logBorderPanel.add(new JPanel(), BorderLayout.NORTH);
        JPanel logFlowPanel = new JPanel(new FlowLayout());
        addJLabel(logFlowPanel, "", GuiUtils.font14_cn);
        // 是否记录监控日志，默认否
        addJCheckBox(logFlowPanel, "保存监控日志", false, GuiUtils.font14_cn, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                if (((JCheckBox) event.getSource()).isSelected()) {
                    saveLog = true;
                    monitorLogFilePathTextField.setEnabled(true);
                } else {
                    saveLog = false;
                    monitorLogFilePathTextField.setEnabled(false);
                }
            }
        });
        addJLabel(logFlowPanel, " 路径: ", GuiUtils.font14_cn);
        logBorderPanel.add(logFlowPanel, BorderLayout.CENTER);
        logChooPanel.add(logBorderPanel, BorderLayout.WEST);

        JPanel logPathPanel = new JPanel(new BorderLayout());
        logPathPanel.add(new JPanel(), BorderLayout.NORTH);
        monitorLogFilePathTextField.setEnabled(false);
        monitorLogFilePathTextField.setEditable(false);
        monitorLogFilePathTextField.addMouseListener(new SimpleMouseListener() {
            @Override
            public void mousePressed(MouseEvent e) {
                fileChooserBrowse(monitorLogFileChooser, monitorLogFilePathTextField);
            }
        });
        addJTextField(logPathPanel, monitorLogFilePathTextField, GuiUtils.font14_un, BorderLayout.CENTER);
        // 监控路径选择控件，仅可选择文件
        monitorLogFileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
        logPathPanel.add(new JPanel(), BorderLayout.SOUTH);
        logChooPanel.add(logPathPanel, BorderLayout.CENTER);

        JPanel logButtonPanel = new JPanel(new BorderLayout());
        logButtonPanel.add(new JPanel(), BorderLayout.NORTH);
        // 展开/收缩高级(条件)按钮
        addJButton(logButtonPanel, "高级", "", GuiUtils.font12_cn, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                advanceConditionPanel.setVisible(!advanceConditionPanel.isVisible());
                getContextPanel().revalidate();
            }
        });
        logButtonPanel.add(new JPanel(), BorderLayout.SOUTH);
        addJLabel(logButtonPanel, "                          ", GuiUtils.font12_cn, BorderLayout.WEST);
        addJLabel(logButtonPanel, " ", GuiUtils.font12_cn, BorderLayout.EAST);
        logChooPanel.add(logButtonPanel, BorderLayout.EAST);
        inputPanel.add(logChooPanel);

        // 高级(条件)及监控日志输出面板，使用边界布局，North为高级(条件)，Center为监控日志输出
        JPanel advanceAndLogPanel = new JPanel(new BorderLayout());
        // 高级(条件
        advanceConditionPanel = new JPanel(new GridLayout(2, 1));
        advanceConditionPanel.setVisible(false);
        advanceAndLogPanel.add(advanceConditionPanel, BorderLayout.NORTH);

        // 文件名包含(不包含)字符
        JPanel fileNameContainsPanel = new JPanel(new FlowLayout(FlowLayout.LEADING));
        addJLabel(fileNameContainsPanel, " 文件名包含字符: ", GuiUtils.font14_cn);
        fileNameContainsTextField = new JTextField(24);
        addJTextField(fileNameContainsPanel, fileNameContainsTextField, GuiUtils.font14_un);
        addJLabel(fileNameContainsPanel, "  文件名不包含字符: ", GuiUtils.font14_cn);
        fileNameNotContainsTextField = new JTextField(24);
        addJTextField(fileNameContainsPanel, fileNameNotContainsTextField, GuiUtils.font14_un);
        // 是否支持正则
        addJCheckBox(fileNameContainsPanel, "支持正则", false, GuiUtils.font14_cn, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                fileNameSupportRegex = ((JCheckBox) event.getSource()).isSelected();
            }
        });
        advanceConditionPanel.add(fileNameContainsPanel);

        // 文件夹路径包含(不包含)字符
        JPanel folderPathContainsPanel = new JPanel(new FlowLayout(FlowLayout.LEADING));
        addJLabel(folderPathContainsPanel, " 文件夹路径包含: ", GuiUtils.font14_cn);
        folderPathContainsTextField = new JTextField(24);
        addJTextField(folderPathContainsPanel, folderPathContainsTextField, GuiUtils.font14_un);
        addJLabel(folderPathContainsPanel, "  文件夹路径不包含: ", GuiUtils.font14_cn);
        folderPathNotContainsTextField = new JTextField(24);
        addJTextField(folderPathContainsPanel, folderPathNotContainsTextField, GuiUtils.font14_un);
        // 是否支持正则
        addJCheckBox(folderPathContainsPanel, "支持正则", false, GuiUtils.font14_cn, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                folderPathSupportRegex = ((JCheckBox) event.getSource()).isSelected();
            }
        });
        advanceConditionPanel.add(folderPathContainsPanel);

        // 监控日志输出
        JPanel monitorLogPanel = new JPanel(new BorderLayout());
        monitorLogPanel.add(new JScrollPane(logTextArea));
        advanceAndLogPanel.add(monitorLogPanel, BorderLayout.CENTER);
        add(advanceAndLogPanel, BorderLayout.CENTER);
    }

}


<|start_filename|>src/main/java/bs/tool/commongui/plugins/EncryptAndDecrypt.java<|end_filename|>
package bs.tool.commongui.plugins;

import bs.tool.commongui.AbstractGuiJPanel;
import bs.tool.commongui.GuiUtils;
import bs.tool.commongui.utils.SimpleMouseListener;
import bs.util.common.CodecUtil;
import org.apache.commons.codec.DecoderException;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.io.UnsupportedEncodingException;

/**
 * 加密解密.
 */
public class EncryptAndDecrypt extends AbstractGuiJPanel {

    private static final long serialVersionUID = 1L;

    /**
     * 明文文本域.
     */
    private JTextArea encrptyTextArea = createJTextArea(GuiUtils.font14_un);
    /**
     * 密文文本域.
     */
    private JTextArea decrptyTextArea = createJTextArea(GuiUtils.font14_un);

    /**
     * 字符集.
     */
    private String[] charsets = new String[]{GuiUtils.CHARSET_UTF_8, GuiUtils.CHARSET_UTF_16BE,
            GuiUtils.CHARSET_UTF_16LE, GuiUtils.CHARSET_UTF_16, GuiUtils.CHARSET_GBK, GuiUtils.CHARSET_Big5,
            GuiUtils.CHARSET_ISO_8859_1};

    /**
     * 当前字符集.
     */
    private String curCharset = charsets[0];

    /**
     * 加密算法. 空""用于填充一个空位.
     */
    private String[] cryptos = new String[]{GuiUtils.CRYPTO_ASCII, GuiUtils.CRYPTO_HEX, GuiUtils.CRYPTO_BASE64,
            GuiUtils.CRYPTO_BASE32, GuiUtils.CRYPTO_URL, "", "", "", GuiUtils.CRYPTO_MD5, "",
            GuiUtils.CRYPTO_SHA, GuiUtils.CRYPTO_SHA256, GuiUtils.CRYPTO_SHA384, GuiUtils.CRYPTO_SHA512};

    /**
     * 当前加密算法.
     */
    private String curCrypto = cryptos[0];

    public EncryptAndDecrypt() {

        // 边界布局
        setLayout(new BorderLayout());
        // Center，加密解密输入输出域，使用2行1列的Grid布局，使其平均显示
        JPanel textAreaPanel = new JPanel(new GridLayout(2, 1));
        add(textAreaPanel, BorderLayout.CENTER);

        JPanel encrptyPanel = new JPanel(new BorderLayout());
        addJLabel(encrptyPanel, " 明文: ", GuiUtils.font14b_cn, BorderLayout.WEST);
        encrptyPanel.add(new JScrollPane(encrptyTextArea), BorderLayout.CENTER);
        textAreaPanel.add(encrptyPanel);

        JPanel decrptyPanel = new JPanel(new BorderLayout());
        addJLabel(decrptyPanel, " 密文: ", GuiUtils.font14b_cn, BorderLayout.WEST);
        decrptyPanel.add(new JScrollPane(decrptyTextArea), BorderLayout.CENTER);
        textAreaPanel.add(decrptyPanel);

        // East，操作区域，使用BorderLayout布局
        JPanel actionPanel = new JPanel(new BorderLayout());
        add(actionPanel, BorderLayout.EAST);
        // 放置下拉框、单选框等
        JPanel actionGridPanel = new JPanel(new GridLayout(10, 1));
        actionPanel.add(actionGridPanel, BorderLayout.NORTH);

        // 字符集下拉框
        JPanel charsetsPanel = new JPanel(new FlowLayout());
        addJLabel(charsetsPanel, "字符集:", GuiUtils.font14b_cn);
        addJComboBox(charsetsPanel, charsets, GuiUtils.font13, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                curCharset = ((JComboBox) event.getSource()).getSelectedItem().toString();
            }
        });
        actionGridPanel.add(charsetsPanel);

        // 仅作填充
        addJLabel(actionGridPanel, " ", GuiUtils.font13);
        // 算法单选框
        ButtonGroup buttonGroup = new ButtonGroup();
        int cryptosLen = cryptos.length;
        int cryptosLoop = cryptosLen / 2;
        cryptosLoop = cryptosLoop * 2 == cryptosLen ? cryptosLoop : (cryptosLoop + 1);
        for (int i = 0; i < cryptosLoop; i++) {
            // FlowLayout.LEADING，此值指示每一行组件都应该与容器方向的开始边对齐，例如，对于从左到右的方向，则与左边对齐
            JPanel cryptoPanel = new JPanel(new FlowLayout(FlowLayout.LEADING));
            String crypto = cryptos[i * 2];
            if (crypto.trim().length() != 0) {
                JRadioButton firstRadio = new JRadioButton(GuiUtils.getFillUpString(crypto, 6));
                firstRadio.setFont(GuiUtils.font13_cn);
                firstRadio.addItemListener(new ItemListener() {
                    @Override
                    public void itemStateChanged(ItemEvent event) {
                        JRadioButton radio = (JRadioButton) event.getSource();
                        if (radio.isSelected()) {
                            curCrypto = radio.getText().trim();
                        }
                    }
                });
                if (GuiUtils.CRYPTO_MD5.equals(firstRadio.getText().trim())) {
                    firstRadio.setSelected(true);
                }
                buttonGroup.add(firstRadio);
                cryptoPanel.add(firstRadio);
            }
            if (!(cryptosLen % 2 == 1 && i * 2 + 1 == cryptosLen)) {
                crypto = GuiUtils.getFillUpString(cryptos[i * 2 + 1], 6);
                if (crypto.trim().length() != 0) {
                    JRadioButton secondRadio = new JRadioButton(crypto);
                    secondRadio.setFont(GuiUtils.font13_cn);
                    secondRadio.addItemListener(new ItemListener() {
                        @Override
                        public void itemStateChanged(ItemEvent event) {
                            JRadioButton radio = (JRadioButton) event.getSource();
                            if (radio.isSelected()) {
                                curCrypto = radio.getText().trim();
                            }
                        }
                    });
                    if (GuiUtils.CRYPTO_MD5.equals(secondRadio.getText().trim())) {
                        secondRadio.setSelected(true);
                    }
                    buttonGroup.add(secondRadio);
                    cryptoPanel.add(secondRadio);
                }
            }
            actionGridPanel.add(cryptoPanel);
        }

        // 仅作填充
        actionPanel.add(new Panel(), BorderLayout.CENTER);

        // 放置加密解密按钮
        JPanel buttonPanel = new JPanel(new GridLayout(3, 1));
        actionPanel.add(buttonPanel, BorderLayout.SOUTH);
        // 加密按钮
        addJButton(buttonPanel, "加密", "", GuiUtils.font14b_cn, new SimpleMouseListener() {
            @Override
            public void mouseReleased(MouseEvent event) {
                String input = encrptyTextArea.getText();
                try {
                    if (GuiUtils.CRYPTO_ASCII.equals(curCrypto)) {
                        decrptyTextArea.setText(CodecUtil.encodeAscii(input, curCharset));
                    } else if (GuiUtils.CRYPTO_HEX.equals(curCrypto)) {
                        decrptyTextArea.setText(CodecUtil.encodeHex(input, curCharset));
                    } else if (GuiUtils.CRYPTO_BASE64.equals(curCrypto)) {
                        decrptyTextArea.setText(CodecUtil.encodeBase64(input, curCharset));
                    } else if (GuiUtils.CRYPTO_BASE32.equals(curCrypto)) {
                        decrptyTextArea.setText(CodecUtil.encodeBase32(input, curCharset));
                    } else if (GuiUtils.CRYPTO_URL.equals(curCrypto)) {
                        decrptyTextArea.setText(CodecUtil.encodeURL(input, curCharset));
                    } else if (GuiUtils.CRYPTO_MD5.equals(curCrypto)) {
                        String md5Val = CodecUtil.encryptMd5(input, curCharset);
                        decrptyTextArea.setText("16Bit：" + md5Val.substring(8, 24) + "\n32Bit：" + md5Val);
                    } else if (GuiUtils.CRYPTO_SHA.equals(curCrypto)) {
                        decrptyTextArea.setText(CodecUtil.encryptSha(input, curCharset));
                    } else if (GuiUtils.CRYPTO_SHA256.equals(curCrypto)) {
                        decrptyTextArea.setText(CodecUtil.encryptSha256(input, curCharset));
                    } else if (GuiUtils.CRYPTO_SHA384.equals(curCrypto)) {
                        decrptyTextArea.setText(CodecUtil.encryptSha384(input, curCharset));
                    } else if (GuiUtils.CRYPTO_SHA512.equals(curCrypto)) {
                        decrptyTextArea.setText(CodecUtil.encryptSha512(input, curCharset));
                    }
                } catch (UnsupportedEncodingException e) {
                    showExceptionMessage(e);
                }
            }

            @Override
            public void mousePressed(MouseEvent e) {
                decrptyTextArea.setText("");
            }
        });
        // 解密按钮
        addJButton(buttonPanel, "解密", "", GuiUtils.font14b_cn, new SimpleMouseListener() {
            @Override
            public void mouseReleased(MouseEvent event) {
                String input = decrptyTextArea.getText();
                try {
                    if (GuiUtils.CRYPTO_ASCII.equals(curCrypto)) {
                        encrptyTextArea.setText(CodecUtil.decodeAscii(input, curCharset));
                    } else if (GuiUtils.CRYPTO_HEX.equals(curCrypto)) {
                        encrptyTextArea.setText(CodecUtil.decodeHex(input, curCharset));
                    } else if (GuiUtils.CRYPTO_BASE64.equals(curCrypto)) {
                        encrptyTextArea.setText(CodecUtil.decodeBase64(input, curCharset));
                    } else if (GuiUtils.CRYPTO_BASE32.equals(curCrypto)) {
                        encrptyTextArea.setText(CodecUtil.decodeBase32(input, curCharset));
                    } else if (GuiUtils.CRYPTO_URL.equals(curCrypto)) {
                        encrptyTextArea.setText(CodecUtil.decodeURL(input, curCharset));
                    } else {
                        encrptyTextArea.setText("不支持此种加密算法的解密！");
                    }
                } catch (UnsupportedEncodingException e) {
                    showExceptionMessage(e);
                } catch (DecoderException e) {
                    showExceptionMessage(e);
                }
            }

            @Override
            public void mousePressed(MouseEvent e) {
                encrptyTextArea.setText("");
            }
        });
    }
}


<|start_filename|>src/main/java/bs/tool/commongui/utils/CollectionUtils.java<|end_filename|>
package bs.tool.commongui.utils;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class CollectionUtils {

    /**
     * 仅支持List及Map.
     *
     * @param t
     * @param <T>
     * @return
     */
    public static <T> T reInitItemValue(T t) {
        if (t instanceof List) {
            List list = (List) t;
            int lSize = list.size();
            List nList = new ArrayList();
            for (int i = 0; i < lSize; i++) {
                Object item = list.get(i);
                if (item instanceof List || item instanceof Map) {
                    reInitItemValue(item);
                    nList.add(item);
                } else {
                    nList.add(reInitValue(item));
                }
            }
            return (T) nList;
        } else if (t instanceof Map) {
            Map map = (Map) t;
            for (Object m : map.keySet()) {
                Object item = map.get(m);
                if (item instanceof List || item instanceof Map) {
                    reInitItemValue(item);
                    map.put(m, item);
                } else {
                    map.put(m, reInitValue(item));
                }
            }
            return t;
        }
        return t;
    }

    public static <T> Object reInitValue(T t) {
        Object v = null;
        if (t instanceof Integer) {
            v = 0;
        } else if (t instanceof Float || t instanceof Double) {
            v = 0.0;
        } else if (t instanceof String) {
            v = "";
        }
        return v;
    }

}


<|start_filename|>src/main/java/bs/tool/eclipse/ProjectPropertiesDealInterface.java<|end_filename|>
package bs.tool.eclipse;

import javax.swing.*;
import java.io.IOException;

/**
 * Eclipse Project Properties Deal Interface.
 *
 * @author Baishui2004
 * @version 1.1
 * @date 2013-4-5
 */
public interface ProjectPropertiesDealInterface {

    /**
     * 解析属性文件获得Project相关属性.
     */
    public void deal(String projectPath) throws IOException;

    /**
     * 是否是Eclipse 的Java Project、Dynamic Web Project或者MyEclipse的Web Project.
     */
    public boolean isJavaOrJavaWebEclipseProject(String projectPath);

    /**
     * 是否是Java Web Project.
     */
    public boolean isJavaWebProject();

    /**
     * 设置项目绝对路径.
     */
    public void setProjectPath(String projectPath);

    public void setRunLogTextArea(JTextArea runLogTextArea);

    /**
     * 获取项目绝对路径.
     */
    public String getProjectPath();

    /**
     * Java Compile Source.
     */
    public String getCompileSource();

    /**
     * Java Compile Target.
     */
    public String getCompileTarget();

    /**
     * 获取项目名称.
     */
    public String getProjectName();

    /**
     * 获取项目Java源码目录(可能多个).
     */
    public String[] getJavaSourcesPath();

    /**
     * 获取项目Java源码编译目录.
     */
    public String getOutputPath();

    /**
     * 获取项目Webapp目录.
     */
    public String getWebappPath();

}


<|start_filename|>src/main/java/bs/tool/commongui/plugins/ClassFinder.java<|end_filename|>
package bs.tool.commongui.plugins;

import bs.tool.commongui.AbstractGuiJPanel;
import bs.tool.commongui.GuiUtils;
import bs.tool.commongui.utils.FileUtils;
import bs.tool.commongui.utils.SimpleMouseListener;
import bs.util.io.PropertiesUtils;

import javax.swing.*;
import javax.swing.filechooser.FileFilter;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.*;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

/**
 * Java类查找.
 */
public class ClassFinder extends AbstractGuiJPanel {

    private static final long serialVersionUID = 1L;

    /**
     * 查找类表单.
     */
    private JTextField searchClassTextField = new JTextField();

    /**
     * 查找文件/文件夹路径表单.
     */
    private JTextField searchPathTextField = new JTextField();
    /**
     * 查找文件/文件夹路径选择.
     */
    private JFileChooser searchPathChooser = new JFileChooser();

    /**
     * 查找按钮.
     */
    private JButton searchButton;

    /**
     * 查找结果文本域.
     */
    private JTextArea resultTextArea = createJTextArea(GuiUtils.font14_un);

    /**
     * 查找的直接文件类型.
     */
    private String searchFileTypes = "";
    /**
     * 查找的压缩文件类型.
     */
    private String compressFileTypes = "";

    {
        String confPropsFile = "conf/ClassFinder/conf.properties";
        try {
            Properties confProperties = PropertiesUtils.getProperties(GuiUtils.getActualPath(confPropsFile));
            searchFileTypes = confProperties.getProperty("SearchFileTypes").trim();
            compressFileTypes = confProperties.getProperty("CompressFileTypes").trim();
        } catch (IOException e) {
            logLoadPropertiesException(confPropsFile, e);
        }
    }

    public ClassFinder() {

        // 主面板：边界布局，分North、Center两部分，North用于放置输入及条件控件，Center是放置查找结果输出
        setLayout(new BorderLayout());

        // 输入及条件Panel
        JPanel inputPanel = new JPanel(new GridLayout(2, 1));

        // Grid第一行条件表单
        JPanel firstLineGridPanel = new JPanel(new BorderLayout());
        inputPanel.add(firstLineGridPanel);
        addJLabel(firstLineGridPanel, "  查找类名: ", GuiUtils.font14_cn, BorderLayout.WEST);
        // 查找类表单
        JPanel classInputPanel = new JPanel(new BorderLayout());
        classInputPanel.add(new JPanel(), BorderLayout.NORTH);
        addJTextField(classInputPanel, searchClassTextField, GuiUtils.font14_un, BorderLayout.CENTER);
        classInputPanel.add(new JPanel(), BorderLayout.SOUTH);
        firstLineGridPanel.add(classInputPanel, BorderLayout.CENTER);

        addJLabel(firstLineGridPanel, " 支持正则且忽略大小写 ", GuiUtils.font14_cn, BorderLayout.EAST);

        // Grid第一行条件表单
        JPanel secondLineGridPanel = new JPanel(new BorderLayout());
        inputPanel.add(secondLineGridPanel);
        addJLabel(secondLineGridPanel, "  文件(夹): ", GuiUtils.font14_cn, BorderLayout.WEST);
        // 查找文件/文件夹表单
        JPanel fileChooPanel = new JPanel(new BorderLayout());
        fileChooPanel.add(new JPanel(), BorderLayout.NORTH);
        addJTextField(fileChooPanel, searchPathTextField, GuiUtils.font14_un, BorderLayout.CENTER);
        fileChooPanel.add(new JPanel(), BorderLayout.SOUTH);
        secondLineGridPanel.add(fileChooPanel, BorderLayout.CENTER);

        JPanel secondFlowPanel = new JPanel(new FlowLayout(FlowLayout.LEADING));
        addJButton(secondFlowPanel, "浏览", "", GuiUtils.font12_cn,
                buttonBrowseListener(searchPathChooser, searchPathTextField));
        addJLabel(secondFlowPanel, " ", GuiUtils.font14_cn);
        // 查找按钮
        searchButton = createJButton("查找", "", GuiUtils.font14b_cn);
        searchButton.addMouseListener(new SimpleMouseListener() {
            @Override
            public void mouseReleased(MouseEvent event) {
                String className = searchClassTextField.getText().trim().toLowerCase();
                if (className.length() == 0) {
                    showMessage("请输入类名！", "警告", JOptionPane.WARNING_MESSAGE);
                    return;
                }
                String actualSearchFileTypes = searchFileTypes;
                String searchFileType = getSearchFileType(className, searchFileTypes);
                if (searchFileType != null) {
                    actualSearchFileTypes = searchFileType;
                    className = className.substring(0, className.length() - ("." + searchFileType).length());
                }

                String path = searchPathTextField.getText().trim();
                File file = new File(path);
                if (!file.exists()) {
                    showMessage("查找文件(夹)不存在！", "警告", JOptionPane.WARNING_MESSAGE);
                    return;
                }
                searchButton.setEnabled(false);

                resultTextArea.setText("");
                try {
                    List<File> files = new ArrayList<File>();

                    if (!file.isDirectory()) {
                        files.add(file);
                    } else {
                        // keys @see Class: bs.tool.commongui.utils.SearchFileAndFolderNamePathParams
                        Map<String, Object> paramsMap = new HashMap<String, Object>();
                        paramsMap.put("containsFolder", false);
                        paramsMap.put("filePathContainsText",
                                "(" + className + "\\.(" + actualSearchFileTypes.replace(",", "|") + ")$)" + "|"
                                        + "(\\.(" + compressFileTypes.replace(",", "|") + ")$)");
                        paramsMap.put("filePathSupportRegex", true);
                        files = FileUtils.getAllSubFiles(path, paramsMap);
                    }

                    Pattern cnPattern = Pattern.compile(className, Pattern.CASE_INSENSITIVE);
                    int cnt = 0;
                    for (File fFile : files) {
                        String fileName = fFile.getName();
                        String compressFileType = getCompressFileTypes(fileName);
                        if (compressFileType == null) {
                            cnt++;
                            resultTextArea.append(fFile.getAbsolutePath() + "\n");
                        } else {
                            cnt += findZipEntries(fFile, actualSearchFileTypes, cnPattern);
                        }
                    }
                    resultTextArea.append("\nFind files: " + cnt + ".");
                } catch (Exception e) {
                    showExceptionMessage(e);
                }
                searchButton.setEnabled(true);
            }

            @Override
            public void mousePressed(MouseEvent e) {
                resultTextArea.setText("");
            }
        });
        secondFlowPanel.add(searchButton);
        // 查找路径选择控件，可选择文件/文件夹
        searchPathChooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
        searchPathChooser.setFileFilter(new FileFilter() {
            @Override
            public String getDescription() {
                return compressFileTypes + ",文件夹";
            }

            @Override
            public boolean accept(File file) {
                if (file.isDirectory()) {
                    return true;
                }
                String fileType = FileUtils.getFileType(file.getName());
                if (("," + compressFileTypes + ",").contains("," + fileType + ",")) {
                    return true;
                }
                return false;
            }
        });
        addJLabel(secondFlowPanel, "  ", GuiUtils.font14_cn);
        secondLineGridPanel.add(secondFlowPanel, BorderLayout.EAST);
        inputPanel.add(secondLineGridPanel);

        add(inputPanel, BorderLayout.NORTH);

        // 查找结果输出
        JPanel searchLogPanel = new JPanel(new BorderLayout());
        searchLogPanel.add(new JScrollPane(resultTextArea));
        add(searchLogPanel, BorderLayout.CENTER);
    }

    /**
     * 获取文件类型，如果不在既定的“查找的直接文件类型”中则返回null.
     */
    private String getSearchFileType(String className, String fileTypes) {
        if (className.contains(".") && !className.endsWith(".")) {
            String type = className.substring(className.lastIndexOf(".") + 1);
            if (("." + fileTypes).contains(type)) {
                return type;
            }
        }
        return null;
    }

    /**
     * 获取文件类型，如果不在既定的"查找的压缩文件类型"中则返回null.
     */
    private String getCompressFileTypes(String fileName) {
        if (fileName.contains(".") && !fileName.endsWith(".")) {
            String type = fileName.substring(fileName.lastIndexOf(".") + 1);
            if (("." + compressFileTypes).contains(type)) {
                return type;
            }
        }
        return null;
    }

    /**
     * 遍历zip文件中的文件.
     */
    private int findZipEntries(File file, String fileTypes, Pattern cnPattern) {
        ZipFile zipFile = null;
        int cnt = 0;
        try {
            zipFile = new ZipFile(file);
            @SuppressWarnings("unchecked")
            Enumeration<ZipEntry> entries = (Enumeration<ZipEntry>) zipFile.entries();
            while (entries.hasMoreElements()) {
                ZipEntry zipEntry = (ZipEntry) entries.nextElement();
                String entryName = zipEntry.getName();
                if (getSearchFileType(entryName, fileTypes) != null && cnPattern.matcher(entryName).find()) {
                    cnt++;
                    resultTextArea.append(file.getAbsolutePath() + " -> " + entryName + "\n");
                }
            }
        } catch (IOException e) {
            GuiUtils.log(e);
        } finally {
            try {
                if (zipFile != null) {
                    zipFile.close();
                }
            } catch (IOException e) {
                GuiUtils.log(e);
            }
        }
        return cnt;
    }
}


<|start_filename|>src/main/java/bs/tool/commongui/GuiUtils.java<|end_filename|>
package bs.tool.commongui;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.swing.*;
import java.awt.*;
import java.io.File;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;
import java.util.SortedMap;

public class GuiUtils {

    private static Logger logger = LoggerFactory.getLogger(GuiUtils.class);


    /**************************** 编码 ****************************/

    // 下面两个转码结果一样
    public static String CHARSET_ISO_8859_1 = "ISO-8859-1";
    public static String CHARSET_US_ASCII = "US-ASCII";

    // java转义\ u后面跟的编码, 即Java Unicode转义字符
    public static String CHARSET_UTF_16BE = "UTF-16BE";
    public static String CHARSET_UTF_16LE = "UTF-16LE";

    public static String CHARSET_UTF_8 = "UTF-8";

    // 下面两个转码结果一样
    public static String CHARSET_UTF_16 = "UTF-16";
    public static String CHARSET_Unicode = "Unicode";

    // GB2312 < GBK < GB18030
    public static String CHARSET_GB2312 = "GB2312";
    public static String CHARSET_GBK = "GBK";
    public static String CHARSET_GB18030 = "GB18030";

    public static String CHARSET_Big5 = "Big5";

    /**************************** 算法 ****************************/
    // 可解密/解码算法
    public static String CRYPTO_ASCII = "Ascii";
    public static String CRYPTO_HEX = "Hex";
    public static String CRYPTO_BASE32 = "Base32";
    public static String CRYPTO_BASE64 = "Base64";
    public static String CRYPTO_URL = "URL";
    // 不可解密算法
    public static String CRYPTO_MD5 = "MD5";
    public static String CRYPTO_SHA = "SHA";
    public static String CRYPTO_SHA256 = "SHA256";
    public static String CRYPTO_SHA384 = "SHA384";
    public static String CRYPTO_SHA512 = "SHA512";

    /**************************** 字体 ****************************/

    /**************************** Code类型 ****************************/
    public static String CODE_TYPE_JSON = "JSON";
    public static String CODE_TYPE_PROPERTIES = "Properties";
    public static String CODE_TYPE_YML = "YML";
    public static String CODE_TYPE_XML = "XML";
    public static String CODE_TYPE_JAVA = "Java";
    public static String CODE_TYPE_JS = "JS";
    public static String CODE_TYPE_PYTHON = "Python";
    public static String CODE_TYPE_SQL = "SQL";

    /**************************** Code类型 ****************************/

    /**************************** Split类型 ****************************/
    public static String SPLIT_TYPE_SIZE = "Size(MB)";
    public static String SPLIT_TYPE_LINE = "Line";

    /**************************** Split类型 ****************************/

    /**************************** 密码组合类型 ****************************/
    public static String PASSWORD_COMBINATION_8_CHAR_NUM_SIMPLE = "8位字母数字[简单]";
    public static String PASSWORD_COMBINATION_8_CHAR_NUM_POPULAR = "8位字母数字[复杂]";

    /**************************** 密码组合类型 ****************************/

    /**
     * 所有字体.
     */
    public static Map<String, Font> availableFontsMap = availableFontsMap();

    /**
     * 中文字体集.
     */
    public static String[] fontStyles_cn;
    /**
     * 中文字体.
     */
    public static String fontStyle_cn;
    /**
     * 英文字体集.
     */
    public static String[] fontStyles;
    /**
     * 英文字体.
     */
    public static String fontStyle;
    /**
     * 支持Unicode的字体集.
     */
    public static String[] fontStyles_un;
    /**
     * 支持Unicode的字体.
     */
    public static String fontStyle_un;

    public static Font font12_cn;

    public static Font font13;
    public static Font font13_cn;

    public static Font font14_cn;
    public static Font font14_un;
    public static Font font14b;
    public static Font font14b_cn;

    public static Font font16;

    /**************************** 文件大小单位 ****************************/
    public static String FileSize_PB = "PB";
    public static String FileSize_TB = "TB";
    public static String FileSize_G = "G";
    public static String FileSize_M = "M";
    public static String FileSize_KB = "KB";
    public static String FileSize_Byte = "Byte";

    /**
     * 初始化字体.
     */
    public static void initFont() {
        font12_cn = new Font(fontStyle_cn, Font.PLAIN, 12);

        font13 = new Font(fontStyle, Font.PLAIN, 13);
        font13_cn = new Font(fontStyle_cn, Font.PLAIN, 13);

        font14_cn = new Font(fontStyle_cn, Font.PLAIN, 14);
        font14_un = new Font(fontStyle_un, Font.PLAIN, 14);
        font14b = new Font(fontStyle, Font.BOLD, 14);
        font14b_cn = new Font(fontStyle_cn, Font.BOLD, 14);

        font16 = new Font(fontStyle, Font.PLAIN, 16);
    }

    /**
     * 当前Java虚拟机支持的charset.
     */
    public static SortedMap<String, Charset> availableCharsets() {
        return Charset.availableCharsets();
    }

    /**
     * 支持的字体.
     */
    public static Font[] availableFonts() {
        GraphicsEnvironment environment = GraphicsEnvironment.getLocalGraphicsEnvironment();
        return environment.getAllFonts();
    }

    /**
     * 支持的字体.
     */
    public static Map<String, Font> availableFontsMap() {
        Font[] fonts = availableFonts();
        Map<String, Font> fontsMap = new HashMap<String, Font>();
        for (Font font : fonts) {
            fontsMap.put(font.getFontName(), font);
        }
        return fontsMap;
    }

    /**
     * 按字符集解码.
     */
    public static String encode(String string, String encode, String decode) throws UnsupportedEncodingException {
        return new String(string.getBytes(encode), decode);
    }

    /**
     * 字符串前缀字符填充.
     */
    public static String addFillString(String string, String fill, int interval) {
        StringBuilder sb = new StringBuilder();
        int len = string.length();
        int loop = len / interval;
        for (int i = 0; i < loop; i++) {
            sb.append(fill).append(string.substring(interval * i, interval * (i + 1)));
        }
        if (loop * interval != len) {
            sb.append(fill).append(string.substring(loop * interval, len));
        }
        return sb.toString();
    }

    /**
     * 字符串前填充以满足固定长度.
     */
    public static String fillStringBefore(String string, String fill, int size) {
        StringBuilder sb = new StringBuilder();
        int len = string.length();
        for (int i = 0; i < size - len; i++) {
            sb.append(fill);
        }
        return sb.append(string).toString();
    }

    /**
     * 优先使用前面的字体，如果不存在，则一个一个向后查找..
     */
    public static String getAvailableFont(String[] fontStyles) {
        String fontName = "";
        for (String fontStyle : fontStyles) {
            if (availableFontsMap.containsKey(fontStyle)) {
                fontName = fontStyle;
                break;
            }
        }
        return fontName;
    }

    /**
     * 右填充字符串.
     * <p>
     * <pre>
     * 依据UTF-8编码中文占三个字节, 英文占一个字节, 且宋体下中文占两个半角空格位, 英文占一个半角空格位的特点;
     * 变通填充为字符中有一个中文字符即当两个半角空格位, 一个英文字符即当一个半角空格位.
     * </pre>
     */
    public static String getFillUpString(String string, int size) {
        StringBuilder sb = new StringBuilder(string);
        int len = 0;
        try {
            len = string.length();
            len = len + (string.getBytes("UTF-8").length - len) / 2;
        } catch (UnsupportedEncodingException e) {
            GuiUtils.log(e);
        }
        for (int i = 0; i < size - len; i++) {
            sb.append(" ");
        }
        return sb.toString();
    }

    /**
     * 计算时间, 形式1Days 2Hours 3Minutes 4Seconds.
     */
    public static String getCountTime(long time) {
        time = time / 1000L;
        long minute = 60L;
        long hour = minute * 60L;
        long day = hour * 24L;
        long seconds = time % minute;
        long minutes = (time - seconds) % hour / minute;
        long hours = (time - minutes - seconds) % day / hour;
        long days = (time - hour - minutes - seconds) / day;
        return days + " Days " + hours + " Hours " + minutes + " Minutes " + seconds + " Seconds";
    }

    /**
     * 计算文件(单位)大小，单位为字节Byte.
     */
    public static Double getCountFileSizeUnit(String size, String unit) {
        return getCountFileSizeUnit(size.length() == 0 ? null : Double.parseDouble(size), unit);
    }

    /**
     * 计算文件(单位)大小，单位为字节Byte.
     */
    public static Double getCountFileSizeUnit(Double size, String unit) {
        if (size == null) {
            return null;
        }
        Double bSize = null;
        int cas = 1024;
        if (unit.equals(FileSize_Byte)) {
            bSize = size;
        } else if (unit.equals(FileSize_KB)) {
            bSize = size * cas;
        } else if (unit.equals(FileSize_M)) {
            bSize = size * cas * cas;
        } else if (unit.equals(FileSize_G)) {
            bSize = size * cas * cas * cas;
        } else if (unit.equals(FileSize_TB)) {
            bSize = size * cas * cas * cas * cas;
        } else if (unit.equals(FileSize_PB)) {
            bSize = size * cas * cas * cas * cas * cas;
        }
        return bSize;
    }

    /**
     * 获取目录img/icon目录下的图标.
     */
    public static Icon getIcon(String path, Toolkit kit) {
        return new ImageIcon(getImage(path, kit));
    }

    /**
     * 获取目录img/icon目录下的图片.
     */
    public static Image getImage(String path, Toolkit kit) {
        URL imgURL = null;
        try {
            imgURL = new File(getActualPath(path)).toURI().toURL();
        } catch (MalformedURLException e) {
            GuiUtils.log("Load Image Error.", e);
        }
        return kit.getImage(imgURL);
    }

    /**
     * 获取类加载路径下的图标.
     */
    public static Icon getIconFromClassloader(String classLoaderImagePath, Toolkit kit) {
        return new ImageIcon(getImageFromClassloader(classLoaderImagePath, kit));
    }

    /**
     * 获取类加载路径下的图片.
     */
    public static Image getImageFromClassloader(String classLoaderImagePath, Toolkit kit) {
        // 这种方式可以从jar包中获取资源路径
        URL imgURL = ClassLoader.getSystemResource(classLoaderImagePath);
        return kit.getImage(imgURL);
    }

    /**
     * 获取与Jar lib或classes目录(前提是GuiUtils类被编译在其下)同级的文件(夹)或同级文件夹的子文件(夹)的绝对路径.
     */
    public static String getActualPath(String path) {
        return LIB_PARENT_PATH + path;
    }

    /**
     * Jar lib或classes目录(前提是GuiUtils类被编译在其下)的父路径.
     */
    private static String LIB_PARENT_PATH = getLibParentPath();

    /**
     * 获取Jar lib或classes目录(前提是GuiUtils类被编译在其下)的父路径.
     */
    private static String getLibParentPath() {
        String classResource = GuiUtils.class.getName().replace(".", "/") + ".class";
        String path = "";
        try {
            path = GuiUtils.class.getClassLoader().getResource(classResource).getPath();
            // "+"号decode后为空格" "，"%2b"号decode后为"+"号
            path = path.replace("+", "%2b");
            path = URLDecoder.decode(path, "UTF-8");
            path = path.substring(0, path.length() - classResource.length());
            if (path.contains(".jar!")) {
                path = path.substring(5, path.indexOf(".jar!") + 4);
                path = path.substring(0, path.lastIndexOf("/"));
                path = path.substring(0, path.length() - "lib".length());
            } else {
                path = path.substring(0, path.length() - "classes".length() - 1);
            }
        } catch (UnsupportedEncodingException e) {
            GuiUtils.log(e);
        }
        return path;
    }

    /**
     * 消息输出到控制台.
     *
     * @param msg 消息
     */
    public static void log(String msg) {
        logger.info(msg);
        GuiMain.msgTextArea.append(msg + "\n");
    }

    /**
     * 异常输出到控制台.
     *
     * @param e 异常
     */
    public static void log(Exception e) {
        e.printStackTrace();
        logger.error("", e);
        StackTraceElement[] ste = e.getStackTrace();
        StringBuilder esb = new StringBuilder();
        esb.append("Exception: ").append(e.getClass().getName()).append(": ").append(e.getMessage()).append("\n");
        for (int i = 0; i < 5 && i < ste.length; i++) {
            esb.append("                    ").append(ste[i].toString()).append("\n");
        }
        GuiMain.msgTextArea.append(esb.toString());
    }

    /**
     * 消息、异常输出到控制台.
     *
     * @param msg 消息
     * @param e   异常
     */
    public static void log(String msg, Throwable e) {
        e.printStackTrace();
        logger.error(msg, e);
        log(msg);
        log(e.getMessage());
    }

    /**
     * parseFalse, 有且仅当对象toString值equalsIgnoreCase("false")时返回false, 其他时候返回true.
     */
    public static boolean parseFalse(Object obj) {
        return "false".equalsIgnoreCase(toString(obj)) ? false : true;
    }

    /**
     * trim.
     */
    public static String trim(Object obj) {
        return toString(obj).trim();
    }

    /**
     * toString.
     */
    public static String toString(Object obj) {
        return obj == null ? "" : obj.toString();
    }

    /**
     * is mac.
     */
    public static boolean IS_MAC = System.getProperty("os.name").contains("Mac");

    public static void showAboutMessage(Toolkit kit, JPanel contextPanel, Map<String, String> propsMap) {
        JOptionPane.showMessageDialog(contextPanel, "Version: " + propsMap.get("Version")
                        + "\nAuthor: <EMAIL>\nDevelop Date: " + propsMap.get("Develop_Date"), "About",
                JOptionPane.INFORMATION_MESSAGE, GuiUtils.getIcon("img/icon/cgt_About.png", kit));
    }

}


<|start_filename|>src/main/java/bs/tool/commongui/plugins/more/EscapeCharacterTool.java<|end_filename|>
package bs.tool.commongui.plugins.more;

import bs.tool.commongui.AbstractGuiJPanel;
import bs.tool.commongui.GuiUtils;
import bs.tool.commongui.utils.EscapeUtils;
import bs.tool.commongui.utils.LanguageUtils;
import bs.tool.commongui.utils.SimpleMouseListener;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;

/**
 * 字符转义工具.
 */
public class EscapeCharacterTool extends AbstractGuiJPanel {

    private static final long serialVersionUID = 1L;

    /**
     * 字符文本域.
     */
    private JTextArea unescapeTextArea = createJTextArea(GuiUtils.font14_un);
    /**
     * 转义文本域.
     */
    private JTextArea escapeTextArea = createJTextArea(GuiUtils.font14_un);
    /**
     * 帮助文本域.
     */
    private JTextArea helpTextArea = createJTextArea(GuiUtils.font14_un);

    /**
     * 字符类型.
     */
    private String[] characterTypes = new String[]{LanguageUtils.CONST_HTML, LanguageUtils.CONST_XML,
            LanguageUtils.CONST_JAVA, LanguageUtils.CONST_JAVASCRIPT, LanguageUtils.CONST_CSV};
    /**
     * 字符类型.
     */
    private String curCharacterType = characterTypes[0];

    /**
     * HTML转义说明.
     */
    private static final String ESCAPE_HELP_HTML = "HTML            See: http://www.w3.org/TR/html4/sgml/entities.html"
            + "\n"
            + "\n <          >            &              \"          no-break space       em space      en space          ®             ©             ™"
            + "\n&lt;      &gt;      &amp;      &quot;            &nbsp;                &emsp;          &ensp;        &reg;      &copy;     &trade;";
    /**
     * XML转义说明.
     */
    private static final String ESCAPE_HELP_XML = "XML              See: http://www.xmlnews.org/docs/xml-basics.html"
            + "\n" + "\n <          >            &               \"               '"
            + "\n&lt;      &gt;      &amp;      &quot;      &apos;";
    /**
     * JAVA转义说明.
     */
    private static final String ESCAPE_HELP_JAVA = "JAVA             See: http://docs.oracle.com/javase/tutorial/java/data/characters.html"
            + "\n"
            + "\n回车符     换行符     制表符     单引号     双引号     反斜杠     退格符     换页符"
            + "\n    \\r             \\n             \\t              \\'             \\\"              \\\\             \\b             \\f";
    /**
     * JavaScript转义说明.
     */
    private static final String ESCAPE_HELP_JAVA_SCRIPT = "JavaScript     See: http://www.w3schools.com/js/js_strings.asp"
            + "\n"
            + "\n回车符     换行符     制表符     单引号     双引号     反斜杠     退格符     换页符"
            + "\n    \\r             \\n             \\t              \\'             \\\"              \\\\             \\b             \\f";
    /**
     * CSV转义说明.
     */
    private static final String ESCAPE_HELP_CSV = "";

    public EscapeCharacterTool() {
        // 边界布局
        setLayout(new BorderLayout());
        // Center，字符及转义结果输入输出域，使用3行1列的Grid布局，使其平均显示
        JPanel textAreaPanel = new JPanel(new GridLayout(3, 1));
        add(textAreaPanel, BorderLayout.CENTER);

        JPanel scriptPanel = new JPanel(new BorderLayout());
        addJLabel(scriptPanel, " 原 字 符: ", GuiUtils.font14b_cn, BorderLayout.WEST);
        scriptPanel.add(new JScrollPane(unescapeTextArea), BorderLayout.CENTER);
        textAreaPanel.add(scriptPanel);

        JPanel resultPanel = new JPanel(new BorderLayout());
        addJLabel(resultPanel, " 转义字符: ", GuiUtils.font14b_cn, BorderLayout.WEST);
        resultPanel.add(new JScrollPane(escapeTextArea), BorderLayout.CENTER);
        textAreaPanel.add(resultPanel);

        JPanel helpTextPanel = new JPanel(new BorderLayout());
        addJLabel(helpTextPanel, " 常用转义: ", GuiUtils.font14b_cn, BorderLayout.WEST);
        helpTextPanel.add(new JScrollPane(helpTextArea), BorderLayout.CENTER);
        textAreaPanel.add(helpTextPanel);

        setHelpTextArea();

        // East，操作区域，使用BorderLayout布局
        JPanel actionPanel = new JPanel(new BorderLayout());
        add(actionPanel, BorderLayout.EAST);

        // 帮助面板
        JPanel helpPanel = new JPanel(new BorderLayout());
        JPanel helpButtonPanel = new JPanel(new BorderLayout());
        helpPanel.add(helpButtonPanel, BorderLayout.EAST);
        addJButton(helpButtonPanel, "帮助", "", GuiUtils.font13_cn, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                showTextAreaMessage("常用转义字符" + "\n\n" + ESCAPE_HELP_HTML + "\n\n\n" + ESCAPE_HELP_XML + "\n\n\n"
                                + ESCAPE_HELP_JAVA + "\n\n\n" + ESCAPE_HELP_JAVA_SCRIPT + "\n\n\n" + ESCAPE_HELP_CSV, "帮助",
                        JOptionPane.INFORMATION_MESSAGE, null, null);
            }
        });

        actionPanel.add(helpPanel, BorderLayout.NORTH);
        // 填充
        actionPanel.add(new JPanel(), BorderLayout.CENTER);

        // 放置按钮
        JPanel buttonPanel = new JPanel(new GridLayout(9, 1));
        actionPanel.add(buttonPanel, BorderLayout.SOUTH);
        addJComboBox(buttonPanel, characterTypes, GuiUtils.font13, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                curCharacterType = ((JComboBox) event.getSource()).getSelectedItem().toString();
                setHelpTextArea();
            }
        });
        // 仅做填充
        buttonPanel.add(new JPanel());
        // 转换
        addJButton(buttonPanel, " 转换 ", "", GuiUtils.font14b_cn, new SimpleMouseListener() {
            @Override
            public void mouseReleased(MouseEvent event) {
                String input = unescapeTextArea.getText();
                escapeTextArea.append(EscapeUtils.escape(input, curCharacterType));
            }

            @Override
            public void mousePressed(MouseEvent e) {
                escapeTextArea.setText("");
            }
        });
        // 仅做填充
        buttonPanel.add(new JPanel());
        // 还原
        addJButton(buttonPanel, " 还原 ", "", GuiUtils.font14b_cn, new SimpleMouseListener() {
            @Override
            public void mouseReleased(MouseEvent event) {
                String input = escapeTextArea.getText();
                unescapeTextArea.append(EscapeUtils.unescape(input, curCharacterType));
            }

            @Override
            public void mousePressed(MouseEvent e) {
                unescapeTextArea.setText("");
            }
        });
        // 仅做填充
        buttonPanel.add(new JPanel());
        // 全部清空
        addJButton(buttonPanel, " 全部清空 ", "", GuiUtils.font14_cn, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                unescapeTextArea.setText("");
                escapeTextArea.setText("");
            }
        });
    }

    /**
     * 设置帮助文本域显示文字.
     */
    private void setHelpTextArea() {
        if (curCharacterType.equals(LanguageUtils.CONST_HTML)) {
            helpTextArea.setText(ESCAPE_HELP_HTML);
        } else if (curCharacterType.equals(LanguageUtils.CONST_XML)) {
            helpTextArea.setText(ESCAPE_HELP_XML);
        } else if (curCharacterType.equals(LanguageUtils.CONST_JAVA)) {
            helpTextArea.setText(ESCAPE_HELP_JAVA);
        } else if (curCharacterType.equals(LanguageUtils.CONST_JAVASCRIPT)) {
            helpTextArea.setText(ESCAPE_HELP_JAVA_SCRIPT);
        } else if (curCharacterType.equals(LanguageUtils.CONST_CSV)) {
            helpTextArea.setText(ESCAPE_HELP_CSV);
        }
    }

}


<|start_filename|>src/main/java/bs/util/io/PropertiesUtils.java<|end_filename|>
package bs.util.io;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.LinkedHashMap;

/**
 * 读取属性文件属性.
 *
 * @author Baishui2004
 * @version 1.0
 */
public class PropertiesUtils {

    /**
     * 获取.properties属性文件所有属性.
     *
     * @param path 属性文件路径
     * @return <code>Properties</code> 属性
     * @throws IOException IO Exception
     */
    public static LinkedProperties getProperties(String path) throws IOException {
        LinkedProperties properties = new LinkedProperties();
        InputStream in = null;
        try {
            in = new FileInputStream(new File(path));
            properties = getProperties(in);
        } finally {
            if (in != null) {
                in.close();
            }
        }
        return properties;
    }

    /**
     * 获取所有属性.
     *
     * @param in InputStream
     * @return <code>Properties</code> 属性
     * @throws IOException IO Exception
     */
    public static LinkedProperties getProperties(InputStream in) throws IOException {
        LinkedProperties properties = new LinkedProperties();
        properties.load(in);
        return properties;
    }

    /**
     * 获取.properties属性文件所有属性的Map集合.
     *
     * @param path 属性文件路径
     * @return <code>Map<String, String></code> 属性的Map集合
     * @throws IOException IO Exception
     */
    public static LinkedHashMap<String, String> getPropertiesMap(String path) throws IOException {
        LinkedProperties properties = getProperties(path);
        return getPropertiesMap(properties);
    }

    /**
     * 获取所有属性.
     *
     * @param in InputStream
     * @return <code>Map<String, String></code> 属性的Map集合
     * @throws IOException IO Exception
     */
    public static LinkedHashMap<String, String> getPropertiesMap(InputStream in) throws IOException {
        LinkedProperties properties = getProperties(in);
        return getPropertiesMap(properties);
    }

    /**
     * 获取properties属性的Map集合.
     *
     * @param properties 属性
     * @return <code>Map<String, String></code> 属性的Map集合
     */
    public static LinkedHashMap<String, String> getPropertiesMap(LinkedProperties properties) {
        Enumeration<Object> keys = properties.keys();
        LinkedHashMap<String, String> map = new LinkedHashMap<String, String>();
        while (keys.hasMoreElements()) {
            String key = (String) keys.nextElement();
            map.put(key, properties.getProperty(key));
        }
        return map;
    }

    /**
     * 获取.properties属性单个属性的方法，获取两个或两个以上属性值使用getPropertiesMap(path)方法.
     *
     * @param path     属性文件路径
     * @param property 属性key
     * @return <code>String</code> 属性值
     * @throws IOException IO Exception
     * @see #getPropertiesMap(String)
     */
    public static String getProperty(String path, String property) throws IOException {
        return getPropertiesMap(path).get(property);
    }

}


<|start_filename|>src/main/java/bs/tool/commongui/utils/interest/LoanInterestUtils.java<|end_filename|>
package bs.tool.commongui.utils.interest;

import bs.tool.commongui.utils.NumberUtils;

import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;

import static bs.tool.commongui.utils.interest.LoanInterestBean.*;

public class LoanInterestUtils {


    // [房贷“等额本息”和“等额本金”怎么选?银行员工:牢记这5点_房产资讯_房天下](https://cd.news.fang.com/open/36952602.html)
    // [等额本息计算公式_等额本息提前还款法_ 等额本息贷款计算 - 融360](https://www.rong360.com/baike/1.html)
    // [等额本金计算公式_等额本金还款法 - 融360](https://www.rong360.com/baike/2.html)

    // 好用的在线房贷、提前还款、利率等等计算器：http://www.tiqianhuandai.cc/

    // 贷款计算：[房贷计算器2021 在线贷款、房贷明细计算器 - 房贷精灵](http://www.baiozhuntuixing.com/)
    // 提前还款计算：[提前还款计算器2021 - 提前还贷计算器2021 - 房贷计算器2021](http://www.tiqianhuandai.cc/)
    // 提前还款是否划算：[提前还款是否划算计算器 贷款、房贷什么时提前还款最划算 - 房贷计算器](http://www.baiozhuntuixing.com/huasuan.aspx)


    // 等额本息：每月还款数额一致，一部分利息，一部分本金，本金所占比例逐月递增，利息所占比例逐月减少。
    //         还款总额=还款月数*贷款额*月利率*(1+月利率)^贷款月数/[(1+月利率)^还款月数 - 1]      // https://www.rong360.com/baike/1.html
    // 等额本金：每月还款本金一致，一部分利息，一部分本金，利息随着本金逐渐减少，每月还款金额逐渐减少。
    //         还款总额=(还款月数+1)*贷款额*月利率/2+贷款额      // https://www.rong360.com/baike/2.html

    // 结论：1. 因为每期利息都等于剩余本金*当期利率，所以一定是等额本息每期利息大于等于等额本金，且只有首期相等；
    //      2. 等额本息前期还款利息较多，不利于提前还款，等额本金方式利息比较稳定，任何时候提前还款都有意义；无论哪种方式，前期已还款利息占比总利息都很高；越早提前还款越省利息；
    //      3. 提前还款叠加缩短还款期限更能节省利息，并且节省的非常多；
    //      4. 当存贷款利率相同时，等额本息与等额本金的最终利率、存款收益可以填平一致；否则，等额本金利息更低；

    // 问题：1. 2种方式不同年限分别哪种方式提前还款相对合算？
    //      2. 2种方式还利息分别达到1/3, 1/2, 2/3, 3/4的时间以及剩余本金比例；
    //      3. 为何分期变长，利率反而有所微降？


    public static void main(String[] args) {
        System.out.println(Math.pow(1 + 0.0075, 12) - 1);

        // 分期名义利率真实利率计算
        String declaredMonthRate = "0.01";
        int periods = 3;
        BigDecimal actualRate = InterestUtils.calculatePeriodActualRate(declaredMonthRate, periods);
        System.out.println(periods + "期名义利率" + declaredMonthRate + "的实际利率："
                + NumberUtils.convertPercentageStr(actualRate)
                + "，年化利率：" + NumberUtils.convertPercentageStr(actualRate.multiply(new BigDecimal(12))));
        int p = 1;
        while (p <= 360) {
            System.out.println(p + "期真实年化利率：" + NumberUtils.convertPercentageStr(
                    InterestUtils.calculatePeriodActualRate(declaredMonthRate, p).multiply(new BigDecimal(12))));
            p++;
        }

        // 假设10000的本金
        int principal = 10000;
        double interest = principal * Double.parseDouble(declaredMonthRate) * periods;
        System.out.println("principal=" + principal + ", avgPrincipal=" + NumberUtils.convertPrecision(principal * 1.0 / periods)
                + ", interest=" + interest + ", periodAmount=" + NumberUtils.convertPrecision((principal + interest) / periods));


        int totalAmount = 250 * 10000;
        String rate = "5.05";
        int year = 30;

        System.out.println("------ 基本情况 ------");
        LoanInterestBean bean = new LoanInterestBean(totalAmount, rate, year, LendType.Interest);
        LoanInterestBean iRsBean = calculateByLendType(bean);
        System.out.println("等额本息 " + iRsBean);
        bean.setLendType(LendType.Principal);
        LoanInterestBean pRsBean = calculateByLendType(bean);
        System.out.println("等额本金 " + pRsBean);


        System.out.println("\n------ 利率对比 ------");
        String cRate = "5.10";
        LoanInterestBean rBean = new LoanInterestBean(totalAmount, cRate, year, LendType.Interest);
        LoanInterestBean iRsBean2 = calculateByLendType(rBean);
        rBean.setLendType(LendType.Principal);
        LoanInterestBean pRsBean2 = calculateByLendType(rBean);
        System.out.println("利率从" + rate + "变到" + cRate
                + "\n    等额本息月还款额变化：" + decimalFormat(iRsBean.getMonthRepayments().get(0).getAmount()) + " -> " + decimalFormat(iRsBean2.getMonthRepayments().get(0).getAmount())
                + "，总利息变化：" + decimalFormat(iRsBean.getInterestTotalAmount()) + " -> " + decimalFormat(iRsBean2.getInterestTotalAmount())
                + "，少还利息：" + decimalFormat(iRsBean.getInterestTotalAmount().subtract(iRsBean2.getInterestTotalAmount()))
                + "\n    等额本金月还款额变化：" + decimalFormat(pRsBean.getMonthRepayments().get(0).getAmount()) + " -> " + decimalFormat(pRsBean2.getMonthRepayments().get(0).getAmount())
                + "，总利息变化：" + decimalFormat(pRsBean.getInterestTotalAmount()) + " -> " + decimalFormat(pRsBean2.getInterestTotalAmount())
                + "，少还利息：" + decimalFormat(pRsBean.getInterestTotalAmount().subtract(pRsBean2.getInterestTotalAmount())));


        System.out.println("\n------ 两者对比 ------");
        // 存款复利收益
        String compoundRate = "4.00";

        // 等额本息相比等额本金
        ComparePrincipalInterest compare = comparePrincipalInterest(new ComparePrincipalInterest(pRsBean, iRsBean, compoundRate));
        int firstThan = compare.getFirstLessThanMonth();
        System.out.println("等额本息比等额本金多还利息：" + LoanInterestUtils.decimalFormat(compare.getSavedInterest())
                + "，月还款额前者首次大于后者的期数：" + firstThan
                + "，截止该期后者比前者累计多还：" + LoanInterestUtils.decimalFormat(compare.getMoreLentBefore())
                + "，自该期后者比前者累计少还：" + LoanInterestUtils.decimalFormat(compare.getLessLentBefore()));

        System.out.println("以年化" + compoundRate + "%计算，等额本息比等额本金存款复利少：" + LoanInterestUtils.decimalFormat(compare.getPrincipalFirstLessThanMonthCompoundInterest()
                .subtract(compare.getFirstLessThanMonthCompoundTwiceInterest()))
                + "，月还款额前者首次大于后者时的前者累积少还部分钱的复利总额：" + LoanInterestUtils.decimalFormat(compare.getFirstLessThanMonthCompoundInterest())
                + "，随后还款日期里该部分钱的复利总额：" + LoanInterestUtils.decimalFormat(compare.getFirstLessThanMonthCompoundTwiceInterest())
                + "，自该期后者比前者累计少还部分钱的复利总额：" + LoanInterestUtils.decimalFormat(compare.getPrincipalFirstLessThanMonthCompoundInterest()));


        System.out.println("\n------ 提前还款 ------");
        // 提前5年、还款50W、还款后缩短还款期限5年
        int tqYear = 5;
        int tqAmount = 50 * 10000;
        int yearReduce = 5;

        // 等额本息提前还款
        LendAheadOfSchedule ahead = new LendAheadOfSchedule(iRsBean, tqYear, tqAmount, LendType.Interest, rate, yearReduce);
        ahead = calculateLendAhead(ahead);
        System.out.println("等额本息" + tqYear + "年已还本金：" + LoanInterestUtils.decimalFormat(ahead.getLentPrincipal())
                + "，已还利息：" + LoanInterestUtils.decimalFormat(ahead.getLentInterest())
                + "，提前" + tqYear + "年还款" + tqAmount + "并且缩短还款" + yearReduce + "年节省利息：" + LoanInterestUtils.decimalFormat(ahead.getSavedInterest())
                + "，实际总利息：" + LoanInterestUtils.decimalFormat(ahead.getLentInterest().add(ahead.getNewBean().getInterestTotalAmount()))
                + "，提前还款后情况 " + ahead.getNewBean());

        // 等额本金提前还款
        ahead = new LendAheadOfSchedule(pRsBean, tqYear, tqAmount, LendType.Principal, rate, yearReduce);
        ahead = calculateLendAhead(ahead);
        System.out.println("等额本金" + tqYear + "年已还本金：" + LoanInterestUtils.decimalFormat(ahead.getLentPrincipal())
                + "，已还利息：" + LoanInterestUtils.decimalFormat(ahead.getLentInterest())
                + "，提前" + tqYear + "年还款" + tqAmount + "并且缩短还款" + yearReduce + "年节省利息：" + LoanInterestUtils.decimalFormat(ahead.getSavedInterest())
                + "，实际总利息：" + LoanInterestUtils.decimalFormat(ahead.getLentInterest().add(ahead.getNewBean().getInterestTotalAmount()))
                + "，提前还款后情况 " + ahead.getNewBean());
    }

    /**
     * 根据计息方式计算.
     *
     * @param bean
     * @return
     */
    public static LoanInterestBean calculateByLendType(LoanInterestBean bean) {
        if (bean.getLendType() == LendType.Interest) {
            return equalMonthlyAmount(bean);
        } else if (bean.getLendType() == LendType.Principal) {
            return equalMonthlyPrincipal(bean);
        }
        return null;
    }

    /**
     * 提前还款计算.
     *
     * @param ahead
     * @return
     */
    public static LendAheadOfSchedule calculateLendAhead(LendAheadOfSchedule ahead) {
        ahead = new LendAheadOfSchedule(ahead);
        // 原还款情况
        LoanInterestBean bean = ahead.getOriginBean();
        // 提前期数的还款情况
        MonthlyLendAmount lentAmount = bean.getMonthRepayments().get(ahead.getYear() * 12 - 1);
        // 已还本金
        BigDecimal lentPrincipal = lentAmount.getPrincipalTotal();
        // 已还利息
        BigDecimal lentInterest = lentAmount.getInterestTotal();
        ahead.setLentPrincipal(lentPrincipal);
        ahead.setLentInterest(lentInterest);

        // 提前还款后还款情况
        LoanInterestBean newBean = calculateByLendType(new LoanInterestBean(bean.getLoanTotalAmount().subtract(lentPrincipal).toBigInteger().intValue() - ahead.getAmount(),
                ahead.getRate(), bean.getLendYear() - ahead.getYear() - ahead.getYearReduce(), ahead.getLendType()));
        ahead.setNewBean(newBean);
        // 节省利息
        ahead.setSavedInterest(bean.getInterestTotalAmount().subtract(lentInterest).subtract(newBean.getInterestTotalAmount()));
        return ahead;
    }

    /**
     * 等额本金与等额本息对比.
     *
     * @param compare
     * @return
     */
    public static ComparePrincipalInterest comparePrincipalInterest(ComparePrincipalInterest compare) {
        compare = new ComparePrincipalInterest(compare);
        LoanInterestBean principalBean = compare.getPrincipalBean();
        LoanInterestBean interestBean = compare.getInterestBean();
        compare.setSavedInterest(interestBean.getInterestTotalAmount().subtract(principalBean.getInterestTotalAmount()));

        // 等额本金每月还款额度首次大于等额本息的每月还款额度的期数
        List<MonthlyLendAmount> iMonthRepayments = interestBean.getMonthRepayments();
        BigDecimal monthRepayment = iMonthRepayments.get(0).getAmount();
        List<MonthlyLendAmount> pMonthRepayments = principalBean.getMonthRepayments();
        int ml = principalBean.getLendMonth();
        // 首次还款数额接近的期数
        int firstThan = -1;
        for (int mi = 0; mi < ml; mi++) {
            BigDecimal pAmount = pMonthRepayments.get(mi).getAmount();
            if (pAmount.compareTo(monthRepayment) < 0) {
                firstThan = mi + 1;
                break;
            }
        }
        compare.setFirstLessThanMonth(firstThan);
        compare.setMoreLentBefore((pMonthRepayments.get(firstThan - 1).getInterestTotal().add(pMonthRepayments.get(firstThan - 1).getPrincipalTotal()))
                .subtract(iMonthRepayments.get(firstThan - 1).getInterestTotal().add(iMonthRepayments.get(firstThan - 1).getPrincipalTotal())));
        compare.setLessLentBefore(compare.getSavedInterest().add(compare.getMoreLentBefore()));

        // 计算存款复利收益
        BigDecimal newMonthRate = new BigDecimal(compare.getCompoundRate()).divide(new BigDecimal(1200), InterestUtils.SCALE, RoundingMode.HALF_UP);
        BigDecimal firstCompound = InterestUtils.compoundInterest(new BigDecimal(0), pMonthRepayments.get(0).getAmount().subtract(monthRepayment),
                principalBean.getMonthDecrease(), newMonthRate, firstThan);
        compare.setFirstLessThanMonthCompoundInterest(firstCompound);

        BigDecimal firstTwiceCompound = InterestUtils.fixedPrincipalCompoundInterest(firstCompound, newMonthRate, ml - firstThan);
        compare.setFirstLessThanMonthCompoundTwiceInterest(firstTwiceCompound);

        BigDecimal secondCompound = InterestUtils.compoundInterest(new BigDecimal(0), new BigDecimal(0),
                new BigDecimal(0).subtract(principalBean.getMonthDecrease()), newMonthRate, ml - firstThan);
        compare.setPrincipalFirstLessThanMonthCompoundInterest(secondCompound);

        return compare;
    }

    /**
     * 等额本金方式计算每期还款情况.
     *
     * @param bean
     * @return
     */
    private static List<MonthlyLendAmount> calculatePrincipalMonthLend(LoanInterestBean bean) {
        List<MonthlyLendAmount> monthlyLendAmounts = new ArrayList<MonthlyLendAmount>();

        // 已还本金
        BigDecimal lentPrincipal = new BigDecimal(0);
        // 已还利息
        BigDecimal lentInterest = new BigDecimal(0);
        int months = bean.getLendMonth();
        // 每月还款本金
        BigDecimal monthlyPrincipal = bean.getLoanTotalAmount().divide(new BigDecimal(months), InterestUtils.SCALE, RoundingMode.HALF_UP);

        BigDecimal loanTotalAmount = bean.getLoanTotalAmount();
        for (int m = 0; m < months; m++) {
            // 当月利息
            BigDecimal curMonthInterest = (loanTotalAmount.subtract(lentPrincipal)).multiply(bean.getLendMonthRate());
            lentPrincipal = lentPrincipal.add(monthlyPrincipal);
            lentInterest = lentInterest.add(curMonthInterest);

            monthlyLendAmounts.add(new MonthlyLendAmount(m + 1, monthlyPrincipal, curMonthInterest, lentPrincipal, lentInterest, bean));
        }
        return monthlyLendAmounts;
    }

    /**
     * 等额本息方式计算每期还款情况.
     *
     * @param bean
     * @return
     */
    private static List<MonthlyLendAmount> calculateInterestMonthLend(LoanInterestBean bean) {
        List<MonthlyLendAmount> monthlyLendAmounts = new ArrayList<MonthlyLendAmount>();

        // 已还本金
        BigDecimal lentPrincipal = new BigDecimal(0);
        // 已还利息
        BigDecimal lentInterest = new BigDecimal(0);
        int months = bean.getLendMonth();
        // 每月还款金额
        BigDecimal monthRepayment = bean.getLoanTotalPrincipalAndInterest().divide(new BigDecimal(months), InterestUtils.SCALE, RoundingMode.HALF_UP);
        BigDecimal loanTotalAmount = bean.getLoanTotalAmount();
        for (int m = 0; m < months; m++) {
            // 当月利息
            BigDecimal curMonthInterest = (loanTotalAmount.subtract(lentPrincipal)).multiply(bean.getLendMonthRate());
            // 当月本金
            BigDecimal curMonthPrincipal = monthRepayment.subtract(curMonthInterest);
            lentPrincipal = lentPrincipal.add(curMonthPrincipal);
            lentInterest = lentInterest.add(curMonthInterest);

            monthlyLendAmounts.add(new MonthlyLendAmount(m + 1, curMonthPrincipal, curMonthInterest, lentPrincipal, lentInterest, bean));
        }
        return monthlyLendAmounts;
    }

    /**
     * 等额本金.
     *
     * @param bean
     * @return
     */
    private static LoanInterestBean equalMonthlyPrincipal(LoanInterestBean bean) {
        bean = new LoanInterestBean(bean);
        int totalLendMonth = bean.getLendMonth();
        BigDecimal monthRate = bean.getLendMonthRate();
        BigDecimal interestTotalAmount = new BigDecimal(totalLendMonth + 1).multiply(bean.getLoanTotalAmount())
                .multiply(monthRate).divide(new BigDecimal(2), InterestUtils.SCALE, RoundingMode.HALF_UP);
        bean.setInterestTotalAmount(interestTotalAmount);
        bean.setLoanTotalPrincipalAndInterest(interestTotalAmount.add(bean.getLoanTotalAmount()));
        bean.setPeriodNominallyYearRate(bean.getInterestTotalAmount().divide(bean.getLoanTotalAmount().multiply(new BigDecimal(bean.getLendYear())), InterestUtils.SCALE, RoundingMode.HALF_UP));

        // 每月还款本金
        BigDecimal monthlyPrincipal = bean.getLoanTotalAmount().divide(new BigDecimal(totalLendMonth), InterestUtils.SCALE, RoundingMode.HALF_UP);
        bean.setMonthRepayments(calculatePrincipalMonthLend(bean));
        bean.setMonthDecrease(new BigDecimal(LoanInterestUtils.decimalFormat(monthlyPrincipal.multiply(monthRate))));
        return bean;
    }

    /**
     * 等额本息.
     *
     * @param bean
     * @return
     */
    private static LoanInterestBean equalMonthlyAmount(LoanInterestBean bean) {
        bean = new LoanInterestBean(bean);
        int totalLendMonth = bean.getLendMonth();
        BigDecimal monthRate = bean.getLendMonthRate();
        BigDecimal pow = (new BigDecimal(1).add(monthRate)).pow(totalLendMonth, MathContext.DECIMAL128);
        BigDecimal divisor = pow.subtract(new BigDecimal(1));
        BigDecimal loanTotalPrincipalAndInterest = (new BigDecimal(totalLendMonth).multiply(bean.getLoanTotalAmount())
                .multiply(monthRate.multiply(pow)))
                .divide(divisor, InterestUtils.SCALE, RoundingMode.HALF_UP);
        bean.setLoanTotalPrincipalAndInterest(loanTotalPrincipalAndInterest);
        bean.setInterestTotalAmount(loanTotalPrincipalAndInterest.subtract(bean.getLoanTotalAmount()));
        bean.setPeriodNominallyYearRate(bean.getInterestTotalAmount().divide(bean.getLoanTotalAmount().multiply(new BigDecimal(bean.getLendYear())), InterestUtils.SCALE, RoundingMode.HALF_UP));
        bean.setMonthRepayments(calculateInterestMonthLend(bean));
        return bean;
    }

    public static String decimalFormat(BigDecimal decimal) {
        return decimalFormat(decimal, "0.00");
    }

    /**
     * BigDecimal format.
     *
     * @param decimal
     * @param formatter
     * @return
     */
    public static String decimalFormat(BigDecimal decimal, String formatter) {
        DecimalFormat format = new DecimalFormat(formatter);
        if (decimal != null) {
            return format.format(decimal);
        }
        return null;
    }
}


<|start_filename|>src/main/java/bs/tool/ant/AntScriptAutoBuild.java<|end_filename|>
package bs.tool.ant;

import bs.tool.commongui.GuiUtils;
import bs.tool.eclipse.ProjectPropertiesDeal;
import bs.tool.eclipse.ProjectPropertiesDealInterface;

import javax.swing.*;
import java.io.*;
import java.net.URISyntaxException;
import java.net.URLDecoder;
import java.util.Date;
import java.util.Properties;

/**
 * Ant Script Auto Build.
 *
 * @author Baishui2004
 * @version 1.1
 * @date 2013-4-5
 */
public class AntScriptAutoBuild {

    /**
     * Eclipse的Java Project、Dynamic Web Project或者MyEclipse的Web Project绝对路径地址.
     */
    private String projectPath;

    /**
     * Project properties.
     */
    private ProjectPropertiesDealInterface projectProperties;

    /**
     * AntScriptAutoBuild conf properties.
     */
    private Properties confProperties;

    /**
     * 生成Ant 脚本属性文件路径.
     */
    private String antPropertiesFilePath;

    /**
     * 生成Ant 脚本文件路径.
     */
    private String antScriptFilePath;

    /**
     * 是否备份已存在的同名构建属性脚本及脚本.
     */
    private boolean isBak = true;

    public void setIsBak(boolean isBak) {
        this.isBak = isBak;
    }

    /**
     * 运行日志输出文本域.
     */
    private JTextArea runLogTextArea;

    public void setRunLogTextArea(JTextArea runLogTextArea) {
        this.runLogTextArea = runLogTextArea;
    }

    /**
     * 输出.
     */
    private void print(String log) {
        if (runLogTextArea != null) {
            runLogTextArea.append(log + "\n");
        } else {
            System.out.print(log + "\n");
        }
    }

    /**
     * Main入口.
     * <p>
     * <pre>
     * 只接受传入一个参数, 即Eclipse的Dynamic Web Project或者MyEclipse的Web Project绝对路径地址.
     * </pre>
     */
    public static void main(String[] args) throws IOException, URISyntaxException {
        if (args.length == 2) {
            AntScriptAutoBuild build = new AntScriptAutoBuild();
            ProjectPropertiesDealInterface propertiesDeal = new ProjectPropertiesDeal();
            propertiesDeal.deal(args[0]);
            build.autoBuild(args[0], propertiesDeal, args[1]);
        } else {
            throw new IllegalArgumentException("Parameters error.");
        }
    }

    /**
     * 根据Eclipse/MyEclipse Project属性创建Ant脚本.
     */
    public void autoBuild(String projectPath, ProjectPropertiesDealInterface properties, String taskNames)
            throws IOException, URISyntaxException {
        this.projectPath = projectPath;
        this.projectProperties = properties;
        this.antPropertiesFilePath = this.projectPath + "/build.properties";
        this.antScriptFilePath = this.projectPath + "/build.xml";

        this.confProperties = new Properties();
        InputStream in = null;
        try {
            in = new FileInputStream(new File(getActualPath("conf/AntScriptAutoBuild/conf.properties")));
            this.confProperties.load(in);
        } finally {
            if (in != null) {
                in.close();
            }
        }

        String actualTask = getActualAndSortTaskNames(taskNames);
        String specialTask = getSpecialAndSortTaskNames(taskNames);
        String printTask = actualTask + "," + specialTask;
        if (printTask.startsWith(",")) {
            printTask = printTask.substring(1);
        }
        if (printTask.endsWith(",")) {
            printTask = printTask.substring(0, printTask.length() - 1);
        }
        print("Ant Task: " + printTask.replace(",", ", ") + ".\n");

        appendPropertiesAndScriptFile(actualTask, specialTask);
        autoReBuildPropertiesFile();

        print("\nAuto build Ant Script Done.");
    }

    /**
     * 重新排序Ant Task.
     */
    private String getActualAndSortTaskNames(String taskNames) {
        String baseTask = this.confProperties.getProperty("baseTask").trim();
        String javaTask = this.confProperties.getProperty("javaTask").trim();
        String javaWebTask = this.confProperties.getProperty("javaWebTask").trim();
        String generalTask = this.confProperties.getProperty("generalTask").trim();

        String allTask = baseTask + "," + javaTask + "," + javaWebTask + "," + generalTask + ",";
        String[] tasks = allTask.split(",");
        StringBuilder sortTaskName = new StringBuilder();
        for (String task : tasks) {
            if (("," + taskNames.toLowerCase() + ",").contains("," + task.toLowerCase() + ",")) {
                if (!(!this.projectProperties.isJavaWebProject() && ("," + javaWebTask.toLowerCase() + ",")
                        .contains("," + task.toLowerCase() + ","))) {
                    sortTaskName.append("," + task);
                }
            }
        }

        return sortTaskName.length() != 0 ? sortTaskName.substring(1) : sortTaskName.toString();
    }

    /**
     * 特殊Ant Task.
     */
    private String getSpecialAndSortTaskNames(String taskNames) {
        String specialTask = this.confProperties.getProperty("specialTask").trim();
        String[] tasks = specialTask.split(",");
        StringBuilder sortTaskName = new StringBuilder();
        for (String task : tasks) {
            if (("," + taskNames.toLowerCase() + ",").contains("," + task.toLowerCase() + ",")) {
                sortTaskName.append("," + task);
            }
        }
        return sortTaskName.length() != 0 ? sortTaskName.substring(1) : sortTaskName.toString();
    }

    /**
     * 组装build.properties及build.xml.
     */
    private void appendPropertiesAndScriptFile(String taskNames, String specialTaskNames) throws IOException,
            URISyntaxException {
        // build.properties
        appendFile(this.antPropertiesFilePath, getActualPath("conf/AntScriptAutoBuild/properties/"), taskNames,
                "#Auto build properties, " + new Date() + ".\n", "");

        // build.xml
        String xmlHeader = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>" + "\n" + "<project name=\""
                + this.projectProperties.getProjectName() + "\" default=\"all\" basedir=\".\">" + "\n\n"
                + "	<!-- properties属性配置文件 -->\n" + "	<property file=\"build.properties\" />";
        String xmlEnd = "\n	<!-- 全部任务 -->\n" + "	<target name=\"all\" depends=\"";
        appendFile(this.antScriptFilePath, getActualPath("conf/AntScriptAutoBuild/script/"), taskNames, xmlHeader,
                xmlEnd + taskNames + "\" />" + "\n\n" + "</project>");

        // 特殊任务，此类任务每个任务单独生成一个构建脚本build-taskName.xml
        if (specialTaskNames.length() != 0) {
            String[] specialTasks = specialTaskNames.split(",");
            for (String task : specialTasks) {
                appendFile(this.antScriptFilePath.substring(0, this.antScriptFilePath.length() - 4) + "-" + task
                        + ".xml", getActualPath("conf/AntScriptAutoBuild/script/"), task, xmlHeader, xmlEnd + task
                        + "\" />" + "\n\n" + "</project>");
            }
        }
    }

    /**
     * 根据taskNames拼装文件.
     */
    private void appendFile(String mergerFilePath, String folderPath, String taskNames, String header, String end)
            throws IOException {
        File mergerFile = new File(mergerFilePath);

        if (mergerFile.exists()) {
            if (isBak) {
                String reNameMergerFilePath = mergerFilePath + "." + System.currentTimeMillis() + "bak";
                File reNameMergerFile = new File(reNameMergerFilePath);
                mergerFile.renameTo(reNameMergerFile);
                print("old file \"" + mergerFile.getName() + "\" renamed to " + "\"" + reNameMergerFile.getName()
                        + "\".");
            } else {
                print("File \"" + mergerFile.getName() + "\" exist but be covered.");
            }
        }

        StringBuilder inSb = new StringBuilder(header + "\n");
        String[] tasks = taskNames.split(",");
        for (String task : tasks) {
            inSb.append("\n");
            appendFile(inSb, folderPath + task);
        }
        inSb.append("\n" + end);

        OutputStream fos = new FileOutputStream(mergerFile);
        // 不采用fos.write(bytes)是为了保证输出文件的存储格式为"UTF-8"
        // fos.write(inSb.toString().getBytes());
        // 使用下面的方式能够保证输出文件的存储格式为"UTF-8"
        Writer writer = new OutputStreamWriter(fos, "UTF-8");
        writer.write(inSb.toString());
        writer.flush();
        writer.close();
        fos.close();

        if (!System.getProperty("os.name").contains("Windows")) {
            try {
                Runtime.getRuntime().exec(new String[]{"chmod", "755", mergerFilePath}).waitFor();
            } catch (Exception e) {
                GuiUtils.log(e);
            }
        }
    }

    /**
     * 拼装文件.
     */
    private void appendFile(StringBuilder inSb, String filePath) throws IOException {
        File file = new File(filePath);
        if (!file.exists()) {
            return;
        }
        InputStream in = new FileInputStream(file);
        InputStreamReader reader = new InputStreamReader(in, "UTF-8");
        BufferedReader bfReader = new BufferedReader(reader);
        while (true) {
            String line = bfReader.readLine();
            if (line == null) {
                break;
            }
            inSb.append(line).append("\n");
        }
        in.close();
        reader.close();
        bfReader.close();
    }

    /**
     * 根据Eclipse/MyEclipse Project属性重设build.properties部分属性值.
     */
    private void autoReBuildPropertiesFile() throws IOException {
        File propertiesFile = new File(this.antPropertiesFilePath);
        InputStream in = new FileInputStream(propertiesFile);
        InputStreamReader reader = new InputStreamReader(in, "UTF-8");
        BufferedReader bfReader = new BufferedReader(reader);
        StringBuilder inSb = new StringBuilder();
        while (true) {
            String line = bfReader.readLine();
            if (line == null) {
                break;
            }
            if (line.startsWith("project.name=")) {
                line = "project.name=" + this.projectProperties.getProjectName();
            } else if (line.startsWith("dir.lib=") && this.projectProperties.isJavaWebProject()) {
                line = "dir.lib=" + this.projectProperties.getWebappPath() + "/WEB-INF/lib";
            } else if (line.startsWith("dir.web=") && this.projectProperties.isJavaWebProject()) {
                line = "dir.web=" + this.projectProperties.getWebappPath();
            } else if (line.startsWith("project.compile.source=")) {
                line = "project.compile.source=" + this.projectProperties.getCompileSource();
            } else if (line.startsWith("project.compile.target=")) {
                line = "project.compile.target=" + this.projectProperties.getCompileTarget();
            } else if (line.startsWith("dir.src=")) {
                line = "dir.src=" + this.projectProperties.getJavaSourcesPath()[0];
            } else if (line.startsWith("dir.classes=")) {
                line = "dir.classes=" + this.projectProperties.getOutputPath();
            }
            inSb.append(line).append("\n");
        }
        OutputStream fos = new FileOutputStream(propertiesFile);
        // 不采用fos.write(bytes)是为了保证输出文件的存储格式为"UTF-8"
        // fos.write(inSb.toString().getBytes());
        // 使用下面的方式能够保证输出文件的存储格式为"UTF-8"
        Writer writer = new OutputStreamWriter(fos, "UTF-8");
        writer.write(inSb.toString());
        writer.flush();
        writer.close();

        in.close();
        reader.close();
        bfReader.close();
        fos.close();
    }

    /**
     * 获取与Jar lib目录同级的文件(夹)或同级文件夹的子文件(夹)的绝对路径.
     */
    public static String getActualPath(String path) {
        return LIB_PARENT_PATH + path;
    }

    /**
     * Jar lib目录的父路径.
     */
    private static String LIB_PARENT_PATH = getLibParentPath();

    /**
     * 获取Jar lib目录的父路径.
     */
    private static String getLibParentPath() {
        String classResource = AntScriptAutoBuild.class.getName().replace(".", "/") + ".class";
        String path = "";
        try {
            path = AntScriptAutoBuild.class.getClassLoader().getResource(classResource).getPath();
            // "+"号decode后为空格" "，"%2b"号decode后为"+"号
            path = path.replace("+", "%2b");
            path = URLDecoder.decode(path, "UTF-8");
            path = path.substring(0, path.length() - classResource.length());
            if (path.contains(".jar!")) {
                path = path.substring(5, path.indexOf(".jar!") + 4);
                path = path.substring(0, path.lastIndexOf("/"));
                path = path.substring(0, path.length() - "lib".length());
            } else {
                path = path.substring(0, path.length() - "classes".length() - 1);
            }
        } catch (UnsupportedEncodingException e) {
            GuiUtils.log(e);
        }
        return path;
    }

}


<|start_filename|>src/main/java/bs/tool/commongui/utils/interest/LendType.java<|end_filename|>
package bs.tool.commongui.utils.interest;

/**
 * 还款计息方式.
 */
public enum LendType {
    /**
     * 等额本息.
     */
    Interest("等额本息"),
    /**
     * 等额本金.
     */
    Principal("等额本金");

    LendType(String desc) {
        this.desc = desc;
    }

    private String desc;

    public String getDesc() {
        return desc;
    }
}


<|start_filename|>src/main/java/bs/tool/commongui/utils/RadixUtils.java<|end_filename|>
package bs.tool.commongui.utils;

/**
 * 进制转换类.
 * <p>
 * <pre>
 * 特别说明：此处的字符皆为1个字节8位为1个转换单位，因此：
 *           1，1个字节转16进制为2位16进制，不足前面补零；
 *           2，1个字节转10进制为3位10进制，不足前面补零；
 *           3，1个字节转8进制为3位8进制，不足前面补零；
 *           这个类中的所有进制字符转换方法都是依据以上条件而定，当不满足以上条件时，程序会发生异常或不能得到预期结果！
 * </pre>
 */
public class RadixUtils {

    /**
     * 字符串前填充以满足固定长度.
     */
    public static String fillStringBefore(String string, String fill, int size) {
        StringBuilder sb = new StringBuilder();
        int len = string.length();
        for (int i = 0; i < size - len; i++) {
            sb.append(fill);
        }
        return sb.append(string).toString();
    }

    /**
     * 将16进制字符转为10进制字符.
     */
    public static String convertRadixString16To10(String radix16) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < radix16.length() / 2; i++) {
            sb.append(fillStringBefore(Integer.toString(Integer.valueOf(radix16.substring(i * 2, (i + 1) * 2), 16)),
                    "0", 3));
        }
        return sb.toString();
    }

    /**
     * 将10进制字符转为16进制字符.
     */
    public static String convertRadixString10To16(String radix10) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < radix10.length() / 3; i++) {
            sb.append(Integer.toHexString(Integer.valueOf(radix10.substring(i * 3, (i + 1) * 3))));
        }
        return sb.toString();
    }

    /**
     * 将16进制字符转为8进制字符.
     */
    public static String convertRadixString16To8(String radix16) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < radix16.length() / 2; i++) {
            sb.append(fillStringBefore(
                    Integer.toOctalString(Integer.valueOf(radix16.substring(i * 2, (i + 1) * 2), 16)), "0", 3));
        }
        return sb.toString();
    }

    /**
     * 将8进制字符转为16进制字符.
     */
    public static String convertRadixString8To16(String radix8) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < radix8.length() / 3; i++) {
            sb.append(Integer.toHexString(Integer.valueOf(radix8.substring(i * 3, (i + 1) * 3), 8)));
        }
        return sb.toString();
    }

    /**
     * 将16进制字符转为2进制字符.
     */
    public static String convertRadixString16To2(String radix16) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < radix16.length(); i++) {
            sb.append(fillStringBefore(Integer.toBinaryString(Integer.valueOf(radix16.substring(i, i + 1), 16)), "0", 4));
        }
        return sb.toString();
    }

    /**
     * 将2进制字符转为16进制字符.
     */
    public static String convertRadixString2To16(String radix2) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < radix2.length() / 4; i++) {
            sb.append(Integer.toHexString(Integer.valueOf(radix2.substring(i * 4, (i + 1) * 4), 2)));
        }
        return sb.toString();
    }

}


<|start_filename|>src/main/java/bs/tool/commongui/GuiMain.java<|end_filename|>
package bs.tool.commongui;

import bs.tool.commongui.utils.MacUtils;
import bs.tool.commongui.utils.SimpleMouseListener;
import bs.util.io.PropertiesUtils;

import javax.swing.*;
import javax.swing.text.DefaultEditorKit;
import java.awt.*;
import java.awt.datatransfer.StringSelection;
import java.awt.event.*;
import java.io.File;
import java.util.List;
import java.util.*;

/**
 * GUI主界面.
 */
public class GuiMain extends JFrame {

    private static final long serialVersionUID = 1L;

    /**
     * 软件名称.
     */
    private final String softName = "Common Gui Tools";

    /**
     * GUI配置属性Map.
     */
    private static LinkedHashMap<String, String> cgt_propsMap;

    /**
     * GUI配置属性Map(工具).
     */
    private static LinkedHashMap<String, String> tools_propsMap;

    /**
     * 常用插件.
     */
    private static String commonUsePlugins;

    /**
     * 宽度, 满足宽高比例1024:768.
     */
    private static int gui_width;

    /**
     * 高度.
     */
    private static int gui_height;

    /**
     * 入口程序.
     */
    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    cgt_propsMap = PropertiesUtils.getPropertiesMap(GuiUtils
                            .getActualPath("conf/common_gui_tools.properties"));
                    tools_propsMap = PropertiesUtils.getPropertiesMap(GuiUtils
                            .getActualPath("conf/tools.properties"));
                    commonUsePlugins = cgt_propsMap.get("CommonUseTools");
                    gui_width = Integer.parseInt(cgt_propsMap.get("GUIWidth"));
                    gui_height = gui_width * 768 / 1024;

                    // 设置皮肤外观
                    String guiSkinStr = cgt_propsMap.get("GUISkin").trim();
                    if (guiSkinStr.length() > 0) {
                        String[] guiSkins = guiSkinStr.split(",");
                        for (String skin : guiSkins) {
                            if (setLookAndFeel(skin)) {
                                break;
                            }
                        }
                    }

                    // 设置显示字体
                    // 中文字体
                    GuiUtils.fontStyles_cn = new String(cgt_propsMap.get("fontStyles_cn").getBytes("ISO-8859-1"), "UTF-8")
                            .split(",");
                    GuiUtils.fontStyle_cn = GuiUtils.getAvailableFont(GuiUtils.fontStyles_cn);
                    // 英文字体
                    GuiUtils.fontStyles = (new String(cgt_propsMap.get("fontStyles").getBytes("ISO-8859-1"), "UTF-8") + "," + GuiUtils.fontStyle_cn)
                            .split(",");
                    GuiUtils.fontStyle = GuiUtils.getAvailableFont(GuiUtils.fontStyles);
                    // 支持Unicode的字体
                    GuiUtils.fontStyles_un = (new String(cgt_propsMap.get("fontStyles_un").getBytes("ISO-8859-1"), "UTF-8")
                            + "," + GuiUtils.fontStyle_cn).split(",");
                    GuiUtils.fontStyle_un = GuiUtils.getAvailableFont(GuiUtils.fontStyles_un);
                    // 初始化字体
                    GuiUtils.initFont();

                    GuiMain frame = new GuiMain();
                    frame.setVisible(true);
                } catch (Exception e) {
                    GuiUtils.log(e);
                }
            }

            /**
             * 设置皮肤外观.
             */
            private boolean setLookAndFeel(String lookAndFeel) {
                try {
                    UIManager.setLookAndFeel(lookAndFeel);
                    JFrame.setDefaultLookAndFeelDecorated(true);
                    JDialog.setDefaultLookAndFeelDecorated(true);
                    setMacCommandCopyPaste();
                    return true;
                } catch (Exception e) {
                    GuiUtils.log(e);
                    return false;
                }
            }

            // How to use Command-c/Command-v shortcut in Mac to copy/paste text?
            // https://stackoverflow.com/questions/7252749/how-to-use-command-c-command-v-shortcut-in-mac-to-copy-paste-text
            private void setMacCommandCopyPaste() {
                if (GuiUtils.IS_MAC) {
                    try {
                        InputMap im = (InputMap) UIManager.get("TextField.focusInputMap");
                        im.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, KeyEvent.META_DOWN_MASK), DefaultEditorKit.selectAllAction);
                        im.put(KeyStroke.getKeyStroke(KeyEvent.VK_C, KeyEvent.META_DOWN_MASK), DefaultEditorKit.copyAction);
                        im.put(KeyStroke.getKeyStroke(KeyEvent.VK_V, KeyEvent.META_DOWN_MASK), DefaultEditorKit.pasteAction);
                        im.put(KeyStroke.getKeyStroke(KeyEvent.VK_X, KeyEvent.META_DOWN_MASK), DefaultEditorKit.cutAction);

                        InputMap tim = (InputMap) UIManager.get("TextArea.focusInputMap");
                        tim.put(KeyStroke.getKeyStroke(KeyEvent.VK_A, KeyEvent.META_DOWN_MASK), DefaultEditorKit.selectAllAction);
                        tim.put(KeyStroke.getKeyStroke(KeyEvent.VK_C, KeyEvent.META_DOWN_MASK), DefaultEditorKit.copyAction);
                        tim.put(KeyStroke.getKeyStroke(KeyEvent.VK_V, KeyEvent.META_DOWN_MASK), DefaultEditorKit.pasteAction);
                        tim.put(KeyStroke.getKeyStroke(KeyEvent.VK_X, KeyEvent.META_DOWN_MASK), DefaultEditorKit.cutAction);
                    } catch (Exception e) {
                        GuiUtils.log("", e);
                    }
                }
            }
        });
    }

    private final Toolkit kit = Toolkit.getDefaultToolkit();
    /**
     * Swing面板.
     */
    private final JPanel contextPanel;

    /**
     * 警告、错误消息输出表单.
     */
    public static JTextArea msgTextArea = new JTextArea();

    /**
     * Tab Panel.
     */
    private final JTabbedPane tabbedPane = new JTabbedPane();

    /**
     * 插件属性，key：插件名称，value：{插件名称, 插件类名全称, 插件图标}.
     */
    private final Map<String, List<String>> pluginsProperties = new LinkedHashMap<String, List<String>>();
    /**
     * 插件title面板，key：插件名称，value：title面板.
     */
    private final Map<String, JPanel> titlesPanel = new HashMap<String, JPanel>();
    /**
     * 插件面板，key：插件名称，value：JPanel面板.
     */
    private final Map<String, JPanel> pluginsPanel = new HashMap<String, JPanel>();
    /**
     * 记录插件在当前TabPanel的位置，key：插件名称，value：Index索引位置.
     */
    private final Map<String, Integer> pluginsTabIndex = new HashMap<String, Integer>();
    /**
     * 记录当前TabPanel位置的插件，key：Index索引位置，value：插件名称.
     */
    private final Map<Integer, String> tabPluginsId = new HashMap<Integer, String>();

    /**
     * 设定显示位置及大小.
     */
    private void setLocationAndSize() {
        Toolkit kit = Toolkit.getDefaultToolkit();
        Dimension dimension = kit.getScreenSize();
        int screenWidth = (int) dimension.getWidth();
        int screenHeight = (int) dimension.getHeight();
        // 如果显示屏幕分辨率没有已设定的面板大, 则按显示屏幕分辨率显示
        gui_width = Math.min(screenWidth, gui_width);
        gui_height = Math.min(screenHeight, gui_height);
        // 默认距屏幕左上角(0, 0), 此处设为居中显示屏幕
        setLocation((screenWidth - gui_width) / 2, (screenHeight - gui_height) / 2);
        // JFrame大小
        setSize(gui_width, gui_height);
    }

    public GuiMain() {
        contextPanel = (JPanel) getContentPane();
        // 标题
        setTitle(softName);
        // 默认不设置时是true
        // setResizable(false);
        setLocationAndSize();
        // 流布局管理器(默认居中对齐)
        // setLayout(new FlowLayout(FlowLayout.RIGHT));
        // 边界布局管理器
        setLayout(new BorderLayout());
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        Image icon = GuiUtils.getImage("img/icon/cgt_Icon.png", kit);
        // set mac dock icon
        if (GuiUtils.IS_MAC) {
            MacUtils.showAboutMessage(kit, contextPanel, icon, cgt_propsMap);
        }
        setIconImage(icon);

        JMenuBar menuBar = new JMenuBar();
        // 菜单工具条
        setJMenuBar(menuBar);

        add(tabbedPane, BorderLayout.CENTER);
        // 警告、错误消息输出域
        JPanel msgPanel = new JPanel(new BorderLayout());
        msgPanel.setBorder(BorderFactory.createTitledBorder("Console:"));
        msgTextArea.setFont(GuiUtils.font13);
        msgTextArea.setRows(5);
        msgTextArea.setEditable(false);
        // 自动换行
        msgTextArea.setLineWrap(true);
        msgPanel.add(new JScrollPane(msgTextArea), BorderLayout.CENTER);
        // paste复制按钮、clear清楚按钮
        JPanel msgButtonPanel = new JPanel(new GridLayout(2, 1));
        JButton pasteButton = new JButton("paste");
        pasteButton.setFont(GuiUtils.font13_cn);
        pasteButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                StringSelection selection = new StringSelection(msgTextArea.getText());
                // 获取系统剪切板，复制输出消息
                Toolkit.getDefaultToolkit().getSystemClipboard().setContents(selection, selection);
            }
        });
        msgButtonPanel.add(pasteButton);
        JButton clearButton = new JButton("clear");
        clearButton.setFont(GuiUtils.font13_cn);
        clearButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                msgTextArea.setText("");
            }
        });
        msgButtonPanel.add(clearButton);
        msgPanel.add(msgButtonPanel, BorderLayout.EAST);
        add(msgPanel, BorderLayout.SOUTH);

        JMenu fileMenu = new JMenu("  File  ");
        // File菜单
        menuBar.add(fileMenu);
        JMenuItem exitItem = new JMenuItem("Exit");
        exitItem.setIcon(GuiUtils.getIcon("img/icon/cgt_Exit.png", kit));
        fileMenu.add(exitItem);
        exitItem.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                System.exit(0);
            }
        });

        // 关闭图标
        final Icon closeIcon = GuiUtils.getIcon("img/icon/cgt_Close.png", kit);

        // 关闭Label事件
        final MouseListener closeLabelListener = new SimpleMouseListener() {
            @Override
            public void mouseReleased(MouseEvent e) {
                // 当前点击Label的父Panel
                int closeIndex = tabbedPane.indexOfTabComponent(((JLabel) e.getSource()).getParent());
                tabbedPane.remove(closeIndex);

                // 移除插件在当前TabPanel的位置的记录
                pluginsTabIndex.remove(tabPluginsId.get(closeIndex));
                // 清空原TabPanel位置的插件记录，重新记录
                tabPluginsId.clear();
                // remove插件面板后调整索引位置
                for (String i : pluginsTabIndex.keySet()) {
                    Integer index = pluginsTabIndex.get(i);
                    if (index > closeIndex) {
                        pluginsTabIndex.put(i, index - 1);
                    }
                    tabPluginsId.put(pluginsTabIndex.get(i), i);
                }
            }
        };

        tabbedPane.setFont(GuiUtils.font14b_cn);
        String toolPrefix = "Tool_";
        try {
            List<String> pluginSortNames = new ArrayList<String>();
            // 工具类别
            Map<String, String> toolsKindMap = new HashMap<String, String>();
            // 工具类别菜单
            Map<String, JMenu> toolsMenuMap = new HashMap<String, JMenu>();
            for (String key : tools_propsMap.keySet()) {
                if (key.startsWith(toolPrefix)) {
                    int li = key.lastIndexOf("_");
                    String kind = key.substring(toolPrefix.length(), li);
                    String pluginName = key.substring(li + 1);
                    toolsKindMap.put(pluginName, kind);
                    if (toolsMenuMap.get(kind) == null) {
                        // 工具菜单
                        JMenu toolsMenu = new JMenu("   " + kind + " Tools   ");
                        menuBar.add(toolsMenu);
                        toolsMenuMap.put(kind, toolsMenu);
                    }
                    pluginSortNames.add(pluginName);
                }
            }

            // 插件属性
            for (final String pluginName : pluginSortNames) {
                List<String> pluginProps = new ArrayList<String>();
                pluginProps.add(pluginName);

                String classAndIcon = tools_propsMap.get(toolPrefix + toolsKindMap.get(pluginName) + "_" + pluginName);
                String className = classAndIcon.split("_")[0];
                pluginProps.add(className);
                pluginProps.add(classAndIcon.indexOf("_") > 0 ? classAndIcon.substring(className.length() + 1) : "");

                try {
                    Class.forName(className);
                    pluginsProperties.put(pluginName, pluginProps);
                    // 插件图标
                    String iconPath = pluginProps.get(2).trim();
                    Icon toolIcon = null;
                    if (iconPath.length() > 0) {
                        toolIcon = GuiUtils.getIcon(iconPath, kit);
                    }
                    if (("," + commonUsePlugins + ",").contains("," + pluginName + ",")) {
                        // 初始只加载常用插件
                        JPanel pluginPanel = (JPanel) (Class.forName(className)).newInstance();
                        pluginsPanel.put(pluginName, pluginPanel);

                        // 设置title栏左侧图标，中间插件名称，右侧关闭图标
                        JPanel titlePanel = new JPanel();
                        titlePanel.setOpaque(false);
                        titlePanel.add(new JLabel(toolIcon), BorderLayout.WEST);
                        titlePanel.add(new JLabel(pluginName + "  "), BorderLayout.CENTER);
                        JLabel closeLabel = new JLabel(closeIcon);
                        closeLabel.addMouseListener(closeLabelListener);
                        titlePanel.add(closeLabel, BorderLayout.EAST);

                        titlesPanel.put(pluginName, titlePanel);

                        tabbedPane.addTab(null, pluginPanel);
                        tabbedPane.setTabComponentAt(tabbedPane.getTabCount() - 1, titlePanel);

                        pluginsTabIndex.put(pluginName, tabbedPane.getTabCount() - 1);
                        tabPluginsId.put(tabbedPane.getTabCount() - 1, pluginName);

                        /*
                        // 设置图标，图标在右侧
                        // tabbedPane.addTab(pluginName + "  ", (JPanel) (Class.forName(className)).newInstance());
                        // tabbedPane.setIconAt(tabbedPane.getTabCount() - 1, toolIcon);
                        */
                    }

                    // 设置二级菜单
                    JMenuItem toolItem = new JMenuItem(pluginName);
                    toolItem.setName(pluginName);
                    JMenu menu = toolsMenuMap.get(toolsKindMap.get(pluginName));
                    if (menu.getItemCount() > 0) {
                        menu.addSeparator();
                    }
                    menu.add(toolItem);
                    toolItem.setIcon(toolIcon);
                    toolItem.addActionListener(new ActionListener() {
                        // 菜单事件
                        @Override
                        public void actionPerformed(ActionEvent event) {
                            JMenuItem item = (JMenuItem) event.getSource();
                            String pluginName = item.getName();
                            // 如果面板没有add到tablePanel上，则add
                            if (pluginsTabIndex.get(pluginName) == null) {
                                List<String> pluginProps = pluginsProperties.get(pluginName);
                                // 懒加载未加载的插件
                                JPanel pluginPanel = pluginsPanel.get(pluginName);
                                if (pluginPanel == null) {
                                    try {
                                        pluginPanel = (JPanel) (Class.forName(pluginProps.get(1))).newInstance();
                                        pluginsPanel.put(pluginName, pluginPanel);
                                    } catch (Exception e) {
                                        GuiUtils.log(e);
                                    }
                                }

                                // 设置title栏左侧图标，中间插件名称，右侧关闭图标
                                JPanel titlePanel = titlesPanel.get(pluginName);
                                if (titlePanel == null) {
                                    titlePanel = new JPanel();
                                    titlePanel.setOpaque(false);
                                    String iconPath = pluginProps.get(2).trim();
                                    if (iconPath.length() > 0) {
                                        titlePanel.add(
                                                new JLabel(GuiUtils.getIcon(iconPath, Toolkit.getDefaultToolkit())),
                                                BorderLayout.WEST);
                                    }
                                    titlePanel.add(new JLabel(pluginProps.get(0) + "  "), BorderLayout.CENTER);
                                    JLabel closeLabel = new JLabel(closeIcon);
                                    closeLabel.addMouseListener(closeLabelListener);
                                    titlePanel.add(closeLabel, BorderLayout.EAST);
                                    titlesPanel.put(pluginName, titlePanel);
                                }

                                tabbedPane.addTab(null, pluginPanel);
                                tabbedPane.setTabComponentAt(tabbedPane.getTabCount() - 1, titlePanel);

                                pluginsTabIndex.put(pluginName, tabbedPane.getTabCount() - 1);
                                tabPluginsId.put(tabbedPane.getTabCount() - 1, pluginName);

                                // 选中当前新add的插件面板
                                tabbedPane.setSelectedIndex(tabbedPane.getTabCount() - 1);
                            } else {
                                // 如果面板已经add到tablePanel上，则选中
                                tabbedPane.setSelectedIndex(pluginsTabIndex.get(pluginName));
                            }
                        }
                    });
                } catch (ClassNotFoundException e) {
                    GuiUtils.log("Warn: Ignore plugin \"" + pluginName
                            + "\", because can not find it's Class \"" + className + "\".", e);
                }
            }
        } catch (Exception e) {
            GuiUtils.log(e);
        }

        JMenu helpMenu = new JMenu("   Help   ");
        menuBar.add(helpMenu);
        JMenuItem fontItem = new JMenuItem("Font");
        final Icon fontIcon = GuiUtils.getIcon("img/icon/cgt_Font.png", kit);
        fontItem.setIcon(fontIcon);
        helpMenu.add(fontItem);
        fontItem.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                JOptionPane.showMessageDialog(contextPanel, softName + " 使用的字体\n\n中文字体:  " + GuiUtils.fontStyle_cn
                                + "\n英文字体:  " + GuiUtils.fontStyle + "\nUnicode字体:  " + GuiUtils.fontStyle_un + "\n", "Font",
                        JOptionPane.INFORMATION_MESSAGE, fontIcon);
            }
        });
        helpMenu.addSeparator(); // 分隔符
        JMenuItem aboutItem = new JMenuItem("About");
        aboutItem.setIcon(GuiUtils.getIcon("img/icon/cgt_About.png", kit));
        helpMenu.add(aboutItem);
        aboutItem.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                GuiUtils.showAboutMessage(kit, contextPanel, cgt_propsMap);
            }
        });
        helpMenu.addSeparator(); // 分隔符
        JMenuItem donateItem = new JMenuItem("Donate");
        final Icon donateIcon = GuiUtils.getIcon("img/icon/cgt_Donate.png", kit);
        donateItem.setIcon(donateIcon);
        helpMenu.add(donateItem);
        donateItem.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                try {
                    String donateWeixin = "file://" + new File(GuiUtils.getActualPath("img/donate/weixin.png")).toURI().toURL().getPath();
                    String donateAlipay = "file://" + new File(GuiUtils.getActualPath("img/donate/alipay.jpg")).toURI().toURL().getPath();
                    JOptionPane.showMessageDialog(contextPanel, "<html><body><table><tr><td>微信</td><td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</td><td>支付宝</td></tr><tr><td><img src=\"" + donateWeixin
                                    + "\"/></td><td></td><td><img src=\"" + donateAlipay + "\"/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</td></tr></table></body></html>",
                            "Donate", JOptionPane.INFORMATION_MESSAGE, donateIcon);
                } catch (Exception se) {
                    GuiUtils.log(se);
                }
            }
        });

    }

}


<|start_filename|>src/main/java/bs/tool/commongui/plugins/RegexTester.java<|end_filename|>
package bs.tool.commongui.plugins;

import bs.tool.commongui.AbstractGuiJPanel;
import bs.tool.commongui.GuiUtils;
import bs.tool.commongui.utils.SimpleMouseListener;
import bs.util.io.PropertiesUtils;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 正则表达式验证.
 */
public class RegexTester extends AbstractGuiJPanel {

    private static final long serialVersionUID = 1L;

    /**
     * 正则表达式文本域.
     */
    private JTextArea regexTextArea = createJTextArea(GuiUtils.font14_un);
    /**
     * 验证字符文本域.
     */
    private JTextArea sourceTextArea = createJTextArea(GuiUtils.font14_un);
    /**
     * 匹配结果文本域.
     */
    private JTextArea matchTextArea = createJTextArea(GuiUtils.font14_un);

    /**
     * 帮助.
     */
    private String helpText;

    /**
     * 是否忽略大小写，默认否.
     */
    private boolean ignoreCase = false;
    /**
     * 是否显示详细匹配，默认否.
     */
    private boolean viewDetail = false;
    /**
     * 替换匹配字符文本域.
     */
    private JTextField replaceTextField = new JTextField("");

    /**
     * 常用正则表达式-名称.
     */
    private String[] expNames = null;

    /**
     * 常用正则表达式-表达式.
     */
    private Map<String, String> expsMap = new HashMap<String, String>();

    {
        // 正则帮助
        String helpPropsFile = "conf/RegexTester/help.txt";
        try {
            InputStream helpInputStream = new FileInputStream(GuiUtils.getActualPath(helpPropsFile));
            int count = 0;
            while (count == 0) {
                count = helpInputStream.available();
            }
            byte[] bytes = new byte[count];
            helpInputStream.read(bytes);
            helpText = new String(bytes, "UTF-8");
        } catch (IOException e) {
            logLoadPropertiesException(helpPropsFile, e);
        }

        // 常用正则表达式
        String expressionPropsFile = "conf/RegexTester/expression.properties";
        try {
            Map<String, String> expressionsMap = PropertiesUtils.getPropertiesMap(GuiUtils
                    .getActualPath(expressionPropsFile));
            List<String> expsSortAndNames = new ArrayList<String>();
            for (String key : expressionsMap.keySet()) {
                expsSortAndNames.add(key);
            }
            // 排序
            Collections.sort(expsSortAndNames);

            int expSize = expsSortAndNames.size() + 1;
            expNames = new String[expSize];
            expNames[0] = "常用正则表达式";

            // 常用正则表达式，按序号正排序
            for (int i = 1; i < expSize; i++) {
                String key = expsSortAndNames.get(i - 1);
                String[] keySplit = key.split("_");
                expNames[i] = keySplit.length > 1 ? key.substring(keySplit[0].length() + 1) : keySplit[0];
                expsMap.put(expNames[i], expressionsMap.get(key));
            }
        } catch (IOException e) {
            logLoadPropertiesException(expressionPropsFile, e);
        }
    }

    public RegexTester() {

        // 主面板：边界布局，只有Center部分
        setLayout(new BorderLayout());
        // Center，加密解密输入输出域，使用3行1列的Grid布局，使其平均显示
        JPanel textAreaPanel = new JPanel(new GridLayout(3, 1));
        add(textAreaPanel, BorderLayout.CENTER);

        JPanel regexPanel = new JPanel(new BorderLayout());
        addJLabel(regexPanel, " 匹配正则: ", GuiUtils.font14b_cn, BorderLayout.WEST);
        regexPanel.add(new JScrollPane(regexTextArea), BorderLayout.CENTER);
        textAreaPanel.add(regexPanel);

        JPanel sourcePanel = new JPanel(new BorderLayout());
        addJLabel(sourcePanel, " 匹配文本: ", GuiUtils.font14b_cn, BorderLayout.WEST);
        sourcePanel.add(new JScrollPane(sourceTextArea), BorderLayout.CENTER);
        textAreaPanel.add(sourcePanel);

        JPanel matchPanel = new JPanel(new BorderLayout());
        addJLabel(matchPanel, " 匹配结果: ", GuiUtils.font14b_cn, BorderLayout.WEST);
        matchPanel.add(new JScrollPane(matchTextArea), BorderLayout.CENTER);
        textAreaPanel.add(matchPanel);

        // East，操作区域，使用BorderLayout布局
        JPanel actionPanel = new JPanel(new BorderLayout());
        add(actionPanel, BorderLayout.EAST);
        // 放置多选框等
        JPanel actionGridPanel = new JPanel(new GridLayout(10, 1));
        actionPanel.add(actionGridPanel, BorderLayout.NORTH);

        // 放置帮助按钮
        JPanel helpButtonPanel = new JPanel(new GridLayout(1, 2));
        // 仅作填充
        helpButtonPanel.add(new Panel());
        addJButton(helpButtonPanel, "帮助", "", GuiUtils.font13_cn, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                showTextAreaMessage(helpText, "帮助", JOptionPane.INFORMATION_MESSAGE, null, null);
            }
        });
        actionGridPanel.add(helpButtonPanel);

        if (expNames != null && expNames.length != 0) {
            // 仅作填充
            actionGridPanel.add(new JLabel(""));

            // 常用正则表达式下拉框
            JPanel expressionsBoxPanel = new JPanel(new BorderLayout());
            addJLabel(expressionsBoxPanel, "  ", GuiUtils.font14_cn, BorderLayout.WEST);
            addJComboBox(expressionsBoxPanel, expNames, GuiUtils.font13_cn, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent event) {
                    String exp = expsMap.get(((JComboBox) event.getSource()).getSelectedItem().toString());
                    if (exp != null) {
                        regexTextArea.setText(exp);
                    }
                }
            }, BorderLayout.CENTER);
            actionGridPanel.add(expressionsBoxPanel);
        }

        // 仅作填充
        actionGridPanel.add(new JLabel(""));

        // 是否忽略大小写
        JPanel ignoreCasePanel = new JPanel(new FlowLayout(FlowLayout.LEADING));
        addJCheckBox(ignoreCasePanel, "忽略大小写", false, GuiUtils.font14_cn, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                JCheckBox checkBox = (JCheckBox) event.getSource();
                ignoreCase = checkBox.isSelected();
            }
        });
        actionGridPanel.add(ignoreCasePanel);
        // 是否显示详细匹配
        JPanel viewDetailPanel = new JPanel(new FlowLayout(FlowLayout.LEADING));
        addJCheckBox(viewDetailPanel, "显示详细匹配", false, GuiUtils.font14_cn, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                JCheckBox checkBox = (JCheckBox) event.getSource();
                viewDetail = checkBox.isSelected();
            }
        });
        actionGridPanel.add(viewDetailPanel);

        // 仅作填充
        actionGridPanel.add(new JLabel(""));

        // 替换匹配
        JPanel replaceLabelPanel = new JPanel(new FlowLayout(FlowLayout.LEADING));
        addJLabel(replaceLabelPanel, " 替换匹配: ", GuiUtils.font14_cn);
        actionGridPanel.add(replaceLabelPanel);
        JPanel replaceTextPanel = new JPanel(new BorderLayout());
        addJLabel(replaceTextPanel, "  ", GuiUtils.font14_cn, BorderLayout.WEST);
        addJTextField(replaceTextPanel, replaceTextField, GuiUtils.font14_un, BorderLayout.CENTER);
        actionGridPanel.add(replaceTextPanel);

        // 仅作填充
        actionPanel.add(new Panel(), BorderLayout.CENTER);

        // 放置匹配按钮
        JPanel buttonPanel = new JPanel(new GridLayout(2, 1));
        actionPanel.add(buttonPanel, BorderLayout.SOUTH);
        // 加密按钮
        addJButton(buttonPanel, "匹配", "", GuiUtils.font14b_cn, new SimpleMouseListener() {
            @Override
            public void mouseReleased(MouseEvent event) {
                String regexText = regexTextArea.getText().trim();
                String sourceText = sourceTextArea.getText().trim();
                String replaceText = replaceTextField.getText();
                Pattern p = null;
                if (ignoreCase) {
                    p = Pattern.compile(regexText, Pattern.CASE_INSENSITIVE); // 不区分大小写
                } else {
                    p = Pattern.compile(regexText);
                }
                // 用Pattern类的matcher()方法生成一个Matcher对象
                Matcher m = p.matcher(sourceText);
                StringBuffer sb = new StringBuffer();
                StringBuffer rsb = new StringBuffer(); // 替换匹配
                // 使用find()方法查找第一个匹配的对象
                boolean result = m.find();
                // 使用循环找出模式匹配的内容替换之,再将内容加到sb里
                int cnt = 0; // 匹配总数
                int start = 0;
                int end = 0;
                while (result) {
                    m.appendReplacement(rsb, replaceText); // 替换匹配
                    cnt++;
                    sb.append("\n");
                    start = m.start();
                    end = m.end();
                    String matchText = sourceText.substring(start, end);
                    if (viewDetail) {
                        sb.append("Match[").append(cnt).append("]: ");
                    }
                    sb.append(matchText);
                    if (viewDetail) {
                        sb.append(" [start: ").append(start).append(", end: ").append(end).append("]");
                    }
                    result = m.find();
                }
                sb.append("\n\n匹配总数: " + cnt);
                if (replaceText.length() != 0) {
                    m.appendTail(rsb);
                    sb.append("\n\n替换匹配: ").append(rsb);
                }
                matchTextArea.setText(sb.length() > 0 ? sb.substring(1) : "");
            }

            @Override
            public void mousePressed(MouseEvent e) {
                matchTextArea.setText("");
            }
        });
    }

}


<|start_filename|>src/main/java/bs/tool/commongui/utils/TimeUtils.java<|end_filename|>
package bs.tool.commongui.utils;

/**
 * 时间工具类.
 */
public class TimeUtils {

    /**
     * yyyy-MM-dd HH:mm:ss.
     */
    public static final String FORMATTER = "yyyy-MM-dd HH:mm:ss";

    /**
     * yyyy-MM-dd HH:mm:ss.SSS.
     */
    public static final String FORMATTER_MILLISECOND = "yyyy-MM-dd HH:mm:ss.SSS";

    /**
     * yyyy年MM月dd日HH时mm分ss秒.
     */
    public static final String FORMATTER_ZH = "yyyy年MM月dd日HH时mm分ss秒";

    /**
     * yyyy-MM-dd.
     */
    public static final String FORMATTER_YEAR = "yyyy-MM-dd";

    /**
     * yyyy年MM月dd日.
     */
    public static final String FORMATTER_ZH_YEAR = "yyyy年MM月dd日";

}


<|start_filename|>src/main/java/bs/tool/commongui/plugins/RunScript.java<|end_filename|>
package bs.tool.commongui.plugins;

import bs.tool.commongui.AbstractGuiJPanel;
import bs.tool.commongui.GuiUtils;
import bs.tool.commongui.utils.SimpleMouseListener;

import javax.script.ScriptEngine;
import javax.script.ScriptEngineFactory;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

/**
 * 执行Script脚本.
 */
public class RunScript extends AbstractGuiJPanel {

    private static final long serialVersionUID = 1L;

    /**
     * 脚本文本域.
     */
    private JTextArea scriptTextArea = createJTextArea(GuiUtils.font14_un);
    /**
     * 结果文本域.
     */
    private JTextArea resultTextArea = createJTextArea(GuiUtils.font14_un);

    /**
     * 脚本类型.
     */
    private String[] scriptTypes = getScriptTypes();
    /**
     * 当前脚本类型.
     */
    private String curScriptType = scriptTypes[0];

    public RunScript() {
        // 边界布局
        setLayout(new BorderLayout());
        // Center，脚本及结果输入输出域，使用2行1列的Grid布局，使其平均显示
        JPanel textAreaPanel = new JPanel(new GridLayout(2, 1));
        add(textAreaPanel, BorderLayout.CENTER);

        JPanel scriptPanel = new JPanel(new BorderLayout());
        addJLabel(scriptPanel, " 执行脚本: ", GuiUtils.font14b_cn, BorderLayout.WEST);
        scriptPanel.add(new JScrollPane(scriptTextArea), BorderLayout.CENTER);
        textAreaPanel.add(scriptPanel);

        JPanel resultPanel = new JPanel(new BorderLayout());
        addJLabel(resultPanel, " 执行结果: ", GuiUtils.font14b_cn, BorderLayout.WEST);
        resultPanel.add(new JScrollPane(resultTextArea), BorderLayout.CENTER);
        textAreaPanel.add(resultPanel);

        // East，操作区域，使用BorderLayout布局
        JPanel actionPanel = new JPanel(new BorderLayout());
        add(actionPanel, BorderLayout.EAST);
        // 放置下拉框等
        JPanel actionGridPanel = new JPanel(new GridLayout(5, 1));
        actionPanel.add(actionGridPanel, BorderLayout.NORTH);

        // 脚本类型下拉框
        JPanel scriptTypesPanel = new JPanel(new FlowLayout());
        addJLabel(scriptTypesPanel, "脚本类型:", GuiUtils.font14b_cn);
        addJComboBox(scriptTypesPanel, scriptTypes, GuiUtils.font13, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                curScriptType = ((JComboBox) event.getSource()).getSelectedItem().toString();
            }
        });
        actionGridPanel.add(scriptTypesPanel);

        // 放置执行按钮
        JPanel buttonPanel = new JPanel(new GridLayout(2, 1));
        actionPanel.add(buttonPanel, BorderLayout.SOUTH);
        // 执行按钮
        addJButton(buttonPanel, "执行", "", GuiUtils.font14b_cn, new SimpleMouseListener() {
            @Override
            public void mouseReleased(MouseEvent event) {
                String input = scriptTextArea.getText();

                ScriptEngineManager sem = new ScriptEngineManager();
                ScriptEngine se = sem.getEngineByName(curScriptType);
                try {
                    resultTextArea.setText(String.valueOf(se.eval(input)));
                } catch (ScriptException e) {
                    showExceptionMessage(e);
                }
            }

            @Override
            public void mousePressed(MouseEvent e) {
                resultTextArea.setText("");
            }
        });
    }

    /**
     * 支持的脚本类型.
     */
    private String[] getScriptTypes() {
        ScriptEngineManager sem = new ScriptEngineManager();
        List<ScriptEngineFactory> factorys = sem.getEngineFactories();
        Set<String> languages = new LinkedHashSet<String>();
        for (ScriptEngineFactory factory : factorys) {
            languages.add(factory.getLanguageName());
        }
        String[] types = new String[languages.size()];
        int i = 0;
        for (String name : languages) {
            types[i] = name;
            i++;
        }
        return types;
    }

}


<|start_filename|>src/main/java/bs/tool/commongui/plugins/more/TextFileSplit.java<|end_filename|>
package bs.tool.commongui.plugins.more;

import bs.tool.commongui.AbstractGuiJPanel;
import bs.tool.commongui.GuiUtils;
import bs.tool.commongui.utils.FileUtils;
import bs.tool.commongui.utils.SearchFileNameParams;
import bs.tool.commongui.utils.SimpleMouseListener;
import bs.util.io.PropertiesUtils;
import org.apache.commons.io.LineIterator;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.*;

/**
 * 文本文件切分.
 */
public class TextFileSplit extends AbstractGuiJPanel {

    private static final long serialVersionUID = 1L;

    /**
     * 切分文本/文件夹路径表单.
     */
    private JTextField splitPathTextField = new JTextField();
    /**
     * 切分文本/文件夹路径选择.
     */
    private JFileChooser splitPathChooser = new JFileChooser();

    /**
     * 切分文件结果目录.
     */
    private String splitResultDirectory;

    /**
     * 切分按钮.
     */
    private JButton splitButton;

    /**
     * 按行数或大小切分表单.
     */
    private JTextField splitValueField = new JTextField("10", 5);

    /**
     * 切分类型.
     */
    private String[] splitTypes = new String[]{GuiUtils.SPLIT_TYPE_SIZE, GuiUtils.SPLIT_TYPE_LINE};

    /**
     * 当期切分类型.
     */
    private String currentSplitType = splitTypes[0];

    /**
     * 文件名包含字符表单.
     */
    private JTextField fileNameContainsTextField;
    /**
     * 文件名不包含字符表单.
     */
    private JTextField fileNameNotContainsTextField;
    /**
     * 文件名是否支持正则.
     */
    private boolean fileNameSupportRegex = true;

    /**
     * 源文件编码 - 程序自动检测.
     */
    private String sourceEncodingAuto = "程序自动检测";
    /**
     * 源文件编码.
     */
    private JTextField sourceEncodingField = new JTextField(sourceEncodingAuto, 8);
    /**
     * 切分结果文件编码.
     */
    private JTextField targetEncodingField = new JTextField(GuiUtils.CHARSET_UTF_8, 6);

    /**
     * 行分隔符.
     */
    private String[] lineSeparators = new String[]{"\\n", "\\r\\n", "\\r"};
    private Map<String, String> lineSeparatorsMap = new HashMap<String, String>() {{
        put("\\n", "\n");
        put("\\r\\n", "\r\n");
        put("\\r", "\r");
    }};
    /**
     * 当期行分隔符.
     */
    private String currentLineSeparator = lineSeparatorsMap.get(lineSeparators[0]);

    /**
     * 切分结果文本域.
     */
    private JTextArea resultTextArea = createJTextArea(GuiUtils.font14_un);

    public TextFileSplit() {

        // 主面板：边界布局，分North、Center两部分，North用于放置输入及条件控件，Center是放置切分结果输出
        setLayout(new BorderLayout());

        // 输入及条件Panel
        JPanel inputPanel = new JPanel(new GridLayout(3, 1));

        // 切分文本/文件夹表单、切分按钮Panel
        JPanel fileChooAndSplitPanel = new JPanel(new BorderLayout());
        // 切分文本/文件夹表单
        addJLabel(fileChooAndSplitPanel, "  切分文本/文件夹: ", GuiUtils.font14_cn, BorderLayout.WEST);
        JPanel fileChooPanel = new JPanel(new BorderLayout());
        fileChooPanel.add(new JPanel(), BorderLayout.NORTH);
        addJTextField(fileChooPanel, splitPathTextField, GuiUtils.font14_un, BorderLayout.CENTER);
        fileChooPanel.add(new JPanel(), BorderLayout.SOUTH);
        fileChooAndSplitPanel.add(fileChooPanel, BorderLayout.CENTER);

        JPanel buttonFlowPanel = new JPanel(new FlowLayout(FlowLayout.LEADING));
        addJButton(buttonFlowPanel, "浏览", "", GuiUtils.font12_cn,
                buttonBrowseListener(splitPathChooser, splitPathTextField));
        // 切分按钮
        splitButton = createJButton("切分", "", GuiUtils.font14b_cn);
        splitButton.addMouseListener(new SimpleMouseListener() {
            @Override
            public void mouseReleased(MouseEvent event) {
                String path = splitPathTextField.getText().trim();
                File file = new File(path);
                if (!file.exists()) {
                    showMessage("切分文本/文件夹不存在！", "警告", JOptionPane.WARNING_MESSAGE);
                    return;
                }
                splitResultDirectory = file.getAbsolutePath() + " - split result";
                File splitResultDir = new File(splitResultDirectory);
                if (splitResultDir.exists()) {
                    try {
                        org.apache.commons.io.FileUtils.deleteDirectory(splitResultDir);
                    } catch (IOException e) {
                        showMessage("删除原切分结果文件夹失败！", "警告", JOptionPane.WARNING_MESSAGE);
                    }
                }
                splitResultDir.mkdir();
                Map<String, Object> paramsMap = new HashMap<String, Object>();
                paramsMap.put("fileNameContainsText", fileNameContainsTextField.getText().trim());
                paramsMap.put("fileNameNotContainsText", fileNameNotContainsTextField.getText().trim());
                paramsMap.put("fileNameSupportRegex", fileNameSupportRegex);
                splitButton.setEnabled(false);

                resultTextArea.setText("");
                try {
                    String sourceEncoding = sourceEncodingField.getText().trim();
                    String targetEncoding = targetEncodingField.getText().trim();
                    if (sourceEncoding.length() == 0) {
                        showMessage("源文件编码不可为空！", "警告", JOptionPane.WARNING_MESSAGE);
                        return;
                    }
                    if (targetEncoding.length() == 0) {
                        showMessage("切分结果文件编码不可为空！", "警告", JOptionPane.WARNING_MESSAGE);
                        return;
                    }
                    int splitLength = Integer.parseInt(splitValueField.getText().trim());
                    if (!file.isDirectory()) {
                        appendSplitResult(resultTextArea, file, splitLength, sourceEncoding, targetEncoding);
                    } else {
                        List<File> files = new ArrayList<File>();
                        FileUtils.loopDirectory(file, files, new SearchFileNameParams(paramsMap));
                        for (File fFile : files) {
                            appendSplitResult(resultTextArea, fFile, splitLength, sourceEncoding, targetEncoding);
                        }
                        resultTextArea.append("\nCount files: " + files.size());
                    }
                } catch (NumberFormatException e) {
                    showMessage("切分大小必须是正整数！", "警告", JOptionPane.WARNING_MESSAGE);
                } catch (IOException e) {
                    showExceptionMessage(e);
                }
                splitButton.setEnabled(true);
            }

            @Override
            public void mousePressed(MouseEvent e) {
                resultTextArea.setText("");
            }
        });
        buttonFlowPanel.add(splitButton);
        // 切分路径选择控件，可选择文件/文件夹
        splitPathChooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
        fileChooAndSplitPanel.add(buttonFlowPanel, BorderLayout.EAST);
        inputPanel.add(fileChooAndSplitPanel);

        // 高级条件Panel
        JPanel advancePanel = new JPanel(new FlowLayout(FlowLayout.LEADING));
        // 切分大小
        addJLabel(advancePanel, " ", GuiUtils.font12_cn);
        addJLabel(advancePanel, "切分条件: ", GuiUtils.font14_cn);
        addJTextField(advancePanel, splitValueField, GuiUtils.font14_cn);
        // 切分型下拉框
        addJComboBox(advancePanel, splitTypes, GuiUtils.font13, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                currentSplitType = ((JComboBox) event.getSource()).getSelectedItem().toString();
            }
        });

        // 文件名包含(不包含)字符
        String confPropsFile = "conf/TextFileSplit/searchFiletypes.properties";
        String containsFiletypes = "";
        String notContainsFiletypes = "";
        try {
            Properties confProperties = PropertiesUtils.getProperties(GuiUtils.getActualPath(confPropsFile));
            containsFiletypes = confProperties.getProperty("ContainsFiletypes").trim();
            notContainsFiletypes = confProperties.getProperty("NotContainsFiletypes").trim();
        } catch (IOException e) {
            logLoadPropertiesException(confPropsFile, e);
        }
        addJLabel(advancePanel, " 文件名包含字符: ", GuiUtils.font14_cn);
        String fileNameContains = containsFiletypes.length() > 0 ? "\\.(" + containsFiletypes.replace(",", "|") + ")$"
                : "";
        fileNameContainsTextField = new JTextField(fileNameContains, 15);
        addJTextField(advancePanel, fileNameContainsTextField, GuiUtils.font14_un);
        addJLabel(advancePanel, "  文件名不包含字符: ", GuiUtils.font14_cn);
        String fileNameNotContains = notContainsFiletypes.length() > 0 ? "\\.("
                + notContainsFiletypes.replace(",", "|") + ")$" : "";
        fileNameNotContainsTextField = new JTextField(fileNameNotContains, 15);
        addJTextField(advancePanel, fileNameNotContainsTextField, GuiUtils.font14_un);
        // 是否支持正则
        addJCheckBox(advancePanel, "支持正则", true, GuiUtils.font14_cn, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                fileNameSupportRegex = ((JCheckBox) event.getSource()).isSelected();
            }
        });
        inputPanel.add(advancePanel);

        // 高级条件面板
        JPanel advanceConditionPanel = new JPanel(new FlowLayout(FlowLayout.LEADING));
        addJLabel(advanceConditionPanel, " ", GuiUtils.font12_cn);
        addJLabel(advanceConditionPanel, "源文件编码:", GuiUtils.font14_cn);
        advanceConditionPanel.add(sourceEncodingField);
        addJLabel(advanceConditionPanel, " 切分结果文件编码:", GuiUtils.font14_cn);
        advanceConditionPanel.add(targetEncodingField);
        addJLabel(advanceConditionPanel, " 行分隔符:", GuiUtils.font14_cn);
        // 行分隔符下拉框
        addJComboBox(advanceConditionPanel, lineSeparators, GuiUtils.font13, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                currentLineSeparator = lineSeparatorsMap.get(((JComboBox) event.getSource()).getSelectedItem().toString());
            }
        });
        inputPanel.add(advanceConditionPanel);

        add(inputPanel, BorderLayout.NORTH);

        // 切分结果输出
        JPanel splitLogPanel = new JPanel(new BorderLayout());
        splitLogPanel.add(new JScrollPane(resultTextArea));
        add(splitLogPanel, BorderLayout.CENTER);
    }

    /**
     * append切分文本编码结果.
     */
    private void appendSplitResult(JTextArea resultTextArea, File file, int splitLength, String sourceEncoding, String targetEncoding) throws IOException {
        resultTextArea.append(file.getAbsolutePath() + "\n    Result: " + splitFileResult(file, splitLength, sourceEncoding, targetEncoding)
                + "\n");
    }

    /**
     * 切分文本编码.
     */
    private String splitFileResult(File file, int splitLength, String sourceEncoding, String targetEncoding) throws IOException {
        StringBuilder rsb = new StringBuilder();
        String charset = sourceEncoding;
        if (charset.equals(sourceEncodingAuto)) {
            charset = JUniversalChardet.detectFileCharset(file, 4096);
        }
        LineIterator lineIterator = org.apache.commons.io.FileUtils.lineIterator(file, charset);
        String fileName = file.getName();
        int lastIndex = fileName.lastIndexOf(".");
        lastIndex = (lastIndex == -1 ? fileName.length() : lastIndex);
        String fileSimpleName = fileName.substring(0, lastIndex);
        String fileType = fileName.substring(lastIndex + 1, fileName.length());
        String rs;
        int fileNum = 1;
        while ((rs = loopSplitFile(lineIterator, splitLength, fileSimpleName + "_" + (fileNum++) + (fileType.length() != 0 ? "." : "") + fileType, targetEncoding)) != null) {
            if (rsb.length() != 0) {
                rsb.append("            ");
            }
            rsb.append(rs).append("\n");
        }
        lineIterator.close();
        return rsb.toString();
    }

    private String loopSplitFile(LineIterator lineIterator, int splitLength, String splitFileName, String targetEncoding) throws IOException {
        File splitFile = new File(splitResultDirectory + "/" + splitFileName);
        int lineCnt = 0;
        List<String> lines;
        List<String> writeLines = new ArrayList<String>();
        while ((lines = iteratorLine(lineIterator, 100, splitLength, lineCnt)).size() != 0) {
            lineCnt += lines.size();
            writeLines.addAll(lines);
            if (currentSplitType.equals(GuiUtils.SPLIT_TYPE_SIZE) && splitFile.getTotalSpace() >= splitLength * 1024 * 1024) {
                break;
            }
        }
        if (writeLines.size() != 0) {
            org.apache.commons.io.FileUtils.writeLines(splitFile, targetEncoding, writeLines, currentLineSeparator);
            return splitFile.getAbsolutePath();
        } else {
            return null;
        }
    }

    private List<String> iteratorLine(LineIterator lineIterator, int line, int splitLength, int lineCnt) {
        List<String> lines = new ArrayList<String>();
        while (lineIterator.hasNext() && (line-- > 0)) {
            if (currentSplitType.equals(GuiUtils.SPLIT_TYPE_LINE) && lineCnt + lines.size() >= splitLength) {
                break;
            } else {
                lines.add(lineIterator.nextLine());
            }
        }
        return lines;
    }

}


<|start_filename|>src/main/java/bs/tool/commongui/utils/SearchFileAndFolderNamePathParams.java<|end_filename|>
package bs.tool.commongui.utils;

import bs.tool.commongui.GuiUtils;

import java.util.Map;
import java.util.regex.Pattern;

public class SearchFileAndFolderNamePathParams {

    public Boolean typeRepeatSearch;
    public Boolean typeSameNameSearch;
    public Boolean typeBlankSearch;
    public String fileType;
    public boolean containsFile;
    public boolean containsFolder;
    public boolean containsHidden;
    public boolean containsNotHidden;
    public boolean repeatSameSuffix;
    public Double sizeFrom;
    public Double sizeTo;
    public Long modifyTimeFrom;
    public Long modifyTimeTo;
    public String filePathCsText;
    public String filePathNCsText;
    public boolean filePathSRegex;
    public String fileNameCsText;
    public String fileNameNCsText;
    public boolean fileNameSRegex;
    public String folderPathCsText;
    public String folderPathNCsText;
    public boolean folderPathSRegex;

    public Pattern filePathCsPattern;
    public Pattern filePathNCsPattern;
    public Pattern fileNameCsPattern;
    public Pattern fileNameNCsPattern;
    public Pattern folderPathCsPattern;
    public Pattern folderPathNCsPattern;
    // 0表示不限制
    public int folderHierarchy;

    public SearchFileAndFolderNamePathParams(Map<String, Object> paramsMap) {
        typeRepeatSearch = Boolean.parseBoolean(GuiUtils.toString(paramsMap.get("type_repeatSearch")));
        typeSameNameSearch = Boolean.parseBoolean(GuiUtils.toString(paramsMap.get("type_sameNameSearch")));
        typeBlankSearch = Boolean.parseBoolean(GuiUtils.toString(paramsMap.get("type_blankSearch")));
        fileType = GuiUtils.toString(paramsMap.get("searchFileType"));
        containsFile = GuiUtils.parseFalse(paramsMap.get("containsFile"));
        containsFolder = GuiUtils.parseFalse(paramsMap.get("containsFolder"));
        containsHidden = GuiUtils.parseFalse(paramsMap.get("containsHidden"));
        containsNotHidden = GuiUtils.parseFalse(paramsMap.get("containsNotHidden"));
        repeatSameSuffix = GuiUtils.parseFalse(paramsMap.get("repeatSameSuffix"));
        sizeFrom = (Double) paramsMap.get("fileSizeFrom");
        sizeTo = (Double) paramsMap.get("fileSizeTo");
        modifyTimeFrom = (Long) paramsMap.get("modifyTimeFrom");
        modifyTimeTo = (Long) paramsMap.get("modifyTimeTo");
        filePathCsText = GuiUtils.toString(paramsMap.get("filePathContainsText"));
        filePathNCsText = GuiUtils.toString(paramsMap.get("filePathNotContainsText"));
        filePathSRegex = Boolean.parseBoolean(GuiUtils.toString(paramsMap.get("filePathSupportRegex")));
        fileNameCsText = GuiUtils.toString(paramsMap.get("fileNameContainsText"));
        fileNameNCsText = GuiUtils.toString(paramsMap.get("fileNameNotContainsText"));
        fileNameSRegex = Boolean.parseBoolean(GuiUtils.toString(paramsMap.get("fileNameSupportRegex")));
        folderPathCsText = GuiUtils.toString(paramsMap.get("folderPathContainsText"));
        folderPathNCsText = GuiUtils.toString(paramsMap.get("folderPathNotContainsText"));
        folderPathSRegex = Boolean.parseBoolean(GuiUtils.toString(paramsMap.get("folderPathSupportRegex")));
        if (GuiUtils.toString(paramsMap.get("folderHierarchyText")).length() != 0) {
            folderHierarchy = Integer.parseInt(GuiUtils.toString(paramsMap.get("folderHierarchyText")));
        } else {
            folderHierarchy = 0;
        }

        if (filePathSRegex) {
            filePathCsPattern = Pattern.compile(filePathCsText, Pattern.CASE_INSENSITIVE);
            filePathNCsPattern = Pattern.compile(filePathNCsText, Pattern.CASE_INSENSITIVE);
        }
        if (fileNameSRegex) {
            fileNameCsPattern = Pattern.compile(fileNameCsText, Pattern.CASE_INSENSITIVE);
            fileNameNCsPattern = Pattern.compile(fileNameNCsText, Pattern.CASE_INSENSITIVE);
        }
        if (folderPathSRegex) {
            folderPathCsPattern = Pattern.compile(folderPathCsText, Pattern.CASE_INSENSITIVE);
            folderPathNCsPattern = Pattern.compile(folderPathNCsText, Pattern.CASE_INSENSITIVE);
        }
    }

}


<|start_filename|>src/main/sources/bin/start.bat<|end_filename|>
start javaw -Xms64m -Xmx256m -cp "%CD%\lib\*" bs.tool.commongui.GuiMain

<|start_filename|>src/main/java/bs/tool/commongui/utils/SearchFileNameParams.java<|end_filename|>
package bs.tool.commongui.utils;

import bs.tool.commongui.GuiUtils;

import java.util.Map;
import java.util.regex.Pattern;

public class SearchFileNameParams {

    public String fileNameCsText;
    public String fileNameNCsText;
    public boolean fileNameSRegex;

    public Pattern fileNameCsPattern;
    public Pattern fileNameNCsPattern;

    public SearchFileNameParams(Map<String, Object> paramsMap) {
        fileNameCsText = GuiUtils.toString(paramsMap.get("fileNameContainsText"));
        fileNameNCsText = GuiUtils.toString(paramsMap.get("fileNameNotContainsText"));
        fileNameSRegex = Boolean.parseBoolean(GuiUtils.toString(paramsMap.get("fileNameSupportRegex")));
        if (fileNameSRegex) {
            fileNameCsPattern = Pattern.compile(fileNameCsText, Pattern.CASE_INSENSITIVE);
            fileNameNCsPattern = Pattern.compile(fileNameNCsText, Pattern.CASE_INSENSITIVE);
        }
    }

}


<|start_filename|>src/main/java/bs/tool/commongui/code/QrCodeUtil.java<|end_filename|>
package bs.tool.commongui.code;

import com.google.zxing.*;
import com.google.zxing.client.j2se.BufferedImageLuminanceSource;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.common.HybridBinarizer;
import org.apache.commons.codec.binary.Base64;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

public class QrCodeUtil {

    public static final String FORMAT_JPG = "jpg";
    public static final String FORMAT_PNG = "png";
    public static final String FORMAT_BMP = "bmp";

    public static void genQrCodeJpgFile(String content, String filePath, int width, int height) throws WriterException, IOException {
        genQrCodeFile(content, filePath, QrCodeUtil.FORMAT_JPG, width, height);
    }

    public static void genQrCodeFile(String content, String filePath, String format, int width, int height) throws WriterException, IOException {
        MultiFormatWriter multiFormatWriter = new MultiFormatWriter();
        Map hints = new HashMap();
        hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
        hints.put(EncodeHintType.MARGIN, 2);
        BitMatrix bitMatrix = multiFormatWriter.encode(content, BarcodeFormat.QR_CODE, width, height, hints);
        File file = new File(filePath);
        MatrixToImageWriter.writeToFile(bitMatrix, format, file);
    }

    public static String genQrCodeBase64String(String content, String format, int width, int height) throws WriterException, IOException {
        MultiFormatWriter multiFormatWriter = new MultiFormatWriter();
        Map hints = new HashMap();
        hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
        hints.put(EncodeHintType.MARGIN, 2);
        BitMatrix bitMatrix = multiFormatWriter.encode(content, BarcodeFormat.QR_CODE, width, height, hints);
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        MatrixToImageWriter.writeToStream(bitMatrix, format, bos);
        return new String(Base64.encodeBase64(bos.toByteArray()), "utf-8");
    }

    public static Result decodeQrCodeFile(String filePath) throws IOException, NotFoundException {
        MultiFormatReader formatReader = new MultiFormatReader();
        File file = new File(filePath);
        BufferedImage image = ImageIO.read(file);
        LuminanceSource source = new BufferedImageLuminanceSource(image);
        Binarizer binarizer = new HybridBinarizer(source);
        BinaryBitmap binaryBitmap = new BinaryBitmap(binarizer);
        Map hints = new HashMap();
        hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
        return formatReader.decode(binaryBitmap, hints);
    }

}


<|start_filename|>src/main/java/bs/tool/commongui/AbstractGuiJPanel.java<|end_filename|>
package bs.tool.commongui;

import javax.swing.*;
import javax.swing.filechooser.FileNameExtensionFilter;
import javax.swing.text.DateFormatter;
import javax.swing.text.NumberFormatter;
import java.awt.*;
import java.awt.event.*;
import java.io.File;
import java.text.DecimalFormat;
import java.text.Format;
import java.text.ParseException;
import java.text.SimpleDateFormat;

public abstract class AbstractGuiJPanel extends JPanel {

    private static final long serialVersionUID = 1L;

    public AbstractGuiJPanel() {
    }

    /**
     * 获取当前面板.
     */
    public JPanel getContextPanel() {
        return this;
    }

    /**
     * 给面板增加指定标题及字体的Label.
     */
    public void addJLabel(JPanel panel, String name, Font font) {
        JLabel label = new JLabel(name);
        label.setFont(font);
        panel.add(label);
    }

    /**
     * 给面板增加指定标题及字体的Label，并指定布局位置.
     */
    public void addJLabel(JPanel panel, String name, Font font, String layout) {
        JLabel label = new JLabel(name);
        label.setFont(font);
        panel.add(label, layout);
    }

    /**
     * 给面板增加指定字体的JTextField.
     */
    public void addJTextField(JPanel panel, JTextField textField, Font font) {
        textField.setFont(font);
        panel.add(textField);
    }

    /**
     * 给面板增加指定字体的JTextField，并指定布局位置.
     */
    public void addJTextField(JPanel panel, JTextField textField, Font font, String layout) {
        textField.setFont(font);
        panel.add(textField, layout);
    }

    /**
     * 创建指定字体、自动换行的JTextArea.
     */
    public JTextArea createJTextArea(Font font) {
        return createJTextArea(font, "");
    }

    /**
     * 创建指定字体、自动换行的JTextArea.
     */
    public JTextArea createJTextArea(Font font, String defaultText) {
        JTextArea textArea = new JTextArea(defaultText);
        textArea.setFont(font);
        textArea.setLineWrap(true);
        return textArea;
    }

    /**
     * 浏览按钮对于JFileChooser表单选择文件/文件夹的事件.
     */
    public ActionListener buttonBrowseListener(final JFileChooser fileChooser, final JTextField pathTextField) {
        return new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                fileChooserBrowse(fileChooser, pathTextField);
            }
        };
    }

    /**
     * JFileChooser表单选择文件/文件夹.
     */
    public void fileChooserBrowse(final JFileChooser fileChooser, final JTextField pathTextField) {
        if (fileChooser.showDialog(getContextPanel(), "确定") != JFileChooser.CANCEL_OPTION) {
            File selectFile = fileChooser.getSelectedFile();
            if (selectFile != null) {
                pathTextField.setText(selectFile.getAbsolutePath());
            }
        }
    }

    /**
     * 图片选择.
     */
    public JFileChooser createImageChooser() {
        return createFileChooser("png, jpg, jpeg, gif, bmp", "png", "jpg", "jpeg", "gif", "bmp");
    }

    /**
     * 文件选择.
     *
     * @param description 描述
     * @param extensions  文件后缀
     * @return
     */
    public JFileChooser createFileChooser(String description, String... extensions) {
        JFileChooser fileChooser = new JFileChooser();
        fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
        // 不显示所有文件
        fileChooser.setAcceptAllFileFilterUsed(false);
        fileChooser.addChoosableFileFilter(new FileNameExtensionFilter(description, extensions));
        return fileChooser;
    }

    /**
     * 给面板增加指定标题、名称、字体及ActionListener的JButton.
     */
    public void addJButton(JPanel panel, String title, String name, Font font, ActionListener listener) {
        JButton button = createJButton(title, name, font);
        button.addActionListener(listener);
        panel.add(button);
    }

    /**
     * 给面板增加指定标题、名称、字体及MouseListener的JButton.
     */
    public void addJButton(JPanel panel, String title, String name, Font font, MouseListener listener) {
        JButton button = createJButton(title, name, font);
        button.addMouseListener(listener);
        panel.add(button);
    }

    /**
     * 创建指定标题、名称、字体的JButton.
     */
    public JButton createJButton(String title, String name, Font font) {
        JButton button = new JButton(title);
        button.setFont(font);
        button.setName(name);
        return button;
    }

    /**
     * 给面板增加指定标题、是否selected、字体及ActionListener的JCheckbox.
     */
    public void addJCheckBox(JPanel panel, String title, boolean isSelected, Font font, ActionListener listener) {
        panel.add(createJCheckBox(title, isSelected, font, listener));
    }

    /**
     * 给面板增加指定标题、是否selected、字体及ActionListener的JCheckbox，并指定布局位置.
     */
    public void addJCheckBox(JPanel panel, String title, boolean isSelected, Font font, ActionListener listener,
                             String layout) {
        panel.add(createJCheckBox(title, isSelected, font, listener), layout);
    }

    /**
     * 获取指定标题、是否selected、字体及ActionListener的JCheckbox.
     */
    public JCheckBox createJCheckBox(String title, boolean isSelected, Font font, ActionListener listener) {
        JCheckBox checkBox = new JCheckBox(title);
        checkBox.setFont(font);
        checkBox.setSelected(isSelected);
        checkBox.addActionListener(listener);
        return checkBox;
    }

    /**
     * 给面板增加指定下拉Items、字体及ActionListener的JComboBox.
     */
    public void addJComboBox(JPanel panel, String[] items, Font font, ActionListener listener) {
        panel.add(createJComboBox(items, font, listener));
    }

    /**
     * 给面板增加指定下拉Items、字体及ActionListener的JComboBox，并选择特定位置的Item.
     */
    public void addJComboBox(JPanel panel, String[] items, Integer selectIndex, Font font, ActionListener listener) {
        panel.add(createJComboBox(items, selectIndex, font, listener));
    }

    /**
     * 给面板增加指定下拉Items、字体及ActionListener的JComboBox，并指定布局位置.
     */
    public void addJComboBox(JPanel panel, String[] items, Font font, ActionListener listener, String layout) {
        panel.add(createJComboBox(items, font, listener), layout);
    }

    /**
     * 给面板增加指定下拉Items、字体及ActionListener的JComboBox，并指定布局位置，并选择特定位置的Item.
     */
    public void addJComboBox(JPanel panel, String[] items, Integer selectIndex, Font font, ActionListener listener,
                             String layout) {
        panel.add(createJComboBox(items, selectIndex, font, listener), layout);
    }

    /**
     * 创建指定下拉Items、字体及ActionListener的JComboBox.
     */
    public JComboBox createJComboBox(String[] items, Font font, ActionListener listener) {
        return createJComboBox(items, 0, font, listener);
    }

    /**
     * 创建指定下拉Items、字体及ActionListener的JComboBox，并选择特定位置的Item.
     */
    public JComboBox createJComboBox(String[] items, Integer selectIndex, Font font, ActionListener listener) {
        JComboBox comboBox = new JComboBox();
        comboBox.setFont(font);
        for (int i = 0; i < items.length; i++) {
            comboBox.addItem(items[i]);
        }
        comboBox.setSelectedIndex(selectIndex);
        comboBox.addActionListener(listener);
        return comboBox;
    }

    /**
     * 消息提示.
     */
    public void showMessage(String message, String title, int type) {
        JOptionPane.showMessageDialog(this, message, title, type);
    }

    /**
     * 消息提示.
     */
    public void showTextAreaMessage(String message, String title, int type, Font font, Color background) {
        JTextArea textArea = new JTextArea(message);
        if (font == null) {
            textArea.setFont(GuiUtils.font14_un);
        } else {
            textArea.setFont(font);
        }
        textArea.setEditable(false);
        if (background == null) {
            textArea.setBackground(new Color(214, 217, 223));
        } else {
            textArea.setBackground(background);
        }
        JOptionPane.showMessageDialog(this, textArea, title, type);
    }

    /**
     * 确认提示.
     */
    public int showConfirmMessage(String message, String title, int type) {
        return JOptionPane.showConfirmDialog(this, message, title, type);
    }

    /**
     * 格式化数字-三位有效数字.
     */
    public DecimalFormat formatDouble3 = new DecimalFormat("0.000");
    /**
     * 格式化数字-六位有效数字.
     */
    public DecimalFormat formatDouble6 = new DecimalFormat("0.000000");

    /**
     * 日期格式化Formatter yyyyMMdd.
     */
    public final static String FORMATTER_YYYYMMDD = "yyyyMMdd";
    /**
     * 日期格式化Formatter yyyy-MM-dd HH:mm:ss.
     */
    public final static String FORMATTER_YYYYMMDDHHMMSS = "yyyy-MM-dd HH:mm:ss";

    /**
     * 时间输入表单，默认格式yyyy-MM-dd HH:mm:ss.
     */
    public JFormattedTextField createDateTextField() {
        return createDateTextField(new SimpleDateFormat(FORMATTER_YYYYMMDDHHMMSS), (GuiUtils.IS_MAC ? 11 : 19), GuiUtils.font14_cn, "");
    }

    /**
     * 获取格式化时间字符串的Long型表示.
     */
    public Long getLongFormatTime(String fomartStr, SimpleDateFormat dateFormat) {
        Long time = null;
        try {
            if (fomartStr != null && fomartStr.length() != 0) {
                time = dateFormat.parse(fomartStr).getTime();
            }
        } catch (ParseException e) {
            GuiUtils.log(e);
        }
        return time;
    }

    /**
     * 时间输入表单. 时间格式为"yyyyMMdd"或"yyyy-MM-dd"或"yyyy.MM.dd"或"yyyy,MM,dd"或"yyyy-MM-dd HH:mm:ss".
     */
    public JFormattedTextField createDateTextField(Format format, int columns, Font font, String defaultValue) {
        JFormattedTextField field = new JFormattedTextField(format);
        field.setColumns(columns);
        field.setFont(font);
        field.setText(defaultValue);
        /**
         * <pre>
         * JFormattedTextField.REVERT            恢复显示以匹配 getValue，这可能丢失当前的编辑内容。
         * JFormattedTextField.COMMIT            提交当前值。如果 AbstractFormatter 不认为所编辑的值是合法值，则抛出 ParseException，然后不更改该值并保留已编辑的值。
         * JFormattedTextField.COMMIT_OR_REVERT  与COMMIT 类似，但是如果该值不是合法的，则其行为类似于 REVERT。
         * JFormattedTextField.PERSIST           不执行任何操作，不获取新的 AbstractFormatter 也不更新该值。
         * 默认值为 JFormattedTextField.COMMIT_OR_REVERT。
         * </pre>
         */
        field.setFocusLostBehavior(JFormattedTextField.COMMIT);
        field.addFocusListener(new FocusListener() {
            @Override
            public void focusLost(FocusEvent event) {
                JFormattedTextField field = (JFormattedTextField) event.getSource();
                String value = field.getText().trim();
                if (value.length() == 8 || value.length() == 10) {
                    String mayDate = value.replace("-", "").replace(".", "").replace(",", "");
                    if (mayDate.length() == 8) {
                        try {
                            new SimpleDateFormat(FORMATTER_YYYYMMDD).parse(mayDate);
                            field.setText(mayDate.substring(0, 4) + "-" + mayDate.substring(4, 6) + "-"
                                    + mayDate.substring(6, 8) + " 00:00:00");
                        } catch (ParseException e) {
                            GuiUtils.log(e);
                        }
                    }
                }
                if (value.length() != 0 && !field.isEditValid()) {
                    SimpleDateFormat dateFormat = (SimpleDateFormat) (((DateFormatter) field.getFormatter())
                            .getFormat());
                    showMessage(
                            "时间格式必须为\"yyyyMMdd\"或\"yyyy-MM-dd\"或\"yyyy.MM.dd\"或\"yyyy,MM,dd\"或\""
                                    + dateFormat.toPattern() + "\"！", "警告", JOptionPane.WARNING_MESSAGE);
                    field.setText("");
                }
            }

            @Override
            public void focusGained(FocusEvent event) {
            }
        });
        return field;
    }

    /**
     * 数字输入表单.
     */
    public JFormattedTextField createNumberTextField() {
        return createNumberTextField(new NumberFormatter(), (GuiUtils.IS_MAC ? 7 : 13), GuiUtils.font14_cn, "");
    }

    /**
     * 数字输入表单.
     */
    public JFormattedTextField createNumberTextField(NumberFormatter format, int columns, Font font, String defaultValue) {
        JFormattedTextField field = new JFormattedTextField(format);
        field.setColumns(columns);
        field.setFont(font);
        field.setText(defaultValue);
        /**
         * <pre>
         * JFormattedTextField.REVERT            恢复显示以匹配 getValue，这可能丢失当前的编辑内容。
         * JFormattedTextField.COMMIT            提交当前值。如果 AbstractFormatter 不认为所编辑的值是合法值，则抛出 ParseException，然后不更改该值并保留已编辑的值。
         * JFormattedTextField.COMMIT_OR_REVERT  与COMMIT 类似，但是如果该值不是合法的，则其行为类似于 REVERT。
         * JFormattedTextField.PERSIST           不执行任何操作，不获取新的 AbstractFormatter 也不更新该值。
         * 默认值为 JFormattedTextField.COMMIT_OR_REVERT。
         * </pre>
         */
        field.setFocusLostBehavior(JFormattedTextField.COMMIT);
        field.addFocusListener(new FocusListener() {
            @Override
            public void focusLost(FocusEvent event) {
                JFormattedTextField field = (JFormattedTextField) event.getSource();
                String value = field.getText().trim();
                if (value.length() != 0 && !field.isEditValid()) {
                    /*
                     * NumberFormat numberFormat = (NumberFormat) (((NumberFormatter)
                     * field.getFormatter()).getFormat());
                     */
                    showMessage("必须填写数字！", "警告", JOptionPane.WARNING_MESSAGE);
                    field.setText("");
                }
            }

            @Override
            public void focusGained(FocusEvent event) {
            }
        });
        return field;
    }

    /**
     * 文件大小单位.
     */
    public String[] fileSizeUnit = new String[]{GuiUtils.FileSize_G, GuiUtils.FileSize_M, GuiUtils.FileSize_KB,
            GuiUtils.FileSize_Byte};

    /**
     * 文件大小单位下拉框.
     */
    public JComboBox createFileSizeUnitBox(Font font) {
        JComboBox fileSizeUnitBox = new JComboBox();
        fileSizeUnitBox.setFont(font);
        for (int i = 0; i < fileSizeUnit.length; i++) {
            fileSizeUnitBox.addItem(fileSizeUnit[i]);
        }
        fileSizeUnitBox.setSelectedIndex(1);
        return fileSizeUnitBox;
    }

    /**
     * 异常输出.
     *
     * @param e 异常
     */
    public void showExceptionMessage(Exception e) {
        showExceptionMessage(e, e.getClass().getName() + ": " + e.getMessage());
    }

    /**
     * 异常输出.
     *
     * @param e       异常
     * @param message 提示信息
     */
    public void showExceptionMessage(Exception e, String message) {
        GuiUtils.log(e);
        showMessage(message, "异常", JOptionPane.ERROR_MESSAGE);
    }

    /**
     * 加载配置异常输出.
     *
     * @param filePath 配置文件路径
     * @param e        异常
     */
    public void logLoadPropertiesException(String filePath, Exception e) {
        GuiUtils.log("加载配置\"" + filePath + "\"出错！", e);
    }

}


<|start_filename|>src/main/java/bs/tool/commongui/utils/ExcelUtils.java<|end_filename|>
package bs.tool.commongui.utils;


import jxl.format.*;
import jxl.write.WritableCellFormat;
import jxl.write.WritableFont;
import jxl.write.WriteException;

public class ExcelUtils {

    public static WritableCellFormat getHeaderCellFormat() throws WriteException {
        return getCellFormat(Alignment.CENTRE, Colour.GRAY_25);
    }

    public static WritableCellFormat getBodyCellFormat() throws WriteException {
        return getCellFormat(Alignment.LEFT, Colour.WHITE);
    }

    private static WritableCellFormat getCellFormat(Alignment alignment, Colour colour) throws WriteException {
        WritableFont arial10font = new WritableFont(WritableFont.ARIAL, 10);
        WritableCellFormat arial10format = new WritableCellFormat(arial10font);
        arial10format.setAlignment(alignment);
        arial10format.setVerticalAlignment(VerticalAlignment.CENTRE);
        arial10format.setBackground(colour);
        arial10format.setWrap(true);
        arial10format.setBorder(Border.ALL, BorderLineStyle.THIN, Colour.BLACK);
        return arial10format;
    }

}


<|start_filename|>src/main/java/bs/tool/commongui/plugins/more/JODConverterVisual.java<|end_filename|>
package bs.tool.commongui.plugins.more;

import bs.tool.commongui.AbstractGuiJPanel;
import bs.tool.commongui.GuiUtils;
import bs.tool.commongui.utils.FileUtils;
import bs.tool.commongui.utils.SearchFileNameParams;
import bs.tool.commongui.utils.SimpleMouseListener;
import bs.util.io.PropertiesUtils;
import com.artofsolving.jodconverter.DocumentConverter;
import com.artofsolving.jodconverter.openoffice.connection.OpenOfficeConnection;
import com.artofsolving.jodconverter.openoffice.connection.SocketOpenOfficeConnection;
import com.artofsolving.jodconverter.openoffice.converter.OpenOfficeDocumentConverter;

import javax.swing.*;
import javax.swing.text.NumberFormatter;
import java.awt.*;
import java.awt.event.*;
import java.io.File;
import java.io.IOException;
import java.net.ConnectException;
import java.util.List;
import java.util.*;

/**
 * OpenOffice文档转换.
 */
public class JODConverterVisual extends AbstractGuiJPanel {

    private static final long serialVersionUID = 1L;

    /**
     * 转换文件/文件夹路径表单.
     */
    private JTextField convertPathTextField = new JTextField();
    /**
     * 转换文件/文件夹路径选择.
     */
    private JFileChooser convertPathChooser = new JFileChooser();

    /**
     * 转换按钮.
     */
    private JButton convertButton;

    /**
     * 转换目标文件夹路径表单.
     */
    private JTextField convertedPathTextField = new JTextField();
    /**
     * 转换目标文件夹路径选择.
     */
    private JFileChooser convertedPathChooser = new JFileChooser();

    /**
     * 转换文件格式.
     */
    private String fileFromType = "";
    /**
     * 转换文件格式.
     */
    private String fileToType = "";

    /**
     * 高级条件面板.
     */
    private JPanel advanceConditionPanel;

    /**
     * 设置IP表单.
     */
    private JTextField ipTextField = new JTextField("127.0.0.1", 8);
    /**
     * 设置端口表单.
     */
    private JFormattedTextField portTextField = createNumberTextField(new NumberFormatter(), 5, GuiUtils.font14_cn,
            "8100");

    /**
     * 文件名包含字符表单.
     */
    private JTextField fileNameContainsTextField;
    /**
     * 文件名不包含字符表单.
     */
    private JTextField fileNameNotContainsTextField;
    /**
     * 文件名是否支持正则.
     */
    private boolean fileNameSupportRegex;

    /**
     * 转换结果文件域.
     */
    private JTextArea resultTextArea = createJTextArea(GuiUtils.font14_un);

    /**
     * 可进行转换的文件格式.
     */
    private String fromComboboxItems = "";
    /**
     * 转换文件格式Map.
     */
    private Map<String, String> formatsFromMap = new HashMap<String, String>();
    /**
     * 目标文件格式Map.
     */
    private Map<String, String> formatsToMap = new HashMap<String, String>();

    /**
     * 目标文件名是否保留原始文件类型.
     */
    private boolean keepOriginalFileType = false;

    /**
     * 把需要转换的文件当作txt处理表单.
     */
    private JCheckBox filesDealAsTxtBox;

    {
        // 转换配置
        String converterPropsFile = "conf/JODConverterVisual/converter.properties";
        try {
            Properties converterConfProperties = PropertiesUtils.getProperties(GuiUtils
                    .getActualPath(converterPropsFile));
            fromComboboxItems = converterConfProperties.getProperty("fromComboboxItems").trim().toLowerCase();
            for (Object propObj : converterConfProperties.keySet()) {
                String propStr = propObj.toString();
                if (propStr.endsWith(".formats.from")) {
                    formatsFromMap.put(propStr, "," + converterConfProperties.getProperty(propStr).trim().toLowerCase()
                            + ",");
                } else if (propStr.endsWith(".formats.to")) {
                    formatsToMap.put(propStr, "," + converterConfProperties.getProperty(propStr).trim().toLowerCase()
                            + ",");
                }
            }
        } catch (IOException e) {
            logLoadPropertiesException(converterPropsFile, e);
        }
    }

    public JODConverterVisual() {

        // 主面板：边界布局，分North、Center两部分，North用于放置输入及条件控件，Center是放置转换结果输出
        setLayout(new BorderLayout());

        // 输入及条件Panel
        JPanel inputPanel = new JPanel(new GridLayout(2, 1));
        add(inputPanel, BorderLayout.NORTH);

        // 转换from格式->to格式
        final DefaultComboBoxModel fileToBoxModel = new DefaultComboBoxModel();
        resetFileToBoxModel(fileToBoxModel, fromComboboxItems.split(",")[0]);
        final JComboBox fileFromBox = createJComboBox(fromComboboxItems.split(","), GuiUtils.font13_cn,
                new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent event) {
                        fileFromType = ((JComboBox) event.getSource()).getSelectedItem().toString();
                        resetFileToBoxModel(fileToBoxModel, fileFromType);
                    }
                });
        fileFromType = fileFromBox.getSelectedItem().toString();
        final JComboBox fileToBox = new JComboBox(fileToBoxModel);
        fileToBox.setFont(GuiUtils.font12_cn);
        fileToBox.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                if (((JComboBox) event.getSource()).getSelectedItem() != null) {
                    fileToType = ((JComboBox) event.getSource()).getSelectedItem().toString();
                }
            }
        });
        fileToType = fileToBox.getSelectedItem().toString();

        // 转换文件/文件夹表单、转换按钮Panel
        JPanel fileChooAndDetectPanel = new JPanel(new BorderLayout());
        // 转换文件/文件夹表单
        addJLabel(fileChooAndDetectPanel, "  文件/文件夹: ", GuiUtils.font14_cn, BorderLayout.WEST);
        JPanel fileChooPanel = new JPanel(new BorderLayout());
        fileChooPanel.add(new JPanel(), BorderLayout.NORTH);
        convertPathTextField.setEditable(false);
        convertPathTextField.addMouseListener(new SimpleMouseListener() {
            @Override
            public void mouseReleased(MouseEvent e) {
                chooseConvertFile(fileFromBox);
            }
        });
        addJTextField(fileChooPanel, convertPathTextField, GuiUtils.font14_un, BorderLayout.CENTER);
        fileChooPanel.add(new JPanel(), BorderLayout.SOUTH);
        fileChooAndDetectPanel.add(fileChooPanel, BorderLayout.CENTER);

        JPanel buttonFlowPanel = new JPanel(new FlowLayout(FlowLayout.LEADING));
        addJButton(buttonFlowPanel, "浏览", "", GuiUtils.font12_cn, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                chooseConvertFile(fileFromBox);
            }
        });
        // 转换按钮
        convertButton = createJButton("转换", "", GuiUtils.font14b_cn);
        convertButton.addMouseListener(new SimpleMouseListener() {
            @Override
            public void mouseReleased(MouseEvent event) {
                File convertFile = new File(convertPathTextField.getText().trim());
                Map<String, Object> paramsMap = new HashMap<String, Object>();
                paramsMap.put("fileNameContainsText", fileNameContainsTextField.getText().trim());
                paramsMap.put("fileNameNotContainsText", fileNameNotContainsTextField.getText().trim());
                paramsMap.put("fileNameSupportRegex", fileNameSupportRegex);
                convertButton.setEnabled(false);

                try {
                    List<File> files = new ArrayList<File>();
                    if (!convertFile.isDirectory()) {
                        files.add(convertFile);
                    } else {
                        FileUtils.loopDirectory(convertFile, files, new SearchFileNameParams(paramsMap));
                    }
                    boolean filesDealAsTxt = filesDealAsTxtBox.isSelected();
                    appendConvertResult(resultTextArea, files, filesDealAsTxt);
                } catch (IOException e) {
                    showExceptionMessage(e);
                }
                convertButton.setEnabled(true);
            }

            @Override
            public void mousePressed(MouseEvent e) {
                File convertFile = new File(convertPathTextField.getText().trim());
                if (!convertFile.exists()) {
                    showMessage("转换文件/文件夹不存在！", "警告", JOptionPane.WARNING_MESSAGE);
                    return;
                }
                String convertedPath = convertedPathTextField.getText().trim();
                if (convertedPath.length() == 0) {
                    showMessage("转换目标文件夹未选择！", "警告", JOptionPane.WARNING_MESSAGE);
                    return;
                }
                resultTextArea.setText("开始转换，请稍候......\n\n");
            }
        });

        // 转换文件格式 -> 目标文件格式
        addJLabel(buttonFlowPanel, " 格式:", GuiUtils.font14_cn);
        buttonFlowPanel.add(fileFromBox);
        addJLabel(buttonFlowPanel, "->", GuiUtils.font14_cn);
        buttonFlowPanel.add(fileToBox);

        buttonFlowPanel.add(convertButton);

        // 转换路径选择控件，可选择文件/文件夹
        convertPathChooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
        fileChooAndDetectPanel.add(buttonFlowPanel, BorderLayout.EAST);
        inputPanel.add(fileChooAndDetectPanel);

        // 转换目标文件夹表单面板
        JPanel convertPanel = new JPanel(new BorderLayout());
        // 转换目标文件夹表单
        addJLabel(convertPanel, "  目标文件夹:  ", GuiUtils.font14_cn, BorderLayout.WEST);
        JPanel convertFileChooPanel = new JPanel(new BorderLayout());
        convertFileChooPanel.add(new JPanel(), BorderLayout.NORTH);
        convertedPathTextField.setEditable(false);
        convertedPathTextField.addMouseListener(new SimpleMouseListener() {
            @Override
            public void mouseReleased(MouseEvent e) {
                chooseConvertedFolder();
            }
        });
        addJTextField(convertFileChooPanel, convertedPathTextField, GuiUtils.font14_un, BorderLayout.CENTER);
        convertFileChooPanel.add(new JPanel(), BorderLayout.SOUTH);
        convertPanel.add(convertFileChooPanel, BorderLayout.CENTER);
        JPanel comboxButtonFlowPanel = new JPanel(new FlowLayout(FlowLayout.LEADING));

        addJButton(comboxButtonFlowPanel, "浏览", "", GuiUtils.font12_cn, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                chooseConvertedFolder();
            }
        });
        // 目标路径选择控件，只可选择文件夹
        convertedPathChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);

        // 目标文件名是否保留原始文件类型.
        addJCheckBox(comboxButtonFlowPanel, "文件名中保留原类型", false, GuiUtils.font14_cn, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                keepOriginalFileType = ((JCheckBox) event.getSource()).isSelected();
            }
        });

        // 是否作为文本处理
        filesDealAsTxtBox = new JCheckBox("作为文本处理(全部) ");
        filesDealAsTxtBox.setFont(GuiUtils.font14_cn);
        filesDealAsTxtBox.setSelected(false);
        filesDealAsTxtBox.addItemListener(new ItemListener() {
            @Override
            public void itemStateChanged(ItemEvent event) {
                JCheckBox checkBox = (JCheckBox) event.getSource();
                if (checkBox.isSelected()) {
                    fileFromBox.setSelectedItem("txt");
                    fileFromBox.setEnabled(false);
                } else {
                    fileFromBox.setEnabled(true);
                    File selectFile = convertPathChooser.getSelectedFile();
                    if (!selectFile.isDirectory()) {
                        String fileType = FileUtils.getFileType(selectFile.getName());
                        if (("," + fromComboboxItems + ",").contains("," + fileType + ",")) {
                            fileFromBox.setSelectedItem(fileType);
                            fileFromBox.setEnabled(false);
                        }
                    }
                }
            }
        });
        filesDealAsTxtBox.setEnabled(false);
        comboxButtonFlowPanel.add(filesDealAsTxtBox);

        // 展开/收缩高级(条件)按钮
        addJButton(comboxButtonFlowPanel, "高级", "", GuiUtils.font14_cn, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                advanceConditionPanel.setVisible(!advanceConditionPanel.isVisible());
                getContextPanel().revalidate();
            }
        });
        convertPanel.add(comboxButtonFlowPanel, BorderLayout.EAST);
        inputPanel.add(convertPanel);

        // 高级(条件)及转换日志输出面板，使用边界布局，North为高级(条件)，Center为转换日志输出
        JPanel advanceAndLogPanel = new JPanel(new BorderLayout());
        // 高级(条件)
        advanceConditionPanel = new JPanel(new FlowLayout(FlowLayout.LEADING));
        advanceConditionPanel.setVisible(false);
        advanceAndLogPanel.add(advanceConditionPanel, BorderLayout.NORTH);

        // 设置IP
        addJLabel(advanceConditionPanel, " IP:", GuiUtils.font14_cn);
        advanceConditionPanel.add(ipTextField);
        // 设置端口
        addJLabel(advanceConditionPanel, "端口:", GuiUtils.font14_cn);
        advanceConditionPanel.add(portTextField);

        // 文件名包含(不包含)字符
        addJLabel(advanceConditionPanel, " 文件名包含字符:", GuiUtils.font14_cn);
        fileNameContainsTextField = new JTextField(14);
        addJTextField(advanceConditionPanel, fileNameContainsTextField, GuiUtils.font14_un);
        addJLabel(advanceConditionPanel, " 文件名不包含字符:", GuiUtils.font14_cn);
        fileNameNotContainsTextField = new JTextField(14);
        addJTextField(advanceConditionPanel, fileNameNotContainsTextField, GuiUtils.font14_un);
        // 是否支持正则
        addJCheckBox(advanceConditionPanel, "支持正则", false, GuiUtils.font14_cn, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                fileNameSupportRegex = ((JCheckBox) event.getSource()).isSelected();
            }
        });

        // 转换结果输出
        JPanel convertLogPanel = new JPanel(new BorderLayout());
        convertLogPanel.add(new JScrollPane(resultTextArea));
        advanceAndLogPanel.add(convertLogPanel, BorderLayout.CENTER);

        add(advanceAndLogPanel, BorderLayout.CENTER);
    }

    /**
     * 根据文件格式获取可转换的文件格式.
     *
     * @param type 原格式
     * @return <code>String</code> 获取可转换的文件格式
     */
    private String getConvertAbleTypes(String type) {
        if (type == null || type.trim().length() == 0) {
            return "";
        }
        for (String formatFrom : formatsFromMap.keySet()) {
            if (formatsFromMap.get(formatFrom).contains("," + type + ",")) {
                return formatsToMap.get(formatFrom.subSequence(0, formatFrom.length() - 4) + "to");
            }
        }
        return "";
    }

    /**
     * 重设FileToBoxModel，实现联动.
     *
     * @param fileToBoxModel JCombobox Model
     * @param fromType       转换文件格式
     */
    private void resetFileToBoxModel(DefaultComboBoxModel fileToBoxModel, String fromType) {
        String ableTypes = getConvertAbleTypes(fromType);
        fileToBoxModel.removeAllElements();
        for (String type : ableTypes.split(",")) {
            type = type.trim();
            if (type.length() != 0 && !type.equals(fromType)) {
                fileToBoxModel.addElement(type);
            }
        }
    }

    /**
     * append转换文件结果.
     */
    private void appendConvertResult(JTextArea resultTextArea, List<File> files, boolean filesDealAsTxt)
            throws IOException {
        OpenOfficeConnection con = new SocketOpenOfficeConnection(ipTextField.getText().trim(),
                Integer.parseInt(portTextField.getText().trim().replace(",", "")));
        try {
            con.connect();
        } catch (ConnectException e) {
            resultTextArea
                    .append("转换出错，请检查OpenOffice服务是否启动或者点开高级按钮查看IP、端口设置是否正确\n"
                            + "本机OpenOffice服务启动方法：CMD下，OpenOffice_HOME/program>soffice -headless -accept=\"socket,host=127.0.0.1,port=8100;urp;\" -nofirststartwizard\n\n");
            GuiUtils.log(e);
            return;
        }
        DocumentConverter converter = new OpenOfficeDocumentConverter(con);

        int convertNum = 0;
        int convertedNum = 0;

        String convertPath = convertPathTextField.getText().trim();
        File convertFile = new File(convertPath);
        String convertedPath = convertedPathTextField.getText().trim();
        // 转换文件
        if (!convertFile.isDirectory()) {
            String convertFileName = convertFile.getName();
            String fileType = FileUtils.getFileType(convertFileName);
            convertFileName = (keepOriginalFileType ? convertFileName + "." : convertFileName.substring(0,
                    convertFileName.length() - FileUtils.getFileType(convertFileName).length())
                    + (fileType.length() == 0 ? "." : ""));
            File convertedFile = new File(convertedPath + File.separator + convertFileName + fileToType);
            convertNum++;
            String success = convertFile(convertFile, convertedFile, converter, resultTextArea, filesDealAsTxt);
            if ("Success".equals(success)) {
                convertedNum++;
            }
            resultTextArea.append(convertFile.getAbsolutePath() + "         Convert: " + success + "\n");
        }
        // 转换文件夹下的文件
        else {
            for (File file : files) {
                String fileType = FileUtils.getFileType(file.getName());
                if (fileType.equals(fileFromType) || filesDealAsTxt) {
                    convertNum++;
                    String fileName = file.getName();
                    fileName = (keepOriginalFileType ? fileName + "." : fileName.substring(0, fileName.length()
                            - fileType.length())
                            + (fileType.length() == 0 ? "." : ""));
                    String success = convertFile(file,
                            new File(convertedPath + File.separator + fileName + fileToType), converter,
                            resultTextArea, filesDealAsTxt);
                    if ("Success".equals(success)) {
                        convertedNum++;
                    }
                    resultTextArea.append(file.getAbsolutePath() + "         Convert: " + success + "\n");
                }
            }
        }
        con.disconnect();

        resultTextArea.append("\nConvert files: " + convertNum + ", Success: " + convertedNum + ", Fail: "
                + (convertNum - convertedNum) + ".");
    }

    /**
     * 转换文件.
     */
    private String convertFile(File fromFile, File toFile, DocumentConverter converter, JTextArea resultTextArea,
                               boolean filesDealAsTxt) {
        try {
            if (filesDealAsTxt) {
                File copyToFile = new File(toFile.getAbsolutePath() + ".convert-as-txt-tmp.txt");
                org.apache.commons.io.FileUtils.copyFile(fromFile, copyToFile);
                converter.convert(copyToFile, toFile);
                copyToFile.delete();
            } else {
                converter.convert(fromFile, toFile);
            }
            return "Success";
        } catch (Exception e) {
            GuiUtils.log(e);
            return "Fail[Exception: " + e.getMessage() + "]";
        }
    }

    /**
     * 选择转换文件/文件夹.
     */
    private void chooseConvertFile(JComboBox fileFromBox) {
        if (convertPathChooser.showDialog(getContextPanel(), "确定") == JFileChooser.CANCEL_OPTION) {
            return;
        }
        File selectFile = convertPathChooser.getSelectedFile();
        if (selectFile != null) {
            boolean flag = true;
            if (!selectFile.exists()) {
                flag = false;
                showMessage("所选文件/文件夹不存在！", "警告", JOptionPane.WARNING_MESSAGE);
            } else if (selectFile.isDirectory()) {
                fileFromBox.setEnabled(true);
                filesDealAsTxtBox.setEnabled(true);
            } else if (!selectFile.isDirectory()) {
                String fileType = FileUtils.getFileType(selectFile.getName());
                if (("," + fromComboboxItems + ",").contains("," + fileType + ",")) {
                    fileFromBox.setSelectedItem(fileType);
                    fileFromBox.setEnabled(false);
                    filesDealAsTxtBox.setSelected(false);
                    filesDealAsTxtBox.setEnabled(true);
                } else {
                    int dealAsTxt = showConfirmMessage("不支持转换此种格式的文件，是否作为文本处理！", "确认", JOptionPane.OK_CANCEL_OPTION);
                    if (dealAsTxt == JOptionPane.OK_OPTION) {
                        fileFromBox.setEnabled(false);
                        filesDealAsTxtBox.setSelected(true);
                        filesDealAsTxtBox.setEnabled(false);
                    } else {
                        flag = false;
                    }
                }
            }
            if (flag) {
                convertPathTextField.setText(selectFile.getAbsolutePath());
            }
        }
    }

    /**
     * 选择目标文件夹.
     */
    private void chooseConvertedFolder() {
        if (convertedPathChooser.showDialog(getContextPanel(), "确定") == JFileChooser.CANCEL_OPTION) {
            return;
        }
        File selectFile = convertedPathChooser.getSelectedFile();
        if (selectFile != null) {
            if (!selectFile.exists()) {
                showMessage("转换目标文件夹不存在将自动创建！", "提示", JOptionPane.INFORMATION_MESSAGE);
                try {
                    org.apache.commons.io.FileUtils.forceMkdir(selectFile);
                } catch (IOException e) {
                    showExceptionMessage(e, "创建转换目标文件夹失败！");
                    return;
                }
            }
            convertedPathTextField.setText(selectFile.getAbsolutePath());
        }
    }

}


<|start_filename|>src/main/java/bs/tool/commongui/plugins/AntScriptAutoBuild.java<|end_filename|>
package bs.tool.commongui.plugins;

import bs.tool.commongui.AbstractGuiJPanel;
import bs.tool.commongui.GuiUtils;
import bs.tool.commongui.utils.SimpleMouseListener;
import bs.tool.eclipse.ProjectPropertiesDeal;
import bs.tool.eclipse.ProjectPropertiesDealInterface;
import bs.util.io.PropertiesUtils;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.Properties;

/**
 * Auto Build Ant构建脚本.
 */
public class AntScriptAutoBuild extends AbstractGuiJPanel {

    private static final long serialVersionUID = 1L;

    /**
     * Project路径表单.
     */
    private JTextField projectPathTextField = new JTextField();
    /**
     * Project路径选择.
     */
    private JFileChooser projectPathChooser = new JFileChooser();

    /**
     * 是否备份已存在的同名构建属性脚本及脚本.
     */
    private boolean isBak = true;

    /**
     * Ant 任务.
     */
    private String taskNames = "";

    /**
     * 运行日志输出文本域.
     */
    private JTextArea runLogTextArea = createJTextArea(GuiUtils.font14_un);

    public AntScriptAutoBuild() {

        // 主面板：边界布局，分North、Center两部分，North用于放置条件控件，Center放置运行日志输出
        setLayout(new BorderLayout());

        // 输入条件/操作
        JPanel inputPanel = new JPanel(new BorderLayout());
        // Project路径选择/填写
        JPanel fileChooPanel = new JPanel(new BorderLayout());
        addJLabel(fileChooPanel, "  Project路径: ", GuiUtils.font14_cn, BorderLayout.WEST);
        JPanel pathPanel = new JPanel(new BorderLayout());
        pathPanel.add(new JPanel(), BorderLayout.NORTH);
        addJTextField(pathPanel, projectPathTextField, GuiUtils.font14_un, BorderLayout.CENTER);
        pathPanel.add(new JPanel(), BorderLayout.SOUTH);
        fileChooPanel.add(pathPanel, BorderLayout.CENTER);
        JPanel buttonFlowPanel = new JPanel(new FlowLayout(FlowLayout.LEADING));
        addJButton(buttonFlowPanel, "浏览", "", GuiUtils.font12_cn,
                buttonBrowseListener(projectPathChooser, projectPathTextField));
        addJCheckBox(buttonFlowPanel, "备份已存在脚本", true, GuiUtils.font12_cn, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                isBak = ((JCheckBox) event.getSource()).isSelected();
            }
        });
        // 按钮
        addJButton(buttonFlowPanel, "Build", "", GuiUtils.font14b_cn, new SimpleMouseListener() {
            @Override
            public void mouseReleased(MouseEvent event) {
                String projectPath = projectPathTextField.getText().trim();
                if (!new File(projectPath).exists()) {
                    showMessage("Project路径不存在！", "警告", JOptionPane.WARNING_MESSAGE);
                    return;
                }
                try {
                    bs.tool.ant.AntScriptAutoBuild build = new bs.tool.ant.AntScriptAutoBuild();
                    build.setIsBak(isBak);
                    ProjectPropertiesDealInterface propertiesDeal = new ProjectPropertiesDeal();
                    if (!propertiesDeal.isJavaOrJavaWebEclipseProject(projectPath)) {
                        runLogTextArea.append("Error: The Path \'" + projectPath
                                + "\' not has a Eclipse Java Project, Dynamic Web Project or MyEclipse Web Project.");
                        return;
                    }
                    propertiesDeal.setRunLogTextArea(runLogTextArea);
                    propertiesDeal.deal(projectPath);
                    build.setRunLogTextArea(runLogTextArea);
                    build.autoBuild(projectPath, propertiesDeal, taskNames);
                } catch (IOException e) {
                    showExceptionMessage(e);
                } catch (URISyntaxException e) {
                    showExceptionMessage(e);
                }
            }

            @Override
            public void mousePressed(MouseEvent e) {
                runLogTextArea.setText("");
            }
        });
        // Project路径选择控件，仅可选择文件夹
        projectPathChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
        fileChooPanel.add(buttonFlowPanel, BorderLayout.EAST);
        inputPanel.add(fileChooPanel, BorderLayout.NORTH);

        int gridRow = 5;
        int gridCol = 6;
        // Ant Task任务JPanel
        JPanel taskGridPanel = new JPanel(new GridLayout(gridRow, gridCol));
        String antTaskPropsFile = "conf/AntScriptAutoBuild/conf.properties";
        try {
            Properties taskConfProperties = PropertiesUtils.getProperties(GuiUtils.getActualPath(antTaskPropsFile));

            String baseTask = taskConfProperties.getProperty("baseTask").trim();
            String javaTask = taskConfProperties.getProperty("javaTask").trim();
            String javaWebTask = taskConfProperties.getProperty("javaWebTask").trim();
            String generalTask = taskConfProperties.getProperty("generalTask").trim();
            String specialTask = taskConfProperties.getProperty("specialTask").trim();

            addTaskCheckBox(taskGridPanel, "  Java Base", baseTask + "," + javaTask, baseTask, gridCol);
            addTaskCheckBox(taskGridPanel, "  Java Web ", javaWebTask, baseTask, gridCol);
            addTaskCheckBox(taskGridPanel, "  General  ", generalTask, baseTask, gridCol);
            addTaskCheckBox(taskGridPanel, "  Special  ", specialTask, baseTask, gridCol);
        } catch (IOException e) {
            logLoadPropertiesException(antTaskPropsFile, e);
        }
        inputPanel.add(taskGridPanel, BorderLayout.CENTER);

        add(inputPanel, BorderLayout.NORTH);

        add(new JScrollPane(runLogTextArea), BorderLayout.CENTER);
    }

    /**
     * 添加Ant Task JCheckBox选择框.
     */
    private void addTaskCheckBox(JPanel taskGridPanel, String labelName, String taskNames, String baseTask, int gridCol) {
        addJLabel(taskGridPanel, labelName + " 任务：", GuiUtils.font14_cn);
        String[] tasks = taskNames.split(",");
        int row = tasks.length / (gridCol - 1);
        row = row * (gridCol - 1) == tasks.length ? row : row + 1;
        int num = row * gridCol - 1;

        for (int i = 0; i < num; i++) {
            String title = "";
            if (i < tasks.length + i / (gridCol - 1) && !(i % (gridCol - 1) == 0 && i != 0)) {
                title = tasks[i - i / (gridCol - 1)];
            }
            if ("".equals(title)) {
                addJLabel(taskGridPanel, title, GuiUtils.font14_cn);
            } else {
                boolean isSelected = false;
                if (("," + baseTask + ",").contains("," + title + ",") || "jar".equals(title)) {
                    isSelected = true;
                }
                JCheckBox taskBox = createJCheckBox(title, isSelected, GuiUtils.font16, new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent event) {
                        JCheckBox checkBox = (JCheckBox) event.getSource();
                        setTaskNames(checkBox.getText(), checkBox.isSelected());
                    }
                });
                if (isSelected) {
                    if ("base".equals(title)) {
                        taskBox.setEnabled(false);
                    }
                    this.taskNames += "," + title;
                }
                taskGridPanel.add(taskBox);
            }
        }

    }

    /**
     * 设置被选择的Ant任务.
     */
    private void setTaskNames(String taskName, boolean add) {
        if (("," + this.taskNames + ",").contains("," + taskName + ",")) {
            if (!add) {
                this.taskNames = ("," + this.taskNames + ",").replace("," + taskName + ",", "");
            }
        } else {
            if (add) {
                this.taskNames = this.taskNames + "," + taskName;
            }
        }
        if (this.taskNames.startsWith(",")) {
            this.taskNames = this.taskNames.substring(1);
        }
    }

}


<|start_filename|>src/main/java/bs/tool/commongui/plugins/ColorTool.java<|end_filename|>
package bs.tool.commongui.plugins;

import bs.tool.commongui.AbstractGuiJPanel;
import bs.tool.commongui.GuiUtils;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

/**
 * 颜色工具.
 */
public class ColorTool extends AbstractGuiJPanel {

    private static final long serialVersionUID = 1L;

    /**
     * R表单.
     */
    private JTextField rTextField = new JTextField(3);
    /**
     * G表单.
     */
    private JTextField gTextField = new JTextField(3);
    /**
     * B表单.
     */
    private JTextField bTextField = new JTextField(3);

    /**
     * HTML表单.
     */
    private JTextField htmlTextField = new JTextField(6);

    public ColorTool() {

        // 主面板：边界布局，分North、Center两部分，North用于放置输入及条件控件，Center是放置空面面板
        setLayout(new BorderLayout());

        // 输入及条件Panel
        JPanel colorPanel = new JPanel(new BorderLayout());

        JPanel flowPanel = new JPanel(new FlowLayout());

        addJLabel(flowPanel, "  R:", GuiUtils.font13);
        flowPanel.add(rTextField);
        addJLabel(flowPanel, "G:", GuiUtils.font13);
        flowPanel.add(gTextField);
        addJLabel(flowPanel, "B:", GuiUtils.font13);
        flowPanel.add(bTextField);
        addJLabel(flowPanel, "  HTML: ", GuiUtils.font13);

        addJButton(flowPanel, "<-", "", GuiUtils.font14b_cn, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                Color color = getHTMLFillColor();
                if (color != null) {
                    rTextField.setText(Integer.toString(color.getRed()));
                    gTextField.setText(Integer.toString(color.getGreen()));
                    bTextField.setText(Integer.toString(color.getBlue()));
                } else {
                    showMessage("未输入正确的颜色值！", "警告", JOptionPane.WARNING_MESSAGE);
                }
            }
        });

        addJButton(flowPanel, "->", "", GuiUtils.font14b_cn, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                Color color = getRGBFillColor();
                if (color != null) {
                    htmlTextField.setText("#" + hex(color.getRed()) + hex(color.getGreen()) + hex(color.getBlue()));
                } else {
                    showMessage("未输入正确的颜色值！", "警告", JOptionPane.WARNING_MESSAGE);
                }
            }
        });

        flowPanel.add(htmlTextField);
        colorPanel.add(flowPanel, BorderLayout.CENTER);
        colorPanel.add(new JPanel(), BorderLayout.EAST);

        JButton paletteButton = createJButton("显示调色板", "", GuiUtils.font14_cn);
        paletteButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                Color fillColor = getRGBFillColor();
                fillColor = fillColor == null ? getHTMLFillColor() : fillColor;
                Color chooseColor = fillColor == null ? Color.black : fillColor;
                Color color = JColorChooser.showDialog(ColorTool.this, "调色板", chooseColor);
                if (color != null) {
                    int r = color.getRed();
                    int g = color.getGreen();
                    int b = color.getBlue();
                    rTextField.setText(Integer.toString(r));
                    gTextField.setText(Integer.toString(g));
                    bTextField.setText(Integer.toString(b));
                    htmlTextField.setText("#" + hex(r) + hex(g) + hex(b));
                }
            }
        });
        flowPanel.add(paletteButton);

        add(colorPanel, BorderLayout.NORTH);

        // 空面面板
        JPanel resultPanel = new JPanel(new BorderLayout());
        // 空白文本域
        JTextArea resultTextArea = createJTextArea(GuiUtils.font14_un);
        resultTextArea.setEditable(false);
        resultTextArea.setText("说明：" + "\n1，R、G、B的范围0-255" + "\n2，HTML颜色码可识别3位或6位，且可带或不带井号#，不区分大小写"
                + "\n3，调色板颜色选择说明，如果RGB有正确的值则选择其值，否则选择HTML颜色码正确的值，若两者都无正确的值，则选择黑色");
        resultPanel.add(new JScrollPane(resultTextArea));
        add(resultPanel, BorderLayout.CENTER);
    }

    /**
     * 将0-255转为双位数字的16进制.
     */
    private String hex(int code) {
        String hex = Integer.toHexString(code);
        return hex.length() == 1 ? "0" + hex : hex;
    }

    /**
     * 获取RGB表单设置的颜色.
     */
    private Color getRGBFillColor() {
        Color color = null;
        String r = rTextField.getText().trim();
        String g = gTextField.getText().trim();
        String b = bTextField.getText().trim();
        try {
            if (!"".equals(r) && !"".equals(g) && !"".equals(b)) {
                color = new Color(Integer.parseInt(r), Integer.parseInt(g), Integer.parseInt(b));
            }
        } catch (Exception e) {
            GuiUtils.log(e);
        }
        return color;
    }

    /**
     * 获取HTML颜色表单设置的颜色.
     */
    private Color getHTMLFillColor() {
        Color color = null;
        String r = "";
        String g = "";
        String b = "";
        String html = htmlTextField.getText().trim();
        try {
            if (!"".equals(html)) {
                if (html.startsWith("#")) {
                    html = html.substring(1);
                }
                if (html.length() == 3) {
                    r = html.substring(0, 1) + html.substring(0, 1);
                    g = html.substring(1, 2) + html.substring(1, 2);
                    b = html.substring(2, 3) + html.substring(2, 3);
                } else if (html.length() == 6) {
                    r = html.substring(0, 2);
                    g = html.substring(2, 4);
                    b = html.substring(4, 6);
                }
                color = new Color(Integer.parseInt(r, 16), Integer.parseInt(g, 16), Integer.parseInt(b, 16));
            }
        } catch (Exception e) {
            GuiUtils.log(e);
        }
        return color;
    }

}


<|start_filename|>src/main/java/bs/tool/commongui/utils/interest/LoanInterestBean.java<|end_filename|>
package bs.tool.commongui.utils.interest;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.List;

public class LoanInterestBean {

    /**
     * 贷款总额.
     */
    private BigDecimal loanTotalAmount;
    /**
     * 贷款利率.
     */
    private BigDecimal lendRate;
    /**
     * 贷款月利率.
     */
    private BigDecimal lendMonthRate;
    /**
     * 以分期方式计算的名义年化利率.
     */
    private BigDecimal periodNominallyYearRate;
    /**
     * 贷款年限.
     */
    private int lendYear;
    /**
     * 贷款月数.
     */
    private int lendMonth;
    /**
     * 计息方式.
     */
    private LendType lendType;

    /**
     * 本息合计.
     */
    private BigDecimal loanTotalPrincipalAndInterest;
    /**
     * 利息合计.
     */
    private BigDecimal interestTotalAmount;
    /**
     * 每月还款.
     */
    private List<MonthlyLendAmount> monthRepayments;
    /**
     * 每月还款递减金额.
     */
    private BigDecimal monthDecrease = new BigDecimal(0);

    public LoanInterestBean(int loanTotal, String rate, int year, LendType type) {
        loanTotalAmount = new BigDecimal(loanTotal);
        lendRate = new BigDecimal(rate);
        lendMonthRate = lendRate.divide(new BigDecimal(12 * 100), InterestUtils.SCALE, RoundingMode.HALF_UP);
        lendYear = year;
        lendMonth = year * 12;
        lendType = type;
    }

    public LoanInterestBean(LoanInterestBean bean) {
        loanTotalAmount = bean.getLoanTotalAmount();
        lendRate = bean.getLendRate();
        lendMonthRate = bean.getLendMonthRate();
        lendYear = bean.getLendYear();
        lendMonth = bean.getLendMonth();
        lendType = bean.getLendType();
    }

    public BigDecimal getLoanTotalAmount() {
        return loanTotalAmount;
    }

    public void setLoanTotalAmount(BigDecimal loanTotalAmount) {
        this.loanTotalAmount = loanTotalAmount;
    }

    public BigDecimal getLendRate() {
        return lendRate;
    }

    public void setLendRate(BigDecimal lendRate) {
        this.lendRate = lendRate;
    }

    public BigDecimal getLendMonthRate() {
        return lendMonthRate;
    }

    public void setLendMonthRate(BigDecimal lendMonthRate) {
        this.lendMonthRate = lendMonthRate;
    }

    public BigDecimal getPeriodNominallyYearRate() {
        return periodNominallyYearRate;
    }

    public void setPeriodNominallyYearRate(BigDecimal periodNominallyYearRate) {
        this.periodNominallyYearRate = periodNominallyYearRate;
    }

    public int getLendYear() {
        return lendYear;
    }

    public void setLendYear(int lendYear) {
        this.lendYear = lendYear;
    }

    public int getLendMonth() {
        return lendMonth;
    }

    public void setLendMonth(int lendMonth) {
        this.lendMonth = lendMonth;
    }

    public LendType getLendType() {
        return lendType;
    }

    public void setLendType(LendType lendType) {
        this.lendType = lendType;
    }

    public BigDecimal getLoanTotalPrincipalAndInterest() {
        return loanTotalPrincipalAndInterest;
    }

    public void setLoanTotalPrincipalAndInterest(BigDecimal loanTotalPrincipalAndInterest) {
        this.loanTotalPrincipalAndInterest = loanTotalPrincipalAndInterest;
    }

    public BigDecimal getInterestTotalAmount() {
        return interestTotalAmount;
    }

    public void setInterestTotalAmount(BigDecimal interestTotalAmount) {
        this.interestTotalAmount = interestTotalAmount;
    }

    public List<MonthlyLendAmount> getMonthRepayments() {
        return monthRepayments;
    }

    public void setMonthRepayments(List<MonthlyLendAmount> monthRepayments) {
        this.monthRepayments = monthRepayments;
    }

    public BigDecimal getMonthDecrease() {
        return monthDecrease;
    }

    public void setMonthDecrease(BigDecimal monthDecrease) {
        this.monthDecrease = monthDecrease;
    }

    @Override
    public String toString() {
        return "LoanInterestBean{" +
                "loanTotalAmount=" + loanTotalAmount +
                ", lendRate=" + lendRate +
                ", lendMonthRate=" + LoanInterestUtils.decimalFormat(lendMonthRate, "0.00000") +
                ", lendYear=" + lendYear +
                ", lendMonth=" + lendMonth +
                ", lendType=" + lendType +
                ", loanTotalPrincipalAndInterest=" + LoanInterestUtils.decimalFormat(loanTotalPrincipalAndInterest) +
                ", interestTotalAmount=" + LoanInterestUtils.decimalFormat(interestTotalAmount) +
                ", monthDecrease=" + monthDecrease +
                ", monthRepayments=" + monthRepayments.toString() +
                '}';
    }

    /**
     * 等额本金与等额本息对比.
     */
    public static class ComparePrincipalInterest {

        /**
         * 等额本金还款情况.
         */
        private LoanInterestBean principalBean;
        /**
         * 等额本息还款情况.
         */
        private LoanInterestBean interestBean;
        /**
         * 节省利息.
         */
        private BigDecimal savedInterest;
        /**
         * 首次等额本金还款额小于等额本息还款额的月数.
         */
        private int firstLessThanMonth;
        /**
         * 截止firstLessThanMonth等额本金相比等额本息累计多还金额.
         */
        private BigDecimal moreLentBefore;
        /**
         * 自firstLessThanMonth后等额本金相比等额本息累计少还金额.
         */
        private BigDecimal lessLentBefore;
        /**
         * 复利年华利率.
         */
        private String compoundRate = "0.00";
        /**
         * 截止firstLessThanMonth等额本金相比等额本息累计多还金额的复利总额.
         */
        private BigDecimal firstLessThanMonthCompoundInterest;
        /**
         * 截止firstLessThanMonth等额本金相比等额本息累计多还金额的复利总额再次累计余下的还款日期里的复利总额.
         */
        private BigDecimal firstLessThanMonthCompoundTwiceInterest;
        /**
         * 自firstLessThanMonth等额本金相比等额本息累计少还金额的复利总额.
         */
        private BigDecimal principalFirstLessThanMonthCompoundInterest;

        public ComparePrincipalInterest(LoanInterestBean principalBean, LoanInterestBean interestBean, String compoundRate) {
            this.principalBean = principalBean;
            this.interestBean = interestBean;
            this.compoundRate = compoundRate;
        }

        public ComparePrincipalInterest(ComparePrincipalInterest compare) {
            principalBean = compare.getPrincipalBean();
            interestBean = compare.getInterestBean();
            compoundRate = compare.getCompoundRate();
        }

        public LoanInterestBean getPrincipalBean() {
            return principalBean;
        }

        public void setPrincipalBean(LoanInterestBean principalBean) {
            this.principalBean = principalBean;
        }

        public LoanInterestBean getInterestBean() {
            return interestBean;
        }

        public void setInterestBean(LoanInterestBean interestBean) {
            this.interestBean = interestBean;
        }

        public BigDecimal getSavedInterest() {
            return savedInterest;
        }

        public void setSavedInterest(BigDecimal savedInterest) {
            this.savedInterest = savedInterest;
        }

        public int getFirstLessThanMonth() {
            return firstLessThanMonth;
        }

        public void setFirstLessThanMonth(int firstLessThanMonth) {
            this.firstLessThanMonth = firstLessThanMonth;
        }

        public BigDecimal getMoreLentBefore() {
            return moreLentBefore;
        }

        public void setMoreLentBefore(BigDecimal moreLentBefore) {
            this.moreLentBefore = moreLentBefore;
        }

        public BigDecimal getLessLentBefore() {
            return lessLentBefore;
        }

        public void setLessLentBefore(BigDecimal lessLentBefore) {
            this.lessLentBefore = lessLentBefore;
        }

        public String getCompoundRate() {
            return compoundRate;
        }

        public void setCompoundRate(String compoundRate) {
            this.compoundRate = compoundRate;
        }

        public BigDecimal getFirstLessThanMonthCompoundInterest() {
            return firstLessThanMonthCompoundInterest;
        }

        public void setFirstLessThanMonthCompoundInterest(BigDecimal firstLessThanMonthCompoundInterest) {
            this.firstLessThanMonthCompoundInterest = firstLessThanMonthCompoundInterest;
        }

        public BigDecimal getFirstLessThanMonthCompoundTwiceInterest() {
            return firstLessThanMonthCompoundTwiceInterest;
        }

        public void setFirstLessThanMonthCompoundTwiceInterest(BigDecimal firstLessThanMonthCompoundTwiceInterest) {
            this.firstLessThanMonthCompoundTwiceInterest = firstLessThanMonthCompoundTwiceInterest;
        }

        public BigDecimal getPrincipalFirstLessThanMonthCompoundInterest() {
            return principalFirstLessThanMonthCompoundInterest;
        }

        public void setPrincipalFirstLessThanMonthCompoundInterest(BigDecimal principalFirstLessThanMonthCompoundInterest) {
            this.principalFirstLessThanMonthCompoundInterest = principalFirstLessThanMonthCompoundInterest;
        }
    }

    /**
     * 提前还款.
     */
    public static class LendAheadOfSchedule {
        /**
         * 提前前的还款Bean.
         */
        private LoanInterestBean originBean;
        /**
         * 提前还款年数.
         */
        private int year;
        /**
         * 缩短年数.
         */
        private int yearReduce;
        /**
         * 提前还款额.
         */
        private int amount;
        /**
         * 计息方式.
         */
        private LendType lendType;
        /**
         * 贷款利率.
         */
        private String rate;
        /**
         * 已还本金.
         */
        private BigDecimal lentPrincipal;
        /**
         * 已还利息.
         */
        private BigDecimal lentInterest;
        /**
         * 节省利息.
         */
        private BigDecimal savedInterest;
        /**
         * 提前后的还款Bean.
         */
        private LoanInterestBean newBean;

        public LendAheadOfSchedule(LoanInterestBean originBean, int year, int amount, LendType lendType, String rate, int yearReduce) {
            this.originBean = originBean;
            this.year = year;
            this.amount = amount;
            this.lendType = lendType;
            this.rate = rate;
            this.yearReduce = yearReduce;
        }

        public LendAheadOfSchedule(LendAheadOfSchedule ahead) {
            originBean = ahead.getOriginBean();
            year = ahead.getYear();
            amount = ahead.getAmount();
            lendType = ahead.getLendType();
            rate = ahead.getRate();
            yearReduce = ahead.getYearReduce();
        }

        public LoanInterestBean getOriginBean() {
            return originBean;
        }

        public void setOriginBean(LoanInterestBean originBean) {
            this.originBean = originBean;
        }

        public int getYear() {
            return year;
        }

        public void setYear(int year) {
            this.year = year;
        }

        public int getYearReduce() {
            return yearReduce;
        }

        public void setYearReduce(int yearReduce) {
            this.yearReduce = yearReduce;
        }

        public int getAmount() {
            return amount;
        }

        public void setAmount(int amount) {
            this.amount = amount;
        }

        public LendType getLendType() {
            return lendType;
        }

        public void setLendType(LendType lendType) {
            this.lendType = lendType;
        }

        public String getRate() {
            return rate;
        }

        public void setRate(String rate) {
            this.rate = rate;
        }

        public BigDecimal getLentPrincipal() {
            return lentPrincipal;
        }

        public void setLentPrincipal(BigDecimal lentPrincipal) {
            this.lentPrincipal = lentPrincipal;
        }

        public BigDecimal getLentInterest() {
            return lentInterest;
        }

        public void setLentInterest(BigDecimal lentInterest) {
            this.lentInterest = lentInterest;
        }

        public BigDecimal getSavedInterest() {
            return savedInterest;
        }

        public void setSavedInterest(BigDecimal savedInterest) {
            this.savedInterest = savedInterest;
        }

        public LoanInterestBean getNewBean() {
            return newBean;
        }

        public void setNewBean(LoanInterestBean newBean) {
            this.newBean = newBean;
        }
    }

    public static class MonthlyLendAmount {
        /**
         * 期数.
         */
        private int month;
        /**
         * 金额.
         */
        private BigDecimal amount;
        /**
         * 本金.
         */
        private BigDecimal principal;
        /**
         * 利息.
         */
        private BigDecimal interest;
        /**
         * 本金占比.
         */
        private double principalProportion;
        /**
         * 利息占比.
         */
        private double interestProportion;
        /**
         * 已还本金.
         */
        private BigDecimal principalTotal;
        /**
         * 已还利息.
         */
        private BigDecimal interestTotal;
        /**
         * 已还本金占比总本金.
         */
        private double principalProportionTotal;
        /**
         * 已还利息占比总利息.
         */
        private double interestProportionTotal;

        public int getMonth() {
            return month;
        }

        public void setMonth(int month) {
            this.month = month;
        }

        public BigDecimal getAmount() {
            return amount;
        }

        public void setAmount(BigDecimal amount) {
            this.amount = amount;
        }

        public BigDecimal getPrincipal() {
            return principal;
        }

        public void setPrincipal(BigDecimal principal) {
            this.principal = principal;
        }

        public BigDecimal getInterest() {
            return interest;
        }

        public void setInterest(BigDecimal interest) {
            this.interest = interest;
        }

        public double getPrincipalProportion() {
            return principalProportion;
        }

        public void setPrincipalProportion(double principalProportion) {
            this.principalProportion = principalProportion;
        }

        public double getInterestProportion() {
            return interestProportion;
        }

        public void setInterestProportion(double interestProportion) {
            this.interestProportion = interestProportion;
        }

        public BigDecimal getPrincipalTotal() {
            return principalTotal;
        }

        public void setPrincipalTotal(BigDecimal principalTotal) {
            this.principalTotal = principalTotal;
        }

        public BigDecimal getInterestTotal() {
            return interestTotal;
        }

        public void setInterestTotal(BigDecimal interestTotal) {
            this.interestTotal = interestTotal;
        }

        public double getPrincipalProportionTotal() {
            return principalProportionTotal;
        }

        public void setPrincipalProportionTotal(double principalProportionTotal) {
            this.principalProportionTotal = principalProportionTotal;
        }

        public double getInterestProportionTotal() {
            return interestProportionTotal;
        }

        public void setInterestProportionTotal(double interestProportionTotal) {
            this.interestProportionTotal = interestProportionTotal;
        }

        public MonthlyLendAmount() {
        }

        public MonthlyLendAmount(int month, BigDecimal principal, BigDecimal interest, BigDecimal principalTotal, BigDecimal interestTotal, LoanInterestBean bean) {
            this.month = month;
            this.amount = principal.add(interest);
            this.principal = principal;
            this.interest = interest;
            this.principalProportion = Double.parseDouble(LoanInterestUtils.decimalFormat(principal.divide(this.amount, InterestUtils.SCALE, RoundingMode.HALF_UP), "0.00000"));
            this.interestProportion = Double.parseDouble(LoanInterestUtils.decimalFormat(interest.divide(this.amount, InterestUtils.SCALE, RoundingMode.HALF_UP), "0.00000"));
            this.principalTotal = principalTotal;
            this.interestTotal = interestTotal;
            this.principalProportionTotal = Double.parseDouble(LoanInterestUtils.decimalFormat(principalTotal.divide(bean.getLoanTotalAmount(), InterestUtils.SCALE, RoundingMode.HALF_UP), "0.00000"));
            this.interestProportionTotal = Double.parseDouble(LoanInterestUtils.decimalFormat(interestTotal.divide(bean.getInterestTotalAmount(), InterestUtils.SCALE, RoundingMode.HALF_UP), "0.00000"));
        }

        public String toString2() {
            return "MonthlyLendAmount{" +
                    "month=" + month +
                    ", amount=" + LoanInterestUtils.decimalFormat(amount) +
                    ", principal=" + LoanInterestUtils.decimalFormat(principal) +
                    ", interest=" + LoanInterestUtils.decimalFormat(interest) +
                    ", principalProportion=" + principalProportion +
                    ", interestProportion=" + interestProportion +
                    ", principalTotal=" + LoanInterestUtils.decimalFormat(principalTotal) +
                    ", interestTotal=" + LoanInterestUtils.decimalFormat(interestTotal) +
                    ", principalProportionTotal=" + principalProportionTotal +
                    ", interestProportionTotal=" + interestProportionTotal +
                    '}';
        }

        @Override
        public String toString() {
            return month +
                    "\t" + LoanInterestUtils.decimalFormat(amount) +
                    "\t" + LoanInterestUtils.decimalFormat(principal) +
                    "\t" + LoanInterestUtils.decimalFormat(interest) +
                    "\t" + principalProportion +
                    "\t" + interestProportion +
                    "\t" + LoanInterestUtils.decimalFormat(principalTotal) +
                    "\t" + LoanInterestUtils.decimalFormat(interestTotal) +
                    "\t" + principalProportionTotal +
                    "\t" + interestProportionTotal;
        }
    }
}

<|start_filename|>src/main/java/bs/tool/commongui/plugins/CharacterConverter.java<|end_filename|>
package bs.tool.commongui.plugins;

import bs.tool.commongui.AbstractGuiJPanel;
import bs.tool.commongui.GuiUtils;
import bs.tool.commongui.utils.RadixUtils;
import bs.util.common.CodecUtil;
import org.apache.commons.codec.DecoderException;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.UnsupportedEncodingException;

/**
 * 编码转换.
 */
public class CharacterConverter extends AbstractGuiJPanel {

    private static final long serialVersionUID = 1L;

    /**
     * Encode TextField.
     */
    private JTextField encodeTextField = new JTextField("");
    /**
     * Encode JButton.
     */
    private JButton encodeButton = createJButton("Encode", Integer.toString(0), GuiUtils.font16);

    /**
     * 2进制.
     */
    private String codeType2Radix = "二进制";
    /**
     * 8进制.
     */
    private String codeType8Radix = "八进制";
    /**
     * 10进制.
     */
    private String codeType10Radix = "十进制";
    /**
     * 16进制.
     */
    private String codeType16Radix = "十六进制";

    /**
     * 乱码解码.
     */
    private String codeTypeDecode = "乱码解码";

    /**
     * 编解码类别.
     */
    private String[] codeTypes = new String[]{codeType2Radix, codeType8Radix, codeType10Radix, codeType16Radix,
            codeTypeDecode};

    /**
     * 当前编码类别.
     */
    private String curCodeType = codeType16Radix;

    /**
     * 进制前缀符.
     */
    private String[] prefixs = new String[]{"\\u", "\\x", "%", "-", "空格", "空"};

    /**
     * 进制前缀符下拉框.
     */
    private JComboBox prefixsBox = createJComboBox(prefixs, GuiUtils.font14_cn, new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent event) {
            curPrefix = getCorrectSeparator(((JComboBox) event.getSource()).getSelectedItem().toString());
        }
    });

    /**
     * 当前进制前缀符.
     */
    private String curPrefix = getCorrectSeparator(prefixs[0]);

    /**
     * 获取正确的前缀符.
     */
    private String getCorrectSeparator(String separ) {
        if ("空格".equals(separ)) {
            return " ";
        } else if ("空".equals(separ)) {
            return "";
        } else {
            return separ;
        }
    }

    /**
     * 进制编码大小写.
     */
    private String[] lowUpCase = new String[]{"小写", "大写"};

    /**
     * 进制编码大小写下拉框.
     */
    private JComboBox lowUpCaseBox = createJComboBox(lowUpCase, GuiUtils.font14_cn, new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent event) {
            curLowUpCase = ((JComboBox) event.getSource()).getSelectedItem().toString();
        }
    });

    /**
     * 当前进制编码大小写.
     */
    private String curLowUpCase = lowUpCase[0];

    /**
     * 分隔字节数.
     */
    private String[] splitByte = new String[]{"1", "2"};

    /**
     * 当前分隔字节数.
     */
    private int curSplitByte = Integer.parseInt(splitByte[0]);

    /**
     * 字符集.
     */
    private String[] charsets = new String[]{GuiUtils.CHARSET_UTF_16BE, GuiUtils.CHARSET_UTF_16LE,
            GuiUtils.CHARSET_UTF_8, GuiUtils.CHARSET_UTF_16, GuiUtils.CHARSET_GB2312, GuiUtils.CHARSET_GBK,
            GuiUtils.CHARSET_GB18030, GuiUtils.CHARSET_Big5, GuiUtils.CHARSET_ISO_8859_1, ""};

    /**
     * 手动输入的字符编码框.
     */
    private JTextField customCharsetField = new JTextField("UTF-8");

    /**
     * TextField数组，与字符集对应.
     */
    private JTextField[] fields = new JTextField[charsets.length];

    {
        for (int i = 0; i < fields.length; i++) {
            fields[i] = new JTextField();
        }
    }

    /**
     * Converter Encode、Decode行数.
     */
    private int rows = fields.length + 2;

    public CharacterConverter() {

        // 主面板：边界布局，分North、Center两部分，North用于放置控件，Center是一个空面板
        setLayout(new BorderLayout());
        // Center空面板
        add(new JPanel(), BorderLayout.CENTER);
        // North面板
        JPanel topPanel = new JPanel(new BorderLayout());
        add(topPanel, BorderLayout.NORTH);
        // 左侧：放置Label
        JPanel labelsPanel = new JPanel(new GridLayout(rows, 1));
        topPanel.add(labelsPanel, BorderLayout.WEST);
        // 中间：放置输入输出框
        JPanel fieldsPanel = new JPanel(new GridLayout(rows, 1));
        topPanel.add(fieldsPanel, BorderLayout.CENTER);
        // 右侧：放置按钮
        JPanel buttonsPanel = new JPanel(new GridLayout(rows, 1));
        topPanel.add(buttonsPanel, BorderLayout.EAST);

        // 左侧：放置Label
        addJLabel(labelsPanel, " Encode String: ", GuiUtils.font16);
        // 仅作填充
        addJLabel(labelsPanel, " ", GuiUtils.font16);
        for (int i = 0; i < fields.length - 1; i++) {
            addJLabel(labelsPanel, " " + charsets[i] + ": ", GuiUtils.font16);
        }
        // 手动输入的字符编码框
        addJTextField(labelsPanel, customCharsetField, GuiUtils.font14_un);

        // 中间：放置输入输出框
        addJTextField(fieldsPanel, encodeTextField, GuiUtils.font14_un);
        JPanel comboxPanel = new JPanel(new BorderLayout());
        fieldsPanel.add(comboxPanel);
        for (int i = 0; i < fields.length; i++) {
            addJTextField(fieldsPanel, fields[i], GuiUtils.font14_un);
        }
        // 下拉框面板
        JPanel flowComboxPanel = new JPanel(new FlowLayout());
        comboxPanel.add(flowComboxPanel, BorderLayout.WEST);
        comboxPanel.add(new JPanel(), BorderLayout.CENTER);
        addJLabel(flowComboxPanel, "编码类别:", GuiUtils.font14_cn);
        // 编码类别下拉框，默认选择16进制
        addJComboBox(flowComboxPanel, codeTypes, 3, GuiUtils.font14_cn, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                curCodeType = ((JComboBox) event.getSource()).getSelectedItem().toString();
                if (codeTypeDecode.equals(curCodeType)) {
                    encodeTextField.setEnabled(false);
                    encodeButton.setEnabled(false);
                    prefixsBox.setEnabled(false);
                    lowUpCaseBox.setEnabled(false);
                } else {
                    encodeTextField.setEnabled(true);
                    encodeButton.setEnabled(true);
                    prefixsBox.setEnabled(true);
                    lowUpCaseBox.setEnabled(true);
                }
            }
        });
        addJLabel(flowComboxPanel, " 进制前缀符:", GuiUtils.font14_cn);
        // 进制前缀符下拉框
        prefixsBox.setEditable(true);
        flowComboxPanel.add(prefixsBox);
        addJLabel(flowComboxPanel, " 进制大小写:", GuiUtils.font14_cn);
        // 进制编码大小写下拉框
        flowComboxPanel.add(lowUpCaseBox);
        addJLabel(flowComboxPanel, " 分隔字节数:", GuiUtils.font14_cn);
        // 分隔字节数下拉框
        flowComboxPanel.add(createJComboBox(splitByte, GuiUtils.font14_cn, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                curSplitByte = Integer.parseInt(((JComboBox) event.getSource()).getSelectedItem().toString());
            }
        }));
        addJLabel(flowComboxPanel, "   ", GuiUtils.font14_cn);
        addJButton(flowComboxPanel, "清除输入输出", "clear", GuiUtils.font14_cn, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                clearTextFields();
            }
        });

        // 右侧：放置按钮
        encodeButton.addActionListener(getButtonActionListener(0));
        buttonsPanel.add(encodeButton);
        // 仅作填充
        addJLabel(buttonsPanel, " ", GuiUtils.font16);
        for (int i = 0; i < fields.length; i++) {
            addJButton(buttonsPanel, "Decode", Integer.toString(i + 1), GuiUtils.font16, getButtonActionListener(i + 1));
        }
    }

    /**
     * 清除输入输出.
     */
    private void clearTextFields() {
        encodeTextField.setText("");
        for (int i = 0; i < fields.length; i++) {
            fields[i].setText("");
        }
    }

    /**
     * 按钮点击事件.
     */
    private ActionListener getButtonActionListener(int current) {
        return new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                // 手动输入的字符编码
                charsets[charsets.length - 1] = customCharsetField.getText().trim();
                int sort = Integer.parseInt(((JButton) event.getSource()).getName());
                if (sort == 0) {
                    String input = encodeTextField.getText();
                    clearTextFields();
                    encodeTextField.setText(input);
                    if (input.length() == 0) {
                        return;
                    }
                    for (int i = 0; i < fields.length; i++) {
                        try {
                            if (codeType16Radix.equals(curCodeType)) {
                                fields[i].setText(encode16RadixAddPrefix(input, charsets[i], curPrefix));
                            } else if (codeType10Radix.equals(curCodeType)) {
                                fields[i].setText(encode10RadixAddPrefix(input, charsets[i], curPrefix));
                            } else if (codeType8Radix.equals(curCodeType)) {
                                fields[i].setText(encode8RadixAddPrefix(input, charsets[i], curPrefix));
                            } else if (codeType2Radix.equals(curCodeType)) {
                                fields[i].setText(encode2RadixAddPrefix(input, charsets[i], curPrefix));
                            }
                        } catch (Exception e) {
                            showExceptionMessage(e);
                            return;
                        }
                    }
                } else {
                    String input = fields[sort - 1].getText();
                    clearTextFields();
                    fields[sort - 1].setText(input);
                    if (input.length() == 0) {
                        return;
                    }
                    try {
                        String decodeString = convertRadixAndDecodeHex(input, charsets[sort - 1]);
                        encodeTextField.setText(decodeString);
                        if (codeTypeDecode.equals(curCodeType)) {
                            encodeTextField.setText("\"" + codeTypeDecode + "\"与Encode String无关！");
                        }
                        for (int i = 0; i < fields.length; i++) {
                            if (i != sort - 1) {
                                if (codeType16Radix.equals(curCodeType)) {
                                    fields[i].setText(encode16RadixAddPrefix(decodeString, charsets[i], curPrefix));
                                } else if (codeType10Radix.equals(curCodeType)) {
                                    fields[i].setText(encode10RadixAddPrefix(decodeString, charsets[i], curPrefix));
                                } else if (codeType8Radix.equals(curCodeType)) {
                                    fields[i].setText(encode8RadixAddPrefix(decodeString, charsets[i], curPrefix));
                                } else if (codeType2Radix.equals(curCodeType)) {
                                    fields[i].setText(encode2RadixAddPrefix(decodeString, charsets[i], curPrefix));
                                } else if (codeTypeDecode.equals(curCodeType)) {
                                    fields[i].setText(GuiUtils.encode(input, charsets[sort - 1], charsets[i]));
                                }
                            }
                        }
                    } catch (Exception e) {
                        showExceptionMessage(e);
                        return;
                    }

                }
                // 大小写
                if (codeType16Radix.equals(curCodeType) || codeType2Radix.equals(curCodeType)) {
                    if (lowUpCase[0].equals(curLowUpCase)) {
                        for (int fi = 0; fi < fields.length; fi++) {
                            fields[fi].setText(fields[fi].getText().toLowerCase());
                        }
                    } else if (lowUpCase[1].equals(curLowUpCase)) {
                        for (int fi = 0; fi < fields.length; fi++) {
                            fields[fi].setText(fields[fi].getText().toUpperCase());
                        }
                    }
                }
            }
        };
    }

    /**
     * 转换为16进制字符并decode.
     */
    private String convertRadixAndDecodeHex(String input, String charset) throws UnsupportedEncodingException, DecoderException {
        if (input == null || input.length() == 0) {
            return "";
        }
        if (curPrefix.length() == 0) {
            return CodecUtil.decodeHex(input.toString(), charset);
        }
        StringBuilder sb = new StringBuilder();
        String spliter = (curPrefix.startsWith("\\") ? ("\\") : "") + curPrefix;
        String[] splits = input.split(spliter);
        StringBuilder radix16Sb = new StringBuilder();
        sb.append(splits[0]);
        for (int i = 1; i < splits.length; i++) {
            String split = splits[i];
            String[] radix16Arr = new String[]{"", ""};
            if (codeType16Radix.equals(curCodeType)) {
                radix16Arr = radix16AndNoneRadix16Str(split, 2);
            } else if (codeType10Radix.equals(curCodeType) || codeType8Radix.equals(curCodeType)) {
                radix16Arr = radix16AndNoneRadix16Str(split, 3);
            } else if (codeType2Radix.equals(curCodeType)) {
                radix16Arr = radix16AndNoneRadix16Str(split, 8);
            }
            radix16Sb.append(radix16Arr[0]);
            if (radix16Arr[1].length() != 0) {
                sb.append(CodecUtil.decodeHex(radix16Sb.toString(), charset)).append(radix16Arr[1]);
                radix16Sb = new StringBuilder();
            }
        }
        sb.append(CodecUtil.decodeHex(radix16Sb.toString(), charset));
        return sb.toString();
    }

    /**
     * 取单split字符中编码和非编码的两部分.
     */
    private String[] radix16AndNoneRadix16Str(String split, int subLen) {
        String radix16Str = "";
        String noneRadix16Str = "";
        subLen = subLen * curSplitByte;
        if (split.length() >= subLen) {
            radix16Str = split.substring(0, subLen);
            if (codeType10Radix.equals(curCodeType)) {
                radix16Str = RadixUtils.convertRadixString10To16(radix16Str);
            } else if (codeType8Radix.equals(curCodeType)) {
                radix16Str = RadixUtils.convertRadixString8To16(radix16Str);
            } else if (codeType2Radix.equals(curCodeType)) {
                radix16Str = RadixUtils.convertRadixString2To16(radix16Str);
            }
            noneRadix16Str = split.substring(subLen);
        } else {
            noneRadix16Str = split;
        }
        return new String[]{radix16Str, noneRadix16Str};
    }

    /**
     * 字符编码进制前缀字符填充 - 16进制.
     */
    private String encode16RadixAddPrefix(String input, String charset, String prefix)
            throws UnsupportedEncodingException {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < input.length(); i++) {
            // CodecUtil.encodeHex 转16进制
            sb.append(appendPrefix(CodecUtil.encodeHex(input.substring(i, i + 1), charset), prefix, 2));
        }
        return sb.toString();
    }

    /**
     * 字符编码进制前缀字符填充 - 10进制.
     */
    private String encode10RadixAddPrefix(String input, String charset, String prefix)
            throws UnsupportedEncodingException {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < input.length(); i++) {
            sb.append(appendPrefix(
                    RadixUtils.convertRadixString16To10(CodecUtil.encodeHex(input.substring(i, i + 1), charset)), prefix, 3));
        }
        return sb.toString();
    }

    /**
     * 字符编码进制前缀字符填充 - 8进制.
     */
    private String encode8RadixAddPrefix(String input, String charset, String prefix)
            throws UnsupportedEncodingException {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < input.length(); i++) {
            sb.append(appendPrefix(
                    RadixUtils.convertRadixString16To8(CodecUtil.encodeHex(input.substring(i, i + 1), charset)), prefix, 3));
        }
        return sb.toString();
    }

    /**
     * 字符编码进制前缀字符填充 - 2进制.
     */
    private String encode2RadixAddPrefix(String input, String charset, String prefix)
            throws UnsupportedEncodingException {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < input.length(); i++) {
            sb.append(appendPrefix(
                    RadixUtils.convertRadixString16To2(CodecUtil.encodeHex(input.substring(i, i + 1), charset)), prefix, 8));
        }
        return sb.toString();
    }

    /**
     * 按单字节补充前缀字符.
     */
    private String appendPrefix(String str, String prefix, int len) {
        StringBuilder sb = new StringBuilder();
        len = len * curSplitByte;
        for (int i = 0; i < str.length() / len; i++) {
            sb.append(prefix).append(str.substring(i * len, (i + 1) * len));
        }
        return sb.toString();
    }

}


<|start_filename|>src/main/java/bs/tool/commongui/utils/EscapeUtils.java<|end_filename|>
package bs.tool.commongui.utils;

import org.apache.commons.lang.StringEscapeUtils;

public class EscapeUtils {

    /**
     * 转义字符.
     *
     * @param string 字符
     * @param type   字符类型
     */
    public static String escape(String string, String type) {
        String unescape = "不支持对" + type + "字符的转义";
        if (type.equals(LanguageUtils.CONST_HTML)) {
            unescape = StringEscapeUtils.escapeHtml(string);
        } else if (type.equals(LanguageUtils.CONST_XML)) {
            unescape = StringEscapeUtils.escapeXml(string);
        } else if (type.equals(LanguageUtils.CONST_SQL)) {
            unescape = StringEscapeUtils.escapeSql(string);
        } else if (type.equals(LanguageUtils.CONST_JAVA)) {
            unescape = StringEscapeUtils.escapeJava(string);
        } else if (type.equals(LanguageUtils.CONST_JAVASCRIPT)) {
            unescape = StringEscapeUtils.escapeJavaScript(string);
        } else if (type.equals(LanguageUtils.CONST_CSV)) {
            unescape = StringEscapeUtils.escapeCsv(string);
        }
        return unescape;
    }

    /**
     * 还原转义字符.
     *
     * @param string 转义字符
     * @param type   字符类型
     */
    public static String unescape(String string, String type) {
        String escape = "转义字符还原遇到错误";
        if (type.equals(LanguageUtils.CONST_HTML)) {
            escape = StringEscapeUtils.unescapeHtml(string);
        } else if (type.equals(LanguageUtils.CONST_XML)) {
            escape = StringEscapeUtils.unescapeXml(string);
        } else if (type.equals(LanguageUtils.CONST_SQL)) {
            escape = type + "转义字符不能进行还原";
        } else if (type.equals(LanguageUtils.CONST_JAVA)) {
            escape = StringEscapeUtils.unescapeJava(string);
        } else if (type.equals(LanguageUtils.CONST_JAVASCRIPT)) {
            escape = StringEscapeUtils.unescapeJavaScript(string);
        } else if (type.equals(LanguageUtils.CONST_CSV)) {
            escape = StringEscapeUtils.unescapeCsv(string);
        }
        return escape;
    }

}


<|start_filename|>src/main/java/bs/tool/commongui/plugins/FileDigitalSignature.java<|end_filename|>
package bs.tool.commongui.plugins;

import bs.tool.commongui.AbstractGuiJPanel;
import bs.tool.commongui.GuiUtils;
import bs.tool.commongui.utils.FileUtils;
import bs.tool.commongui.utils.SearchFileNameParams;
import bs.tool.commongui.utils.SimpleMouseListener;
import org.apache.commons.codec.digest.DigestUtils;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 计算文件数字签名.
 */
public class FileDigitalSignature extends AbstractGuiJPanel {

    private static final long serialVersionUID = 1L;

    /**
     * 文本/文件夹路径表单.
     */
    private JTextField digitalPathTextField = new JTextField();
    /**
     * 文本/文件夹路径选择.
     */
    private JFileChooser digitalPathChooser = new JFileChooser();

    /**
     * 是否计算MD5.
     */
    private boolean ifDigitalMd5 = true;

    /**
     * 是否计算SHA1.
     */
    private boolean ifDigitalSha1 = false;

    /**
     * 计算按钮.
     */
    private JButton digitalButton;

    /**
     * 文件名包含字符表单.
     */
    private JTextField fileNameContainsTextField;
    /**
     * 文件名不包含字符表单.
     */
    private JTextField fileNameNotContainsTextField;
    /**
     * 文件名是否支持正则.
     */
    private boolean fileNameSupportRegex = false;

    /**
     * 计算结果文本域.
     */
    private JTextArea resultTextArea = createJTextArea(GuiUtils.font14_un);

    public FileDigitalSignature() {

        // 主面板：边界布局，分North、Center两部分，North用于放置输入及条件控件，Center是放置计算结果输出
        setLayout(new BorderLayout());

        // 输入及条件Panel
        JPanel inputPanel = new JPanel(new GridLayout(2, 1));

        // 文本/文件夹表单、计算按钮Panel
        JPanel fileChooAndDetectPanel = new JPanel(new BorderLayout());
        // 文本/文件夹表单
        addJLabel(fileChooAndDetectPanel, "  文件/文件夹: ", GuiUtils.font14_cn, BorderLayout.WEST);
        JPanel fileChooPanel = new JPanel(new BorderLayout());
        fileChooPanel.add(new JPanel(), BorderLayout.NORTH);
        addJTextField(fileChooPanel, digitalPathTextField, GuiUtils.font14_un, BorderLayout.CENTER);
        fileChooPanel.add(new JPanel(), BorderLayout.SOUTH);
        fileChooAndDetectPanel.add(fileChooPanel, BorderLayout.CENTER);

        JPanel buttonFlowPanel = new JPanel(new FlowLayout(FlowLayout.LEADING));
        addJButton(buttonFlowPanel, "浏览", "", GuiUtils.font12_cn,
                buttonBrowseListener(digitalPathChooser, digitalPathTextField));
        // 计算按钮
        digitalButton = createJButton("计算", "", GuiUtils.font14b_cn);
        digitalButton.addMouseListener(new SimpleMouseListener() {
            @Override
            public void mouseReleased(MouseEvent event) {
                String path = digitalPathTextField.getText().trim();
                File file = new File(path);
                if (!file.exists()) {
                    showMessage("文件/文件夹不存在！", "警告", JOptionPane.WARNING_MESSAGE);
                    return;
                }
                Map<String, Object> paramsMap = new HashMap<String, Object>();
                paramsMap.put("fileNameContainsText", fileNameContainsTextField.getText().trim());
                paramsMap.put("fileNameNotContainsText", fileNameNotContainsTextField.getText().trim());
                paramsMap.put("fileNameSupportRegex", fileNameSupportRegex);
                digitalButton.setEnabled(false);

                resultTextArea.setText("");
                try {
                    if (!file.isDirectory()) {
                        appendDigitalResult(resultTextArea, file, ifDigitalMd5, ifDigitalSha1);
                    } else {
                        List<File> files = new ArrayList<File>();
                        FileUtils.loopDirectory(file, files, new SearchFileNameParams(paramsMap));
                        for (File fFile : files) {
                            appendDigitalResult(resultTextArea, fFile, ifDigitalMd5, ifDigitalSha1);
                        }
                        resultTextArea.append("\nDigital files: " + files.size());
                    }
                } catch (NumberFormatException e) {
                    showMessage("取样大小必须是正整数！", "警告", JOptionPane.WARNING_MESSAGE);
                } catch (IOException e) {
                    showExceptionMessage(e);
                }
                digitalButton.setEnabled(true);
            }

            @Override
            public void mousePressed(MouseEvent e) {
                resultTextArea.setText("");
            }
        });
        buttonFlowPanel.add(digitalButton);
        // 路径选择控件，可选择文件/文件夹
        digitalPathChooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
        fileChooAndDetectPanel.add(buttonFlowPanel, BorderLayout.EAST);
        inputPanel.add(fileChooAndDetectPanel);

        // 高级条件Panel
        JPanel advancePanel = new JPanel(new FlowLayout(FlowLayout.LEADING));
        addJLabel(advancePanel, " ", GuiUtils.font12_cn);
        // 是否计算MD5
        addJCheckBox(advancePanel, "MD5 ", true, GuiUtils.font14_cn, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                ifDigitalMd5 = ((JCheckBox) event.getSource()).isSelected();
            }
        });
        // 是否计算SHA1
        addJCheckBox(advancePanel, "SHA1 ", false, GuiUtils.font14_cn, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                ifDigitalSha1 = ((JCheckBox) event.getSource()).isSelected();
            }
        });
        // 文件名包含(不包含)字符
        addJLabel(advancePanel, "  文件名包含字符: ", GuiUtils.font14_cn);
        fileNameContainsTextField = new JTextField(16);
        addJTextField(advancePanel, fileNameContainsTextField, GuiUtils.font14_un);
        addJLabel(advancePanel, "  文件名不包含字符: ", GuiUtils.font14_cn);
        fileNameNotContainsTextField = new JTextField(16);
        addJTextField(advancePanel, fileNameNotContainsTextField, GuiUtils.font14_un);
        // 是否支持正则
        addJCheckBox(advancePanel, "支持正则", false, GuiUtils.font14_cn, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                fileNameSupportRegex = ((JCheckBox) event.getSource()).isSelected();
            }
        });
        inputPanel.add(advancePanel);

        add(inputPanel, BorderLayout.NORTH);

        // 计算结果输出
        JPanel detectLogPanel = new JPanel(new BorderLayout());
        detectLogPanel.add(new JScrollPane(resultTextArea));
        add(detectLogPanel, BorderLayout.CENTER);
    }

    /**
     * append计算文件数字签名结果.
     */
    private void appendDigitalResult(JTextArea resultTextArea, File file, boolean ifDigitalMd5, boolean ifDigitalSha1)
            throws IOException {
        resultTextArea.append(file.getAbsolutePath());
        if (ifDigitalMd5) {
            resultTextArea.append("         MD5: " + digitalFileMd5(file));
        }
        if (ifDigitalSha1) {
            resultTextArea.append("         SHA1: " + digitalFileSha1(file));
        }
        resultTextArea.append("\n");
    }

    /**
     * 计算文件数字签名-MD5.
     */
    public static String digitalFileMd5(File file) throws IOException {
        return DigestUtils.md5Hex(new FileInputStream(file));
    }

    /**
     * 计算文件数字签名-SHA1.
     */
    public static String digitalFileSha1(File file) throws IOException {
        return DigestUtils.shaHex(new FileInputStream(file));
    }

}


<|start_filename|>src/main/java/bs/tool/commongui/plugins/more/ZHConverterVisual.java<|end_filename|>
package bs.tool.commongui.plugins.more;

import bs.tool.commongui.AbstractGuiJPanel;
import bs.tool.commongui.GuiUtils;
import bs.tool.commongui.utils.SimpleMouseListener;
import com.spreada.utils.chinese.ZHConverter;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;

/**
 * 中文简繁体转换.
 */
public class ZHConverterVisual extends AbstractGuiJPanel {

    private static final long serialVersionUID = 1L;

    /**
     * 简体文本域.
     */
    private JTextArea simplifiedTextArea = createJTextArea(GuiUtils.font14_un);
    /**
     * 繁体文本域.
     */
    private JTextArea traditionalTextArea = createJTextArea(GuiUtils.font14_un);

    public ZHConverterVisual() {
        // 边界布局
        setLayout(new BorderLayout());
        // Center，文字及转换结果输入输出域，使用2行1列的Grid布局，使其平均显示
        JPanel textAreaPanel = new JPanel(new GridLayout(2, 1));
        add(textAreaPanel, BorderLayout.CENTER);

        JPanel scriptPanel = new JPanel(new BorderLayout());
        addJLabel(scriptPanel, " 简体文本: ", GuiUtils.font14b_cn, BorderLayout.WEST);
        scriptPanel.add(new JScrollPane(simplifiedTextArea), BorderLayout.CENTER);
        textAreaPanel.add(scriptPanel);

        JPanel resultPanel = new JPanel(new BorderLayout());
        addJLabel(resultPanel, " 繁体文本: ", GuiUtils.font14b_cn, BorderLayout.WEST);
        resultPanel.add(new JScrollPane(traditionalTextArea), BorderLayout.CENTER);
        textAreaPanel.add(resultPanel);

        // East，操作区域，使用BorderLayout布局
        JPanel actionPanel = new JPanel(new BorderLayout());
        add(actionPanel, BorderLayout.EAST);

        // 填充
        actionPanel.add(new JPanel(), BorderLayout.CENTER);

        // 放置按钮
        JPanel buttonPanel = new JPanel(new GridLayout(7, 1));
        actionPanel.add(buttonPanel, BorderLayout.SOUTH);
        // 简->繁
        addJButton(buttonPanel, " 简 -> 繁 ", "", GuiUtils.font14b_cn, new SimpleMouseListener() {
            @Override
            public void mouseReleased(MouseEvent event) {
                String input = simplifiedTextArea.getText();
                traditionalTextArea.append(ZHConverter.convert(input, ZHConverter.TRADITIONAL));
            }

            @Override
            public void mousePressed(MouseEvent e) {
                traditionalTextArea.setText("");
            }
        });
        // 仅做填充
        buttonPanel.add(new JPanel());
        // 繁->简
        addJButton(buttonPanel, " 繁 -> 简 ", "", GuiUtils.font14b_cn, new SimpleMouseListener() {
            @Override
            public void mouseReleased(MouseEvent event) {
                String input = traditionalTextArea.getText();
                simplifiedTextArea.append(ZHConverter.convert(input, ZHConverter.SIMPLIFIED));
            }

            @Override
            public void mousePressed(MouseEvent e) {
                simplifiedTextArea.setText("");
            }
        });
        // 仅做填充
        buttonPanel.add(new JPanel());
        // 全部清空
        addJButton(buttonPanel, " 全部清空 ", "", GuiUtils.font14_cn, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                simplifiedTextArea.setText("");
                traditionalTextArea.setText("");
            }
        });
    }

}


<|start_filename|>src/main/java/bs/tool/commongui/plugins/more/PasswordGenerator.java<|end_filename|>
package bs.tool.commongui.plugins.more;

import bs.tool.commongui.AbstractGuiJPanel;
import bs.tool.commongui.GuiUtils;
import bs.tool.commongui.utils.SimpleMouseListener;
import org.passay.CharacterRule;
import org.passay.EnglishCharacterData;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;

/**
 * 随机密码生成.
 */
public class PasswordGenerator extends AbstractGuiJPanel {

    private static final long serialVersionUID = 1L;

    /**
     * 密码文本域.
     */
    private JTextArea passwordTextArea = createJTextArea(GuiUtils.font14_un);
    /**
     * 规则校验文本域.
     */
    private JTextArea ruleCheckTextArea = createJTextArea(GuiUtils.font14_un);

    /**
     * 常见密码组合.
     */
    private String[] passwordCombinations = new String[]{GuiUtils.PASSWORD_COMBINATION_8_CHAR_NUM_SIMPLE,
            GuiUtils.PASSWORD_COMBINATION_8_CHAR_NUM_POPULAR};
    private String curPasswordCombination = passwordCombinations[0];

    /**
     * 生成数量输入框.
     */
    private JTextField passwordNumberTextField = new JTextField("10", 5);
    /**
     * 数字位数输入框.
     */
    private JTextField numberCharacterRuleTextField = new JTextField("6", 5);
    /**
     * 字母位数输入框.
     */
    private JTextField englishCharacterRuleTextField = new JTextField("2", 5);
    /**
     * 特殊字符位数输入框.
     */
    private JTextField specialCharacterRuleTextField = new JTextField("0", 5);
    /**
     * 大小字母位数输入框.
     */
    private JTextField upperCaseCharacterRuleTextField = new JTextField(5);
    /**
     * 小写字母位数输入框.
     */
    private JTextField lowerCaseCharacterRuleTextField = new JTextField(5);

    public PasswordGenerator() {

        // 边界布局
        setLayout(new BorderLayout());
        // Center，加密解密输入输出域，使用2行1列的Grid布局，使其平均显示
        JPanel textAreaPanel = new JPanel(new GridLayout(2, 1));
        add(textAreaPanel, BorderLayout.CENTER);

        JPanel encrptyPanel = new JPanel(new BorderLayout());
        addJLabel(encrptyPanel, " 密码: ", GuiUtils.font14b_cn, BorderLayout.WEST);
        encrptyPanel.add(new JScrollPane(passwordTextArea), BorderLayout.CENTER);
        textAreaPanel.add(encrptyPanel);

        JPanel decrptyPanel = new JPanel(new BorderLayout());
        addJLabel(decrptyPanel, " 校验: ", GuiUtils.font14b_cn, BorderLayout.WEST);
        decrptyPanel.add(new JScrollPane(ruleCheckTextArea), BorderLayout.CENTER);
        textAreaPanel.add(decrptyPanel);

        // East，操作区域，使用BorderLayout布局
        JPanel actionPanel = new JPanel(new BorderLayout());
        add(actionPanel, BorderLayout.EAST);
        // 放置下拉框、单选框等
        JPanel actionGridPanel = new JPanel(new GridLayout(10, 1));
        actionPanel.add(actionGridPanel, BorderLayout.NORTH);

        // 密码组合下拉框
        JPanel passwordCombinationsPanel = new JPanel(new FlowLayout());
        addJLabel(passwordCombinationsPanel, "密码组合:", GuiUtils.font14b_cn);
        addJComboBox(passwordCombinationsPanel, passwordCombinations, GuiUtils.font13_cn, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                curPasswordCombination = ((JComboBox) event.getSource()).getSelectedItem().toString();
                if (curPasswordCombination.equals(GuiUtils.PASSWORD_COMBINATION_8_CHAR_NUM_SIMPLE)) {
                    numberCharacterRuleTextField.setText("6");
                    englishCharacterRuleTextField.setText("2");
                    specialCharacterRuleTextField.setText("0");
                    upperCaseCharacterRuleTextField.setText("");
                    lowerCaseCharacterRuleTextField.setText("");
                } else if (curPasswordCombination.equals(GuiUtils.PASSWORD_COMBINATION_8_CHAR_NUM_POPULAR)) {
                    numberCharacterRuleTextField.setText("2");
                    englishCharacterRuleTextField.setText("3");
                    specialCharacterRuleTextField.setText("1");
                    upperCaseCharacterRuleTextField.setText("1");
                    lowerCaseCharacterRuleTextField.setText("1");
                }
            }
        });
        actionGridPanel.add(passwordCombinationsPanel);

        JPanel numPanel = new JPanel(new FlowLayout(FlowLayout.LEADING));
        addJLabel(numPanel, "生成数量:", GuiUtils.font14b_cn);
        numPanel.add(passwordNumberTextField);
        actionGridPanel.add(numPanel);

        // 仅作填充
        addJLabel(actionGridPanel, " ", GuiUtils.font13);

        addJLabel(actionGridPanel, " 密码规则:", GuiUtils.font14b_cn);

        // FlowLayout.LEADING，此值指示每一行组件都应该与容器方向的开始边对齐，例如，对于从左到右的方向，则与左边对齐
        JPanel rfPanel = new JPanel(new FlowLayout(FlowLayout.LEADING));
        addJLabel(rfPanel, "   数字位数:", GuiUtils.font13_cn);
        rfPanel.add(numberCharacterRuleTextField);
        actionGridPanel.add(rfPanel);

        JPanel rsPanel = new JPanel(new FlowLayout(FlowLayout.LEADING));
        addJLabel(rsPanel, "   字母位数:", GuiUtils.font13_cn);
        rsPanel.add(englishCharacterRuleTextField);
        actionGridPanel.add(rsPanel);

        JPanel rtPanel = new JPanel(new FlowLayout(FlowLayout.LEADING));
        addJLabel(rtPanel, "   特殊字符:", GuiUtils.font13_cn);
        rtPanel.add(specialCharacterRuleTextField);
        actionGridPanel.add(rtPanel);

        JPanel rfoPanel = new JPanel(new FlowLayout(FlowLayout.LEADING));
        addJLabel(rfoPanel, "   大写字母:", GuiUtils.font13_cn);
        rfoPanel.add(upperCaseCharacterRuleTextField);
        actionGridPanel.add(rfoPanel);

        JPanel rfiPanel = new JPanel(new FlowLayout(FlowLayout.LEADING));
        addJLabel(rfiPanel, "   小写字母:", GuiUtils.font13_cn);
        rfiPanel.add(lowerCaseCharacterRuleTextField);
        actionGridPanel.add(rfiPanel);

        // 仅作填充
        actionPanel.add(new Panel(), BorderLayout.CENTER);

        // 放置生成校验按钮
        JPanel buttonPanel = new JPanel(new GridLayout(3, 1));
        actionPanel.add(buttonPanel, BorderLayout.SOUTH);
        // 生成按钮
        addJButton(buttonPanel, "生成", "", GuiUtils.font14b_cn, new SimpleMouseListener() {
            @Override
            public void mouseReleased(MouseEvent event) {
                try {
                    // https://github.com/vt-middleware/passay/blob/master/src/test/java/org/passay/PasswordGeneratorTest.java
                    List<CharacterRule> rules = new ArrayList<CharacterRule>();
                    int pt = 0;
                    pt += addRuleIfNotNull(numberCharacterRuleTextField.getText(), EnglishCharacterData.Digit, rules);
                    pt += addRuleIfNotNull(englishCharacterRuleTextField.getText(), EnglishCharacterData.Alphabetical, rules);
                    pt += addRuleIfNotNull(specialCharacterRuleTextField.getText(), EnglishCharacterData.Special, rules);
                    pt += addRuleIfNotNull(upperCaseCharacterRuleTextField.getText(), EnglishCharacterData.UpperCase, rules);
                    pt += addRuleIfNotNull(lowerCaseCharacterRuleTextField.getText(), EnglishCharacterData.LowerCase, rules);

                    org.passay.PasswordGenerator generator = new org.passay.PasswordGenerator();
                    int number = Integer.parseInt(passwordNumberTextField.getText().trim());
                    StringBuilder sb = new StringBuilder();
                    for (int i = 0; i < number; i++) {
                        sb.append("\n").append(generator.generatePassword(pt, rules));
                    }
                    passwordTextArea.setText(sb.length() > 0 ? sb.substring(1) : "");
                } catch (Exception e) {
                    showExceptionMessage(e);
                }
            }

            @Override
            public void mousePressed(MouseEvent e) {
                ruleCheckTextArea.setText("");
            }
        });
        // 校验按钮
        addJButton(buttonPanel, "校验", "", GuiUtils.font14b_cn, new SimpleMouseListener() {
            @Override
            public void mouseReleased(MouseEvent event) {
                String input = passwordTextArea.getText();
                try {
                    showMessage("暂未实现！", "提示", JOptionPane.INFORMATION_MESSAGE);
                } catch (Exception e) {
                    showExceptionMessage(e);
                }
            }

            @Override
            public void mousePressed(MouseEvent e) {
                passwordTextArea.setText("");
            }
        });
    }

    private int addRuleIfNotNull(String value, EnglishCharacterData cData, List<CharacterRule> rules) {
        int n = 0;
        String v = GuiUtils.trim(value);
        if (v.length() != 0) {
            n = Integer.parseInt(v);
            if (n > 0) {
                rules.add(new CharacterRule(cData, n));
            } else {
                n = 0;
            }
        }
        return n;
    }
}


<|start_filename|>src/main/java/bs/tool/commongui/utils/MacUtils.java<|end_filename|>
package bs.tool.commongui.utils;

import bs.tool.commongui.GuiUtils;

import javax.swing.*;
import java.awt.*;
import java.util.Map;

public class MacUtils {

    public static void showAboutMessage(final Toolkit kit, final JPanel contextPanel, Image icon, final Map<String, String> propsMap) {
        try {
            com.apple.eawt.Application app = com.apple.eawt.Application.getApplication();
            app.setDockIconImage(icon);
            app.setDockIconBadge("");
            app.setAboutHandler(new com.apple.eawt.AboutHandler() {
                @Override
                public void handleAbout(com.apple.eawt.AppEvent.AboutEvent aboutEvent) {
                    GuiUtils.showAboutMessage(kit, contextPanel, propsMap);
                }
            });
        } catch (Error e) {
            e.printStackTrace();
        }
    }

}


<|start_filename|>src/main/java/bs/util/io/LinkedProperties.java<|end_filename|>
package bs.util.io;

import java.util.*;

/**
 * copyright: https://stackoverflow.com/questions/1312383/pulling-values-from-a-java-properties-file-in-order
 */
public class LinkedProperties extends Properties {
    private final HashSet<Object> keys = new LinkedHashSet<Object>();

    public LinkedProperties() {
    }

    public Iterable<Object> orderedKeys() {
        return Collections.list(keys());
    }

    @Override
    public Enumeration<Object> keys() {
        return Collections.<Object>enumeration(keys);
    }

    @Override
    public Object put(Object key, Object value) {
        keys.add(key);
        return super.put(key, value);
    }
}


<|start_filename|>src/main/java/bs/tool/commongui/GuiImagePanel.java<|end_filename|>
package bs.tool.commongui;

import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

/**
 * How to add an image to a JPanel?
 * https://stackoverflow.com/questions/299495/how-to-add-an-image-to-a-jpanel
 */
public class GuiImagePanel extends JPanel {

    private BufferedImage image;
    private int imageWidth;
    private int imageHeight;

    public GuiImagePanel() {
    }

    public GuiImagePanel(String imagePath) {
        setImage(imagePath);
    }

    public GuiImagePanel(int imageWidth, int imageHeight) {
        setImageWidth(imageWidth);
        setImageHeight(imageHeight);
    }

    public GuiImagePanel(String imagePath, int imageWidth, int imageHeight) {
        this(imageWidth, imageHeight);
        setImage(imagePath);
    }

    private void readImage(String imagePath) {
        try {
            image = ImageIO.read(new File(imagePath));
        } catch (IOException e) {
            GuiUtils.log(e);
        }
    }

    public void setImage(String imagePath) {
        readImage(imagePath);
    }

    public void setImageWidth(int imageWidth) {
        this.imageWidth = imageWidth;
    }

    public void setImageHeight(int imageHeight) {
        this.imageHeight = imageHeight;
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        if (image != null) {
            g.drawImage(image, 0, 0, imageWidth, imageHeight, this);
        }
    }

    public void repaint(String imagePath) {
        setImage(imagePath);
        super.repaint();
    }

}

<|start_filename|>src/main/java/bs/tool/commongui/utils/FileUtils.java<|end_filename|>
package bs.tool.commongui.utils;

import bs.tool.commongui.GuiUtils;
import org.apache.commons.codec.digest.DigestUtils;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public final class FileUtils {

    /**
     * java.io.tmpdir(临时目录).
     */
    public final static String JAVA_IO_TMPDIR = System.getProperty("java.io.tmpdir");

    /**
     * 重复文件查找时用的Map（存储的是所有大小的文件，并非仅是同一个大小大于1的才存储）.
     * <p>
     * <pre>
     * key的格式："文件大小".
     * </pre>
     */
    public static Map<String, List<File>> searchRepeatSizeFilesMap;
    /**
     * 重复文件查找时用的Map（存储的是同一个大小大于1的）.
     * <p>
     * <pre>
     * key的格式："文件大小:MD5".
     */
    public static Map<String, List<File>> searchRepeatSameSizeFilesMap;
    /**
     * 重复文件查找时用的Set.
     * <p>
     * <pre>
     * 元素String的格式："文件大小:MD5".
     * 判断repeatFilesMap的value List<File>.size()>1，则加入repeatFilesSet中.
     * </pre>
     */
    public static Set<String> repeatFilesSet;

    /**
     * 同名文件查找时用的Map（存储的是所有文件名的文件，并非仅是同一个文件名大于1的才存储）.
     * <p>
     * <pre>
     * key的格式："文件名".
     * </pre>
     */
    public static Map<String, List<File>> searchSameNameFilesMap;
    /**
     * 同名文件查找时用的Set.
     * <p>
     * <pre>
     * 元素String的格式："文件名".
     * 判断sameNameFilesMap的value List<File>.size()>1，则加入sameNameFilesSet中.
     * </pre>
     */
    public static Set<String> sameNameFilesSet;

    /**
     * 迭代获取文件夹目录下所有文件(夹).
     *
     * @param path      文件夹目录地址
     * @param paramsMap 参数
     * @return <code>List<File></code> 文(夹)件集合
     */
    public static List<File> getAllSubFiles(String path, Map<String, Object> paramsMap) {
        File file = new File(path);
        List<File> fileList = new ArrayList<File>();
        SearchFileAndFolderNamePathParams searchFileAndFolderNamePathParams = new SearchFileAndFolderNamePathParams(
                paramsMap);
        if (file.isDirectory()) {
            loopDirectory(file, fileList, searchFileAndFolderNamePathParams, 0);
        }
        // 重复文件查找 计算相同大小文件的MD5值
        if (searchFileAndFolderNamePathParams.typeRepeatSearch) {
            for (String fileSize : searchRepeatSizeFilesMap.keySet()) {
                List<File> sizeFiles = searchRepeatSizeFilesMap.get(fileSize);
                // 小于2个的无需判断重复
                if (sizeFiles.size() < 2) {
                    continue;
                }
                for (File sameSizeFile : sizeFiles) {
                    try {
                        byte[] bytes = new byte[2048];
                        new FileInputStream(sameSizeFile).read(bytes);
                        String prop = fileSize + ":" + DigestUtils.md5Hex(bytes);
                        List<File> repeatFiles = searchRepeatSameSizeFilesMap.get(prop);
                        if (repeatFiles == null) {
                            repeatFiles = new ArrayList<File>();
                            searchRepeatSameSizeFilesMap.put(prop, repeatFiles);
                        } else if (repeatFiles.size() > 0) {
                            repeatFilesSet.add(prop);
                        }
                        repeatFiles.add(sameSizeFile);
                    } catch (FileNotFoundException e) {
                        GuiUtils.log(e);
                    } catch (IOException e) {
                        GuiUtils.log(e);
                    }
                }
            }
        }
        // 同名文件查找
        else if (searchFileAndFolderNamePathParams.typeSameNameSearch) {
            for (String fileName : searchSameNameFilesMap.keySet()) {
                List<File> nameFiles = searchSameNameFilesMap.get(fileName);
                if (nameFiles.size() > 1) {
                    sameNameFilesSet.add(fileName);
                }
            }
        }
        return fileList;
    }

    /**
     * 迭代目录，将符合条件的文件(不包括文件夹)添加到fileList中.
     *
     * @param directory 目录
     * @param fileList  file集合
     * @param sps       参数
     */
    public static void loopDirectory(File directory, List<File> fileList, SearchFileNameParams sps) {
        File[] files = directory.listFiles();
        if (files == null) {
            return;
        }
        for (File file : files) {
            if (file.isDirectory()) {
                loopDirectory(file, fileList, sps);
            } else {
                ifAddFile(file, fileList, sps);
            }
        }
    }

    /**
     * 迭代目录，将符合条件的子文件或子文件夹添加到fileList中.
     *
     * @param directory 目录
     * @param fileList  file集合
     * @param sps       参数
     * @param hierarchy 目录层级 与sps.folderHierarchy做对比进行目录层级遍历深度使用，传0不对遍历层级进行限制
     */
    private static void loopDirectory(File directory, List<File> fileList, SearchFileAndFolderNamePathParams sps, int hierarchy) {
        File[] files = directory.listFiles();
        if (files == null) {
            return;
        }
        hierarchy += 1;
        for (File file : files) {
            ifAddFile(file, fileList, sps);
            if (sps.folderHierarchy != 0 && hierarchy >= sps.folderHierarchy) {
                continue;
            }
            if (file.isDirectory()) {
                loopDirectory(file, fileList, sps, hierarchy);
            }
        }
    }

    /**
     * 迭代获取地址所有文件(夹)，包括file本身.
     *
     * @param file 文件(夹)
     * @return <code>List<File></code> 文(夹)件集合
     */
    public static List<File> getAllFile(File file) {
        List<File> fileList = new ArrayList<File>();
        if (file.exists()) {
            fileList.add(file);
        }
        if (file.isDirectory()) {
            loopDirectory(file, fileList);
        }
        return fileList;
    }

    /**
     * 迭代目录，将所有子文件或者子文件夹添加到fileList中.
     *
     * @param directory 目录
     * @param fileList  file集合
     */
    public static void loopDirectory(File directory, List<File> fileList) {
        File[] files = directory.listFiles();
        if (files == null) {
            return;
        }
        for (File file : files) {
            fileList.add(file);
            if (file.isDirectory()) {
                loopDirectory(file, fileList);
            }
        }
    }

    /**
     * 根据条件判断是否将文件add到file集合.
     *
     * @param file     文件
     * @param fileList file集合
     * @param sps      参数
     */
    private static void ifAddFile(File file, List<File> fileList, SearchFileNameParams sps) {
        // 判断文件名是否满足匹配
        if (FileUtils.ifMatchText(file.getName(), sps.fileNameCsText, sps.fileNameNCsText, sps.fileNameSRegex,
                sps.fileNameCsPattern, sps.fileNameNCsPattern)) {
            fileList.add(file);
        }
    }

    /**
     * 根据条件判断是否将文件(夹)add到file集合.
     *
     * @param file     文件(夹)
     * @param fileList file集合
     * @param sps      参数
     */
    private static void ifAddFile(File file, List<File> fileList, SearchFileAndFolderNamePathParams sps) {
        boolean hidden = file.isHidden();
        boolean isDir = file.isDirectory();

        // 是否是查找空文件/文件夹
        boolean blankDirOrFile = (isDir && (file.listFiles() == null || file.listFiles().length != 0)) || (!isDir && file.length() != 0);
        if (sps.typeBlankSearch && blankDirOrFile) {
            return;
        }
        // 判断是否包括隐藏文件、非隐藏文件、文件、文件夹
        boolean ifAdd = ifAddFileFolderHidden(hidden, isDir, sps);
        if (ifAdd) {
            // 比较修改时间
            ifAdd = ifInSideTime(file.lastModified(), sps.modifyTimeFrom, sps.modifyTimeTo);
        }
        if (ifAdd) {
            // 文件(夹)路径包含(不包含)字符
            ifAdd = ifMatchText(file.getAbsolutePath(), sps.filePathCsText, sps.filePathNCsText, sps.filePathSRegex,
                    sps.filePathCsPattern, sps.filePathNCsPattern);
        }
        if (ifAdd) {
            if (isDir) {
                if (!sps.typeRepeatSearch && !sps.typeSameNameSearch) {
                    // 文件夹路径包含(不包含)字符
                    ifAdd = ifMatchText(file.getAbsolutePath(), sps.folderPathCsText, sps.folderPathNCsText,
                            sps.folderPathSRegex, sps.folderPathCsPattern, sps.folderPathNCsPattern);
                }
            } else {
                long fileSize = file.length();
                // 比较文件大小
                ifAdd = ifInSideSize(fileSize, sps.sizeFrom, sps.sizeTo);
                if (ifAdd) {
                    String fileName = file.getName();
                    String fileType = getFileType(fileName);
                    // 比较文件类型
                    ifAdd = sps.fileType.length() == 0 || ("," + sps.fileType + ",").contains("," + fileType + ",");
                    if (ifAdd) {
                        // 文件名包含(不包含)字符，比较名称/路径是否匹配
                        ifAdd = ifMatchText(fileName, sps.fileNameCsText, sps.fileNameNCsText, sps.fileNameSRegex,
                                sps.fileNameCsPattern, sps.fileNameNCsPattern);
                    }
                    if (ifAdd) {
                        // 重复文件查找 先找到大小相同的文件
                        if (sps.typeRepeatSearch && !isDir) {
                            String prop = Long.toString(fileSize);
                            if (sps.repeatSameSuffix) {
                                prop = fileType + ":" + prop;
                            }
                            List<File> sizeFiles = searchRepeatSizeFilesMap.get(prop);
                            if (sizeFiles == null) {
                                sizeFiles = new ArrayList<File>();
                                searchRepeatSizeFilesMap.put(prop, sizeFiles);
                            }
                            sizeFiles.add(file);
                        } else if (sps.typeSameNameSearch) {
                            // 同名文件查找
                            String prop = "";
                            if (sps.repeatSameSuffix) {
                                prop = fileName;
                            } else {
                                prop = fileName.substring(0, fileName.length() - fileType.length() - 1);
                            }
                            List<File> nameFiles = searchSameNameFilesMap.get(prop);
                            if (nameFiles == null) {
                                nameFiles = new ArrayList<File>();
                                searchSameNameFilesMap.put(prop, nameFiles);
                            }
                            nameFiles.add(file);
                        }
                    }
                }
            }
        }
        // 当查找类型为'重复文件查找'，files最后长度为0，结果保存在FileUtils.repeatFilesSet及FileUtils.repeatFilesMap中
        // 当查找类型为'同名文件查找'，files最后长度为0，结果保存在FileUtils.sameNameFilesSet及FileUtils.sameNameFilesMap中
        if (ifAdd && !sps.typeRepeatSearch && !sps.typeSameNameSearch) {
            fileList.add(file);
        }
    }

    /**
     * 判断是否包括隐藏文件、非隐藏文件、文件、文件夹.
     */
    public static boolean ifAddFileFolderHidden(boolean hidden, boolean isDir, SearchFileAndFolderNamePathParams sps) {
        boolean add = true;
        if (!isDir && !sps.containsFile) {
            add = false;
        } else if (isDir && !sps.containsFolder) {
            add = false;
        }
        if (!hidden && !sps.containsNotHidden) {
            add = false;
        } else if (hidden && !sps.containsHidden) {
            add = false;
        }
        return add;
    }

    /**
     * 比较是否在时间之间.
     */
    public static boolean ifInSideTime(long side, Long from, Long to) {
        boolean inSide = true;
        if (from != null && to == null) {
            inSide = side >= from;
        } else if (from == null && to != null) {
            inSide = side <= to;
        } else if (from != null && to != null) {
            inSide = side >= from && side <= to;
        }
        return inSide;
    }

    /**
     * 比较是否在大小之间.
     */
    public static boolean ifInSideSize(long side, Double from, Double to) {
        boolean inSide = true;
        if (from != null && to == null) {
            inSide = side >= from;
        } else if (from == null && to != null) {
            inSide = side <= to;
        } else if (from != null && to != null) {
            inSide = side >= from && side <= to;
        }
        return inSide;
    }

    /**
     * 获取文件后缀名，小写.
     */
    public static String getFileType(String fileName) {
        String fileType = "";
        int lIndex = fileName.lastIndexOf(".");
        if (lIndex > 0) {
            fileType = fileName.substring(lIndex + 1, fileName.length()).toLowerCase();
        }
        return fileType;
    }

    /**
     * 判断文件(夹)名是否满足匹配.
     */
    public static boolean ifMatchText(String fileName, String csText, String ncsText, boolean sRegex,
                                      Pattern csPattern, Pattern ncsPattern) {
        boolean match = true;
        String lFileName = fileName.toLowerCase();
        String lcsText = csText.toLowerCase();
        String lncsText = ncsText.toLowerCase();
        if (sRegex) {
            if (csText.length() != 0) {
                Matcher m = csPattern.matcher(fileName);
                match = m.find();
            }
            if (match && ncsText.length() != 0) {
                Matcher m = ncsPattern.matcher(fileName);
                match = !m.find();
            }
        } else {
            if (csText.length() != 0) {
                match = lFileName.contains(lcsText);
            }
            if (match && ncsText.length() != 0) {
                match = !lFileName.contains(lncsText);
            }
        }
        return match;
    }

}


<|start_filename|>src/main/java/bs/tool/commongui/plugins/SystemInformation.java<|end_filename|>
package bs.tool.commongui.plugins;

import bs.tool.commongui.AbstractGuiJPanel;
import bs.tool.commongui.GuiUtils;
import bs.tool.commongui.utils.SimpleMouseListener;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.lang.management.*;
import java.nio.charset.Charset;
import java.util.List;
import java.util.*;

/**
 * 系统信息.
 */
public class SystemInformation extends AbstractGuiJPanel {

    private static final long serialVersionUID = 1L;

    /**
     * 查询类别.
     */
    private String[] searchTypes = new String[]{"Overview", "Running Status", "System Properties", "Support Charsets",
            "Support Fonts"};

    /**
     * 当前查询类别.
     */
    private String curSearchType = searchTypes[0];

    /**
     * 输出文本域.
     */
    private JTextArea textArea = createJTextArea(GuiUtils.font14_cn);

    public SystemInformation() {

        // 主面板：边界布局，分North、Center两部分，North用于放置条件控件，Center是放置输出
        setLayout(new BorderLayout());
        JPanel topPanel = new JPanel(new FlowLayout());
        add(topPanel, BorderLayout.NORTH);
        add(new JScrollPane(textArea), BorderLayout.CENTER);

        // North面板
        addJLabel(topPanel, "Search Type:", GuiUtils.font14b);
        // 查询类别下拉框
        addJComboBox(topPanel, searchTypes, GuiUtils.font13, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                curSearchType = ((JComboBox) event.getSource()).getSelectedItem().toString();
            }
        });
        addJLabel(topPanel, " ", GuiUtils.font14b);
        addJButton(topPanel, " Search ", "", GuiUtils.font14b, new SimpleMouseListener() {
            @Override
            public void mouseReleased(MouseEvent e) {
                int typeLen = searchTypes.length;
                String text = "";
                if (searchTypes[typeLen - 5].equals(curSearchType)) {
                    text = getSystemOverview();
                } else if (searchTypes[typeLen - 4].equals(curSearchType)) {
                    text = getRunningStatus();
                } else if (searchTypes[typeLen - 3].equals(curSearchType)) {
                    text = getSystemProperties();
                } else if (searchTypes[typeLen - 2].equals(curSearchType)) {
                    text = getAvailableCharsets();
                } else if (searchTypes[typeLen - 1].equals(curSearchType)) {
                    text = getAvailableFonts();
                }
                textArea.append(text);
            }

            @Override
            public void mousePressed(MouseEvent e) {
                // clear文本域
                textArea.setText("");
            }
        });
    }

    /**
     * System Information Overview.
     */
    private String getSystemOverview() {
        StringBuilder sb = new StringBuilder();
        sb.append("\n").append(
                getPropertiesString(new String[]{"java.runtime.name", "java.runtime.version", "java.vm.name"}));
        sb.append("\n").append(
                getPropertiesString(new String[]{"os.name", "os.version", "sun.os.patch.level", "os.arch",
                        "sun.arch.data.model"}));
        sb.append("\n").append(
                getPropertiesString(new String[]{"user.home", "user.dir", "user.name", "user.timezone",
                        "user.language", "user.country", "user.variant"}));
        sb.append("\n").append(getPropertiesString(new String[]{"java.home", "java.io.tmpdir"}));
        sb.append("\n").append(
                getPropertiesString(new String[]{"path.separator", "file.separator", "line.separator"}));
        sb.append("\n").append(
                getPropertiesString(new String[]{"file.encoding", "file.encoding.pkg", "sun.jnu.encoding",
                        "sun.cpu.endian", "sun.io.unicode.encoding", "sun.cpu.isalist"}));
        sb.append("\n").append(
                getPropertiesString(new String[]{"sun.boot.library.path", "sun.boot.class.path", "java.ext.dirs",
                        "java.endorsed.dirs", "java.library.path", "java.class.path"}));
        sb.append("\n").append(
                getPropertiesString(new String[]{"sun.desktop", "awt.toolkit", "java.awt.graphicsenv",
                        "java.awt.printerjob"}));
        sb.append("\n").append(
                getPropertiesString(new String[]{"java.version", "java.vm.info", "java.vm.version",
                        "java.class.version", "sun.java.launcher", "sun.management.compiler"}));
        sb.append("\n").append(
                getPropertiesString(new String[]{"java.specification.name", "java.specification.vendor",
                        "java.specification.version", "java.vendor", "java.vendor.url", "java.vendor.url.bug"}));
        sb.append("\n").append(
                getPropertiesString(new String[]{"java.vm.specification.name", "java.vm.specification.vendor",
                        "java.vm.specification.version", "java.vm.vendor"}));
        return sb.substring(1);
    }

    private String getPropertiesString(String[] keys) {
        StringBuilder sb = new StringBuilder();
        for (String key : keys) {
            sb.append("\n").append(GuiUtils.getFillUpString(key, 32));
            String value = System.getProperty(key);
            if ("line.separator".equals(key)) {
                sb.append(value.replace("\n", "\\n").replace("\r", "\\r"));
            } else {
                sb.append(value);
            }
        }
        return sb.toString();
    }

    /**
     * 虚拟机运行情况.
     */
    private String getRunningStatus() {
        StringBuilder sb = new StringBuilder();

        // 虚拟机开始及总运行时间
        RuntimeMXBean runBean = ManagementFactory.getRuntimeMXBean();
        sb.append("\n").append(GuiUtils.getFillUpString("Current Time", 32)).append(new Date());
        sb.append("\n").append(GuiUtils.getFillUpString("Start Time", 32)).append(new Date(runBean.getStartTime()));
        sb.append("\n").append(GuiUtils.getFillUpString("Run Time", 32))
                .append(GuiUtils.getCountTime(runBean.getUptime()));

        sb.append("\n");
        // 操作系统可用处理器数目
        OperatingSystemMXBean operateBean = ManagementFactory.getOperatingSystemMXBean();
        sb.append("\n").append(GuiUtils.getFillUpString("System Processors", 32))
                .append(operateBean.getAvailableProcessors());

        // 内存使用情况
        MemoryMXBean memBean = ManagementFactory.getMemoryMXBean();
        MemoryUsage heapMem = memBean.getHeapMemoryUsage();
        MemoryUsage nonHeapMem = memBean.getNonHeapMemoryUsage();

        sb.append("\n");
        sb.append("\n").append(GuiUtils.getFillUpString("Heap Used", 32)).append(getMemoey(heapMem.getUsed()));
        sb.append("\n").append(GuiUtils.getFillUpString("Heap Committed", 32))
                .append(getMemoey(heapMem.getCommitted()));
        sb.append("\n").append(GuiUtils.getFillUpString("Heap Init", 32)).append(getMemoey(heapMem.getInit()));
        sb.append("\n").append(GuiUtils.getFillUpString("Heap Max", 32)).append(getMemoey(heapMem.getMax()));
        sb.append("\n");
        sb.append("\n").append(GuiUtils.getFillUpString("NonHeap Used", 32)).append(getMemoey(nonHeapMem.getUsed()));
        sb.append("\n").append(GuiUtils.getFillUpString("NonHeap Committed", 32))
                .append(getMemoey(nonHeapMem.getCommitted()));
        sb.append("\n").append(GuiUtils.getFillUpString("NonHeap Init", 32)).append(getMemoey(nonHeapMem.getInit()));
        sb.append("\n").append(GuiUtils.getFillUpString("NonHeap Max", 32)).append(getMemoey(nonHeapMem.getMax()));

        sb.append("\n");
        Runtime curRuntime = Runtime.getRuntime();
        sb.append("\n").append(GuiUtils.getFillUpString("Free Memory", 32))
                .append(getMemoey(curRuntime.freeMemory()));
        sb.append("\n").append(GuiUtils.getFillUpString("Total Memory", 32))
                .append(getMemoey(curRuntime.totalMemory()));
        sb.append("\n").append(GuiUtils.getFillUpString("Max Memory", 32)).append(getMemoey(curRuntime.maxMemory()));

        // 当前运行线程
        sb.append("\n");
        sb.append("\n").append(GuiUtils.getFillUpString("  Thread Name", 32)).append("Thread ID");
        ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
        long[] threads = threadBean.getAllThreadIds();
        ThreadInfo[] threadInfos = threadBean.getThreadInfo(threads);
        for (int i = threadInfos.length - 1; i > -1; i--) {
            sb.append("\n").append(GuiUtils.getFillUpString(threadInfos[i].getThreadName(), 32))
                    .append(threadInfos[i].getThreadId());
        }
        return sb.toString();
    }

    private String getMemoey(long mem) {
        return (double) mem / (1024.0 * 1024.0) + "M";
    }

    /**
     * System.getProperties().
     */
    private String getSystemProperties() {
        StringBuilder sb = new StringBuilder();
        Properties properties = System.getProperties();
        Set<Object> sets = properties.keySet();
        List<String> keys = new ArrayList<String>();
        for (Object key : sets) {
            keys.add((String) key);
        }
        Collections.sort(keys);
        for (String key : keys) {
            sb.append("\n").append(GuiUtils.getFillUpString(key, 32));
            String value = properties.getProperty(key);
            if ("line.separator".equals(key)) {
                sb.append(value.replace("\n", "\\n").replace("\r", "\\r"));
            } else {
                sb.append(value);
            }
        }
        sb.append("\n\nCount " + sets.size() + " Properties.\n");
        return sb.substring(1);
    }

    /**
     * JDK 1.6 中文 API Charset类说明： Java 编程语言的本机字符编码方案是 UTF-16。因此 Java 平台的 charset 定义了 16 位 UTF-16 代码单元序列和字节序列之间的映射关系.
     */
    private String getAvailableCharsets() {
        int interval = 5;
        StringBuilder sb = new StringBuilder();
        SortedMap<String, Charset> map = GuiUtils.availableCharsets();
        // 首字母小写对应ASCII的十进制值
        int i = 0;
        // 上一个首字母小写对应ASCII的十进制值
        int i_o = 0;
        // 同一字母开头的字符集数目过多, 则10个分一行
        int l = 0;
        for (String key : map.keySet()) {
            i = (key.substring(0, 1).toLowerCase()).toCharArray()[0];
            if (i_o != 0 && i_o != i) {
                i_o = i;
                if (l % interval != 0) {
                    sb.append("\n");
                }
                l = 0;
            }
            sb.append(GuiUtils.getFillUpString(key, 22));
            l++;
            if (i_o == i && l % interval == 0) {
                sb.append("\n");
            }
            if (i_o == 0) {
                i_o = i;
            }
        }
        sb.append("\n\nCount " + map.size() + " Charsets.\n");
        return sb.toString();
    }

    /**
     * 支持字体.
     */
    private String getAvailableFonts() {
        StringBuilder sb = new StringBuilder();
        Font[] fonts = GuiUtils.availableFonts();
        List<String> names = new ArrayList<String>();
        for (Font font : fonts) {
            names.add(font.getFontName());
        }
        // 排序
        Collections.sort(names);
        int i = 0;
        for (String name : names) {
            sb.append(GuiUtils.getFillUpString(name, 42));
            i++;
            if (i % 3 == 0) {
                sb.append("\n");
            }
        }
        sb.append("\n\nCount " + fonts.length + " Fonts.\n");
        return sb.toString();
    }

}


<|start_filename|>src/main/java/bs/tool/commongui/plugins/more/QrCodeConverter.java<|end_filename|>
package bs.tool.commongui.plugins.more;

import bs.tool.commongui.AbstractGuiJPanel;
import bs.tool.commongui.GuiImagePanel;
import bs.tool.commongui.GuiUtils;
import bs.tool.commongui.code.QrCodeUtil;
import bs.tool.commongui.utils.FileUtils;
import bs.tool.commongui.utils.SimpleMouseListener;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.io.File;
import java.io.IOException;

/**
 * 二维码转换.
 */
public class QrCodeConverter extends AbstractGuiJPanel {

    private static final long serialVersionUID = 1L;

    /**
     * 文本域.
     */
    private JTextArea textArea = createJTextArea(GuiUtils.font14_un, System.getProperty("QrCodeConverter.defaultText"));
    private int imageWidth = 420;
    private int imageHeight = 420;
    private String qrCodeImageFile = FileUtils.JAVA_IO_TMPDIR + "/" + "$QrCodeConverter$.jpg";
    private String blankImageFile = GuiUtils.getActualPath("img/cgt_blank.png");
    private String currentQrCodeImageFile = blankImageFile;
    /**
     * 二维码图片域.
     */
    private GuiImagePanel qrCodeImagePanel = new GuiImagePanel(imageWidth, imageHeight);
    /**
     * 图片路径选择.
     */
    private JFileChooser imageChooser = createImageChooser();

    public QrCodeConverter() {

        // 主面板：边界布局，分North、Center、East三部分，North用于放置文本，Center是放置二维码图片，East是按钮
        setLayout(new BorderLayout());

        // 文本
        JPanel textAreaPanel = new JPanel(new BorderLayout());
        add(textAreaPanel, BorderLayout.NORTH);
        addJLabel(textAreaPanel, " 字  符: ", GuiUtils.font14b_cn, BorderLayout.WEST);
        textArea.setRows(4);
        textAreaPanel.add(new JScrollPane(textArea), BorderLayout.CENTER);
        // 填充
        addJLabel(textAreaPanel, "               ", GuiUtils.font14b_cn, BorderLayout.EAST);

        // Center，二维码图片域
        JPanel imagePanel = new JPanel(new BorderLayout());
        add(imagePanel, BorderLayout.CENTER);
        addJLabel(imagePanel, " 二维码: ", GuiUtils.font14b_cn, BorderLayout.WEST);
        imagePanel.add(new JScrollPane(qrCodeImagePanel), BorderLayout.CENTER);

        // East，操作区域，使用BorderLayout布局
        JPanel actionPanel = new JPanel(new BorderLayout());
        add(actionPanel, BorderLayout.EAST);

        // 填充
        actionPanel.add(new JPanel(), BorderLayout.CENTER);

        // 放置按钮
        JPanel buttonPanel = new JPanel(new GridLayout(6, 1));
        actionPanel.add(buttonPanel, BorderLayout.SOUTH);
        // 生成
        addJButton(buttonPanel, " 生成二维码 ", "", GuiUtils.font14b_cn, new SimpleMouseListener() {
            @Override
            public void mouseReleased(MouseEvent event) {
                String input = textArea.getText();
                if (input.length() != 0) {
                    currentQrCodeImageFile = qrCodeImageFile;
                    genQrCodeImage(input);
                }
            }

            @Override
            public void mousePressed(MouseEvent e) {
                currentQrCodeImageFile = blankImageFile;
                qrCodeImagePanel.repaint(currentQrCodeImageFile);
            }
        });

        // 另存图片
        addJButton(buttonPanel, " 另存图片 ", "", GuiUtils.font14_cn,
                new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent event) {
                        int option = imageChooser.showSaveDialog(null);
                        if (option == JFileChooser.APPROVE_OPTION) {
                            try {
                                // 补后缀名
                                File selectedFile = imageChooser.getSelectedFile();
                                if (selectedFile.getName().indexOf(".") < 0) {
                                    imageChooser.setSelectedFile(new File(selectedFile.getAbsolutePath() + ".png"));
                                }
                                // 复制图片
                                org.apache.commons.io.FileUtils.copyFile(new File(currentQrCodeImageFile), imageChooser.getSelectedFile());
                            } catch (IOException e) {
                                showExceptionMessage(e);
                            }
                        }
                    }
                });

        // 仅做填充
        buttonPanel.add(new JPanel());

        // 选择图片
        addJButton(buttonPanel, " 选择图片 ", "", GuiUtils.font14_cn,
                new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent event) {
                        if (imageChooser.showDialog(getContextPanel(), "确定") != JFileChooser.CANCEL_OPTION) {
                            textArea.setText("");
                            File selectFile = imageChooser.getSelectedFile();
                            if (selectFile != null) {
                                currentQrCodeImageFile = selectFile.getAbsolutePath();
                                decodeQrCodeImage(currentQrCodeImageFile);
                                qrCodeImagePanel.repaint(currentQrCodeImageFile);
                            }
                        }
                    }
                });

        // 解析
        addJButton(buttonPanel, " 解析二维码 ", "", GuiUtils.font14b_cn, new SimpleMouseListener() {
            @Override
            public void mouseReleased(MouseEvent event) {
                decodeQrCodeImage(currentQrCodeImageFile);
            }

            @Override
            public void mousePressed(MouseEvent e) {
                textArea.setText("");
            }
        });
    }

    private void genQrCodeImage(String code) {
        try {
            QrCodeUtil.genQrCodeFile(code, currentQrCodeImageFile, QrCodeUtil.FORMAT_PNG, imageWidth, imageHeight);
            qrCodeImagePanel.repaint(currentQrCodeImageFile);
        } catch (Exception e) {
            showExceptionMessage(e, "generate qrCode image error.");
        }
    }

    private void decodeQrCodeImage(String imagePath) {
        if (!blankImageFile.equals(imagePath)) {
            try {
                textArea.setText(QrCodeUtil.decodeQrCodeFile(imagePath).getText());
            } catch (Exception e) {
                showExceptionMessage(e, "decode qrCode image error.");
            }
        }
    }

}
