Can you define alternative shorthands for Kotlin Annotations? - kotlin

I am using annotations and reflection to create a parser for some custom made files used in the project I work with
I have this annotation that will be used to annotate most data class constructor parameters
annotation class Element(val name: String = "",val type: ElementType = ElementType.Value)
the enum ElementType has these values
enum class XElementType {
Value,
Attribute,
Ignore
}
is there a way to create a shorthand or alternate so that instead of using
#Element(type=ElementType.Ignore)
val ignoredVariable:String
I can use something like
#IgnoreElement
val ignoredVariable:String
which will resolve to Element("",ElementType.Ignore) ?

Related

How to set serializer to an internal class extending a public interface?

I'm trying to create a serializer using kotlinx.serialization for Compose Desktop classes, I have this :
#Serializer(forClass = MutableState::class)
class MutableStateSerializer<T>(private val dataSerializer: KSerializer<T>) : KSerializer<MutableState<T>> {
override fun deserialize(decoder: Decoder) = mutableStateOf(decoder.decodeSerializableValue(dataSerializer))
override val descriptor: SerialDescriptor = dataSerializer.descriptor
override fun serialize(encoder: Encoder, value: MutableState<T>) = encoder.encodeSerializableValue(dataSerializer, value.value)
}
That should be used for instances of MutableState class (as the #Serializer annotation says), but I have to put an explicit serializer for each properties otherwise I get this error :
xception in thread "main" kotlinx.serialization.SerializationException: Class 'SnapshotMutableStateImpl' is not registered for polymorphic serialization in the scope of 'MutableState'.
Mark the base class as 'sealed' or register the serializer explicitly
Code used :
#Serializable
class Test {
var number = mutableStateOf(0)
}
fun main() {
val json = Json { prettyPrint = true }
val serialized = json.encodeToString(Test())
println(serialized)
}
I have to put this annotation on my property :
#Serializable(with = MutableStateSerializer::class)
Isn't there a way to automatically link my serializer to the MutableState interface ? As the SnapshotMutableStateImpl is internal I can't set it to this class.
What you want is currently not possible. Other people seem to have requested a feature similar to what you need on GitHub: Global Custom Serializers.
Currently, for 3rd party classes, you need to specify the serializer in one of three ways:
Pass the custom serializer to the encode/decode method in case you are serializing it as the root object.
Specify the serializer on the property using #Serializable, as you do now.
Specify the serializer to be used by a full file using #file:UseSerializers.
Note that due to type inference, number will be attempted to be serialized as the return type of mutableStateOf. If you specify the type as an interface instead (does it have a supertype?), using polymorphic serialization, you could try to register the concrete type and pass your custom serializer there for the concrete type. Not really what this feature is designed for, but I believe it may work if you don't want to specify your serializer in multiple places. However, the serialized form will then include a type discriminator everywhere.

Getting annotation of enum value

I know how to get an annotation of an enum value in Java.
However Kotlin has its own reflection library and I feel there should be a better way to do the job.
Could please anybody post an example.
Just to be specific let's define an enum class
enum class Enum {
#SerialName("constant")
Constant
}
I need a function f(e: Enum): String so that f(Enum.Constant) == "constant".
You can use a similar approach with java by getting the field by name and then reaching out to the annotation using annotation class.
So if you have below enum and annotation class definitions;
enum class Enum {
#SerialName("constant")
Constant
}
annotation class SerialName(val value: String)
Then you can define the below function and call it as shown below;
fun getAnnotationValue(enum:Enum):String = enum.declaringClass.getField(enum.name).getAnnotation(SerialName::class.java).value
fun main(args: Array<String>) {
println(getAnnotationValue(Enum.Constant))
}
Hope this helps.
I did only a little research here, but it appears there isn't support for this in the Kotlin reflection library. In fact, I discovered the linter doesn't even correctly suggest an annotation target of FIELD for your annotation if you give it one that doesn't work for Enum values, and instead incorrectly offers to automatically add a target of CLASS.
The problem is that Enum values are basically static member fields, which don't exist in Kotlin except in Enum classes. And the reflection classes don't seem to provide a way to access this special case.
I am struggling however to come up with a use case for Enum value annotations that can't be solved using properties in the Enum constructor(s).
enum class MyEnum(val someConstant: String? = null) {
SomeValue("myConstant")
}

Acquire annotation parameters (or annotation instance) from Kotlin PSI

I have a Kotlin annotation:
#Retention(AnnotationRetention.SOURCE)
#Target(AnnotationTarget.CLASS)
annotation class Type(
val type: String
)
It can be used on the Kotlin classes in two ways: using the named parameter syntax, or using the positional parameter syntax:
#Type(type = "named")
data class Named(
…
)
#Type("positional")
data class Positional
…
)
I use this annotation in my custom detekt rules for some extra checks. I need to extract the value of the type parameter to perform some check based on it. I do that like:
private fun getType(klass: KtClass): String? {
val annotation = klass
.annotationEntries
.find {
"Type" == it?.shortName?.asString()
}
val type = (annotation
?.valueArguments
?.find {
it.getArgumentName()?.asName?.asString() == "type"
}
?.getArgumentExpression() as? KtStringTemplateExpression)
?.takeUnless { it.hasInterpolation() }
?.plainContent
return type
}
But this code works only with "named" parameters syntax, and fails for the positional one. Is there any way to get the value of an annotation parameter no matter what syntax was used? It would be perfect if I could acquire my Type annotation instance directly from PSI / AST / KtElements and use it like usually. Is it possible to instantiate an annotation from the PSI tree?

How do I get the class name from a type name?

I am trying to deserialize a Json string into an object of type OperationResult<String> using Jackson with Kotlin.
I need to construct a type object like so:
val mapper : ObjectMapper = ObjectMapper();
val type : JavaType = mapper.getTypeFactory()
.constructParametricType(*/ class of OperationResult */,,
/* class of String */);
val result : OperationResult<String> = mapper.readValue(
responseString, type);
I've tried the following but they do not work.
val type : JavaType = mapper.getTypeFactory()
.constructParametricType(
javaClass<OperationResult>,
javaClass<String>); // Unresolved javaClass<T>
val type : JavaType = mapper.getTypeFactory()
.constructParametricType(
OperationResult::class,
String::class);
How do I get a java class from the type names?
You need to obtain instance of Class not KClass. To get it you simply use ::class.java instead of ::class.
val type : JavaType = mapper.typeFactory.constructParametricType(OperationResult::class.java, String::class.java)
Kotlin has a few things that become a concern when using Jackson, GSON or other libraries that instantiate Kotlin objects. One, is how do you get the Class, TypeToken, TypeReference or other specialized class that some libraries want to know about. The other is how can they construct classes that do not always have default constructors, or are immutable.
For Jackson, a module was built specifically to cover these cases. It is mentioned in #miensol's answer. He shows an example similar to:
import com.fasterxml.jackson.module.kotlin.* // added for clarity
val operationalResult: OperationalResult<Long> = mapper.readValue(""{"result":"5"}""")
This is actually calling an inline extension function added to ObjectMapper by the Kotlin module, and it uses the inferred type of the result grabbing the reified generics (available to inline functions) to do whatever is needed to tell Jackson about the data type. It creates a Jackson TypeReference behind the scenes for you and passes it along to Jackson. This is the source of the function:
inline fun <reified T: Any> ObjectMapper.readValue(content: String): T = readValue(content, object: TypeReference<T>() {})
You can easily code the same, but the module has a larger number of these helpers to do this work for you. In addition it handles being able to call non-default constructors and static factory methods for you as well. And in Jackson 2.8.+ it also can deal more intelligently with nullability and default method parameters (allowing the values to be missing in the JSON and therefore using the default value). Without the module, you will soon find new errors.
As for your use of mapper.typeFactory.constructParametricType you should use TypeReference instead, it is much easier and follows the same pattern as above.
val myTypeRef = object: TypeReference<SomeOtherClass>() {}
This code creates an anonymous instance of a class (via an object expression) that has a super type of TypeRefrence with your generic class specified. Java reflection can then query this information.
Be careful using Class directly because it erases generic type information, so using SomeOtherClass::class or SomeOtherClass::class.java all lose the generics and should be avoided for things that require knowledge of them.
So even if you can get away with some things without using the Jackson-Kotlin module, you'll soon run into a lot of pain later. Instead of having to mangle your Kotlin this module removes these types of errors and lets you do things more in the "Kotlin way."
The following works as expected:
val type = mapper.typeFactory.constructParametricType(OperationalResult::class.java, String::class.java)
val operationalResult = mapper.readValue<OperationalResult<String>>("""{"result":"stack"}""", type)
println(operationalResult.result) // -> stack
A simpler alternative to deserialize generic types using com.fasterxml.jackson.core.type.TypeReference:
val operationalResult = mapper.readValue<OperationalResult<Double>>("""{"result":"5.5"}""",
object : TypeReference<OperationalResult<Double>>() {})
println(operationalResult.result) // -> 5.5
And with the aid of jackson-kotlin-module you can even write:
val operationalResult = mapper.readValue<OperationalResult<Long>>("""{"result":"5"}""")
println(operationalResult.result)

Use of Parceler with Kotlin data class with constructor for serialization

Is there a way to use Parceler with Kotlin data classes and constructor for serialization without using #ParcelProperty annotation for each field?
If I try and use library like this:
#Parcel
data class Valve #ParcelConstructor constructor(val size: Int)
I get Error:Parceler: No corresponding property found for constructor parameter arg0. But if I add #ParcelProperty("size") it works just fine.
Why is that?
Update:
There are other another way to use this library.
I could just remove #ParcelConstructor annotation, but then I will get error
Error:Parceler: No #ParcelConstructor annotated constructor and no default empty bean constructor found.
I think (haven't tested it) I also could make all constructor parameters optional and add #JvmOverloads but that has a side effect that I have to check all properties of the class if they are null or not.
Update 2:
This is what worked for me:
#Parcel
data class Valve(val size: Int? = null)
In short generated Java class must have default empty constructor. One way to achieve that is to do as above - all variables should have default values.
According to the docs, Parceler by default works with public fields. But a usual Kotlin data class (as in your example) is rather a "traditional getter/setter bean", since every Kotlin property is represented by a private field and a getter/[setter].
TL; DR: I think this will work:
#Parcel(Serialization.BEAN)
data class Valve(val size: Int = 10)
Note the default value, it allows Kotlin to automatically generate an additional empty constructor, which is required by the Java Been specification.
Another way would be to mark the constructor that we already have:
#Parcel(Serialization.BEAN)
data class Driver #ParcelConstructor constructor(val name: String)
The specific document: https://github.com/johncarl81/parceler#gettersetter-serialization
I know this question already has an answer, but for future viewers who are also struggling to get Parceler to work with kotlin data objects, I wrote a new annotation processor to generate the Parcelable boilerplate for Kotlin data classes. It's designed to massively reduce the boilerplate code in making your data classes Parcelable:
https://github.com/grandstaish/paperparcel
Usage:
Annotate your data class with #PaperParcel, implement PaperParcelable, and add a JVM static instance of the generated CREATOR e.g.:
#PaperParcel
data class Example(
val test: Int,
...
) : PaperParcelable {
companion object {
#JvmField val CREATOR = PaperParcelExample.CREATOR
}
}
Now your data class is Parcelable and can be passed directly to a Bundle or Intent
Edit: Update with latest API
Just add the default constructor:
#Parcel
data class Valve(val size: Int) {
constructor() : this(0)
}
if you use Kotlin 1.1.4 or above it's easier to use #Parcelize annotation
For doing this first add this to build.gradle
android {
//other codes
//for using latest experimental build of Android Extensions
androidExtensions {
experimental = true
}
}
Then change your class like this
#Parcelize
data class Valve(val size: Int? = null) : Parcelable