Acquire annotation parameters (or annotation instance) from Kotlin PSI - kotlin

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?

Related

How to read kotlin annotation

I have annotation classes
annotation class Endpoint(val name: String)
#Target(AnnotationTarget.TYPE)
annotation class JsonObjectKey(val name: String)
These are used like so
#Endpoint("my-endpoint")
fun myEndpoint(): #JsonObjectKey("key") String {
return "value"
}
With a method: java.lang.reflect.Method object representing myEndpoint,
I am able to access the Endpoint-Annotation, but I fail to access the JsonObjectKey-Annotatation.
method.annotations only contains Endpoint
method.annotatedReturnType.annotations is empty
How to read JsonObjectKey (key in this scenario)?
Intention is to generate JsonObject {"key": "value"}
Use the Kotlin reflection API!
For example, this works:
fun main() {
println(::myEndpoint.returnType.annotations)
}
#Target(AnnotationTarget.TYPE)
annotation class JsonObjectKey(val name: String)
fun myEndpoint(): #JsonObjectKey("key") String {
return "value"
}
This outputs:
[#JsonObjectKey(name=key)]
If you only have a java.lang.reflect.Method for some reason, you can convert it to a KFunction using kotlinFunction:
println(someJavaMethod?.kotlinFunction?.returnType?.annotations)
It appears that annotations annotated in this position in Java:
public static #JsonObjectKey(name = "foo") String foo()
are very different from those annotated in this position in Kotlin:
fun foo(): #JsonObjectKey("key") String = ""
These seem to be two different positions. Kotlin reflection cannot see the annotation on the Java return type, and Java reflection cannot see the annotation on the Kotlin return type.
Compare how the annotations appear in the class file. For the Java method, the annotation shows up in the RuntimeVisibleTypeAnnotations attribute:
RuntimeVisibleTypeAnnotations:
0: #23(#24=s#20): METHOD_RETURN
JsonObjectKey(
name="foo"
)
On the other hand, the Kotlin method instead has the annotation stored as part of the kotlin.Metadata annotation:
RuntimeVisibleAnnotations:
0: #86(#87=[I#88,I#89,I#90],#91=I#92,#93=I#94,#95=[s#96],#97=[s#5,s#98,s#76,s#98,s#99,s#100,s#101,s#102])
kotlin.Metadata(
mv=[1,6,0]
k=2
xi=48
d1=["..."]
d2=["main","","myEndpoint","","LJsonObjectKey;","name","key","myproject"]
)

Can you define alternative shorthands for Kotlin Annotations?

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) ?

::property.isInitialized cannot differentiate between method and property with same name

I'm creating a builder (for Java compat), where context is both a private property and public method.
private lateinit var context: Context
fun context(appContext: Context) = apply {
context = appContext
}
fun build(): MySdk {
// this::context fails to compile because it cannot differentiate between the
// method `context()` vs property `context`
require(this::context.isInitialized) {
"context == null"
}
But I get a compilation issue for ::context.isInitialized, because it cannot differentiate between the method context() vs property context
Does Kotlin have a workaround for this? or am I forced to use unique property/method names?
This is a case of overload resolution ambiguity and the kotlin compiler is unable to identify whether you are using the property or the method.
This is because of callable references (::) . Internally when you are using the callable references it calls a method.
Callable references : References to functions, properties, and
constructors, apart from introspecting the program structure, can also
be called or used as instances of function types.
The common supertype for all callable references is KCallable, where R is the return value type, which is the property type for properties, and the constructed type for constructors.
KCallable<out R> // supertype for all callable references
So, for function the type is KFunction and for properties the type is KProperty
interface KFunction<out R> : KCallable<R>, Function<R> (source)
interface KProperty<out R> : KCallable<R> (source)
When you use a function like :
fun context(appContext: Context) = apply {
context = appContext
}
It can be used as a Function reference
::context // This is a Function reference i.e. KFunction
When you use a property reference, like
private lateinit var context: Context
fun something(){
::context // this is a property reference, KProperty
}
A property reference can be used where a function with one parameter is expected:
val strs = listOf("a", "bc", "def")
println(strs.map(String::length))
So, its not that Kotlin forces you to use different property and function names("although it is not recommended"). Its just that its unable to differentiate in this case as
Both are KCallable and have the same name
A property reference can be used where a function with one parameter is expected
You can resolve the ambiguity between the property and the method by giving the expected type:
val prop: kotlin.reflect.KProperty0<*> = this::context
Alas, prop.isInitialized then gives a compilation error:
This declaration can only be called on a property literal (e.g. 'Foo::bar')
So this doesn't appear to be possible currently. OTOH, since the error shows isInitialized is already handled specially by the compiler, it's likely possible to fix; I suggest reporting it on http://youtrack.jetbrains.com/ (after searching for duplicates).

Kotlin - Extension for final class

Is it possible to create extension of final classes like String? Like in swift it is possible to add additional methods inside a extension of final class.
For an example - I would like to create a method in String extension which will tell me String have valid length for password.
val password : String = mEdtPassword!!.getText().toString()
// how to define haveValidLength method in extension
val isValid : Boolean = password.haveValidLength()
Note - That example is just for a sake to understand usability of extension, not a real scenario.
yes, you can. Kotin extension method provides the ability to extend a class with new functionality without having to inherit from the class or use any type of design pattern such as Decorator.
Below is an extension method for a String:
// v--- the extension method receiver type
fun String.at(value: Int) = this[value]
And the extension method code generated as Java below:
public static char at(String receiver, int value){
return receiver.charAt(value);
}
So an extension method in Kotlin is using delegation rather than inheritance.
Then you can calling an extension method like as its member function as below:
println("bar".at(1))//println 'a'
You also can write an extension method for the existing extension function, for example:
fun String.substring(value: Int): String = TODO()
// v--- throws exception rather than return "ar"
"bar".substring(1)
But you can't write an extension method for the existing member function, for example:
operator fun String.get(value: Int): Char = TODO()
// v--- return 'a' rather than throws an Exception
val second = "bar"[1]
Trying to add more detail, this answer might be helpful for someone.
Yes we can add additional methods to final classes like String. For an example I would like to add one method in String which will tell me that my String have valid number of characters for password or not.
So what I have to do is, I have ti create a below function which can be written in same class or at different separate class file.
fun String.hasValidPassword() : Boolean {
// Even no need to send string from outside, use 'this' for reference of a String
return !TextUtils.isEmpty(this) && this.length > 6
}
And now from anywhere call
val isValid : Boolean = password.haveValidLength()
Suggestion
If your application just has a single password validation, then there is no problem.
However, I don't suggest you write such a extension method hasValidPassword if the application has more than one validation. since the extension method is satically, you can't change your hasValidPassword in runtime. So if you want to change the validation in runtime, you should using a function instead, for example:
class PasswordRepository(private val validate:(String)->Boolean){
fun save(value:String){
if(validate(value)){
//TODO persist the password
}
}
}
val permitAll = PasswordRepository {true}
val denyAll = PasswordRepository {false}
permitAll.save("it will be persisted")
denyAll.save("it will not be persisted")
In other words, the extension method above violates Single Responsibility Principle, it does validation & string operations.
You can do that with extension functions in Kotlin. With extensions, you are able to add extra functionality to a class that you do or do not have access to; for example a legacy code base. In the example given in the Kotlin docs here, swap was added to MutableList<Int> which doesn't have swap originally. A this keyword is used that refers to the object that the swap functionality will operate on. In the example below, this refers to testList
val testList = mutableListOf(1, 2, 3)
testList.swap(0, 2)

Setter overloading in Kotlin

When trying to define a setter that accepts a parameter type that can be used to construct a property, thusly:
class Buffer(buf: String) {}
class Foo {
var buffer: Buffer? = null
set(value: String) {
field = Buffer(value)
}
}
I get the error message:
Setter parameter type must be equal to the type of the property
So what's meant to be the Kotlin way of doing this?
As of Kotlin 1.1 it is not possible to overload property setters. The feature request is tracked here:
https://youtrack.jetbrains.com/issue/KT-4075
Currently, you would have to define a buffer extension function on String:
val String.buffer : Buffer
get() = Buffer(this)
and set the value with
Foo().buffer = "123".buffer