Обнаружение синглетонов в капте
Я пишу процессор аннотаций для Kotlin, который должен знать, как вызывать метод, т.е. нужно ли создавать класс.
Следующее работает для кода Java, для значений и для @JvmStatic
код, но не для котлина object Foo {}
одиночки:
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)
Как лучше всего определить, можно ли вызвать метод, не создавая класс?
2 ответа
Это не идеальное решение, но это то, что я придумал. Это проверяет на kotlin.Metadata
в двоичных данных, чтобы увидеть, является ли это класс Kotlin, а затем использует эвристику, чтобы определить, является ли она статической.
Подобная техника требуется, если вы хотите найти основной файл, то есть файл, который можно запустить из командной строки.
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
}
}
Так внутренне object
в Kotlin
это простой Singleton
, Разница в том, что он управляется самим языком Kotlin. Такой класс еще нужно создать, чтобы вызывать его функции. Именно этот экземпляр сделан Kotlin, так как объекты имеют приватный конструктор и статический INSTANCE
поле, которое содержит свой единственный экземпляр. Давайте посмотрим на этот пример. я имею object A
определяется так:
object A {
fun a() {
}
}
Если мы посмотрим на байт-код Java и преобразуем его в Java, мы получим следующее:
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;
}
}
Как вы можете видеть только для чтения тела класса, мы не можем понять, если это object
или нет, потому что если мы создадим подобный класс, используя только Java, он будет вести себя class
(не object
).
public final class B {
public static final B INSTANCE;
public final void b() {
}
static {
B var0 = new B();
INSTANCE = var0;
}
}
Но когда мы попытаемся создать новый экземпляр из Java, как это, он скажет, что A
имеет приватный конструктор и не может быть создан.
new A();
new B();
Так что разница здесь как-то сделана с Kotlin @Metadata
аннотация, которая определяет эту разницу.
В качестве решения вы можете проверить INSTANCE
статическое поле, или вы можете прочитать метаданные Kotlin как-то:)