玩转字符串篇--Gradle+代码生成器=懒人必备

3,365 阅读5分钟

前言:我是爱偷懒的勤快人

我们的口号是:
能用脑子解决的绝对不靠体力,能用电脑完成的绝对不靠脑子,能懒就懒,懒出奇迹

1.1:关于代码

你眼中的代码是什么,类的逻辑关系?方法的实现?不要忽略一个重要的本质:
代码是人可以读懂的可编译成可执行文件的字符串,所以字符串很重要。
陆陆续续写了好几篇关于字符串的文章了,量变的基类往往带来质变的升华
今天我灵光一闪,便有此文。为了不让优秀石沉大海,之前写的几篇连接放在下面:

1.玩转字符串篇--代码自动生成,解放双手
2.玩转字符串篇--数据遍地是,看你取不取
3.玩转字符串篇--练习MySQL没素材?来一波字符串操作 link
4.玩转字符串篇--替换的鬼斧神工


1.2:本篇起由

今天写Flutter自定义组件,感觉写个StatefulWidget要罗里吧嗦一大堆
而且一开始都是差不多的,于是想来个一键生成,并放到gradle里
写着写着发现用上次的字符串替代类可以用,不用走平时的拼接了
果然有心栽花花不开,无心插柳柳成荫!本文主要的是对字符串的想法


2.模板替换解析器:TemplateParser

这是我再玩转字符串篇--替换的鬼斧神工的基础上进行完善的产物

1).可指定匹配符
2).优化了结构,使用Properties替换了HashMap,并使用配置文件  
3).支持单文件和文件夹多文件替换

2.1:Properties的使用

在此之前,先说一下Properties的使用,感觉这个也挺好的,可以根据配置文件读成映射表
他继承自HashTable,只存放String到String的映射,下面来看一下他的用法
这里在项目最外面创建一个config.properties,写着键值对className=TolyWidget

public class Generation {
    public static void main(String[] args) throws IOException {
        Properties prop = new Properties();
        //读取属性文件a.properties
        InputStream in = new BufferedInputStream(new FileInputStream("config.properties"));
        prop.load(in);     ///加载属性列表
        Iterator<String> it = prop.stringPropertyNames().iterator();
        while (it.hasNext()) {
            String key = it.next();
            System.out.println(key + ":" + prop.getProperty(key));
        }
        in.close();
    }
}
---->[控制台输出]----
className:TolyWidget

这样就可以根据配置文件在代码中使用字符串的键值对了


2.2:解析类

最终的效果是可以通过配置文件的映射字符串,替换掉一个模板中的所有相应被标识部分
默认配置文件的位置在项目根部,名称config.properties,输出到模板的父目录同级的dest下

package generation;

import java.io.*;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class TemplateParser {
    /**
     * 默认:$[X],自定义注意\\转义 
     */
    private String symbol;

    private Properties properties;

    public TemplateParser() {
        this("\\$\\[X\\]", "config.properties");
    }

    public TemplateParser(String symbol, String propFilePath) {
        this.symbol = symbol;
        loadProperties(propFilePath);
    }

    /**
     * 载入配置文件
     * @param path 配置文件路径
     */
    private void loadProperties(String path) {
        properties = new Properties();
        //读取属性文件a.properties
        InputStream in = null;
        try {
            in = new BufferedInputStream(new FileInputStream(path));
            properties.load(in);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (in != null) {
                    in.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 解析字符串
     *
     * @param target 目标字符串
     * @return 处理后的字符串
     */
    public String parser(String target) {
        String[] symbols = symbol.split("X");

        Map<Integer, String> cutPos = new TreeMap<>();
        String regex = symbols[0] + "(?<result>.*?)" + symbols[1];
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(target);
        while (matcher.find()) {
            String matchStr = matcher.group("result");
            cutPos.put(matcher.end(), matchStr);
        }

        Iterator<Map.Entry<Integer, String>> iterator = cutPos.entrySet().iterator();
        String temp;
        int offset = 0;
        while (iterator.hasNext()) {
            Map.Entry<Integer, String> e = iterator.next();
            int k = e.getKey();
            String v = e.getValue();
            String src = "$[" + v + "]";
            String result = properties.getProperty(v);
            String substring = target.substring(0, k + offset);
            temp = substring.replace(src, result);
            target = target.replace(substring, temp);
            offset += result.length() - src.length();
        }
        return target;
    }


    /**
     * 根据路径解析所有文件
     * @param path 路径
     */
    public void parserDir(String path) {
        copyDir(path, path + "-dest");//先拷贝一分
        parserDir(new File(path + "-dest"));
    }

    private void parserDir(File file) {
        if (file.isDirectory()) {
            File[] files = file.listFiles();
            if (files == null) {
                return;
            }
            for (File f : files) {
                if (f.isDirectory()) {
                    parserDir(f);
                } else {
                    saveFile(f, parserFile(file));
                }
            }
        } else {
            parserFile(file);
        }
    }


    /**
     * 解析一个文件
     *
     * @param path 路径
     * @return 解析后的字符串
     */
    public String parserFile(String path) {
        File file = new File(path);
        String result = parserFile(new File(path));
        String out = file.getParentFile().getParentFile().getAbsolutePath() + File.separator + "dest" + File.separator + file.getName();
        saveFile(new File(out), result);
        return result;
    }

    /**
     * 根据文件解析
     *
     * @param file 文件
     * @return 解析后的字符串
     */
    private String parserFile(File file) {
        InputStream is = null;
        StringBuilder sb = new StringBuilder();
        try {
            is = new FileInputStream(file);
            int len = 0;
            byte[] buffer = new byte[1024];
            while ((len = is.read(buffer)) != -1) {
                sb.append(new String(buffer, 0, len));
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (is != null) {
                    is.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return parser(sb.toString());
    }

    //==========================文件相关操作===========================
    /**
     * 保存字符串文件
     *
     * @param file    文件
     * @param content 字符串内容
     */
    private void saveFile(File file, String content) {
        ifNotExistMakeIt(file);
        FileWriter fw = null;

        try {
            fw = new FileWriter(file);//写出到磁盘
            fw.write(content);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (fw != null) {
                    fw.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }

    /**
     * 判断文件是否存在,不存在则创建它
     *
     * @param file
     */
    private static void ifNotExistMakeIt(File file) {
        if (file.exists()) {//2.判断文件是否存在
            return;
        }
        File parent = file.getParentFile();//3.获取父文件
        if (!parent.exists()) {
            if (!parent.mkdirs()) {//4.创建父文件
                return;
            }
        }
        try {
            file.createNewFile();//5.创建文件
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void ifNotExistMakeIt(String path) {
        File file = new File(path);//1.创建文件
        ifNotExistMakeIt(file);
    }


    /**
     * 复制整个文件夹内容
     *
     * @param oldPath String 原文件路径
     * @param newPath String 复制后路径
     * @return boolean
     */
    private void copyDir(String oldPath, String newPath) {
        try {
            (new File(newPath)).mkdirs(); //如果文件夹不存在 则建立新文件夹
            File a = new File(oldPath);
            String[] file = a.list();
            File temp = null;
            if (file == null) {
                return;
            }
            for (int i = 0; i < file.length; i++) {
                if (oldPath.endsWith(File.separator)) {
                    temp = new File(oldPath + file[i]);
                } else {
                    temp = new File(oldPath + File.separator + file[i]);
                }
                if (temp.isFile()) {
                    FileInputStream input = new FileInputStream(temp);
                    FileOutputStream output = new FileOutputStream(newPath + "/" +
                            (temp.getName()).toString());
                    byte[] b = new byte[1024 * 5];
                    int len;
                    while ((len = input.read(b)) != -1) {
                        output.write(b, 0, len);
                    }
                    output.flush();
                    output.close();
                    input.close();
                }
                if (temp.isDirectory()) {//如果是子文件夹
                    copyDir(oldPath + "/" + file[i], newPath + "/" + file[i]);
                }
            }
        } catch (Exception e) {
            System.out.println("复制出错");
            e.printStackTrace();
        }
    }
}

里面有很多文件常用的操作,也可以抽出一个工具类收藏一下


3.Gradle里如何使用Java代码

关于Gradle的知识我有一篇专文:杂篇:一代版本一代神[-Gradle-]

3.1:Gradle里的task和路径获取

新建一个task在左边Gradle->other会有相应的任务,点一下就可以运行其中的代码
可以用System.getProperty("user.dir")获取当前项目的根目录

---->[app/build.gradle最后面]----
task generationTask() {//自定义一个任务
    doFirst {
        String root=System.getProperty("user.dir");
        println("hello gradel:"+root)
}

3.2:Gradle中读取配置文件

由于Gradle中使用的是和Java兼容的Groovy语言,所以Java代码也是能运行的
这里在项目根文件下创建generation文件夹用来盛放配置文件以及模板和输出文件

task generationTask() {//自定义一个任务
    doFirst {

        String root=System.getProperty("user.dir");
        println("hello gradel:"+root)

        Properties prop = new Properties();
        //读取属性文件a.properties
        InputStream is = new BufferedInputStream(new FileInputStream(root+"/generation/config.properties"));
        prop.load(is);     ///加载属性列表
        Iterator<String> itor = prop.stringPropertyNames().iterator();
        while (itor.hasNext()) {
            String key = itor.next();
            System.out.println(key + ":" + prop.getProperty(key));
        }
        is.close();
    }
}

然后运行就可以看到获取的结果:className:TolyWidget


3.3:使用

把刚才写的类全部拷到下面。说几个比较坑的点吧

1.Groovy中正则匹配不能用分组,我勒个去  
2.符号$要用单引号,否则报错  
3.函数不能重载,我勒个去


3.4:插件以及拆分文件导入

那么大一段写在一块不怎么雅观,拆一下呗,将插件逻辑全部抽到另一个文件了
也放在generation包里,这样整个流程所需要的东西都在一起,整个gradle只管用就行了
我们只需要在意模板和配置,两个都写好之后,轻轻一点,模板中需要替换的全部搞定

---->[使用方法,app/build.gradle]----
apply from: "./generation/generation.gradle"

3.5: generation.gradle全览

把这个拷过去用就行了,以后有什么写着比较烦而且没有技术含量的批量换一下呗。

// Create By 张风捷特烈(toly) in 2019.7.17 ---------

apply plugin: TemplateParserPlugin//声明使用插件

//----------------------------以下是插件部分--------------------------------
class TemplateParserPlugin implements Plugin<Project> {
    //该接口定义了一个apply()方法,在该方法中,我们可以操作Project,
    //比如向其中加入Task,定义额外的Property等。
    void apply(Project project) {
        //加载Extension
        project.extensions.create("Config", Extension)

        //使用Extension配置信息
        project.task('TemplateParser') << {
            String path = project.Config.root
            new TemplateParser(path+"config.properties")
                    .parserFile(path+"template/StatefulTemplate.txt")
        }

    }
}

class Extension {//拓展参数
    String root = System.getProperty("user.dir")+"/generation/"
}

///----------------- 以下是解析类 -----------------

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class TemplateParser {
    /**
     * 默认:$[X],自定义注意\\转义
     */
    private String symbol;

    private Properties properties;

    public TemplateParser() {
        this('\\$\\[X\\]', "config.properties");
    }

    public TemplateParser( String propFilePath) {
        this('\\$\\[X\\]',propFilePath);
    }

    public TemplateParser(String symbol, String propFilePath) {
        this.symbol = symbol;
        loadProperties(propFilePath);
    }

    /**
     * 载入配置文件
     * @param path 配置文件路径
     */
    private void loadProperties(String path) {
        properties = new Properties();
        //读取属性文件a.properties
        InputStream is = null;
        try {
            is = new BufferedInputStream(new FileInputStream(path));
            properties.load(is);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (is != null) {
                    is.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 解析字符串
     *
     * @param target 目标字符串
     * @return 处理后的字符串
     */
    public String parser(String target) {
        String[] symbols = symbol.split("X");

        Map<Integer, String> cutPos = new TreeMap<>();
        String regex = symbols[0] + ".*?" + symbols[1];
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(target);

        while (matcher.find()) {
            String matchStr = matcher.group(0);
            String v = matchStr.split(symbols[0])[1].split(symbols[1])[0]
            cutPos.put(matcher.end(), v);
        }

        Iterator<Map.Entry<Integer, String>> iterator = cutPos.entrySet().iterator();
        String temp;
        int offset = 0;
        while (iterator.hasNext()) {
            Map.Entry<Integer, String> e = iterator.next();
            int k = e.getKey();
            String v = e.getValue();
            String src = '$[' + v + "]";
            String result = properties.getProperty(v);
            String substring = target.substring(0, k + offset);
            temp = substring.replace(src, result);
            target = target.replace(substring, temp);
            offset += result.length() - src.length();
        }
        return target;
    }


    /**
     * 根据路径解析所有文件
     * @param path 路径
     */
    public void parserDir(String path) {
        copyDir(path, path + "-dest");//先拷贝一分
        _parserDir(new File(path + "-dest"));
    }

    private void _parserDir(File file) {
        if (file.isDirectory()) {
            File[] files = file.listFiles();
            if (files == null) {
                return;
            }
            for (File f : files) {
                if (f.isDirectory()) {
                    parserDir(f);
                } else {
                    saveFile(f, parserFile(file));
                }
            }
        } else {
            parserFile(file);
        }
    }


    /**
     * 解析一个文件
     *
     * @param path 路径
     * @return 解析后的字符串
     */
    public String parserFile(String path) {
        File file = new File(path);
        String result = _parserFile(new File(path));
        String out = file.getParentFile().getParentFile().getAbsolutePath() + File.separator + "dest" + File.separator + file.getName();
        saveFile(new File(out), result);
        return result;
    }

    /**
     * 根据文件解析
     *
     * @param file 文件
     * @return 解析后的字符串
     */
    private String _parserFile(File file) {
        InputStream is = null;
        StringBuilder sb = new StringBuilder();
        try {
            is = new FileInputStream(file);
            int len = 0;
            byte[] buffer = new byte[1024];
            while ((len = is.read(buffer)) != -1) {
                sb.append(new String(buffer, 0, len));
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (is != null) {
                    is.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return parser(sb.toString());
    }

    //=============================================文件相关操作=============================================

    /**
     * 保存字符串文件
     *
     * @param file    文件
     * @param content 字符串内容
     */
    private void saveFile(File file, String content) {
        ifNotExistMakeIt(file);
        FileWriter fw = null;

        try {
            fw = new FileWriter(file);//写出到磁盘
            fw.write(content);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (fw != null) {
                    fw.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }

    /**
     * 判断文件是否存在,不存在则创建它
     *
     * @param file
     */
    private static void ifNotExistMakeIt(File file) {
        if (file.exists()) {//2.判断文件是否存在
            return;
        }
        File parent = file.getParentFile();//3.获取父文件
        if (!parent.exists()) {
            if (!parent.mkdirs()) {//4.创建父文件
                return;
            }
        }
        try {
            file.createNewFile();//5.创建文件
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void ifNotExistMakeIt(String path) {
        File file = new File(path);//1.创建文件
        ifNotExistMakeIt(file);
    }

    /**
     * 复制整个文件夹内容
     *
     * @param oldPath String 原文件路径
     * @param newPath String 复制后路径
     * @return boolean
     */
    private void copyDir(String oldPath, String newPath) {
        try {
            (new File(newPath)).mkdirs(); //如果文件夹不存在 则建立新文件夹
            File a = new File(oldPath);
            String[] file = a.list();
            File temp = null;
            if (file == null) {
                return;
            }
            for (int i = 0; i < file.length; i++) {
                if (oldPath.endsWith(File.separator)) {
                    temp = new File(oldPath + file[i]);
                } else {
                    temp = new File(oldPath + File.separator + file[i]);
                }
                if (temp.isFile()) {
                    FileInputStream input = new FileInputStream(temp);
                    FileOutputStream output = new FileOutputStream(newPath + "/" +
                            (temp.getName()).toString());
                    byte[] b = new byte[1024 * 5];
                    int len;
                    while ((len = input.read(b)) != -1) {
                        output.write(b, 0, len);
                    }
                    output.flush();
                    output.close();
                    input.close();
                }
                if (temp.isDirectory()) {//如果是子文件夹
                    copyDir(oldPath + "/" + file[i], newPath + "/" + file[i]);
                }
            }
        } catch (Exception e) {
            System.out.println("复制整个文件夹内容操作出错");
            e.printStackTrace();
        }
    }
}