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