• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2018 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.metalava.JAVA_LANG_OBJECT
20 import com.android.tools.metalava.compatibility
21 import com.android.tools.metalava.model.AnnotationItem
22 import com.android.tools.metalava.model.Codebase
23 import com.android.tools.metalava.model.Item
24 import com.android.tools.metalava.model.SUPPORT_TYPE_USE_ANNOTATIONS
25 import com.android.tools.metalava.model.isNonNullAnnotation
26 import com.android.tools.metalava.model.isNullableAnnotation
27 import com.android.tools.metalava.options
28 import com.intellij.openapi.util.text.StringUtil
29 import com.intellij.psi.PsiAnnotatedJavaCodeReferenceElement
30 import com.intellij.psi.PsiAnnotation
31 import com.intellij.psi.PsiAnonymousClass
32 import com.intellij.psi.PsiArrayType
33 import com.intellij.psi.PsiCapturedWildcardType
34 import com.intellij.psi.PsiClass
35 import com.intellij.psi.PsiDisjunctionType
36 import com.intellij.psi.PsiEllipsisType
37 import com.intellij.psi.PsiFile
38 import com.intellij.psi.PsiIntersectionType
39 import com.intellij.psi.PsiModifier
40 import com.intellij.psi.PsiPackage
41 import com.intellij.psi.PsiPrimitiveType
42 import com.intellij.psi.PsiSubstitutor
43 import com.intellij.psi.PsiType
44 import com.intellij.psi.PsiWildcardType
45 import com.intellij.psi.PsiWildcardType.EXTENDS_PREFIX
46 import com.intellij.psi.PsiWildcardType.SUPER_PREFIX
47 import com.intellij.psi.impl.PsiImplUtil
48 import com.intellij.psi.impl.compiled.ClsJavaCodeReferenceElementImpl
49 import com.intellij.psi.impl.source.PsiClassReferenceType
50 import com.intellij.psi.impl.source.PsiImmediateClassType
51 import com.intellij.psi.impl.source.PsiJavaCodeReferenceElementImpl
52 import com.intellij.psi.impl.source.tree.JavaSourceUtil
53 import com.intellij.psi.impl.source.tree.java.PsiReferenceExpressionImpl
54 import com.intellij.psi.util.PsiTreeUtil
55 import com.intellij.psi.util.PsiUtil
56 import com.intellij.psi.util.PsiUtilCore
57 import java.util.Arrays
58 import java.util.function.Predicate
59 
60 /**
61  * Type printer which can take a [PsiType] and print it to a fully canonical
62  * string, in one of two formats:
63  *  <li>
64  *    <li> Kotlin syntax, e.g. java.lang.Object?
65  *    <li> Java syntax, e.g. java.lang.@androidx.annotation.Nullable Object
66  *  </li>
67  *
68  * The main features of this class relative to PsiType.getCanonicalText(annotated)
69  * is that it can perform filtering (to remove annotations not part of the API)
70  * and Kotlin style printing which cannot be done by simple replacements
71  * of @Nullable->? etc since the annotations and the suffixes appear in different
72  * places.
73  */
74 class PsiTypePrinter(
75     private val codebase: Codebase,
76     private val filter: Predicate<Item>? = null,
77     private val mapAnnotations: Boolean = false,
78     private val kotlinStyleNulls: Boolean = options.outputKotlinStyleNulls,
79     private val supportTypeUseAnnotations: Boolean = SUPPORT_TYPE_USE_ANNOTATIONS
80 ) {
81     // This class inlines a lot of methods from IntelliJ, but with (a) annotated=true, (b) calling local
82     // getCanonicalText methods instead of instance methods, and (c) deferring annotations if kotlinStyleNulls
83     // is true and instead printing it out as a suffix. Dead code paths are also removed.
84 
85     fun getAnnotatedCanonicalText(type: PsiType, elementAnnotations: List<AnnotationItem>? = null): String {
86         return getCanonicalText(type, elementAnnotations)
87     }
88 
89     private fun appendNullnessSuffix(
90         annotations: Array<PsiAnnotation>,
91         sb: StringBuilder,
92         elementAnnotations: List<AnnotationItem>?
93     ) {
94         val nullable = getNullable(annotations, elementAnnotations)
95         appendNullnessSuffix(nullable, sb) // else: non null
96     }
97 
98     private fun appendNullnessSuffix(
99         list: List<PsiAnnotation>?,
100         buffer: StringBuilder,
101         elementAnnotations: List<AnnotationItem>?
102 
103     ) {
104         val nullable: Boolean? = getNullable(list, elementAnnotations)
105         appendNullnessSuffix(nullable, buffer) // else: not null: no suffix
106     }
107 
108     private fun appendNullnessSuffix(nullable: Boolean?, sb: StringBuilder) {
109         if (nullable == true) {
110             sb.append('?')
111         } else if (nullable == null) {
112             sb.append('!')
113         }
114     }
115 
116     private fun getCanonicalText(
117         type: PsiType,
118         elementAnnotations: List<AnnotationItem>?
119     ): String {
120         when (type) {
121             is PsiClassReferenceType -> return getCanonicalText(type, elementAnnotations)
122             is PsiPrimitiveType -> return getCanonicalText(type, elementAnnotations)
123             is PsiImmediateClassType -> return getCanonicalText(type, elementAnnotations)
124             is PsiEllipsisType -> return getText(
125                 type,
126                 getCanonicalText(type.componentType, null),
127                 "..."
128             )
129             is PsiArrayType -> return getCanonicalText(type, elementAnnotations)
130             is PsiWildcardType -> {
131                 val bound = type.bound
132                 // Don't include ! in type bounds
133                 val suffix = if (bound == null) null else getCanonicalText(bound, elementAnnotations).removeSuffix("!")
134                 return getText(type, suffix, elementAnnotations)
135             }
136             is PsiCapturedWildcardType ->
137                 // Based on PsiCapturedWildcardType.getCanonicalText(true)
138                 return getCanonicalText(type.wildcard, elementAnnotations)
139             is PsiDisjunctionType ->
140                 // Based on PsiDisjunctionType.getCanonicalText(true)
141                 return StringUtil.join<PsiType>(type.disjunctions, { psiType ->
142                     getCanonicalText(
143                         psiType,
144                         elementAnnotations
145                     )
146                 }, " | ")
147             is PsiIntersectionType -> return getCanonicalText(type.conjuncts[0], elementAnnotations)
148             else -> return type.getCanonicalText(true)
149         }
150     }
151 
152     // From PsiWildcardType.getText, with qualified always true
153     private fun getText(
154         type: PsiWildcardType,
155         suffix: String?,
156         elementAnnotations: List<AnnotationItem>?
157     ): String {
158         val annotations = type.annotations
159         if (annotations.isEmpty() && suffix == null) return "?"
160 
161         val sb = StringBuilder()
162         appendAnnotations(sb, annotations, elementAnnotations)
163         if (suffix == null) {
164             sb.append('?')
165         } else {
166             if (suffix == JAVA_LANG_OBJECT &&
167                 !compatibility.includeExtendsObjectInWildcard &&
168                 type.isExtends
169             ) {
170                 sb.append('?')
171             } else {
172                 sb.append(if (type.isExtends) EXTENDS_PREFIX else SUPER_PREFIX)
173                 sb.append(suffix)
174             }
175         }
176         return sb.toString()
177     }
178 
179     // From PsiEllipsisType.getText, with qualified always true
180     private fun getText(
181         type: PsiEllipsisType,
182         prefix: String,
183         suffix: String
184     ): String {
185         val sb = StringBuilder(prefix.length + suffix.length)
186         sb.append(prefix)
187         val annotations = type.annotations
188         if (annotations.isNotEmpty()) {
189             appendAnnotations(sb, annotations, null)
190         }
191         sb.append(suffix)
192 
193         // No kotlin style suffix here: vararg parameters aren't nullable
194 
195         return sb.toString()
196     }
197 
198     // From PsiArrayType.getCanonicalText(true))
199     private fun getCanonicalText(type: PsiArrayType, elementAnnotations: List<AnnotationItem>?): String {
200         return getText(type, getCanonicalText(type.componentType, null), "[]", elementAnnotations)
201     }
202 
203     // From PsiArrayType.getText(String,String,boolean,boolean), with qualified = true
204     private fun getText(
205         type: PsiArrayType,
206         prefix: String,
207         suffix: String,
208         elementAnnotations: List<AnnotationItem>?
209     ): String {
210         val sb = StringBuilder(prefix.length + suffix.length)
211         sb.append(prefix)
212         val annotations = type.annotations
213 
214         if (annotations.isNotEmpty()) {
215             val originalLength = sb.length
216             sb.append(' ')
217             appendAnnotations(sb, annotations, elementAnnotations)
218             if (sb.length == originalLength + 1) {
219                 // Didn't emit any annotations (e.g. skipped because only null annotations and replacing with ?)
220                 sb.setLength(originalLength)
221             }
222         }
223         sb.append(suffix)
224 
225         if (kotlinStyleNulls) {
226             appendNullnessSuffix(annotations, sb, elementAnnotations)
227         }
228 
229         return sb.toString()
230     }
231 
232     // Copied from PsiPrimitiveType.getCanonicalText(true))
233     private fun getCanonicalText(type: PsiPrimitiveType, elementAnnotations: List<AnnotationItem>?): String {
234         return getText(type, elementAnnotations)
235     }
236 
237     // Copied from PsiPrimitiveType.getText(boolean, boolean), with annotated = true and qualified = true
238     private fun getText(
239         type: PsiPrimitiveType,
240         elementAnnotations: List<AnnotationItem>?
241     ): String {
242         val annotations = type.annotations
243         if (annotations.isEmpty()) return type.name
244 
245         val sb = StringBuilder()
246         appendAnnotations(sb, annotations, elementAnnotations)
247         sb.append(type.name)
248         return sb.toString()
249     }
250 
251     private fun getCanonicalText(type: PsiClassReferenceType, elementAnnotations: List<AnnotationItem>?): String {
252         val reference = type.reference
253         if (reference is PsiAnnotatedJavaCodeReferenceElement) {
254             val annotations = type.annotations
255 
256             when (reference) {
257                 is ClsJavaCodeReferenceElementImpl -> {
258                     // From ClsJavaCodeReferenceElementImpl.getCanonicalText(boolean PsiAnnotation[])
259                     val text = reference.getCanonicalText()
260 
261                     val sb = StringBuilder()
262 
263                     val prefix = getOuterClassRef(text)
264                     var tailStart = 0
265                     if (!StringUtil.isEmpty(prefix)) {
266                         sb.append(prefix).append('.')
267                         tailStart = prefix.length + 1
268                     }
269 
270                     appendAnnotations(sb, Arrays.asList(*annotations), elementAnnotations)
271 
272                     sb.append(text, tailStart, text.length)
273 
274                     if (kotlinStyleNulls) {
275                         appendNullnessSuffix(annotations, sb, elementAnnotations)
276                     }
277 
278                     return sb.toString()
279                 }
280                 is PsiJavaCodeReferenceElementImpl -> return getCanonicalText(
281                     reference,
282                     annotations,
283                     reference.containingFile,
284                     elementAnnotations,
285                     kotlinStyleNulls
286                 )
287                 else -> // Unexpected implementation: fallback
288                     return reference.getCanonicalText(true, if (annotations.isEmpty()) null else annotations)
289             }
290         }
291         return reference.canonicalText
292     }
293 
294     // From PsiJavaCodeReferenceElementImpl.getCanonicalText(bool PsiAnnotation[], PsiFile)
295     private fun getCanonicalText(
296         reference: PsiJavaCodeReferenceElementImpl,
297         annotations: Array<PsiAnnotation>?,
298         containingFile: PsiFile,
299         elementAnnotations: List<AnnotationItem>?,
300         allowKotlinSuffix: Boolean
301     ): String {
302         var remaining = annotations
303         val kind = reference.getKindEnum(containingFile)
304         when (kind) {
305             PsiJavaCodeReferenceElementImpl.Kind.CLASS_NAME_KIND,
306             PsiJavaCodeReferenceElementImpl.Kind.CLASS_OR_PACKAGE_NAME_KIND,
307             PsiJavaCodeReferenceElementImpl.Kind.CLASS_IN_QUALIFIED_NEW_KIND -> {
308                 val results = PsiImplUtil.multiResolveImpl(
309                     containingFile.project,
310                     containingFile,
311                     reference,
312                     false,
313                     PsiReferenceExpressionImpl.OurGenericsResolver.INSTANCE
314                 )
315                 val target = if (results.size == 1) results[0].element else null
316                 when (target) {
317                     is PsiClass -> {
318                         val buffer = StringBuilder()
319                         val qualifier = reference.qualifier
320                         var prefix: String? = null
321                         if (qualifier is PsiJavaCodeReferenceElementImpl) {
322                             prefix = getCanonicalText(
323                                 qualifier,
324                                 remaining,
325                                 containingFile,
326                                 null,
327                                 false
328                             )
329                             remaining = null
330                         } else {
331                             val fqn = target.qualifiedName
332                             if (fqn != null) {
333                                 prefix = StringUtil.getPackageName(fqn)
334                             }
335                         }
336 
337                         if (!StringUtil.isEmpty(prefix)) {
338                             buffer.append(prefix)
339                             buffer.append('.')
340                         }
341 
342                         val list = if (remaining != null) Arrays.asList(*remaining) else getAnnotations(reference)
343                         appendAnnotations(buffer, list, elementAnnotations)
344 
345                         buffer.append(target.name)
346 
347                         appendTypeArgs(
348                             buffer,
349                             reference.typeParameters,
350                             null
351                         )
352 
353                         if (allowKotlinSuffix && kotlinStyleNulls) {
354                             appendNullnessSuffix(list, buffer, elementAnnotations)
355                         }
356 
357                         return buffer.toString()
358                     }
359                     is PsiPackage -> return target.qualifiedName
360                     else -> return JavaSourceUtil.getReferenceText(reference)
361                 }
362             }
363 
364             PsiJavaCodeReferenceElementImpl.Kind.PACKAGE_NAME_KIND,
365             PsiJavaCodeReferenceElementImpl.Kind.CLASS_FQ_NAME_KIND,
366             PsiJavaCodeReferenceElementImpl.Kind.CLASS_FQ_OR_PACKAGE_NAME_KIND ->
367                 return JavaSourceUtil.getReferenceText(reference)
368 
369             else -> {
370                 error("Unexpected kind $kind")
371             }
372         }
373     }
374 
375     private fun getNullable(list: List<PsiAnnotation>?, elementAnnotations: List<AnnotationItem>?): Boolean? {
376         if (elementAnnotations != null) {
377             for (annotation in elementAnnotations) {
378                 if (annotation.isNullable()) {
379                     return true
380                 } else if (annotation.isNonNull()) {
381                     return false
382                 }
383             }
384         }
385 
386         list ?: return null
387 
388         for (annotation in list) {
389             val name = annotation.qualifiedName ?: continue
390             if (isNullableAnnotation(name)) {
391                 return true
392             } else if (isNonNullAnnotation(name)) {
393                 return false
394             }
395         }
396 
397         return null
398     }
399 
400     private fun getNullable(list: Array<PsiAnnotation>?, elementAnnotations: List<AnnotationItem>?): Boolean? {
401         if (elementAnnotations != null) {
402             for (annotation in elementAnnotations) {
403                 if (annotation.isNullable()) {
404                     return true
405                 } else if (annotation.isNonNull()) {
406                     return false
407                 }
408             }
409         }
410 
411         list ?: return null
412 
413         for (annotation in list) {
414             val name = annotation.qualifiedName ?: continue
415             if (isNullableAnnotation(name)) {
416                 return true
417             } else if (isNonNullAnnotation(name)) {
418                 return false
419             }
420         }
421 
422         return null
423     }
424 
425     // From PsiNameHelper.appendTypeArgs, but with annotated = true and canonical = true
426     private fun appendTypeArgs(
427         sb: StringBuilder,
428         types: Array<PsiType>,
429         elementAnnotations: List<AnnotationItem>?
430     ) {
431         if (types.isEmpty()) return
432 
433         sb.append('<')
434         for (i in types.indices) {
435             if (i > 0) {
436                 sb.append(if (!compatibility.spaceAfterCommaInTypes) "," else ", ")
437             }
438 
439             val type = types[i]
440             sb.append(getCanonicalText(type, elementAnnotations))
441         }
442         sb.append('>')
443     }
444 
445     // From PsiJavaCodeReferenceElementImpl.getAnnotations()
446     private fun getAnnotations(reference: PsiJavaCodeReferenceElementImpl): List<PsiAnnotation> {
447         val annotations = PsiTreeUtil.getChildrenOfTypeAsList(reference, PsiAnnotation::class.java)
448 
449         if (!reference.isQualified) {
450             val modifierList = PsiImplUtil.findNeighbourModifierList(reference)
451             if (modifierList != null) {
452                 PsiImplUtil.collectTypeUseAnnotations(modifierList, annotations)
453             }
454         }
455 
456         return annotations
457     }
458 
459     // From ClsJavaCodeReferenceElementImpl
460     private fun getOuterClassRef(ref: String): String {
461         var stack = 0
462         for (i in ref.length - 1 downTo 0) {
463             val c = ref[i]
464             when (c) {
465                 '<' -> stack--
466                 '>' -> stack++
467                 '.' -> if (stack == 0) return ref.substring(0, i)
468             }
469         }
470 
471         return ""
472     }
473 
474     // From PsiNameHelper.appendAnnotations
475 
476     private fun appendAnnotations(
477         sb: StringBuilder,
478         annotations: Array<PsiAnnotation>,
479         elementAnnotations: List<AnnotationItem>?
480     ): Boolean {
481         return appendAnnotations(sb, Arrays.asList(*annotations), elementAnnotations)
482     }
483 
484     private fun mapAnnotation(qualifiedName: String?): String? {
485         qualifiedName ?: return null
486         if (kotlinStyleNulls && (isNullableAnnotation(qualifiedName) || isNonNullAnnotation(qualifiedName))) {
487             return null
488         }
489         if (!supportTypeUseAnnotations) {
490             return null
491         }
492 
493         val mapped =
494             if (mapAnnotations) {
495                 AnnotationItem.mapName(codebase, qualifiedName) ?: return null
496             } else {
497                 qualifiedName
498             }
499 
500         if (filter != null) {
501             val item = codebase.findClass(mapped)
502             if (item == null || !filter.test(item)) {
503                 return null
504             }
505         }
506 
507         return mapped
508     }
509 
510     // From PsiNameHelper.appendAnnotations, with deltas to optionally map names
511 
512     private fun appendAnnotations(
513         sb: StringBuilder,
514         annotations: List<PsiAnnotation>,
515         elementAnnotations: List<AnnotationItem>?
516     ): Boolean {
517         var updated = false
518         for (annotation in annotations) {
519             val name = mapAnnotation(annotation.qualifiedName)
520             if (name != null) {
521                 sb.append('@').append(name).append(annotation.parameterList.text).append(' ')
522                 updated = true
523             }
524         }
525 
526         if (elementAnnotations != null) {
527             for (annotation in elementAnnotations) {
528                 val name = mapAnnotation(annotation.qualifiedName())
529                 if (name != null) {
530                     sb.append(annotation.toSource()).append(' ')
531                     updated = true
532                 }
533             }
534         }
535         return updated
536     }
537 
538     // From PsiImmediateClassType
539 
540     private fun getCanonicalText(type: PsiImmediateClassType, elementAnnotations: List<AnnotationItem>?): String {
541         return getText(type, elementAnnotations)
542     }
543 
544     private fun getText(
545         type: PsiImmediateClassType,
546         elementAnnotations: List<AnnotationItem>?
547     ): String {
548         val cls = type.resolve() ?: return ""
549         val buffer = StringBuilder()
550         buildText(type, cls, PsiSubstitutor.EMPTY, buffer, elementAnnotations)
551         return buffer.toString()
552     }
553 
554     private fun buildText(
555         type: PsiImmediateClassType,
556         aClass: PsiClass,
557         substitutor: PsiSubstitutor,
558         buffer: StringBuilder,
559         elementAnnotations: List<AnnotationItem>?
560     ) {
561         if (aClass is PsiAnonymousClass) {
562             val baseResolveResult = aClass.baseClassType.resolveGenerics()
563             val baseClass = baseResolveResult.element
564             if (baseClass != null) {
565                 buildText(type, baseClass, baseResolveResult.substitutor, buffer, null)
566             } else {
567                 buffer.append(aClass.baseClassReference.canonicalText)
568             }
569             return
570         }
571 
572         var enclosingClass: PsiClass? = null
573         if (!aClass.hasModifierProperty(PsiModifier.STATIC)) {
574             val parent = aClass.parent
575             if (parent is PsiClass && parent !is PsiAnonymousClass) {
576                 enclosingClass = parent
577             }
578         }
579         if (enclosingClass != null) {
580             buildText(type, enclosingClass, substitutor, buffer, null)
581             buffer.append('.')
582         } else {
583             val fqn = aClass.qualifiedName
584             if (fqn != null) {
585                 val prefix = StringUtil.getPackageName(fqn)
586                 if (!StringUtil.isEmpty(prefix)) {
587                     buffer.append(prefix)
588                     buffer.append('.')
589                 }
590             }
591         }
592 
593         val annotations = type.annotations
594         appendAnnotations(buffer, annotations, elementAnnotations)
595 
596         buffer.append(aClass.name)
597 
598         val typeParameters = aClass.typeParameters
599         if (typeParameters.isNotEmpty()) {
600             var pos = buffer.length
601             buffer.append('<')
602 
603             for (i in typeParameters.indices) {
604                 val typeParameter = typeParameters[i]
605                 PsiUtilCore.ensureValid(typeParameter)
606 
607                 if (i > 0) {
608                     buffer.append(',')
609                 }
610 
611                 val substitutionResult = substitutor.substitute(typeParameter)
612                 if (substitutionResult == null) {
613                     buffer.setLength(pos)
614                     pos = -1
615                     break
616                 }
617                 PsiUtil.ensureValidType(substitutionResult)
618 
619                 buffer.append(getCanonicalText(substitutionResult, null)) // not passing in merge annotations here
620             }
621 
622             if (pos >= 0) {
623                 buffer.append('>')
624             }
625         }
626 
627         if (kotlinStyleNulls) {
628             appendNullnessSuffix(annotations, buffer, elementAnnotations)
629         }
630     }
631 }