SampleClass has a field, that is annotated with MyAnnotation.

I can detect that MyAnnotation is present on the field name using ASM visitor pattern, but unsure how to check if @NotNull is part of MyAnnotation.

How can I detect if @NotNull is present either directly on the field or via another annotation?

import jakarta.validation.constraints.NotNull;

import javax.persistence.*;

@Entity
public class SampleClass {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @MyAnnotation("John")
    public String name;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }
}

MyAnnotation.java:

import jakarta.validation.constraints.NotNull;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@NotNull
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.METHOD})
public @interface MyAnnotation {
    String value() default "";
}

Gradle build script:

import org.objectweb.asm.*
import org.objectweb.asm.Opcodes.ASM9
import java.util.*


plugins {
    java
}

group "org.example"
version "1.0-SNAPSHOT"

repositories {
    mavenCentral()
}

dependencies {
    implementation("org.ow2.asm:asm:9.5")
    implementation("jakarta.persistence:jakarta.persistence-api:2.2.3")
    implementation("jakarta.validation:jakarta.validation-api:3.0.0")
}

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.ow2.asm:asm:9.5")
        classpath("jakarta.persistence:jakarta.persistence-api:2.2.3")
        classpath("jakarta.validation:jakarta.validation-api:3.0.0")
    }
}

tasks.compileJava {
    doLast {

        val notNullAnnotation = Type.getType("Ljakarta/validation/constraints/NotNull")

        val classReader = ClassReader(file("build/classes/java/main/org/example/SampleClass.class").readBytes())
        val classWriter = ClassWriter(classReader, ClassWriter.COMPUTE_MAXS)

        classReader.accept(object : ClassVisitor(ASM9, classWriter) {
            var isEntity = false

            override fun visit(
                version: Int,
                access: Int,
                name: String,
                signature: String?,
                superName: String?,
                interfaces: Array<out String>?
            ) {
                super.visit(version, access, name, signature, superName, interfaces)
            }

            override fun visitField(
                access: Int,
                name: String,
                descriptor: String,
                signature: String?,
                value: Any?
            ): FieldVisitor {
                val fieldVisitor = super.visitField(access, name, descriptor, signature, value)

                return object : FieldVisitor(Opcodes.ASM9, fieldVisitor) {
                    var hasNotNullAnnotation = false

                    override fun visitAnnotation(descriptor: String, visible: Boolean): AnnotationVisitor {

                        val av = super.visitAnnotation(descriptor, visible)

                        if (descriptor.contains(notNullAnnotation.descriptor)) {
                            hasNotNullAnnotation = true
                        }
                    }

                    override fun visitEnd() {

                        System.out.println("hasNotNullAnnotation: " + hasNotNullAnnotation); // always false

                        super.visitEnd()
                    }
                }
            }
        }, 0)

        file("build/classes/java/main/org/example/SampleClass.class").writeBytes(classWriter.toByteArray())
    }
}

val asmConfiguration = project.configurations.getByName("implementation")
tasks.compileJava {

    doLast {
        asmConfiguration
    }
}
1

There are 1 best solutions below

3
Yahor Barkouski On

Your example basically doesn't work because you're only checking if the @NotNull annotation is directly present on the field, but not handling the real case, where @NotNull is a meta-annotation. You might try to use the explicit custom AnnotationVisitor instead:

override fun visitField(
    access: Int,
    name: String,
    descriptor: String,
    signature: String?,
    value: Any?
): FieldVisitor {
    val fieldVisitor = super.visitField(access, name, descriptor, signature, value)

    return object : FieldVisitor(Opcodes.ASM9, fieldVisitor) {
        var hasNotNullAnnotation = false

        override fun visitAnnotation(descriptor: String, visible: Boolean): AnnotationVisitor {
            val av = super.visitAnnotation(descriptor, visible)

            return object : AnnotationVisitor(ASM9, av) {
                override fun visitAnnotation(name: String, descriptor: String): AnnotationVisitor {
                    if (descriptor.contains(notNullAnnotation.descriptor)) {
                        hasNotNullAnnotation = true
                    }
                    return this
                }
            }
        }

        override fun visitEnd() {
            System.out.println("hasNotNullAnnotation: " + hasNotNullAnnotation); 
            super.visitEnd()
        }
    }
}