blob: 07ee499311821b6c04fc5e93fc0476dbd652cf1e [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.processor
import androidx.room.RewriteQueriesToDropUnusedColumns
import androidx.room.compiler.codegen.CodeLanguage
import androidx.room.compiler.processing.XElement
import androidx.room.compiler.processing.XProcessingEnv
import androidx.room.compiler.processing.XType
import androidx.room.ext.CommonTypeNames
import androidx.room.log.RLog
import androidx.room.parser.expansion.ProjectionExpander
import androidx.room.parser.optimization.RemoveUnusedColumnQueryRewriter
import androidx.room.preconditions.Checks
import androidx.room.processor.cache.Cache
import androidx.room.solver.TypeAdapterStore
import androidx.room.verifier.DatabaseVerifier
import androidx.room.vo.BuiltInConverterFlags
import androidx.room.vo.Warning
import javax.tools.Diagnostic
class Context private constructor(
val processingEnv: XProcessingEnv,
val logger: RLog,
private val typeConverters: CustomConverterProcessor.ProcessResult,
private val inheritedAdapterStore: TypeAdapterStore?,
val cache: Cache,
private val canRewriteQueriesToDropUnusedColumns: Boolean,
) {
val checker: Checks = Checks(logger)
val COMMON_TYPES = CommonTypes(processingEnv)
/**
* Checks whether we should use the TypeConverter store that has a specific heuristic for
* nullability. Defaults to true in KSP, false in javac.
*/
val useNullAwareConverter: Boolean by lazy {
BooleanProcessorOptions.USE_NULL_AWARE_CONVERTER.getInputValue(processingEnv)
?: (processingEnv.backend == XProcessingEnv.Backend.KSP)
}
val typeAdapterStore by lazy {
if (inheritedAdapterStore != null) {
TypeAdapterStore.copy(this, inheritedAdapterStore)
} else {
TypeAdapterStore.create(
this, typeConverters.builtInConverterFlags,
typeConverters.converters
)
}
}
// set when database and its entities are processed.
var databaseVerifier: DatabaseVerifier? = null
private set
val queryRewriter: QueryRewriter by lazy {
val verifier = databaseVerifier
if (verifier == null) {
QueryRewriter.NoOpRewriter
} else {
if (canRewriteQueriesToDropUnusedColumns) {
RemoveUnusedColumnQueryRewriter
} else if (BooleanProcessorOptions.EXPAND_PROJECTION.getValue(processingEnv)) {
ProjectionExpander(
tables = verifier.entitiesAndViews
)
} else {
QueryRewriter.NoOpRewriter
}
}
}
val codeLanguage: CodeLanguage by lazy {
if (BooleanProcessorOptions.GENERATE_KOTLIN.getValue(processingEnv)) {
if (processingEnv.backend == XProcessingEnv.Backend.KSP) {
CodeLanguage.KOTLIN
} else {
processingEnv.messager.printMessage(
Diagnostic.Kind.ERROR,
"${BooleanProcessorOptions.GENERATE_KOTLIN.argName} can only be enabled in KSP."
)
CodeLanguage.JAVA
}
} else {
CodeLanguage.JAVA
}
}
companion object {
val ARG_OPTIONS by lazy {
ProcessorOptions.values().map { it.argName } +
BooleanProcessorOptions.values().map { it.argName }
}
}
fun attachDatabaseVerifier(databaseVerifier: DatabaseVerifier) {
check(this.databaseVerifier == null) {
"database verifier is already set"
}
this.databaseVerifier = databaseVerifier
}
constructor(processingEnv: XProcessingEnv) : this(
processingEnv = processingEnv,
logger = RLog(processingEnv.messager, emptySet(), null),
typeConverters = CustomConverterProcessor.ProcessResult.EMPTY,
inheritedAdapterStore = null,
cache = Cache(
parent = null,
converters = LinkedHashSet(),
suppressedWarnings = emptySet(),
builtInConverterFlags = BuiltInConverterFlags.DEFAULT
),
canRewriteQueriesToDropUnusedColumns = false
)
class CommonTypes(val processingEnv: XProcessingEnv) {
val VOID: XType by lazy {
processingEnv.requireType(CommonTypeNames.VOID)
}
val STRING: XType by lazy {
processingEnv.requireType(CommonTypeNames.STRING)
}
val READONLY_COLLECTION: XType by lazy {
processingEnv.requireType(CommonTypeNames.COLLECTION)
}
val LIST: XType by lazy {
processingEnv.requireType(CommonTypeNames.LIST)
}
val SET: XType by lazy {
processingEnv.requireType(CommonTypeNames.SET)
}
}
val schemaOutFolderPath by lazy {
val arg = processingEnv.options[ProcessorOptions.OPTION_SCHEMA_FOLDER.argName]
if (arg?.isNotEmpty() == true) {
arg
} else {
null
}
}
fun <T> collectLogs(handler: (Context) -> T): Pair<T, RLog.CollectingMessager> {
val collector = RLog.CollectingMessager()
val subContext = Context(
processingEnv = processingEnv,
logger = RLog(collector, logger.suppressedWarnings, logger.defaultElement),
typeConverters = this.typeConverters,
inheritedAdapterStore = typeAdapterStore,
cache = cache,
canRewriteQueriesToDropUnusedColumns = canRewriteQueriesToDropUnusedColumns
)
subContext.databaseVerifier = databaseVerifier
val result = handler(subContext)
return Pair(result, collector)
}
/**
* Forks the processor context adding suppressed warnings a type converters found in the
* given [element].
*
* @param element the element from which to create the fork.
* @param forceSuppressedWarnings the warning that will be silenced regardless if they are
* present or not in the [element].
* @param forceBuiltInConverters the built-in converter states that will be set regardless of
* the states found in the [element].
*/
fun fork(
element: XElement,
forceSuppressedWarnings: Set<Warning> = emptySet(),
forceBuiltInConverters: BuiltInConverterFlags? = null
): Context {
val suppressedWarnings = SuppressWarningProcessor.getSuppressedWarnings(element)
val processConvertersResult =
CustomConverterProcessor.findConverters(this, element).let { result ->
if (forceBuiltInConverters != null) {
result.copy(
builtInConverterFlags =
result.builtInConverterFlags.withNext(forceBuiltInConverters)
)
} else {
result
}
}
val subBuiltInConverterFlags = typeConverters.builtInConverterFlags.withNext(
processConvertersResult.builtInConverterFlags
)
val canReUseAdapterStore =
subBuiltInConverterFlags == typeConverters.builtInConverterFlags &&
processConvertersResult.classes.isEmpty()
// order here is important since the sub context should give priority to new converters.
val subTypeConverters = if (canReUseAdapterStore) {
this.typeConverters
} else {
processConvertersResult + this.typeConverters
}
val subSuppressedWarnings =
forceSuppressedWarnings + suppressedWarnings + logger.suppressedWarnings
val subCache = Cache(
parent = cache,
converters = subTypeConverters.classes,
suppressedWarnings = subSuppressedWarnings,
builtInConverterFlags = subBuiltInConverterFlags
)
val subCanRemoveUnusedColumns = canRewriteQueriesToDropUnusedColumns ||
element.hasRemoveUnusedColumnsAnnotation()
val subContext = Context(
processingEnv = processingEnv,
logger = RLog(logger.messager, subSuppressedWarnings, element),
typeConverters = subTypeConverters,
inheritedAdapterStore = if (canReUseAdapterStore) typeAdapterStore else null,
cache = subCache,
canRewriteQueriesToDropUnusedColumns = subCanRemoveUnusedColumns
)
subContext.databaseVerifier = databaseVerifier
return subContext
}
private fun XElement.hasRemoveUnusedColumnsAnnotation(): Boolean {
return hasAnnotation(RewriteQueriesToDropUnusedColumns::class).also { annotated ->
if (annotated && BooleanProcessorOptions.EXPAND_PROJECTION.getValue(processingEnv)) {
logger.w(
warning = Warning.EXPAND_PROJECTION_WITH_REMOVE_UNUSED_COLUMNS,
element = this,
msg = ProcessorErrors.EXPAND_PROJECTION_ALONG_WITH_REMOVE_UNUSED
)
}
}
}
fun reportMissingType(typeName: String) {
logger.e("${RLog.MISSING_TYPE_PREFIX}: Type '$typeName' is not present")
}
fun reportMissingTypeReference(containerName: String) {
logger.e(
"${RLog.MISSING_TYPE_PREFIX}: Element '$containerName' references a type that is " +
"not present"
)
}
enum class ProcessorOptions(val argName: String) {
OPTION_SCHEMA_FOLDER("room.schemaLocation")
}
enum class BooleanProcessorOptions(val argName: String, private val defaultValue: Boolean) {
INCREMENTAL("room.incremental", defaultValue = true),
EXPAND_PROJECTION("room.expandProjection", defaultValue = false),
USE_NULL_AWARE_CONVERTER("room.useNullAwareTypeAnalysis", defaultValue = false),
GENERATE_KOTLIN("room.generateKotlin", defaultValue = false),
EXPORT_SCHEMA_RESOURCE("room.exportSchemaResource", defaultValue = false);
/**
* Returns the value of this option passed through the [XProcessingEnv]. If the value
* is null or blank, it returns the default value instead.
*/
fun getValue(processingEnv: XProcessingEnv): Boolean {
return getInputValue(processingEnv) ?: defaultValue
}
fun getValue(options: Map<String, String>): Boolean {
return getInputValue(options) ?: defaultValue
}
fun getInputValue(processingEnv: XProcessingEnv): Boolean? {
return getInputValue(processingEnv.options)
}
private fun getInputValue(options: Map<String, String>): Boolean? {
return options[argName]?.takeIf {
it.isNotBlank()
}?.toBoolean()
}
}
}