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