| /* |
| * Copyright (C) 2018 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.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.DefaultAnnotationItem |
| import com.android.tools.metalava.model.Item |
| import com.intellij.psi.PsiAnnotationMethod |
| 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.impl.JavaConstantExpressionEvaluator |
| import org.jetbrains.kotlin.asJava.elements.KtLightNullabilityAnnotation |
| import org.jetbrains.uast.UAnnotation |
| import org.jetbrains.uast.UBinaryExpression |
| import org.jetbrains.uast.UCallExpression |
| import org.jetbrains.uast.UElement |
| import org.jetbrains.uast.UExpression |
| import org.jetbrains.uast.ULiteralExpression |
| import org.jetbrains.uast.UReferenceExpression |
| import org.jetbrains.uast.util.isArrayInitializer |
| |
| class UAnnotationItem private constructor( |
| override val codebase: PsiBasedCodebase, |
| val uAnnotation: UAnnotation, |
| 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, uAnnotation, originalName, target, showDefaultAttrs) |
| return sb.toString() |
| } |
| |
| override fun resolve(): ClassItem? { |
| return codebase.findOrCreateClass(originalName ?: return null) |
| } |
| |
| override fun isNonNull(): Boolean { |
| if (uAnnotation.javaPsi is KtLightNullabilityAnnotation<*> && |
| originalName == "" |
| ) { |
| // Hack/workaround: some UAST annotation nodes do not provide qualified name :=( |
| return true |
| } |
| return super.isNonNull() |
| } |
| |
| override val attributes: List<UAnnotationAttribute> by lazy { |
| uAnnotation.attributeValues.map { attribute -> |
| UAnnotationAttribute(codebase, attribute.name ?: ATTR_VALUE, attribute.expression) |
| }.toList() |
| } |
| |
| override val targets: Set<AnnotationTarget> by lazy { |
| AnnotationItem.computeTargets(this, codebase::findOrCreateClass) |
| } |
| |
| companion object { |
| fun create(codebase: PsiBasedCodebase, uAnnotation: UAnnotation, qualifiedName: String? = uAnnotation.qualifiedName): UAnnotationItem { |
| return UAnnotationItem(codebase, uAnnotation, qualifiedName) |
| } |
| |
| fun create(codebase: PsiBasedCodebase, original: UAnnotationItem): UAnnotationItem { |
| return UAnnotationItem(codebase, original.uAnnotation, original.originalName) |
| } |
| |
| private fun getAttributes(annotation: UAnnotation, showDefaultAttrs: Boolean): |
| List<Pair<String?, UExpression?>> { |
| val annotationClass = annotation.javaPsi?.nameReferenceElement?.resolve() as? PsiClass |
| val list = mutableListOf<Pair<String?, UExpression?>>() |
| 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.attributeValues) { |
| list.add(Pair(attr.name, attr.expression)) |
| } |
| } |
| return list |
| } |
| |
| private fun appendAnnotation( |
| codebase: PsiBasedCodebase, |
| sb: StringBuilder, |
| uAnnotation: UAnnotation, |
| originalName: String?, |
| target: AnnotationTarget, |
| showDefaultAttrs: Boolean |
| ) { |
| val qualifiedName = AnnotationItem.mapName(codebase, originalName, null, target) ?: return |
| |
| val attributes = getAttributes(uAnnotation, 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: UExpression?, |
| 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 ULiteralExpression -> sb.append(CodePrinter.constantToSource(value.value)) |
| is UReferenceExpression -> { |
| 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()) { |
| // 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.sourcePsi?.text ?: value.asSourceString()) |
| } |
| } |
| } |
| is UBinaryExpression -> { |
| appendValue(codebase, sb, value.leftOperand, target, showDefaultAttrs) |
| sb.append(' ') |
| sb.append(value.operator.text) |
| sb.append(' ') |
| appendValue(codebase, sb, value.rightOperand, target, showDefaultAttrs) |
| } |
| is UCallExpression -> { |
| if (value.isArrayInitializer()) { |
| sb.append('{') |
| var first = true |
| for (initializer in value.valueArguments) { |
| if (first) { |
| first = false |
| } else { |
| sb.append(", ") |
| } |
| appendValue(codebase, sb, initializer, target, showDefaultAttrs) |
| } |
| sb.append('}') |
| } // TODO: support UCallExpression for other cases than array initializers |
| } |
| is UAnnotation -> { |
| appendAnnotation(codebase, sb, value, value.qualifiedName, target, showDefaultAttrs) |
| } |
| else -> { |
| val source = getConstantSource(value) |
| if (source != null) { |
| sb.append(source) |
| return |
| } |
| sb.append(value.sourcePsi?.text ?: value.asSourceString()) |
| } |
| } |
| } |
| |
| private fun getConstantSource(value: UExpression): String? { |
| val constant = value.evaluate() |
| return CodePrinter.constantToExpression(constant) |
| } |
| |
| private fun getConstantSource(value: PsiExpression): String? { |
| val constant = JavaConstantExpressionEvaluator.computeConstantExpression(value, false) |
| return CodePrinter.constantToExpression(constant) |
| } |
| } |
| } |
| |
| class UAnnotationAttribute( |
| codebase: PsiBasedCodebase, |
| override val name: String, |
| psiValue: UExpression |
| ) : AnnotationAttribute { |
| override val value: AnnotationAttributeValue = UAnnotationValue.create( |
| codebase, psiValue |
| ) |
| } |
| |
| abstract class UAnnotationValue : AnnotationAttributeValue { |
| companion object { |
| fun create(codebase: PsiBasedCodebase, value: UExpression): UAnnotationValue { |
| return if (value.isArrayInitializer()) { |
| UAnnotationArrayAttributeValue(codebase, value as UCallExpression) |
| } else { |
| UAnnotationSingleAttributeValue(codebase, value) |
| } |
| } |
| } |
| |
| override fun toString(): String = toSource() |
| } |
| |
| class UAnnotationSingleAttributeValue( |
| private val codebase: PsiBasedCodebase, |
| private val psiValue: UExpression |
| ) : UAnnotationValue(), AnnotationSingleAttributeValue { |
| override val valueSource: String = getText(psiValue) |
| override val value: Any? |
| get() { |
| if (psiValue is ULiteralExpression) { |
| val value = psiValue.value |
| if (value != null) { |
| return value |
| } else if (psiValue.isNull) { |
| return null |
| } |
| } |
| if (psiValue is PsiLiteral) { |
| return psiValue.value ?: getText(psiValue).removeSurrounding("\"") |
| } |
| |
| val value = ConstantEvaluator.evaluate(null, psiValue) |
| if (value != null) { |
| return value |
| } |
| |
| return getText(psiValue).removeSurrounding("\"") |
| } |
| |
| override fun value(): Any? = value |
| |
| override fun toSource(): String = getText(psiValue) |
| |
| override fun resolve(): Item? { |
| if (psiValue is UReferenceExpression) { |
| 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 UAnnotationArrayAttributeValue(codebase: PsiBasedCodebase, private val value: UCallExpression) : |
| UAnnotationValue(), AnnotationArrayAttributeValue { |
| override val values = value.valueArguments.map { |
| create(codebase, it) |
| }.toList() |
| |
| override fun toSource(): String = getText(value) |
| } |
| |
| private fun getText(element: UElement): String { |
| return element.sourcePsi?.text ?: element.asSourceString() |
| } |