iT邦幫忙

2021 iThome 鐵人賽

DAY 18
0
Mobile Development

如何使用 Kotlin Annotation Processor 做出自己的 Custom Data Parser Library系列 第 18

Logger 與 Extension Generator for Kotlin

Logger

在 compile time 的時候,不像我們一般再開發的時候很容易的去 log 一些我們要的資訊,這邊我們必須要透過 processor 提供的 Messager 才能進行 log ,這個 log 的內容會顯示在 IDE 的 build console 內。

Screen Shot 2021-08-30 at 7.29.58 PM.png

我們也可以自己用一個 class 去封 Messager ,像是這樣:

class Logger(private val log: Messager) {

    fun log(msg: String) {
        log.printMessage(Diagnostic.Kind.NOTE, "$msg\r\n")
    }

    fun warn(msg: String) {
        log.printMessage(Diagnostic.Kind.WARNING, "$msg\r\n")
    }

    fun error(msg: String, element: Element) {
        log.printMessage(Diagnostic.Kind.ERROR, "$msg\r\n", element)
    }
}

之後就可以直接使用方法來 log 不同層級的資訊!

KotlinExtensionGenerator

Extension 是幫助我們在產生 parser 程式碼的過程中會用到的一些工具,我們可以從前幾篇在實作 DOM parser 的時候,有用到的東西去做規劃。

  1. 取值: readString
  2. 取屬性: getAttribute
  3. 搜尋元素: getElementByTag
  4. Boolean 轉換: toBoolean

於是我們可以知道我們想要產生的類別大概長怎樣,我們就可以先在編輯器先寫上想產生的類別:

object ParserExtensions {
  fun Element.readString(name: String, parentTag: String? = null): String? {
    val nodeList = getElementsByTagName(name)
    if (parentTag == null) {
    	return nodeList.item(0)?.textContent
    } else {
    	for (i in 0 until nodeList.length) {
    		val e = nodeList.item(i) as? Element ?: continue
    		val parent = e.parentNode as? Element
    		if (parent?.tagName != parentTag) continue
    	
    		return e.textContent
    	}
    	return null
    }
  }

  fun Element.getAttributeOrNull(tag: String): String? {
    val attr = getAttribute(tag) ?: return null
    return if (attr.isEmpty() || attr.isBlank()) null else attr
  }

  fun Element.getElementByTag(tag: String): Element? {
    val nodeList = getElementsByTagName(tag)
    if (nodeList.length == 0) return null
    return nodeList.item(0) as? Element 
  }

  fun String.toBooleanOrNull(): Boolean? = when (toLowerCase()) {
  	"true", "yes" -> true
  	"no", "false" -> false
  	else -> null
  }
}

程式碼的細節可以參考前幾篇的 "使用 DOM Parser" ,裡面有針對 Element 的介紹,這邊就不再贅述。接下來就可以勾勒出 KotlinExtensionGenerator 的大概結構:

class KotlinExtensionGenerator(
    private val logger: Logger
) : ExtensionGenerator() {

    private val elementClass = ClassName("org.w3c.dom", "Element")

    override fun generate(): FileSpec {
        logger.log("Generating $EXTENSION_NAME for Kotlin.")
        return FileSpec.builder(GENERATOR_PACKAGE,EXTENSION_NAME)
            .addType(
                TypeSpec.objectBuilder(EXTENSION_NAME)
                    .addFunction(getReadStringFunSpec())
                    .addFunction(getAttributeOrNullFunSpec())
                    .addFunction(getElementByTagFunSpec())
                    .addFunction(getBooleanConversionFunSpec())
                    .build()
            )
            .build()
    }

		private fun getReadStringFunSpec() {
			// 略
		}

		private fun getAttributeOrNullFunSpec() {
			// 略
		}

		private fun getElementByTagFunSpec() {
			// 略
		}
}

generate 的方法裡主要定義了 ParserExtension 會有幾個方法與它的型態,這邊是定義成 object 。裡面的 fun spec 就比較沒什麼特別的,都可以用簡單的 KotlinPoet 語法完成,如果有興趣的朋友可以參考這邊。這個類別還有一個 getBooleanConversionFunSpec 還沒有被提到,因為它是父類別 ExtensionGenerator 的方法,主要是產生 boolean 值轉換的方法,對應到 ParserExtension 裡的 toBooleanOrNull 方法。這個抽象類別同時也被 ExtensionGenerator 繼承。

abstract class ExtensionGenerator : Generator {
    protected fun getBooleanConversionFunSpec() = FunSpec.builder(METHOD_TO_BOOLEAN)
        .receiver(String::class)
        .addCode(
            """
            |return when (toLowerCase()) {
            |${TAB}"true", "yes" -> true
            |${TAB}"no", "false" -> false
            |${TAB}else -> null
            |}
        """.trimMargin()
        )
        .returns(Boolean::class.asTypeName().copy(nullable = true))
        .build()
}

實作到這裡,我們可以產生一些 extension 在 parser 的 generator 裡面可以用,下篇我們會提到以 DOM parser 為基底產生的 parser 。


上一篇
各種 Code Generator 的功能
下一篇
Parser Generator (一)
系列文
如何使用 Kotlin Annotation Processor 做出自己的 Custom Data Parser Library30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言