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