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 }