1 /*
2  * 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.MethodItem
20 import com.android.tools.metalava.model.ParameterItem
21 import com.android.tools.metalava.model.TypeItem
22 import com.android.tools.metalava.model.psi.CodePrinter.Companion.constantToSource
23 import com.intellij.psi.PsiParameter
24 import org.jetbrains.kotlin.psi.KtConstantExpression
25 import org.jetbrains.kotlin.psi.KtNamedFunction
26 import org.jetbrains.kotlin.psi.KtParameter
27 import org.jetbrains.uast.UExpression
28 import org.jetbrains.uast.UastFacade
29 import org.jetbrains.uast.kotlin.declarations.KotlinUMethod
30 
31 class PsiParameterItem(
32     override val codebase: PsiBasedCodebase,
33     private val psiParameter: PsiParameter,
34     private val name: String,
35     override val parameterIndex: Int,
36     modifiers: PsiModifierItem,
37     documentation: String,
38     private val type: PsiTypeItem
39 ) : PsiItem(
40     codebase = codebase,
41     modifiers = modifiers,
42     documentation = documentation,
43     element = psiParameter
44 ), ParameterItem {
45     lateinit var containingMethod: PsiMethodItem
46 
namenull47     override fun name(): String = name
48 
49     override fun publicName(): String? {
50         if (isKotlin(psiParameter)) {
51             // Don't print out names for extension function receiver parameters
52             if (isReceiver()) {
53                 return null
54             }
55             return name
56         } else {
57             // Java: Look for @ParameterName annotation
58             val annotation = modifiers.annotations().firstOrNull { it.isParameterName() }
59             if (annotation != null) {
60                 return annotation.attributes().firstOrNull()?.value?.value()?.toString()
61             }
62         }
63 
64         return null
65     }
66 
hasDefaultValuenull67     override fun hasDefaultValue(): Boolean {
68         return if (isKotlin(psiParameter)) {
69             getKtParameter()?.hasDefaultValue() ?: false && defaultValue() != INVALID_VALUE
70         } else {
71             // Java: Look for @ParameterName annotation
72             modifiers.annotations().any { it.isDefaultValue() }
73         }
74     }
75 
76     // Note receiver parameter used to be named $receiver in previous UAST versions, now it is $this$functionName
isReceivernull77     private fun isReceiver(): Boolean = parameterIndex == 0 && name.startsWith("\$this\$")
78 
79     private fun getKtParameter(): KtParameter? {
80         val ktParameters =
81             ((containingMethod.psiMethod as? KotlinUMethod)?.sourcePsi as? KtNamedFunction)?.valueParameters
82                 ?: return null
83 
84         // Perform matching based on parameter names, because indices won't work in the
85         // presence of @JvmOverloads where UAST generates multiple permutations of the
86         // method from the same KtParameters array.
87 
88         // Quick lookup first which usually works (lined up from the end to account
89         // for receivers for extension methods etc)
90         val rem = containingMethod.parameters().size - parameterIndex
91         val index = ktParameters.size - rem
92         if (index >= 0) {
93             val parameter = ktParameters[index]
94             if (parameter.name == name) {
95                 return parameter
96             }
97         }
98 
99         for (parameter in ktParameters) {
100             if (parameter.name == name) {
101                 return parameter
102             }
103         }
104 
105         // Fallback to handle scenario where the real parameter names are hidden by
106         // UAST (see UastKotlinPsiParameter which replaces parameter names to p$index)
107         if (index >= 0) {
108             val parameter = ktParameters[index]
109             if (!isReceiver()) {
110                 return parameter
111             }
112         }
113 
114         return null
115     }
116 
117     override val synthetic: Boolean get() = containingMethod.isEnumSyntheticMethod()
118 
119     private var defaultValue: String? = null
120 
defaultValuenull121     override fun defaultValue(): String? {
122         if (defaultValue == null) {
123             defaultValue = computeDefaultValue()
124         }
125         return defaultValue
126     }
127 
computeDefaultValuenull128     private fun computeDefaultValue(): String? {
129         if (isKotlin(psiParameter)) {
130             val ktParameter = getKtParameter() ?: return null
131             if (ktParameter.hasDefaultValue()) {
132                 val defaultValue = ktParameter.defaultValue ?: return null
133                 if (defaultValue is KtConstantExpression) {
134                     return defaultValue.text
135                 }
136 
137                 val defaultExpression: UExpression = UastFacade.convertElement(
138                     defaultValue, null,
139                     UExpression::class.java
140                 ) as? UExpression ?: return INVALID_VALUE
141                 val constant = defaultExpression.evaluate()
142                 return if (constant != null && constant !is Pair<*, *>) {
143                     constantToSource(constant)
144                 } else {
145                     // Expression: Compute from UAST rather than just using the source text
146                     // such that we can ensure references are fully qualified etc.
147                     codebase.printer.toSourceString(defaultExpression)
148                 }
149             }
150 
151             return INVALID_VALUE
152         } else {
153             // Java: Look for @ParameterName annotation
154             val annotation = modifiers.annotations().firstOrNull { it.isDefaultValue() }
155             if (annotation != null) {
156                 return annotation.attributes().firstOrNull()?.value?.value()?.toString()
157             }
158         }
159 
160         return null
161     }
162 
typenull163     override fun type(): TypeItem = type
164     override fun containingMethod(): MethodItem = containingMethod
165 
166     override fun equals(other: Any?): Boolean {
167         if (this === other) {
168             return true
169         }
170         return other is ParameterItem && parameterIndex == other.parameterIndex && containingMethod == other.containingMethod()
171     }
172 
hashCodenull173     override fun hashCode(): Int {
174         return parameterIndex
175     }
176 
toStringnull177     override fun toString(): String = "parameter ${name()}"
178 
179     override fun isVarArgs(): Boolean {
180         return psiParameter.isVarArgs || modifiers.isVarArg()
181     }
182 
183     companion object {
createnull184         fun create(
185             codebase: PsiBasedCodebase,
186             psiParameter: PsiParameter,
187             parameterIndex: Int
188         ): PsiParameterItem {
189             val name = psiParameter.name
190             val commentText = "" // no javadocs on individual parameters
191             val modifiers = modifiers(codebase, psiParameter, commentText)
192             val type = codebase.getType(psiParameter.type)
193             val parameter = PsiParameterItem(
194                 codebase = codebase,
195                 psiParameter = psiParameter,
196                 name = name,
197                 parameterIndex = parameterIndex,
198                 documentation = commentText,
199                 modifiers = modifiers,
200                 type = type
201             )
202             parameter.modifiers.setOwner(parameter)
203             return parameter
204         }
205 
createnull206         fun create(
207             codebase: PsiBasedCodebase,
208             original: PsiParameterItem
209         ): PsiParameterItem {
210             val parameter = PsiParameterItem(
211                 codebase = codebase,
212                 psiParameter = original.psiParameter,
213                 name = original.name,
214                 parameterIndex = original.parameterIndex,
215                 documentation = original.documentation,
216                 modifiers = PsiModifierItem.create(codebase, original.modifiers),
217                 type = PsiTypeItem.create(codebase, original.type)
218             )
219             parameter.modifiers.setOwner(parameter)
220             return parameter
221         }
222 
createnull223         fun create(
224             codebase: PsiBasedCodebase,
225             original: List<ParameterItem>
226         ): List<PsiParameterItem> {
227             return original.map { create(codebase, it as PsiParameterItem) }
228         }
229 
230         /**
231          * Private marker return value from [#computeDefaultValue] signifying that the parameter
232          * has a default value but we were unable to compute a suitable static string representation for it
233          */
234         private const val INVALID_VALUE = "__invalid_value__"
235     }
236 }