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*Regex\s* 。

 

从最简单的开始 

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

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 += "\n\[email protected]\n"
                fileContents += "\tprivate " + daoClassName + " " + daoRefName + ";\n\n"
                daoFlag = false
            }
            else if (line.contains("interface")) {
                fileContents += "@Service\npublic class " + serviceClassName + " implements " + daoClassName.replace("DAO", "Service") + " { \n"
                daoFlag = true
            }
            else if (line.contains(";")) {
                if (!line.contains("import") && !line.contains("package")) {
                    val parsed = parseMethod(line)
                    val replaceStr = " {\n\t\treturn " + daoRefName + "." + parsed(0) + "(" + parsed(1) + ");\n\t}\n"
                    var assertQualifier = ""
                    if (!line.contains("public")) {
                        assertQualifier = "public "
                    }
                    fileContents += "\t" + assertQualifier + " " + line.trim.replace(";", replaceStr)
                }
                else if (line.contains("package")) {
                    fileContents += line.replace("dao", "service") + "\n\n"
                    var bizTypeStr = "."
                    if (bizType.length > 0) {
                        bizTypeStr = "." + bizType + "."
                    }
                    fileContents += "import " + apiPackageName + bizTypeStr + "service." + daoClassName.replace("DAO", "Service") + ";\n"
                    fileContents += "import javax.annotation.Resource;\n"
                    fileContents += "import org.springframework.stereotype.Service;\n"
                }
                else {
                    fileContents += line + "\n"
                }
            }
            else {
                fileContents += line + "\n"
            }
        }

        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); \n" -> 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);
    }

}
时间: 2025-01-04 07:34:48

Scala正则和抽取器:解析方法参数的相关文章

SpringMVC ArgumentREsoler(方法参数解析器)

例: 先创建自定义注解 // @Target(ElementType.PARAMETER) //在运行时生效 //RetentionPolicy.RUNTIME 给方法使用 @Retention(RetentionPolicy.RUNTIME) public @interface CurrentUser { } 去定义自定义注解的规则 package com.lanou.demo.resolvers; import com.lanou.demo.annotation.CurrentUser; i

使用Scala基于词法单元的解析器定制EBNF范式文法解析

一.前言 近期在做Oracle迁移到Spark平台的项目上遇到了一些平台公式翻译为SparkSQL(on Hive)的需求,而Spark采用亲妈语言Scala进行开发.分析过大概需求过后,拟使用编译原理中的EBNF范式模式,进行基于词法的文法解析.于是拟采用传统的正则词法解析到EBNF文法解析的套路来实现,直到发现了StandardTokenParsers这个Scala基于词法单元的解析器类. 二.平台公式及翻译后的SparkSQL 平台公式的样子如下所示: 1 if(XX1_m001[D003

springmvc 方法参数自定义的解析

1.实现HandlerMethodArgumentResolver接口: 2.在配置文件中添加配置<mvc:argument-resolvers>   <bean class=""></bean>  </mvc:argument-resolvers> class文件是第一步中定义的class 3.定义注解用于只解析controller带有该注解的方法参数 自定义方法参数解析实现了前端和后端的数据的统一,比如将表单数据定义成bean,通过定

五种URL参数解析方法的性能比较

因为在最近项目中需要解析日志中的 URL 的参数,所以我对比了一下五种不同 的 URL 参数解析方法的性能.URL 参数解析方法: httpclient org.apache.http.client.utils.URLEncodedUtils URLEncodedUtils.parse(query, Charset.forName("UTF-8"));jettyUtil org.eclipse.jetty.util.UrlEncoded MultiMapvalues = new Mul

关于laravel5.5控制器方法参数依赖注入原理深度解析及问题修复

在laravel5.5中,可以根据控制器方法的参数类型,自动注入一个实例化对象,极大提升了编程的效率,但是相比较与Java的SpringMVC框架,功能还是有所欠缺,使用起来还是不太方便,主要体现在方法参数的注入不完全是按照参数名称进行的,如果改变了传入参数的顺序会导致类型不匹配的错误. 一.控制器方法参数注入步骤设计 1.在/routes/web.php中添加路由 Route::get('/diary/show/{diary}/{page?}','Diary\[email protected]

bean的创建(五)第三部分 bean工厂方法参数的解析

准备好一系列参数之后,开始参数类型的转换,方法参数的对应. ConstructorResolver.createArgumentArray private ArgumentsHolder createArgumentArray( String beanName, RootBeanDefinition mbd, ConstructorArgumentValues resolvedValues, BeanWrapper bw, Class<?>[] paramTypes, String[] par

jQuery中$.ajax()方法参数解析

本文实例为大家讲解了jQuery $.ajax()方法参数,供大家参考,具体内容如下 $.ajax({ url:'test.do', data:{id:123,name:'xiaoming'}, type:'post', dataType:'json', success:function(data){ alert(data);//弹窗 //TODO ........ }, error:function(data){ alert(data);//弹窗 //TODO ........ } }) ur

python进行文档抽取与解析的简单实现

一.前文 之前被叫去做网络爬虫,爬取新浪新闻的url,标题,内容和评论,不过在需求上有点改变,主要是评论的间隔被要求有'\t'的分割,比如将 <comment> 2014-12-10 18:53:20    1004400533   遗弃亲生骨肉猪狗不如,难道就不怕受到良心谴责?你能睡得安稳? 2014-12-10 17:17:07    3294923134   这父亲是人吗? </comment> 改为 <comment> 2014-12-10 18:53:20 1

Document树的解析方法

一.本次总结用到的xml文本 1.    <?xml version="1.0" encoding="UTF-8" standalone="no"?>        <!-- 引进dtd文件的标签 --><!-- <!DOCTYPE 书架 SYSTEM "book.dtd"> -->        <书架>            <书 ISBN="a&