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.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.intellij.psi.PsiAnnotationMethod
32 import com.intellij.psi.PsiClass
33 import com.intellij.psi.PsiExpression
34 import com.intellij.psi.PsiField
35 import com.intellij.psi.PsiLiteral
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 internal class UAnnotationItem
50 private constructor(
51 override val annotationContext: PsiBasedCodebase,
52 val uAnnotation: UAnnotation,
53 originalName: String,
54 qualifiedName: String,
55 ) :
56 DefaultAnnotationItem(
57 annotationContext = annotationContext,
58 fileLocation = PsiFileLocation.fromPsiElement(uAnnotation.sourcePsi),
59 originalName = originalName,
60 qualifiedName = qualifiedName,
61 attributesGetter = { getAnnotationAttributes(annotationContext, uAnnotation) },
62 ) {
63
toSourcenull64 override fun toSource(target: AnnotationTarget, showDefaultAttrs: Boolean): String {
65 val sb = StringBuilder(60)
66 appendAnnotation(
67 annotationContext,
68 sb,
69 uAnnotation,
70 qualifiedName,
71 target,
72 showDefaultAttrs
73 )
74 return sb.toString()
75 }
76
snapshotnull77 override fun snapshot(targetCodebase: Codebase) = this
78
79 override fun isNonNull(): Boolean {
80 if (uAnnotation.javaPsi is KtLightNullabilityAnnotation<*> && originalName == "") {
81 // Hack/workaround: some UAST annotation nodes do not provide qualified name :=(
82 return true
83 }
84 return super.isNonNull()
85 }
86
87 companion object {
getAnnotationAttributesnull88 private fun getAnnotationAttributes(
89 codebase: PsiBasedCodebase,
90 uAnnotation: UAnnotation
91 ): List<AnnotationAttribute> =
92 uAnnotation.attributeValues
93 .map { attribute ->
94 DefaultAnnotationAttribute(
95 attribute.name ?: ANNOTATION_ATTR_VALUE,
96 createValue(codebase, attribute.expression)
97 )
98 }
99 .toList()
100
createnull101 fun create(
102 codebase: PsiBasedCodebase,
103 uAnnotation: UAnnotation,
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 uAnnotation.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 UAnnotationItem(
116 annotationContext = codebase,
117 uAnnotation = uAnnotation,
118 originalName = originalName,
119 qualifiedName = qualifiedName,
120 )
121 }
122
getAttributesnull123 private fun getAttributes(
124 annotation: UAnnotation,
125 showDefaultAttrs: Boolean
126 ): List<Pair<String?, UExpression?>> {
127 val annotationClass = annotation.javaPsi?.nameReferenceElement?.resolve() as? PsiClass
128 val list = mutableListOf<Pair<String?, UExpression?>>()
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.attributeValues) {
138 list.add(Pair(attr.name, attr.expression))
139 }
140 }
141 return list
142 }
143
appendAnnotationnull144 private fun appendAnnotation(
145 codebase: PsiBasedCodebase,
146 sb: StringBuilder,
147 uAnnotation: UAnnotation,
148 originalName: String?,
149 target: AnnotationTarget,
150 showDefaultAttrs: Boolean
151 ) {
152 val qualifiedName =
153 codebase.annotationManager.normalizeOutputName(originalName, target) ?: return
154
155 val attributes = getAttributes(uAnnotation, showDefaultAttrs)
156 if (attributes.isEmpty()) {
157 sb.append("@$qualifiedName")
158 return
159 }
160
161 sb.append("@")
162 sb.append(qualifiedName)
163 sb.append("(")
164 if (
165 attributes.size == 1 &&
166 (attributes[0].first == null || attributes[0].first == ANNOTATION_ATTR_VALUE)
167 ) {
168 // Special case: omit "value" if it's the only attribute
169 appendValue(codebase, sb, attributes[0].second, target, showDefaultAttrs)
170 } else {
171 var first = true
172 for (attribute in attributes) {
173 if (first) {
174 first = false
175 } else {
176 sb.append(", ")
177 }
178 sb.append(attribute.first ?: ANNOTATION_ATTR_VALUE)
179 sb.append('=')
180 appendValue(codebase, sb, attribute.second, target, showDefaultAttrs)
181 }
182 }
183 sb.append(")")
184 }
185
appendValuenull186 private fun appendValue(
187 codebase: PsiBasedCodebase,
188 sb: StringBuilder,
189 value: UExpression?,
190 target: AnnotationTarget,
191 showDefaultAttrs: Boolean
192 ) {
193 // Compute annotation string -- we don't just use value.text here
194 // because that may not use fully qualified names, e.g. the source may say
195 // @RequiresPermission(Manifest.permission.ACCESS_COARSE_LOCATION)
196 // and we want to compute
197 //
198 // @androidx.annotation.RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION)
199 when (value) {
200 null -> sb.append("null")
201 is ULiteralExpression -> sb.append(CodePrinter.constantToSource(value.value))
202 is UQualifiedReferenceExpression -> { // the value is a Foo.BAR type of reference.
203 // expand `Foo` to fully qualified name `com.example.Foo`
204 appendQualifiedName(codebase, sb, value.receiver as UReferenceExpression)
205 // append accessor `.`
206 sb.append(value.accessType.name)
207 // append `BAR`
208 sb.append(value.selector.asRenderString())
209 }
210 is UReferenceExpression -> {
211 // expand Foo to fully qualified name com.example.Foo
212 appendQualifiedName(codebase, sb, value)
213 }
214 is UBinaryExpression -> {
215 appendValue(codebase, sb, value.leftOperand, target, showDefaultAttrs)
216 sb.append(' ')
217 sb.append(value.operator.text)
218 sb.append(' ')
219 appendValue(codebase, sb, value.rightOperand, target, showDefaultAttrs)
220 }
221 is UCallExpression -> {
222 if (value.isArrayInitializer()) {
223 sb.append('{')
224 var first = true
225 for (initializer in value.valueArguments) {
226 if (first) {
227 first = false
228 } else {
229 sb.append(", ")
230 }
231 appendValue(codebase, sb, initializer, target, showDefaultAttrs)
232 }
233 sb.append('}')
234 } // TODO: support UCallExpression for other cases than array initializers
235 }
236 is UAnnotation -> {
237 appendAnnotation(
238 codebase,
239 sb,
240 value,
241 // Normalize the input name of the annotation.
242 codebase.annotationManager.normalizeInputName(value.qualifiedName!!),
243 target,
244 showDefaultAttrs
245 )
246 }
247 else -> {
248 val source = getConstantSource(value)
249 if (source != null) {
250 sb.append(source)
251 return
252 }
253 sb.append(value.sourcePsi?.text ?: value.asSourceString())
254 }
255 }
256 }
257
appendQualifiedNamenull258 private fun appendQualifiedName(
259 codebase: PsiBasedCodebase,
260 sb: StringBuilder,
261 value: UReferenceExpression
262 ) {
263 when (val resolved = value.resolve()) {
264 is PsiField -> {
265 val containing = resolved.containingClass
266 if (containing != null) {
267 // If it's a field reference, see if it looks like the field is hidden; if
268 // so, inline the value
269 val cls = codebase.findOrCreateClass(containing)
270 val initializer = resolved.initializer
271 if (initializer != null) {
272 val fieldItem = cls.findField(resolved.name)
273 if (fieldItem == null || fieldItem.isHiddenOrRemoved()) {
274 // Use the literal value instead
275 val source = getConstantSource(initializer)
276 if (source != null) {
277 sb.append(source)
278 return
279 }
280 }
281 }
282 containing.qualifiedName?.let { sb.append(it).append('.') }
283 }
284
285 sb.append(resolved.name)
286 }
287 is PsiClass -> resolved.qualifiedName?.let { sb.append(it) }
288 else -> {
289 sb.append(value.sourcePsi?.text ?: value.asSourceString())
290 }
291 }
292 }
293
getConstantSourcenull294 private fun getConstantSource(value: UExpression): String? {
295 val constant = value.evaluate()
296 return CodePrinter.constantToExpression(constant)
297 }
298
getConstantSourcenull299 private fun getConstantSource(value: PsiExpression): String? {
300 val constant = JavaConstantExpressionEvaluator.computeConstantExpression(value, false)
301 return CodePrinter.constantToExpression(constant)
302 }
303 }
304 }
305
createValuenull306 private fun createValue(codebase: PsiBasedCodebase, value: UExpression): AnnotationAttributeValue {
307 return if (value.isArrayInitializer()) {
308 val uCallExpression = value as UCallExpression
309 DefaultAnnotationArrayAttributeValue(
310 { getText(uCallExpression) },
311 { uCallExpression.valueArguments.map { createValue(codebase, it) }.toList() }
312 )
313 } else {
314 UAnnotationSingleAttributeValue(codebase, value)
315 }
316 }
317
318 internal class UAnnotationSingleAttributeValue(
319 private val codebase: PsiBasedCodebase,
320 private val psiValue: UExpression
<lambda>null321 ) : DefaultAnnotationSingleAttributeValue({ getText(psiValue) }, { getValue(psiValue) }) {
322
323 companion object {
getValuenull324 private fun getValue(psiValue: UExpression): Any? {
325 if (psiValue is ULiteralExpression) {
326 val value = psiValue.value
327 if (value != null) {
328 return value
329 } else if (psiValue.isNull) {
330 return null
331 }
332 }
333 if (psiValue is PsiLiteral) {
334 return psiValue.value ?: getText(psiValue).removeSurrounding("\"")
335 }
336
337 val value = ConstantEvaluator.evaluate(null, psiValue)
338 if (value != null) {
339 return value
340 }
341
342 if (psiValue is UClassLiteralExpression) {
343 // The value of a class literal expression like String.class or String::class
344 // is the fully qualified name, java.lang.String
345 val type = psiValue.type
346 if (type != null) {
347 return type.canonicalText
348 }
349 }
350
351 return getText(psiValue).removeSurrounding("\"")
352 }
353 }
354
resolvenull355 override fun resolve(): Item? {
356 if (psiValue is UReferenceExpression) {
357 when (val resolved = psiValue.resolve()) {
358 is PsiField -> return codebase.findField(resolved)
359 is PsiClass -> return codebase.findOrCreateClass(resolved)
360 }
361 }
362 return null
363 }
364 }
365
getTextnull366 private fun getText(element: UElement): String {
367 return element.sourcePsi?.text ?: element.asSourceString()
368 }
369