Retrieve annotations from KType - java

I have a simple TYPE_USE annotation:
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
public #interface Cool {
}
And the following sample Kotlin class:
class Item(
id: Long? = null,
var names: List<#Cool String> = emptyList())
Is there any way to extract the annotation using Java reflection?
Item.class.getMethod("getName").getAnnotatedReturnType() loses the annotations, same with getting the field.
Can I even get the annotation from Kotlin?
Item::class.memberProperties.elementAt(0).returnType return a KType which has the annotation, but I don't see a way to extract it. Nor to obtain an AnnotatedType from KType, even though I have JDK8 extensions.
All I see is KType#javaType but this returns Type, not AnnotatedType... so it looses the annotations again.

Edit: this is a bug and has been reported. There is not yet a target version, but its priority has been set to Major. This was fixed in Kotlin 1.3.
TL; DR: no...?
The item annotated with #Cool is the first type argument, so you need to retrieve it:
val type = Item::class.memberProperties.elementAt(0).returnType
val arg = type.arguments[0]
println(arg) // KTypeProjection(variance=INVARIANT, type=#Cool kotlin.String)
Unfortunately there doesn't seem to be a way to retrieve the annotations on a KType (as you have mentioned).
Strangely, this is a very internal process. Looking into the source for KTypeImpl shows that toString is implemented via ReflectionObjectRenderer.renderType(type), (where type is a KotlinType) which is delegated to DescriptorRenderer.FQ_NAMES_IN_TYPES, which we can see is a DescriptorRenderer with modifiers ALL.
The renderer checks if the type is a subclass of kotlin.reflect.jvm.internal.impl.descriptors.annotations.Annotated, and then accesses its annotations property.
I tried this:
val retType = Item::class.memberProperties.elementAt(0).returnType
val arg = retType.arguments[0]
println(arg) // KTypeProjection(variance=INVARIANT, type=#Cool kotlin.String)
val type = arg.type!!
println(type)
val field = type::class.memberProperties.first { it.name == "type" }
val kotlinType = field.call(type) as Annotated
println(kotlinType)
println(kotlinType.annotations)
Unfortunately, I get a ClassNotFoundException for org.jetbrains.kotlin.types.KotlinType, so that option is gone.
Equally strangely, KType is not a subtype of KAnnotatedElement (which is why it has no annotations property).
I suppose this may have been an oversight, since KTypeImpl wraps a KotlinType, which does contain annotations.

Related

Kotlin annotation not in fieldDecleration or compiled java

For my data objects in Kotlin I have added a custom annotation for GSON to have an exclusion rule.
In the past this has worked perfectly, now it does not show up in my class reflection (this.javaClass.declaredFields[3].annotations is null) nor does it show up in the compiled java output.
I have tried different things, like upgrading my kotlin version, adding kotlin-kapt, using different #Retention types, restarting my computer (you never know) and have looked at other annotations. Those other annotations (for instance for Hibernate a #OneToOne) shows up with no issue.
Annotation definition:
#Retention(AnnotationRetention.RUNTIME)
#Repeatable
#Target(
AnnotationTarget.FIELD,
AnnotationTarget.PROPERTY_GETTER,
AnnotationTarget.PROPERTY_SETTER,
AnnotationTarget.PROPERTY,
AnnotationTarget.VALUE_PARAMETER
)
annotation class ExcludeFromJSON
Usage in data class:
#Entity
#Table(name = "user")
class User (
var username: String = "",
var email: String = "",
#ExcludeFromJSON
private var password: String
) {}
I expect the annotation to show up in the javaClass reflection and in the compiled java code. It does neither.
Compiled password var (no annotation...):
private final var password: kotlin.String /* compiled code */`
You should qualify the annotation with the appropriate use-site target:
#field:ExcludeFromJSON
private var password: String
This will cause the annotation to be present on the Java field generated by this property.
From the Kotlin Reference regarding Annotation Use-site Targets:
When you're annotating a property or a primary constructor parameter, there are multiple Java elements which are generated from the corresponding Kotlin element, and therefore multiple possible locations for the annotation in the generated Java bytecode. [...]
[...]
The full list of supported use-site targets is:
file;
property (annotations with this target are not visible to Java);
field;
get (property getter);
set (property setter);
receiver (receiver parameter of an extension function or property);
param (constructor parameter);
setparam (property setter parameter);
delegate (the field storing the delegate instance for a delegated property).
[...]
If you don't specify a use-site target, the target is chosen according to the #Target annotation of the annotation being used. If there are multiple applicable targets, the first applicable target from the following list is used:
param;
property;
field.
The three important things to take away from that are:
Annotations in Kotlin have a certain amount of ambiguity1 regarding where they're ultimately applied. For instance, placing your annotation where you did means said annotation could be applied to one of at least five different locations: property, field, getter, setter, setter parameter.
An annotation applied to a Kotlin property is not visible on the Java side.
An annotation on a Kotlin property, without a use-site target, will only be applied to the backing field if it's not also applicable to being applied to the Kotlin property.
Your annotation has both AnnotationTarget.FIELD and AnnotationTarget.PROPERTY in its #Target annotation. Since you don't specify a use-site target, the property takes precedence—meaning the annotation is not visible to Java.
For more information regarding properties and fields, see the Properties and Fields page of the Kotlin Reference.
1. It's not technically ambiguous, as everything is well defined.

How do I add a dynamic number of annotated parameters to a new method in Bytebuddy?

I'm attempting to create a number of classes at runtime using ByteBuddy. So far I've managed to create the classes, add methods and annotate them. It's a great tool and I've enjoyed using it so far.
But now I'm stuck. I have my class, with a method and there are n number of parameters (dynamically controlled by config). This works something like...
DynamicType.Builder<?> builder = new ByteBuddy().subclass(Object.class)
.name(newClassName)
.annotateType(AnnotationDescription.Builder.ofType(Controller.class).build());
// loop for all methods to create
for (final MethodDefinition methodDefinition : methodDefinitions) {
final List<TypeDefinition> parameters = new ArrayList<>();
for (final MethodParameterDefinition methodParamDef : methodDefinition.getMethodParameterDefinitions()) {
parameters.add( TypeDescription.Generic.Builder.rawType(methodParamDef.getType()).build() );
}
// define the method
builder = builder
.defineMethod(methodDefinition.getName(), outputValueObjectClass, Modifier.PUBLIC)
.withParameters(parameters)
.annotateMethod(AnnotationDescription.Builder.ofType(RequestMapping.class)
.defineEnumerationArray("method", RequestMethod.class, RequestMethod.valueOf(methodDefinition.getHttpMethod()))
.defineArray("path", methodDefinition.getUrl()).build())
.annotateMethod(AnnotationDescription.Builder.ofType(ResponseBody.class).build())
}
final DynamicType.Unloaded unloadedClass = builder.make();
But when I attempt to add an annotation to one of the parameters by using the following code...
for (final MethodParameterDefinition methodParamDef : methodDefinition.getMethodParameterDefinitions()) {
parameters.add( TypeDescription.Generic.Builder.rawType(methodParamDef.getType())
.annotate(AnnotationDescription.Builder.ofType(PathVariable.class).define("name", methodParamDef.getName()).build())
.build() );
}
...I get the following exception....
java.lang.IllegalStateException: Illegal type annotations return type class...
If I know the number of method parameters I can add these using code like...
builder = builder
.defineMethod(methodDefinition.getName(), outputValueObjectClass, Modifier.PUBLIC)
.withParameter(methodParameterClass).annotateParameter(AnnotationDescription.Builder.ofType(ModelAttribute.class).build())
.annotateMethod(AnnotationDescription.Builder.ofType(RequestMapping.class)
.defineEnumerationArray("method", RequestMethod.class, RequestMethod.valueOf(methodDefinition.getHttpMethod()))
.defineArray("path", methodDefinition.getUrl()).build())
.annotateMethod(AnnotationDescription.Builder.ofType(ResponseBody.class).build())
But this doesn't work for a dynamic approach.
Does anyone know how to add a dynamic number of parameters (with annotations) to a method?
Thanks in advance!
I just checked the code and the exception message is misleading due to a copy-paste error. The error message should tell you that you are annotating a type with an annotation that is not a type annotation.
Note the difference between the annotation and the annotateParameter in the code generation DSL. The former annotates the type, the other one the parameter. This might seem confusing as the syntax is ambigous:
void foo(#Bar List<?> qux) { ... }
can mean both in Java, if #Bar is a type annotation, it annotates the List type, if it is a parameter annotation, it annotates the parameter qux, if it is both, the type and the parameter will be annotated. On the byte code level, you can choose what element to annotate. But since your annotation is only compatible to parameters, the (misleading, this is now fixed on master) error message is shown.
If you want to add a dynamic number of parameters, just run the loop on the DynamicType.Builder and add the parameters like this:
MethodDefinition.ParameterDefinition builder = ...
for (...) {
builder = builder.withParameter(...).annotateParameter(...);
}
This should do the trick.

Firebase: clean way for using enum fields in Kotlin/Java?

My data on firebase uses many fields which have string type, but really are enum values (which I check in my validation rules). To download the data into my Android app, following the guide, the field must be a basic String. I know I can work around this with a second (excluded) field which is an enum, and set this basing on the string value. A short example:
class UserData : BaseModel() {
val email: String? = null
val id: String = ""
val created: Long = 0
// ... more fields omitted for clarity
#Exclude
var weightUnitEnum: WeightUnit = WeightUnit.KG
var weightUnit: String
get() = weightUnitEnum.toString()
set(value) { weightUnitEnum = WeightUnit.fromString(value) }
}
enum class WeightUnit(val str: String) {
KG("kg"), LB("lb");
override fun toString(): String = str
companion object {
#JvmStatic
fun fromString(s: String): WeightUnit = WeightUnit.valueOf(s.toUpperCase())
}
}
Now, while this works, it's not really clean:
The enum class itself is (1) kinda long for an
enum, (2) the insides are repeated for every enum. And I have more of them.
It's not only enums, the created field above is really a timestamp,
not a Long.
Each model uses these enum fields a lot of times, which bloats the model classes with repeatable code...
The helper field/functions are getting much worse/longer for fields with types such as Map<SomeEnum, Timestamp>...
So, is there any way to do this properly? Some library maybe? Or some way to write a magic "field wrapper" that would automatically convert strings to enums, or numbers to timestamps, and so on, but is still compatible with Firebase library for getting/setting data?
(Java solutions are welcome too :) )
If the conversion between a property with your enum value and another property of String type is enough, this can be easily done in a flexible way using Kotlin delegated properties.
To say it short, you can implement a delegate for String properties which performs the conversion and actually gets/sets the value of another property storing the enum values, and then delegate the String property to it.
One possible implementation would look like this:
class EnumStringDelegate<T : Enum<T>>(
private val enumClass: Class<T>,
private val otherProperty: KMutableProperty<T>,
private val enumNameToString: (String) -> String,
private val stringToEnumName: (String) -> String) {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return enumNameToString(otherProperty.call(thisRef).toString())
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
val enumValue = java.lang.Enum.valueOf(enumClass, stringToEnumName(value))
otherProperty.setter.call(thisRef, enumValue)
}
}
Note: This code requires you to add the Kotlin reflection API, kotlin-reflect, as a dependency to your project. With Gradle, use compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version".
This will be explained below, but first let me add a convenience method to avoid creating the instances directly:
inline fun <reified T : Enum<T>> enumStringLowerCase(
property: KMutableProperty<T>) = EnumStringDelegate(
T::class.java,
property,
String::toLowerCase,
String::toUpperCase)
And a usage example for your class:
// if you don't need the `str` anywhere else, the enum class can be shortened to this:
enum class WeightUnit { KG, LB }
class UserData : BaseModel() {
// ... more fields omitted for clarity
#Exclude
var weightUnitEnum: WeightUnit = WeightUnit.KG
var weightUnit: String by enumStringLowerCase(UserData::weightUnitEnum)
}
Now, the explanation:
When you write var weightUnit: String by enumStringLowerCase(UserData::weightUnitEnum), you delegate the String property to the constructed delegate object. This means that when the property is accessed, the delegate methods are called instead. And the delegate object, in turn, works with the weightUnitEnum property under the hood.
The convenience function I added saves you from the necessity of writing UserData::class.java at the property declaration site (using a reified type parameter) and provides the conversion functions to EnumStringDelegate (you can create other functions with different conversions at any time, or even make a function that receives the conversion functions as lambdas).
Basically, this solution saves you from the boilerplate code that represents a property of enum type as a String property, given the conversion logic, and also allows you to get rid of the redundant code in your enum, if you don't use it anywhere else.
Using this technique, you can implement any other conversion between properties, like the number to timestamp you mentioned.
I am in similar situation & thus found your question, plus whole lot of other similar questions/answers.
Cant answer your question directly but this is what I ended up doing: I decided to change my app & not use enum data types at all - mainly because of the advice from Google dev portal which shows how bad the enum's are on app's performance. See the video below https://www.youtube.com/watch?v=Hzs6OBcvNQE

Java annotations: pass value of annotation attribute to another annotation

I have interface Resource and several classes implementing it, for example Audio, Video... Further, I have created custom annotation MyAnnotation with Class type param:
#MyAnnotation(type = Audio.class)
class Audio {
...
}
#MyAnnotation(type = Video.class)
class Video{
...
}
In some other place in code I have to use Interface Resource as a returned type:
public class Operations<T extends Resource> {
....
#OtherAnnotation(type = Audio.class (if audio), type = Video.class (if video) )
T getResource();
....
}
The question is how to appropriatelly annotate annotation #OtherAnnotation depending of what kind of Resource type will be returned ?
What you are asking is for dynamic values for annotation attributes.
However annotations can only be set at compile time which is the reason why their values can only be compile time constants. You may only read them at runtime.
There was a similar question in which someone tried to generate the annotation value , it's answer explains why there is no way to dynamically generate a value used in annotation in a bit more detail. In that question there was an attempt to use a final class variable generated with a static method.
There are annotation processors which offer a bit more flexibility by handling placeholders. However i don't think this fits your case, as you want the dynamic values at runtime.
This answer refers to spring's use of the expression language for the Value annotation in which the placeholder (#Value("#{systemProperties.dbName})") gets overrided with the data from one of the property sources defined ( example in spring boot )
In any case, you will have to rethink your architecture a bit.

Java Annotations values provided in dynamic manner

I want to provide annotations with some values generated by some methods.
I tried this so far:
public #interface MyInterface {
String aString();
}
#MyInterface(aString = MyClass.GENERIC_GENERATED_NAME)
public class MyClass {
static final String GENERIC_GENERATED_NAME = MyClass.generateName(MyClass.class);
public static final String generateName(final Class<?> c) {
return c.getClass().getName();
}
}
Thought GENERIC_GENERATED_NAME is static final, it complains that
The value for annotation attribute MyInterface.aString must be a constant expression
So how to achieve this ?
There is no way to dynamically generate a string used in an annotation. The compiler evaluates annotation metadata for RetentionPolicy.RUNTIME annotations at compile time, but GENERIC_GENERATED_NAME isn't known until runtime. And you can't use generated values for annotations that are RetentionPolicy.SOURCE because they are discarded after compile time, so those generated values would never be known.
The solution is to use an annotated method instead. Call that method (with reflection) to get the dynamic value.
From the user's perspective we'd have:
#MyInterface
public class MyClass {
#MyName
public String generateName() {
return MyClass.class.getName();
}
}
The annotation itself would be defined as
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface #MyName {
}
Implementing the lookup for both of these annotations is rather straight-forward.
// as looked up by #MyInterface
Class<?> clazz;
Method[] methods = clazz.getDeclaredMethods();
if (methods.length != 1) {
// error
}
Method method = methods[0];
if (!method.isAnnotationPresent(MyName.class)) {
// error as well
}
// This works if the class has a public empty constructor
// (otherwise, get constructor & use setAccessible(true))
Object instance = clazz.newInstance();
// the dynamic value is here:
String name = (String) method.invoke(instance);
There is no way to modify the properties of an annotation dynamically like others said. Still if you want to achieve that, there are two ways to do this.
Assign an expression to the property in the annotation and process that expression whenever you retrieve the annotation. In your case your annotation can be
#MyInterface(aString = "objectA.doSomething(args1, args2)")
When you read that, you can process the string and make the method invocation and retrieve the value. Spring does that by SPEL (Spring expression language). This is resource intensive and the cpu cycles are wasted every time we want to process the expression. If you are using spring, you can hook in a beanPostProcessor and process the expression once and store the result somewhere. (Either a global properties object or in a map which can be retrieved anywhere).
This is a hacky way of doing what we want. Java stores a private variable which maintains a map of annotations on the class/field/method. You can use reflection and get hold of that map. So while processing the annotation for the first time, we resolve the expression and find the actual value. Then we create an annotation object of the required type. We can put the newly created annotation with the actual value (which is constant) on the property of the annotation and override the actual annotation in the retrieved map.
The way jdk stores the annotation map is java version dependent and is not reliable since it is not exposed for use (it is private).
You can find a reference implementation here.
https://rationaleemotions.wordpress.com/2016/05/27/changing-annotation-values-at-runtime/
P.S: I haven't tried and tested the second method.

Categories