• Home
  • History
  • Annotate
  • 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.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  }