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.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.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.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
49 private constructor(
50 override val codebase: PsiBasedCodebase,
51 val psiAnnotation: PsiAnnotation,
52 originalName: String?
53 ) :
54 DefaultAnnotationItem(
55 codebase,
56 originalName,
57 { getAnnotationAttributes(codebase, psiAnnotation) }
58 ) {
59
toSourcenull60 override fun toSource(target: AnnotationTarget, showDefaultAttrs: Boolean): String {
61 val sb = StringBuilder(60)
62 appendAnnotation(codebase, sb, psiAnnotation, qualifiedName, target, showDefaultAttrs)
63 return sb.toString()
64 }
65
resolvenull66 override fun resolve(): ClassItem? {
67 return codebase.findOrCreateClass(originalName ?: return null)
68 }
69
isNonNullnull70 override fun isNonNull(): Boolean {
71 if (psiAnnotation is KtLightNullabilityAnnotation<*> && originalName == "") {
72 // Hack/workaround: some UAST annotation nodes do not provide qualified name :=(
73 return true
74 }
75 return super.isNonNull()
76 }
77
<lambda>null78 override val targets: Set<AnnotationTarget> by lazy {
79 codebase.annotationManager.computeTargets(this, codebase::findOrCreateClass)
80 }
81
82 companion object {
getAnnotationAttributesnull83 private fun getAnnotationAttributes(
84 codebase: PsiBasedCodebase,
85 psiAnnotation: PsiAnnotation
86 ): List<AnnotationAttribute> =
87 psiAnnotation.parameterList.attributes
88 .mapNotNull { attribute ->
89 attribute.value?.let { value ->
90 DefaultAnnotationAttribute(
91 attribute.name ?: ANNOTATION_ATTR_VALUE,
92 createValue(codebase, value),
93 )
94 }
95 }
96 .toList()
97
createnull98 fun create(
99 codebase: PsiBasedCodebase,
100 psiAnnotation: PsiAnnotation,
101 qualifiedName: String? = psiAnnotation.qualifiedName
102 ): AnnotationItem {
103 return PsiAnnotationItem(codebase, psiAnnotation, qualifiedName)
104 }
105
getAttributesnull106 private fun getAttributes(
107 annotation: PsiAnnotation,
108 showDefaultAttrs: Boolean
109 ): List<Pair<String?, PsiAnnotationMemberValue?>> {
110 val annotationClass = annotation.nameReferenceElement?.resolve() as? PsiClass
111 val list = mutableListOf<Pair<String?, PsiAnnotationMemberValue?>>()
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.parameterList.attributes) {
121 list.add(Pair(attr.name, attr.value))
122 }
123 }
124 return list
125 }
126
appendAnnotationnull127 private fun appendAnnotation(
128 codebase: PsiBasedCodebase,
129 sb: StringBuilder,
130 psiAnnotation: PsiAnnotation,
131 qualifiedName: String?,
132 target: AnnotationTarget,
133 showDefaultAttrs: Boolean
134 ) {
135 val alwaysInlineValues = qualifiedName == "android.annotation.FlaggedApi"
136 val outputName =
137 codebase.annotationManager.normalizeOutputName(qualifiedName, target) ?: return
138
139 val attributes = getAttributes(psiAnnotation, showDefaultAttrs)
140 if (attributes.isEmpty()) {
141 sb.append("@$outputName")
142 return
143 }
144
145 sb.append("@")
146 sb.append(outputName)
147 sb.append("(")
148 if (
149 attributes.size == 1 &&
150 (attributes[0].first == null || attributes[0].first == ANNOTATION_ATTR_VALUE)
151 ) {
152 // Special case: omit "value" if it's the only attribute
153 appendValue(
154 codebase,
155 sb,
156 attributes[0].second,
157 target,
158 showDefaultAttrs = showDefaultAttrs,
159 alwaysInlineValues = alwaysInlineValues,
160 )
161 } else {
162 var first = true
163 for (attribute in attributes) {
164 if (first) {
165 first = false
166 } else {
167 sb.append(", ")
168 }
169 sb.append(attribute.first ?: ANNOTATION_ATTR_VALUE)
170 sb.append('=')
171 appendValue(
172 codebase,
173 sb,
174 attribute.second,
175 target,
176 showDefaultAttrs = showDefaultAttrs,
177 alwaysInlineValues = alwaysInlineValues,
178 )
179 }
180 }
181 sb.append(")")
182 }
183
appendValuenull184 private fun appendValue(
185 codebase: PsiBasedCodebase,
186 sb: StringBuilder,
187 value: PsiAnnotationMemberValue?,
188 target: AnnotationTarget,
189 showDefaultAttrs: Boolean,
190 alwaysInlineValues: Boolean,
191 ) {
192 // Compute annotation string -- we don't just use value.text here
193 // because that may not use fully qualified names, e.g. the source may say
194 // @RequiresPermission(Manifest.permission.ACCESS_COARSE_LOCATION)
195 // and we want to compute
196 //
197 // @androidx.annotation.RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION)
198 when (value) {
199 null -> sb.append("null")
200 is PsiLiteral -> sb.append(constantToSource(value.value))
201 is PsiReference -> {
202 when (val resolved = value.resolve()) {
203 is PsiField -> {
204 val containing = resolved.containingClass
205 if (containing != null) {
206 // If it's a field reference, see if it looks like the field is
207 // hidden; if
208 // so, inline the value
209 val cls = codebase.findOrCreateClass(containing)
210 val initializer = resolved.initializer
211 if (initializer != null) {
212 val fieldItem = cls.findField(resolved.name)
213 if (
214 alwaysInlineValues ||
215 fieldItem == null ||
216 fieldItem.isHiddenOrRemoved() ||
217 !fieldItem.isPublic
218 ) {
219 // Use the literal value instead
220 val source = getConstantSource(initializer)
221 if (source != null) {
222 sb.append(source)
223 return
224 }
225 }
226 }
227 containing.qualifiedName?.let { sb.append(it).append('.') }
228 }
229
230 sb.append(resolved.name)
231 }
232 is PsiClass -> resolved.qualifiedName?.let { sb.append(it) }
233 else -> {
234 sb.append(value.text)
235 }
236 }
237 }
238 is PsiBinaryExpression -> {
239 appendValue(
240 codebase,
241 sb,
242 value.lOperand,
243 target,
244 showDefaultAttrs = showDefaultAttrs,
245 alwaysInlineValues = alwaysInlineValues,
246 )
247 sb.append(' ')
248 sb.append(value.operationSign.text)
249 sb.append(' ')
250 appendValue(
251 codebase,
252 sb,
253 value.rOperand,
254 target,
255 showDefaultAttrs = showDefaultAttrs,
256 alwaysInlineValues = alwaysInlineValues,
257 )
258 }
259 is PsiArrayInitializerMemberValue -> {
260 sb.append('{')
261 var first = true
262 for (initializer in value.initializers) {
263 if (first) {
264 first = false
265 } else {
266 sb.append(", ")
267 }
268 appendValue(
269 codebase,
270 sb,
271 initializer,
272 target,
273 showDefaultAttrs = showDefaultAttrs,
274 alwaysInlineValues = alwaysInlineValues,
275 )
276 }
277 sb.append('}')
278 }
279 is PsiAnnotation -> {
280 appendAnnotation(
281 codebase,
282 sb,
283 value,
284 // Normalize the input name of the annotation.
285 codebase.annotationManager.normalizeInputName(value.qualifiedName),
286 target,
287 showDefaultAttrs
288 )
289 }
290 else -> {
291 if (value is PsiExpression) {
292 val source = getConstantSource(value)
293 if (source != null) {
294 sb.append(source)
295 return
296 }
297 }
298 sb.append(value.text)
299 }
300 }
301 }
302
getConstantSourcenull303 private fun getConstantSource(value: PsiExpression): String? {
304 val constant = JavaConstantExpressionEvaluator.computeConstantExpression(value, false)
305 return constantToExpression(constant)
306 }
307 }
308 }
309
createValuenull310 private fun createValue(
311 codebase: PsiBasedCodebase,
312 value: PsiAnnotationMemberValue
313 ): AnnotationAttributeValue {
314 return if (value is PsiArrayInitializerMemberValue) {
315 DefaultAnnotationArrayAttributeValue(
316 { value.text },
317 { value.initializers.map { createValue(codebase, it) }.toList() }
318 )
319 } else {
320 PsiAnnotationSingleAttributeValue(codebase, value)
321 }
322 }
323
324 class PsiAnnotationSingleAttributeValue(
325 private val codebase: PsiBasedCodebase,
326 private val psiValue: PsiAnnotationMemberValue
<lambda>null327 ) : DefaultAnnotationSingleAttributeValue({ psiValue.text }, { getValue(psiValue) }) {
328
329 companion object {
getValuenull330 private fun getValue(psiValue: PsiAnnotationMemberValue): Any {
331 if (psiValue is PsiLiteral) {
332 return psiValue.value ?: psiValue.text.removeSurrounding("\"")
333 }
334
335 val value = ConstantEvaluator.evaluate(null, psiValue)
336 if (value != null) {
337 return value
338 }
339
340 if (psiValue is PsiClassObjectAccessExpression) {
341 // The value of a class literal expression like String.class or String::class
342 // is the fully qualified name, java.lang.String
343 return psiValue.operand.type.canonicalText
344 }
345
346 return psiValue.text ?: psiValue.text.removeSurrounding("\"")
347 }
348 }
349
resolvenull350 override fun resolve(): Item? {
351 if (psiValue is PsiReference) {
352 when (val resolved = psiValue.resolve()) {
353 is PsiField -> return codebase.findField(resolved)
354 is PsiClass -> return codebase.findOrCreateClass(resolved)
355 is PsiMethod -> return codebase.findMethod(resolved)
356 }
357 }
358 return null
359 }
360 }
361