Parser Generator (二)

上一篇我们讲解怎麽产生目标 parser 的 parse 方法,这篇来讲解 generator 的内部结构,这会用到上篇提到的 getParseFuncSpec 。我们的 generator 会先继承一个基底类别,叫做 ParserGenerator ,里面是包含一些 generator 的基础方法和流程。

const val METHOD_GET_ATTR_OR_NULL = "getAttributeOrNull"
const val METHOD_GET_ELEMENT_BY_TAG = "getElementByTag"

class KotlinParserGenerator(
    private val element: Element,
    private val isRoot: Boolean,
    logger: Logger
) : ParserGenerator(logger) {

    private val outputClass = ClassName(element.getPackage(), element.simpleName.toString())
    private val exceptionClass = ClassName("java.lang", "IllegalStateException")
    private val docBuilderFactoryClass = ClassName("javax.xml.parsers", "DocumentBuilderFactory")
    private val elementClassName = ClassName("org.w3c.dom", "Element")
    private val listClassName = ClassName("java.util", "ArrayList")
    private val getAttributeOrNullMemberName = MemberName(extensionFullPath, METHOD_GET_ATTR_OR_NULL)
    private val getElementByTagMemberName = MemberName(extensionFullPath, METHOD_GET_ELEMENT_BY_TAG)

    override fun generate(): FileSpec {
        val generatedClassName = "${element.simpleName}$PARSER_SUFFIX"
        return FileSpec.builder(GENERATOR_PACKAGE, generatedClassName)
            .addType(getObjectTypeSpec(generatedClassName))
            .build()
    }

    private fun getObjectTypeSpec(className: String): TypeSpec {
        val builder = TypeSpec.objectBuilder(className)
        val outputClassName = element.simpleName.toString()

        if (isRoot) {
            builder.addFunction(getParseFuncSpec())
        }
        return builder
            .addFunction(getClassFunSpec(element, outputClassName, builder))
            .build()
    }

		private fun getParseFuncSpec(): FunSpec {
				// 略
		}

		private fun getClassFunSpec(
        rootElement: Element,
        outputClassName: String,
        objectBuilder: TypeSpec.Builder
    ): FunSpec {
				// 略
		}
}

在 annotation processor 侦测到 annotation 元素时,就会呼叫 parser generator 帮它产生对应的程序码,只要放入相对应的 Element 就可以,而在建构仔我们看到的 isRoot 指的是这个 element 是不是 channel tag 。之後,generator 的 generate 方法就会被呼叫,把用 KotlinPoet 产生的程序码写入档案中。上方程序码里的 getClassFunSpec 就是用来针对每个被标注 @RssTag 的类别产生对应的 parser 程序码,也是本篇的重点。要怎麽针对它产生对应的 parser 程序码?我们先想想产生出来的程序码应该要长怎麽样。假设我们有一个 data class RssItem

@RssTag(name = "item")
data class RssItem(
    val title: String?,
    val author: String?,
    val guid: TestGuid?
): Serializable

@RssTag(name = "guid")
data class TestGuid(
    @RssAttribute
    val isPermaLink: Boolean?
): Serializable

那它产生出来的程序码,预计要长成这样:

object RssItemParser {
	fun Element.getItem(): RssItem {
			// #1 Value Statement
			val titleTitle: String? = readString("title")
	    val titleItunesTitle: String? = readString("itunes:title")
	    val titleGoogleplayTitle: String? = readString("googleplay:title")
	    val authorAuthor: String? = readString("author")
	    val authorItunesAuthor: String? = readString("itunes:author")
	    val authorGoogleplayAuthor: String? = readString("googleplay:author")
	    val guidGuid: TestGuid? = getElementByTag("guid")?.getGuid()
	    val guidItunesGuid: TestGuid? = getElementByTag("itunes:guid")?.getGuid()
	    val guidGoogleplayGuid: TestGuid? = getElementByTag("googleplay:guid")?.getGuid()
	
			// #2 Use class constructor
      return RssItem(
	  		title = titleTitle ?: titleItunesTitle ?: titleGoogleplayTitle,
	  		author = authorAuthor ?: authorItunesAuthor ?: authorGoogleplayAuthor,
	  		guid = guidGuid ?: guidItunesGuid ?: guidGoogleplayGuid
	  	)
	}
}

为了要产生上面的程序码,我们可以把步骤拆成四个:

Generator process.drawio.png

  • Annotation Pre-processing
    • 预先处理一些 annotation ,将它们放在一个有名称和 annotation 的对照 map 里面。
  • Convert to ParseData
    • 将上个步骤的资讯,转成一个自定义的 data class ParseData
  • Generate Value Statement
    • ParseData 整理好的资讯来产生 value 宣告的程序码。(上面程序码标注 #1 的部分)
  • Generate Constructor
    • 接续上个步骤产生的 value 宣告,用它们来产生 constructor 的程序码。(上面程序码标注 #2 的部分)

下篇文章我会各别把这四个步骤用程序码来讲解他们的实作。


<<:  AI ninja project [day 25] QLattice -- 基础分类

>>:  docker上建立测试环境DVWA

Day2-看看JDK内有些什麽好用的工具!

前言 工作了好一段时间後,直到那次处理了OOM(Out Of Memory)问题,才发现JDK内有很...

如何下载安装 WordPress 站台,设定资料库连线,建立全新部落格 (适用 IIS 架站)

全球有超过 42% 的网站使用 WordPress 架设,WordPress 适合架设部落格、小型企...

JS Library 学习笔记:首先当然来试试 jQuery (三)

除了监听事件外,jQuery也提供了定义好的动态效果函式,让开发者直接使用,并透过传入相关参数,去自...

【20】从头自己建一个 keras 内建模型 (以 MobileNetV2 为例)

Colab连结 虽然 Tensorflow 提供了几个预训练模型让我们可以很快的完成训练任务,但是有...

Linux 系列的发行版,以及不同发行版之间的联系和区别

Linux 系列的主流发行版主要分为Red Hat Linux(包含CentOS和RHEL)和Ubu...