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.tools.metalava.compatibility 20 import com.android.tools.metalava.model.AnnotationTarget 21 import com.android.tools.metalava.model.ClassItem 22 import com.android.tools.metalava.model.MethodItem 23 import com.android.tools.metalava.model.ModifierList 24 import com.android.tools.metalava.model.ParameterItem 25 import com.android.tools.metalava.model.TypeItem 26 import com.android.tools.metalava.model.TypeParameterList 27 import com.intellij.psi.PsiAnnotationMethod 28 import com.intellij.psi.PsiArrayType 29 import com.intellij.psi.PsiClass 30 import com.intellij.psi.PsiMethod 31 import com.intellij.psi.util.PsiTypesUtil 32 import com.intellij.psi.util.TypeConversionUtil 33 import org.intellij.lang.annotations.Language 34 import org.jetbrains.kotlin.psi.KtNamedFunction 35 import org.jetbrains.kotlin.psi.KtProperty 36 import org.jetbrains.uast.UClass 37 import org.jetbrains.uast.UElement 38 import org.jetbrains.uast.UExpression 39 import org.jetbrains.uast.UMethod 40 import org.jetbrains.uast.UThrowExpression 41 import org.jetbrains.uast.UTryExpression 42 import org.jetbrains.uast.UastFacade 43 import org.jetbrains.uast.getParentOfType 44 import org.jetbrains.uast.kotlin.declarations.KotlinUMethod 45 import org.jetbrains.uast.visitor.AbstractUastVisitor 46 import java.io.StringWriter 47 48 open class PsiMethodItem( 49 override val codebase: PsiBasedCodebase, 50 val psiMethod: PsiMethod, 51 private val containingClass: PsiClassItem, 52 private val name: String, 53 modifiers: PsiModifierItem, 54 documentation: String, 55 private val returnType: PsiTypeItem, 56 private val parameters: List<PsiParameterItem> 57 ) : 58 PsiItem( 59 codebase = codebase, 60 modifiers = modifiers, 61 documentation = documentation, 62 element = psiMethod 63 ), MethodItem { 64 65 init { 66 for (parameter in parameters) { 67 @Suppress("LeakingThis") 68 parameter.containingMethod = this 69 } 70 } 71 72 /** 73 * If this item was created by filtering down a different codebase, this temporarily 74 * points to the original item during construction. This is used to let us initialize 75 * for example throws lists later, when all classes in the codebase have been 76 * initialized. 77 */ 78 internal var source: PsiMethodItem? = null 79 80 override var inheritedMethod: Boolean = false 81 override var inheritedFrom: ClassItem? = null 82 83 override fun name(): String = name 84 override fun containingClass(): PsiClassItem = containingClass 85 86 override fun equals(other: Any?): Boolean { 87 // TODO: Allow mix and matching with other MethodItems? 88 if (this === other) return true 89 if (javaClass != other?.javaClass) return false 90 91 other as PsiMethodItem 92 93 if (psiMethod != other.psiMethod) return false 94 95 return true 96 } 97 98 override fun hashCode(): Int { 99 return psiMethod.hashCode() 100 } 101 102 override fun isConstructor(): Boolean = false 103 104 override fun isImplicitConstructor(): Boolean = false 105 106 override fun returnType(): TypeItem? = returnType 107 108 override fun parameters(): List<ParameterItem> = parameters 109 110 override val synthetic: Boolean get() = isEnumSyntheticMethod() 111 112 private var superMethods: List<MethodItem>? = null 113 override fun superMethods(): List<MethodItem> { 114 if (superMethods == null) { 115 val result = mutableListOf<MethodItem>() 116 psiMethod.findSuperMethods().mapTo(result) { codebase.findMethod(it) } 117 superMethods = result 118 } 119 120 return superMethods!! 121 } 122 123 override fun typeParameterList(): TypeParameterList { 124 if (psiMethod.hasTypeParameters()) { 125 return PsiTypeParameterList( 126 codebase, psiMethod.typeParameterList 127 ?: return TypeParameterList.NONE 128 ) 129 } else { 130 return TypeParameterList.NONE 131 } 132 } 133 134 override fun typeArgumentClasses(): List<ClassItem> { 135 return PsiTypeItem.typeParameterClasses(codebase, psiMethod.typeParameterList) 136 } 137 138 // private var throwsTypes: List<ClassItem>? = null 139 private lateinit var throwsTypes: List<ClassItem> 140 141 fun setThrowsTypes(throwsTypes: List<ClassItem>) { 142 this.throwsTypes = throwsTypes 143 } 144 145 override fun throwsTypes(): List<ClassItem> = throwsTypes 146 147 override fun isCloned(): Boolean { 148 val psiClass = run { 149 val p = containingClass().psi() as? PsiClass ?: return false 150 if (p is UClass) { 151 p.sourcePsi as? PsiClass ?: return false 152 } else { 153 p 154 } 155 } 156 return psiMethod.containingClass != psiClass 157 } 158 159 override fun isExtensionMethod(): Boolean { 160 if (isKotlin()) { 161 val ktParameters = 162 ((psiMethod as? KotlinUMethod)?.sourcePsi as? KtNamedFunction)?.valueParameters 163 ?: return false 164 return ktParameters.size < parameters.size 165 } 166 167 return false 168 } 169 170 override fun isKotlinProperty(): Boolean { 171 return psiMethod is KotlinUMethod && psiMethod.sourcePsi is KtProperty 172 } 173 174 override fun findThrownExceptions(): Set<ClassItem> { 175 val method = psiMethod as? UMethod ?: return emptySet() 176 if (!isKotlin()) { 177 return emptySet() 178 } 179 180 val exceptions = mutableSetOf<ClassItem>() 181 182 method.accept(object : AbstractUastVisitor() { 183 override fun visitThrowExpression(node: UThrowExpression): Boolean { 184 val type = node.thrownExpression.getExpressionType() 185 if (type != null) { 186 val exceptionClass = codebase.getType(type).asClass() 187 if (exceptionClass != null && !isCaught(exceptionClass, node)) { 188 exceptions.add(exceptionClass) 189 } 190 } 191 return super.visitThrowExpression(node) 192 } 193 194 private fun isCaught(exceptionClass: ClassItem, node: UThrowExpression): Boolean { 195 var current: UElement = node 196 while (true) { 197 val tryExpression = current.getParentOfType<UTryExpression>( 198 UTryExpression::class.java, true, UMethod::class.java 199 ) ?: return false 200 201 for (catchClause in tryExpression.catchClauses) { 202 for (type in catchClause.types) { 203 val qualifiedName = type.canonicalText 204 if (exceptionClass.extends(qualifiedName)) { 205 return true 206 } 207 } 208 } 209 210 current = tryExpression 211 } 212 } 213 }) 214 215 return exceptions 216 } 217 218 override fun defaultValue(): String { 219 if (psiMethod is PsiAnnotationMethod) { 220 val value = psiMethod.defaultValue 221 if (value != null) { 222 if (isKotlin(value)) { 223 val defaultExpression: UExpression = UastFacade.convertElement( 224 value, null, 225 UExpression::class.java 226 ) as? UExpression ?: return "" 227 val constant = defaultExpression.evaluate() 228 return if (constant != null) { 229 CodePrinter.constantToSource(constant) 230 } else { 231 // Expression: Compute from UAST rather than just using the source text 232 // such that we can ensure references are fully qualified etc. 233 codebase.printer.toSourceString(defaultExpression) ?: "" 234 } 235 } else { 236 return codebase.printer.toSourceExpression(value, this) 237 } 238 } 239 } 240 241 return super.defaultValue() 242 } 243 244 override fun duplicate(targetContainingClass: ClassItem): PsiMethodItem { 245 val duplicated = create(codebase, targetContainingClass as PsiClassItem, psiMethod) 246 247 duplicated.inheritedFrom = containingClass 248 249 // Preserve flags that may have been inherited (propagated) from surrounding packages 250 if (targetContainingClass.hidden) { 251 duplicated.hidden = true 252 } 253 if (targetContainingClass.removed) { 254 duplicated.removed = true 255 } 256 if (targetContainingClass.docOnly) { 257 duplicated.docOnly = true 258 } 259 if (targetContainingClass.deprecated && compatibility.propagateDeprecatedMembers) { 260 duplicated.deprecated = true 261 } 262 duplicated.throwsTypes = throwsTypes 263 return duplicated 264 } 265 266 /* Call corresponding PSI utility method -- if I can find it! 267 override fun matches(other: MethodItem): Boolean { 268 if (other !is PsiMethodItem) { 269 return super.matches(other) 270 } 271 272 // TODO: Find better API: this also checks surrounding class which we don't want! 273 return psiMethod.isEquivalentTo(other.psiMethod) 274 } 275 */ 276 277 @Language("JAVA") 278 fun toStub(replacementMap: Map<String, String> = emptyMap()): String { 279 val method = this 280 // There are type variables; we have to recreate the method signature 281 val sb = StringBuilder(100) 282 283 val modifierString = StringWriter() 284 ModifierList.write( 285 modifierString, method.modifiers, method, 286 target = AnnotationTarget.SDK_STUBS_FILE, 287 removeAbstract = false, 288 removeFinal = false, 289 addPublic = true 290 ) 291 sb.append(modifierString.toString()) 292 293 val typeParameters = typeParameterList().toString() 294 if (typeParameters.isNotEmpty()) { 295 sb.append(' ') 296 sb.append(TypeItem.convertTypeString(typeParameters, replacementMap)) 297 } 298 299 val returnType = method.returnType() 300 sb.append(returnType?.convertTypeString(replacementMap)) 301 302 sb.append(' ') 303 sb.append(method.name()) 304 305 sb.append("(") 306 method.parameters().asSequence().forEachIndexed { i, parameter -> 307 if (i > 0) { 308 sb.append(", ") 309 } 310 311 val parameterModifierString = StringWriter() 312 ModifierList.write( 313 parameterModifierString, parameter.modifiers, parameter, 314 target = AnnotationTarget.SDK_STUBS_FILE 315 ) 316 sb.append(parameterModifierString.toString()) 317 sb.append(parameter.type().convertTypeString(replacementMap)) 318 sb.append(' ') 319 sb.append(parameter.name()) 320 } 321 sb.append(")") 322 323 val throws = method.throwsTypes().asSequence().sortedWith(ClassItem.fullNameComparator) 324 if (throws.any()) { 325 sb.append(" throws ") 326 throws.asSequence().sortedWith(ClassItem.fullNameComparator).forEachIndexed { i, type -> 327 if (i > 0) { 328 sb.append(", ") 329 } 330 // No need to replace variables; we can't have type arguments for exceptions 331 sb.append(type.qualifiedName()) 332 } 333 } 334 335 sb.append(" { return ") 336 val defaultValue = PsiTypesUtil.getDefaultValueOfType(method.psiMethod.returnType) 337 sb.append(defaultValue) 338 sb.append("; }") 339 340 return sb.toString() 341 } 342 343 override fun finishInitialization() { 344 super.finishInitialization() 345 346 throwsTypes = throwsTypes(codebase, psiMethod) 347 } 348 349 companion object { 350 fun create( 351 codebase: PsiBasedCodebase, 352 containingClass: PsiClassItem, 353 psiMethod: PsiMethod 354 ): PsiMethodItem { 355 assert(!psiMethod.isConstructor) 356 val name = psiMethod.name 357 val commentText = javadoc(psiMethod) 358 val modifiers = modifiers(codebase, psiMethod, commentText) 359 if (modifiers.isFinal() && containingClass.modifiers.isFinal()) { 360 // The containing class is final, so it is implied that every method is final as well. 361 // No need to apply 'final' to each method. (We do it here rather than just in the 362 // signature emit code since we want to make sure that the signature comparison 363 // methods with super methods also consider this method non-final.) 364 modifiers.setFinal(false) 365 } 366 val parameters = 367 if (psiMethod is UMethod) { 368 psiMethod.uastParameters.mapIndexed { index, parameter -> 369 PsiParameterItem.create(codebase, parameter, index) 370 } 371 } else { 372 psiMethod.parameterList.parameters.mapIndexed { index, parameter -> 373 PsiParameterItem.create(codebase, parameter, index) 374 } 375 } 376 var psiReturnType = psiMethod.returnType 377 378 // UAST workaround: the enum synthetic methods are sometimes missing return types, 379 // see https://youtrack.jetbrains.com/issue/KT-39560 380 if (psiReturnType == null && containingClass.isEnum()) { 381 if (name == "valueOf") { 382 psiReturnType = codebase.getClassType(containingClass.psiClass) 383 } else if (name == "values") { 384 psiReturnType = PsiArrayType(codebase.getClassType(containingClass.psiClass)) 385 } 386 } 387 388 val returnType = codebase.getType(psiReturnType!!) 389 val method = PsiMethodItem( 390 codebase = codebase, 391 psiMethod = psiMethod, 392 containingClass = containingClass, 393 name = name, 394 documentation = commentText, 395 modifiers = modifiers, 396 returnType = returnType, 397 parameters = parameters 398 ) 399 method.modifiers.setOwner(method) 400 return method 401 } 402 403 fun create( 404 codebase: PsiBasedCodebase, 405 containingClass: PsiClassItem, 406 original: PsiMethodItem 407 ): PsiMethodItem { 408 val method = PsiMethodItem( 409 codebase = codebase, 410 psiMethod = original.psiMethod, 411 containingClass = containingClass, 412 name = original.name(), 413 documentation = original.documentation, 414 modifiers = PsiModifierItem.create(codebase, original.modifiers), 415 returnType = PsiTypeItem.create(codebase, original.returnType), 416 parameters = PsiParameterItem.create(codebase, original.parameters()) 417 ) 418 method.modifiers.setOwner(method) 419 method.source = original 420 method.inheritedMethod = original.inheritedMethod 421 422 return method 423 } 424 425 private fun throwsTypes(codebase: PsiBasedCodebase, psiMethod: PsiMethod): List<ClassItem> { 426 val interfaces = psiMethod.throwsList.referencedTypes 427 if (interfaces.isEmpty()) { 428 return emptyList() 429 } 430 431 val result = ArrayList<ClassItem>(interfaces.size) 432 for (cls in interfaces) { 433 if (compatibility.useErasureInThrows) { 434 val erased = TypeConversionUtil.erasure(cls) 435 result.add(codebase.findClass(erased) ?: continue) 436 continue 437 } 438 439 result.add(codebase.findClass(cls) ?: continue) 440 } 441 442 // We're sorting the names here even though outputs typically do their own sorting, 443 // since for example the MethodItem.sameSignature check wants to do an element-by-element 444 // comparison to see if the signature matches, and that should match overrides even if 445 // they specify their elements in different orders. 446 result.sortWith(ClassItem.fullNameComparator) 447 return result 448 } 449 } 450 451 override fun toString(): String = "${if (isConstructor()) "constructor" else "method"} ${ 452 containingClass.qualifiedName()}.${name()}(${parameters().joinToString { it.type().toSimpleType() }})" 453 }