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