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 }