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