1 /*
<lambda>null2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.tools.metalava.model.psi
18 
19 import com.android.tools.lint.detector.api.ConstantEvaluator
20 import com.android.tools.metalava.model.ANNOTATION_ATTR_VALUE
21 import com.android.tools.metalava.model.AnnotationAttribute
22 import com.android.tools.metalava.model.AnnotationAttributeValue
23 import com.android.tools.metalava.model.AnnotationItem
24 import com.android.tools.metalava.model.AnnotationTarget
25 import com.android.tools.metalava.model.ClassItem
26 import com.android.tools.metalava.model.DefaultAnnotationArrayAttributeValue
27 import com.android.tools.metalava.model.DefaultAnnotationAttribute
28 import com.android.tools.metalava.model.DefaultAnnotationItem
29 import com.android.tools.metalava.model.DefaultAnnotationSingleAttributeValue
30 import com.android.tools.metalava.model.Item
31 import com.android.tools.metalava.model.psi.CodePrinter.Companion.constantToExpression
32 import com.android.tools.metalava.model.psi.CodePrinter.Companion.constantToSource
33 import com.intellij.psi.PsiAnnotation
34 import com.intellij.psi.PsiAnnotationMemberValue
35 import com.intellij.psi.PsiAnnotationMethod
36 import com.intellij.psi.PsiArrayInitializerMemberValue
37 import com.intellij.psi.PsiBinaryExpression
38 import com.intellij.psi.PsiClass
39 import com.intellij.psi.PsiClassObjectAccessExpression
40 import com.intellij.psi.PsiExpression
41 import com.intellij.psi.PsiField
42 import com.intellij.psi.PsiLiteral
43 import com.intellij.psi.PsiMethod
44 import com.intellij.psi.PsiReference
45 import com.intellij.psi.impl.JavaConstantExpressionEvaluator
46 import org.jetbrains.kotlin.asJava.elements.KtLightNullabilityAnnotation
47 
48 class PsiAnnotationItem
49 private constructor(
50     override val codebase: PsiBasedCodebase,
51     val psiAnnotation: PsiAnnotation,
52     originalName: String?
53 ) :
54     DefaultAnnotationItem(
55         codebase,
56         originalName,
57         { getAnnotationAttributes(codebase, psiAnnotation) }
58     ) {
59 
toSourcenull60     override fun toSource(target: AnnotationTarget, showDefaultAttrs: Boolean): String {
61         val sb = StringBuilder(60)
62         appendAnnotation(codebase, sb, psiAnnotation, qualifiedName, target, showDefaultAttrs)
63         return sb.toString()
64     }
65 
resolvenull66     override fun resolve(): ClassItem? {
67         return codebase.findOrCreateClass(originalName ?: return null)
68     }
69 
isNonNullnull70     override fun isNonNull(): Boolean {
71         if (psiAnnotation is KtLightNullabilityAnnotation<*> && originalName == "") {
72             // Hack/workaround: some UAST annotation nodes do not provide qualified name :=(
73             return true
74         }
75         return super.isNonNull()
76     }
77 
<lambda>null78     override val targets: Set<AnnotationTarget> by lazy {
79         codebase.annotationManager.computeTargets(this, codebase::findOrCreateClass)
80     }
81 
82     companion object {
getAnnotationAttributesnull83         private fun getAnnotationAttributes(
84             codebase: PsiBasedCodebase,
85             psiAnnotation: PsiAnnotation
86         ): List<AnnotationAttribute> =
87             psiAnnotation.parameterList.attributes
88                 .mapNotNull { attribute ->
89                     attribute.value?.let { value ->
90                         DefaultAnnotationAttribute(
91                             attribute.name ?: ANNOTATION_ATTR_VALUE,
92                             createValue(codebase, value),
93                         )
94                     }
95                 }
96                 .toList()
97 
createnull98         fun create(
99             codebase: PsiBasedCodebase,
100             psiAnnotation: PsiAnnotation,
101             qualifiedName: String? = psiAnnotation.qualifiedName
102         ): AnnotationItem {
103             return PsiAnnotationItem(codebase, psiAnnotation, qualifiedName)
104         }
105 
getAttributesnull106         private fun getAttributes(
107             annotation: PsiAnnotation,
108             showDefaultAttrs: Boolean
109         ): List<Pair<String?, PsiAnnotationMemberValue?>> {
110             val annotationClass = annotation.nameReferenceElement?.resolve() as? PsiClass
111             val list = mutableListOf<Pair<String?, PsiAnnotationMemberValue?>>()
112             if (annotationClass != null && showDefaultAttrs) {
113                 for (method in annotationClass.methods) {
114                     if (method !is PsiAnnotationMethod) {
115                         continue
116                     }
117                     list.add(Pair(method.name, annotation.findAttributeValue(method.name)))
118                 }
119             } else {
120                 for (attr in annotation.parameterList.attributes) {
121                     list.add(Pair(attr.name, attr.value))
122                 }
123             }
124             return list
125         }
126 
appendAnnotationnull127         private fun appendAnnotation(
128             codebase: PsiBasedCodebase,
129             sb: StringBuilder,
130             psiAnnotation: PsiAnnotation,
131             qualifiedName: String?,
132             target: AnnotationTarget,
133             showDefaultAttrs: Boolean
134         ) {
135             val alwaysInlineValues = qualifiedName == "android.annotation.FlaggedApi"
136             val outputName =
137                 codebase.annotationManager.normalizeOutputName(qualifiedName, target) ?: return
138 
139             val attributes = getAttributes(psiAnnotation, showDefaultAttrs)
140             if (attributes.isEmpty()) {
141                 sb.append("@$outputName")
142                 return
143             }
144 
145             sb.append("@")
146             sb.append(outputName)
147             sb.append("(")
148             if (
149                 attributes.size == 1 &&
150                     (attributes[0].first == null || attributes[0].first == ANNOTATION_ATTR_VALUE)
151             ) {
152                 // Special case: omit "value" if it's the only attribute
153                 appendValue(
154                     codebase,
155                     sb,
156                     attributes[0].second,
157                     target,
158                     showDefaultAttrs = showDefaultAttrs,
159                     alwaysInlineValues = alwaysInlineValues,
160                 )
161             } else {
162                 var first = true
163                 for (attribute in attributes) {
164                     if (first) {
165                         first = false
166                     } else {
167                         sb.append(", ")
168                     }
169                     sb.append(attribute.first ?: ANNOTATION_ATTR_VALUE)
170                     sb.append('=')
171                     appendValue(
172                         codebase,
173                         sb,
174                         attribute.second,
175                         target,
176                         showDefaultAttrs = showDefaultAttrs,
177                         alwaysInlineValues = alwaysInlineValues,
178                     )
179                 }
180             }
181             sb.append(")")
182         }
183 
appendValuenull184         private fun appendValue(
185             codebase: PsiBasedCodebase,
186             sb: StringBuilder,
187             value: PsiAnnotationMemberValue?,
188             target: AnnotationTarget,
189             showDefaultAttrs: Boolean,
190             alwaysInlineValues: Boolean,
191         ) {
192             // Compute annotation string -- we don't just use value.text here
193             // because that may not use fully qualified names, e.g. the source may say
194             //  @RequiresPermission(Manifest.permission.ACCESS_COARSE_LOCATION)
195             // and we want to compute
196             //
197             // @androidx.annotation.RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION)
198             when (value) {
199                 null -> sb.append("null")
200                 is PsiLiteral -> sb.append(constantToSource(value.value))
201                 is PsiReference -> {
202                     when (val resolved = value.resolve()) {
203                         is PsiField -> {
204                             val containing = resolved.containingClass
205                             if (containing != null) {
206                                 // If it's a field reference, see if it looks like the field is
207                                 // hidden; if
208                                 // so, inline the value
209                                 val cls = codebase.findOrCreateClass(containing)
210                                 val initializer = resolved.initializer
211                                 if (initializer != null) {
212                                     val fieldItem = cls.findField(resolved.name)
213                                     if (
214                                         alwaysInlineValues ||
215                                             fieldItem == null ||
216                                             fieldItem.isHiddenOrRemoved() ||
217                                             !fieldItem.isPublic
218                                     ) {
219                                         // Use the literal value instead
220                                         val source = getConstantSource(initializer)
221                                         if (source != null) {
222                                             sb.append(source)
223                                             return
224                                         }
225                                     }
226                                 }
227                                 containing.qualifiedName?.let { sb.append(it).append('.') }
228                             }
229 
230                             sb.append(resolved.name)
231                         }
232                         is PsiClass -> resolved.qualifiedName?.let { sb.append(it) }
233                         else -> {
234                             sb.append(value.text)
235                         }
236                     }
237                 }
238                 is PsiBinaryExpression -> {
239                     appendValue(
240                         codebase,
241                         sb,
242                         value.lOperand,
243                         target,
244                         showDefaultAttrs = showDefaultAttrs,
245                         alwaysInlineValues = alwaysInlineValues,
246                     )
247                     sb.append(' ')
248                     sb.append(value.operationSign.text)
249                     sb.append(' ')
250                     appendValue(
251                         codebase,
252                         sb,
253                         value.rOperand,
254                         target,
255                         showDefaultAttrs = showDefaultAttrs,
256                         alwaysInlineValues = alwaysInlineValues,
257                     )
258                 }
259                 is PsiArrayInitializerMemberValue -> {
260                     sb.append('{')
261                     var first = true
262                     for (initializer in value.initializers) {
263                         if (first) {
264                             first = false
265                         } else {
266                             sb.append(", ")
267                         }
268                         appendValue(
269                             codebase,
270                             sb,
271                             initializer,
272                             target,
273                             showDefaultAttrs = showDefaultAttrs,
274                             alwaysInlineValues = alwaysInlineValues,
275                         )
276                     }
277                     sb.append('}')
278                 }
279                 is PsiAnnotation -> {
280                     appendAnnotation(
281                         codebase,
282                         sb,
283                         value,
284                         // Normalize the input name of the annotation.
285                         codebase.annotationManager.normalizeInputName(value.qualifiedName),
286                         target,
287                         showDefaultAttrs
288                     )
289                 }
290                 else -> {
291                     if (value is PsiExpression) {
292                         val source = getConstantSource(value)
293                         if (source != null) {
294                             sb.append(source)
295                             return
296                         }
297                     }
298                     sb.append(value.text)
299                 }
300             }
301         }
302 
getConstantSourcenull303         private fun getConstantSource(value: PsiExpression): String? {
304             val constant = JavaConstantExpressionEvaluator.computeConstantExpression(value, false)
305             return constantToExpression(constant)
306         }
307     }
308 }
309 
createValuenull310 private fun createValue(
311     codebase: PsiBasedCodebase,
312     value: PsiAnnotationMemberValue
313 ): AnnotationAttributeValue {
314     return if (value is PsiArrayInitializerMemberValue) {
315         DefaultAnnotationArrayAttributeValue(
316             { value.text },
317             { value.initializers.map { createValue(codebase, it) }.toList() }
318         )
319     } else {
320         PsiAnnotationSingleAttributeValue(codebase, value)
321     }
322 }
323 
324 class PsiAnnotationSingleAttributeValue(
325     private val codebase: PsiBasedCodebase,
326     private val psiValue: PsiAnnotationMemberValue
<lambda>null327 ) : DefaultAnnotationSingleAttributeValue({ psiValue.text }, { getValue(psiValue) }) {
328 
329     companion object {
getValuenull330         private fun getValue(psiValue: PsiAnnotationMemberValue): Any {
331             if (psiValue is PsiLiteral) {
332                 return psiValue.value ?: psiValue.text.removeSurrounding("\"")
333             }
334 
335             val value = ConstantEvaluator.evaluate(null, psiValue)
336             if (value != null) {
337                 return value
338             }
339 
340             if (psiValue is PsiClassObjectAccessExpression) {
341                 // The value of a class literal expression like String.class or String::class
342                 // is the fully qualified name, java.lang.String
343                 return psiValue.operand.type.canonicalText
344             }
345 
346             return psiValue.text ?: psiValue.text.removeSurrounding("\"")
347         }
348     }
349 
resolvenull350     override fun resolve(): Item? {
351         if (psiValue is PsiReference) {
352             when (val resolved = psiValue.resolve()) {
353                 is PsiField -> return codebase.findField(resolved)
354                 is PsiClass -> return codebase.findOrCreateClass(resolved)
355                 is PsiMethod -> return codebase.findMethod(resolved)
356             }
357         }
358         return null
359     }
360 }
361