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