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 。


<<:  Day 23:获取位置权限

>>:  [DAY15]k8s必备良药-Lens

Day-19 承载游戏梦想的南蛮黑船 XBOX 再启航

微软也做游戏机!这种在现在听来理所当然的事情、发生在 20 年前、其实还是让人感到挺震惊的。而当时诞...

[Day 04] 眼前的黑不是黑,你说的白是什麽白?(直方图均衡化)

前言 「眼前的黑不是黑,你说的白是什麽白」-- 你是我的眼(萧煌奇) 没错,并非所有图片都是在理想的...

[DAY04] 建立 Datastore 和 Dataset (下)

DAY04 建立 Datastore 和 Dataset (下) 今天我们就要把昨天建立好的 dat...

Re: 新手让网页 act 起来: Day05 - 建立元件

昨天我们提到了该如何撰写 JSX,今天就来学习怎麽建立元件,来把同类型的 React element...

高并发系统系列-使用lock & Interlocked CAS(compare and swap)

前言 在之前我有写一篇关於资料库的ACID分享RDBMS资料库基本原则 假如我们系统是一个多执行续高...