1

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
  • 2
    The concept that “@NotNull is present … on the field … via another annotation” does not exist in Java. Tools processing annotations may implement such a logic but since you’re the one implementing the tool, it’s on you to check whether MyAnnotation has been annotated with @NotNull which is what actually has been done on the language level and bytecode level. There is no way around reading and parsing the class file of MyAnnotation to find out whether the @NotNull annotation is present. Commented Jul 14, 2023 at 18:25

1 Answer 1

1

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()
        }
    }
}
Sign up to request clarification or add additional context in comments.

3 Comments

Hmm I tried adding the return block but it never gets executed. Could you provide a more detailed answer, as to what code and where should your snippet be placed inside the script I provided?
sorry, the example is a bit tricky to reproduce, but I tried by best to update the original answer, hope it helps
This answer is basically the same as the code in the question. You visit a field, then visit all annotations on the field .. this answer does not check meta-annotations

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.