• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.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.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.PsiReference
44 import com.intellij.psi.impl.JavaConstantExpressionEvaluator
45 import org.jetbrains.kotlin.asJava.elements.KtLightNullabilityAnnotation
46 
47 internal class PsiAnnotationItem
48 private constructor(
49     override val annotationContext: PsiBasedCodebase,
50     val psiAnnotation: PsiAnnotation,
51     originalName: String,
52     qualifiedName: String,
53 ) :
54     DefaultAnnotationItem(
55         annotationContext = annotationContext,
56         fileLocation = PsiFileLocation.fromPsiElement(psiAnnotation),
57         originalName = originalName,
58         qualifiedName = qualifiedName,
59         attributesGetter = { getAnnotationAttributes(annotationContext, psiAnnotation) },
60     ) {
61 
toSourcenull62     override fun toSource(target: AnnotationTarget, showDefaultAttrs: Boolean): String {
63         val sb = StringBuilder(60)
64         appendAnnotation(
65             annotationContext,
66             sb,
67             psiAnnotation,
68             qualifiedName,
69             target,
70             showDefaultAttrs
71         )
72         return sb.toString()
73     }
74 
snapshotnull75     override fun snapshot(targetCodebase: Codebase) = this
76 
77     override fun isNonNull(): Boolean {
78         if (psiAnnotation is KtLightNullabilityAnnotation<*> && originalName == "") {
79             // Hack/workaround: some UAST annotation nodes do not provide qualified name :=(
80             return true
81         }
82         return super.isNonNull()
83     }
84 
85     companion object {
getAnnotationAttributesnull86         private fun getAnnotationAttributes(
87             codebase: PsiBasedCodebase,
88             psiAnnotation: PsiAnnotation
89         ): List<AnnotationAttribute> =
90             psiAnnotation.parameterList.attributes
91                 .mapNotNull { attribute ->
92                     attribute.value?.let { value ->
93                         DefaultAnnotationAttribute(
94                             attribute.name ?: ANNOTATION_ATTR_VALUE,
95                             createValue(codebase, value),
96                         )
97                     }
98                 }
99                 .toList()
100 
createnull101         fun create(
102             codebase: PsiBasedCodebase,
103             psiAnnotation: PsiAnnotation,
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                 psiAnnotation.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 PsiAnnotationItem(
116                 annotationContext = codebase,
117                 psiAnnotation = psiAnnotation,
118                 originalName = originalName,
119                 qualifiedName = qualifiedName,
120             )
121         }
122 
getAttributesnull123         private fun getAttributes(
124             annotation: PsiAnnotation,
125             showDefaultAttrs: Boolean
126         ): List<Pair<String?, PsiAnnotationMemberValue?>> {
127             val annotationClass = annotation.nameReferenceElement?.resolve() as? PsiClass
128             val list = mutableListOf<Pair<String?, PsiAnnotationMemberValue?>>()
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.parameterList.attributes) {
138                     list.add(Pair(attr.name, attr.value))
139                 }
140             }
141             return list
142         }
143 
appendAnnotationnull144         private fun appendAnnotation(
145             codebase: PsiBasedCodebase,
146             sb: StringBuilder,
147             psiAnnotation: PsiAnnotation,
148             qualifiedName: String?,
149             target: AnnotationTarget,
150             showDefaultAttrs: Boolean
151         ) {
152             val alwaysInlineValues = qualifiedName == "android.annotation.FlaggedApi"
153             val outputName =
154                 codebase.annotationManager.normalizeOutputName(qualifiedName, target) ?: return
155 
156             val attributes = getAttributes(psiAnnotation, showDefaultAttrs)
157             if (attributes.isEmpty()) {
158                 sb.append("@$outputName")
159                 return
160             }
161 
162             sb.append("@")
163             sb.append(outputName)
164             sb.append("(")
165             if (
166                 attributes.size == 1 &&
167                     (attributes[0].first == null || attributes[0].first == ANNOTATION_ATTR_VALUE)
168             ) {
169                 // Special case: omit "value" if it's the only attribute
170                 appendValue(
171                     codebase,
172                     sb,
173                     attributes[0].second,
174                     target,
175                     showDefaultAttrs = showDefaultAttrs,
176                     alwaysInlineValues = alwaysInlineValues,
177                 )
178             } else {
179                 var first = true
180                 for (attribute in attributes) {
181                     if (first) {
182                         first = false
183                     } else {
184                         sb.append(", ")
185                     }
186                     sb.append(attribute.first ?: ANNOTATION_ATTR_VALUE)
187                     sb.append('=')
188                     appendValue(
189                         codebase,
190                         sb,
191                         attribute.second,
192                         target,
193                         showDefaultAttrs = showDefaultAttrs,
194                         alwaysInlineValues = alwaysInlineValues,
195                     )
196                 }
197             }
198             sb.append(")")
199         }
200 
appendValuenull201         private fun appendValue(
202             codebase: PsiBasedCodebase,
203             sb: StringBuilder,
204             value: PsiAnnotationMemberValue?,
205             target: AnnotationTarget,
206             showDefaultAttrs: Boolean,
207             alwaysInlineValues: Boolean,
208         ) {
209             // Compute annotation string -- we don't just use value.text here
210             // because that may not use fully qualified names, e.g. the source may say
211             //  @RequiresPermission(Manifest.permission.ACCESS_COARSE_LOCATION)
212             // and we want to compute
213             //
214             // @androidx.annotation.RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION)
215             when (value) {
216                 null -> sb.append("null")
217                 is PsiLiteral -> sb.append(constantToSource(value.value))
218                 is PsiReference -> {
219                     when (val resolved = value.resolve()) {
220                         is PsiField -> {
221                             val containing = resolved.containingClass
222                             if (containing != null) {
223                                 // If it's a field reference, see if it looks like the field is
224                                 // hidden; if
225                                 // so, inline the value
226                                 val cls = codebase.findOrCreateClass(containing)
227                                 val initializer = resolved.initializer
228                                 if (initializer != null) {
229                                     val fieldItem = cls.findField(resolved.name)
230                                     if (
231                                         alwaysInlineValues ||
232                                             fieldItem == null ||
233                                             fieldItem.isHiddenOrRemoved() ||
234                                             !fieldItem.isPublic
235                                     ) {
236                                         // Use the literal value instead
237                                         val source = getConstantSource(initializer)
238                                         if (source != null) {
239                                             sb.append(source)
240                                             return
241                                         }
242                                     }
243                                 }
244                                 containing.qualifiedName?.let { sb.append(it).append('.') }
245                             }
246 
247                             sb.append(resolved.name)
248                         }
249                         is PsiClass -> resolved.qualifiedName?.let { sb.append(it) }
250                         else -> {
251                             sb.append(value.text)
252                         }
253                     }
254                 }
255                 is PsiBinaryExpression -> {
256                     appendValue(
257                         codebase,
258                         sb,
259                         value.lOperand,
260                         target,
261                         showDefaultAttrs = showDefaultAttrs,
262                         alwaysInlineValues = alwaysInlineValues,
263                     )
264                     sb.append(' ')
265                     sb.append(value.operationSign.text)
266                     sb.append(' ')
267                     appendValue(
268                         codebase,
269                         sb,
270                         value.rOperand,
271                         target,
272                         showDefaultAttrs = showDefaultAttrs,
273                         alwaysInlineValues = alwaysInlineValues,
274                     )
275                 }
276                 is PsiArrayInitializerMemberValue -> {
277                     sb.append('{')
278                     var first = true
279                     for (initializer in value.initializers) {
280                         if (first) {
281                             first = false
282                         } else {
283                             sb.append(", ")
284                         }
285                         appendValue(
286                             codebase,
287                             sb,
288                             initializer,
289                             target,
290                             showDefaultAttrs = showDefaultAttrs,
291                             alwaysInlineValues = alwaysInlineValues,
292                         )
293                     }
294                     sb.append('}')
295                 }
296                 is PsiAnnotation -> {
297                     appendAnnotation(
298                         codebase,
299                         sb,
300                         value,
301                         // Normalize the input name of the annotation.
302                         codebase.annotationManager.normalizeInputName(value.qualifiedName),
303                         target,
304                         showDefaultAttrs
305                     )
306                 }
307                 else -> {
308                     if (value is PsiExpression) {
309                         val source = getConstantSource(value)
310                         if (source != null) {
311                             sb.append(source)
312                             return
313                         }
314                     }
315                     sb.append(value.text)
316                 }
317             }
318         }
319 
getConstantSourcenull320         private fun getConstantSource(value: PsiExpression): String? {
321             val constant = JavaConstantExpressionEvaluator.computeConstantExpression(value, false)
322             return constantToExpression(constant)
323         }
324     }
325 }
326 
createValuenull327 private fun createValue(
328     codebase: PsiBasedCodebase,
329     value: PsiAnnotationMemberValue
330 ): AnnotationAttributeValue {
331     return if (value is PsiArrayInitializerMemberValue) {
332         DefaultAnnotationArrayAttributeValue(
333             { value.text },
334             { value.initializers.map { createValue(codebase, it) }.toList() }
335         )
336     } else {
337         PsiAnnotationSingleAttributeValue(codebase, value)
338     }
339 }
340 
341 internal class PsiAnnotationSingleAttributeValue(
342     private val codebase: PsiBasedCodebase,
343     private val psiValue: PsiAnnotationMemberValue
<lambda>null344 ) : DefaultAnnotationSingleAttributeValue({ psiValue.text }, { getValue(psiValue) }) {
345 
346     companion object {
getValuenull347         private fun getValue(psiValue: PsiAnnotationMemberValue): Any {
348             if (psiValue is PsiLiteral) {
349                 return psiValue.value ?: psiValue.text.removeSurrounding("\"")
350             }
351 
352             val value = ConstantEvaluator.evaluate(null, psiValue)
353             if (value != null) {
354                 return value
355             }
356 
357             if (psiValue is PsiClassObjectAccessExpression) {
358                 // The value of a class literal expression like String.class or String::class
359                 // is the fully qualified name, java.lang.String
360                 return psiValue.operand.type.canonicalText
361             }
362 
363             return psiValue.text ?: psiValue.text.removeSurrounding("\"")
364         }
365     }
366 
resolvenull367     override fun resolve(): Item? {
368         if (psiValue is PsiReference) {
369             when (val resolved = psiValue.resolve()) {
370                 is PsiField -> return codebase.findField(resolved)
371                 is PsiClass -> return codebase.findOrCreateClass(resolved)
372             }
373         }
374         return null
375     }
376 }
377