1 /*
<lambda>null2  * 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.compiler.codegen
18 
19 import androidx.room.compiler.codegen.XCodeBlock.Builder.Companion.applyTo
20 import androidx.room.compiler.codegen.impl.XCodeBlockImpl
21 import androidx.room.compiler.codegen.java.JavaCodeBlock
22 import androidx.room.compiler.codegen.kotlin.KotlinCodeBlock
23 
24 /**
25  * A fragment of a .java or .kt file, potentially containing declarations, statements.
26  *
27  * Code blocks support placeholders like [java.text.Format]. This uses a percent sign `%` but has
28  * its own set of permitted placeholders:
29  * * `%L` emits a *literal* value with no escaping. Arguments for literals may be strings,
30  *   primitives, [type declarations][XTypeSpec], [annotations][XAnnotationSpec] and even other code
31  *   blocks.
32  * * `%N` emits a *name*, using name collision avoidance where necessary. Arguments for names may be
33  *   strings (actually any [character sequence][CharSequence]), [parameters][XParameterSpec],
34  *   [properties][XPropertySpec], [functions][XFunSpec], and [types][XTypeSpec].
35  * * `%S` escapes the value as a *string*, wraps it with double quotes, and emits that.
36  * * `%T` emits a *type* reference. Types will be imported if possible. Arguments for types are
37  *   their [names][XTypeName].
38  * * `%M` emits a *member* reference. A member is either a function or a property. If the member is
39  *   importable, e.g. it's a top-level function or a property declared inside an object, the import
40  *   will be resolved if possible. Arguments for members must be of type [XMemberName].
41  */
42 interface XCodeBlock {
43 
44     interface Builder {
45 
46         fun add(code: XCodeBlock): Builder
47 
48         fun add(format: String, vararg args: Any?): Builder
49 
50         fun addStatement(format: String, vararg args: Any?): Builder
51 
52         fun addLocalVariable(
53             name: String,
54             typeName: XTypeName,
55             isMutable: Boolean = false,
56             assignExpr: XCodeBlock? = null
57         ): Builder
58 
59         fun beginControlFlow(controlFlow: String, vararg args: Any?): Builder
60 
61         fun nextControlFlow(controlFlow: String, vararg args: Any?): Builder
62 
63         fun endControlFlow(): Builder
64 
65         fun indent(): Builder
66 
67         fun unindent(): Builder
68 
69         /**
70          * Convenience local immutable variable emitter.
71          *
72          * Shouldn't contain declaration, only right hand assignment expression.
73          */
74         fun addLocalVal(
75             name: String,
76             typeName: XTypeName,
77             assignExprFormat: String,
78             vararg assignExprArgs: Any?
79         ) = apply {
80             addLocalVariable(
81                 name = name,
82                 typeName = typeName,
83                 isMutable = false,
84                 assignExpr = of(assignExprFormat, *assignExprArgs)
85             )
86         }
87 
88         /**
89          * Convenience for-each control flow emitter taking into account the receiver's
90          * [CodeLanguage].
91          *
92          * For Java this will emit: `for (<typeName> <itemVarName> : <iteratorVarName>)`
93          *
94          * For Kotlin this will emit: `for (<itemVarName>: <typeName> in <iteratorVarName>)`
95          */
96         fun beginForEachControlFlow(
97             itemVarName: String,
98             typeName: XTypeName,
99             iteratorVarName: String
100         ) = applyTo { language ->
101             when (language) {
102                 CodeLanguage.JAVA ->
103                     beginControlFlow("for (%T %L : %L)", typeName, itemVarName, iteratorVarName)
104                 CodeLanguage.KOTLIN ->
105                     beginControlFlow("for (%L: %T in %L)", itemVarName, typeName, iteratorVarName)
106             }
107         }
108 
109         fun build(): XCodeBlock
110 
111         companion object {
112             fun Builder.applyTo(block: Builder.(CodeLanguage) -> Unit) = apply {
113                 when (this) {
114                     is XCodeBlockImpl.Builder -> {
115                         this.java.block(CodeLanguage.JAVA)
116                         this.kotlin.block(CodeLanguage.KOTLIN)
117                     }
118                     is JavaCodeBlock.Builder -> block(CodeLanguage.JAVA)
119                     is KotlinCodeBlock.Builder -> block(CodeLanguage.KOTLIN)
120                 }
121             }
122 
123             fun Builder.applyTo(language: CodeLanguage, block: Builder.() -> Unit) =
124                 applyTo { codeLanguage ->
125                     if (codeLanguage == language) {
126                         block()
127                     }
128                 }
129         }
130     }
131 
132     companion object {
133         @JvmStatic
134         fun builder(): Builder =
135             XCodeBlockImpl.Builder(
136                 JavaCodeBlock.Builder(JCodeBlock.builder()),
137                 KotlinCodeBlock.Builder(KCodeBlock.builder())
138             )
139 
140         @JvmStatic fun of(format: String, vararg args: Any?) = builder().add(format, *args).build()
141 
142         @JvmStatic
143         fun ofString(java: String, kotlin: String) = buildCodeBlock { language ->
144             when (language) {
145                 CodeLanguage.JAVA -> add(java)
146                 CodeLanguage.KOTLIN -> add(kotlin)
147             }
148         }
149 
150         /**
151          * Convenience code block of a new instantiation expression.
152          *
153          * Shouldn't contain parenthesis.
154          */
155         @JvmStatic
156         fun ofNewInstance(typeName: XTypeName, argsFormat: String = "", vararg args: Any?) =
157             buildCodeBlock { language ->
158                 when (language) {
159                     CodeLanguage.JAVA ->
160                         add("new %T($argsFormat)", typeName.copy(nullable = false), *args)
161                     CodeLanguage.KOTLIN ->
162                         add("%T($argsFormat)", typeName.copy(nullable = false), *args)
163                 }
164             }
165 
166         /** Convenience code block of an unsafe cast expression. */
167         @JvmStatic
168         fun ofCast(typeName: XTypeName, expressionBlock: XCodeBlock) = buildCodeBlock { language ->
169             when (language) {
170                 CodeLanguage.JAVA -> add("(%T) (%L)", typeName, expressionBlock)
171                 CodeLanguage.KOTLIN -> add("(%L) as %T", expressionBlock, typeName)
172             }
173         }
174 
175         /** Convenience code block of a Java class literal. */
176         @JvmStatic
177         fun ofJavaClassLiteral(typeName: XClassName) = buildCodeBlock { language ->
178             when (language) {
179                 CodeLanguage.JAVA -> add("%T.class", typeName)
180                 CodeLanguage.KOTLIN -> add("%T::class.java", typeName)
181             }
182         }
183 
184         /** Convenience code block of a Kotlin class literal. */
185         @JvmStatic
186         fun ofKotlinClassLiteral(typeName: XClassName) = buildCodeBlock { language ->
187             when (language) {
188                 CodeLanguage.JAVA ->
189                     add(
190                         "%T.getKotlinClass(%T.class)",
191                         XClassName.get("kotlin.jvm", "JvmClassMappingKt"),
192                         typeName
193                     )
194                 CodeLanguage.KOTLIN -> add("%T::class", typeName)
195             }
196         }
197 
198         /**
199          * Convenience code block of a conditional expression representing a ternary if.
200          *
201          * For Java this will emit: ` <condition> ? <leftExpr> : <rightExpr>)`
202          *
203          * For Kotlin this will emit: `if (<condition>) <leftExpr> else <rightExpr>)`
204          */
205         @JvmStatic
206         fun ofTernaryIf(
207             condition: XCodeBlock,
208             leftExpr: XCodeBlock,
209             rightExpr: XCodeBlock,
210         ) = buildCodeBlock { language ->
211             when (language) {
212                 CodeLanguage.JAVA -> add("%L ? %L : %L", condition, leftExpr, rightExpr)
213                 CodeLanguage.KOTLIN -> add("if (%L) %L else %L", condition, leftExpr, rightExpr)
214             }
215         }
216 
217         /**
218          * Convenience code block of an extension function call.
219          *
220          * For Java this will emit: ` <memberName>(<receiverVariableName>, <args>)`
221          *
222          * For Kotlin this will emit: `<receiverVarName>.<memberName>(<args>)`
223          */
224         @JvmStatic
225         fun ofExtensionCall(memberName: XMemberName, receiverVarName: String, args: XCodeBlock) =
226             buildCodeBlock { language ->
227                 when (language) {
228                     CodeLanguage.JAVA -> add("%M(%L, %L)", memberName, receiverVarName, args)
229                     CodeLanguage.KOTLIN -> add("%L.%M(%L)", receiverVarName, memberName, args)
230                 }
231             }
232     }
233 }
234 
buildCodeBlocknull235 fun buildCodeBlock(block: XCodeBlock.Builder.(CodeLanguage) -> Unit) =
236     XCodeBlock.builder().applyTo { language -> block(language) }.build()
237