上一篇我們講解怎麼產生目標 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
)
}
}
為了要產生上面的程式碼,我們可以把步驟拆成四個:
ParseData
ParseData
整理好的資訊來產生 value 宣告的程式碼。(上面程式碼標註 #1 的部分)下篇文章我會各別把這四個步驟用程式碼來講解他們的實作。