1 /* 2 * Copyright (C) 2018 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 20 import com.android.tools.lint.detector.api.ConstantEvaluator 21 import com.android.tools.metalava.model.AnnotationArrayAttributeValue 22 import com.android.tools.metalava.model.AnnotationAttribute 23 import com.android.tools.metalava.model.AnnotationAttributeValue 24 import com.android.tools.metalava.model.AnnotationItem 25 import com.android.tools.metalava.model.AnnotationSingleAttributeValue 26 import com.android.tools.metalava.model.AnnotationTarget 27 import com.android.tools.metalava.model.ClassItem 28 import com.android.tools.metalava.model.DefaultAnnotationItem 29 import com.android.tools.metalava.model.Item 30 import com.intellij.psi.PsiClass 31 import com.intellij.psi.PsiExpression 32 import com.intellij.psi.PsiField 33 import com.intellij.psi.PsiLiteral 34 import com.intellij.psi.PsiMethod 35 import com.intellij.psi.impl.JavaConstantExpressionEvaluator 36 import org.jetbrains.kotlin.asJava.elements.KtLightNullabilityAnnotation 37 import org.jetbrains.uast.UAnnotation 38 import org.jetbrains.uast.UBinaryExpression 39 import org.jetbrains.uast.UCallExpression 40 import org.jetbrains.uast.UElement 41 import org.jetbrains.uast.UExpression 42 import org.jetbrains.uast.ULiteralExpression 43 import org.jetbrains.uast.UReferenceExpression 44 import org.jetbrains.uast.util.isArrayInitializer 45 46 class UAnnotationItem private constructor( 47 override val codebase: PsiBasedCodebase, 48 val uAnnotation: UAnnotation, 49 private val originalName: String? 50 ) : DefaultAnnotationItem(codebase) { 51 private val qualifiedName = AnnotationItem.mapName(codebase, originalName) 52 53 private var attributes: List<AnnotationAttribute>? = null 54 originalNamenull55 override fun originalName(): String? = originalName 56 57 override fun toString(): String = toSource() 58 59 override fun toSource(target: AnnotationTarget): String { 60 val sb = StringBuilder(60) 61 appendAnnotation(codebase, sb, uAnnotation, originalName, target) 62 return sb.toString() 63 } 64 resolvenull65 override fun resolve(): ClassItem? { 66 return codebase.findClass(originalName ?: return null) 67 } 68 isNonNullnull69 override fun isNonNull(): Boolean { 70 if (uAnnotation.javaPsi is KtLightNullabilityAnnotation && 71 originalName == "" 72 ) { 73 // Hack/workaround: some UAST annotation nodes do not provide qualified name :=( 74 return true 75 } 76 return super.isNonNull() 77 } 78 qualifiedNamenull79 override fun qualifiedName() = qualifiedName 80 81 override fun attributes(): List<AnnotationAttribute> { 82 if (attributes == null) { 83 val uAttributes = uAnnotation.attributeValues 84 attributes = if (uAttributes.isEmpty()) { 85 emptyList() 86 } else { 87 val list = mutableListOf<AnnotationAttribute>() 88 for (parameter in uAttributes) { 89 list.add( 90 UAnnotationAttribute( 91 codebase, 92 parameter.name ?: SdkConstants.ATTR_VALUE, parameter.expression 93 ) 94 ) 95 } 96 list 97 } 98 } 99 100 return attributes!! 101 } 102 103 companion object { createnull104 fun create(codebase: PsiBasedCodebase, uAnnotation: UAnnotation, qualifiedName: String? = uAnnotation.qualifiedName): UAnnotationItem { 105 return UAnnotationItem(codebase, uAnnotation, qualifiedName) 106 } 107 createnull108 fun create(codebase: PsiBasedCodebase, original: UAnnotationItem): UAnnotationItem { 109 return UAnnotationItem(codebase, original.uAnnotation, original.originalName) 110 } 111 appendAnnotationnull112 private fun appendAnnotation( 113 codebase: PsiBasedCodebase, 114 sb: StringBuilder, 115 uAnnotation: UAnnotation, 116 originalName: String?, 117 target: AnnotationTarget 118 ) { 119 val qualifiedName = AnnotationItem.mapName(codebase, originalName, null, target) ?: return 120 121 val attributes = uAnnotation.attributeValues 122 if (attributes.isEmpty()) { 123 sb.append("@$qualifiedName") 124 return 125 } 126 127 sb.append("@") 128 sb.append(qualifiedName) 129 sb.append("(") 130 if (attributes.size == 1 && (attributes[0].name == null || attributes[0].name == SdkConstants.ATTR_VALUE)) { 131 // Special case: omit "value" if it's the only attribute 132 appendValue(codebase, sb, attributes[0].expression, target) 133 } else { 134 var first = true 135 for (attribute in attributes) { 136 if (first) { 137 first = false 138 } else { 139 sb.append(", ") 140 } 141 sb.append(attribute.name ?: SdkConstants.ATTR_VALUE) 142 sb.append('=') 143 appendValue(codebase, sb, attribute.expression, target) 144 } 145 } 146 sb.append(")") 147 } 148 appendValuenull149 private fun appendValue( 150 codebase: PsiBasedCodebase, 151 sb: StringBuilder, 152 value: UExpression?, 153 target: AnnotationTarget 154 ) { 155 // Compute annotation string -- we don't just use value.text here 156 // because that may not use fully qualified names, e.g. the source may say 157 // @RequiresPermission(Manifest.permission.ACCESS_COARSE_LOCATION) 158 // and we want to compute 159 // @android.support.annotation.RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) 160 when (value) { 161 null -> sb.append("null") 162 is ULiteralExpression -> sb.append(CodePrinter.constantToSource(value.value)) 163 is UReferenceExpression -> { 164 val resolved = value.resolve() 165 when (resolved) { 166 is PsiField -> { 167 val containing = resolved.containingClass 168 if (containing != null) { 169 // If it's a field reference, see if it looks like the field is hidden; if 170 // so, inline the value 171 val cls = codebase.findOrCreateClass(containing) 172 val initializer = resolved.initializer 173 if (initializer != null) { 174 val fieldItem = cls.findField(resolved.name) 175 if (fieldItem == null || fieldItem.isHiddenOrRemoved()) { 176 // Use the literal value instead 177 val source = getConstantSource(initializer) 178 if (source != null) { 179 sb.append(source) 180 return 181 } 182 } 183 } 184 containing.qualifiedName?.let { 185 sb.append(it).append('.') 186 } 187 } 188 189 sb.append(resolved.name) 190 } 191 is PsiClass -> resolved.qualifiedName?.let { sb.append(it) } 192 else -> { 193 sb.append(value.sourcePsi?.text ?: value.asSourceString()) 194 } 195 } 196 } 197 is UBinaryExpression -> { 198 appendValue(codebase, sb, value.leftOperand, target) 199 sb.append(' ') 200 sb.append(value.operator.text) 201 sb.append(' ') 202 appendValue(codebase, sb, value.rightOperand, target) 203 } 204 is UCallExpression -> { 205 if (value.isArrayInitializer()) { 206 sb.append('{') 207 var first = true 208 for (initializer in value.valueArguments) { 209 if (first) { 210 first = false 211 } else { 212 sb.append(", ") 213 } 214 appendValue(codebase, sb, initializer, target) 215 } 216 sb.append('}') 217 } else { 218 println("todo") 219 } 220 } 221 is UAnnotation -> { 222 appendAnnotation(codebase, sb, value, value.qualifiedName, target) 223 } 224 else -> { 225 val source = getConstantSource(value) 226 if (source != null) { 227 sb.append(source) 228 return 229 } 230 sb.append(value.sourcePsi?.text ?: value.asSourceString()) 231 } 232 } 233 } 234 getConstantSourcenull235 private fun getConstantSource(value: UExpression): String? { 236 val constant = value.evaluate() 237 return CodePrinter.constantToExpression(constant) 238 } 239 getConstantSourcenull240 private fun getConstantSource(value: PsiExpression): String? { 241 val constant = JavaConstantExpressionEvaluator.computeConstantExpression(value, false) 242 return CodePrinter.constantToExpression(constant) 243 } 244 } 245 } 246 247 class UAnnotationAttribute( 248 codebase: PsiBasedCodebase, 249 override val name: String, 250 psiValue: UExpression 251 ) : AnnotationAttribute { 252 override val value: AnnotationAttributeValue = UAnnotationValue.create( 253 codebase, psiValue 254 ) 255 } 256 257 abstract class UAnnotationValue : AnnotationAttributeValue { 258 companion object { createnull259 fun create(codebase: PsiBasedCodebase, value: UExpression): UAnnotationValue { 260 return if (value.isArrayInitializer()) { 261 UAnnotationArrayAttributeValue(codebase, value as UCallExpression) 262 } else { 263 UAnnotationSingleAttributeValue(codebase, value) 264 } 265 } 266 } 267 toStringnull268 override fun toString(): String = toSource() 269 } 270 271 class UAnnotationSingleAttributeValue( 272 private val codebase: PsiBasedCodebase, 273 private val psiValue: UExpression 274 ) : UAnnotationValue(), AnnotationSingleAttributeValue { 275 override val valueSource: String = getText(psiValue) 276 override val value: Any? 277 get() { 278 if (psiValue is ULiteralExpression) { 279 val value = psiValue.value 280 if (value != null) { 281 return value 282 } else if (psiValue.isNull) { 283 return null 284 } 285 } 286 if (psiValue is PsiLiteral) { 287 return psiValue.value ?: getText(psiValue).removeSurrounding("\"") 288 } 289 290 val value = ConstantEvaluator.evaluate(null, psiValue) 291 if (value != null) { 292 return value 293 } 294 295 return getText(psiValue).removeSurrounding("\"") 296 } 297 298 override fun value(): Any? = value 299 300 override fun toSource(): String = getText(psiValue) 301 302 override fun resolve(): Item? { 303 if (psiValue is UReferenceExpression) { 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 UAnnotationArrayAttributeValue(codebase: PsiBasedCodebase, private val value: UCallExpression) : 316 UAnnotationValue(), AnnotationArrayAttributeValue { <lambda>null317 override val values = value.valueArguments.map { 318 create(codebase, it) 319 }.toList() 320 toSourcenull321 override fun toSource(): String = getText(value) 322 } 323 324 private fun getText(element: UElement): String { 325 return element.sourcePsi?.text ?: element.asSourceString() 326 }