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

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

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

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

  编写正则表达式的技巧:

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

 

从最简单的开始 

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

  1. val methodNameRegexStr = "\\s*(?:\\w+\\s+)?\\w+<?\\w+>?\\s+(\\w+)"
  2. val singleParamRegexStr = "[^,]*\\w+<?\\w+>?\\s+(\\w+)\\s*"
  3. 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 。  

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

处理双参数

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

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

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

使用: 

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

  

处理任意多个参数

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

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

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

  抽取器

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

  1.  
  1. val generalParamMethodSignRegStr = methodNameRegexStr + "\\((.*)\\);\\s*"
  1. object Method {
  2.  
  3. def unapplySeq(methodSign:String): Option[(String, Seq[String])] = {
  4. try {
  5. val generalParamMethodSignReg = generalParamMethodSignRegStr.r
  6. val generalParamMethodSignReg(methodName, args) = methodSign
  7. val params = args.split(',').toList
  8. val argNames = params.map(extractArgName(_))
  9. println("parsed: " + Some(methodName, argNames))
  10. Some(methodName, argNames)
  11. } catch {
  12. case _ => Some("", List())
  13. }
  14.  
  15. }
  16.  
  17. def extractArgName(singleParam: String): String = {
  18. val singleParamRegex = singleParamRegexStr.r
  19. val singleParamRegex(argName) = singleParam
  20. return argName
  21. }
  22.  
  23. }

  然后可以这样使用:

  1.    generalParamMethodSign match {
  2. case Method(methodName, firstArg, restArgs @ _*) =>
  3. println("Case Match Way: " + methodName + "(" + firstArg + ", " + restArgs.mkString(", ") + ")")
  4. case _ => println("Not matched")
  5. }
  6.  
  7. val Method(methodName5, firstArg, restArgs @ _*) = generalParamMethodSign
  8. println("Extractor Way: " + methodName5 + "(" + firstArg + ", " + restArgs.mkString(", ") + ")")

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

    

  完整代码:

  1. package scalastudy.basic
  2.  
  3. import java.io.PrintWriter
  4.  
  5. import scalastudy.utils.DefaultFileUtil._
  6. import scalastudy.utils.PathConstants
  7.  
  8. /**
  9. * Created by shuqin on 16/4/22.
  10. */
  11. object AutoGenerateJavaCodes extends App {
  12.  
  13. val methodNameRegexStr = "\\s*(?:\\w+\\s+)?\\w+<?\\w+>?\\s+(\\w+)"
  14. val singleParamRegexStr = "[^,]*\\w+<?\\w+>?\\s+(\\w+)\\s*"
  15. val simpleMethodSignRexStr = methodNameRegexStr + "\\(" + singleParamRegexStr + "\\)\\s*;\\s*"
  16. val twoParamMethodSignRegStr = methodNameRegexStr + "\\(" + singleParamRegexStr + "," + singleParamRegexStr + "\\);\\s*"
  17. //val generalParamMethodSignRegStr = methodNameRegexStr + "\\((" + singleParamRegexStr + "(?:," + singleParamRegexStr + ")*)\\);\\s*"
  18. val generalParamMethodSignRegStr = methodNameRegexStr + "\\((.*)\\);\\s*"
  19.  
  20. val apiPackageName = "cc.lovesqcc"
  21.  
  22. launch()
  23.  
  24. object Method {
  25.  
  26. def unapplySeq(methodSign:String): Option[(String, Seq[String])] = {
  27. try {
  28. val generalParamMethodSignReg = generalParamMethodSignRegStr.r
  29. val generalParamMethodSignReg(methodName, args) = methodSign
  30. val params = args.split(',').toList
  31. val argNames = params.map(extractArgName(_))
  32. println("parsed: " + Some(methodName, argNames))
  33. Some(methodName, argNames)
  34. } catch {
  35. case _ => Some("", List())
  36. }
  37.  
  38. }
  39.  
  40. def extractArgName(singleParam: String): String = {
  41. val singleParamRegex = singleParamRegexStr.r
  42. val singleParamRegex(argName) = singleParam
  43. return argName
  44. }
  45.  
  46. }
  47.  
  48. def generateJavaFile(filepath: String): List[Any] = {
  49.  
  50. val basePath = "/tmp/"
  51. val biz = List("order", "payment")
  52.  
  53. var writePath = basePath
  54. var bizType = ""
  55. biz.foreach { e =>
  56. if (filepath.contains(e)) {
  57. writePath = writePath + "/" + e + "/"
  58. bizType = e
  59. }
  60. }
  61. val daoFileName = extraFilename(filepath)
  62. val daoClassName = daoFileName.substring(0, daoFileName.indexOf('.'))
  63. val writeFilename = writePath + daoFileName.replaceAll("DAO", "ServiceImpl")
  64. val serviceClassName = daoClassName.replace("DAO", "ServiceImpl")
  65. val daoRefName = firstLower(daoClassName)
  66. val lines = readFileLines(filepath)
  67. var fileContents = ""
  68. var daoFlag = false
  69. lines.foreach { line =>
  70. if (daoFlag) {
  71. fileContents += "\n\t@Resource\n"
  72. fileContents += "\tprivate " + daoClassName + " " + daoRefName + ";\n\n"
  73. daoFlag = false
  74. }
  75. else if (line.contains("interface")) {
  76. fileContents += "@Service\npublic class " + serviceClassName + " implements " + daoClassName.replace("DAO", "Service") + " { \n"
  77. daoFlag = true
  78. }
  79. else if (line.contains(";")) {
  80. if (!line.contains("import") && !line.contains("package")) {
  81. val parsed = parseMethod(line)
  82. val replaceStr = " {\n\t\treturn " + daoRefName + "." + parsed(0) + "(" + parsed(1) + ");\n\t}\n"
  83. var assertQualifier = ""
  84. if (!line.contains("public")) {
  85. assertQualifier = "public "
  86. }
  87. fileContents += "\t" + assertQualifier + " " + line.trim.replace(";", replaceStr)
  88. }
  89. else if (line.contains("package")) {
  90. fileContents += line.replace("dao", "service") + "\n\n"
  91. var bizTypeStr = "."
  92. if (bizType.length > 0) {
  93. bizTypeStr = "." + bizType + "."
  94. }
  95. fileContents += "import " + apiPackageName + bizTypeStr + "service." + daoClassName.replace("DAO", "Service") + ";\n"
  96. fileContents += "import javax.annotation.Resource;\n"
  97. fileContents += "import org.springframework.stereotype.Service;\n"
  98. }
  99. else {
  100. fileContents += line + "\n"
  101. }
  102. }
  103. else {
  104. fileContents += line + "\n"
  105. }
  106. }
  107.  
  108. mkdir(writePath)
  109. val writeFile = new PrintWriter(writeFilename)
  110. writeFile.println(fileContents)
  111. writeFile.close
  112.  
  113. return List[Any]();
  114. }
  115.  
  116. def daoRefName(daoClassName: String): String = {
  117. return firstLower(daoClassName)
  118. }
  119.  
  120. def firstLower(str: String): String = {
  121. return str.charAt(0).toLower + str.substring(1)
  122. }
  123.  
  124. def parseMethod(methodSign: String): List[String] = {
  125.  
  126. val simpleMethodSignRex = simpleMethodSignRexStr.r
  127. try {
  128. val Method(methodName, firstArg, restArgs @ _*) = methodSign
  129. if (restArgs.size == 0) {
  130. return List(methodName, firstArg)
  131. }
  132. else {
  133. return List(methodName, firstArg + ", " + restArgs.mkString(", "))
  134. }
  135.  
  136. } catch {
  137. case _ => return List("", "")
  138. }
  139.  
  140. }
  141.  
  142. def debug(): Unit = {
  143.  
  144. // simple catch regex groups
  145. val methodSign = " int insert(@Param(\"kdtId\") BuyerAddressDO buyerAddressDO); "
  146. val simpleMethodSignRex = simpleMethodSignRexStr.r
  147. val simpleMethodSignRex(methodName, arg) = methodSign
  148. println(methodName + " " + arg)
  149.  
  150. val twoParamMethodSign = "OrderExpressDO getById(@Param(\"id\") int id, @Param(\"kdtId\") int kdtId); "
  151. val twoParamMethodSignRex = twoParamMethodSignRegStr.r
  152. val twoParamMethodSignRex(methodName2, arg1, arg2) = twoParamMethodSign
  153. println(List(methodName2, arg1 + ", " + arg2))
  154.  
  155. val text = "Good query(@Param(\"goodId\") goodId, int kdtId)"
  156. val regex = "\\s*(?:\\w+\\s+)?\\w+<?\\w+>?\\s+(\\w+)\\((.*)\\)".r
  157. val regex(methodName3, wholearg1) = text
  158. println(methodName3 + " " + wholearg1);
  159.  
  160. val generalParamMethodSign = " OrderDO queryOrder(@Param(\"orderNos\") List<String> orderNos, @Param(\"page\") Integer page, @Param(\"pageSize\") Integer pageSize, boolean flag); "
  161. val regex2 = generalParamMethodSignRegStr.r
  162. val regex2(methodName4, wholearg2) = generalParamMethodSign
  163. println(methodName4 + " : " + wholearg2);
  164.  
  165. generalParamMethodSign match {
  166. case Method(methodName, firstArg, restArgs @ _*) =>
  167. println("Case Match Way: " + methodName + "(" + firstArg + ", " + restArgs.mkString(", ") + ")")
  168. case _ => println("Not matched")
  169. }
  170.  
  171. val Method(methodName5, firstArg, restArgs @ _*) = generalParamMethodSign
  172. println("Extractor Way: " + methodName5 + "(" + firstArg + ", " + restArgs.mkString(", ") + ")")
  173. }
  174.  
  175. def testParseMethod(): Unit = {
  176. val testMethods = Map(
  177. " List<OrderDO> queryOrder(int kdtId); " -> List("queryOrder", "kdtId"),
  178. " List<OrderDO> queryOrder( int kdtId ); " -> List("queryOrder", "kdtId"),
  179. " OrderDO queryOrder(@Param(\"kdtId\") int kdtId); " -> List("queryOrder", "kdtId"),
  180. " List<OrderDO> queryOrder(List<String> orderNos); " -> List("queryOrder", "orderNos"),
  181. " List<OrderDO> queryOrder(@Param(\"orderNos\") List<String> orderNos); " -> List("queryOrder", "orderNos"),
  182. " OrderDO queryOrder(String orderNo, Integer kdtId); " -> List("queryOrder", "orderNo, kdtId"),
  183. " OrderDO queryOrder(String orderNo, @Param(\"kdtId\") Integer kdtId); " -> List("queryOrder", "orderNo, kdtId"),
  184. " OrderDO queryOrder(@Param(\"orderNo\") String orderNo, Integer kdtId); " -> List("queryOrder", "orderNo, kdtId"),
  185. " OrderDO queryOrder(@Param(\"orderNo\") String orderNo, @Param(\"kdtId\") Integer kdtId); " -> List("queryOrder", "orderNo, kdtId"),
  186. " OrderDO queryOrder(List<String> orderNos, Integer kdtId); \n" -> List("queryOrder", "orderNos, kdtId"),
  187. " OrderDO queryOrder(@Param(\"orderNos\") List<String> orderNos, Integer kdtId); " -> List("queryOrder", "orderNos, kdtId"),
  188. " OrderDO queryOrder(List<String> orderNos, @Param(\"kdtId\") Integer kdtId); " -> List("queryOrder", "orderNos, kdtId"),
  189. " OrderDO queryOrder(@Param(\"orderNos\") List<String> orderNos, @Param(\"kdtId\") Integer kdtId); " -> List("queryOrder", "orderNos, kdtId"),
  190. " OrderDO queryOrder(@Param(\"orderNos\") List<String> orderNos, @Param(\"page\") Integer page, @Param(\"pageSize\") Integer pageSize); " -> List("queryOrder", "orderNos, page, pageSize"))
  191. testMethods.foreach { testMethod =>
  192. println("test method: " + testMethod._1)
  193. val parsed = parseMethod(testMethod._1)
  194. println(parsed)
  195. assert(parseMethod(testMethod._1) == testMethod._2)
  196. }
  197. println("test ParseMethod passed.")
  198. }
  199.  
  200. def launch(): Unit = {
  201.  
  202. debug
  203. testParseMethod
  204.  
  205. val dirpath = "/tmp/";
  206. handleFiles(fetchAllFiles)((file: String) => file.endsWith("DAO.java"))(List(generateJavaFile(_)))((liststr: List[Any]) => "")(dirpath);
  207. }
  208.  
  209. }

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

  1. Java获取方法参数名、Spring SpEL解析

    @Test public void testParse() { //表达式解析 ExpressionParser expressionParser = new SpelExpressionParser ...

  2. 一个解析url参数方法

    function getRequestParameter(a) { var b = document.location.search || document.location.hash; if (a ...

  3. springmvc 方法参数自定义的解析

    1.实现HandlerMethodArgumentResolver接口: 2.在配置文件中添加配置<mvc:argument-resolvers>   <bean class=&qu ...

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

    在laravel5.5中,可以根据控制器方法的参数类型,自动注入一个实例化对象,极大提升了编程的效率,但是相比较与Java的SpringMVC框架,功能还是有所欠缺,使用起来还是不太方便,主要体现在方 ...

  5. springMVC源码解析--HandlerMethodArgumentResolverComposite参数解析器集合(二)

    上一篇博客springMVC源码分析--HandlerMethodArgumentResolver参数解析器(一)中我们已经介绍了参数解析相关的东西,并且也提到了HandlerMethodArgume ...

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

    准备好一系列参数之后,开始参数类型的转换,方法参数的对应. ConstructorResolver.createArgumentArray private ArgumentsHolder create ...

  7. HandlerMethodReturnValueHandler SpringMVC 参数解析 继承关系以及各解析器解析类型

    I HandlerMethodReturnValueHandler (org.springframework.web.method.support) AbstractMessageConverterM ...

  8. HandlerMethodArgumentResolver SpringMVC 参数解析 继承关系以及各解析器解析类型

    HandlerMethodArgumentResolver SpringMVC 参数解析 继承关系以及各解析器解析类型 I HandlerMethodArgumentResolver (org.spr ...

  9. typescript装饰器 方法装饰器 方法参数装饰器 装饰器的执行顺序

    /* 装饰器:装饰器是一种特殊类型的声明,它能够被附加到类声明,方法,属性或参数上,可以修改类的行为. 通俗的讲装饰器就是一个方法,可以注入到类.方法.属性参数上来扩展类.属性.方法.参数的功能. 常 ...

随机推荐

  1. Fling——K

    K. Fling Fling is a kind of puzzle games available on phone.This game is played on a board with 7 ro ...

  2. [MetroUI-1]无边框模式

    Wpf中取消边框,使用 WindowStyle="None" AllowsTransparency="True"

  3. [LintCode] Valid Palindrome 验证回文字符串

    Given a string, determine if it is a palindrome, considering only alphanumeric characters and ignori ...

  4. android-Intent and IntentFilter

    一.Intent简介 Android使用Intent来封装程序的调用"意图",Activity.Service.BroadcastReceiver三种重要的组件都是依靠Intent ...

  5. 常用JS表单验证方法

    /*输入:str返回:如果全是空返回true,否则返回false*/function isNull(str) {if (str == "") return true;var reg ...

  6. td内容过长,省略号表示

    .word{ min-width:100px; max-width:200px; overflow:hidden; white-space:nowrap; text-overflow:ellipsis ...

  7. # 20145334赵文豪 《Java程序设计》第6周学习总结

    20145334赵文豪 <Java程序设计>第6周学习总结 教材学习内容总结 第十章 输入/输出 数据流 I/O操作主要是指使用Java进行输入,输出操作. Java所有的I/O机制都是基 ...

  8. Cookie案例-显示商品浏览历史纪录

    package cn.itcast.cookie; import java.io.IOException; import java.io.PrintWriter; import java.util.D ...

  9. DOM概述

    <!-- DOM:Document Object Model 文档对象模型 用来将标记型文档封装成对象,并将标记型文档中的所有内容(标签,文本,属性等)都封装成对象 封装成对象的目的是为了更为方 ...

  10. 使用 Git@OSC 管理代码

    开源中国的 git 服务的地址是:http://git.oschina.net/ 以下记录 push 本地已有的项目至 git@osc 的过程. ① 注册登录之后,创建一个自己的项目: 创建好的默认项 ...