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