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