Java annotation implementation to Kotlin - kotlin

I am trying to replicate the implementation of #Age constraint(found it on online) from Java to Kotlin, I copied the java code base and used the IDE to convert it Kotlin code.
Java code
#Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
#Retention(RUNTIME)
#Repeatable(Age.List.class)
#Documented
#Constraint(validatedBy = { })
public #interface Age {
String message() default "Must be greater than {value}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
long value();
#Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
#Retention(RUNTIME)
#Documented
#interface List {
Age[] value();
}
}
Corresponding Kotlin code generated by Intellij
#Target(AnnotationTarget.*)
#Retention(RUNTIME)
#Repeatable(Age.List::class)
#Documented
#Constraint(validatedBy = arrayOf())
annotation class Age(
val value: Long,
val message: String = "Must be greater than {value}",
val groups: Array<KClass<*>> = arrayOf(),
val payload: Array<KClass<out Payload>> = arrayOf()) {
#Target(AnnotationTarget.FUNCTION,
AnnotationTarget.PROPERTY_GETTER,
AnnotationTarget.PROPERTY_SETTER,
AnnotationTarget.FIELD,
AnnotationTarget.ANNOTATION_CLASS,
AnnotationTarget.CONSTRUCTOR,
AnnotationTarget.VALUE_PARAMETER,
AnnotationTarget.TYPE)
#Retention(RUNTIME)
#Documented
annotation class List(vararg val value: Age)
}
The Kotlin code results in error stating "Members are not allowed in annotation class". Does moving the annotation class List out from Age should resolve the issue? Is there any other way to implement an annotation class within another one?
Thank You.

Yes, you need to move List out of Age. The restriction on having nested classes inside annotations will very likely be removed in a future version of Kotlin; it's more of a design oversight than an essential issue.

Related

Possible to use Spring AOP (AspectJ) with Kotlin properties?

Is it possible to use Spring AOP (AspectJ) with Kotlin properties? Specifically due to how Kotlin compiles properties to Java:
a getter method, with the name calculated by prepending the get prefix
a setter method, with the name calculated by prepending the set prefix (only for var properties)
a private field, with the same name as the property name (only for properties with backing fields)
Consider the following minimal reproducible example:
#Retention(AnnotationRetention.RUNTIME)
#Target(AnnotationTarget.PROPERTY, AnnotationTarget.FUNCTION)
annotation class TestAnnotation
...
#Aspect
class TestAspect {
#Around("#annotation(annotation)")
fun throwingAround(joinPoint: ProceedingJoinPoint, annotation: TestAnnotation): Any? {
throw RuntimeException()
}
}
...
internal class MinimalReproducibleExample {
open class TestProperties {
#TestAnnotation
val sampleProperty: String = "sample property"
#TestAnnotation
fun sampleFunction(): String = "sample function"
}
private lateinit var testProperties: TestProperties
#BeforeEach
fun setUp() {
val aspectJProxyFactory = AspectJProxyFactory(TestProperties())
aspectJProxyFactory.addAspect(TestAspect())
val aopProxyFactory = DefaultAopProxyFactory()
val aopProxy = aopProxyFactory.createAopProxy(aspectJProxyFactory)
testProperties = aopProxy.proxy as TestProperties
}
#Test
fun test() {
println(testProperties.sampleProperty)
println(testProperties.sampleFunction())
}
}
Running the test yields:
null
sample function
When debugging I can see that the generated proxy is a cglib-backed proxy, which should be able to proxy to a concrete class, but it does not seem to invoke the configured aspect. Is there something wrong with my #Around definition, or is this a limitation of Kotlin properties and/or proxying concrete classes?
Was able to trigger the aspect above with the following changes:
Use a "site target" for the getter: #get:TestAnnotation
Make the property/function both open

How to get Jackson JsonProperty of enum values in Kotlin?

I'm building a schema generator and I'm trying to get the JsonProperty of enum values, for example, for this class:
enum class Size {
#JsonProperty("really-tall") TALL,
#JsonProperty("really-grande") GRANDE;
}
I'd like to get the list "really-tall", "really-grande".
How do I access the annotation of an enum?
Thanks!
UPDATE:
Solution based on this reply for a generic KType:
return (jvmErasure.java as Class<Enum<*>>)
.enumConstants
.map {
it.javaClass.getField(it.name).getAnnotation(JsonProperty::class.java)?.value // Get the JsonProperty string first if exists
?: it.name
}
Update: Additional question from OP
How do I make the first approach work for a generic KType
inline fun <reified T : Enum<T>> getJsonPropertyAnnotations() = enumValues<T>().map {
it.declaringClass
.getField(it.name)
.getAnnotation(JsonProperty::class.java)
.value
}
class SomeTest : StringSpec({
"getJsonPropertyAnnotations" {
getJsonPropertyAnnotations<Size>()
shouldBe listOf("really-tall", "really-grande")
}
})
Please note that with Kotlin 1.7, IntelliJ may show a deprecation warning with wrong replacement for declaringClass in getJsonPropertyAnnotations. I guess this will be sorted out in later versions. Link to related source
The following code should do what you want.
class SomeTest : StringSpec({
"getting annotation values" {
val result = enumValues<Size>().map {
it.declaringClass.getField(it.name).getAnnotation(JsonProperty::class.java).value
}
result shouldBe listOf("really-tall", "really-grande")
}
})
An alternative (less code): Add a String property to your enum class (I called it someFieldName in the below code), annotate with #get:JsonValue, and construct each enum entry with the string value you want. #get:JsonValue will use someFieldName instead of the enum value during serialization.
enum class Size(#get:JsonValue val someFieldName: String) {
TALL("really-tall"),
GRANDE("really-grande");
}
Same test again
class SomeTest : StringSpec({
"getting prop values" {
val result = enumValues<Size>().map {
it.someFieldName
}
result shouldBe listOf("really-tall", "really-grande")
}
})
We're using the latter approach in an ongoing project.
Here's a Kotlin implementation of the technique suggested by user #aSemy.
It's an extension function on ObjectMapper for asking the mapper how it would serialize the values of an enum. This is more robust that just inspecting the #JsonProperty annotation, since it works with #JsonValue as well as any custom annotation introspectors registered with the mapper.
inline fun <reified T : Enum<T>> ObjectMapper.enumValues() : List<String> {
return convertValue(
kotlin.enumValues<T>(),
jacksonTypeRef<List<String>>()
)
}
Usage:
println(jsonMapper().enumValues<Size>())

Jackson Serializaiton/Deserialization by custom property in enum

I want to make Jackson work with enums not by name and not by ordinal, but with a custom property I added called "stringId".
I wanted to support this with all Enums in the system so I made an interface called StringIdEnum which the FooEnum will implement.
I'm using Kotlin so I created a property in the interface called stringId which I override in each enum value.
Now I want to make Jackson serialize and deserialize using this stringId field, from what I seen I have several options:
Use #JsonProperty annotation on each enum value and make sure it is aligned with the stringId property.
I see two issues with this approach. one it's a lot of annotation to add (we have many enum classes across the system). two I need to make sure the annotation value and the property value should be always the same which can cause issues in the future.
I tried to use the READ_ENUMS_USING_TO_STRING feature, but because I'm using an interface I can't override the toString in the interface class (I can override it in every enum class but that again seems like a lot of redundant code)
Implement a custom serializer/deserializer.
The serializer is pretty straightforward, however, I had trouble with the deserializer.
I wanted to register the deserializer on the StringIdEnum interface, but I had an issue getting all the runtime enum values for the actual FooType enum.
StringIdEnum:
interface StringIdEnum {
val stringId: String
}
enum class FooType(override val stringId: String) : StringIdEnum {
FOO("FOO"),
GOO("GOO");
}
Managed to get it working:
#JsonSerialize(using = StringIdEnumSerializer::class)
#JsonDeserialize(using = StringIdEnumDeserializer::class)
interface StringIdEnum: DbEnum {
val stringId: String
}
class StringIdEnumSerializer: StdSerializer<StringIdEnum>(StringIdEnum::class.java) {
override fun serialize(value: StringIdEnum, gen: JsonGenerator, provider: SerializerProvider) {
gen.writeString(value.stringId)
}
}
class StringIdEnumDeserializer : JsonDeserializer<Enum<*>>(), ContextualDeserializer {
private lateinit var type: JavaType
override fun deserialize(p: JsonParser, ctxt: DeserializationContext): Enum<*> {
val t = p.text
val enumConstants = (type.rawClass as Class<Enum<*>>).enumConstants
return enumConstants.single { (it as StringIdEnum).stringId == t }
}
override fun createContextual(ctxt: DeserializationContext?, property: BeanProperty?): JsonDeserializer<*> {
val wrapperType: JavaType = property!!.type
val stringIdEnumDeserializer = StringIdEnumDeserializer()
stringIdEnumDeserializer.type = wrapperType
return stringIdEnumDeserializer
}
}

POJO class mismatch

I have the following class User that extends the BaseResponse class. I
am getting a type mismatch error:
Required => String
Found => String.Companion
for return apiKey
package com.touchsides.rxjavanetworking.network.model
import com.google.gson.annotations.SerializedName
class User: BaseResponse()
{
#SerializedName("api_key")
val apiKey = String
fun getApiKey(): String
{
return apiKey
}
}
abstract class BaseResponse(var error: String?=null)
{
}
How is the current implementation of this wrong
You used = instead : while declaration of api_key (apiKey = String). Which actually means you are initialising api_key with String.Companion Object.
And you don't need to create getApiKey() (getter) method as by default you will have getter method for your properties.
class User : BaseResponse() {
#SerializedName("api_key")
var apiKey: String? = null
private set
}
abstract class BaseResponse(var error: String? = null)
in fact you can use data class for this purposes
data class User(#SerializedName("api_key") val apiKey: String):BaseResponse()
fun main(args: Array<String>) {
Gson().fromJson<User>("{\"api_key\":\"my api key\"}", User::class.java).let {
println(it.apiKey)
}
}
A complete answer is that your code should look like this:
class User: BaseResponse()
{
#SerializedName("api_key")
lateinit var apiKey: String // must be set by something before being read
}
abstract class BaseResponse(var error: String?=null) {
}
You do not need a default value for the apiKey property if you intend to set it via deserialization later, if not then you should also add a default value as below. The getApiKey() method is removed because you do not need that in Kotlin, all properties have automatically generated getters built-in and by adding your own you would end up with a conflict between the generated getter and the one you manually created (two methods with the same name, same signature).
If you do need a default value for apiKey then stay with a var so that deserialization can work (if you intend to do that) and add a default empty string or make it a nullable string and set it to null.
class User: BaseResponse()
{
#SerializedName("api_key")
var apiKey: String = "" // if you want a default regardless, or make it nullable and null
}
abstract class BaseResponse(var error: String?=null) {}
You're stuck with the way Java do things. In kotlin when defining Getter and Setter, you don't have to write them yourself. Once you declare a variable, both methods would be automatically created.
EDIT: So delete the getter in your POJO class.

Create an annotation instance in Kotlin

I have a framework written in Java that, using reflection, get the fields on an annotation and make some decisions based on them. At some point I am also able to create an ad-hoc instance of the annotation and set the fields myself. This part looks something like this:
public #interface ThirdPartyAnnotation{
String foo();
}
class MyApp{
ThirdPartyAnnotation getInstanceOfAnnotation(final String foo)
{
ThirdPartyAnnotation annotation = new ThirdPartyAnnotation()
{
#Override
public String foo()
{
return foo;
}
};
return annotation;
}
}
Now I am trying to do the exact thing in Kotlin. Bear in mind that the annotation is in a third party jar.
Anyway, here is how I tried it in Kotlin:
class MyApp{
fun getAnnotationInstance(fooString:String):ThirdPartyAnnotation{
return ThirdPartyAnnotation(){
override fun foo=fooString
}
}
But the compiler complains about: Annotation class cannot be instantiated
So the question is: how should I do this in Kotlin?
You can do this with Kotlin reflection:
val annotation = ThirdPartyAnnotation::class.constructors.first().call("fooValue")
In the case of annotation having no-arg constructor (e.g. each annotation field has a default value), you can use following approach:
annotation class SomeAnnotation(
val someField: Boolean = false,
)
val annotation = SomeAnnotation::class.createInstance()
This is the solution I might have found but feels like a hack to me and I would prefer to be able to solve it within the language.
Anyway, for what is worth,it goes like this:
class MyApp {
fun getInstanceOfAnnotation(foo: String): ThirdPartyAnnotation {
val annotationListener = object : InvocationHandler {
override fun invoke(proxy: Any?, method: Method?, args: Array<out Any>?): Any? {
return when (method?.name) {
"foo" -> foo
else -> FindBy::class.java
}
}
}
return Proxy.newProxyInstance(ThirdPartyAnnotation::class.java.classLoader, arrayOf(ThirdPartyAnnotation::class.java), annotationListener) as ThirdPartyAnnotation
}
}