1 /*
<lambda>null2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.room.processor
18 
19 import androidx.room.RewriteQueriesToDropUnusedColumns
20 import androidx.room.compiler.codegen.CodeLanguage
21 import androidx.room.compiler.processing.XElement
22 import androidx.room.compiler.processing.XProcessingEnv
23 import androidx.room.log.RLog
24 import androidx.room.parser.expansion.ProjectionExpander
25 import androidx.room.parser.optimization.RemoveUnusedColumnQueryRewriter
26 import androidx.room.preconditions.Checks
27 import androidx.room.processor.cache.Cache
28 import androidx.room.solver.TypeAdapterStore
29 import androidx.room.verifier.DatabaseVerifier
30 import androidx.room.vo.BuiltInConverterFlags
31 import androidx.room.vo.Warning
32 
33 class Context
34 private constructor(
35     val processingEnv: XProcessingEnv,
36     val logger: RLog,
37     private val typeConverters: CustomConverterProcessor.ProcessResult,
38     private val inheritedAdapterStore: TypeAdapterStore?,
39     val cache: Cache,
40     private val canRewriteQueriesToDropUnusedColumns: Boolean,
41 ) {
42     val checker: Checks = Checks(logger)
43 
44     /**
45      * Checks whether we should use the TypeConverter store that has a specific heuristic for
46      * nullability. Defaults to true in KSP, false in javac.
47      */
48     val useNullAwareConverter: Boolean by lazy {
49         BooleanProcessorOptions.USE_NULL_AWARE_CONVERTER.getInputValue(processingEnv)
50             ?: (processingEnv.backend == XProcessingEnv.Backend.KSP)
51     }
52 
53     val typeAdapterStore by lazy {
54         if (inheritedAdapterStore != null) {
55             TypeAdapterStore.copy(this, inheritedAdapterStore)
56         } else {
57             TypeAdapterStore.create(
58                 this,
59                 typeConverters.builtInConverterFlags,
60                 typeConverters.converters
61             )
62         }
63     }
64 
65     // set when database and its entities are processed.
66     var databaseVerifier: DatabaseVerifier? = null
67         private set
68 
69     val queryRewriter: QueryRewriter by lazy {
70         val verifier = databaseVerifier
71         if (verifier == null) {
72             QueryRewriter.NoOpRewriter
73         } else {
74             if (canRewriteQueriesToDropUnusedColumns) {
75                 RemoveUnusedColumnQueryRewriter
76             } else if (BooleanProcessorOptions.EXPAND_PROJECTION.getValue(processingEnv)) {
77                 ProjectionExpander(tables = verifier.entitiesAndViews)
78             } else {
79                 QueryRewriter.NoOpRewriter
80             }
81         }
82     }
83 
84     val codeLanguage: CodeLanguage by lazy {
85         if (processingEnv.backend == XProcessingEnv.Backend.KSP) {
86             if (BooleanProcessorOptions.GENERATE_KOTLIN.getValue(processingEnv)) {
87                 CodeLanguage.KOTLIN
88             } else {
89                 CodeLanguage.JAVA
90             }
91         } else {
92             if (BooleanProcessorOptions.GENERATE_KOTLIN.getInputValue(processingEnv) == true) {
93                 logger.e(ProcessorErrors.INVALID_KOTLIN_CODE_GEN_IN_JAVAC)
94             }
95             CodeLanguage.JAVA
96         }
97     }
98 
99     // Whether Java 8's lambda syntax is available to be emitted or not.
100     val javaLambdaSyntaxAvailable by lazy { processingEnv.jvmVersion >= 8 }
101 
102     companion object {
103         val ARG_OPTIONS by lazy {
104             ProcessorOptions.values().map { it.argName } +
105                 BooleanProcessorOptions.values().map { it.argName }
106         }
107     }
108 
109     fun attachDatabaseVerifier(databaseVerifier: DatabaseVerifier) {
110         check(this.databaseVerifier == null) { "database verifier is already set" }
111         this.databaseVerifier = databaseVerifier
112     }
113 
114     constructor(
115         processingEnv: XProcessingEnv
116     ) : this(
117         processingEnv = processingEnv,
118         logger = RLog(processingEnv.messager, emptySet(), null),
119         typeConverters = CustomConverterProcessor.ProcessResult.EMPTY,
120         inheritedAdapterStore = null,
121         cache =
122             Cache(
123                 parent = null,
124                 converters = LinkedHashSet(),
125                 suppressedWarnings = emptySet(),
126                 builtInConverterFlags = BuiltInConverterFlags.DEFAULT
127             ),
128         canRewriteQueriesToDropUnusedColumns = false
129     )
130 
131     val schemaInFolderPath by lazy {
132         val internalInputFolder =
133             processingEnv.options[ProcessorOptions.INTERNAL_SCHEMA_INPUT_FOLDER.argName]
134                 // Warning: Format must match with room-gradle-plugin
135                 ?.replace("%20", " ")
136         val legacySchemaFolder =
137             processingEnv.options[ProcessorOptions.OPTION_SCHEMA_FOLDER.argName]
138         if (!internalInputFolder.isNullOrBlank()) {
139             internalInputFolder
140         } else if (!legacySchemaFolder.isNullOrBlank()) {
141             legacySchemaFolder
142         } else {
143             null
144         }
145     }
146 
147     val schemaOutFolderPath by lazy {
148         val internalOutputFolder =
149             processingEnv.options[ProcessorOptions.INTERNAL_SCHEMA_OUTPUT_FOLDER.argName]
150                 // Warning: Format must match with room-gradle-plugin
151                 ?.replace("%20", " ")
152         val legacySchemaFolder =
153             processingEnv.options[ProcessorOptions.OPTION_SCHEMA_FOLDER.argName]
154         if (!internalOutputFolder.isNullOrBlank() && !legacySchemaFolder.isNullOrBlank()) {
155             logger.e(ProcessorErrors.INVALID_GRADLE_PLUGIN_AND_SCHEMA_LOCATION_OPTION)
156         }
157         if (!internalOutputFolder.isNullOrBlank()) {
158             internalOutputFolder
159         } else if (!legacySchemaFolder.isNullOrBlank()) {
160             legacySchemaFolder
161         } else {
162             null
163         }
164     }
165 
166     fun <T> collectLogs(handler: (Context) -> T): Pair<T, RLog.CollectingMessager> {
167         val collector = RLog.CollectingMessager()
168         val subContext =
169             Context(
170                 processingEnv = processingEnv,
171                 logger = RLog(collector, logger.suppressedWarnings, logger.defaultElement),
172                 typeConverters = this.typeConverters,
173                 inheritedAdapterStore = typeAdapterStore,
174                 cache = cache,
175                 canRewriteQueriesToDropUnusedColumns = canRewriteQueriesToDropUnusedColumns
176             )
177         subContext.databaseVerifier = databaseVerifier
178         val result = handler(subContext)
179         return Pair(result, collector)
180     }
181 
182     /**
183      * Forks the processor context adding suppressed warnings a type converters found in the given
184      * [element].
185      *
186      * @param element the element from which to create the fork.
187      * @param forceSuppressedWarnings the warning that will be silenced regardless if they are
188      *   present or not in the [element].
189      * @param forceBuiltInConverters the built-in converter states that will be set regardless of
190      *   the states found in the [element].
191      */
192     fun fork(
193         element: XElement,
194         forceSuppressedWarnings: Set<Warning> = emptySet(),
195         forceBuiltInConverters: BuiltInConverterFlags? = null
196     ): Context {
197         val suppressedWarnings = SuppressWarningProcessor.getSuppressedWarnings(element)
198         val processConvertersResult =
199             CustomConverterProcessor.findConverters(this, element).let { result ->
200                 if (forceBuiltInConverters != null) {
201                     result.copy(
202                         builtInConverterFlags =
203                             result.builtInConverterFlags.withNext(forceBuiltInConverters)
204                     )
205                 } else {
206                     result
207                 }
208             }
209         val subBuiltInConverterFlags =
210             typeConverters.builtInConverterFlags.withNext(
211                 processConvertersResult.builtInConverterFlags
212             )
213         val canReUseAdapterStore =
214             subBuiltInConverterFlags == typeConverters.builtInConverterFlags &&
215                 processConvertersResult.classes.isEmpty()
216         // order here is important since the sub context should give priority to new converters.
217         val subTypeConverters =
218             if (canReUseAdapterStore) {
219                 this.typeConverters
220             } else {
221                 processConvertersResult + this.typeConverters
222             }
223         val subSuppressedWarnings =
224             forceSuppressedWarnings + suppressedWarnings + logger.suppressedWarnings
225         val subCache =
226             Cache(
227                 parent = cache,
228                 converters = subTypeConverters.classes,
229                 suppressedWarnings = subSuppressedWarnings,
230                 builtInConverterFlags = subBuiltInConverterFlags
231             )
232         val subCanRemoveUnusedColumns =
233             canRewriteQueriesToDropUnusedColumns || element.hasRemoveUnusedColumnsAnnotation()
234         val subContext =
235             Context(
236                 processingEnv = processingEnv,
237                 logger = RLog(logger.messager, subSuppressedWarnings, element),
238                 typeConverters = subTypeConverters,
239                 inheritedAdapterStore = if (canReUseAdapterStore) typeAdapterStore else null,
240                 cache = subCache,
241                 canRewriteQueriesToDropUnusedColumns = subCanRemoveUnusedColumns
242             )
243         subContext.databaseVerifier = databaseVerifier
244         return subContext
245     }
246 
247     private fun XElement.hasRemoveUnusedColumnsAnnotation(): Boolean {
248         return hasAnnotation(RewriteQueriesToDropUnusedColumns::class).also { annotated ->
249             if (annotated && BooleanProcessorOptions.EXPAND_PROJECTION.getValue(processingEnv)) {
250                 logger.w(
251                     warning = Warning.EXPAND_PROJECTION_WITH_REMOVE_UNUSED_COLUMNS,
252                     element = this,
253                     msg = ProcessorErrors.EXPAND_PROJECTION_ALONG_WITH_REMOVE_UNUSED
254                 )
255             }
256         }
257     }
258 
259     fun reportMissingType(typeName: String) {
260         logger.e("${RLog.MISSING_TYPE_PREFIX}: Type '$typeName' is not present")
261     }
262 
263     fun reportMissingTypeReference(containerName: String) {
264         logger.e(
265             "${RLog.MISSING_TYPE_PREFIX}: Element '$containerName' references a type that is " +
266                 "not present"
267         )
268     }
269 
270     enum class ProcessorOptions(val argName: String) {
271         OPTION_SCHEMA_FOLDER("room.schemaLocation"),
272         INTERNAL_SCHEMA_INPUT_FOLDER("room.internal.schemaInput"),
273         INTERNAL_SCHEMA_OUTPUT_FOLDER("room.internal.schemaOutput"),
274     }
275 
276     enum class BooleanProcessorOptions(val argName: String, private val defaultValue: Boolean) {
277         INCREMENTAL("room.incremental", defaultValue = true),
278         EXPAND_PROJECTION("room.expandProjection", defaultValue = false),
279         USE_NULL_AWARE_CONVERTER("room.useNullAwareTypeAnalysis", defaultValue = false),
280         GENERATE_KOTLIN("room.generateKotlin", defaultValue = true),
281         EXPORT_SCHEMA_RESOURCE("room.exportSchemaResource", defaultValue = false);
282 
283         /**
284          * Returns the value of this option passed through the [XProcessingEnv]. If the value is
285          * null or blank, it returns the default value instead.
286          */
287         fun getValue(processingEnv: XProcessingEnv): Boolean {
288             return getInputValue(processingEnv) ?: defaultValue
289         }
290 
291         fun getValue(options: Map<String, String>): Boolean {
292             return getInputValue(options) ?: defaultValue
293         }
294 
295         fun getInputValue(processingEnv: XProcessingEnv): Boolean? {
296             return getInputValue(processingEnv.options)
297         }
298 
299         private fun getInputValue(options: Map<String, String>): Boolean? {
300             return options[argName]?.takeIf { it.isNotBlank() }?.toBoolean()
301         }
302     }
303 
304     /**
305      * Check if the target platform is only Android.
306      *
307      * Note that there is no 'Android' target in the `targetPlatforms` list, so instead we check for
308      * JVM and also validate that an Android only class `android.content.Context` is in the
309      * classpath.
310      */
311     fun isAndroidOnlyTarget(): Boolean {
312         val targetPlatforms = this.processingEnv.targetPlatforms
313         return targetPlatforms.size == 1 &&
314             targetPlatforms.contains(XProcessingEnv.Platform.JVM) &&
315             this.processingEnv.findType("android.content.Context") != null
316     }
317 
318     /** Check if the target platform is JVM. */
319     fun isJvmOnlyTarget(): Boolean {
320         val targetPlatforms = this.processingEnv.targetPlatforms
321         return targetPlatforms.size == 1 && targetPlatforms.contains(XProcessingEnv.Platform.JVM)
322     }
323 }
324