• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.SdkConstants.ATTR_VALUE
20 import com.android.tools.lint.detector.api.ConstantEvaluator
21 import com.android.tools.metalava.XmlBackedAnnotationItem
22 import com.android.tools.metalava.model.AnnotationArrayAttributeValue
23 import com.android.tools.metalava.model.AnnotationAttribute
24 import com.android.tools.metalava.model.AnnotationAttributeValue
25 import com.android.tools.metalava.model.AnnotationItem
26 import com.android.tools.metalava.model.AnnotationSingleAttributeValue
27 import com.android.tools.metalava.model.AnnotationTarget
28 import com.android.tools.metalava.model.ClassItem
29 import com.android.tools.metalava.model.Codebase
30 import com.android.tools.metalava.model.DefaultAnnotationItem
31 import com.android.tools.metalava.model.Item
32 import com.android.tools.metalava.model.psi.CodePrinter.Companion.constantToExpression
33 import com.android.tools.metalava.model.psi.CodePrinter.Companion.constantToSource
34 import com.intellij.psi.PsiAnnotation
35 import com.intellij.psi.PsiAnnotationMemberValue
36 import com.intellij.psi.PsiAnnotationMethod
37 import com.intellij.psi.PsiArrayInitializerMemberValue
38 import com.intellij.psi.PsiBinaryExpression
39 import com.intellij.psi.PsiClass
40 import com.intellij.psi.PsiClassObjectAccessExpression
41 import com.intellij.psi.PsiExpression
42 import com.intellij.psi.PsiField
43 import com.intellij.psi.PsiLiteral
44 import com.intellij.psi.PsiMethod
45 import com.intellij.psi.PsiReference
46 import com.intellij.psi.impl.JavaConstantExpressionEvaluator
47 import org.jetbrains.kotlin.asJava.elements.KtLightNullabilityAnnotation
48 
49 class PsiAnnotationItem private constructor(
50     override val codebase: PsiBasedCodebase,
51     val psiAnnotation: PsiAnnotation,
52     override val originalName: String?
53 ) : DefaultAnnotationItem(codebase) {
54     override val qualifiedName: String? = AnnotationItem.mapName(codebase, originalName)
55 
56     override fun toString(): String = toSource()
57 
58     override fun toSource(target: AnnotationTarget, showDefaultAttrs: Boolean): String {
59         val sb = StringBuilder(60)
60         appendAnnotation(codebase, sb, psiAnnotation, originalName, target, showDefaultAttrs)
61         return sb.toString()
62     }
63 
64     override fun resolve(): ClassItem? {
65         return codebase.findOrCreateClass(originalName ?: return null)
66     }
67 
68     override fun isNonNull(): Boolean {
69         if (psiAnnotation is KtLightNullabilityAnnotation<*> &&
70             originalName == ""
71         ) {
72             // Hack/workaround: some UAST annotation nodes do not provide qualified name :=(
73             return true
74         }
75         return super.isNonNull()
76     }
77 
78     override val attributes: List<PsiAnnotationAttribute> by lazy {
79         psiAnnotation.parameterList.attributes.mapNotNull { attribute ->
80             attribute.value?.let { value ->
81                 PsiAnnotationAttribute(codebase, attribute.name ?: ATTR_VALUE, value)
82             }
83         }.toList()
84     }
85 
86     override val targets: Set<AnnotationTarget> by lazy {
87         AnnotationItem.computeTargets(this, codebase::findOrCreateClass)
88     }
89 
90     companion object {
91         fun create(codebase: PsiBasedCodebase, psiAnnotation: PsiAnnotation, qualifiedName: String? = psiAnnotation.qualifiedName): PsiAnnotationItem {
92             return PsiAnnotationItem(codebase, psiAnnotation, qualifiedName)
93         }
94 
95         fun create(codebase: PsiBasedCodebase, original: PsiAnnotationItem): PsiAnnotationItem {
96             return PsiAnnotationItem(codebase, original.psiAnnotation, original.originalName)
97         }
98 
99         // TODO: Inline this such that instead of constructing XmlBackedAnnotationItem
100         // and then producing source and parsing it, produce source directly
101         fun create(
102             codebase: Codebase,
103             xmlAnnotation: XmlBackedAnnotationItem,
104             context: Item? = null
105         ): PsiAnnotationItem {
106             if (codebase is PsiBasedCodebase) {
107                 return codebase.createAnnotation(xmlAnnotation.toSource(), context)
108             } else {
109                 codebase.unsupported("Converting to PSI annotation requires PSI codebase")
110             }
111         }
112 
113         private fun getAttributes(annotation: PsiAnnotation, showDefaultAttrs: Boolean):
114             List<Pair<String?, PsiAnnotationMemberValue?>> {
115             val annotationClass = annotation.nameReferenceElement?.resolve() as? PsiClass
116             val list = mutableListOf<Pair<String?, PsiAnnotationMemberValue?>>()
117             if (annotationClass != null && showDefaultAttrs) {
118                 for (method in annotationClass.methods) {
119                     if (method !is PsiAnnotationMethod) {
120                         continue
121                     }
122                     list.add(Pair(method.name, annotation.findAttributeValue(method.name)))
123                 }
124             } else {
125                 for (attr in annotation.parameterList.attributes) {
126                     list.add(Pair(attr.name, attr.value))
127                 }
128             }
129             return list
130         }
131 
132         private fun appendAnnotation(
133             codebase: PsiBasedCodebase,
134             sb: StringBuilder,
135             psiAnnotation: PsiAnnotation,
136             originalName: String?,
137             target: AnnotationTarget,
138             showDefaultAttrs: Boolean
139         ) {
140             val qualifiedName = AnnotationItem.mapName(codebase, originalName, null, target) ?: return
141 
142             val attributes = getAttributes(psiAnnotation, showDefaultAttrs)
143             if (attributes.isEmpty()) {
144                 sb.append("@$qualifiedName")
145                 return
146             }
147 
148             sb.append("@")
149             sb.append(qualifiedName)
150             sb.append("(")
151             if (attributes.size == 1 && (attributes[0].first == null || attributes[0].first == ATTR_VALUE)) {
152                 // Special case: omit "value" if it's the only attribute
153                 appendValue(codebase, sb, attributes[0].second, target, showDefaultAttrs)
154             } else {
155                 var first = true
156                 for (attribute in attributes) {
157                     if (first) {
158                         first = false
159                     } else {
160                         sb.append(", ")
161                     }
162                     sb.append(attribute.first ?: ATTR_VALUE)
163                     sb.append('=')
164                     appendValue(codebase, sb, attribute.second, target, showDefaultAttrs)
165                 }
166             }
167             sb.append(")")
168         }
169 
170         private fun appendValue(
171             codebase: PsiBasedCodebase,
172             sb: StringBuilder,
173             value: PsiAnnotationMemberValue?,
174             target: AnnotationTarget,
175             showDefaultAttrs: Boolean
176         ) {
177             // Compute annotation string -- we don't just use value.text here
178             // because that may not use fully qualified names, e.g. the source may say
179             //  @RequiresPermission(Manifest.permission.ACCESS_COARSE_LOCATION)
180             // and we want to compute
181             //  @androidx.annotation.RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION)
182             when (value) {
183                 null -> sb.append("null")
184                 is PsiLiteral -> sb.append(constantToSource(value.value))
185                 is PsiReference -> {
186                     when (val resolved = value.resolve()) {
187                         is PsiField -> {
188                             val containing = resolved.containingClass
189                             if (containing != null) {
190                                 // If it's a field reference, see if it looks like the field is hidden; if
191                                 // so, inline the value
192                                 val cls = codebase.findOrCreateClass(containing)
193                                 val initializer = resolved.initializer
194                                 if (initializer != null) {
195                                     val fieldItem = cls.findField(resolved.name)
196                                     if (fieldItem == null || fieldItem.isHiddenOrRemoved() ||
197                                         !fieldItem.isPublic
198                                     ) {
199                                         // Use the literal value instead
200                                         val source = getConstantSource(initializer)
201                                         if (source != null) {
202                                             sb.append(source)
203                                             return
204                                         }
205                                     }
206                                 }
207                                 containing.qualifiedName?.let {
208                                     sb.append(it).append('.')
209                                 }
210                             }
211 
212                             sb.append(resolved.name)
213                         }
214                         is PsiClass -> resolved.qualifiedName?.let { sb.append(it) }
215                         else -> {
216                             sb.append(value.text)
217                         }
218                     }
219                 }
220                 is PsiBinaryExpression -> {
221                     appendValue(codebase, sb, value.lOperand, target, showDefaultAttrs)
222                     sb.append(' ')
223                     sb.append(value.operationSign.text)
224                     sb.append(' ')
225                     appendValue(codebase, sb, value.rOperand, target, showDefaultAttrs)
226                 }
227                 is PsiArrayInitializerMemberValue -> {
228                     sb.append('{')
229                     var first = true
230                     for (initializer in value.initializers) {
231                         if (first) {
232                             first = false
233                         } else {
234                             sb.append(", ")
235                         }
236                         appendValue(codebase, sb, initializer, target, showDefaultAttrs)
237                     }
238                     sb.append('}')
239                 }
240                 is PsiAnnotation -> {
241                     appendAnnotation(codebase, sb, value, value.qualifiedName, target, showDefaultAttrs)
242                 }
243                 else -> {
244                     if (value is PsiExpression) {
245                         val source = getConstantSource(value)
246                         if (source != null) {
247                             sb.append(source)
248                             return
249                         }
250                     }
251                     sb.append(value.text)
252                 }
253             }
254         }
255 
256         private fun getConstantSource(value: PsiExpression): String? {
257             val constant = JavaConstantExpressionEvaluator.computeConstantExpression(value, false)
258             return constantToExpression(constant)
259         }
260     }
261 }
262 
263 class PsiAnnotationAttribute(
264     codebase: PsiBasedCodebase,
265     override val name: String,
266     psiValue: PsiAnnotationMemberValue
267 ) : AnnotationAttribute {
268     override val value: AnnotationAttributeValue = PsiAnnotationValue.create(
269         codebase, psiValue
270     )
271 }
272 
273 abstract class PsiAnnotationValue : AnnotationAttributeValue {
274     companion object {
createnull275         fun create(codebase: PsiBasedCodebase, value: PsiAnnotationMemberValue): PsiAnnotationValue {
276             return if (value is PsiArrayInitializerMemberValue) {
277                 PsiAnnotationArrayAttributeValue(codebase, value)
278             } else {
279                 PsiAnnotationSingleAttributeValue(codebase, value)
280             }
281         }
282     }
283 
toStringnull284     override fun toString(): String = toSource()
285 }
286 
287 class PsiAnnotationSingleAttributeValue(
288     private val codebase: PsiBasedCodebase,
289     private val psiValue: PsiAnnotationMemberValue
290 ) : PsiAnnotationValue(), AnnotationSingleAttributeValue {
291     override val valueSource: String = psiValue.text
292     override val value: Any?
293         get() {
294             if (psiValue is PsiLiteral) {
295                 return psiValue.value ?: psiValue.text.removeSurrounding("\"")
296             }
297 
298             val value = ConstantEvaluator.evaluate(null, psiValue)
299             if (value != null) {
300                 return value
301             }
302 
303             if (psiValue is PsiClassObjectAccessExpression) {
304                 // The value of a class literal expression like String.class or String::class
305                 // is the fully qualified name, java.lang.String
306                 return psiValue.operand.type.canonicalText
307             }
308 
309             return psiValue.text ?: psiValue.text.removeSurrounding("\"")
310         }
311 
312     override fun value(): Any? = value
313 
314     override fun toSource(): String = psiValue.text
315 
316     override fun resolve(): Item? {
317         if (psiValue is PsiReference) {
318             when (val resolved = psiValue.resolve()) {
319                 is PsiField -> return codebase.findField(resolved)
320                 is PsiClass -> return codebase.findOrCreateClass(resolved)
321                 is PsiMethod -> return codebase.findMethod(resolved)
322             }
323         }
324         return null
325     }
326 }
327 
328 class PsiAnnotationArrayAttributeValue(codebase: PsiBasedCodebase, private val value: PsiArrayInitializerMemberValue) :
329     PsiAnnotationValue(), AnnotationArrayAttributeValue {
<lambda>null330     override val values = value.initializers.map {
331         create(codebase, it)
332     }.toList()
333 
toSourcenull334     override fun toSource(): String = value.text
335 }
336