How to list (java) annotations on Kotlin data class fields? - reflection

I am using Firestore's Java-based annotation for marking fields and methods for mapping document fields to Java class elements:
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.METHOD, ElementType.FIELD})
public #interface PropertyName {
String value();
}
I am using it on a field in a Kotlin data class, which compiles fine:
data class MyDataClass(
#PropertyName("z") val x: Int
)
In IntelliJ and Android Studio, I can see it show up in the decompiled class dump:
public final data class MyDataClass public constructor(x: kotlin.Int) {
#field:com.google.cloud.firestore.annotation.PropertyName public final val x: kotlin.Int /* compiled code */
public final operator fun component1(): kotlin.Int { /* compiled code */ }
}
My impression at this point is that this annotation should be discoverable somehow via Kotlin reflection. As far as I can tell, it is not. I've tried iterating the annotations on:
Each Kotlin data class constructor fields
Each Kotlin field
Each Kotlin function
Each Java constructor
Each Java field
Each Java method
It just does not show up anywhere.
The moment I change the usage of the annotation like this (note the target specifier "get" now):
data class MyDataClass(
#get:PropertyName("z") val x: Int
)
The annotation now shows up in the generated getter of the Java class object. This is at least workable in practice, but I'm curious why Kotlin lets me compile the annotation in as a field-targeted annotation, but doesn't allow me to get it back out at runtime (unless I'm missing something in the kotlin-reflect APIs?).
If I use this Kotlin-based annotation instead:
#Retention(AnnotationRetention.RUNTIME)
#Target(AnnotationTarget.PROPERTY)
annotation class PropertyName(val value: String)
With this, the annotation shows up at runtime on the Kotlin field. This is curious because Java's ElementType.FIELD simply does not seem to map perfectly to Kotlin's AnnotationTarget.FIELD.
(Incidentally, if I change this to AnnotationTarget.VALUE_PARAMETER, I can also discover this annotation in the data class constructor parameter.)
This feels like a bug to me, but I'm open to seeing if I just did something wrong here. Or maybe this is just not supported. I'm using Kotlin 1.3.11. Same behavior on JVM and Android.
Code that looks for the annotation:
Log.d("#####", "\n\nDump of $kclass")
val ctor = kclass.constructors.first()
Log.d("#####", "Constructor parameters")
ctor.parameters.forEach { p ->
Log.d("#####", p.toString())
Log.d("#####", p.annotations.size.toString())
p.annotations.forEach { a ->
Log.d("#####", " " + a.annotationClass)
}
}
Log.d("#####", "kotlin functions")
kclass.functions.forEach { f ->
Log.d("#####", f.toString())
if (f.annotations.isNotEmpty()) {
Log.d("#####", "*** " + f.annotations.toString())
}
}
Log.d("#####", "kotlin members")
kclass.members.forEach { f ->
Log.d("#####", f.toString())
if (f.annotations.isNotEmpty()) {
Log.d("#####", "*** " + f.annotations.toString())
}
}
Log.d("#####", "kotlin declared functions")
kclass.declaredFunctions.forEach { f ->
Log.d("#####", f.toString())
if (f.annotations.isNotEmpty()) {
Log.d("#####", "*** " + f.annotations.toString())
}
}
val t = kclass.java
Log.d("#####", "java constructors")
t.constructors.forEach { f ->
Log.d("#####", f.toString())
}
Log.d("#####", "java methods")
t.methods.forEach { f ->
Log.d("#####", f.toString())
if (f.annotations.isNotEmpty()) {
Log.d("#####", "*** " + f.annotations.toString())
}
}
Log.d("#####", "java fields")
t.fields.forEach { f ->
Log.d("#####", f.toString())
if (f.annotations.isNotEmpty()) {
Log.d("#####", "*** " + f.annotations.toString())
}
}

The problem here is that my expectations (and possibly the documentation) didn't prepare me for what the Kotlin compiler will do with annotations of various types. My assumption was that a FIELD target annotation target on a Kotlin data class property would apply the annotation directly to the Kotlin synthetic property. This assumption was not true.
What Kotlin will do with a FIELD annotation on a synthetic property is push the FIELD annotation down to the actual backing field for the property in the generated class file. This means that any sort of reflection on the annotated Kotlin property will not find the annotation at all. You have to reach down into the Java Class object to find it.
If you want to annotate a Kotlin class property, and have it found via KClass reflection, you have to use the PROPERTY type annotation, which is unique to Kotlin. With this, if you find the property in the members list of a KClass, it will have that annotation (but not the underlying backing field!).
Going further, with Kotlin data classes, the constructor is the most important thing that defines the properties for the class. So, if you want to create a data class instance via reflection at runtime, it might be best to annotate its properties via its constructor. This means applying an annotation with the VALUE_PARAMETER type to the data class constructor properties, where they can be discovered by reflection of the constructor parameters itself.
In a more general sense, the annotation types that are defined by Java only apply to Java Class reflection, while the annotation types extended by Kotlin only apply to KClass reflection. The Kotlin compiler will forbid you from using Kotlin-specific annotation types on Java elements. The exception here is that, it will allow you to apply Java annotation types to Kotlin concepts (properties with backing fields) that "boil down" to Java native concepts. (FWIW, if you copy Java native annotation code into Kotlin and have it auto-convert, the conversion may not make sense without this in mind.)
If your favorite Java library exposes only annotations that apply to Java layer concepts, consider asking them to provide Kotlin extensions that help you work with their annotations at a more purely Kotlin level. Though this might be tricky to consume in Java code.
Someone please update the docs. :-)

While I can find it in Kotlin KClass, I can find it in Java.
#Retention(AnnotationRetention.RUNTIME)
#Target(AnnotationTarget.FIELD)
annotation class PropertyName(val value: String)
data class MyDataClass(
#PropertyName("z") val x: Int
)
And I use the following code
val a = MyDataClass(1)
a::class.java.declaredFields.forEach {
it.annotations.forEach { annotation ->
Log.e(it.name, annotation.toString())
}
}
It print
2018-12-19 11:33:07.663 25318-25318/com.example.application E/x: #com.example.PropertyName(value=z)

Related

Micronaut custom validation Annotation does not work

I was trying to write a custom annotation to validate a field in a Micronaut project, I came across this section in their documentation Defining Additional Constraints
My understanding was that is completely possible to write my own annotation to validate a field in my POJO but after trying for hours, I can't get my validation to work, it simply does not get invoked, could it be I'm missing something fundamental about the way how Micronaut works?
Annotation
#Retention(RUNTIME)
#Constraint(validatedBy = [])
annotation class FieldValidator(
val message: String = "invalid format ({validatedValue})"
)
Factory
#Factory
class ValidatorFactory {
#Singleton
fun fieldPatternValidator(): ConstraintValidator<FieldValidator, CharSequence> {
return ConstraintValidator { value, annotation, context ->
context.messageTemplate("invalid format ({validatedValue}), should be test")
value == "test"
}
}
}
Filter
#Introspected
data class HelloWorldFilter(
#FieldValidator
val field: String?
)
Controller
#Controller("/hello")
open class HelloController() {
#Get("{?filters*}")
#Produces(MediaType.TEXT_PLAIN)
open fun index(#Valid filters: HelloWorldFilter): String {
return filters.toString()
}
}
I have a small demo on Github, to reproduce
run ./gradlew run
call http://localhost:8080/hello?field=abc that expected behaviour should be bad request since the field is matching the desired value.
Using your demo project, I changed your HelloWorldFilter class to
#Introspected
data class HelloWorldFilter(
#field:FieldValidator
val field: String?
)
Ran it, then:
curl "http://localhost:8080/hello?field=abc"
Output was as you expect:
{"message":"Bad Request","_embedded":{"errors":[{"message":"filters.field: invalid format (abc), should be test"}]},"_links":{"self":{"href":"/hello?field=abc","templated":false}}}
With:
curl "http://localhost:8080/hello?field=test"
Output:
HelloWorldFilter(field=test)

Kotlin reflection returns incorrect type

I have an activity that implements a listener with a generic type that requires me to implement the onResponse function.
The listener is defined in the following format:
public interface ListenerClass<T> {
onResponse(T type)
}
And my activity implements it using T == ModelClass.
class MyActivity : ListenerClass<ModelClass> {
(...)
override fun onResponse(response: ModelClass) {
(...)
}
}
The listener is part of an SDK so I cannot change any code in there. They use a piece of reflection to get the type of my 'ModelClass' using the following code:
listener.getClass().getDeclaredMethods()[onResponseIndex].getParameterTypes()[0]
The result of this code should be Class<ModelClass> but I get Class<Object>. I found out that when I call the code above on a Java class, I get the correct result, but my activity is written in Kotlin.
1) How can I get the parameter type of a kotlin function using reflection?
2) If I have an answer to question 1 I still won't be able to change the SDK's code. Is there anything I can write in my Kotlin code that makes the piece of code above find the correct parameter class type?

Kotlin reflection on object instance

I've been trying some stuff from kotlin.reflection during my project, and got stuck on something what occurs to me as hard to understand, I have declared object as follows:
object WebsiteMapping
{
const val ADMIN = "/admin"
}
once I call:
Arrays
.stream(WebsiteMapping::class.java.declaredFields)
.forEach { field -> println(field.type) }
what I get is:
class java.lang.String
class mapping.WebsiteMapping
When I looked a little bit into what is behind declaredFields invocation I grasped why it works as it is, but is there any convenient way of taking only declared consts within that object without getting also root of the whole structure?
The field with the type class mapping.WebsiteMapping is, basically, not the root of the structure but a special field generated in the object type that holds the reference to the singleton object.
In Kotlin, this field is named INSTANCE by convention. You can therefore filter the fields that you get from the class as follows:
WebsiteMapping::class.java.declaredFields
.filter { it.name != "INSTANCE" }
.forEach { println(it.type) }
Another solution is to switch from java.reflect.* to the Kotlin reflection API kotlin.reflect (needs a dependency on the kotlin-reflect module), which automatically filters the property:
WebsiteMapping::class.memberProperties
.forEach { println(it.returnType) }

Creating a new instance of a KClass

I have a Kotlin class whose primary (and only) constructor is empty.
I have a reference to this class:
val kClass: KClass<MyClass> = MyClass::class
How do I create an instance of this class using reflection?
In Java I would do myClass.newInstance() but it seems in Kotlin I need to find the constructor first:
kClass.constructors.first().call()
I have seen mention of primaryConstructor in some bug reports but it's not showing up in my IDE.
In your case, Java reflection might be enough: you can use MyClass::class.java and create a new instance in the same way as you would with Java reflection (see #IngoKegel's answer).
But in case there's more than one constructor and you really need to get the primary one (not the default no-arg one), use the primaryConstructor extension function of a KClass<T>. It is a part of Kotlin reflection, which is not shipped within kotlin-stdlib.
To use it, you have to add kotlin-reflect as a dependency, e.g. a in Gradle project:
dependencies {
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
}
Assuming that there is ext.kotlin_version, otherwise replace $kotlin_version with the version you use.
Then you will be able to use primaryConstructor, for example:
fun <T : Any> construct(kClass: KClass<T>): T? {
val ctor = kClass.primaryConstructor
return if (ctor != null && ctor.parameters.isEmpty())
ctor.call() else
null
}
You can use the Java class to create new instance:
MyClass::class.java.newInstance()
For those checking this question now, since Kotlin 1.1 there's also createInstance() extension method on KClass
Much like the accepted answer, this function works only in case class has an empty constructor or constructor with all default arguments.
https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.reflect.full/create-instance.html
Expanding on Alexeys Answer, to include a primary constructor call with parameters:
/* Example class with no-args constructor */
class MyClass
/* Example class requiring parameters */
class MyClassWithParams(parameter1: String, parameter2: MyClass)
val myKClass: KClass<MyClass> = MyClass::class
val myKClassWithParameters: KClass<MyClassWithParams> = MyClassWithParams::class
/* We can create an object by calling createInstance when no constructor parameters are required as explained in other answers. */
val myObject: MyClass = myKClass.createInstance()
/* To create an object with parameters, we need to get the constructor first, and call it with the parameters instead, similarly to how we would do in Java. */
val myObjectWithParameters: MyClassWithParams? =
myKClassWithParameters.primaryConstructor?.call(
"StringParameter", myObject
)

Type Parameters on Scala Macro Annotations

I'm trying to use macro annotations in scala, where my macro annotation would take an argument of another type. It would then use scala reflection to look at the passed in type, and add some methods as appropriate.Eg.
trait MyTrait {
def x: Int
def y: Float
}
#MyAnnotation class MyClass //<-- somehow, this annotation should reference MyTrait
class MyAnnotation(val target: Any) extends StaticAnnotation {
def macroTransform(annottees: Any*) = macro MyAnnotationImpl.impl
}
object MyAnnotationImpl {
def impl(c: Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
// if I can get a handle on the type MyTrait in here
// then I can call .members on it, etc.
...
}
}
Basically, the same thing as Using Scala reflection in Scala macros, except using macro annotations. However, when I try to template my macro annotation with a TypeTag
class MyAnnotation[T](val target: Any) extends StaticAnnotation {
def macroTransform[T](annottees: Any*) = macro MyAnnotationImpl.impl[T]
}
object MyAnnotationImpl {
def impl[T: c.WeakTypeTag](c: Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
...
}
}
I get
[error] /Users/imran/other_projs/learn_macros/macros/src/main/scala/com/imranrashid/oleander/macros/MacrosWithReflection.scala:7: macro annotation has wrong shape:
[error] required: def macroTransform(annottees: Any*) = macro ...
[error] found : def macroTransform[T](annottees: Any*) = macro ...
[error] class MyAnnotation[T](val target: Any) extends StaticAnnotation {
[error] ^
I've also tried to make the type an argument to my annotation, so I would use it like #MyAnnotation(MyTrait) class Foo .... I can extract the name as a String with something like
val targetTrait = c.prefix.tree match {
case Apply(Select(New(Ident(_)), nme.CONSTRUCTOR), List(Ident(termName))) => termName
}
but, I'm not sure what I can do w/ that String to get back the full type. I've also tried variants like #MyAnnotation(typeOf[MyTrait]) class Foo ..., and then use c.eval on the typeOf inside my macro, but that doesn't compile either.
In macro paradise 2.0.0-SNAPSHOT we have quite a tricky way of accessing type parameters for macro annotations (the situation will improve later on when we have dedicated APIs for that, but right now it's very difficult to introduce new functionality to scala-reflect.jar in macro paradise, so the current API is a bit rough).
For now it's necessary to specify the type parameter on the annotation class and not to declare any type parameters on the macroTransform method. Then, in macro expansion, access c.macroApplication and extract the untyped tree corresponding to the passed type parameter. Afterwards, do c.typeCheck as described in Can't access Parent's Members while dealing with Macro Annotations.
As Eugene points out in his answer it is possible to match on the tree of the whole macro application. Like every Scala method, annotation macro applications can take multiple type argument lists as well as multiple value argument lists.
Consider the macro application of an annotation macro called test:
#test[A, B][C, D](a, b)(c, d) trait Foo
In the implementation of test we can inspect the macro application by
println(show(c.macroApplication))
which will result in:
new test[A, B][C, D](a, b)(c, d).macroTransform(abstract trait Foo extends scala.AnyRef)
To extract the (type/value) parameters from the tree you have to pattern match on the tree. A parser for an arbitrary amount of parameter lists can be found in this project
Using this parser retrieving the first value argument of the macro application is as easy as
val List(List(arg)) = MacroApp(c.macroApplication).termArgs

Resources