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