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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM