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) , 因此,需要从方法签名中提取出方法名称和参数列表。正则表达式尤其适合做这件事。
编写正则表达式的技巧:
从最简单的开始
编写正则表达式,从最简单的开始。先处理只有一个参数的方法签名,可以拆解为方法名称及方法参数列表两个部分:
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\t@Resource\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);
} }
Scala正则和抽取器:解析方法参数的更多相关文章
- Java获取方法参数名、Spring SpEL解析
@Test public void testParse() { //表达式解析 ExpressionParser expressionParser = new SpelExpressionParser ...
- 一个解析url参数方法
function getRequestParameter(a) { var b = document.location.search || document.location.hash; if (a ...
- springmvc 方法参数自定义的解析
1.实现HandlerMethodArgumentResolver接口: 2.在配置文件中添加配置<mvc:argument-resolvers> <bean class=&qu ...
- 关于laravel5.5控制器方法参数依赖注入原理深度解析及问题修复
在laravel5.5中,可以根据控制器方法的参数类型,自动注入一个实例化对象,极大提升了编程的效率,但是相比较与Java的SpringMVC框架,功能还是有所欠缺,使用起来还是不太方便,主要体现在方 ...
- springMVC源码解析--HandlerMethodArgumentResolverComposite参数解析器集合(二)
上一篇博客springMVC源码分析--HandlerMethodArgumentResolver参数解析器(一)中我们已经介绍了参数解析相关的东西,并且也提到了HandlerMethodArgume ...
- bean的创建(五)第三部分 bean工厂方法参数的解析
准备好一系列参数之后,开始参数类型的转换,方法参数的对应. ConstructorResolver.createArgumentArray private ArgumentsHolder create ...
- HandlerMethodReturnValueHandler SpringMVC 参数解析 继承关系以及各解析器解析类型
I HandlerMethodReturnValueHandler (org.springframework.web.method.support) AbstractMessageConverterM ...
- HandlerMethodArgumentResolver SpringMVC 参数解析 继承关系以及各解析器解析类型
HandlerMethodArgumentResolver SpringMVC 参数解析 继承关系以及各解析器解析类型 I HandlerMethodArgumentResolver (org.spr ...
- typescript装饰器 方法装饰器 方法参数装饰器 装饰器的执行顺序
/* 装饰器:装饰器是一种特殊类型的声明,它能够被附加到类声明,方法,属性或参数上,可以修改类的行为. 通俗的讲装饰器就是一个方法,可以注入到类.方法.属性参数上来扩展类.属性.方法.参数的功能. 常 ...
随机推荐
- js判断图片加载完成后获取图片实际宽高
通常,我们会用jq的.width()/.height()方法获取图片的宽度/高度或者用js的.offsetwidth/.offsetheight方法来获取图片的宽度/高度,但这些方法在我们通过样式设置 ...
- JDBC的事务处理
JDBC的事务处理 事务,也是数据库事务,指的是作为单个逻辑工作单元执行的一系列操作.正常的情况下,操作应该顺利进行,与操作相关的所有数据库信息也成功地更新: 但是,如果在这一系列过程中任何一个环节出 ...
- android-ContentProvider
一.理解ContentProvider 为了在应用程序之间交换数据,Android提供了ContentProvider,它是不同应用程序之间进行数据交换的标准API,当一个应用程序需要把自己的数据暴露 ...
- Linux_Shell
一.Shell 种类与归属 Unix与Linux常见的Shell脚本解释器有bash,sh,csh,ksh等(PS: bash 完全兼容sh) bash : linux 默认的shell sh : u ...
- Windows8.1自定义快捷方式添加到开始屏幕
Windows8.1自定义快捷方式添加到开始屏幕 将快捷方式复制到如下路径,在开始屏幕的所有中找到对应快捷方式,右键选择添加到开始屏幕即可. C:\Users\%USERNAME%\AppData\R ...
- 段落的展开收起(substring的应用)
<!DOCTYPE HTML> <html> <head> <meta http-equiv="Content-Type" content ...
- 理解insert all/insert first的使用
在常用的SQL写法中我们会经常遇到把一个表的数据插入另外一张表的情况,这是一个insert into 表名 select .... from 表名 就可以解决了.但是如果是把一张表的数据同时插入两 ...
- Linux常用命令(持续更新)
lsb_release -a 查看linux操作系统信息 getconf LONG_BIT 查看linux操作系统位数 useradd [-g groupname] username 创建用户,并指定 ...
- zabbix_agent 步骤
Zabbix server 做好了,只要在安装一个zabbix-agent(监控端就可以啦) groupadd zabbix useradd -g zabbix zabbix 下载一个客户端的安装包: ...
- graph | hungary
匈牙利算法,求二分图最大匹配. 若P是图G中一条连通两个未匹配顶点的路径,并且属于M的边和不属于M的边(即已匹配和待匹配的边)在P上交替出现,则称P为相对于M的一条增广路径.(M为一个匹配) 由增广路 ...