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.findOrCreateClass(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                                             !fieldItem.isPublic) {
222                                         // Use the literal value instead
223                                         val source = getConstantSource(initializer)
224                                         if (source != null) {
225                                             sb.append(source)
226                                             return
227                                         }
228                                     }
229                                 }
230                                 containing.qualifiedName?.let {
231                                     sb.append(it).append('.')
232                                 }
233                             }
234 
235                             sb.append(resolved.name)
236                         }
237                         is PsiClass -> resolved.qualifiedName?.let { sb.append(it) }
238                         else -> {
239                             sb.append(value.text)
240                         }
241                     }
242                 }
243                 is PsiBinaryExpression -> {
244                     appendValue(codebase, sb, value.lOperand, target, showDefaultAttrs)
245                     sb.append(' ')
246                     sb.append(value.operationSign.text)
247                     sb.append(' ')
248                     appendValue(codebase, sb, value.rOperand, target, showDefaultAttrs)
249                 }
250                 is PsiArrayInitializerMemberValue -> {
251                     sb.append('{')
252                     var first = true
253                     for (initializer in value.initializers) {
254                         if (first) {
255                             first = false
256                         } else {
257                             sb.append(", ")
258                         }
259                         appendValue(codebase, sb, initializer, target, showDefaultAttrs)
260                     }
261                     sb.append('}')
262                 }
263                 is PsiAnnotation -> {
264                     appendAnnotation(codebase, sb, value, value.qualifiedName, target, showDefaultAttrs)
265                 }
266                 else -> {
267                     if (value is PsiExpression) {
268                         val source = getConstantSource(value)
269                         if (source != null) {
270                             sb.append(source)
271                             return
272                         }
273                     }
274                     sb.append(value.text)
275                 }
276             }
277         }
278 
279         private fun getConstantSource(value: PsiExpression): String? {
280             val constant = JavaConstantExpressionEvaluator.computeConstantExpression(value, false)
281             return constantToExpression(constant)
282         }
283     }
284 }
285 
286 class PsiAnnotationAttribute(
287     codebase: PsiBasedCodebase,
288     override val name: String,
289     psiValue: PsiAnnotationMemberValue
290 ) : AnnotationAttribute {
291     override val value: AnnotationAttributeValue = PsiAnnotationValue.create(
292         codebase, psiValue
293     )
294 }
295 
296 abstract class PsiAnnotationValue : AnnotationAttributeValue {
297     companion object {
createnull298         fun create(codebase: PsiBasedCodebase, value: PsiAnnotationMemberValue): PsiAnnotationValue {
299             return if (value is PsiArrayInitializerMemberValue) {
300                 PsiAnnotationArrayAttributeValue(codebase, value)
301             } else {
302                 PsiAnnotationSingleAttributeValue(codebase, value)
303             }
304         }
305     }
306 
toStringnull307     override fun toString(): String = toSource()
308 }
309 
310 class PsiAnnotationSingleAttributeValue(
311     private val codebase: PsiBasedCodebase,
312     private val psiValue: PsiAnnotationMemberValue
313 ) : PsiAnnotationValue(), AnnotationSingleAttributeValue {
314     override val valueSource: String = psiValue.text
315     override val value: Any?
316         get() {
317             if (psiValue is PsiLiteral) {
318                 return psiValue.value ?: psiValue.text.removeSurrounding("\"")
319             }
320 
321             val value = ConstantEvaluator.evaluate(null, psiValue)
322             if (value != null) {
323                 return value
324             }
325 
326             return psiValue.text ?: psiValue.text.removeSurrounding("\"")
327         }
328 
329     override fun value(): Any? = value
330 
331     override fun toSource(): String = psiValue.text
332 
333     override fun resolve(): Item? {
334         if (psiValue is PsiReference) {
335             when (val resolved = psiValue.resolve()) {
336                 is PsiField -> return codebase.findField(resolved)
337                 is PsiClass -> return codebase.findOrCreateClass(resolved)
338                 is PsiMethod -> return codebase.findMethod(resolved)
339             }
340         }
341         return null
342     }
343 }
344 
345 class PsiAnnotationArrayAttributeValue(codebase: PsiBasedCodebase, private val value: PsiArrayInitializerMemberValue) :
346     PsiAnnotationValue(), AnnotationArrayAttributeValue {
<lambda>null347     override val values = value.initializers.map {
348         create(codebase, it)
349     }.toList()
350 
toSourcenull351     override fun toSource(): String = value.text
352 }
353