1 /*
2 * Copyright (C) 2015 Square, Inc.
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 * https://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 @file:JvmName("TypeVariableNames")
17
18 package com.squareup.kotlinpoet
19
20 import java.lang.reflect.Type
21 import java.util.Collections
22 import javax.lang.model.element.TypeParameterElement
23 import javax.lang.model.type.TypeMirror
24 import javax.lang.model.type.TypeVariable
25 import kotlin.reflect.KClass
26 import kotlin.reflect.KType
27 import kotlin.reflect.KTypeParameter
28 import kotlin.reflect.KVariance
29
30 public class TypeVariableName private constructor(
31 public val name: String,
32 public val bounds: List<TypeName>,
33
34 /** Either [KModifier.IN], [KModifier.OUT], or null. */
35 public val variance: KModifier? = null,
36 public val isReified: Boolean = false,
37 nullable: Boolean = false,
38 annotations: List<AnnotationSpec> = emptyList(),
39 tags: Map<KClass<*>, Any> = emptyMap(),
40 ) : TypeName(nullable, annotations, TagMap(tags)) {
41
copynull42 override fun copy(
43 nullable: Boolean,
44 annotations: List<AnnotationSpec>,
45 tags: Map<KClass<*>, Any>,
46 ): TypeVariableName {
47 return copy(nullable, annotations, this.bounds, this.isReified, tags)
48 }
49
copynull50 public fun copy(
51 nullable: Boolean = this.isNullable,
52 annotations: List<AnnotationSpec> = this.annotations.toList(),
53 bounds: List<TypeName> = this.bounds.toList(),
54 reified: Boolean = this.isReified,
55 tags: Map<KClass<*>, Any> = this.tagMap.tags,
56 ): TypeVariableName {
57 return TypeVariableName(
58 name,
59 bounds.withoutImplicitBound(),
60 variance,
61 reified,
62 nullable,
63 annotations,
64 tags,
65 )
66 }
67
withoutImplicitBoundnull68 private fun List<TypeName>.withoutImplicitBound(): List<TypeName> {
69 return if (size == 1) this else filterNot { it == NULLABLE_ANY }
70 }
71
emitnull72 override fun emit(out: CodeWriter) = out.emit(name)
73
74 override fun equals(other: Any?): Boolean {
75 if (this === other) return true
76 if (javaClass != other?.javaClass) return false
77 if (!super.equals(other)) return false
78
79 other as TypeVariableName
80
81 if (name != other.name) return false
82 if (bounds != other.bounds) return false
83 if (variance != other.variance) return false
84 if (isReified != other.isReified) return false
85
86 return true
87 }
88
hashCodenull89 override fun hashCode(): Int {
90 var result = super.hashCode()
91 result = 31 * result + name.hashCode()
92 result = 31 * result + bounds.hashCode()
93 result = 31 * result + (variance?.hashCode() ?: 0)
94 result = 31 * result + isReified.hashCode()
95 return result
96 }
97
98 public companion object {
ofnull99 internal fun of(
100 name: String,
101 bounds: List<TypeName>,
102 variance: KModifier?,
103 ): TypeVariableName {
104 require(variance == null || variance.isOneOf(KModifier.IN, KModifier.OUT)) {
105 "$variance is an invalid variance modifier, the only allowed values are in and out!"
106 }
107 require(bounds.isNotEmpty()) {
108 "$name has no bounds"
109 }
110 // Strip Any? from bounds if it is present.
111 return TypeVariableName(name, bounds, variance)
112 }
113
114 /** Returns type variable named `name` with `variance` and without bounds. */
115 @JvmStatic
116 @JvmName("get")
117 @JvmOverloads
invokenull118 public operator fun invoke(name: String, variance: KModifier? = null): TypeVariableName =
119 of(name = name, bounds = NULLABLE_ANY_LIST, variance = variance)
120
121 /** Returns type variable named `name` with `variance` and `bounds`. */
122 @JvmStatic
123 @JvmName("get")
124 @JvmOverloads
125 public operator fun invoke(
126 name: String,
127 vararg bounds: TypeName,
128 variance: KModifier? = null,
129 ): TypeVariableName =
130 of(
131 name = name,
132 bounds = bounds.toList().ifEmpty(::NULLABLE_ANY_LIST),
133 variance = variance,
134 )
135
136 /** Returns type variable named `name` with `variance` and `bounds`. */
137 @JvmStatic
138 @JvmName("get")
139 @JvmOverloads
140 public operator fun invoke(
141 name: String,
142 vararg bounds: KClass<*>,
143 variance: KModifier? = null,
144 ): TypeVariableName =
145 of(
146 name = name,
147 bounds = bounds.map(KClass<*>::asTypeName).ifEmpty(::NULLABLE_ANY_LIST),
148 variance = variance,
149 )
150
151 /** Returns type variable named `name` with `variance` and `bounds`. */
152 @JvmStatic
153 @JvmName("get")
154 @JvmOverloads
155 public operator fun invoke(
156 name: String,
157 vararg bounds: Type,
158 variance: KModifier? = null,
159 ): TypeVariableName =
160 of(
161 name = name,
162 bounds = bounds.map(Type::asTypeName).ifEmpty(::NULLABLE_ANY_LIST),
163 variance = variance,
164 )
165
166 /** Returns type variable named `name` with `variance` and `bounds`. */
167 @JvmStatic
168 @JvmName("get")
169 @JvmOverloads
170 public operator fun invoke(
171 name: String,
172 bounds: List<TypeName>,
173 variance: KModifier? = null,
174 ): TypeVariableName = of(name, bounds.ifEmpty(::NULLABLE_ANY_LIST), variance)
175
176 /** Returns type variable named `name` with `variance` and `bounds`. */
177 @JvmStatic
178 @JvmName("getWithClasses")
179 @JvmOverloads
180 public operator fun invoke(
181 name: String,
182 bounds: Iterable<KClass<*>>,
183 variance: KModifier? = null,
184 ): TypeVariableName =
185 of(
186 name,
187 bounds.map { it.asTypeName() }.ifEmpty(::NULLABLE_ANY_LIST),
188 variance,
189 )
190
191 /** Returns type variable named `name` with `variance` and `bounds`. */
192 @JvmStatic
193 @JvmName("getWithTypes")
194 @JvmOverloads
invokenull195 public operator fun invoke(
196 name: String,
197 bounds: Iterable<Type>,
198 variance: KModifier? = null,
199 ): TypeVariableName =
200 of(
201 name,
202 bounds.map { it.asTypeName() }.ifEmpty(::NULLABLE_ANY_LIST),
203 variance,
204 )
205
206 /**
207 * Make a TypeVariableName for the given TypeMirror. This form is used internally to avoid
208 * infinite recursion in cases like `Enum<E extends Enum<E>>`. When we encounter such a
209 * thing, we will make a TypeVariableName without bounds and add that to the `typeVariables`
210 * map before looking up the bounds. Then if we encounter this TypeVariable again while
211 * constructing the bounds, we can just return it from the map. And, the code that put the entry
212 * in `variables` will make sure that the bounds are filled in before returning.
213 */
getnull214 internal fun get(
215 mirror: javax.lang.model.type.TypeVariable,
216 typeVariables: MutableMap<TypeParameterElement, TypeVariableName>,
217 ): TypeVariableName {
218 val element = mirror.asElement() as TypeParameterElement
219 var typeVariableName: TypeVariableName? = typeVariables[element]
220 if (typeVariableName == null) {
221 // Since the bounds field is public, we need to make it an unmodifiableList. But we control
222 // the List that that wraps, which means we can change it before returning.
223 val bounds = mutableListOf<TypeName>()
224 val visibleBounds = Collections.unmodifiableList(bounds)
225 typeVariableName = TypeVariableName(element.simpleName.toString(), visibleBounds)
226 typeVariables[element] = typeVariableName
227 for (typeMirror in element.bounds) {
228 bounds += get(typeMirror, typeVariables)
229 }
230 bounds.remove(ANY)
231 bounds.remove(JAVA_OBJECT)
232 if (bounds.isEmpty()) {
233 bounds.add(NULLABLE_ANY)
234 }
235 }
236 return typeVariableName
237 }
238
239 /** Returns type variable equivalent to `type`. */
getnull240 internal fun get(
241 type: java.lang.reflect.TypeVariable<*>,
242 map: MutableMap<Type, TypeVariableName> = mutableMapOf(),
243 ): TypeVariableName {
244 var result: TypeVariableName? = map[type]
245 if (result == null) {
246 val bounds = mutableListOf<TypeName>()
247 val visibleBounds = Collections.unmodifiableList(bounds)
248 result = TypeVariableName(type.name, visibleBounds)
249 map[type] = result
250 for (bound in type.bounds) {
251 bounds += get(bound, map)
252 }
253 bounds.remove(ANY)
254 bounds.remove(JAVA_OBJECT)
255 if (bounds.isEmpty()) {
256 bounds.add(NULLABLE_ANY)
257 }
258 }
259 return result
260 }
261
262 internal val NULLABLE_ANY_LIST = listOf(NULLABLE_ANY)
263 private val JAVA_OBJECT = ClassName("java.lang", "Object")
264 }
265 }
266
267 /** Returns type variable equivalent to `mirror`. */
268 @DelicateKotlinPoetApi(
269 message = "Java reflection APIs don't give complete information on Kotlin types. Consider using" +
270 " the kotlinpoet-metadata APIs instead.",
271 )
272 @JvmName("get")
asTypeVariableNamenull273 public fun TypeVariable.asTypeVariableName(): TypeVariableName =
274 (asElement() as TypeParameterElement).asTypeVariableName()
275
276 /** Returns type variable equivalent to `element`. */
277 @DelicateKotlinPoetApi(
278 message = "Element APIs don't give complete information on Kotlin types. Consider using" +
279 " the kotlinpoet-metadata APIs instead.",
280 )
281 @JvmName("get")
282 public fun TypeParameterElement.asTypeVariableName(): TypeVariableName {
283 val name = simpleName.toString()
284 val boundsTypeNames = bounds.map(TypeMirror::asTypeName)
285 .ifEmpty(TypeVariableName.Companion::NULLABLE_ANY_LIST)
286 return TypeVariableName.of(name, boundsTypeNames, variance = null)
287 }
288
asTypeVariableNamenull289 public fun KTypeParameter.asTypeVariableName(): TypeVariableName {
290 return TypeVariableName.of(
291 name = name,
292 bounds = upperBounds.map(KType::asTypeName)
293 .ifEmpty(TypeVariableName.Companion::NULLABLE_ANY_LIST),
294 variance = when (variance) {
295 KVariance.INVARIANT -> null
296 KVariance.IN -> KModifier.IN
297 KVariance.OUT -> KModifier.OUT
298 },
299 )
300 }
301