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