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