blob: 567a9354fc2d66f54ca4428352d12d36aec7c275 [file] [log] [blame]
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.tools.metalava.model.psi
import com.android.SdkConstants.ATTR_VALUE
import com.android.tools.lint.detector.api.ConstantEvaluator
import com.android.tools.metalava.XmlBackedAnnotationItem
import com.android.tools.metalava.model.AnnotationArrayAttributeValue
import com.android.tools.metalava.model.AnnotationAttribute
import com.android.tools.metalava.model.AnnotationAttributeValue
import com.android.tools.metalava.model.AnnotationItem
import com.android.tools.metalava.model.AnnotationSingleAttributeValue
import com.android.tools.metalava.model.AnnotationTarget
import com.android.tools.metalava.model.ClassItem
import com.android.tools.metalava.model.Codebase
import com.android.tools.metalava.model.DefaultAnnotationItem
import com.android.tools.metalava.model.Item
import com.android.tools.metalava.model.psi.CodePrinter.Companion.constantToExpression
import com.android.tools.metalava.model.psi.CodePrinter.Companion.constantToSource
import com.intellij.psi.PsiAnnotation
import com.intellij.psi.PsiAnnotationMemberValue
import com.intellij.psi.PsiAnnotationMethod
import com.intellij.psi.PsiArrayInitializerMemberValue
import com.intellij.psi.PsiBinaryExpression
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiExpression
import com.intellij.psi.PsiField
import com.intellij.psi.PsiLiteral
import com.intellij.psi.PsiMethod
import com.intellij.psi.PsiReference
import com.intellij.psi.impl.JavaConstantExpressionEvaluator
import org.jetbrains.kotlin.asJava.elements.KtLightNullabilityAnnotation
class PsiAnnotationItem private constructor(
override val codebase: PsiBasedCodebase,
val psiAnnotation: PsiAnnotation,
override val originalName: String?
) : DefaultAnnotationItem(codebase) {
override val qualifiedName: String? = AnnotationItem.mapName(codebase, originalName)
override fun toString(): String = toSource()
override fun toSource(target: AnnotationTarget, showDefaultAttrs: Boolean): String {
val sb = StringBuilder(60)
appendAnnotation(codebase, sb, psiAnnotation, originalName, target, showDefaultAttrs)
return sb.toString()
}
override fun resolve(): ClassItem? {
return codebase.findOrCreateClass(originalName ?: return null)
}
override fun isNonNull(): Boolean {
if (psiAnnotation is KtLightNullabilityAnnotation<*> &&
originalName == ""
) {
// Hack/workaround: some UAST annotation nodes do not provide qualified name :=(
return true
}
return super.isNonNull()
}
override val attributes: List<PsiAnnotationAttribute> by lazy {
psiAnnotation.parameterList.attributes.mapNotNull { attribute ->
attribute.value?.let { value ->
PsiAnnotationAttribute(codebase, attribute.name ?: ATTR_VALUE, value)
}
}.toList()
}
override val targets: Set<AnnotationTarget> by lazy {
AnnotationItem.computeTargets(this, codebase::findOrCreateClass)
}
companion object {
fun create(codebase: PsiBasedCodebase, psiAnnotation: PsiAnnotation, qualifiedName: String? = psiAnnotation.qualifiedName): PsiAnnotationItem {
return PsiAnnotationItem(codebase, psiAnnotation, qualifiedName)
}
fun create(codebase: PsiBasedCodebase, original: PsiAnnotationItem): PsiAnnotationItem {
return PsiAnnotationItem(codebase, original.psiAnnotation, original.originalName)
}
// TODO: Inline this such that instead of constructing XmlBackedAnnotationItem
// and then producing source and parsing it, produce source directly
fun create(
codebase: Codebase,
xmlAnnotation: XmlBackedAnnotationItem,
context: Item? = null
): PsiAnnotationItem {
if (codebase is PsiBasedCodebase) {
return codebase.createAnnotation(xmlAnnotation.toSource(), context)
} else {
codebase.unsupported("Converting to PSI annotation requires PSI codebase")
}
}
private fun getAttributes(annotation: PsiAnnotation, showDefaultAttrs: Boolean):
List<Pair<String?, PsiAnnotationMemberValue?>> {
val annotationClass = annotation.nameReferenceElement?.resolve() as? PsiClass
val list = mutableListOf<Pair<String?, PsiAnnotationMemberValue?>>()
if (annotationClass != null && showDefaultAttrs) {
for (method in annotationClass.methods) {
if (method !is PsiAnnotationMethod) {
continue
}
list.add(Pair(method.name, annotation.findAttributeValue(method.name)))
}
} else {
for (attr in annotation.parameterList.attributes) {
list.add(Pair(attr.name, attr.value))
}
}
return list
}
private fun appendAnnotation(
codebase: PsiBasedCodebase,
sb: StringBuilder,
psiAnnotation: PsiAnnotation,
originalName: String?,
target: AnnotationTarget,
showDefaultAttrs: Boolean
) {
val qualifiedName = AnnotationItem.mapName(codebase, originalName, null, target) ?: return
val attributes = getAttributes(psiAnnotation, showDefaultAttrs)
if (attributes.isEmpty()) {
sb.append("@$qualifiedName")
return
}
sb.append("@")
sb.append(qualifiedName)
sb.append("(")
if (attributes.size == 1 && (attributes[0].first == null || attributes[0].first == ATTR_VALUE)) {
// Special case: omit "value" if it's the only attribute
appendValue(codebase, sb, attributes[0].second, target, showDefaultAttrs)
} else {
var first = true
for (attribute in attributes) {
if (first) {
first = false
} else {
sb.append(", ")
}
sb.append(attribute.first ?: ATTR_VALUE)
sb.append('=')
appendValue(codebase, sb, attribute.second, target, showDefaultAttrs)
}
}
sb.append(")")
}
private fun appendValue(
codebase: PsiBasedCodebase,
sb: StringBuilder,
value: PsiAnnotationMemberValue?,
target: AnnotationTarget,
showDefaultAttrs: Boolean
) {
// Compute annotation string -- we don't just use value.text here
// because that may not use fully qualified names, e.g. the source may say
// @RequiresPermission(Manifest.permission.ACCESS_COARSE_LOCATION)
// and we want to compute
// @android.support.annotation.RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION)
when (value) {
null -> sb.append("null")
is PsiLiteral -> sb.append(constantToSource(value.value))
is PsiReference -> {
when (val resolved = value.resolve()) {
is PsiField -> {
val containing = resolved.containingClass
if (containing != null) {
// If it's a field reference, see if it looks like the field is hidden; if
// so, inline the value
val cls = codebase.findOrCreateClass(containing)
val initializer = resolved.initializer
if (initializer != null) {
val fieldItem = cls.findField(resolved.name)
if (fieldItem == null || fieldItem.isHiddenOrRemoved() ||
!fieldItem.isPublic
) {
// Use the literal value instead
val source = getConstantSource(initializer)
if (source != null) {
sb.append(source)
return
}
}
}
containing.qualifiedName?.let {
sb.append(it).append('.')
}
}
sb.append(resolved.name)
}
is PsiClass -> resolved.qualifiedName?.let { sb.append(it) }
else -> {
sb.append(value.text)
}
}
}
is PsiBinaryExpression -> {
appendValue(codebase, sb, value.lOperand, target, showDefaultAttrs)
sb.append(' ')
sb.append(value.operationSign.text)
sb.append(' ')
appendValue(codebase, sb, value.rOperand, target, showDefaultAttrs)
}
is PsiArrayInitializerMemberValue -> {
sb.append('{')
var first = true
for (initializer in value.initializers) {
if (first) {
first = false
} else {
sb.append(", ")
}
appendValue(codebase, sb, initializer, target, showDefaultAttrs)
}
sb.append('}')
}
is PsiAnnotation -> {
appendAnnotation(codebase, sb, value, value.qualifiedName, target, showDefaultAttrs)
}
else -> {
if (value is PsiExpression) {
val source = getConstantSource(value)
if (source != null) {
sb.append(source)
return
}
}
sb.append(value.text)
}
}
}
private fun getConstantSource(value: PsiExpression): String? {
val constant = JavaConstantExpressionEvaluator.computeConstantExpression(value, false)
return constantToExpression(constant)
}
}
}
class PsiAnnotationAttribute(
codebase: PsiBasedCodebase,
override val name: String,
psiValue: PsiAnnotationMemberValue
) : AnnotationAttribute {
override val value: AnnotationAttributeValue = PsiAnnotationValue.create(
codebase, psiValue
)
}
abstract class PsiAnnotationValue : AnnotationAttributeValue {
companion object {
fun create(codebase: PsiBasedCodebase, value: PsiAnnotationMemberValue): PsiAnnotationValue {
return if (value is PsiArrayInitializerMemberValue) {
PsiAnnotationArrayAttributeValue(codebase, value)
} else {
PsiAnnotationSingleAttributeValue(codebase, value)
}
}
}
override fun toString(): String = toSource()
}
class PsiAnnotationSingleAttributeValue(
private val codebase: PsiBasedCodebase,
private val psiValue: PsiAnnotationMemberValue
) : PsiAnnotationValue(), AnnotationSingleAttributeValue {
override val valueSource: String = psiValue.text
override val value: Any?
get() {
if (psiValue is PsiLiteral) {
return psiValue.value ?: psiValue.text.removeSurrounding("\"")
}
val value = ConstantEvaluator.evaluate(null, psiValue)
if (value != null) {
return value
}
return psiValue.text ?: psiValue.text.removeSurrounding("\"")
}
override fun value(): Any? = value
override fun toSource(): String = psiValue.text
override fun resolve(): Item? {
if (psiValue is PsiReference) {
when (val resolved = psiValue.resolve()) {
is PsiField -> return codebase.findField(resolved)
is PsiClass -> return codebase.findOrCreateClass(resolved)
is PsiMethod -> return codebase.findMethod(resolved)
}
}
return null
}
}
class PsiAnnotationArrayAttributeValue(codebase: PsiBasedCodebase, private val value: PsiArrayInitializerMemberValue) :
PsiAnnotationValue(), AnnotationArrayAttributeValue {
override val values = value.initializers.map {
create(codebase, it)
}.toList()
override fun toSource(): String = value.text
}