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