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