为了活动小家电-注解Annotation(六)

127 阅读1分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的28天,点击查看活动详情

为了活动小家电,接着上篇搞!

test-processor

test-processor 模块是通过在构建时查找带注释的类来生成代码的模块。

build.gradle 文件如下所示。

apply plugin: 'kotlin'
apply plugin: 'kotlin-kapt'

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation project(':datavalidation-annotation')
    implementation 'com.squareup:kotlinpoet:1.5.0'
    implementation "com.google.auto.service:auto-service:1.0-rc6"
    kapt "com.google.auto.service:auto-service:1.0-rc6"
}

sourceCompatibility = "7"
targetCompatibility = "7"

我使用自定义注释引用了 test-annotation 模块。
kotlinpoet 是一个方便自动化 kotlin 文件的库。
auto-service 是一个用于向编译器自动注册注解处理器的库。(如果你想在没有库的情况下这样做,你需要制作一个META-INF文件并配置一些设置,但我没有尝试过)

下面是实际的Processor代码。

@AutoService(Processor::class)
class Processor : AbstractProcessor() {

    companion object {
        const val KAPT_KOTLIN_GENERATED_OPTION_NAME = "kapt.kotlin.generated"
        // 用于文件输出
        val fileBuilder =
            FileSpec.builder("com.datavalidation.generated", "DataValidationExtension")
    }

    // 获取支持的注释类型
    override fun getSupportedAnnotationTypes(): MutableSet<String> {
        return mutableSetOf(
            DataValidation::class.java.name,
            Nested::class.java.name,
            NotNull::class.java.name,
            MinLength::class.java.name,
            MaxLength::class.java.name,
            MinValue::class.java.name,
            MaxValue::class.java.name,
            Regex::class.java.name
        )
    }

    // 获取支持的源版本
    override fun getSupportedSourceVersion(): SourceVersion {
        return SourceVersion.latestSupported()
    }

    // 调用两次或多次
    // 主要执行逻辑
    override fun process(
        annotations: MutableSet<out TypeElement>?,
        roundEnv: RoundEnvironment
    ): Boolean {

        // 获取带有 DataValidation 注解的元素
        val classElements = roundEnv.getElementsAnnotatedWith(DataValidation::class.java)

        if (!checkElementType(ElementKind.CLASS, classElements)) return false
        
        // 进行验证
        classElements.forEach { fileBuilder.addFunction(validate(it)) }

        fileBuilder.addImport(FieldNameTag::class.java, "")
        val kaptKotlinGeneratedDir = processingEnv.options[KAPT_KOTLIN_GENERATED_OPTION_NAME]
        fileBuilder.build().writeTo(File(kaptKotlinGeneratedDir))
        return true
    }

    // 验证函数
    // 以下逻辑皆为kotlinpoet 自动生成校验类过程
    private fun validate(classElement: Element): FunSpec {
        val validateFunSpec = FunSpec.builder("validate")
            .receiver(classElement.asType().asTypeName())
            .returns(ValidationResult::class)
            .addStatement("val result = %T()", ValidationResult::class.java)

        val fieldElement = classElement.enclosedElements
        fieldElement.forEach {
            val nonNull = it.getAnnotation(NotNull::class.java)
            val minLength = it.getAnnotation(MinLength::class.java)
            val maxLength = it.getAnnotation(MaxLength::class.java)
            val minValue = it.getAnnotation(MinValue::class.java)
            val maxValue = it.getAnnotation(MaxValue::class.java)
            val regex = it.getAnnotation(Regex::class.java)
            val nested = it.getAnnotation(Nested::class.java)

            nonNull?.let { annotation ->
                validateFunSpec.addComment("NonNull Check")
                validateFunSpec.beginControlFlow("if(${it.simpleName} == null)")
                validateFunSpec.addStatement("result.isValid = false")
                validateFunSpec.addStatement("result.invalidFieldNameTags.add(${createFieldNameTag(it.simpleName, annotation.tag)})")
                validateFunSpec.endControlFlow()
            }

            minLength?.let { annotation ->
                validateFunSpec.addComment("Minimum Length Check")
                validateFunSpec.beginControlFlow("if(${it.simpleName} == null || ${it.simpleName}.length < ${annotation.length})")
                validateFunSpec.addStatement("result.isValid = false")
                validateFunSpec.addStatement("result.invalidFieldNameTags.add(${createFieldNameTag(it.simpleName, annotation.tag)})")
                validateFunSpec.endControlFlow()
            }

            maxLength?.let { annotation ->
                validateFunSpec.addComment("Maximum Length Check")
                validateFunSpec.beginControlFlow("if(${it.simpleName} == null || ${it.simpleName}.length > ${annotation.length})")
                validateFunSpec.addStatement("result.isValid = false")
                validateFunSpec.addStatement("result.invalidFieldNameTags.add(${createFieldNameTag(it.simpleName, annotation.tag)})")
                validateFunSpec.endControlFlow()
            }

            minValue?.let { anno ->
                validateFunSpec.addComment("Minimum Value Check")
                validateFunSpec.beginControlFlow("if(${it.simpleName} == null || ${it.simpleName}.toLong() < ${anno.value})")
                validateFunSpec.addStatement("result.isValid = false")
                validateFunSpec.addStatement("result.invalidFieldNameTags.add(${createFieldNameTag(it.simpleName, anno.tag)})")
                validateFunSpec.endControlFlow()
            }

            maxValue?.let { anno ->
                validateFunSpec.addComment("Minimum Value Check")
                validateFunSpec.beginControlFlow("if(${it.simpleName} == null || ${it.simpleName}.toLong() > ${anno.value})")
                validateFunSpec.addStatement("result.isValid = false")
                validateFunSpec.addStatement("result.invalidFieldNameTags.add(${createFieldNameTag(it.simpleName, anno.tag)})")
                validateFunSpec.endControlFlow()
            }

            regex?.let { anno ->
                validateFunSpec.addComment("Regex Match Check")
                validateFunSpec.beginControlFlow("if(${it.simpleName} == null || !%S.toRegex().matches(${it.simpleName}))", anno.regex)
                validateFunSpec.addStatement("result.isValid = false")
                validateFunSpec.addStatement("result.invalidFieldNameTags.add(${createFieldNameTag(it.simpleName, anno.tag)})")
                validateFunSpec.endControlFlow()
            }

            nested?.let { _ ->
                validateFunSpec.addComment("Nested Check")
                validateFunSpec.beginControlFlow("if(${it.simpleName} != null)")
                validateFunSpec.addStatement("val nestedValidation = ${it.simpleName}.validate()")
                validateFunSpec.beginControlFlow("if(!nestedValidation.isValid)")
                validateFunSpec.addStatement("result.isValid = false")
                validateFunSpec.addStatement("result.invalidFieldNameTags.addAll(nestedValidation.invalidFieldNameTags)")
                validateFunSpec.endControlFlow()
                validateFunSpec.endControlFlow()
            }
        }

        return validateFunSpec.addStatement("return result").build()
    }

    private fun checkElementType(kind: ElementKind, elements: Set<Element>): Boolean {
        if (elements.isEmpty()) return false

        elements.forEach {
            if (it.kind != kind) {
                printMessage(
                    Diagnostic.Kind.ERROR, "Only ${kind.name} Are Supported", it
                )
                return false
            }
        }
        return true
    }

    private fun printMessage(kind: Diagnostic.Kind, message: String, element: Element) {
        processingEnv.messager.printMessage(kind, message, element)
    }

    private fun createFieldNameTag(fieldName: Name, tag: String): String {
        return "FieldNameTag("$fieldName", "$tag")"
    }
}

首先,我继承了 AbstractProcessor 并覆盖了所需的方法。

@AutoService(Processor::class) 是上面提到的自动服务库的一个函数,它只用一个注解自动向编译器注册一个处理器。

kapt.kotlin.generated如果您创建的app\build\generated\source\kaptKotlin文件的键替换为 ,则指示保存位置。

getSupportedAnnotationTypes()在方法中,它告诉相应的处理器要处理的注释。

getSupportedSourceVersion()该方法告诉您要支持哪个版本的源代码。

process()是创建代码的逻辑所在。
您可以通过 RoundEnvironment 提取所需的元素。

总流量为

  • 使用 DataValidation 提取类元素并
  • fun class.validate()我为要创建的文件中的每个元素编写了函数。

为了活动小家电,下篇继续搞!