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.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.android.tools.metalava.model.psi.CodePrinter.Companion.constantToExpression
32 import com.android.tools.metalava.model.psi.CodePrinter.Companion.constantToSource
33 import com.intellij.psi.PsiAnnotation
34 import com.intellij.psi.PsiAnnotationMemberValue
35 import com.intellij.psi.PsiAnnotationMethod
36 import com.intellij.psi.PsiArrayInitializerMemberValue
37 import com.intellij.psi.PsiBinaryExpression
38 import com.intellij.psi.PsiClass
39 import com.intellij.psi.PsiClassObjectAccessExpression
40 import com.intellij.psi.PsiExpression
41 import com.intellij.psi.PsiField
42 import com.intellij.psi.PsiLiteral
43 import com.intellij.psi.PsiReference
44 import com.intellij.psi.impl.JavaConstantExpressionEvaluator
45 import org.jetbrains.kotlin.asJava.elements.KtLightNullabilityAnnotation
46
47 internal class PsiAnnotationItem
48 private constructor(
49 override val annotationContext: PsiBasedCodebase,
50 val psiAnnotation: PsiAnnotation,
51 originalName: String,
52 qualifiedName: String,
53 ) :
54 DefaultAnnotationItem(
55 annotationContext = annotationContext,
56 fileLocation = PsiFileLocation.fromPsiElement(psiAnnotation),
57 originalName = originalName,
58 qualifiedName = qualifiedName,
59 attributesGetter = { getAnnotationAttributes(annotationContext, psiAnnotation) },
60 ) {
61
toSourcenull62 override fun toSource(target: AnnotationTarget, showDefaultAttrs: Boolean): String {
63 val sb = StringBuilder(60)
64 appendAnnotation(
65 annotationContext,
66 sb,
67 psiAnnotation,
68 qualifiedName,
69 target,
70 showDefaultAttrs
71 )
72 return sb.toString()
73 }
74
snapshotnull75 override fun snapshot(targetCodebase: Codebase) = this
76
77 override fun isNonNull(): Boolean {
78 if (psiAnnotation is KtLightNullabilityAnnotation<*> && originalName == "") {
79 // Hack/workaround: some UAST annotation nodes do not provide qualified name :=(
80 return true
81 }
82 return super.isNonNull()
83 }
84
85 companion object {
getAnnotationAttributesnull86 private fun getAnnotationAttributes(
87 codebase: PsiBasedCodebase,
88 psiAnnotation: PsiAnnotation
89 ): List<AnnotationAttribute> =
90 psiAnnotation.parameterList.attributes
91 .mapNotNull { attribute ->
92 attribute.value?.let { value ->
93 DefaultAnnotationAttribute(
94 attribute.name ?: ANNOTATION_ATTR_VALUE,
95 createValue(codebase, value),
96 )
97 }
98 }
99 .toList()
100
createnull101 fun create(
102 codebase: PsiBasedCodebase,
103 psiAnnotation: PsiAnnotation,
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 psiAnnotation.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 PsiAnnotationItem(
116 annotationContext = codebase,
117 psiAnnotation = psiAnnotation,
118 originalName = originalName,
119 qualifiedName = qualifiedName,
120 )
121 }
122
getAttributesnull123 private fun getAttributes(
124 annotation: PsiAnnotation,
125 showDefaultAttrs: Boolean
126 ): List<Pair<String?, PsiAnnotationMemberValue?>> {
127 val annotationClass = annotation.nameReferenceElement?.resolve() as? PsiClass
128 val list = mutableListOf<Pair<String?, PsiAnnotationMemberValue?>>()
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.parameterList.attributes) {
138 list.add(Pair(attr.name, attr.value))
139 }
140 }
141 return list
142 }
143
appendAnnotationnull144 private fun appendAnnotation(
145 codebase: PsiBasedCodebase,
146 sb: StringBuilder,
147 psiAnnotation: PsiAnnotation,
148 qualifiedName: String?,
149 target: AnnotationTarget,
150 showDefaultAttrs: Boolean
151 ) {
152 val alwaysInlineValues = qualifiedName == "android.annotation.FlaggedApi"
153 val outputName =
154 codebase.annotationManager.normalizeOutputName(qualifiedName, target) ?: return
155
156 val attributes = getAttributes(psiAnnotation, showDefaultAttrs)
157 if (attributes.isEmpty()) {
158 sb.append("@$outputName")
159 return
160 }
161
162 sb.append("@")
163 sb.append(outputName)
164 sb.append("(")
165 if (
166 attributes.size == 1 &&
167 (attributes[0].first == null || attributes[0].first == ANNOTATION_ATTR_VALUE)
168 ) {
169 // Special case: omit "value" if it's the only attribute
170 appendValue(
171 codebase,
172 sb,
173 attributes[0].second,
174 target,
175 showDefaultAttrs = showDefaultAttrs,
176 alwaysInlineValues = alwaysInlineValues,
177 )
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 ?: ANNOTATION_ATTR_VALUE)
187 sb.append('=')
188 appendValue(
189 codebase,
190 sb,
191 attribute.second,
192 target,
193 showDefaultAttrs = showDefaultAttrs,
194 alwaysInlineValues = alwaysInlineValues,
195 )
196 }
197 }
198 sb.append(")")
199 }
200
appendValuenull201 private fun appendValue(
202 codebase: PsiBasedCodebase,
203 sb: StringBuilder,
204 value: PsiAnnotationMemberValue?,
205 target: AnnotationTarget,
206 showDefaultAttrs: Boolean,
207 alwaysInlineValues: Boolean,
208 ) {
209 // Compute annotation string -- we don't just use value.text here
210 // because that may not use fully qualified names, e.g. the source may say
211 // @RequiresPermission(Manifest.permission.ACCESS_COARSE_LOCATION)
212 // and we want to compute
213 //
214 // @androidx.annotation.RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION)
215 when (value) {
216 null -> sb.append("null")
217 is PsiLiteral -> sb.append(constantToSource(value.value))
218 is PsiReference -> {
219 when (val resolved = value.resolve()) {
220 is PsiField -> {
221 val containing = resolved.containingClass
222 if (containing != null) {
223 // If it's a field reference, see if it looks like the field is
224 // hidden; if
225 // so, inline the value
226 val cls = codebase.findOrCreateClass(containing)
227 val initializer = resolved.initializer
228 if (initializer != null) {
229 val fieldItem = cls.findField(resolved.name)
230 if (
231 alwaysInlineValues ||
232 fieldItem == null ||
233 fieldItem.isHiddenOrRemoved() ||
234 !fieldItem.isPublic
235 ) {
236 // Use the literal value instead
237 val source = getConstantSource(initializer)
238 if (source != null) {
239 sb.append(source)
240 return
241 }
242 }
243 }
244 containing.qualifiedName?.let { sb.append(it).append('.') }
245 }
246
247 sb.append(resolved.name)
248 }
249 is PsiClass -> resolved.qualifiedName?.let { sb.append(it) }
250 else -> {
251 sb.append(value.text)
252 }
253 }
254 }
255 is PsiBinaryExpression -> {
256 appendValue(
257 codebase,
258 sb,
259 value.lOperand,
260 target,
261 showDefaultAttrs = showDefaultAttrs,
262 alwaysInlineValues = alwaysInlineValues,
263 )
264 sb.append(' ')
265 sb.append(value.operationSign.text)
266 sb.append(' ')
267 appendValue(
268 codebase,
269 sb,
270 value.rOperand,
271 target,
272 showDefaultAttrs = showDefaultAttrs,
273 alwaysInlineValues = alwaysInlineValues,
274 )
275 }
276 is PsiArrayInitializerMemberValue -> {
277 sb.append('{')
278 var first = true
279 for (initializer in value.initializers) {
280 if (first) {
281 first = false
282 } else {
283 sb.append(", ")
284 }
285 appendValue(
286 codebase,
287 sb,
288 initializer,
289 target,
290 showDefaultAttrs = showDefaultAttrs,
291 alwaysInlineValues = alwaysInlineValues,
292 )
293 }
294 sb.append('}')
295 }
296 is PsiAnnotation -> {
297 appendAnnotation(
298 codebase,
299 sb,
300 value,
301 // Normalize the input name of the annotation.
302 codebase.annotationManager.normalizeInputName(value.qualifiedName),
303 target,
304 showDefaultAttrs
305 )
306 }
307 else -> {
308 if (value is PsiExpression) {
309 val source = getConstantSource(value)
310 if (source != null) {
311 sb.append(source)
312 return
313 }
314 }
315 sb.append(value.text)
316 }
317 }
318 }
319
getConstantSourcenull320 private fun getConstantSource(value: PsiExpression): String? {
321 val constant = JavaConstantExpressionEvaluator.computeConstantExpression(value, false)
322 return constantToExpression(constant)
323 }
324 }
325 }
326
createValuenull327 private fun createValue(
328 codebase: PsiBasedCodebase,
329 value: PsiAnnotationMemberValue
330 ): AnnotationAttributeValue {
331 return if (value is PsiArrayInitializerMemberValue) {
332 DefaultAnnotationArrayAttributeValue(
333 { value.text },
334 { value.initializers.map { createValue(codebase, it) }.toList() }
335 )
336 } else {
337 PsiAnnotationSingleAttributeValue(codebase, value)
338 }
339 }
340
341 internal class PsiAnnotationSingleAttributeValue(
342 private val codebase: PsiBasedCodebase,
343 private val psiValue: PsiAnnotationMemberValue
<lambda>null344 ) : DefaultAnnotationSingleAttributeValue({ psiValue.text }, { getValue(psiValue) }) {
345
346 companion object {
getValuenull347 private fun getValue(psiValue: PsiAnnotationMemberValue): Any {
348 if (psiValue is PsiLiteral) {
349 return psiValue.value ?: psiValue.text.removeSurrounding("\"")
350 }
351
352 val value = ConstantEvaluator.evaluate(null, psiValue)
353 if (value != null) {
354 return value
355 }
356
357 if (psiValue is PsiClassObjectAccessExpression) {
358 // The value of a class literal expression like String.class or String::class
359 // is the fully qualified name, java.lang.String
360 return psiValue.operand.type.canonicalText
361 }
362
363 return psiValue.text ?: psiValue.text.removeSurrounding("\"")
364 }
365 }
366
resolvenull367 override fun resolve(): Item? {
368 if (psiValue is PsiReference) {
369 when (val resolved = psiValue.resolve()) {
370 is PsiField -> return codebase.findField(resolved)
371 is PsiClass -> return codebase.findOrCreateClass(resolved)
372 }
373 }
374 return null
375 }
376 }
377