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