Cannot fetch java.lang.Method from KProperty, is it expected behaviour?


#1

I’m writing a library in Kotlin targeting the JVM, which means I need 100% usability in Java. I plan to use the kotlin-reflect library, but found some unexpected result when writing test cases against these reflection operations in Java.

Edit: I use the Kotlin version 1.0.0-beta-4589.

I have an annotation written in Java:

@Target({ ElementType.METHOD, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface Marker {
}

And a Java bean:

public class JavaPerson {

    private String name;

    @Marker
    private String email;

    @Marker
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

}

Also I have an equivalent version in Kotlin:

class KotlinPerson(@get:org.swordess.toy.kotlin.misc.reflection.Marker var name: String?, @field:Marker var email: String?)

I try to dig out what behavior Kotlin exactly make via:


fun printProperties(c: KClass<*>) {
    println("${c.qualifiedName} >>>")
    c.memberProperties.forEach {
        println("property(name=${it.name}, annotations=${it.annotations})")

        println("\tkotlinGetter: ${it.getter}, annotations=${it.getter.annotations}")
        it.javaGetter?.let { println("\tjavaGetter: $it, annotations=${it.annotations}") }

        if (it is KMutableProperty<*>) {
            println("\tkotlinSetter=${it.setter}, annotations=${it.setter.annotations}")
            it.javaSetter?.let { println("\tjavaSetter=$it, annotations=${it.annotations}") }
        }
    }
}

And the output when applying printProperties to my two versions of beans:

org.swordess.toy.kotlin.misc.reflection.KotlinPerson >>>
property(name=email, annotations=[])
    kotlinGetter: kotlin.reflect.jvm.internal.KProperty1Impl$Getter@71809907, annotations=[]
    javaGetter: public final java.lang.String org.swordess.toy.kotlin.misc.reflection.KotlinPerson.getEmail(), annotations=[Ljava.lang.annotation.Annotation;@6b927fb
    kotlinSetter=kotlin.reflect.jvm.internal.KMutableProperty1Impl$Setter@5876a9af, annotations=[]
    javaSetter=public final void org.swordess.toy.kotlin.misc.reflection.KotlinPerson.setEmail(java.lang.String), annotations=[Ljava.lang.annotation.Annotation;@6b927fb
property(name=name, annotations=[])
    kotlinGetter: kotlin.reflect.jvm.internal.KProperty1Impl$Getter@6572421, annotations=[]
    javaGetter: public final java.lang.String org.swordess.toy.kotlin.misc.reflection.KotlinPerson.getName(), annotations=[Ljava.lang.annotation.Annotation;@6b81ce95
    kotlinSetter=kotlin.reflect.jvm.internal.KMutableProperty1Impl$Setter@2a798d51, annotations=[]
    javaSetter=public final void org.swordess.toy.kotlin.misc.reflection.KotlinPerson.setName(java.lang.String), annotations=[Ljava.lang.annotation.Annotation;@6b927fb

org.swordess.toy.kotlin.misc.reflection.JavaPerson >>>
property(name=email, annotations=[])
    kotlinGetter: kotlin.reflect.jvm.internal.KProperty1Impl$Getter@65d09a04, annotations=[]
    kotlinSetter=kotlin.reflect.jvm.internal.KMutableProperty1Impl$Setter@6a2f6f80, annotations=[]
property(name=name, annotations=[])
    kotlinGetter: kotlin.reflect.jvm.internal.KProperty1Impl$Getter@5b94b04d, annotations=[]
    kotlinSetter=kotlin.reflect.jvm.internal.KMutableProperty1Impl$Setter@8c3b9d, annotations=[]

From the output, I found:

  • Though I declare use-site target Marker annotated to KotlinPerson's username and email property, annotation are both visible by javaGetter and javaSetter, is this expected?
  • It’s not possible to fetch annotations from JavaPersion, is this expected? If yes, I have no way to say kprop.javaGetter.isAnnotationPresent(Marker::class.java), which further means NOT POSSIBLE to use my Kotlin library (which relies on kotlin-reflect) in Java! It’s very frustrated!

And this question come to me when I use Kotlin, also related to Annotation and reflection:

  • Kotlin has a type KAnnotatedElement which has a property public val annotations: List<Annotation>, but I have no way to declare a (maybe) Annotation Type, thus I cannot say, for example Marker::class.anno in kElem.annotations. From the source code of the mentioned Annotation:
package kotlin

public interface Annotation {
}

Nothing is there. Or, I can treat the Kotlin’s Annotation type like a general type, so I could say kElem.annotations.firstOrNull { it is Marker }?

In a word, what is this KAnnotatedElement.annotations property designed for?


#2

Hi! Thanks for the feedback.

Though I declare use-site target Marker annotated to KotlinPerson’s username and email property, annotation are both visible by javaGetter and javaSetter, is this expected?

In fact this annotation is present only in name's javaGetter.annotation. In your example, arrays of annotations from Java reflection are not rendered properly, please call .toList() to render all the elements (and to observe that they are empty in 3 of 4 cases):

...
println("\tkotlinGetter: ${it.getter}, annotations=${it.getter.annotations.toList()}")
...

What’s more interesting is that no annotations were found with Kotlin reflection. This is a bug, I’ve reported it here: Use-site targeted annotations do not work automatically in reflection.

It’s not possible to fetch annotations from JavaPersion, is this expected?

This is a bug, I’ve reported it here: No annotations loaded on Java members via reflection.

In a word, what is this KAnnotatedElement.annotations property designed for?

Any annotation class coming either from Kotlin or Java extends Annotation, and to find a specific annotation in annotations you can simply use is-checks or standard library functions, for example:

val marker = kElem.annotations.filterIsInstance<Marker>().firstOrNull()

There’s also an extension property annotationClass declared on an Annotation which allows you to obtain the KClass of the corresponding annotation class:

val anno = kElem.annotations.firstOrNull()
if (anno?.annotationClass == Marker::class.java) {
    ...
}

#3

Thanks @udalov. After using annotations.toList() the program outputs:

org.swordess.toy.kotlin.misc.reflection.KotlinPerson >>>
property(name=email, annotations=[])
    javaField: private java.lang.String org.swordess.toy.kotlin.misc.reflection.KotlinPerson.email, annotations=[@org.swordess.toy.kotlin.misc.reflection.Marker()]
    kotlinGetter: kotlin.reflect.jvm.internal.KProperty1Impl$Getter@4009e306, annotations=[]
    javaGetter: public final java.lang.String org.swordess.toy.kotlin.misc.reflection.KotlinPerson.getEmail(), annotations=[]
    kotlinSetter: kotlin.reflect.jvm.internal.KMutableProperty1Impl$Setter@3c3d9b6b, annotations=[]
    javaSetter: public final void org.swordess.toy.kotlin.misc.reflection.KotlinPerson.setEmail(java.lang.String), annotations=[]
property(name=name, annotations=[])
    javaField: private java.lang.String org.swordess.toy.kotlin.misc.reflection.KotlinPerson.name, annotations=[]
    kotlinGetter: kotlin.reflect.jvm.internal.KProperty1Impl$Getter@79d8407f, annotations=[]
    javaGetter: public final java.lang.String org.swordess.toy.kotlin.misc.reflection.KotlinPerson.getName(), annotations=[@org.swordess.toy.kotlin.misc.reflection.Marker()]
    kotlinSetter: kotlin.reflect.jvm.internal.KMutableProperty1Impl$Setter@5fbe4146, annotations=[]
    javaSetter: public final void org.swordess.toy.kotlin.misc.reflection.KotlinPerson.setName(java.lang.String), annotations=[]
org.swordess.toy.kotlin.misc.reflection.JavaPerson >>>
property(name=email, annotations=[])
    javaField: private java.lang.String org.swordess.toy.kotlin.misc.reflection.JavaPerson.email, annotations=[@org.swordess.toy.kotlin.misc.reflection.Marker()]
    kotlinGetter: kotlin.reflect.jvm.internal.KProperty1Impl$Getter@1e66f1f5, annotations=[]
    kotlinSetter: kotlin.reflect.jvm.internal.KMutableProperty1Impl$Setter@394df057, annotations=[]
property(name=name, annotations=[])
    javaField: private java.lang.String org.swordess.toy.kotlin.misc.reflection.JavaPerson.name, annotations=[]
    kotlinGetter: kotlin.reflect.jvm.internal.KProperty1Impl$Getter@75db5df9, annotations=[]
    kotlinSetter: kotlin.reflect.jvm.internal.KMutableProperty1Impl$Setter@707194ba, annotations=[]

This behave correctly that only the field of KotlinPerson.email and javaGetter of KotlinPerson.name have the Marker annotation, but still the getter of KotlinPerson.name cannot see the annotation as you previously replied.

One more bug I forgot to mention is java.lang.Method cannot be fetched by Kotlin as this post’s title:

org.swordess.toy.kotlin.misc.reflection.JavaPerson >>>
property(name=email, annotations=[])
    javaField: private java.lang.String org.swordess.toy.kotlin.misc.reflection.JavaPerson.email, annotations=[@org.swordess.toy.kotlin.misc.reflection.Marker()]
    kotlinGetter: kotlin.reflect.jvm.internal.KProperty1Impl$Getter@1e66f1f5, annotations=[]
    kotlinSetter: kotlin.reflect.jvm.internal.KMutableProperty1Impl$Setter@394df057, annotations=[]
property(name=name, annotations=[])
    javaField: private java.lang.String org.swordess.toy.kotlin.misc.reflection.JavaPerson.name, annotations=[]
    kotlinGetter: kotlin.reflect.jvm.internal.KProperty1Impl$Getter@75db5df9, annotations=[]
    kotlinSetter: kotlin.reflect.jvm.internal.KMutableProperty1Impl$Setter@707194ba, annotations=[]

No javaGetter and javaSetter in the output.


#4

One more bug I forgot to mention is java.lang.Method cannot be fetched by Kotlin as this post’s title

Unfortunately, get- and set-methods of Java classes are not loaded as property accessors by Kotlin, thus they do not become the accessors of the corresponding Java property (which is in fact only a field). To find them in the class, lookup methods named get (set) + capitalized property name. We may be able to improve this at some point though, thanks.


#5

Got it!

Looking forward Kotlin’s release.:grinning: