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.compiler.codegen
18
19 import androidx.room.compiler.processing.XNullability
20 import com.squareup.kotlinpoet.ARRAY
21 import com.squareup.kotlinpoet.BOOLEAN_ARRAY
22 import com.squareup.kotlinpoet.BYTE_ARRAY
23 import com.squareup.kotlinpoet.CHAR_ARRAY
24 import com.squareup.kotlinpoet.DOUBLE_ARRAY
25 import com.squareup.kotlinpoet.FLOAT_ARRAY
26 import com.squareup.kotlinpoet.INT_ARRAY
27 import com.squareup.kotlinpoet.LONG_ARRAY
28 import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
29 import com.squareup.kotlinpoet.SHORT_ARRAY
30 import com.squareup.kotlinpoet.asTypeName as asKTypeName
31 import com.squareup.kotlinpoet.javapoet.JClassName
32 import com.squareup.kotlinpoet.javapoet.JParameterizedTypeName
33 import com.squareup.kotlinpoet.javapoet.JTypeName
34 import com.squareup.kotlinpoet.javapoet.JTypeVariableName
35 import com.squareup.kotlinpoet.javapoet.JWildcardTypeName
36 import com.squareup.kotlinpoet.javapoet.KClassName
37 import com.squareup.kotlinpoet.javapoet.KParameterizedTypeName
38 import com.squareup.kotlinpoet.javapoet.KTypeName
39 import com.squareup.kotlinpoet.javapoet.KTypeVariableName
40 import com.squareup.kotlinpoet.javapoet.KWildcardTypeName
41 import kotlin.reflect.KClass
42
43 /**
44 * Represents a type name in Java and Kotlin's type system.
45 *
46 * It simply contains a [com.squareup.javapoet.TypeName] and a [com.squareup.kotlinpoet.TypeName].
47 * If the name comes from xprocessing APIs then the KotlinPoet name will default to 'Unavailable' if
48 * the processing backend is not KSP.
49 *
50 * @see [androidx.room.compiler.processing.XType.asTypeName]
51 */
52 open class XTypeName
53 protected constructor(
54 internal open val java: JTypeName,
55 internal open val kotlin: KTypeName,
56 val nullability: XNullability
57 ) {
58 val isPrimitive: Boolean
59 get() = java.isPrimitive
60
61 val isBoxedPrimitive: Boolean
62 get() = java.isBoxedPrimitive
63
64 /**
65 * Returns the raw [XTypeName] if this is a parametrized type name, or itself if not.
66 *
67 * @see [XClassName.parametrizedBy]
68 */
69 val rawTypeName: XTypeName
70 get() {
<lambda>null71 val javaRawType = java.let { if (it is JParameterizedTypeName) it.rawType else it }
<lambda>null72 val kotlinRawType = kotlin.let { if (it is KParameterizedTypeName) it.rawType else it }
73 return XTypeName(javaRawType, kotlinRawType, nullability)
74 }
75
copynull76 open fun copy(nullable: Boolean): XTypeName {
77 // TODO(b/248633751): Handle primitive to boxed when becoming nullable?
78 return XTypeName(
79 java = java,
80 kotlin =
81 if (kotlin != UNAVAILABLE_KTYPE_NAME) {
82 kotlin.copy(nullable = nullable)
83 } else {
84 UNAVAILABLE_KTYPE_NAME
85 },
86 nullability = if (nullable) XNullability.NULLABLE else XNullability.NONNULL
87 )
88 }
89
equalsIgnoreNullabilitynull90 fun equalsIgnoreNullability(other: XTypeName): Boolean {
91 return this.copy(nullable = false) == other.copy(nullable = false)
92 }
93
equalsnull94 override fun equals(other: Any?): Boolean {
95 if (this === other) return true
96 if (other !is XTypeName) return false
97 if (java != other.java) return false
98 if (kotlin != UNAVAILABLE_KTYPE_NAME && other.kotlin != UNAVAILABLE_KTYPE_NAME) {
99 if (kotlin != other.kotlin) return false
100 }
101 return true
102 }
103
hashCodenull104 override fun hashCode(): Int {
105 return java.hashCode()
106 }
107
<lambda>null108 override fun toString() = buildString {
109 append("XTypeName[")
110 append(java)
111 append(" / ")
112 if (kotlin != UNAVAILABLE_KTYPE_NAME) {
113 append(kotlin)
114 } else {
115 append("UNAVAILABLE")
116 }
117 append("]")
118 }
119
toStringnull120 fun toString(codeLanguage: CodeLanguage) =
121 when (codeLanguage) {
122 CodeLanguage.JAVA -> java.toString()
123 CodeLanguage.KOTLIN -> kotlin.toString()
124 }
125
126 companion object {
127 /** A convenience [XTypeName] that represents [Unit] in Kotlin and `void` in Java. */
128 @JvmField
129 val UNIT_VOID = XTypeName(java = JTypeName.VOID, kotlin = com.squareup.kotlinpoet.UNIT)
130
131 /** A convenience [XTypeName] that represents [Any] in Kotlin and [Object] in Java. */
132 @JvmField
133 val ANY_OBJECT = XTypeName(java = JTypeName.OBJECT, kotlin = com.squareup.kotlinpoet.ANY)
134
135 /**
136 * A convenience [XTypeName] that represents [Suppress] in Kotlin and [SuppressWarnings] in
137 * Java.
138 */
139 @JvmField
140 val SUPPRESS =
141 XClassName(
142 java = JClassName.get("java.lang", "SuppressWarnings"),
143 kotlin = KClassName("kotlin", "Suppress"),
144 nullability = XNullability.NONNULL
145 )
146
147 /**
148 * A convenience [XTypeName] that represents [kotlin.Enum] in Kotlin and [java.lang.Enum] in
149 * Java.
150 */
151 @JvmField
152 val ENUM =
153 XTypeName(
154 java = JClassName.get(java.lang.Enum::class.java),
155 kotlin = com.squareup.kotlinpoet.ENUM
156 )
157
158 @JvmField val STRING = String::class.asClassName()
159 @JvmField val ITERABLE = Iterable::class.asClassName()
160 @JvmField val COLLECTION = Collection::class.asClassName()
161 @JvmField val LIST = List::class.asClassName()
162 @JvmField val SET = Set::class.asClassName()
163 @JvmField val MAP = Map::class.asClassName()
164 @JvmField val MAP_ENTRY = Map.Entry::class.asClassName()
165
166 @JvmField val MUTABLE_ITERABLE = Iterable::class.asMutableClassName()
167 @JvmField val MUTABLE_COLLECTION = Collection::class.asMutableClassName()
168 @JvmField val MUTABLE_LIST = List::class.asMutableClassName()
169 @JvmField val MUTABLE_SET = Set::class.asMutableClassName()
170 @JvmField val MUTABLE_MAP = Map::class.asMutableClassName()
171 @JvmField val MUTABLE_MAP_ENTRY = Map.Entry::class.asMutableClassName()
172
173 @JvmField val PRIMITIVE_BOOLEAN = Boolean::class.asPrimitiveTypeName()
174 @JvmField val PRIMITIVE_BYTE = Byte::class.asPrimitiveTypeName()
175 @JvmField val PRIMITIVE_SHORT = Short::class.asPrimitiveTypeName()
176 @JvmField val PRIMITIVE_INT = Int::class.asPrimitiveTypeName()
177 @JvmField val PRIMITIVE_LONG = Long::class.asPrimitiveTypeName()
178 @JvmField val PRIMITIVE_CHAR = Char::class.asPrimitiveTypeName()
179 @JvmField val PRIMITIVE_FLOAT = Float::class.asPrimitiveTypeName()
180 @JvmField val PRIMITIVE_DOUBLE = Double::class.asPrimitiveTypeName()
181
182 @JvmField val BOXED_BOOLEAN = Boolean::class.asClassName()
183 @JvmField val BOXED_BYTE = Byte::class.asClassName()
184 @JvmField val BOXED_SHORT = Short::class.asClassName()
185 @JvmField val BOXED_INT = Int::class.asClassName()
186 @JvmField val BOXED_LONG = Long::class.asClassName()
187 @JvmField val BOXED_CHAR = Char::class.asClassName()
188 @JvmField val BOXED_FLOAT = Float::class.asClassName()
189 @JvmField val BOXED_DOUBLE = Double::class.asClassName()
190
191 @JvmField
192 val ANY_WILDCARD =
193 XTypeName(
194 java = JWildcardTypeName.subtypeOf(Object::class.java),
195 kotlin = com.squareup.kotlinpoet.STAR
196 )
197
198 /** The default [KTypeName] returned by xprocessing APIs when the backend is not KSP. */
199 internal val UNAVAILABLE_KTYPE_NAME =
200 KClassName("androidx.room.compiler.codegen", "Unavailable")
201
invokenull202 operator fun invoke(
203 java: JTypeName,
204 kotlin: KTypeName,
205 nullability: XNullability = XNullability.NONNULL
206 ): XTypeName {
207 return XTypeName(java, kotlin, nullability)
208 }
209
210 /**
211 * Gets a [XTypeName] that represents an array.
212 *
213 * If the [componentTypeName] is one of the primitive names, such as [PRIMITIVE_INT], then
214 * the equivalent Kotlin and Java type names are represented, [IntArray] and `int[]`
215 * respectively.
216 */
217 @JvmStatic
getArrayNamenull218 fun getArrayName(componentTypeName: XTypeName): XTypeName {
219 componentTypeName.java.let {
220 require(it !is JWildcardTypeName || it.lowerBounds.isEmpty()) {
221 "Can't have contra-variant component types in Java arrays. Found '$it'."
222 }
223 }
224
225 val (java, kotlin) =
226 when (componentTypeName) {
227 PRIMITIVE_BOOLEAN -> JArrayTypeName.of(JTypeName.BOOLEAN) to BOOLEAN_ARRAY
228 PRIMITIVE_BYTE -> JArrayTypeName.of(JTypeName.BYTE) to BYTE_ARRAY
229 PRIMITIVE_SHORT -> JArrayTypeName.of(JTypeName.SHORT) to SHORT_ARRAY
230 PRIMITIVE_INT -> JArrayTypeName.of(JTypeName.INT) to INT_ARRAY
231 PRIMITIVE_LONG -> JArrayTypeName.of(JTypeName.LONG) to LONG_ARRAY
232 PRIMITIVE_CHAR -> JArrayTypeName.of(JTypeName.CHAR) to CHAR_ARRAY
233 PRIMITIVE_FLOAT -> JArrayTypeName.of(JTypeName.FLOAT) to FLOAT_ARRAY
234 PRIMITIVE_DOUBLE -> JArrayTypeName.of(JTypeName.DOUBLE) to DOUBLE_ARRAY
235 else -> {
236 componentTypeName.java.let {
237 if (it is JWildcardTypeName) {
238 JArrayTypeName.of(it.upperBounds.single())
239 } else {
240 JArrayTypeName.of(it)
241 }
242 } to ARRAY.parameterizedBy(componentTypeName.kotlin)
243 }
244 }
245 return XTypeName(
246 java = java,
247 kotlin =
248 if (componentTypeName.kotlin != UNAVAILABLE_KTYPE_NAME) {
249 kotlin
250 } else {
251 UNAVAILABLE_KTYPE_NAME
252 }
253 )
254 }
255
256 /**
257 * Create a contravariant wildcard type name, to use as a consumer site-variance
258 * declaration.
259 *
260 * In Java: `? super <bound>` In Kotlin `in <bound>
261 */
262 @JvmStatic
getConsumerSuperNamenull263 fun getConsumerSuperName(bound: XTypeName): XTypeName {
264 return XTypeName(
265 java = JWildcardTypeName.supertypeOf(bound.java),
266 kotlin =
267 if (bound.kotlin != UNAVAILABLE_KTYPE_NAME) {
268 KWildcardTypeName.consumerOf(bound.kotlin)
269 } else {
270 UNAVAILABLE_KTYPE_NAME
271 }
272 )
273 }
274
275 /**
276 * Create a covariant wildcard type name, to use as a producer site-variance declaration.
277 *
278 * In Java: `? extends <bound>` In Kotlin `out <bound>
279 */
280 @JvmStatic
getProducerExtendsNamenull281 fun getProducerExtendsName(bound: XTypeName): XTypeName {
282 return XTypeName(
283 java = JWildcardTypeName.subtypeOf(bound.java),
284 kotlin =
285 if (bound.kotlin != UNAVAILABLE_KTYPE_NAME) {
286 KWildcardTypeName.producerOf(bound.kotlin)
287 } else {
288 UNAVAILABLE_KTYPE_NAME
289 }
290 )
291 }
292
293 /** Creates a type variable named with bounds. */
294 @JvmStatic
getTypeVariableNamenull295 fun getTypeVariableName(name: String, bounds: List<XTypeName> = emptyList()): XTypeName {
296 return XTypeName(
297 java = JTypeVariableName.get(name, *bounds.map { it.java }.toTypedArray()),
298 kotlin = KTypeVariableName(name, bounds.map { it.kotlin })
299 )
300 }
301 }
302 }
303
304 /**
305 * Creates a [XTypeName] whose JavaPoet name is a primitive name and KotlinPoet is the interop type.
306 *
307 * This function is useful since [asClassName] only supports creating class names and specifically
308 * only the boxed version of primitives.
309 */
asPrimitiveTypeNamenull310 internal fun KClass<*>.asPrimitiveTypeName(): XTypeName {
311 require(this.java.isPrimitive) { "$this does not represent a primitive." }
312 val jTypeName = getPrimitiveJTypeName(this.java)
313 val kTypeName = this.asKTypeName()
314 return XTypeName(jTypeName, kTypeName)
315 }
316
getPrimitiveJTypeNamenull317 private fun getPrimitiveJTypeName(klass: Class<*>): JTypeName =
318 when (klass) {
319 java.lang.Void.TYPE -> JTypeName.VOID
320 java.lang.Boolean.TYPE -> JTypeName.BOOLEAN
321 java.lang.Byte.TYPE -> JTypeName.BYTE
322 java.lang.Short.TYPE -> JTypeName.SHORT
323 java.lang.Integer.TYPE -> JTypeName.INT
324 java.lang.Long.TYPE -> JTypeName.LONG
325 java.lang.Character.TYPE -> JTypeName.CHAR
326 java.lang.Float.TYPE -> JTypeName.FLOAT
327 java.lang.Double.TYPE -> JTypeName.DOUBLE
328 else -> error("Can't get JTypeName from java.lang.Class: $klass")
329 }
330
XTypeNamenull331 fun XTypeName.box() = XTypeName(java.box(), kotlin)
332
333 fun XTypeName.unbox() = XTypeName(java.unbox(), kotlin.copy(nullable = false), XNullability.NONNULL)
334
335 fun XTypeName.toJavaPoet(): JTypeName = this.java
336
337 fun XClassName.toJavaPoet(): JClassName = this.java
338
339 fun XTypeName.toKotlinPoet(): KTypeName = this.kotlin
340
341 fun XClassName.toKotlinPoet(): KClassName = this.kotlin
342