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