• 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.openapi.components.ServiceManager
28 import com.intellij.psi.PsiAnnotationMethod
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.UastContext
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     private var superMethods: List<MethodItem>? = null
111     override fun superMethods(): List<MethodItem> {
112         if (superMethods == null) {
113             val result = mutableListOf<MethodItem>()
114             psiMethod.findSuperMethods().mapTo(result) { codebase.findMethod(it) }
115             superMethods = result
116         }
117 
118         return superMethods!!
119     }
120 
121     override fun typeParameterList(): TypeParameterList {
122         if (psiMethod.hasTypeParameters()) {
123             return PsiTypeParameterList(
124                 codebase, psiMethod.typeParameterList
125                     ?: return TypeParameterList.NONE
126             )
127         } else {
128             return TypeParameterList.NONE
129         }
130     }
131 
132     override fun typeArgumentClasses(): List<ClassItem> {
133         return PsiTypeItem.typeParameterClasses(codebase, psiMethod.typeParameterList)
134     }
135 
136     //    private var throwsTypes: List<ClassItem>? = null
137     private lateinit var throwsTypes: List<ClassItem>
138 
139     fun setThrowsTypes(throwsTypes: List<ClassItem>) {
140         this.throwsTypes = throwsTypes
141     }
142 
143     override fun throwsTypes(): List<ClassItem> = throwsTypes
144 
145     override fun isCloned(): Boolean {
146         val psiClass = run {
147             val p = containingClass().psi() as? PsiClass ?: return false
148             if (p is UClass) {
149                 p.sourcePsi as? PsiClass ?: return false
150             } else {
151                 p
152             }
153         }
154         return psiMethod.containingClass != psiClass
155     }
156 
157     override fun isExtensionMethod(): Boolean {
158         if (isKotlin()) {
159             val ktParameters =
160                 ((psiMethod as? KotlinUMethod)?.sourcePsi as? KtNamedFunction)?.valueParameters
161                     ?: return false
162             return ktParameters.size < parameters.size
163         }
164 
165         return false
166     }
167 
168     override fun isKotlinProperty(): Boolean {
169         return psiMethod is KotlinUMethod && psiMethod.sourcePsi is KtProperty
170     }
171 
172     override fun findThrownExceptions(): Set<ClassItem> {
173         val method = psiMethod as? UMethod ?: return emptySet()
174         if (!isKotlin()) {
175             return emptySet()
176         }
177 
178         val exceptions = mutableSetOf<ClassItem>()
179 
180         method.accept(object : AbstractUastVisitor() {
181             override fun visitThrowExpression(node: UThrowExpression): Boolean {
182                 val type = node.thrownExpression.getExpressionType()
183                 if (type != null) {
184                     val exceptionClass = codebase.getType(type).asClass()
185                     if (exceptionClass != null && !isCaught(exceptionClass, node)) {
186                         exceptions.add(exceptionClass)
187                     }
188                 }
189                 return super.visitThrowExpression(node)
190             }
191 
192             private fun isCaught(exceptionClass: ClassItem, node: UThrowExpression): Boolean {
193                 var current: UElement = node
194                 while (true) {
195                     val tryExpression = current.getParentOfType<UTryExpression>(
196                         UTryExpression::class.java, true, UMethod::class.java
197                     ) ?: return false
198 
199                     for (catchClause in tryExpression.catchClauses) {
200                         for (type in catchClause.types) {
201                             val qualifiedName = type.canonicalText
202                             if (exceptionClass.extends(qualifiedName)) {
203                                 return true
204                             }
205                         }
206                     }
207 
208                     current = tryExpression
209                 }
210             }
211         })
212 
213         return exceptions
214     }
215 
216     override fun defaultValue(): String {
217         if (psiMethod is PsiAnnotationMethod) {
218             val value = psiMethod.defaultValue
219             if (value != null) {
220                 if (PsiItem.isKotlin(value)) {
221                     val uastContext = ServiceManager.getService(value.project, UastContext::class.java)
222                         ?: error("UastContext not found")
223                     val defaultExpression: UExpression = uastContext.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             sb.append(parameter.type().convertTypeString(replacementMap))
312             sb.append(' ')
313             sb.append(parameter.name())
314         }
315         sb.append(")")
316 
317         val throws = method.throwsTypes().asSequence().sortedWith(ClassItem.fullNameComparator)
318         if (throws.any()) {
319             sb.append(" throws ")
320             throws.asSequence().sortedWith(ClassItem.fullNameComparator).forEachIndexed { i, type ->
321                 if (i > 0) {
322                     sb.append(", ")
323                 }
324                 // No need to replace variables; we can't have type arguments for exceptions
325                 sb.append(type.qualifiedName())
326             }
327         }
328 
329         sb.append(" { return ")
330         val defaultValue = PsiTypesUtil.getDefaultValueOfType(method.psiMethod.returnType)
331         sb.append(defaultValue)
332         sb.append("; }")
333 
334         return sb.toString()
335     }
336 
337     override fun finishInitialization() {
338         super.finishInitialization()
339 
340         throwsTypes = throwsTypes(codebase, psiMethod)
341     }
342 
343     companion object {
344         fun create(
345             codebase: PsiBasedCodebase,
346             containingClass: PsiClassItem,
347             psiMethod: PsiMethod
348         ): PsiMethodItem {
349             assert(!psiMethod.isConstructor)
350             val name = psiMethod.name
351             val commentText = javadoc(psiMethod)
352             val modifiers = modifiers(codebase, psiMethod, commentText)
353             if (modifiers.isFinal() && containingClass.modifiers.isFinal()) {
354                 // The containing class is final, so it is implied that every method is final as well.
355                 // No need to apply 'final' to each method. (We do it here rather than just in the
356                 // signature emit code since we want to make sure that the signature comparison
357                 // methods with super methods also consider this method non-final.)
358                 modifiers.setFinal(false)
359             }
360             val parameters =
361                 if (psiMethod is UMethod) {
362                     psiMethod.uastParameters.mapIndexed { index, parameter ->
363                         PsiParameterItem.create(codebase, parameter, index)
364                     }
365                 } else {
366                     psiMethod.parameterList.parameters.mapIndexed { index, parameter ->
367                         PsiParameterItem.create(codebase, parameter, index)
368                     }
369                 }
370             val returnType = codebase.getType(psiMethod.returnType!!)
371             val method = PsiMethodItem(
372                 codebase = codebase,
373                 psiMethod = psiMethod,
374                 containingClass = containingClass,
375                 name = name,
376                 documentation = commentText,
377                 modifiers = modifiers,
378                 returnType = returnType,
379                 parameters = parameters
380             )
381             method.modifiers.setOwner(method)
382             return method
383         }
384 
385         fun create(
386             codebase: PsiBasedCodebase,
387             containingClass: PsiClassItem,
388             original: PsiMethodItem
389         ): PsiMethodItem {
390             val method = PsiMethodItem(
391                 codebase = codebase,
392                 psiMethod = original.psiMethod,
393                 containingClass = containingClass,
394                 name = original.name(),
395                 documentation = original.documentation,
396                 modifiers = PsiModifierItem.create(codebase, original.modifiers),
397                 returnType = PsiTypeItem.create(codebase, original.returnType),
398                 parameters = PsiParameterItem.create(codebase, original.parameters())
399             )
400             method.modifiers.setOwner(method)
401             method.source = original
402             method.inheritedMethod = original.inheritedMethod
403 
404             return method
405         }
406 
407         private fun throwsTypes(codebase: PsiBasedCodebase, psiMethod: PsiMethod): List<ClassItem> {
408             val interfaces = psiMethod.throwsList.referencedTypes
409             if (interfaces.isEmpty()) {
410                 return emptyList()
411             }
412 
413             val result = ArrayList<ClassItem>(interfaces.size)
414             for (cls in interfaces) {
415                 if (compatibility.useErasureInThrows) {
416                     val erased = TypeConversionUtil.erasure(cls)
417                     result.add(codebase.findClass(erased) ?: continue)
418                     continue
419                 }
420 
421                 result.add(codebase.findClass(cls) ?: continue)
422             }
423 
424             // We're sorting the names here even though outputs typically do their own sorting,
425             // since for example the MethodItem.sameSignature check wants to do an element-by-element
426             // comparison to see if the signature matches, and that should match overrides even if
427             // they specify their elements in different orders.
428             result.sortWith(ClassItem.fullNameComparator)
429             return result
430         }
431     }
432 
433     override fun toString(): String = "${if (isConstructor()) "constructor" else "method"} ${
434     containingClass.qualifiedName()}.${name()}(${parameters().joinToString { it.type().toSimpleType() }})"
435 }