1 /*
<lambda>null2 * Copyright (C) 2017 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 com.android.tools.metalava.model
18
19 import java.util.Objects
20
21 /**
22 * Whether metalava supports type use annotations. Note that you can't just turn this flag back on;
23 * you have to also add TYPE_USE back to the handful of nullness annotations in
24 * stub-annotations/src/main/java/.
25 */
26 const val SUPPORT_TYPE_USE_ANNOTATIONS = false
27
28 /**
29 * Represents a {@link https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/Type.html Type}
30 */
31 @MetalavaApi
32 interface TypeItem {
33 /** Modifiers for the type. Contains type-use annotation information. */
34 val modifiers: TypeModifiers
35
36 fun accept(visitor: TypeVisitor)
37
38 fun accept(visitor: MultipleTypeVisitor, other: List<TypeItem>)
39
40 /**
41 * Whether this type is equal to [other], not considering modifiers.
42 *
43 * This is implemented on each sub-interface of [TypeItem] instead of [equals] because
44 * interfaces are not allowed to implement [equals]. An [equals] implementation is provided by
45 * [DefaultTypeItem].
46 */
47 fun equalToType(other: TypeItem?): Boolean
48
49 /**
50 * Hashcode for the type.
51 *
52 * This is implemented on each sub-interface of [TypeItem] instead of [hashCode] because
53 * interfaces are not allowed to implement [hashCode]. A [hashCode] implementation is provided
54 * by [DefaultTypeItem].
55 */
56 fun hashCodeForType(): Int
57
58 /**
59 * Provide a helpful description of the type, for use in error messages.
60 *
61 * This is not suitable for use in signature or stubs as while it defaults to [toTypeString] for
62 * most types it is overridden by others to provide additional information.
63 */
64 fun description(): String = toTypeString()
65
66 /**
67 * Generates a string for this type.
68 *
69 * @see [TypeStringConfiguration] for information on the parameters.
70 */
71 fun toTypeString(
72 configuration: TypeStringConfiguration = TypeStringConfiguration.DEFAULT
73 ): String
74
75 /**
76 * Get a string representation of the erased type.
77 *
78 * Implements the behavior described
79 * [here](https://docs.oracle.com/javase/tutorial/java/generics/genTypes.html).
80 *
81 * One point to note is that vararg parameters are represented using standard array syntax, i.e.
82 * `[]`, not the special source `...` syntax. The reason for that is that the erased type is
83 * mainly used at runtime which treats a vararg parameter as a standard array type.
84 */
85 @MetalavaApi fun toErasedTypeString(): String
86
87 /** Returns the internal name of the type, as seen in bytecode. */
88 fun internalName(): String
89
90 fun asClass(): ClassItem?
91
92 fun toSimpleType() = toTypeString(SIMPLE_TYPE_CONFIGURATION)
93
94 /**
95 * Helper methods to compare types, especially types from signature files with types from
96 * parsing, which may have slightly different formats, e.g. varargs ("...") versus arrays
97 * ("[]"), java.lang. prefixes removed in wildcard signatures, etc.
98 */
99 fun toCanonicalType() = toTypeString(CANONICAL_TYPE_CONFIGURATION)
100
101 /**
102 * Makes substitutions to the type based on the [typeParameterBindings]. For instance, if the
103 * [typeParameterBindings] contains `{T -> String}`, calling this method on `T` would return
104 * `String`, and calling it on `List<T>` would return `List<String>` (in both cases the
105 * modifiers on the `String` will be independently mutable from the `String` in the
106 * [typeParameterBindings]). Calling it on an unrelated type like `int` would return a duplicate
107 * of that type.
108 *
109 * This method is intended to be used in conjunction with [ClassItem.mapTypeVariables],
110 */
111 fun convertType(typeParameterBindings: TypeParameterBindings): TypeItem
112
113 fun convertType(from: ClassItem, to: ClassItem): TypeItem {
114 val map = from.mapTypeVariables(to)
115 if (map.isNotEmpty()) {
116 return convertType(map)
117 }
118
119 return this
120 }
121
122 /** Returns `true` if `this` type can be assigned from `other` without unboxing the other. */
123 fun isAssignableFromWithoutUnboxing(other: TypeItem): Boolean {
124 // Limited text based check
125 if (this == other) return true
126 val bounds =
127 (other as? VariableTypeItem)?.asTypeParameter?.typeBounds()?.map { it.toTypeString() }
128 ?: emptyList()
129 return bounds.contains(toTypeString())
130 }
131
132 fun isJavaLangObject(): Boolean = false
133
134 fun isString(): Boolean = false
135
136 fun defaultValue(): Any? = null
137
138 fun defaultValueString(): String = "null"
139
140 /**
141 * Duplicates this type substituting in the provided [modifiers] in place of this instance's
142 * [modifiers].
143 */
144 fun substitute(modifiers: TypeModifiers): TypeItem
145
146 /**
147 * Return a [TypeItem] instance identical to this on except its [modifiers]'s
148 * [TypeModifiers.nullability] property is the same as the [nullability] parameter.
149 *
150 * If the parameter is the same as this instance's [modifiers]'s property then it will just
151 * return this instance, otherwise it will return a new instance with a new [TypeModifiers].
152 */
153 fun substitute(nullability: TypeNullability) =
154 if (modifiers.nullability == nullability) this
155 else substitute(modifiers.substitute(nullability))
156
157 /**
158 * Return a [TypeItem] instance of the same type as this one that was produced by the [TypeItem]
159 * appropriate [TypeTransformer.transform] method.
160 */
161 fun transform(transformer: TypeTransformer): TypeItem
162
163 /** Whether this type was originally a value class type. Defaults to false if not overridden. */
164 fun isValueClassType(): Boolean = false
165
166 companion object {
167 /** [TypeStringConfiguration] for [toSimpleType] to pass to [toTypeString]. */
168 private val SIMPLE_TYPE_CONFIGURATION =
169 TypeStringConfiguration(stripJavaLangPrefix = StripJavaLangPrefix.LEGACY)
170
171 /** [TypeStringConfiguration] for [toCanonicalType] to pass to [toTypeString]. */
172 private val CANONICAL_TYPE_CONFIGURATION =
173 TypeStringConfiguration(
174 stripJavaLangPrefix = StripJavaLangPrefix.ALWAYS,
175 treatVarargsAsArray = true,
176 )
177
178 /** Shortens types, if configured */
179 fun shortenTypes(type: String): String {
180 var cleaned = type
181 if (cleaned.contains("@androidx.annotation.")) {
182 cleaned = cleaned.replace("@androidx.annotation.", "@")
183 }
184 return cleaned
185 }
186
187 /**
188 * Create a [Comparator] that when given two [TypeItem] will treat them as equal if either
189 * returns `null` from [TypeItem.asClass] and will otherwise compare the two [ClassItem]s
190 * using [comparator].
191 *
192 * This only defines a partial ordering over [TypeItem].
193 */
194 private fun typeItemAsClassComparator(
195 comparator: Comparator<ClassItem>
196 ): Comparator<TypeItem> {
197 return Comparator { type1, type2 ->
198 val cls1 = type1.asClass()
199 val cls2 = type2.asClass()
200 if (cls1 != null && cls2 != null) {
201 comparator.compare(cls1, cls2)
202 } else {
203 0
204 }
205 }
206 }
207
208 /** A total ordering over [TypeItem] comparing [TypeItem.toTypeString]. */
209 private val typeStringComparator =
210 Comparator.comparing<TypeItem, String> { it.toTypeString() }
211
212 /**
213 * A total ordering over [TypeItem] comparing [TypeItem.asClass] using
214 * [ClassItem.fullNameThenQualifierComparator] and then comparing [TypeItem.toTypeString].
215 */
216 val totalComparator: Comparator<TypeItem> =
217 typeItemAsClassComparator(ClassItem.fullNameThenQualifierComparator)
218 .thenComparing(typeStringComparator)
219
220 @Deprecated(
221 "" +
222 "this should not be used as it only defines a partial ordering which means that the " +
223 "source order will affect the result"
224 )
225 val partialComparator: Comparator<TypeItem> = Comparator { type1, type2 ->
226 val cls1 = type1.asClass()
227 val cls2 = type2.asClass()
228 if (cls1 != null && cls2 != null) {
229 ClassItem.fullNameComparator.compare(cls1, cls2)
230 } else {
231 type1.toTypeString().compareTo(type2.toTypeString())
232 }
233 }
234
235 /**
236 * Convert a type string containing to its lambda representation or return the original.
237 *
238 * E.g.: `"kotlin.jvm.functions.Function1<Integer, String>"` to `"(Integer) -> String"`.
239 */
240 fun toLambdaFormat(typeName: String): String {
241 // Bail if this isn't a Kotlin function type
242 if (!typeName.startsWith(KOTLIN_FUNCTION_PREFIX)) {
243 return typeName
244 }
245
246 // Find the first character after the first opening angle bracket. This will either be
247 // the first character of the paramTypes of the lambda if it has parameters.
248 val paramTypesStart =
249 typeName.indexOf('<', startIndex = KOTLIN_FUNCTION_PREFIX.length) + 1
250
251 // The last type param is always the return type. We find and set these boundaries with
252 // the push down loop below.
253 var paramTypesEnd = -1
254 var returnTypeStart = -1
255
256 // Get the exclusive end of the return type parameter by finding the last closing
257 // angle bracket.
258 val returnTypeEnd = typeName.lastIndexOf('>')
259
260 // Bail if an an unexpected format broke the indexOf's above.
261 if (paramTypesStart <= 0 || paramTypesStart >= returnTypeEnd) {
262 return typeName
263 }
264
265 // This loop looks for the last comma that is not inside the type parameters of a type
266 // parameter. It's a simple push down state machine that stores its depth as a counter
267 // instead of a stack. It runs backwards from the last character of the type parameters
268 // just before the last closing angle bracket to the beginning just before the first
269 // opening angle bracket.
270 var depth = 0
271 for (i in returnTypeEnd - 1 downTo paramTypesStart) {
272 val c = typeName[i]
273
274 // Increase or decrease stack depth on angle brackets
275 when (c) {
276 '>' -> depth++
277 '<' -> depth--
278 }
279
280 when {
281 depth == 0 ->
282 when { // At the top level
283 c == ',' -> {
284 // When top level comma is found, mark it as the exclusive end of
285 // the
286 // parameter types and end the loop
287 paramTypesEnd = i
288 break
289 }
290 !c.isWhitespace() -> {
291 // Keep moving the start of the return type back until whitespace
292 returnTypeStart = i
293 }
294 }
295 depth < 0 -> return typeName // Bail, unbalanced nesting
296 }
297 }
298
299 // Bail if some sort of unbalanced nesting occurred or the indices around the comma
300 // appear grossly incorrect.
301 if (depth > 0 || returnTypeStart < 0 || returnTypeStart <= paramTypesEnd) {
302 return typeName
303 }
304
305 return buildString(typeName.length) {
306 append("(")
307
308 // Slice param types, if any, and append them between the parenthesis
309 if (paramTypesEnd > 0) {
310 append(typeName, paramTypesStart, paramTypesEnd)
311 }
312
313 append(") -> ")
314
315 // Slice out the return type param and append it after the arrow
316 append(typeName, returnTypeStart, returnTypeEnd)
317 }
318 }
319
320 /** Prefix of Kotlin JVM function types, used for lambdas. */
321 private const val KOTLIN_FUNCTION_PREFIX = "kotlin.jvm.functions.Function"
322 }
323 }
324
325 /** Different ways of handling `java.lang.` prefix stripping in [TypeItem.toTypeString]. */
326 enum class StripJavaLangPrefix {
327 /** Never strip java.lang. prefixes when */
328 NEVER,
329
330 /**
331 * Only strip java.lang. prefixes from the start of the type as long as they are not a generic
332 * varargs parameter.
333 *
334 * This is legacy behavior from when types were treated as strings.
335 */
336 LEGACY,
337
338 /**
339 * A special value that is only used internally within [TypeItem.toTypeString].
340 *
341 * If [LEGACY] was provided for a varargs type then [LEGACY] will be replaced by this when
342 * processing the [ArrayTypeItem] to indicate to the nested [ClassTypeItem] that it is a varargs
343 * parameter and to only strip the `java.lang.` prefix if it is at the start and is a generic
344 * type.
345 */
346 VARARGS,
347
348 /** Always strip java.lang. prefixes from the type. */
349 ALWAYS,
350 }
351
352 /**
353 * A mapping from one class's type parameters to the types provided for those type parameters in a
354 * possibly indirect subclass.
355 *
356 * e.g. Given `Map<K, V>` and a subinterface `StringToIntMap extends Map<String, Integer>` then this
357 * would contain a mapping from `K -> String` and `V -> Integer`.
358 *
359 * Although a `ClassTypeItem`'s arguments can be `WildcardTypeItem`s as well as
360 * `ReferenceTypeItem`s, a `ClassTypeItem` used in an extends or implements list cannot have a
361 * `WildcardTypeItem` as an argument so this cast is safe. See
362 * https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-Superclass
363 */
364 typealias TypeParameterBindings = Map<TypeParameterItem, ReferenceTypeItem>
365
366 abstract class DefaultTypeItem(
367 final override val modifiers: TypeModifiers,
368 ) : TypeItem {
369
370 private lateinit var cachedDefaultType: String
371 private lateinit var cachedErasedType: String
372
toStringnull373 override fun toString(): String = toTypeString()
374
375 override fun toTypeString(configuration: TypeStringConfiguration): String {
376 // Cache the default type string. Other configurations are less likely to be reused.
377 return if (configuration.isDefault) {
378 if (!::cachedDefaultType.isInitialized) {
379 cachedDefaultType = generateTypeString(configuration)
380 }
381 cachedDefaultType
382 } else {
383 generateTypeString(configuration)
384 }
385 }
386
387 /**
388 * Generate a string representation of this type based on [configuration].
389 *
390 * The returned value will be cached if the [configuration] is the default.
391 */
<lambda>null392 private fun generateTypeString(configuration: TypeStringConfiguration) = buildString {
393 appendTypeString(this@DefaultTypeItem, configuration)
394 }
395
toErasedTypeStringnull396 override fun toErasedTypeString(): String {
397 if (!::cachedErasedType.isInitialized) {
398 cachedErasedType = toTypeString(ERASED_TYPE_STRING_CONFIGURATION)
399 }
400 return cachedErasedType
401 }
402
internalNamenull403 override fun internalName(): String {
404 // Default implementation; PSI subclass is more accurate
405 return toSlashFormat(toErasedTypeString())
406 }
407
equalsnull408 override fun equals(other: Any?): Boolean {
409 if (other !is TypeItem) return false
410 return equalToType(other)
411 }
412
hashCodenull413 override fun hashCode(): Int = hashCodeForType()
414
415 companion object {
416 private val ERASED_TYPE_STRING_CONFIGURATION =
417 TypeStringConfiguration(
418 eraseGenerics = true,
419 treatVarargsAsArray = true,
420 )
421
422 private fun StringBuilder.appendTypeString(
423 type: TypeItem,
424 configuration: TypeStringConfiguration
425 ) {
426 when (type) {
427 is PrimitiveTypeItem -> {
428 if (configuration.annotations) {
429 appendAnnotations(type.modifiers, configuration)
430 }
431 append(type.kind.primitiveName)
432 // Primitives must be non-null.
433 }
434 is ArrayTypeItem -> {
435 // Get the nested configuration. This replaces StripJavaLangPrefix.LEGACY
436 // with StripJavaLangPrefix.VARARGS for a varargs type to maintain the legacy
437 // behavior of NOT stripping java.lang. prefix from varargs parameters unless it
438 // has type arguments.
439 val nestedConfiguration =
440 if (
441 type.isVarargs &&
442 configuration.stripJavaLangPrefix == StripJavaLangPrefix.LEGACY
443 ) {
444 configuration.copy(stripJavaLangPrefix = StripJavaLangPrefix.VARARGS)
445 } else configuration
446
447 // Compute the outermost array suffix as that can differ if it is varargs. If
448 // this is a varargs then it must be the outermost, otherwise the outermost is
449 // not a varargs so they will all use the same suffix.
450 val outermostArraySuffix =
451 if (type.isVarargs && !configuration.treatVarargsAsArray) "..." else "[]"
452
453 // The ordering of array annotations means this can't just use a recursive
454 // approach for annotated multi-dimensional arrays, but it can if annotations
455 // aren't included.
456 if (configuration.annotations) {
457 var deepComponentType = type.componentType
458 val arrayModifiers = mutableListOf(type.modifiers)
459 while (deepComponentType is ArrayTypeItem) {
460 arrayModifiers.add(deepComponentType.modifiers)
461 deepComponentType = deepComponentType.componentType
462 }
463 val suffixes = arrayModifiers.map { it.nullability.suffix }.reversed()
464
465 // Print the innermost component type.
466 appendTypeString(deepComponentType, nestedConfiguration)
467
468 // Print modifiers from the outermost array type in, and the array suffixes.
469 arrayModifiers.zip(suffixes).forEachIndexed { index, (modifiers, suffix) ->
470 appendAnnotations(modifiers, configuration, leadingSpace = true)
471 // The array suffix can be different on the outermost array type. The
472 // outermost is the last in the list.
473 if (index == arrayModifiers.lastIndex) {
474 append(outermostArraySuffix)
475 } else {
476 // Only the outermost array can be varargs.
477 append("[]")
478 }
479 if (configuration.kotlinStyleNulls) {
480 append(suffix)
481 }
482 }
483 } else {
484 // Non-annotated case: just recur to the component
485 appendTypeString(type.componentType, nestedConfiguration)
486 append(outermostArraySuffix)
487 if (configuration.kotlinStyleNulls) {
488 append(type.modifiers.nullability.suffix)
489 }
490 }
491 }
492 is ClassTypeItem -> {
493 if (type.outerClassType != null) {
494 // Legacy behavior for stripping java.lang. prefixes is to not strip them
495 // from nested classes. This replicates that by replacing LEGACY with
496 // NEVER in the configuration used to append the type string of the
497 // outermost class which is responsible for stripping the prefix.
498 val nestedConfiguration =
499 if (configuration.stripJavaLangPrefix == StripJavaLangPrefix.LEGACY)
500 configuration.copy(stripJavaLangPrefix = StripJavaLangPrefix.NEVER)
501 else configuration
502 appendTypeString(type.outerClassType!!, nestedConfiguration)
503 append(configuration.nestedClassSeparator)
504 if (configuration.annotations) {
505 appendAnnotations(type.modifiers, configuration)
506 }
507 append(type.className)
508 } else {
509 // Check to see whether a java.lang. prefix should be stripped from the type
510 // name.
511 val stripJavaLangPrefix =
512 when (configuration.stripJavaLangPrefix) {
513 StripJavaLangPrefix.ALWAYS -> true
514 StripJavaLangPrefix.LEGACY ->
515 // This should only strip if this is at the start.
516 isEmpty()
517 StripJavaLangPrefix.VARARGS ->
518 // This should only strip if this is at the start and is a
519 // generic type.
520 isEmpty() && type.hasTypeArguments()
521 else -> false
522 }
523
524 // Get the class name prefix, i.e. the part before the class's simple name
525 // where annotations can be placed. e.g. for java.lang.String the simple
526 // name is `String` and the prefix is `java.lang.`.
527 val classNamePrefix = type.classNamePrefix
528
529 // Append the class name prefix unless it is `java.lang.` and `java.lang.`
530 // prefixes should be stripped.
531 if (!(stripJavaLangPrefix && classNamePrefix == JAVA_LANG_PREFIX)) {
532 append(classNamePrefix)
533 }
534 if (configuration.annotations) {
535 appendAnnotations(type.modifiers, configuration)
536 }
537 append(type.className)
538 }
539
540 if (!configuration.eraseGenerics && type.arguments.isNotEmpty()) {
541 append("<")
542 type.arguments.forEachIndexed { index, parameter ->
543 appendTypeString(parameter, configuration)
544 if (index != type.arguments.size - 1) {
545 append(",")
546 if (configuration.spaceBetweenTypeArguments) {
547 append(" ")
548 }
549 }
550 }
551 append(">")
552 }
553 if (configuration.kotlinStyleNulls) {
554 append(type.modifiers.nullability.suffix)
555 }
556 }
557 is VariableTypeItem -> {
558 if (configuration.annotations) {
559 appendAnnotations(type.modifiers, configuration)
560 }
561 if (configuration.eraseGenerics) {
562 // Replace the type variable with the bounds of the type parameter.
563 val typeParameter = type.asTypeParameter
564 typeParameter.asErasedType()?.let { boundsType ->
565 appendTypeString(boundsType, configuration)
566 }
567 // No explicit bounds were provided so use the default of java.lang.Object.
568 ?: if (configuration.stripJavaLangPrefix == StripJavaLangPrefix.ALWAYS) {
569 append("Object")
570 } else {
571 append(JAVA_LANG_OBJECT)
572 }
573 } else {
574 append(type.name)
575 }
576 if (configuration.kotlinStyleNulls) {
577 append(type.modifiers.nullability.suffix)
578 }
579 }
580 is WildcardTypeItem -> {
581 if (configuration.annotations) {
582 appendAnnotations(type.modifiers, configuration)
583 }
584 append("?")
585
586 type.superBound?.let {
587 append(" super ")
588 appendTypeString(it, configuration)
589 // If there's a super bound, don't also print an object extends bound.
590 return
591 }
592
593 type.extendsBound?.let {
594 if (shouldIncludeExtendsBound(it, configuration)) {
595 append(" extends ")
596 appendTypeString(it, configuration)
597 }
598 }
599
600 // It doesn't make sense to have a nullness suffix on a wildcard, this should be
601 // handled by the bound.
602 }
603 }
604 }
605
606 /**
607 * Returns whether the [extendsBound] should be included in the type string based on the
608 * [configuration].
609 */
610 private fun shouldIncludeExtendsBound(
611 extendsBound: ReferenceTypeItem,
612 configuration: TypeStringConfiguration
613 ): Boolean {
614 // Non-object bounds should always be included.
615 if (!extendsBound.isJavaLangObject()) return true
616
617 // If the bound is Object, it should only be included when the nullability isn't implied
618 // by the configuration. If both kotlinStyleNulls and annotations are false, no
619 // nullability information is included anyway.
620 if (!configuration.kotlinStyleNulls && !configuration.annotations) return false
621
622 // When nullability information is included, excluded bounds imply non-null when
623 // kotlinStyleNulls is true and platform when it is false.
624 val nullability = extendsBound.modifiers.nullability
625 if (configuration.kotlinStyleNulls && nullability == TypeNullability.NONNULL)
626 return false
627 if (!configuration.kotlinStyleNulls && nullability == TypeNullability.PLATFORM)
628 return false
629 return true
630 }
631
632 private fun StringBuilder.appendAnnotations(
633 modifiers: TypeModifiers,
634 configuration: TypeStringConfiguration,
635 leadingSpace: Boolean = false,
636 trailingSpace: Boolean = true
637 ) {
638 val annotations =
639 modifiers.annotations.filter { annotation ->
640 // If Kotlin-style nulls are printed, nullness annotations shouldn't be.
641 !(configuration.kotlinStyleNulls && annotation.isNullnessAnnotation())
642 }
643 if (annotations.isEmpty()) return
644
645 if (leadingSpace) {
646 append(' ')
647 }
648 annotations.forEachIndexed { index, annotation ->
649 append(annotation.toSource())
650 if (index != annotations.size - 1) {
651 append(' ')
652 }
653 }
654 if (trailingSpace) {
655 append(' ')
656 }
657 }
658
659 // Copied from doclava1
660 private fun toSlashFormat(typeName: String): String {
661 var name = typeName
662 var dimension = ""
663 while (name.endsWith("[]")) {
664 dimension += "["
665 name = name.substring(0, name.length - 2)
666 }
667
668 val base: String
669 base =
670 when (name) {
671 "void" -> "V"
672 "byte" -> "B"
673 "boolean" -> "Z"
674 "char" -> "C"
675 "short" -> "S"
676 "int" -> "I"
677 "long" -> "J"
678 "float" -> "F"
679 "double" -> "D"
680 else -> "L" + getInternalName(name) + ";"
681 }
682
683 return dimension + base
684 }
685
686 /**
687 * Computes the internal class name of the given fully qualified class name. For example, it
688 * converts foo.bar.Foo.Bar into foo/bar/Foo$Bar
689 *
690 * @param qualifiedName the fully qualified class name
691 * @return the internal class name
692 */
693 private fun getInternalName(qualifiedName: String): String {
694 if (qualifiedName.indexOf('.') == -1) {
695 return qualifiedName
696 }
697
698 // If class name contains $, it's not an ambiguous nested class name.
699 if (qualifiedName.indexOf('$') != -1) {
700 return qualifiedName.replace('.', '/')
701 }
702 // Let's assume that components that start with Caps are class names.
703 return buildString {
704 var prev: String? = null
705 for (part in qualifiedName.split(".")) {
706 if (!prev.isNullOrEmpty()) {
707 if (Character.isUpperCase(prev[0])) {
708 append('$')
709 } else {
710 append('/')
711 }
712 }
713 append(part)
714 prev = part
715 }
716 }
717 }
718 }
719 }
720
721 /**
722 * Configuration options for how to represent a type as a string.
723 *
724 * @param annotations Whether to include annotations on the type.
725 * @param eraseGenerics If `true` then type parameters are ignored and type variables are replaced
726 * with the upper bound of the type parameter.
727 * @param kotlinStyleNulls Whether to represent nullability with Kotlin-style suffixes: `?` for
728 * nullable, no suffix for non-null, and `!` for platform nullability. For example, the Java type
729 * `@Nullable List<String>` would be represented as `List<String!>?`.
730 * @param nestedClassSeparator The character that is used to separate a nested class from its
731 * containing class.
732 * @param spaceBetweenTypeArguments Whether to include a space between type arguments of a generic
733 * type.
734 * @param stripJavaLangPrefix Controls how `java.lang.` prefixes are removed from the types.
735 * @param treatVarargsAsArray If `false` then a varargs type will use `...` to indicate that it is a
736 * varargs type, otherwise it will use `[]` like a normal array.
737 */
738 data class TypeStringConfiguration(
739 val annotations: Boolean = false,
740 val eraseGenerics: Boolean = false,
741 val kotlinStyleNulls: Boolean = false,
742 val nestedClassSeparator: Char = '.',
743 val spaceBetweenTypeArguments: Boolean = false,
744 val stripJavaLangPrefix: StripJavaLangPrefix = StripJavaLangPrefix.NEVER,
745 val treatVarargsAsArray: Boolean = false,
746 ) {
747 /**
748 * Check to see if this matches [DEFAULT].
749 *
750 * This is computed lazily to avoid the comparison against [DEFAULT] being done while creating
751 * the instance to assign to [DEFAULT] at which point [DEFAULT] would be `null`.
752 */
<lambda>null753 val isDefault by lazy(LazyThreadSafetyMode.NONE) { this == DEFAULT }
754
755 companion object {
756 /** The default[TypeStringConfiguration]. */
757 val DEFAULT: TypeStringConfiguration = TypeStringConfiguration()
758 }
759 }
760
761 /**
762 * The type for [ClassTypeItem.arguments].
763 *
764 * See https://docs.oracle.com/javase/specs/jls/se8/html/jls-4.html#jls-TypeArgument.
765 */
766 interface TypeArgumentTypeItem : TypeItem {
767 /** Override to specialize the return type. */
convertTypenull768 override fun convertType(typeParameterBindings: TypeParameterBindings): TypeArgumentTypeItem
769
770 /** Override to specialize the return type. */
771 override fun substitute(modifiers: TypeModifiers): TypeArgumentTypeItem
772
773 /** Override to specialize the return type. */
774 override fun transform(transformer: TypeTransformer): TypeArgumentTypeItem
775 }
776
777 /**
778 * The type for a reference.
779 *
780 * See https://docs.oracle.com/javase/specs/jls/se8/html/jls-4.html#jls-ReferenceType.
781 */
782 interface ReferenceTypeItem : TypeItem, TypeArgumentTypeItem {
783 /** Override to specialize the return type. */
784 override fun convertType(typeParameterBindings: TypeParameterBindings): ReferenceTypeItem
785
786 /** Override to specialize the return type. */
787 override fun substitute(modifiers: TypeModifiers): ReferenceTypeItem
788
789 /** Override to specialize the return type. */
790 override fun transform(transformer: TypeTransformer): ReferenceTypeItem
791 }
792
793 /**
794 * The type of [TypeParameterItem]'s type bounds.
795 *
796 * See https://docs.oracle.com/javase/specs/jls/se8/html/jls-4.html#jls-TypeBound
797 */
798 interface BoundsTypeItem : TypeItem, ReferenceTypeItem
799
800 /**
801 * The type of [MethodItem.throwsTypes]'s.
802 *
803 * See https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-ExceptionType.
804 */
805 sealed interface ExceptionTypeItem : TypeItem, ReferenceTypeItem {
806 /** Override to specialize the return type. */
transformnull807 override fun transform(transformer: TypeTransformer): ExceptionTypeItem
808
809 /**
810 * Get the erased [ClassItem], if any.
811 *
812 * The erased [ClassItem] is the one which would be used by Java at runtime after the generic
813 * types have been erased. This will cause an error if it is called on a [VariableTypeItem]
814 * whose [TypeParameterItem]'s upper bound is not a [ExceptionTypeItem]. However, that should
815 * never happen as it would be a compile time error.
816 */
817 val erasedClass: ClassItem?
818
819 /**
820 * The best guess of the full name, i.e. the qualified class name without the package but
821 * including the outer class names.
822 *
823 * This is not something that can be accurately determined solely by examining the reference or
824 * even the import as there is no distinction made between a package name and a class name. Java
825 * naming conventions do say that package names should start with a lower case letter and class
826 * names should start with an upper case letter, but they are not enforced so cannot be fully
827 * relied upon.
828 *
829 * It is possible that in some contexts a model could provide a better full name than guessing
830 * from the fully qualified name, e.g. a reference within the same package, however that is not
831 * something that will be supported by all models and so attempting to use that could lead to
832 * subtle model differences that could break users of the models.
833 *
834 * The only way to fully determine the full name is to resolve the class and extract it from
835 * there but this avoids resolving a class as it can be expensive. Instead, this just makes the
836 * best guess assuming normal Java conventions.
837 */
838 @Deprecated(
839 "Do not use as full name is only ever a best guess based on naming conventions; use the full type string instead",
840 ReplaceWith("toTypeString()")
841 )
842 fun fullName(): String = bestGuessAtFullName(toTypeString())
843
844 companion object {
845 /** A partial ordering over [ExceptionTypeItem] comparing [ExceptionTypeItem] full names. */
846 val fullNameComparator: Comparator<ExceptionTypeItem> =
847 Comparator.comparing { @Suppress("DEPRECATION") it.fullName() }
848 }
849 }
850
851 /** Represents a primitive type, like int or boolean. */
852 interface PrimitiveTypeItem : TypeItem {
853 /** The kind of [Primitive] this type is. */
854 val kind: Primitive
855
856 /** The possible kinds of primitives. */
857 enum class Primitive(
858 val primitiveName: String,
859 val defaultValue: Any?,
860 val defaultValueString: String,
861 val wrapperClass: Class<*>,
862 ) {
863 BOOLEAN(
864 primitiveName = "boolean",
865 defaultValue = false,
866 defaultValueString = "false",
867 wrapperClass = java.lang.Boolean::class.java,
868 ),
869 BYTE(
870 primitiveName = "byte",
871 defaultValue = 0.toByte(),
872 defaultValueString = "0",
873 wrapperClass = java.lang.Byte::class.java,
874 ),
875 CHAR(
876 primitiveName = "char",
877 defaultValue = 0.toChar(),
878 defaultValueString = "0",
879 wrapperClass = java.lang.Character::class.java,
880 ),
881 DOUBLE(
882 primitiveName = "double",
883 defaultValue = 0.0,
884 defaultValueString = "0",
885 wrapperClass = java.lang.Double::class.java,
886 ),
887 FLOAT(
888 primitiveName = "float",
889 defaultValue = 0F,
890 defaultValueString = "0",
891 wrapperClass = java.lang.Float::class.java,
892 ),
893 INT(
894 primitiveName = "int",
895 defaultValue = 0,
896 defaultValueString = "0",
897 wrapperClass = java.lang.Integer::class.java,
898 ),
899 LONG(
900 primitiveName = "long",
901 defaultValue = 0L,
902 defaultValueString = "0",
903 wrapperClass = java.lang.Long::class.java,
904 ),
905 SHORT(
906 primitiveName = "short",
907 defaultValue = 0.toShort(),
908 defaultValueString = "0",
909 wrapperClass = java.lang.Short::class.java,
910 ),
911 VOID(
912 primitiveName = "void",
913 defaultValue = null,
914 defaultValueString = "null",
915 wrapperClass = java.lang.Void::class.java,
916 )
917 }
918
defaultValuenull919 override fun defaultValue(): Any? = kind.defaultValue
920
921 override fun defaultValueString(): String = kind.defaultValueString
922
923 override fun accept(visitor: TypeVisitor) {
924 visitor.visit(this)
925 }
926
acceptnull927 override fun accept(visitor: MultipleTypeVisitor, other: List<TypeItem>) {
928 visitor.visit(this, other)
929 }
930
931 @Deprecated(
932 "implementation detail of this class",
933 replaceWith = ReplaceWith("substitute(modifiers)"),
934 )
duplicatenull935 fun duplicate(modifiers: TypeModifiers): PrimitiveTypeItem
936
937 override fun substitute(modifiers: TypeModifiers): PrimitiveTypeItem =
938 if (modifiers !== this.modifiers) @Suppress("DEPRECATION") duplicate(modifiers) else this
939
940 override fun convertType(typeParameterBindings: TypeParameterBindings): PrimitiveTypeItem {
941 // Primitive type is never affected by a type mapping so always return this.
942 return this
943 }
944
transformnull945 override fun transform(transformer: TypeTransformer): PrimitiveTypeItem {
946 return transformer.transform(this)
947 }
948
equalToTypenull949 override fun equalToType(other: TypeItem?): Boolean {
950 return (other as? PrimitiveTypeItem)?.kind == kind
951 }
952
hashCodeForTypenull953 override fun hashCodeForType(): Int = kind.hashCode()
954
955 override fun asClass(): ClassItem? = null
956 }
957
958 /** Represents an array type, including vararg types. */
959 interface ArrayTypeItem : TypeItem, ReferenceTypeItem {
960 /** The array's inner type (which for multidimensional arrays is another array type). */
961 val componentType: TypeItem
962
963 /** Whether this array type represents a varargs parameter. */
964 val isVarargs: Boolean
965
966 override fun accept(visitor: TypeVisitor) {
967 visitor.visit(this)
968 }
969
970 override fun accept(visitor: MultipleTypeVisitor, other: List<TypeItem>) {
971 visitor.visit(this, other)
972 }
973
974 /**
975 * Duplicates this type substituting in the provided [modifiers] and [componentType] in place of
976 * this instance's [modifiers] and [componentType].
977 */
978 @Deprecated(
979 "implementation detail of this class",
980 replaceWith = ReplaceWith("substitute(modifiers, componentType)"),
981 )
982 fun duplicate(modifiers: TypeModifiers, componentType: TypeItem): ArrayTypeItem
983
984 override fun substitute(modifiers: TypeModifiers): ArrayTypeItem =
985 substitute(modifiers, componentType)
986
987 /**
988 * Return an [ArrayTypeItem] instance identical to this one except its [TypeItem.modifiers] and
989 * [ArrayTypeItem.componentType] properties are the same as the [modifiers] and [componentType]
990 * parameters respectively.
991 *
992 * If the parameters are the same as this instance's properties then it will just return this
993 * instance, otherwise it will return a new instance.
994 */
995 fun substitute(
996 modifiers: TypeModifiers = this.modifiers,
997 componentType: TypeItem = this.componentType,
998 ) =
999 if (modifiers !== this.modifiers || componentType !== this.componentType)
1000 @Suppress("DEPRECATION") duplicate(modifiers, componentType)
1001 else this
1002
1003 override fun convertType(typeParameterBindings: TypeParameterBindings): ArrayTypeItem {
1004 return substitute(
1005 componentType = componentType.convertType(typeParameterBindings),
1006 )
1007 }
1008
1009 override fun transform(transformer: TypeTransformer): ArrayTypeItem {
1010 return transformer.transform(this)
1011 }
1012
1013 override fun equalToType(other: TypeItem?): Boolean {
1014 if (other !is ArrayTypeItem) return false
1015 return isVarargs == other.isVarargs && componentType.equalToType(other.componentType)
1016 }
1017
1018 override fun hashCodeForType(): Int = Objects.hash(isVarargs, componentType)
1019
1020 override fun asClass(): ClassItem? = componentType.asClass()
1021 }
1022
1023 /** Represents a class type. */
1024 interface ClassTypeItem : TypeItem, BoundsTypeItem, ReferenceTypeItem, ExceptionTypeItem {
1025 /** The qualified name of this class, e.g. "java.lang.String". */
1026 val qualifiedName: String
1027
1028 /**
1029 * The class type's arguments, empty if it has none.
1030 *
1031 * i.e. The specific types that this class type assigns to each of the referenced [ClassItem]'s
1032 * type parameters.
1033 */
1034 val arguments: List<TypeArgumentTypeItem>
1035
1036 /** The outer class type of this class, if it is a nested type. */
1037 val outerClassType: ClassTypeItem?
1038
1039 /**
1040 * The name of the class, e.g. "String" for "java.lang.String" and "Inner" for
1041 * "test.pkg.Outer.Inner".
1042 */
1043 val className: String
1044
1045 /**
1046 * Get the class name prefix, i.e. the part before [className] and after which type use
1047 * annotations, if any will appear.
1048 *
1049 * e.g. for `java.lang.String`, [className] is `String` and the prefix is `java.lang.`. For
1050 * `java.util.Map.Entry` [className] is `Entry` and the prefix is `java.util.Map.`.
1051 *
1052 * This is the value such that [classNamePrefix] + [className] == [qualifiedName].
1053 */
1054 val classNamePrefix: String
1055 get() {
1056 val classNamePrefixEnd = qualifiedName.length - className.length
1057 return qualifiedName.substring(0, classNamePrefixEnd)
1058 }
1059
1060 override val erasedClass: ClassItem?
1061 get() = asClass()
1062
acceptnull1063 override fun accept(visitor: TypeVisitor) {
1064 visitor.visit(this)
1065 }
1066
acceptnull1067 override fun accept(visitor: MultipleTypeVisitor, other: List<TypeItem>) {
1068 visitor.visit(this, other)
1069 }
1070
1071 /**
1072 * Check to see whether this type has any type arguments.
1073 *
1074 * It will return `true` for say `List<T>`, but `false` for `String`.
1075 */
hasTypeArgumentsnull1076 fun hasTypeArguments() = arguments.isNotEmpty()
1077
1078 override fun isString(): Boolean = qualifiedName == JAVA_LANG_STRING
1079
1080 override fun isJavaLangObject(): Boolean = qualifiedName == JAVA_LANG_OBJECT
1081
1082 /**
1083 * Check to see whether this type is a functional type, i.e. references a function interface,
1084 * which is an interface with at most one abstract method.
1085 */
1086 fun isFunctionalType(): Boolean = error("unsupported")
1087
1088 /**
1089 * Duplicates this type substituting in the provided [modifiers], [outerClassType] and
1090 * [arguments] in place of this instance's [modifiers], [outerClassType] and [arguments].
1091 */
1092 @Deprecated(
1093 "implementation detail of this class",
1094 replaceWith = ReplaceWith("substitute(modifiers, outerClassType, arguments)"),
1095 )
1096 fun duplicate(
1097 modifiers: TypeModifiers,
1098 outerClassType: ClassTypeItem?,
1099 arguments: List<TypeArgumentTypeItem>,
1100 ): ClassTypeItem
1101
1102 override fun substitute(modifiers: TypeModifiers): ClassTypeItem =
1103 substitute(modifiers, outerClassType, arguments)
1104
1105 /**
1106 * Return a [ClassTypeItem] instance identical to this one except its [TypeItem.modifiers],
1107 * [ClassTypeItem.outerClassType] and [ClassTypeItem.arguments] properties are the same as the
1108 * [modifiers], [outerClassType] and [arguments] parameters respectively.
1109 *
1110 * If the parameters are the same as this instance's properties then it will just return this
1111 * instance, otherwise it will return a new instance.
1112 */
1113 fun substitute(
1114 modifiers: TypeModifiers = this.modifiers,
1115 outerClassType: ClassTypeItem? = this.outerClassType,
1116 arguments: List<TypeArgumentTypeItem> = this.arguments,
1117 ) =
1118 if (
1119 modifiers !== this.modifiers ||
1120 outerClassType !== this.outerClassType ||
1121 arguments !== this.arguments
1122 )
1123 @Suppress("DEPRECATION") duplicate(modifiers, outerClassType, arguments)
1124 else this
1125
1126 override fun convertType(typeParameterBindings: TypeParameterBindings): ClassTypeItem {
1127 return substitute(
1128 outerClassType = outerClassType?.convertType(typeParameterBindings),
1129 arguments = arguments.mapIfNotSame { it.convertType(typeParameterBindings) },
1130 )
1131 }
1132
transformnull1133 override fun transform(transformer: TypeTransformer): ClassTypeItem {
1134 return transformer.transform(this)
1135 }
1136
equalToTypenull1137 override fun equalToType(other: TypeItem?): Boolean {
1138 if (other !is ClassTypeItem) return false
1139 return qualifiedName == other.qualifiedName &&
1140 arguments.size == other.arguments.size &&
1141 arguments.zip(other.arguments).all { (p1, p2) -> p1.equalToType(p2) } &&
1142 ((outerClassType == null && other.outerClassType == null) ||
1143 outerClassType?.equalToType(other.outerClassType) == true)
1144 }
1145
hashCodeForTypenull1146 override fun hashCodeForType(): Int = Objects.hash(qualifiedName, outerClassType, arguments)
1147
1148 companion object {
1149 /** Computes the simple name of a class from a qualified class name. */
1150 fun computeClassName(qualifiedName: String): String {
1151 val lastDotIndex = qualifiedName.lastIndexOf('.')
1152 return if (lastDotIndex == -1) {
1153 qualifiedName
1154 } else {
1155 qualifiedName.substring(lastDotIndex + 1)
1156 }
1157 }
1158 }
1159 }
1160
1161 /**
1162 * Represents a kotlin lambda type.
1163 *
1164 * This extends [ClassTypeItem] out of necessity because that is how lambdas have been represented
1165 * in Metalava up until this was created and so until such time as all the code that consumes this
1166 * has been updated to handle lambdas specifically it will need to remain a [ClassTypeItem].
1167 */
1168 interface LambdaTypeItem : ClassTypeItem {
1169 /** True if the lambda is a suspend function, false otherwise. */
1170 val isSuspend: Boolean
1171
1172 /** The type of the optional receiver. */
1173 val receiverType: TypeItem?
1174
1175 /** The parameter types. */
1176 val parameterTypes: List<TypeItem>
1177
1178 /** The return type. */
1179 val returnType: TypeItem
1180
1181 @Deprecated(
1182 "implementation detail of this class",
1183 replaceWith = ReplaceWith("substitute(modifiers, outerClassType, arguments)")
1184 )
duplicatenull1185 override fun duplicate(
1186 modifiers: TypeModifiers,
1187 outerClassType: ClassTypeItem?,
1188 arguments: List<TypeArgumentTypeItem>,
1189 ): LambdaTypeItem
1190
1191 override fun substitute(modifiers: TypeModifiers): LambdaTypeItem =
1192 substitute(modifiers, outerClassType, arguments)
1193
1194 /** Override to specialize the return type. */
1195 override fun substitute(
1196 modifiers: TypeModifiers,
1197 outerClassType: ClassTypeItem?,
1198 arguments: List<TypeArgumentTypeItem>
1199 ) = super.substitute(modifiers, outerClassType, arguments) as LambdaTypeItem
1200
1201 override fun transform(transformer: TypeTransformer): LambdaTypeItem {
1202 return transformer.transform(this)
1203 }
1204 }
1205
1206 /** Represents a type variable type. */
1207 interface VariableTypeItem : TypeItem, BoundsTypeItem, ReferenceTypeItem, ExceptionTypeItem {
1208 /** The name of the type variable */
1209 val name: String
1210
1211 /** The corresponding type parameter for this type variable. */
1212 val asTypeParameter: TypeParameterItem
1213
1214 override val erasedClass: ClassItem?
1215 get() = (asTypeParameter.asErasedType() as ClassTypeItem).erasedClass
1216
descriptionnull1217 override fun description() =
1218 "$name (extends ${this.asTypeParameter.asErasedType()?.description() ?: "unknown type"})}"
1219
1220 override fun accept(visitor: TypeVisitor) {
1221 visitor.visit(this)
1222 }
1223
acceptnull1224 override fun accept(visitor: MultipleTypeVisitor, other: List<TypeItem>) {
1225 visitor.visit(this, other)
1226 }
1227
1228 @Deprecated(
1229 "implementation detail of this class",
1230 replaceWith = ReplaceWith("substitute(modifiers)")
1231 )
duplicatenull1232 fun duplicate(modifiers: TypeModifiers): VariableTypeItem
1233
1234 override fun substitute(modifiers: TypeModifiers): VariableTypeItem =
1235 if (modifiers !== this.modifiers) @Suppress("DEPRECATION") duplicate(modifiers) else this
1236
1237 override fun convertType(typeParameterBindings: TypeParameterBindings): ReferenceTypeItem {
1238 val nullability = modifiers.nullability
1239 return typeParameterBindings[asTypeParameter]?.let { replacement ->
1240 val replacementNullability =
1241 when {
1242 // If this use of the type parameter is marked as nullable, then it overrides
1243 // the nullability of the substituted type.
1244 nullability == TypeNullability.NULLABLE -> nullability
1245 // If the type that is replacing the type parameter has platform nullability,
1246 // i.e. carries no information one way or another about whether it is nullable,
1247 // then use the nullability of the use of the type parameter as while at worst
1248 // it may also have no nullability information, it could have some, e.g. from a
1249 // declaration nullability annotation.
1250 replacement.modifiers.nullability == TypeNullability.PLATFORM -> nullability
1251 else -> null
1252 }
1253
1254 if (replacementNullability == null) {
1255 replacement
1256 } else {
1257 replacement.substitute(replacementNullability) as ReferenceTypeItem
1258 }
1259 }
1260 ?:
1261 // The type parameter binding does not contain a replacement for this variable so use
1262 // this as is.
1263 this
1264 }
1265
transformnull1266 override fun transform(transformer: TypeTransformer): VariableTypeItem {
1267 return transformer.transform(this)
1268 }
1269
asClassnull1270 override fun asClass() = asTypeParameter.asErasedType()?.asClass()
1271
1272 override fun equalToType(other: TypeItem?): Boolean {
1273 return (other as? VariableTypeItem)?.name == name
1274 }
1275
hashCodeForTypenull1276 override fun hashCodeForType(): Int = name.hashCode()
1277 }
1278
1279 /**
1280 * Represents a wildcard type, like `?`, `? extends String`, and `? super String` in Java, or `*`,
1281 * `out String`, and `in String` in Kotlin.
1282 */
1283 interface WildcardTypeItem : TypeItem, TypeArgumentTypeItem {
1284 /** The type this wildcard must extend. If null, the extends bound is implicitly `Object`. */
1285 val extendsBound: ReferenceTypeItem?
1286
1287 /** The type this wildcard must be a super class of. */
1288 val superBound: ReferenceTypeItem?
1289
1290 override fun accept(visitor: TypeVisitor) {
1291 visitor.visit(this)
1292 }
1293
1294 override fun accept(visitor: MultipleTypeVisitor, other: List<TypeItem>) {
1295 visitor.visit(this, other)
1296 }
1297
1298 /**
1299 * Duplicates this type substituting in the provided [modifiers], [extendsBound] and
1300 * [superBound] in place of this instance's [modifiers], [extendsBound] and [superBound].
1301 */
1302 @Deprecated(
1303 "implementation detail of this class",
1304 replaceWith = ReplaceWith("substitute(modifiers, extendsBound, superBound)"),
1305 )
1306 fun duplicate(
1307 modifiers: TypeModifiers,
1308 extendsBound: ReferenceTypeItem?,
1309 superBound: ReferenceTypeItem?,
1310 ): WildcardTypeItem
1311
1312 override fun substitute(modifiers: TypeModifiers): WildcardTypeItem =
1313 substitute(modifiers, extendsBound, superBound)
1314
1315 /**
1316 * Return a [WildcardTypeItem] instance identical to this one except its [TypeItem.modifiers],
1317 * [WildcardTypeItem.extendsBound] and [WildcardTypeItem.superBound] properties are the same as
1318 * the [modifiers], [extendsBound] and [superBound] parameters respectively.
1319 *
1320 * If the parameters are the same as this instance's properties then it will just return this
1321 * instance, otherwise it will return a new instance.
1322 */
1323 fun substitute(
1324 modifiers: TypeModifiers = this.modifiers,
1325 extendsBound: ReferenceTypeItem? = this.extendsBound,
1326 superBound: ReferenceTypeItem? = this.superBound,
1327 ) =
1328 if (
1329 modifiers !== this.modifiers ||
1330 extendsBound !== this.extendsBound ||
1331 superBound !== this.superBound
1332 )
1333 @Suppress("DEPRECATION") duplicate(modifiers, extendsBound, superBound)
1334 else this
1335
1336 override fun convertType(typeParameterBindings: TypeParameterBindings): WildcardTypeItem {
1337 return substitute(
1338 modifiers,
1339 extendsBound?.convertType(typeParameterBindings),
1340 superBound?.convertType(typeParameterBindings)
1341 )
1342 }
1343
1344 override fun transform(transformer: TypeTransformer): WildcardTypeItem {
1345 return transformer.transform(this)
1346 }
1347
1348 override fun equalToType(other: TypeItem?): Boolean {
1349 if (other !is WildcardTypeItem) return false
1350 return extendsBound?.equalToType(other.extendsBound) != false &&
1351 superBound?.equalToType(other.superBound) != false
1352 }
1353
1354 override fun hashCodeForType(): Int = Objects.hash(extendsBound, superBound)
1355
1356 override fun asClass(): ClassItem? = null
1357 }
1358
1359 /**
1360 * Create a [TypeTransformer] that will remove any type annotations for which [filter] returns false
1361 * when called against the [AnnotationItem]'s [ClassItem] return by [AnnotationItem.resolve]. If
1362 * that returns `null` then the [AnnotationItem] will be kept.
1363 */
typeUseAnnotationFilternull1364 fun typeUseAnnotationFilter(filter: FilterPredicate): TypeTransformer =
1365 object : BaseTypeTransformer() {
1366 override fun transform(modifiers: TypeModifiers): TypeModifiers {
1367 if (modifiers.annotations.isEmpty()) return modifiers
1368 return modifiers.substitute(
1369 annotations =
1370 modifiers.annotations.filter { annotationItem ->
1371 // If the annotation cannot be resolved then keep it.
1372 val annotationClass = annotationItem.resolve() ?: return@filter true
1373 filter.test(annotationClass)
1374 }
1375 )
1376 }
1377 }
1378
1379 /**
1380 * Map the items in this list to a new list if [transform] returns at least one item which is not
1381 * the same instance as its input, otherwise return this.
1382 */
mapIfNotSamenull1383 fun <T> List<T>.mapIfNotSame(transform: (T) -> T): List<T> {
1384 if (isEmpty()) return this
1385 val newList = map(transform)
1386 val i1 = iterator()
1387 val i2 = newList.iterator()
1388 while (i1.hasNext() && i2.hasNext()) {
1389 val t1 = i1.next()
1390 val t2 = i2.next()
1391 if (t1 !== t2) return newList
1392 }
1393 return this
1394 }
1395
1396 /**
1397 * Attempt to get the full name from the qualified name.
1398 *
1399 * The full name is the qualified name without the package including any outer class names.
1400 *
1401 * It relies on the convention that packages start with a lower case letter and classes start with
1402 * an upper case letter.
1403 */
bestGuessAtFullNamenull1404 fun bestGuessAtFullName(qualifiedName: String): String {
1405 val length = qualifiedName.length
1406 var prev: Char? = null
1407 var lastDotIndex = -1
1408 for (i in 0..length - 1) {
1409 val c = qualifiedName[i]
1410 if (prev == null || prev == '.') {
1411 if (c.isUpperCase()) {
1412 return qualifiedName.substring(i)
1413 }
1414 }
1415 if (c == '.') {
1416 lastDotIndex = i
1417 }
1418 prev = c
1419 }
1420
1421 return if (lastDotIndex == -1) {
1422 qualifiedName
1423 } else {
1424 qualifiedName.substring(lastDotIndex + 1)
1425 }
1426 }
1427