• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * 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.psi
18 
19 import com.android.tools.lint.detector.api.getInternalName
20 import com.android.tools.metalava.JAVA_LANG_STRING
21 import com.android.tools.metalava.model.AnnotationItem
22 import com.android.tools.metalava.model.ClassItem
23 import com.android.tools.metalava.model.Item
24 import com.android.tools.metalava.model.MemberItem
25 import com.android.tools.metalava.model.MethodItem
26 import com.android.tools.metalava.model.ParameterItem
27 import com.android.tools.metalava.model.TypeItem
28 import com.android.tools.metalava.model.TypeParameterItem
29 import com.intellij.psi.JavaTokenType
30 import com.intellij.psi.PsiArrayType
31 import com.intellij.psi.PsiCapturedWildcardType
32 import com.intellij.psi.PsiClass
33 import com.intellij.psi.PsiClassType
34 import com.intellij.psi.PsiCompiledElement
35 import com.intellij.psi.PsiDisjunctionType
36 import com.intellij.psi.PsiElement
37 import com.intellij.psi.PsiEllipsisType
38 import com.intellij.psi.PsiIntersectionType
39 import com.intellij.psi.PsiJavaCodeReferenceElement
40 import com.intellij.psi.PsiJavaToken
41 import com.intellij.psi.PsiLambdaExpressionType
42 import com.intellij.psi.PsiPrimitiveType
43 import com.intellij.psi.PsiRecursiveElementVisitor
44 import com.intellij.psi.PsiReferenceList
45 import com.intellij.psi.PsiType
46 import com.intellij.psi.PsiTypeElement
47 import com.intellij.psi.PsiTypeParameter
48 import com.intellij.psi.PsiTypeParameterList
49 import com.intellij.psi.PsiTypeVisitor
50 import com.intellij.psi.PsiWildcardType
51 import com.intellij.psi.util.PsiTypesUtil
52 import com.intellij.psi.util.TypeConversionUtil
53 import java.util.function.Predicate
54 
55 /** Represents a type backed by PSI */
56 class PsiTypeItem private constructor(
57     private val codebase: PsiBasedCodebase,
58     var psiType: PsiType
59 ) : TypeItem {
60     private var toString: String? = null
61     private var toAnnotatedString: String? = null
62     private var toInnerAnnotatedString: String? = null
63     private var toErasedString: String? = null
64     private var asClass: PsiClassItem? = null
65 
toStringnull66     override fun toString(): String {
67         return toTypeString()
68     }
69 
toTypeStringnull70     override fun toTypeString(
71         outerAnnotations: Boolean,
72         innerAnnotations: Boolean,
73         erased: Boolean,
74         kotlinStyleNulls: Boolean,
75         context: Item?,
76         filter: Predicate<Item>?
77     ): String {
78         assert(innerAnnotations || !outerAnnotations) // Can't supply outer=true,inner=false
79 
80         if (filter != null) {
81             // No caching when specifying filter.
82             // TODO: When we support type use annotations, here we need to deal with markRecent
83             //  and clearAnnotations not really having done their job.
84             return toTypeString(
85                 codebase = codebase,
86                 type = psiType,
87                 outerAnnotations = outerAnnotations,
88                 innerAnnotations = innerAnnotations,
89                 erased = erased,
90                 kotlinStyleNulls = kotlinStyleNulls,
91                 context = context,
92                 filter = filter
93             )
94         }
95 
96         return if (erased) {
97             if (kotlinStyleNulls && (innerAnnotations || outerAnnotations)) {
98                 // Not cached: Not common
99                 toTypeString(
100                     codebase = codebase,
101                     type = psiType,
102                     outerAnnotations = outerAnnotations,
103                     innerAnnotations = innerAnnotations,
104                     erased = erased,
105                     kotlinStyleNulls = kotlinStyleNulls,
106                     context = context,
107                     filter = filter
108                 )
109             } else {
110                 if (toErasedString == null) {
111                     toErasedString = toTypeString(
112                         codebase = codebase,
113                         type = psiType,
114                         outerAnnotations = outerAnnotations,
115                         innerAnnotations = innerAnnotations,
116                         erased = erased,
117                         kotlinStyleNulls = kotlinStyleNulls,
118                         context = context,
119                         filter = filter
120                     )
121                 }
122                 toErasedString!!
123             }
124         } else {
125             when {
126                 kotlinStyleNulls && outerAnnotations -> {
127                     if (toAnnotatedString == null) {
128                         toAnnotatedString = toTypeString(
129                             codebase = codebase,
130                             type = psiType,
131                             outerAnnotations = outerAnnotations,
132                             innerAnnotations = innerAnnotations,
133                             erased = erased,
134                             kotlinStyleNulls = kotlinStyleNulls,
135                             context = context,
136                             filter = filter
137                         )
138                     }
139                     toAnnotatedString!!
140                 }
141                 kotlinStyleNulls && innerAnnotations -> {
142                     if (toInnerAnnotatedString == null) {
143                         toInnerAnnotatedString = toTypeString(
144                             codebase = codebase,
145                             type = psiType,
146                             outerAnnotations = outerAnnotations,
147                             innerAnnotations = innerAnnotations,
148                             erased = erased,
149                             kotlinStyleNulls = kotlinStyleNulls,
150                             context = context,
151                             filter = filter
152                         )
153                     }
154                     toInnerAnnotatedString!!
155                 }
156                 else -> {
157                     if (toString == null) {
158                         toString = TypeItem.formatType(
159                             getCanonicalText(
160                                 codebase = codebase,
161                                 owner = context,
162                                 type = psiType,
163                                 annotated = false,
164                                 mapAnnotations = false,
165                                 kotlinStyleNulls = kotlinStyleNulls,
166                                 filter = filter
167                             )
168                         )
169                     }
170                     toString!!
171                 }
172             }
173         }
174     }
175 
toErasedTypeStringnull176     override fun toErasedTypeString(context: Item?): String {
177         return toTypeString(
178             outerAnnotations = false,
179             innerAnnotations = false,
180             erased = true,
181             kotlinStyleNulls = false,
182             context = context
183         )
184     }
185 
arrayDimensionsnull186     override fun arrayDimensions(): Int {
187         return psiType.arrayDimensions
188     }
189 
internalNamenull190     override fun internalName(context: Item?): String {
191         if (primitive) {
192             val signature = getPrimitiveSignature(toString())
193             if (signature != null) {
194                 return signature
195             }
196         }
197         val sb = StringBuilder()
198         appendJvmSignature(sb, psiType)
199         return sb.toString()
200     }
201 
equalsnull202     override fun equals(other: Any?): Boolean {
203         if (this === other) return true
204 
205         return when (other) {
206             is TypeItem -> TypeItem.equalsWithoutSpace(toTypeString(), other.toTypeString())
207             else -> false
208         }
209     }
210 
asClassnull211     override fun asClass(): PsiClassItem? {
212         if (primitive) {
213             return null
214         }
215         if (asClass == null) {
216             asClass = codebase.findClass(psiType)
217         }
218         return asClass
219     }
220 
asTypeParameternull221     override fun asTypeParameter(context: MemberItem?): TypeParameterItem? {
222         val cls = asClass() ?: return null
223         return cls as? PsiTypeParameterItem
224     }
225 
hashCodenull226     override fun hashCode(): Int {
227         return psiType.hashCode()
228     }
229 
230     override val primitive: Boolean
231         get() = psiType is PsiPrimitiveType
232 
defaultValuenull233     override fun defaultValue(): Any? {
234         return PsiTypesUtil.getDefaultValue(psiType)
235     }
236 
defaultValueStringnull237     override fun defaultValueString(): String {
238         return PsiTypesUtil.getDefaultValueOfType(psiType)
239     }
240 
typeArgumentClassesnull241     override fun typeArgumentClasses(): List<ClassItem> {
242         if (primitive) {
243             return emptyList()
244         }
245 
246         val classes = mutableListOf<ClassItem>()
247         psiType.accept(object : PsiTypeVisitor<PsiType>() {
248             override fun visitType(type: PsiType): PsiType {
249                 return type
250             }
251 
252             override fun visitClassType(classType: PsiClassType): PsiType? {
253                 codebase.findClass(classType)?.let {
254                     if (!it.isTypeParameter && !classes.contains(it)) {
255                         classes.add(it)
256                     }
257                 }
258                 for (type in classType.parameters) {
259                     type.accept(this)
260                 }
261                 return classType
262             }
263 
264             override fun visitWildcardType(wildcardType: PsiWildcardType): PsiType? {
265                 if (wildcardType.isExtends) {
266                     wildcardType.extendsBound.accept(this)
267                 }
268                 if (wildcardType.isSuper) {
269                     wildcardType.superBound.accept(this)
270                 }
271                 if (wildcardType.isBounded) {
272                     wildcardType.bound?.accept(this)
273                 }
274                 return wildcardType
275             }
276 
277             override fun visitPrimitiveType(primitiveType: PsiPrimitiveType): PsiType? {
278                 return primitiveType
279             }
280 
281             override fun visitEllipsisType(ellipsisType: PsiEllipsisType): PsiType? {
282                 ellipsisType.componentType.accept(this)
283                 return ellipsisType
284             }
285 
286             override fun visitArrayType(arrayType: PsiArrayType): PsiType? {
287                 arrayType.componentType.accept(this)
288                 return arrayType
289             }
290 
291             override fun visitLambdaExpressionType(lambdaExpressionType: PsiLambdaExpressionType): PsiType? {
292                 for (superType in lambdaExpressionType.superTypes) {
293                     superType.accept(this)
294                 }
295                 return lambdaExpressionType
296             }
297 
298             override fun visitCapturedWildcardType(capturedWildcardType: PsiCapturedWildcardType): PsiType? {
299                 capturedWildcardType.upperBound.accept(this)
300                 return capturedWildcardType
301             }
302 
303             override fun visitDisjunctionType(disjunctionType: PsiDisjunctionType): PsiType? {
304                 for (type in disjunctionType.disjunctions) {
305                     type.accept(this)
306                 }
307                 return disjunctionType
308             }
309 
310             override fun visitIntersectionType(intersectionType: PsiIntersectionType): PsiType? {
311                 for (type in intersectionType.conjuncts) {
312                     type.accept(this)
313                 }
314                 return intersectionType
315             }
316         })
317 
318         return classes
319     }
320 
convertTypenull321     override fun convertType(replacementMap: Map<String, String>?, owner: Item?): TypeItem {
322         val s = convertTypeString(replacementMap)
323         return create(codebase, codebase.createPsiType(s, owner?.psi()))
324     }
325 
hasTypeArgumentsnull326     override fun hasTypeArguments(): Boolean {
327         val type = psiType
328         return type is PsiClassType && type.hasParameters()
329     }
330 
markRecentnull331     override fun markRecent() {
332         val source = toTypeString(outerAnnotations = true, innerAnnotations = true)
333             .replace(".NonNull", ".RecentlyNonNull")
334         // TODO: Pass in a context!
335         psiType = codebase.createPsiType(source)
336         toAnnotatedString = null
337         toInnerAnnotatedString = null
338     }
339 
scrubAnnotationsnull340     override fun scrubAnnotations() {
341         toAnnotatedString = toTypeString(outerAnnotations = false, innerAnnotations = false)
342         toInnerAnnotatedString = toAnnotatedString
343     }
344 
345     /**
346      * Returns `true` if `this` type can be assigned from `other` without unboxing the other.
347      */
isAssignableFromWithoutUnboxingnull348     fun isAssignableFromWithoutUnboxing(other: PsiTypeItem): Boolean {
349         if (this.primitive && !other.primitive) {
350             return false
351         }
352         return TypeConversionUtil.isAssignable(psiType, other.psiType)
353     }
354 
355     companion object {
getPrimitiveSignaturenull356         private fun getPrimitiveSignature(typeName: String): String? = when (typeName) {
357             "boolean" -> "Z"
358             "byte" -> "B"
359             "char" -> "C"
360             "short" -> "S"
361             "int" -> "I"
362             "long" -> "J"
363             "float" -> "F"
364             "double" -> "D"
365             "void" -> "V"
366             else -> null
367         }
368 
appendJvmSignaturenull369         private fun appendJvmSignature(
370             buffer: StringBuilder,
371             type: PsiType?
372         ): Boolean {
373             if (type == null) {
374                 return false
375             }
376 
377             when (val psiType = TypeConversionUtil.erasure(type)) {
378                 is PsiArrayType -> {
379                     buffer.append('[')
380                     appendJvmSignature(buffer, psiType.componentType)
381                 }
382                 is PsiClassType -> {
383                     val resolved = psiType.resolve() ?: return false
384                     if (!appendJvmTypeName(buffer, resolved)) {
385                         return false
386                     }
387                 }
388                 is PsiPrimitiveType -> buffer.append(getPrimitiveSignature(psiType.canonicalText))
389                 else -> return false
390             }
391             return true
392         }
393 
appendJvmTypeNamenull394         private fun appendJvmTypeName(
395             signature: StringBuilder,
396             outerClass: PsiClass
397         ): Boolean {
398             val className = getInternalName(outerClass) ?: return false
399             signature.append('L').append(className).append(';')
400             return true
401         }
402 
toTypeStringnull403         fun toTypeString(
404             codebase: PsiBasedCodebase,
405             type: PsiType,
406             outerAnnotations: Boolean,
407             innerAnnotations: Boolean,
408             erased: Boolean,
409             kotlinStyleNulls: Boolean,
410             context: Item?,
411             filter: Predicate<Item>?
412         ): String {
413             if (erased) {
414                 // Recurse with raw type and erase=false
415                 return toTypeString(
416                     codebase,
417                     TypeConversionUtil.erasure(type),
418                     outerAnnotations,
419                     innerAnnotations,
420                     false,
421                     kotlinStyleNulls,
422                     context,
423                     filter
424                 )
425             }
426 
427             val typeString =
428                 if (kotlinStyleNulls && (innerAnnotations || outerAnnotations)) {
429                     try {
430                         getCanonicalText(
431                             codebase = codebase,
432                             owner = context,
433                             type = type,
434                             annotated = true,
435                             mapAnnotations = true,
436                             kotlinStyleNulls = kotlinStyleNulls,
437                             filter = filter
438                         )
439                     } catch (ignore: Throwable) {
440                         type.canonicalText
441                     }
442                 } else {
443                     type.canonicalText
444                 }
445 
446             return TypeItem.formatType(typeString)
447         }
448 
getCanonicalTextnull449         private fun getCanonicalText(
450             codebase: PsiBasedCodebase,
451             owner: Item?,
452             type: PsiType,
453             annotated: Boolean,
454             mapAnnotations: Boolean,
455             kotlinStyleNulls: Boolean,
456             filter: Predicate<Item>?
457         ): String {
458             return try {
459                 if (annotated && kotlinStyleNulls) {
460                     // Any nullness annotations on the element to merge in? When we have something like
461                     //  @Nullable String foo
462                     // the Nullable annotation can be on the element itself rather than the type,
463                     // so if we print the type without knowing the nullness annotation on the
464                     // element, we'll think it's unannotated and we'll display it as "String!".
465                     val nullness = owner?.modifiers?.annotations()?.firstOrNull { it.isNullnessAnnotation() }
466                     var elementAnnotations = if (nullness != null) { listOf(nullness) } else null
467 
468                     val implicitNullness = if (owner != null) AnnotationItem.getImplicitNullness(owner) else null
469                     val annotatedType = if (implicitNullness != null) {
470                         val provider = if (implicitNullness == true) {
471                             codebase.getNullableAnnotationProvider()
472                         } else {
473                             codebase.getNonNullAnnotationProvider()
474                         }
475 
476                         // Special handling for implicitly non-null arrays that also have an
477                         // implicitly non-null component type
478                         if (implicitNullness == false && type is PsiArrayType &&
479                             owner != null && owner.impliesNonNullArrayComponents()
480                         ) {
481                             type.componentType.annotate(provider).createArrayType()
482                                 .annotate(provider)
483                         } else if (implicitNullness == false &&
484                             owner is ParameterItem &&
485                             owner.containingMethod().isEnumSyntheticMethod()
486                         ) {
487                             // Workaround the fact that the Kotlin synthetic enum methods
488                             // do not have nullness information; this must be the parameter
489                             // to the valueOf(String) method.
490                             // See https://youtrack.jetbrains.com/issue/KT-39667.
491                             return JAVA_LANG_STRING
492                         } else {
493                             type.annotate(provider)
494                         }
495                     } else if (nullness != null && owner.modifiers.isVarArg() && owner.isKotlin() && type is PsiEllipsisType) {
496                         // Varargs the annotation applies to the component type instead
497                         val nonNullProvider = codebase.getNonNullAnnotationProvider()
498                         val provider = if (nullness.isNonNull()) {
499                             nonNullProvider
500                         } else codebase.getNullableAnnotationProvider()
501                         val componentType = type.componentType.annotate(provider)
502                         elementAnnotations = null
503                         PsiEllipsisType(componentType, nonNullProvider)
504                     } else {
505                         type
506                     }
507                     val printer = PsiTypePrinter(codebase, filter, mapAnnotations, kotlinStyleNulls)
508 
509                     printer.getAnnotatedCanonicalText(
510                         annotatedType,
511                         elementAnnotations
512                     )
513                 } else if (annotated) {
514                     type.getCanonicalText(true)
515                 } else {
516                     type.getCanonicalText(false)
517                 }
518             } catch (e: Throwable) {
519                 return type.getCanonicalText(false)
520             }
521         }
522 
523         /**
524          * Determine if this item implies that its associated type is a non-null array with
525          * non-null components. This is true for the synthetic `Enum.values()` method and any
526          * annotation properties or accessors.
527          */
Itemnull528         private fun Item.impliesNonNullArrayComponents(): Boolean {
529             return when (this) {
530                 is MemberItem -> containingClass().isAnnotationType() && !modifiers.isStatic()
531                 is MethodItem -> containingClass().isEnum() && modifiers.isStatic() &&
532                     name() == "values" && parameters().isEmpty()
533                 else -> false
534             }
535         }
536 
createnull537         fun create(codebase: PsiBasedCodebase, psiType: PsiType): PsiTypeItem {
538             return PsiTypeItem(codebase, psiType)
539         }
540 
createnull541         fun create(codebase: PsiBasedCodebase, original: PsiTypeItem): PsiTypeItem {
542             return PsiTypeItem(codebase, original.psiType)
543         }
544 
typeParameterListnull545         fun typeParameterList(typeList: PsiTypeParameterList?): String? {
546             if (typeList != null && typeList.typeParameters.isNotEmpty()) {
547                 // TODO: Filter the type list classes? Try to construct a typelist of a private API!
548                 // We can't just use typeList.text here, because that just
549                 // uses the declaration from the source, which may not be
550                 // fully qualified - e.g. we might get
551                 //    <T extends View> instead of <T extends android.view.View>
552                 // Therefore, we'll need to compute it ourselves; I can't find
553                 // a utility for this
554                 val sb = StringBuilder()
555                 typeList.accept(object : PsiRecursiveElementVisitor() {
556                     override fun visitElement(element: PsiElement) {
557                         if (element is PsiTypeParameterList) {
558                             val typeParameters = element.typeParameters
559                             if (typeParameters.isEmpty()) {
560                                 return
561                             }
562                             sb.append("<")
563                             var first = true
564                             for (parameter in typeParameters) {
565                                 if (!first) {
566                                     sb.append(", ")
567                                 }
568                                 first = false
569                                 visitElement(parameter)
570                             }
571                             sb.append(">")
572                             return
573                         } else if (element is PsiTypeParameter) {
574                             if (PsiTypeParameterItem.isReified(element)) {
575                                 sb.append("reified ")
576                             }
577                             sb.append(element.name)
578                             // TODO: How do I get super -- e.g. "Comparable<? super T>"
579                             val extendsList = element.extendsList
580                             val refList = extendsList.referenceElements
581                             if (refList.isNotEmpty()) {
582                                 sb.append(" extends ")
583                                 var first = true
584                                 for (refElement in refList) {
585                                     if (!first) {
586                                         sb.append(" & ")
587                                     } else {
588                                         first = false
589                                     }
590 
591                                     if (refElement is PsiJavaCodeReferenceElement) {
592                                         visitElement(refElement)
593                                         continue
594                                     }
595                                     val resolved = refElement.resolve()
596                                     if (resolved is PsiClass) {
597                                         sb.append(resolved.qualifiedName ?: resolved.name)
598                                         resolved.typeParameterList?.accept(this)
599                                     } else {
600                                         sb.append(refElement.referenceName)
601                                     }
602                                 }
603                             } else {
604                                 val extendsListTypes = element.extendsListTypes
605                                 if (extendsListTypes.isNotEmpty()) {
606                                     sb.append(" extends ")
607                                     var first = true
608                                     for (type in extendsListTypes) {
609                                         if (!first) {
610                                             sb.append(" & ")
611                                         } else {
612                                             first = false
613                                         }
614                                         val resolved = type.resolve()
615                                         if (resolved == null) {
616                                             sb.append(type.className)
617                                         } else {
618                                             sb.append(resolved.qualifiedName ?: resolved.name)
619                                             resolved.typeParameterList?.accept(this)
620                                         }
621                                     }
622                                 }
623                             }
624                             return
625                         } else if (element is PsiJavaCodeReferenceElement) {
626                             val resolved = element.resolve()
627                             if (resolved is PsiClass) {
628                                 if (resolved.qualifiedName == null) {
629                                     sb.append(resolved.name)
630                                 } else {
631                                     sb.append(resolved.qualifiedName)
632                                 }
633                                 val typeParameters = element.parameterList
634                                 if (typeParameters != null) {
635                                     val typeParameterElements = typeParameters.typeParameterElements
636                                     if (typeParameterElements.isEmpty()) {
637                                         return
638                                     }
639 
640                                     // When reading in this from bytecode, the order is sometimes wrong
641                                     // (for example, for
642                                     //    public interface BaseStream<T, S extends BaseStream<T, S>>
643                                     // the extends type BaseStream<T, S> will return the typeParameterElements
644                                     // as [S,T] instead of [T,S]. However, the typeParameters.typeArguments
645                                     // list is correct, so order the elements by the typeArguments array instead
646 
647                                     // Special case: just one type argument: no sorting issue
648                                     if (typeParameterElements.size == 1) {
649                                         sb.append("<")
650                                         var first = true
651                                         for (parameter in typeParameterElements) {
652                                             if (!first) {
653                                                 sb.append(", ")
654                                             }
655                                             first = false
656                                             visitElement(parameter)
657                                         }
658                                         sb.append(">")
659                                         return
660                                     }
661 
662                                     // More than one type argument
663 
664                                     val typeArguments = typeParameters.typeArguments
665                                     if (typeArguments.isNotEmpty()) {
666                                         sb.append("<")
667                                         var first = true
668                                         for (parameter in typeArguments) {
669                                             if (!first) {
670                                                 sb.append(", ")
671                                             }
672                                             first = false
673                                             // Try to match up a type parameter element
674                                             var found = false
675                                             for (typeElement in typeParameterElements) {
676                                                 if (parameter == typeElement.type) {
677                                                     found = true
678                                                     visitElement(typeElement)
679                                                     break
680                                                 }
681                                             }
682                                             if (!found) {
683                                                 // No type element matched: use type instead
684                                                 val classType = PsiTypesUtil.getPsiClass(parameter)
685                                                 if (classType != null) {
686                                                     visitElement(classType)
687                                                 } else {
688                                                     sb.append(parameter.canonicalText)
689                                                 }
690                                             }
691                                         }
692                                         sb.append(">")
693                                     }
694                                 }
695                                 return
696                             }
697                         } else if (element is PsiTypeElement) {
698                             val type = element.type
699                             if (type is PsiWildcardType) {
700                                 sb.append("?")
701                                 if (type.isBounded) {
702                                     if (type.isExtends) {
703                                         sb.append(" extends ")
704                                         sb.append(type.extendsBound.canonicalText)
705                                     }
706                                     if (type.isSuper) {
707                                         sb.append(" super ")
708                                         sb.append(type.superBound.canonicalText)
709                                     }
710                                 }
711                                 return
712                             }
713                             sb.append(type.canonicalText)
714                             return
715                         } else if (element is PsiJavaToken && element.tokenType == JavaTokenType.COMMA) {
716                             sb.append(",")
717                             return
718                         }
719                         if (element.firstChild == null) { // leaf nodes only
720                             if (element is PsiCompiledElement) {
721                                 if (element is PsiReferenceList) {
722                                     val referencedTypes = element.referencedTypes
723                                     var first = true
724                                     for (referenceType in referencedTypes) {
725                                         if (first) {
726                                             first = false
727                                         } else {
728                                             sb.append(", ")
729                                         }
730                                         sb.append(referenceType.canonicalText)
731                                     }
732                                 }
733                             } else {
734                                 sb.append(element.text)
735                             }
736                         }
737                         super.visitElement(element)
738                     }
739                 })
740 
741                 val typeString = sb.toString()
742                 return TypeItem.cleanupGenerics(typeString)
743             }
744 
745             return null
746         }
747 
typeParameterClassesnull748         fun typeParameterClasses(codebase: PsiBasedCodebase, typeList: PsiTypeParameterList?): List<ClassItem> {
749             if (typeList != null && typeList.typeParameters.isNotEmpty()) {
750                 val list = mutableListOf<ClassItem>()
751                 typeList.accept(object : PsiRecursiveElementVisitor() {
752                     override fun visitElement(element: PsiElement) {
753                         if (element is PsiTypeParameterList) {
754                             val typeParameters = element.typeParameters
755                             for (parameter in typeParameters) {
756                                 visitElement(parameter)
757                             }
758                             return
759                         } else if (element is PsiTypeParameter) {
760                             val extendsList = element.extendsList
761                             val refList = extendsList.referenceElements
762                             if (refList.isNotEmpty()) {
763                                 for (refElement in refList) {
764                                     if (refElement is PsiJavaCodeReferenceElement) {
765                                         visitElement(refElement)
766                                         continue
767                                     }
768                                     val resolved = refElement.resolve()
769                                     if (resolved is PsiClass) {
770                                         addRealClass(
771                                             list,
772                                             codebase.findOrCreateClass(resolved)
773                                         )
774                                         resolved.typeParameterList?.accept(this)
775                                     }
776                                 }
777                             } else {
778                                 val extendsListTypes = element.extendsListTypes
779                                 if (extendsListTypes.isNotEmpty()) {
780                                     for (type in extendsListTypes) {
781                                         val resolved = type.resolve()
782                                         if (resolved != null) {
783                                             addRealClass(
784                                                 list, codebase.findOrCreateClass(resolved)
785                                             )
786                                             resolved.typeParameterList?.accept(this)
787                                         }
788                                     }
789                                 }
790                             }
791                             return
792                         } else if (element is PsiJavaCodeReferenceElement) {
793                             val resolved = element.resolve()
794                             if (resolved is PsiClass) {
795                                 addRealClass(
796                                     list,
797                                     codebase.findOrCreateClass(resolved)
798                                 )
799                                 element.parameterList?.accept(this)
800                                 return
801                             }
802                         } else if (element is PsiTypeElement) {
803                             val type = element.type
804                             if (type is PsiWildcardType) {
805                                 if (type.isBounded) {
806                                     addRealClass(
807                                         codebase,
808                                         list, type.bound
809                                     )
810                                 }
811                                 if (type.isExtends) {
812                                     addRealClass(
813                                         codebase,
814                                         list, type.extendsBound
815                                     )
816                                 }
817                                 if (type.isSuper) {
818                                     addRealClass(
819                                         codebase,
820                                         list, type.superBound
821                                     )
822                                 }
823                                 return
824                             }
825                             return
826                         }
827                         super.visitElement(element)
828                     }
829                 })
830 
831                 return list
832             } else {
833                 return emptyList()
834             }
835         }
836 
addRealClassnull837         private fun addRealClass(codebase: PsiBasedCodebase, classes: MutableList<ClassItem>, type: PsiType?) {
838             codebase.findClass(type ?: return)?.let {
839                 addRealClass(classes, it)
840             }
841         }
842 
addRealClassnull843         private fun addRealClass(classes: MutableList<ClassItem>, cls: ClassItem) {
844             if (!cls.isTypeParameter && !classes.contains(cls)) { // typically small number of items, don't need Set
845                 classes.add(cls)
846             }
847         }
848     }
849 }
850