blob: e79616aed25ead1215d19ff403381f7b807c067e [file] [log] [blame]
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.room.ext
import androidx.room.compiler.codegen.CodeLanguage
import androidx.room.compiler.codegen.VisibilityModifier
import androidx.room.compiler.codegen.XClassName
import androidx.room.compiler.codegen.XCodeBlock
import androidx.room.compiler.codegen.XFunSpec
import androidx.room.compiler.codegen.XFunSpec.Builder.Companion.apply
import androidx.room.compiler.codegen.XMemberName.Companion.companionMember
import androidx.room.compiler.codegen.XMemberName.Companion.packageMember
import androidx.room.compiler.codegen.XTypeName
import androidx.room.compiler.codegen.XTypeSpec
import androidx.room.compiler.codegen.asClassName
import androidx.room.compiler.codegen.asMutableClassName
import com.squareup.kotlinpoet.javapoet.JTypeName
import java.util.concurrent.Callable
object SupportDbTypeNames {
val DB = XClassName.get("$SQLITE_PACKAGE.db", "SupportSQLiteDatabase")
val SQLITE_STMT = XClassName.get("$SQLITE_PACKAGE.db", "SupportSQLiteStatement")
val SQLITE_OPEN_HELPER = XClassName.get("$SQLITE_PACKAGE.db", "SupportSQLiteOpenHelper")
val SQLITE_OPEN_HELPER_CALLBACK =
XClassName.get("$SQLITE_PACKAGE.db", "SupportSQLiteOpenHelper", "Callback")
val SQLITE_OPEN_HELPER_CONFIG =
XClassName.get("$SQLITE_PACKAGE.db", "SupportSQLiteOpenHelper", "Configuration")
val QUERY = XClassName.get("$SQLITE_PACKAGE.db", "SupportSQLiteQuery")
}
object RoomTypeNames {
val STRING_UTIL = XClassName.get("$ROOM_PACKAGE.util", "StringUtil")
val ROOM_DB = XClassName.get(ROOM_PACKAGE, "RoomDatabase")
val ROOM_DB_KT = XClassName.get(ROOM_PACKAGE, "RoomDatabaseKt")
val ROOM_DB_CALLBACK = XClassName.get(ROOM_PACKAGE, "RoomDatabase", "Callback")
val ROOM_DB_CONFIG = XClassName.get(ROOM_PACKAGE, "DatabaseConfiguration")
val INSERTION_ADAPTER = XClassName.get(ROOM_PACKAGE, "EntityInsertionAdapter")
val UPSERTION_ADAPTER = XClassName.get(ROOM_PACKAGE, "EntityUpsertionAdapter")
val DELETE_OR_UPDATE_ADAPTER = XClassName.get(ROOM_PACKAGE, "EntityDeletionOrUpdateAdapter")
val SHARED_SQLITE_STMT = XClassName.get(ROOM_PACKAGE, "SharedSQLiteStatement")
val INVALIDATION_TRACKER = XClassName.get(ROOM_PACKAGE, "InvalidationTracker")
val ROOM_SQL_QUERY = XClassName.get(ROOM_PACKAGE, "RoomSQLiteQuery")
val OPEN_HELPER = XClassName.get(ROOM_PACKAGE, "RoomOpenHelper")
val OPEN_HELPER_DELEGATE = XClassName.get(ROOM_PACKAGE, "RoomOpenHelper", "Delegate")
val OPEN_HELPER_VALIDATION_RESULT =
XClassName.get(ROOM_PACKAGE, "RoomOpenHelper", "ValidationResult")
val TABLE_INFO = XClassName.get("$ROOM_PACKAGE.util", "TableInfo")
val TABLE_INFO_COLUMN = XClassName.get("$ROOM_PACKAGE.util", "TableInfo", "Column")
val TABLE_INFO_FOREIGN_KEY = XClassName.get("$ROOM_PACKAGE.util", "TableInfo", "ForeignKey")
val TABLE_INFO_INDEX =
XClassName.get("$ROOM_PACKAGE.util", "TableInfo", "Index")
val FTS_TABLE_INFO = XClassName.get("$ROOM_PACKAGE.util", "FtsTableInfo")
val VIEW_INFO = XClassName.get("$ROOM_PACKAGE.util", "ViewInfo")
val LIMIT_OFFSET_DATA_SOURCE = XClassName.get("$ROOM_PACKAGE.paging", "LimitOffsetDataSource")
val DB_UTIL = XClassName.get("$ROOM_PACKAGE.util", "DBUtil")
val CURSOR_UTIL = XClassName.get("$ROOM_PACKAGE.util", "CursorUtil")
val MIGRATION = XClassName.get("$ROOM_PACKAGE.migration", "Migration")
val AUTO_MIGRATION_SPEC = XClassName.get("$ROOM_PACKAGE.migration", "AutoMigrationSpec")
val UUID_UTIL = XClassName.get("$ROOM_PACKAGE.util", "UUIDUtil")
val AMBIGUOUS_COLUMN_RESOLVER = XClassName.get(ROOM_PACKAGE, "AmbiguousColumnResolver")
val RELATION_UTIL = XClassName.get("androidx.room.util", "RelationUtil")
}
object RoomAnnotationTypeNames {
val QUERY = XClassName.get(ROOM_PACKAGE, "Query")
val DAO = XClassName.get(ROOM_PACKAGE, "Dao")
val DATABASE = XClassName.get(ROOM_PACKAGE, "Database")
val PRIMARY_KEY = XClassName.get(ROOM_PACKAGE, "PrimaryKey")
val TYPE_CONVERTERS = XClassName.get(ROOM_PACKAGE, "TypeConverters")
val TYPE_CONVERTER = XClassName.get(ROOM_PACKAGE, "TypeConverter")
val ENTITY = XClassName.get(ROOM_PACKAGE, "Entity")
}
object PagingTypeNames {
val DATA_SOURCE = XClassName.get(PAGING_PACKAGE, "DataSource")
val POSITIONAL_DATA_SOURCE = XClassName.get(PAGING_PACKAGE, "PositionalDataSource")
val DATA_SOURCE_FACTORY = XClassName.get(PAGING_PACKAGE, "DataSource", "Factory")
val PAGING_SOURCE = XClassName.get(PAGING_PACKAGE, "PagingSource")
val LISTENABLE_FUTURE_PAGING_SOURCE =
XClassName.get(PAGING_PACKAGE, "ListenableFuturePagingSource")
val RX2_PAGING_SOURCE =
XClassName.get("$PAGING_PACKAGE.rxjava2", "RxPagingSource")
val RX3_PAGING_SOURCE =
XClassName.get("$PAGING_PACKAGE.rxjava3", "RxPagingSource")
}
object LifecyclesTypeNames {
val LIVE_DATA = XClassName.get(LIFECYCLE_PACKAGE, "LiveData")
val COMPUTABLE_LIVE_DATA = XClassName.get(
LIFECYCLE_PACKAGE,
"ComputableLiveData"
)
}
object AndroidTypeNames {
val CURSOR = XClassName.get("android.database", "Cursor")
val BUILD = XClassName.get("android.os", "Build")
val CANCELLATION_SIGNAL = XClassName.get("android.os", "CancellationSignal")
}
object CollectionTypeNames {
val ARRAY_MAP = XClassName.get(COLLECTION_PACKAGE, "ArrayMap")
val LONG_SPARSE_ARRAY = XClassName.get(COLLECTION_PACKAGE, "LongSparseArray")
val INT_SPARSE_ARRAY = XClassName.get(COLLECTION_PACKAGE, "SparseArrayCompat")
}
object KotlinCollectionMemberNames {
val ARRAY_OF_NULLS = XClassName.get("kotlin", "LibraryKt")
.packageMember("arrayOfNulls")
}
object CommonTypeNames {
val VOID = Void::class.asClassName()
val COLLECTION = Collection::class.asClassName()
val COLLECTIONS = XClassName.get("java.util", "Collections")
val ARRAYS = XClassName.get("java.util", "Arrays")
val LIST = List::class.asClassName()
val MUTABLE_LIST = List::class.asMutableClassName()
val ARRAY_LIST = XClassName.get("java.util", "ArrayList")
val MAP = Map::class.asClassName()
val MUTABLE_MAP = Map::class.asMutableClassName()
val HASH_MAP = XClassName.get("java.util", "HashMap")
val QUEUE = XClassName.get("java.util", "Queue")
val LINKED_HASH_MAP = LinkedHashMap::class.asClassName()
val SET = Set::class.asClassName()
val MUTABLE_SET = Set::class.asMutableClassName()
val HASH_SET = XClassName.get("java.util", "HashSet")
val STRING = String::class.asClassName()
val STRING_BUILDER = XClassName.get("java.lang", "StringBuilder")
val OPTIONAL = XClassName.get("java.util", "Optional")
val UUID = XClassName.get("java.util", "UUID")
val BYTE_BUFFER = XClassName.get("java.nio", "ByteBuffer")
val JAVA_CLASS = XClassName.get("java.lang", "Class")
val CALLABLE = Callable::class.asClassName()
val DATE = XClassName.get("java.util", "Date")
}
object ExceptionTypeNames {
val ILLEGAL_STATE_EXCEPTION = IllegalStateException::class.asClassName()
val ILLEGAL_ARG_EXCEPTION = IllegalArgumentException::class.asClassName()
}
object GuavaTypeNames {
val OPTIONAL = XClassName.get("com.google.common.base", "Optional")
val IMMUTABLE_MULTIMAP_BUILDER = XClassName.get(
"com.google.common.collect",
"ImmutableMultimap",
"Builder"
)
val IMMUTABLE_SET_MULTIMAP = XClassName.get(
"com.google.common.collect",
"ImmutableSetMultimap"
)
val IMMUTABLE_SET_MULTIMAP_BUILDER = XClassName.get(
"com.google.common.collect",
"ImmutableSetMultimap",
"Builder"
)
val IMMUTABLE_LIST_MULTIMAP = XClassName.get(
"com.google.common.collect",
"ImmutableListMultimap"
)
val IMMUTABLE_LIST_MULTIMAP_BUILDER = XClassName.get(
"com.google.common.collect",
"ImmutableListMultimap",
"Builder"
)
val IMMUTABLE_MAP = XClassName.get("com.google.common.collect", "ImmutableMap")
val IMMUTABLE_LIST = XClassName.get("com.google.common.collect", "ImmutableList")
val IMMUTABLE_LIST_BUILDER = XClassName.get(
"com.google.common.collect",
"ImmutableList",
"Builder"
)
}
object GuavaUtilConcurrentTypeNames {
val LISTENABLE_FUTURE = XClassName.get("com.google.common.util.concurrent", "ListenableFuture")
}
object RxJava2TypeNames {
val FLOWABLE = XClassName.get("io.reactivex", "Flowable")
val OBSERVABLE = XClassName.get("io.reactivex", "Observable")
val MAYBE = XClassName.get("io.reactivex", "Maybe")
val SINGLE = XClassName.get("io.reactivex", "Single")
val COMPLETABLE = XClassName.get("io.reactivex", "Completable")
}
object RxJava3TypeNames {
val FLOWABLE = XClassName.get("io.reactivex.rxjava3.core", "Flowable")
val OBSERVABLE = XClassName.get("io.reactivex.rxjava3.core", "Observable")
val MAYBE = XClassName.get("io.reactivex.rxjava3.core", "Maybe")
val SINGLE = XClassName.get("io.reactivex.rxjava3.core", "Single")
val COMPLETABLE = XClassName.get("io.reactivex.rxjava3.core", "Completable")
}
object ReactiveStreamsTypeNames {
val PUBLISHER = XClassName.get("org.reactivestreams", "Publisher")
}
object RoomGuavaTypeNames {
val GUAVA_ROOM = XClassName.get("$ROOM_PACKAGE.guava", "GuavaRoom")
}
object RoomRxJava2TypeNames {
val RX_ROOM = XClassName.get(ROOM_PACKAGE, "RxRoom")
val RX_ROOM_CREATE_FLOWABLE = "createFlowable"
val RX_ROOM_CREATE_OBSERVABLE = "createObservable"
val RX_EMPTY_RESULT_SET_EXCEPTION = XClassName.get(ROOM_PACKAGE, "EmptyResultSetException")
}
object RoomRxJava3TypeNames {
val RX_ROOM = XClassName.get("$ROOM_PACKAGE.rxjava3", "RxRoom")
val RX_ROOM_CREATE_FLOWABLE = "createFlowable"
val RX_ROOM_CREATE_OBSERVABLE = "createObservable"
val RX_EMPTY_RESULT_SET_EXCEPTION =
XClassName.get("$ROOM_PACKAGE.rxjava3", "EmptyResultSetException")
}
object RoomPagingTypeNames {
val LIMIT_OFFSET_PAGING_SOURCE = XClassName.get(
"$ROOM_PACKAGE.paging", "LimitOffsetPagingSource"
)
}
object RoomPagingGuavaTypeNames {
val LIMIT_OFFSET_LISTENABLE_FUTURE_PAGING_SOURCE = XClassName.get(
"$ROOM_PACKAGE.paging.guava",
"LimitOffsetListenableFuturePagingSource"
)
}
object RoomPagingRx2TypeNames {
val LIMIT_OFFSET_RX_PAGING_SOURCE = XClassName.get(
"$ROOM_PACKAGE.paging.rxjava2",
"LimitOffsetRxPagingSource"
)
}
object RoomPagingRx3TypeNames {
val LIMIT_OFFSET_RX_PAGING_SOURCE = XClassName.get(
"$ROOM_PACKAGE.paging.rxjava3",
"LimitOffsetRxPagingSource"
)
}
object RoomCoroutinesTypeNames {
val COROUTINES_ROOM = XClassName.get(ROOM_PACKAGE, "CoroutinesRoom")
}
object KotlinTypeNames {
val ANY = Any::class.asClassName()
val UNIT = XClassName.get("kotlin", "Unit")
val CONTINUATION = XClassName.get("kotlin.coroutines", "Continuation")
val CHANNEL = XClassName.get("kotlinx.coroutines.channels", "Channel")
val RECEIVE_CHANNEL = XClassName.get("kotlinx.coroutines.channels", "ReceiveChannel")
val SEND_CHANNEL = XClassName.get("kotlinx.coroutines.channels", "SendChannel")
val FLOW = XClassName.get("kotlinx.coroutines.flow", "Flow")
val LAZY = XClassName.get("kotlin", "Lazy")
}
object RoomMemberNames {
val DB_UTIL_QUERY = RoomTypeNames.DB_UTIL.packageMember("query")
val DB_UTIL_DROP_FTS_SYNC_TRIGGERS = RoomTypeNames.DB_UTIL.packageMember("dropFtsSyncTriggers")
val CURSOR_UTIL_GET_COLUMN_INDEX =
RoomTypeNames.CURSOR_UTIL.packageMember("getColumnIndex")
val CURSOR_UTIL_GET_COLUMN_INDEX_OR_THROW =
RoomTypeNames.CURSOR_UTIL.packageMember("getColumnIndexOrThrow")
val CURSOR_UTIL_WRAP_MAPPED_COLUMNS =
RoomTypeNames.CURSOR_UTIL.packageMember("wrapMappedColumns")
val ROOM_SQL_QUERY_ACQUIRE =
RoomTypeNames.ROOM_SQL_QUERY.companionMember("acquire", isJvmStatic = true)
val ROOM_DATABASE_WITH_TRANSACTION =
RoomTypeNames.ROOM_DB_KT.packageMember("withTransaction")
val TABLE_INFO_READ =
RoomTypeNames.TABLE_INFO.companionMember("read", isJvmStatic = true)
val FTS_TABLE_INFO_READ =
RoomTypeNames.FTS_TABLE_INFO.companionMember("read", isJvmStatic = true)
val VIEW_INFO_READ =
RoomTypeNames.VIEW_INFO.companionMember("read", isJvmStatic = true)
}
val DEFERRED_TYPES = listOf(
LifecyclesTypeNames.LIVE_DATA,
LifecyclesTypeNames.COMPUTABLE_LIVE_DATA,
RxJava2TypeNames.FLOWABLE,
RxJava2TypeNames.OBSERVABLE,
RxJava2TypeNames.MAYBE,
RxJava2TypeNames.SINGLE,
RxJava2TypeNames.COMPLETABLE,
RxJava3TypeNames.FLOWABLE,
RxJava3TypeNames.OBSERVABLE,
RxJava3TypeNames.MAYBE,
RxJava3TypeNames.SINGLE,
RxJava3TypeNames.COMPLETABLE,
GuavaUtilConcurrentTypeNames.LISTENABLE_FUTURE,
KotlinTypeNames.FLOW,
ReactiveStreamsTypeNames.PUBLISHER
)
fun XTypeName.defaultValue(): String {
return if (!isPrimitive) {
"null"
} else if (this == XTypeName.PRIMITIVE_BOOLEAN) {
"false"
} else {
"0"
}
}
fun CallableTypeSpecBuilder(
language: CodeLanguage,
parameterTypeName: XTypeName,
callBody: XFunSpec.Builder.() -> Unit
) = XTypeSpec.anonymousClassBuilder(language, "").apply {
addSuperinterface(CommonTypeNames.CALLABLE.parametrizedBy(parameterTypeName))
addFunction(
XFunSpec.builder(
language = language,
name = "call",
visibility = VisibilityModifier.PUBLIC,
isOverride = true
).apply {
returns(parameterTypeName)
callBody()
}.apply(
javaMethodBuilder = {
addException(JTypeName.get(Exception::class.java))
},
kotlinFunctionBuilder = { }
).build()
)
}
fun Function1TypeSpec(
language: CodeLanguage,
parameterTypeName: XTypeName,
parameterName: String,
returnTypeName: XTypeName,
callBody: XFunSpec.Builder.() -> Unit
) = XTypeSpec.anonymousClassBuilder(language, "").apply {
superclass(
Function1::class.asClassName().parametrizedBy(parameterTypeName, returnTypeName)
)
addFunction(
XFunSpec.builder(
language = language,
name = "invoke",
visibility = VisibilityModifier.PUBLIC,
isOverride = true
).apply {
addParameter(parameterTypeName, parameterName)
returns(returnTypeName)
callBody()
}.build()
)
}.build()
/**
* Generates an array literal with the given [values]
*
* Example: `ArrayLiteral(XTypeName.PRIMITIVE_INT, 1, 2, 3)`
*
* For Java will produce: `new int[] {1, 2, 3}`
*
* For Kotlin will produce: `intArrayOf(1, 2, 3)`,
*/
fun ArrayLiteral(
language: CodeLanguage,
type: XTypeName,
vararg values: Any
): XCodeBlock {
val space = when (language) {
CodeLanguage.JAVA -> "%W"
CodeLanguage.KOTLIN -> " "
}
val initExpr = when (language) {
CodeLanguage.JAVA -> XCodeBlock.of(language, "new %T[] ", type)
CodeLanguage.KOTLIN -> XCodeBlock.of(language, getArrayOfFunction(type))
}
val openingChar = when (language) {
CodeLanguage.JAVA -> "{"
CodeLanguage.KOTLIN -> "("
}
val closingChar = when (language) {
CodeLanguage.JAVA -> "}"
CodeLanguage.KOTLIN -> ")"
}
return XCodeBlock.of(
language,
"%L$openingChar%L$closingChar",
initExpr,
XCodeBlock.builder(language).apply {
val joining = Array(values.size) { i ->
XCodeBlock.of(
language,
if (type == CommonTypeNames.STRING) "%S" else "%L",
values[i]
)
}
val placeholders = joining.joinToString(separator = ",$space") { "%L" }
add(placeholders, *joining)
}.build()
)
}
/**
* Generates a 2D array literal where the value at `i`,`j` will be produced by `valueProducer.
* For example:
* ```
* DoubleArrayLiteral(XTypeName.PRIMITIVE_INT, 2, { _ -> 3 }, { i, j -> i + j })
* ```
* For Java will produce:
* ```
* new int[][] {
* {0, 1, 2},
* {1, 2, 3}
* }
* ```
* For Kotlin will produce:
* ```
* arrayOf(
* intArrayOf(0, 1, 2),
* intArrayOf(1, 2, 3)
* )
* ```
*/
fun DoubleArrayLiteral(
language: CodeLanguage,
type: XTypeName,
rowSize: Int,
columnSizeProducer: (Int) -> Int,
valueProducer: (Int, Int) -> Any
): XCodeBlock {
val space = when (language) {
CodeLanguage.JAVA -> "%W"
CodeLanguage.KOTLIN -> " "
}
val outerInit = when (language) {
CodeLanguage.JAVA -> XCodeBlock.of(language, "new %T[][] ", type)
CodeLanguage.KOTLIN -> XCodeBlock.of(language, "arrayOf")
}
val innerInit = when (language) {
CodeLanguage.JAVA -> XCodeBlock.of(language, "", type)
CodeLanguage.KOTLIN -> XCodeBlock.of(language, getArrayOfFunction(type))
}
val openingChar = when (language) {
CodeLanguage.JAVA -> "{"
CodeLanguage.KOTLIN -> "("
}
val closingChar = when (language) {
CodeLanguage.JAVA -> "}"
CodeLanguage.KOTLIN -> ")"
}
return XCodeBlock.of(
language,
"%L$openingChar%L$closingChar",
outerInit,
XCodeBlock.builder(language).apply {
val joining = Array(rowSize) { i ->
XCodeBlock.of(
language,
"%L$openingChar%L$closingChar",
innerInit,
XCodeBlock.builder(language).apply {
val joining = Array(columnSizeProducer(i)) { j ->
XCodeBlock.of(
language,
if (type == CommonTypeNames.STRING) "%S" else "%L",
valueProducer(i, j)
)
}
val placeholders = joining.joinToString(separator = ",$space") { "%L" }
add(placeholders, *joining)
}.build()
)
}
val placeholders = joining.joinToString(separator = ",$space") { "%L" }
add(placeholders, *joining)
}.build()
)
}
private fun getArrayOfFunction(type: XTypeName) = when (type) {
XTypeName.PRIMITIVE_BOOLEAN -> "booleanArrayOf"
XTypeName.PRIMITIVE_BYTE -> "byteArrayOf"
XTypeName.PRIMITIVE_SHORT -> "shortArrayOf"
XTypeName.PRIMITIVE_INT -> "intArrayOf"
XTypeName.PRIMITIVE_LONG -> "longArrayOf"
XTypeName.PRIMITIVE_CHAR -> "charArrayOf"
XTypeName.PRIMITIVE_FLOAT -> "floatArrayOf"
XTypeName.PRIMITIVE_DOUBLE -> "doubleArrayOf"
else -> "arrayOf"
}
fun getToArrayFunction(type: XTypeName) = when (type) {
XTypeName.PRIMITIVE_BOOLEAN -> "toBooleanArray()"
XTypeName.PRIMITIVE_BYTE -> "toByteArray()"
XTypeName.PRIMITIVE_SHORT -> "toShortArray()"
XTypeName.PRIMITIVE_INT -> "toIntArray()"
XTypeName.PRIMITIVE_LONG -> "toLongArray()"
XTypeName.PRIMITIVE_CHAR -> "toCharArray()"
XTypeName.PRIMITIVE_FLOAT -> "toFloatArray()"
XTypeName.PRIMITIVE_DOUBLE -> "toDoubleArray()"
else -> error("Provided type expected to be primitive. Found: $type")
}
/**
* Code of expression for [Collection.size] in Kotlin, and [java.util.Collection.size] for Java.
*/
fun CollectionsSizeExprCode(language: CodeLanguage, varName: String) = XCodeBlock.of(
language,
when (language) {
CodeLanguage.JAVA -> "%L.size()" // java.util.Collections.size()
CodeLanguage.KOTLIN -> "%L.size" // kotlin.collections.Collection.size
},
varName
)
/**
* Code of expression for [Array.size] in Kotlin, and `arr.length` for Java.
*/
fun ArraySizeExprCode(language: CodeLanguage, varName: String) = XCodeBlock.of(
language,
when (language) {
CodeLanguage.JAVA -> "%L.length" // Just `arr.length`
CodeLanguage.KOTLIN -> "%L.size" // kotlin.Array.size and primitives (e.g. IntArray)
},
varName
)
/**
* Code of expression for [Map.keys] in Kotlin, and [java.util.Map.keySet] for Java.
*/
fun MapKeySetExprCode(language: CodeLanguage, varName: String) = XCodeBlock.of(
language,
when (language) {
CodeLanguage.JAVA -> "%L.keySet()" // java.util.Map.keySet()
CodeLanguage.KOTLIN -> "%L.keys" // kotlin.collections.Map.keys
},
varName
)