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.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.intellij.psi.PsiAnnotationMethod
32 import com.intellij.psi.PsiClass
33 import com.intellij.psi.PsiExpression
34 import com.intellij.psi.PsiField
35 import com.intellij.psi.PsiLiteral
36 import com.intellij.psi.PsiMethod
37 import com.intellij.psi.impl.JavaConstantExpressionEvaluator
38 import org.jetbrains.kotlin.asJava.elements.KtLightNullabilityAnnotation
39 import org.jetbrains.uast.UAnnotation
40 import org.jetbrains.uast.UBinaryExpression
41 import org.jetbrains.uast.UCallExpression
42 import org.jetbrains.uast.UClassLiteralExpression
43 import org.jetbrains.uast.UElement
44 import org.jetbrains.uast.UExpression
45 import org.jetbrains.uast.ULiteralExpression
46 import org.jetbrains.uast.UQualifiedReferenceExpression
47 import org.jetbrains.uast.UReferenceExpression
48 import org.jetbrains.uast.util.isArrayInitializer
49 
50 class UAnnotationItem
51 private constructor(
52     override val codebase: PsiBasedCodebase,
53     val uAnnotation: UAnnotation,
54     originalName: String?
55 ) :
56     DefaultAnnotationItem(
57         codebase,
58         originalName,
59         { getAnnotationAttributes(codebase, uAnnotation) }
60     ) {
61 
toSourcenull62     override fun toSource(target: AnnotationTarget, showDefaultAttrs: Boolean): String {
63         val sb = StringBuilder(60)
64         appendAnnotation(codebase, sb, uAnnotation, qualifiedName, target, showDefaultAttrs)
65         return sb.toString()
66     }
67 
resolvenull68     override fun resolve(): ClassItem? {
69         return codebase.findOrCreateClass(originalName ?: return null)
70     }
71 
isNonNullnull72     override fun isNonNull(): Boolean {
73         if (uAnnotation.javaPsi is KtLightNullabilityAnnotation<*> && originalName == "") {
74             // Hack/workaround: some UAST annotation nodes do not provide qualified name :=(
75             return true
76         }
77         return super.isNonNull()
78     }
79 
<lambda>null80     override val targets: Set<AnnotationTarget> by lazy {
81         codebase.annotationManager.computeTargets(this, codebase::findOrCreateClass)
82     }
83 
84     companion object {
getAnnotationAttributesnull85         private fun getAnnotationAttributes(
86             codebase: PsiBasedCodebase,
87             uAnnotation: UAnnotation
88         ): List<AnnotationAttribute> =
89             uAnnotation.attributeValues
90                 .map { attribute ->
91                     DefaultAnnotationAttribute(
92                         attribute.name ?: ANNOTATION_ATTR_VALUE,
93                         createValue(codebase, attribute.expression)
94                     )
95                 }
96                 .toList()
97 
createnull98         fun create(
99             codebase: PsiBasedCodebase,
100             uAnnotation: UAnnotation,
101             qualifiedName: String? = uAnnotation.qualifiedName
102         ): AnnotationItem {
103             return UAnnotationItem(codebase, uAnnotation, qualifiedName)
104         }
105 
getAttributesnull106         private fun getAttributes(
107             annotation: UAnnotation,
108             showDefaultAttrs: Boolean
109         ): List<Pair<String?, UExpression?>> {
110             val annotationClass = annotation.javaPsi?.nameReferenceElement?.resolve() as? PsiClass
111             val list = mutableListOf<Pair<String?, UExpression?>>()
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.attributeValues) {
121                     list.add(Pair(attr.name, attr.expression))
122                 }
123             }
124             return list
125         }
126 
appendAnnotationnull127         private fun appendAnnotation(
128             codebase: PsiBasedCodebase,
129             sb: StringBuilder,
130             uAnnotation: UAnnotation,
131             originalName: String?,
132             target: AnnotationTarget,
133             showDefaultAttrs: Boolean
134         ) {
135             val qualifiedName =
136                 codebase.annotationManager.normalizeOutputName(originalName, target) ?: return
137 
138             val attributes = getAttributes(uAnnotation, showDefaultAttrs)
139             if (attributes.isEmpty()) {
140                 sb.append("@$qualifiedName")
141                 return
142             }
143 
144             sb.append("@")
145             sb.append(qualifiedName)
146             sb.append("(")
147             if (
148                 attributes.size == 1 &&
149                     (attributes[0].first == null || attributes[0].first == ANNOTATION_ATTR_VALUE)
150             ) {
151                 // Special case: omit "value" if it's the only attribute
152                 appendValue(codebase, sb, attributes[0].second, target, showDefaultAttrs)
153             } else {
154                 var first = true
155                 for (attribute in attributes) {
156                     if (first) {
157                         first = false
158                     } else {
159                         sb.append(", ")
160                     }
161                     sb.append(attribute.first ?: ANNOTATION_ATTR_VALUE)
162                     sb.append('=')
163                     appendValue(codebase, sb, attribute.second, target, showDefaultAttrs)
164                 }
165             }
166             sb.append(")")
167         }
168 
appendValuenull169         private fun appendValue(
170             codebase: PsiBasedCodebase,
171             sb: StringBuilder,
172             value: UExpression?,
173             target: AnnotationTarget,
174             showDefaultAttrs: Boolean
175         ) {
176             // Compute annotation string -- we don't just use value.text here
177             // because that may not use fully qualified names, e.g. the source may say
178             //  @RequiresPermission(Manifest.permission.ACCESS_COARSE_LOCATION)
179             // and we want to compute
180             //
181             // @androidx.annotation.RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION)
182             when (value) {
183                 null -> sb.append("null")
184                 is ULiteralExpression -> sb.append(CodePrinter.constantToSource(value.value))
185                 is UQualifiedReferenceExpression -> { // the value is a Foo.BAR type of reference.
186                     // expand `Foo` to fully qualified name `com.example.Foo`
187                     appendQualifiedName(codebase, sb, value.receiver as UReferenceExpression)
188                     // append accessor `.`
189                     sb.append(value.accessType.name)
190                     // append `BAR`
191                     sb.append(value.selector.asRenderString())
192                 }
193                 is UReferenceExpression -> {
194                     // expand Foo to fully qualified name com.example.Foo
195                     appendQualifiedName(codebase, sb, value)
196                 }
197                 is UBinaryExpression -> {
198                     appendValue(codebase, sb, value.leftOperand, target, showDefaultAttrs)
199                     sb.append(' ')
200                     sb.append(value.operator.text)
201                     sb.append(' ')
202                     appendValue(codebase, sb, value.rightOperand, target, showDefaultAttrs)
203                 }
204                 is UCallExpression -> {
205                     if (value.isArrayInitializer()) {
206                         sb.append('{')
207                         var first = true
208                         for (initializer in value.valueArguments) {
209                             if (first) {
210                                 first = false
211                             } else {
212                                 sb.append(", ")
213                             }
214                             appendValue(codebase, sb, initializer, target, showDefaultAttrs)
215                         }
216                         sb.append('}')
217                     } // TODO: support UCallExpression for other cases than array initializers
218                 }
219                 is UAnnotation -> {
220                     appendAnnotation(
221                         codebase,
222                         sb,
223                         value,
224                         // Normalize the input name of the annotation.
225                         codebase.annotationManager.normalizeInputName(value.qualifiedName),
226                         target,
227                         showDefaultAttrs
228                     )
229                 }
230                 else -> {
231                     val source = getConstantSource(value)
232                     if (source != null) {
233                         sb.append(source)
234                         return
235                     }
236                     sb.append(value.sourcePsi?.text ?: value.asSourceString())
237                 }
238             }
239         }
240 
appendQualifiedNamenull241         private fun appendQualifiedName(
242             codebase: PsiBasedCodebase,
243             sb: StringBuilder,
244             value: UReferenceExpression
245         ) {
246             when (val resolved = value.resolve()) {
247                 is PsiField -> {
248                     val containing = resolved.containingClass
249                     if (containing != null) {
250                         // If it's a field reference, see if it looks like the field is hidden; if
251                         // so, inline the value
252                         val cls = codebase.findOrCreateClass(containing)
253                         val initializer = resolved.initializer
254                         if (initializer != null) {
255                             val fieldItem = cls.findField(resolved.name)
256                             if (fieldItem == null || fieldItem.isHiddenOrRemoved()) {
257                                 // Use the literal value instead
258                                 val source = getConstantSource(initializer)
259                                 if (source != null) {
260                                     sb.append(source)
261                                     return
262                                 }
263                             }
264                         }
265                         containing.qualifiedName?.let { sb.append(it).append('.') }
266                     }
267 
268                     sb.append(resolved.name)
269                 }
270                 is PsiClass -> resolved.qualifiedName?.let { sb.append(it) }
271                 else -> {
272                     sb.append(value.sourcePsi?.text ?: value.asSourceString())
273                 }
274             }
275         }
276 
getConstantSourcenull277         private fun getConstantSource(value: UExpression): String? {
278             val constant = value.evaluate()
279             return CodePrinter.constantToExpression(constant)
280         }
281 
getConstantSourcenull282         private fun getConstantSource(value: PsiExpression): String? {
283             val constant = JavaConstantExpressionEvaluator.computeConstantExpression(value, false)
284             return CodePrinter.constantToExpression(constant)
285         }
286     }
287 }
288 
createValuenull289 private fun createValue(codebase: PsiBasedCodebase, value: UExpression): AnnotationAttributeValue {
290     return if (value.isArrayInitializer()) {
291         val uCallExpression = value as UCallExpression
292         DefaultAnnotationArrayAttributeValue(
293             { getText(uCallExpression) },
294             { uCallExpression.valueArguments.map { createValue(codebase, it) }.toList() }
295         )
296     } else {
297         UAnnotationSingleAttributeValue(codebase, value)
298     }
299 }
300 
301 class UAnnotationSingleAttributeValue(
302     private val codebase: PsiBasedCodebase,
303     private val psiValue: UExpression
<lambda>null304 ) : DefaultAnnotationSingleAttributeValue({ getText(psiValue) }, { getValue(psiValue) }) {
305 
306     companion object {
getValuenull307         private fun getValue(psiValue: UExpression): Any? {
308             if (psiValue is ULiteralExpression) {
309                 val value = psiValue.value
310                 if (value != null) {
311                     return value
312                 } else if (psiValue.isNull) {
313                     return null
314                 }
315             }
316             if (psiValue is PsiLiteral) {
317                 return psiValue.value ?: getText(psiValue).removeSurrounding("\"")
318             }
319 
320             val value = ConstantEvaluator.evaluate(null, psiValue)
321             if (value != null) {
322                 return value
323             }
324 
325             if (psiValue is UClassLiteralExpression) {
326                 // The value of a class literal expression like String.class or String::class
327                 // is the fully qualified name, java.lang.String
328                 val type = psiValue.type
329                 if (type != null) {
330                     return type.canonicalText
331                 }
332             }
333 
334             return getText(psiValue).removeSurrounding("\"")
335         }
336     }
337 
resolvenull338     override fun resolve(): Item? {
339         if (psiValue is UReferenceExpression) {
340             when (val resolved = psiValue.resolve()) {
341                 is PsiField -> return codebase.findField(resolved)
342                 is PsiClass -> return codebase.findOrCreateClass(resolved)
343                 is PsiMethod -> return codebase.findMethod(resolved)
344             }
345         }
346         return null
347     }
348 }
349 
getTextnull350 private fun getText(element: UElement): String {
351     return element.sourcePsi?.text ?: element.asSourceString()
352 }
353