1 /* 2 * Copyright 2022 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.writer 18 19 import androidx.room.RoomProcessor 20 import androidx.room.compiler.codegen.CodeLanguage 21 import androidx.room.compiler.codegen.VisibilityModifier 22 import androidx.room.compiler.codegen.XFunSpec 23 import androidx.room.compiler.codegen.XPropertySpec 24 import androidx.room.compiler.codegen.XTypeName 25 import androidx.room.compiler.codegen.XTypeSpec 26 import androidx.room.compiler.codegen.compat.XConverters.applyToJavaPoet 27 import androidx.room.compiler.codegen.compat.XConverters.applyToKotlinPoet 28 import androidx.room.compiler.processing.XProcessingEnv 29 import androidx.room.compiler.processing.writeTo 30 import androidx.room.processor.Context 31 import androidx.room.solver.CodeGenScope 32 import com.squareup.kotlinpoet.javapoet.JAnnotationSpec 33 import com.squareup.kotlinpoet.javapoet.JClassName 34 import com.squareup.kotlinpoet.javapoet.KAnnotationSpec 35 import com.squareup.kotlinpoet.javapoet.KClassName 36 import kotlin.reflect.KClass 37 38 /** Base class for all writers that can produce a class. */ 39 abstract class TypeWriter(val context: WriterContext) { 40 private val sharedFieldSpecs = mutableMapOf<String, XPropertySpec>() 41 private val sharedMethodSpecs = mutableMapOf<String, XFunSpec>() 42 private val sharedFieldNames = mutableSetOf<String>() 43 private val sharedMethodNames = mutableSetOf<String>() 44 private val metadata = mutableMapOf<KClass<*>, Any>() 45 abstract val packageName: String 46 createTypeSpecBuildernull47 abstract fun createTypeSpecBuilder(): XTypeSpec.Builder 48 49 /** 50 * Read additional metadata that can be put by sub code generators. 51 * 52 * @see set for more details. 53 */ 54 operator fun <T> get(key: KClass<*>): T? { 55 @Suppress("UNCHECKED_CAST") return metadata[key] as? T 56 } 57 58 /** 59 * Add additional metadata to the TypeWriter that can be read back later. This is useful for 60 * additional functionality where a sub code generator needs to bubble up information to the 61 * main TypeWriter without copying it in every intermediate step. 62 * 63 * @see get 64 */ setnull65 operator fun set(key: KClass<*>, value: Any) { 66 metadata[key] = value 67 } 68 writenull69 fun write(processingEnv: XProcessingEnv) { 70 val builder = createTypeSpecBuilder() 71 sharedFieldSpecs.values.forEach { builder.addProperty(it) } 72 sharedMethodSpecs.values.forEach { builder.addFunction(it) } 73 addGeneratedAnnotationIfAvailable(builder, processingEnv) 74 addSuppressWarnings(builder) 75 builder 76 .build() 77 .writeTo( 78 language = context.codeLanguage, 79 packageName = packageName, 80 generator = processingEnv.filer 81 ) 82 } 83 addSuppressWarningsnull84 private fun addSuppressWarnings(builder: XTypeSpec.Builder) { 85 builder 86 .applyToJavaPoet { 87 addAnnotation( 88 JAnnotationSpec.builder(SuppressWarnings::class.java) 89 .addMember( 90 "value", 91 "{\$S, \$S, \$S}", 92 "unchecked", 93 "deprecation", 94 "removal" 95 ) 96 .build() 97 ) 98 } 99 .applyToKotlinPoet { 100 addAnnotation( 101 KAnnotationSpec.builder(Suppress::class) 102 .addMember( 103 "names = [%S, %S, %S, %S]", 104 "UNCHECKED_CAST", 105 "DEPRECATION", 106 "REDUNDANT_PROJECTION", 107 "REMOVAL" 108 ) 109 .build() 110 ) 111 } 112 } 113 addGeneratedAnnotationIfAvailablenull114 private fun addGeneratedAnnotationIfAvailable( 115 adapterTypeSpecBuilder: XTypeSpec.Builder, 116 processingEnv: XProcessingEnv 117 ) { 118 processingEnv.findGeneratedAnnotation()?.let { 119 val annotationName = it.asClassName().canonicalName 120 val memberValue = RoomProcessor::class.java.canonicalName 121 adapterTypeSpecBuilder 122 .applyToJavaPoet { 123 addAnnotation( 124 JAnnotationSpec.builder(JClassName.bestGuess(annotationName)) 125 .addMember("value", "\$S", memberValue) 126 .build() 127 ) 128 } 129 .applyToKotlinPoet { 130 addAnnotation( 131 KAnnotationSpec.builder(KClassName.bestGuess(annotationName)) 132 .addMember("value = [%S]", memberValue) 133 .build() 134 ) 135 } 136 } 137 } 138 makeUniquenull139 private fun makeUnique(set: MutableSet<String>, value: String): String { 140 if (!value.startsWith(CodeGenScope.CLASS_PROPERTY_PREFIX)) { 141 return makeUnique(set, "${CodeGenScope.CLASS_PROPERTY_PREFIX}$value") 142 } 143 if (set.add(value)) { 144 return value 145 } 146 var index = 1 147 while (true) { 148 if (set.add("${value}_$index")) { 149 return "${value}_$index" 150 } 151 index++ 152 } 153 } 154 getOrCreatePropertynull155 fun getOrCreateProperty(sharedProperty: SharedPropertySpec): XPropertySpec { 156 return sharedFieldSpecs.getOrPut(sharedProperty.getUniqueKey()) { 157 sharedProperty.build(this, makeUnique(sharedFieldNames, sharedProperty.baseName)) 158 } 159 } 160 getOrCreateFunctionnull161 fun getOrCreateFunction(sharedFunction: SharedFunctionSpec): XFunSpec { 162 return sharedMethodSpecs.getOrPut(sharedFunction.getUniqueKey()) { 163 sharedFunction.build(this, makeUnique(sharedMethodNames, sharedFunction.baseName)) 164 } 165 } 166 167 abstract class SharedPropertySpec(val baseName: String, val type: XTypeName) { 168 169 open val isMutable = false 170 getUniqueKeynull171 abstract fun getUniqueKey(): String 172 173 abstract fun prepare(writer: TypeWriter, builder: XPropertySpec.Builder) 174 175 fun build(classWriter: TypeWriter, name: String): XPropertySpec { 176 val builder = 177 XPropertySpec.builder( 178 name = name, 179 typeName = type, 180 visibility = VisibilityModifier.PRIVATE, 181 isMutable = isMutable 182 ) 183 prepare(classWriter, builder) 184 return builder.build() 185 } 186 } 187 188 abstract class SharedFunctionSpec(val baseName: String) { 189 getUniqueKeynull190 abstract fun getUniqueKey(): String 191 192 abstract fun prepare(functionName: String, writer: TypeWriter, builder: XFunSpec.Builder) 193 194 fun build(writer: TypeWriter, name: String): XFunSpec { 195 val builder = XFunSpec.builder(name, VisibilityModifier.PRIVATE) 196 prepare(name, writer, builder) 197 return builder.build() 198 } 199 } 200 201 class WriterContext( 202 val codeLanguage: CodeLanguage, 203 val targetPlatforms: Set<XProcessingEnv.Platform>, 204 val javaLambdaSyntaxAvailable: Boolean 205 ) { 206 companion object { fromProcessingContextnull207 fun fromProcessingContext(context: Context) = 208 WriterContext( 209 codeLanguage = context.codeLanguage, 210 targetPlatforms = context.processingEnv.targetPlatforms, 211 javaLambdaSyntaxAvailable = context.javaLambdaSyntaxAvailable 212 ) 213 } 214 } 215 } 216