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