Reputation: 3304
I'm writing an annotation processor for Kotlin which needs to know how to call a method, i.e. whether or not a class needs to be constructed.
The following works for Java code, for values, and for @JvmStatic
-labeled code, but not for Kotlin object Foo {}
singletons:
import javax.lang.model.element.Element
import javax.lang.model.element.Modifier
// Fails if el is in a singleton
fun isStatic(el: Element) = el.modifiers.contains(Modifier.STATIC)
What's the best way to detect if a method can be called without constructing a class?
Upvotes: 2
Views: 181
Reputation: 3304
This isn't a perfect solution, but this is what I came up with. It checks for kotlin.Metadata
in the binary data to see if it's a Kotlin class, and then uses heuristics to tease out whether or not it is static.
A similar technique is required if you want to find a main file, i.e. a file which can be run from the command line.
fun isKotlinClass(el: TypeElement)
= el.annotationMirrors.any { it.annotationType.toString() == "kotlin.Metadata" }
/** Check for Java static or Kotlin singleton.
* An imperfect heuristic: if not static, checks for a static INSTANCE field. */
private fun isStatic(element: Element): Boolean {
if (element.modifiers.contains(Modifier.STATIC)) return true
else {
val parent = element.enclosingElement
if (parent is TypeElement && isKotlinClass(parent)) {
val instances = parent.enclosedElements
.filter { "INSTANCE" == it.simpleName.toString() }
.filter { it.modifiers.contains(Modifier.STATIC) }
.filter { it.kind.isField }
return instances.isNotEmpty()
}
return false
}
}
Upvotes: 1
Reputation: 5635
So internally object
in Kotlin
is a simple Singleton
. The difference is that it is managed by the Kotlin language itself. Such class still needs to be instantiated in order to call its functions. Just this instantiation is made by Kotlin since objects have private constructor and a static INSTANCE
field that holds its single instance.
Let's take a look at this example. I have object A
defined like this:
object A {
fun a() {
}
}
If we will look at the java bytecode and will convert it to java we will have this:
import kotlin.Metadata;
@Metadata(
mv = {1, 1, 10},
bv = {1, 0, 2},
k = 1,
d1 = {"\u0000\u0012\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\b\u0002\n\u0002\u0010\u0002\n\u0000\bÆ\u0002\u0018\u00002\u00020\u0001B\u0007\b\u0002¢\u0006\u0002\u0010\u0002J\u0006\u0010\u0003\u001a\u00020\u0004¨\u0006\u0005"},
d2 = {"Lcom/telenor/self_service/app/A;", "", "()V", "a", "", "production sources for module app"}
)
public final class A {
public static final A INSTANCE;
public final void a() {
}
static {
A var0 = new A();
INSTANCE = var0;
}
}
As you can see reading only class body we can't understand if it is an object
or not, cause if we will create similar class using Java only it will behave a class
(not object
).
public final class B {
public static final B INSTANCE;
public final void b() {
}
static {
B var0 = new B();
INSTANCE = var0;
}
}
But, when we try to create a new instance from Java like this, it will say that A
has private constructor and cannot be created.
new A();
new B();
So the difference here is somehow made with Kotlin @Metadata
annotation which defines this difference.
As a solution you can either check the INSTANCE
static field, or you can read Kotlin metadata somehow :)
Upvotes: 0