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