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.intellij.psi.PsiParameter
23 import org.jetbrains.kotlin.psi.KtNamedFunction
24 import org.jetbrains.kotlin.psi.KtParameter
25 import org.jetbrains.kotlin.psi.psiUtil.parameterIndex
26 import org.jetbrains.uast.kotlin.declarations.KotlinUMethod
27 
28 class PsiParameterItem(
29     override val codebase: PsiBasedCodebase,
30     private val psiParameter: PsiParameter,
31     private val name: String,
32     override val parameterIndex: Int,
33     modifiers: PsiModifierItem,
34     documentation: String,
35     private val type: PsiTypeItem
36 ) : PsiItem(
37     codebase = codebase,
38     modifiers = modifiers,
39     documentation = documentation,
40     element = psiParameter
41 ), ParameterItem {
42     lateinit var containingMethod: PsiMethodItem
43 
namenull44     override fun name(): String = name
45 
46     override fun publicName(): String? {
47         if (isKotlin(psiParameter)) {
48             if (name == "\$receiver") {
49                 return null
50             }
51             return name
52         } else {
53             // Java: Look for @ParameterName annotation
54             val annotation = modifiers.annotations().firstOrNull { it.isParameterName() }
55             if (annotation != null) {
56                 return annotation.attributes().firstOrNull()?.value?.value()?.toString()
57             }
58         }
59 
60         return null
61     }
62 
hasDefaultValuenull63     override fun hasDefaultValue(): Boolean {
64         return if (isKotlin(psiParameter)) {
65             getKtParameter()?.hasDefaultValue() ?: false
66         } else {
67             // Java: Look for @ParameterName annotation
68             modifiers.annotations().any { it.isDefaultValue() }
69         }
70     }
71 
getKtParameternull72     private fun getKtParameter(): KtParameter? {
73         val ktParameters =
74             ((containingMethod.psiMethod as? KotlinUMethod)?.sourcePsi as? KtNamedFunction)?.valueParameters
75                 ?: return null
76 
77         // Perform matching based on parameter names, because indices won't work in the
78         // presence of @JvmOverloads where UAST generates multiple permutations of the
79         // method from the same KtParameters array.
80 
81         // Quick lookup first which usually works (lined up from the end to account
82         // for receivers for extension methods etc)
83         val rem = containingMethod.parameters().size - parameterIndex
84         val index = ktParameters.size - rem
85         if (index >= 0) {
86             val parameter = ktParameters[index]
87             if (parameter.name == name) {
88                 return parameter
89             }
90         }
91 
92         for (parameter in ktParameters) {
93             if (parameter.name == name) {
94                 return parameter
95             }
96         }
97 
98         // Fallback to handle scenario where the real parameter names are hidden by
99         // UAST (see UastKotlinPsiParameter which replaces parameter names to p$index)
100         if (index >= 0) {
101             val parameter = ktParameters[index]
102             if (name != "\$receiver") {
103                 return parameter
104             }
105         }
106 
107         return null
108     }
109 
defaultValuenull110     override fun defaultValue(): String? {
111         if (isKotlin(psiParameter)) {
112             val ktParameter = getKtParameter() ?: return null
113             if (ktParameter.hasDefaultValue()) {
114                 return ktParameter.defaultValue?.text
115             }
116 
117             return null
118         } else {
119             // Java: Look for @ParameterName annotation
120             val annotation = modifiers.annotations().firstOrNull { it.isDefaultValue() }
121             if (annotation != null) {
122                 return annotation.attributes().firstOrNull()?.value?.value()?.toString()
123             }
124         }
125 
126         return null
127     }
128 
typenull129     override fun type(): TypeItem = type
130     override fun containingMethod(): MethodItem = containingMethod
131 
132     override fun equals(other: Any?): Boolean {
133         if (this === other) {
134             return true
135         }
136         return other is ParameterItem && parameterIndex == other.parameterIndex && containingMethod == other.containingMethod()
137     }
138 
hashCodenull139     override fun hashCode(): Int {
140         return parameterIndex
141     }
142 
toStringnull143     override fun toString(): String = "parameter ${name()}"
144 
145     override fun isVarArgs(): Boolean {
146         return psiParameter.isVarArgs || modifiers.isVarArg()
147     }
148 
149     companion object {
createnull150         fun create(
151             codebase: PsiBasedCodebase,
152             psiParameter: PsiParameter,
153             parameterIndex: Int
154         ): PsiParameterItem {
155             val name = psiParameter.name ?: "arg${psiParameter.parameterIndex() + 1}"
156             val commentText = "" // no javadocs on individual parameters
157             val modifiers = modifiers(codebase, psiParameter, commentText)
158             val type = codebase.getType(psiParameter.type)
159             val parameter = PsiParameterItem(
160                 codebase = codebase,
161                 psiParameter = psiParameter,
162                 name = name,
163                 parameterIndex = parameterIndex,
164                 documentation = commentText,
165                 modifiers = modifiers,
166                 type = type
167             )
168             parameter.modifiers.setOwner(parameter)
169             return parameter
170         }
171 
createnull172         fun create(
173             codebase: PsiBasedCodebase,
174             original: PsiParameterItem
175         ): PsiParameterItem {
176             val parameter = PsiParameterItem(
177                 codebase = codebase,
178                 psiParameter = original.psiParameter,
179                 name = original.name,
180                 parameterIndex = original.parameterIndex,
181                 documentation = original.documentation,
182                 modifiers = PsiModifierItem.create(codebase, original.modifiers),
183                 type = PsiTypeItem.create(codebase, original.type)
184             )
185             parameter.modifiers.setOwner(parameter)
186             return parameter
187         }
188 
createnull189         fun create(
190             codebase: PsiBasedCodebase,
191             original: List<ParameterItem>
192         ): List<PsiParameterItem> {
193             return original.map { create(codebase, it as PsiParameterItem) }
194         }
195     }
196 }