| /* |
| * 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() |
| } |
| } |
| } |