• Scala正则和抽取器:解析方法参数


      在《正则表达式基础知识》中概括了正则表达式的基础知识, 本文讲解如何使用正则表达式解析方法参数,从而可以根据 DAO 自动生成 Service.

      在做 Java 项目时,常常要根据 DAO 生成 Service , 而 Service 有时是简单的调用 DAO 方法。比如根据 public CreativeDO findByCreativeId(Long creativeId)  生成如下代码:

    public CreativeDO findByCreativeId(Long creativeId) {
          return creativeDAO.findByCreativeId(creativeId);
    }

          实际上就是将分号替换成带左右大括号的部分, 而 return 语句里可变的部分是方法名称 (findByCreativeId)、方法列表 (creativeId) , 因此,需要从方法签名中提取出方法名称和参数列表。正则表达式尤其适合做这件事。 

      编写正则表达式的技巧:

          1.  从简单的表达式写起, 反复试验,逐步逼近;
          2.  分而治之:将要匹配的字符串分解成有意义的小组,分别写出匹配小组的子正则表达式,然后组合成完整的正则表达式;   
          3.  为了兼容处理输入含前导后缀空格的情况,通常在正则表达式前后添加 s*Regexs* 。

     

         从最简单的开始 

      编写正则表达式,从最简单的开始。先处理只有一个参数的方法签名,可以拆解为方法名称及方法参数列表两个部分:

    val methodNameRegexStr = "\s*(?:\w+\s+)?\w+<?\w+>?\s+(\w+)"
    val singleParamRegexStr = "[^,]*\w+<?\w+>?\s+(\w+)\s*"
    val simpleMethodSignRexStr = methodNameRegexStr + "\(" + singleParamRegexStr + "\)\s*;\s*"

      

      这里小小地使用到了"分而治之"的技巧。其中:

      1.  带访问修饰符或不带访问修饰符:  CreativeDO findByCreativeId(Long creativeId) 或  public CreativeDO findByCreativeId(Long creativeId) ;  这里使用了  (?:\w+\s+)? 来匹配带 public 或不带 public 的情况, ? 表示可有可无; (?:regex) 表示匹配 regex 的字符串但是并不捕获该分组,这是由于只要使用了小括号的正则都会被捕获,可以在后续引用匹配结果,但是这里 public 并不是我们感兴趣的目标,忽略掉;

          2.  参数可能是集合类型:  \w+<?\w+>? , 比如 List<String> , 这里不能匹配嵌套的结构, 比如 List<List<String>> 实际中在 DAO 层也很少出现;

      3.  方法名称使用了 (\w+) 来捕获;

      4.  方法参数中使用  \w+<?\w+>?  来匹配参数类型, 比如 Long 或 List<Long> ;  使用  [^,]* 来匹配参数类型前面的部分,比如  @Param("orderNo") ;  [^charset] 使用了字符集排除组,排除匹配 charset 指定的任何字符;   [^,]*\w+<?\w+>?\s+(\w+)\s* 可以匹配 Long creativeId 或 @Param("creativeId")  Long creativeId  ;

         5. 合并起来就是  simpleMethodSignRexStr 用来匹配单参数的方法签名。

       使用: 在字符串上调用方法 r 即可获取对应的正则表达式用来匹配; 使用 regex(values) = text 可以从 text 中抽取匹配 regex 中的分组的值 values 。  

    val methodSign = "  int insert(@Param("kdtId") BuyerAddressDO buyerAddressDO); "
    val simpleMethodSignRex = simpleMethodSignRexStr.r
    val simpleMethodSignRex(methodName, arg) = methodSign
    println(methodName + " " + arg)

         处理双参数

       有了匹配单参数方法签名的正则表达式, 又有匹配单参数的正则表达式, 编写处理双参数的方法签名就简单多了:  

        val twoParamMethodSignRegStr = methodNameRegexStr + "\(" + singleParamRegexStr + "," + singleParamRegexStr + "\);\s*"

       这里感受到了正则表达式复用的滋味了吧! ^_^

         使用:   

    val twoParamMethodSign = "OrderExpressDO getById(@Param("id") int id, @Param("kdtId") int kdtId); "
    val twoParamMethodSignRex = twoParamMethodSignRegStr.r
    val twoParamMethodSignRex(methodName2, arg1, arg2) = twoParamMethodSign
    println(List(methodName2, arg1 + ", " + arg2))

      

         处理任意多个参数

       通常扩展下双参数情况就可以了。 

        //val generalParamMethodSignRegStr = methodNameRegexStr + "\((" + singleParamRegexStr + "(?:," + singleParamRegexStr + ")*)\);\s*"

         不过,事实却没有那么美好。即使这样能够匹配整个方法签名,却只能捕获第一个参数和最后一个参数,中间的参数会被忽略。怎么办了? 查看 Scala 正则表达式的部分寻找答案,发现抽取器似乎能够解决整个问题。

      抽取器

      抽取器并不是什么特别的事物,只是 Scala 增加的一个语法糖,用于从对象值中抽取出感兴趣的值或值集合;就好比 Java 里实现了迭代器方法的对象都可以使用 foreach 遍历一样。实际上, val twoParamMethodSignRex(methodName2, arg1, arg2) = twoParamMethodSign 已经使用到了抽取器的语法,这里正则表达式实现了抽取器的功能。只要对象实现了 unapply 或 unapplySeq 就可以使用抽取器语法。 对于解析方法签名这个例子,可以先将参数列表使用正则表达式抽取出来,然后用逗号分隔,最后使用单参数正则表达式(又一次复用!) 抽取,实现如下:

    
    
    val generalParamMethodSignRegStr = methodNameRegexStr + "\((.*)\);\s*"
    object Method {
    
            def unapplySeq(methodSign:String): Option[(String, Seq[String])] = {
                try {
                    val generalParamMethodSignReg = generalParamMethodSignRegStr.r
                    val generalParamMethodSignReg(methodName, args) = methodSign
                    val params = args.split(',').toList
                    val argNames = params.map(extractArgName(_))
                    println("parsed: " + Some(methodName, argNames))
                    Some(methodName, argNames)
                } catch {
                    case _ => Some("", List())
                }
    
            }
    
            def extractArgName(singleParam: String): String = {
                val singleParamRegex = singleParamRegexStr.r
                val singleParamRegex(argName) = singleParam
                return argName
            }
    
        }

      然后可以这样使用:

       generalParamMethodSign match {
                case Method(methodName, firstArg, restArgs @ _*) =>
                    println("Case Match Way: " + methodName + "(" + firstArg + ", " + restArgs.mkString(", ") + ")")
                case _ => println("Not matched")
            }
    
            val Method(methodName5, firstArg, restArgs @ _*) = generalParamMethodSign
            println("Extractor Way: " + methodName5 + "(" + firstArg + ", " + restArgs.mkString(", ") + ")")

      其中: firstArg 是抽取列表的第一个元素, restArgs 是抽取列表的剩余元素。如果写成  val Method(methodName, args) = generalParamMethodSign 是不行的,估计是因为列表是通过链表的形式实现的: 列表总是第一个元素链接到剩余元素的列表。

         

      完整代码:

    package scalastudy.basic
    
    import java.io.PrintWriter
    
    import scalastudy.utils.DefaultFileUtil._
    import scalastudy.utils.PathConstants
    
    /**
     * Created by shuqin on 16/4/22.
     */
    object AutoGenerateJavaCodes extends App {
    
        val methodNameRegexStr = "\s*(?:\w+\s+)?\w+<?\w+>?\s+(\w+)"
        val singleParamRegexStr = "[^,]*\w+<?\w+>?\s+(\w+)\s*"
        val simpleMethodSignRexStr = methodNameRegexStr + "\(" + singleParamRegexStr + "\)\s*;\s*"
        val twoParamMethodSignRegStr = methodNameRegexStr + "\(" + singleParamRegexStr + "," + singleParamRegexStr + "\);\s*"
        //val generalParamMethodSignRegStr = methodNameRegexStr + "\((" + singleParamRegexStr + "(?:," + singleParamRegexStr + ")*)\);\s*"
        val generalParamMethodSignRegStr = methodNameRegexStr + "\((.*)\);\s*"
    
        val apiPackageName = "cc.lovesqcc"
    
        launch()
    
    
        object Method {
    
            def unapplySeq(methodSign:String): Option[(String, Seq[String])] = {
                try {
                    val generalParamMethodSignReg = generalParamMethodSignRegStr.r
                    val generalParamMethodSignReg(methodName, args) = methodSign
                    val params = args.split(',').toList
                    val argNames = params.map(extractArgName(_))
                    println("parsed: " + Some(methodName, argNames))
                    Some(methodName, argNames)
                } catch {
                    case _ => Some("", List())
                }
    
            }
    
            def extractArgName(singleParam: String): String = {
                val singleParamRegex = singleParamRegexStr.r
                val singleParamRegex(argName) = singleParam
                return argName
            }
    
        }
    
        def generateJavaFile(filepath: String): List[Any] = {
    
            val basePath = "/tmp/"
            val biz = List("order", "payment")
    
            var writePath = basePath
            var bizType = ""
            biz.foreach { e =>
                if (filepath.contains(e)) {
                    writePath = writePath + "/" + e + "/"
                    bizType = e
                }
            }
            val daoFileName = extraFilename(filepath)
            val daoClassName = daoFileName.substring(0, daoFileName.indexOf('.'))
            val writeFilename = writePath + daoFileName.replaceAll("DAO", "ServiceImpl")
            val serviceClassName = daoClassName.replace("DAO", "ServiceImpl")
            val daoRefName = firstLower(daoClassName)
            val lines = readFileLines(filepath)
            var fileContents = ""
            var daoFlag = false
            lines.foreach { line =>
                if (daoFlag) {
                    fileContents += "
    	@Resource
    "
                    fileContents += "	private " + daoClassName + " " + daoRefName + ";
    
    "
                    daoFlag = false
                }
                else if (line.contains("interface")) {
                    fileContents += "@Service
    public class " + serviceClassName + " implements " + daoClassName.replace("DAO", "Service") + " { 
    "
                    daoFlag = true
                }
                else if (line.contains(";")) {
                    if (!line.contains("import") && !line.contains("package")) {
                        val parsed = parseMethod(line)
                        val replaceStr = " {
    		return " + daoRefName + "." + parsed(0) + "(" + parsed(1) + ");
    	}
    "
                        var assertQualifier = ""
                        if (!line.contains("public")) {
                            assertQualifier = "public "
                        }
                        fileContents += "	" + assertQualifier + " " + line.trim.replace(";", replaceStr)
                    }
                    else if (line.contains("package")) {
                        fileContents += line.replace("dao", "service") + "
    
    "
                        var bizTypeStr = "."
                        if (bizType.length > 0) {
                            bizTypeStr = "." + bizType + "."
                        }
                        fileContents += "import " + apiPackageName + bizTypeStr + "service." + daoClassName.replace("DAO", "Service") + ";
    "
                        fileContents += "import javax.annotation.Resource;
    "
                        fileContents += "import org.springframework.stereotype.Service;
    "
                    }
                    else {
                        fileContents += line + "
    "
                    }
                }
                else {
                    fileContents += line + "
    "
                }
            }
    
            mkdir(writePath)
            val writeFile = new PrintWriter(writeFilename)
            writeFile.println(fileContents)
            writeFile.close
    
            return List[Any]();
        }
    
        def daoRefName(daoClassName: String): String = {
            return firstLower(daoClassName)
        }
    
        def firstLower(str: String): String = {
            return str.charAt(0).toLower + str.substring(1)
        }
    
        def parseMethod(methodSign: String): List[String] = {
    
            val simpleMethodSignRex = simpleMethodSignRexStr.r
            try {
                val Method(methodName, firstArg, restArgs @ _*) = methodSign
                if (restArgs.size == 0) {
                    return List(methodName, firstArg)
                }
                else {
                    return List(methodName, firstArg + ", " + restArgs.mkString(", "))
                }
    
            } catch {
                case _ => return List("", "")
            }
    
    
        }
    
    
        def debug(): Unit = {
    
            // simple catch regex groups
            val methodSign = "  int insert(@Param("kdtId") BuyerAddressDO buyerAddressDO); "
            val simpleMethodSignRex = simpleMethodSignRexStr.r
            val simpleMethodSignRex(methodName, arg) = methodSign
            println(methodName + " " + arg)
    
            val twoParamMethodSign = "OrderExpressDO getById(@Param("id") int id, @Param("kdtId") int kdtId); "
            val twoParamMethodSignRex = twoParamMethodSignRegStr.r
            val twoParamMethodSignRex(methodName2, arg1, arg2) = twoParamMethodSign
            println(List(methodName2, arg1 + ", " + arg2))
    
            val text = "Good query(@Param("goodId") goodId, int kdtId)"
            val regex = "\s*(?:\w+\s+)?\w+<?\w+>?\s+(\w+)\((.*)\)".r
            val regex(methodName3, wholearg1) = text
            println(methodName3 + " " + wholearg1);
    
            val generalParamMethodSign = " OrderDO queryOrder(@Param("orderNos") List<String> orderNos, @Param("page") Integer page, @Param("pageSize") Integer pageSize, boolean flag); "
            val regex2 = generalParamMethodSignRegStr.r
            val regex2(methodName4, wholearg2) = generalParamMethodSign
            println(methodName4 + " : " + wholearg2);
    
            generalParamMethodSign match {
                case Method(methodName, firstArg, restArgs @ _*) =>
                    println("Case Match Way: " + methodName + "(" + firstArg + ", " + restArgs.mkString(", ") + ")")
                case _ => println("Not matched")
            }
    
            val Method(methodName5, firstArg, restArgs @ _*) = generalParamMethodSign
            println("Extractor Way: " + methodName5 + "(" + firstArg + ", " + restArgs.mkString(", ") + ")")
        }
    
        def testParseMethod(): Unit = {
            val testMethods = Map(
                " List<OrderDO> queryOrder(int kdtId); " -> List("queryOrder", "kdtId"),
                " List<OrderDO> queryOrder( int kdtId ); " -> List("queryOrder", "kdtId"),
                " OrderDO queryOrder(@Param("kdtId") int kdtId); " -> List("queryOrder", "kdtId"),
                " List<OrderDO> queryOrder(List<String> orderNos); " -> List("queryOrder", "orderNos"),
                " List<OrderDO> queryOrder(@Param("orderNos") List<String> orderNos); " -> List("queryOrder", "orderNos"),
                " OrderDO queryOrder(String orderNo, Integer kdtId); " -> List("queryOrder", "orderNo, kdtId"),
                " OrderDO queryOrder(String orderNo, @Param("kdtId") Integer kdtId); " -> List("queryOrder", "orderNo, kdtId"),
                " OrderDO queryOrder(@Param("orderNo") String orderNo, Integer kdtId); " -> List("queryOrder", "orderNo, kdtId"),
                " OrderDO queryOrder(@Param("orderNo") String orderNo, @Param("kdtId") Integer kdtId); " -> List("queryOrder", "orderNo, kdtId"),
                " OrderDO queryOrder(List<String> orderNos, Integer kdtId); 
    " -> List("queryOrder", "orderNos, kdtId"),
                " OrderDO queryOrder(@Param("orderNos") List<String> orderNos, Integer kdtId); " -> List("queryOrder", "orderNos, kdtId"),
                " OrderDO queryOrder(List<String> orderNos, @Param("kdtId") Integer kdtId); " -> List("queryOrder", "orderNos, kdtId"),
                " OrderDO queryOrder(@Param("orderNos") List<String> orderNos, @Param("kdtId") Integer kdtId); " -> List("queryOrder", "orderNos, kdtId"),
                " OrderDO queryOrder(@Param("orderNos") List<String> orderNos, @Param("page") Integer page, @Param("pageSize") Integer pageSize); " -> List("queryOrder", "orderNos, page, pageSize"))
            testMethods.foreach { testMethod =>
                println("test method: " + testMethod._1)
                val parsed = parseMethod(testMethod._1)
                println(parsed)
                assert(parseMethod(testMethod._1) == testMethod._2)
            }
            println("test ParseMethod passed.")
        }
    
        def launch(): Unit = {
    
            debug
            testParseMethod
    
            val dirpath = "/tmp/";
            handleFiles(fetchAllFiles)((file: String) => file.endsWith("DAO.java"))(List(generateJavaFile(_)))((liststr: List[Any]) => "")(dirpath);
        }
    
    }
  • 相关阅读:
    spring jdbctemplate 集成duckdb docker 镜像
    GLIBCXX_3.4.x 问题解决
    spring jdbctemplate 集成duckdb
    spring-boot-starter-data-jdbc Cannot determine a dialect Please provide a Dialect
    kotlin 学习 三 maven 集成+java 互调用
    kotlin 学习 二 命令行模式运行
    kotlin 学习 一 环境搭建
    pt-ioprofile io 瓶颈分析工具
    zombodb 3000 alpha1发布
    openfeign+retronfit http 访问
  • 原文地址:https://www.cnblogs.com/lovesqcc/p/5450238.html
Copyright © 2020-2023  润新知