1 /*
<lambda>null2 * Copyright (C) 2024 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
18
19 import java.util.Objects
20
21 /** Common to [MethodItem] and [ConstructorItem]. */
22 @MetalavaApi
23 interface CallableItem : MemberItem, TypeParameterListOwner {
24
25 /** Whether this callable is a constructor or a method. */
26 @MetalavaApi fun isConstructor(): Boolean
27
28 /**
29 * The return type of this callable.
30 *
31 * Returns the [ClassItem.type] of the [containingClass] for constructors.
32 */
33 @MetalavaApi fun returnType(): TypeItem
34
35 /** The list of parameters */
36 @MetalavaApi fun parameters(): List<ParameterItem>
37
38 override fun type() = returnType()
39
40 /** Types of exceptions that this callable can throw */
41 fun throwsTypes(): List<ExceptionTypeItem>
42
43 /** The body of this, may not be available. */
44 val body: CallableBody
45
46 /** Returns true if this callable throws the given exception */
47 fun throws(qualifiedName: String): Boolean {
48 for (type in throwsTypes()) {
49 val throwableClass = type.erasedClass ?: continue
50 if (throwableClass.extends(qualifiedName)) {
51 return true
52 }
53 }
54
55 return false
56 }
57
58 fun filteredThrowsTypes(predicate: FilterPredicate): Collection<ExceptionTypeItem> {
59 if (throwsTypes().isEmpty()) {
60 return emptyList()
61 }
62 return filteredThrowsTypes(predicate, LinkedHashSet())
63 }
64
65 private fun filteredThrowsTypes(
66 predicate: FilterPredicate,
67 throwsTypes: LinkedHashSet<ExceptionTypeItem>
68 ): LinkedHashSet<ExceptionTypeItem> {
69 for (exceptionType in throwsTypes()) {
70 if (exceptionType is VariableTypeItem) {
71 throwsTypes.add(exceptionType)
72 } else {
73 val classItem = exceptionType.erasedClass ?: continue
74 if (predicate.test(classItem)) {
75 throwsTypes.add(exceptionType)
76 } else {
77 // Excluded, but it may have super class throwables that are included; if so,
78 // include those.
79 classItem
80 .allSuperClasses()
81 .firstOrNull { superClass -> predicate.test(superClass) }
82 ?.let { superClass -> throwsTypes.add(superClass.type()) }
83 }
84 }
85 }
86 return throwsTypes
87 }
88
89 /** Override to specialize return type. */
90 override fun findCorrespondingItemIn(
91 codebase: Codebase,
92 superMethods: Boolean,
93 duplicate: Boolean,
94 ): CallableItem?
95
96 override fun baselineElementId() = buildString {
97 append(containingClass().qualifiedName())
98 append("#")
99 append(name())
100 append("(")
101 parameters().joinTo(this) { it.type().toSimpleType() }
102 append(")")
103 }
104
105 override fun toStringForItem(): String {
106 return "${if (isConstructor()) "constructor" else "method"} ${
107 containingClass().qualifiedName()}.${name()}(${parameters().joinToString { it.type().toSimpleType() }})"
108 }
109
110 override fun equalsToItem(other: Any?): Boolean {
111 if (this === other) return true
112 if (other !is CallableItem) return false
113
114 // The name check will ensure that methods and constructors do not compare equally to each
115 // other even if the rest of this method would return true. That is because constructor
116 // names MUST be the class' `simpleName`, whereas method names MUST NOT be the class'
117 // `simpleName`.
118 if (name() != other.name()) {
119 return false
120 }
121
122 if (containingClass() != other.containingClass()) {
123 return false
124 }
125
126 val parameters1 = parameters()
127 val parameters2 = other.parameters()
128
129 if (parameters1.size != parameters2.size) {
130 return false
131 }
132
133 for (i in parameters1.indices) {
134 val parameter1 = parameters1[i]
135 val parameter2 = parameters2[i]
136 if (parameter1.type() != parameter2.type()) {
137 return false
138 }
139 }
140
141 val typeParameters1 = typeParameterList
142 val typeParameters2 = other.typeParameterList
143
144 if (typeParameters1.size != typeParameters2.size) {
145 return false
146 }
147
148 for (i in typeParameters1.indices) {
149 val typeParameter1 = typeParameters1[i]
150 val typeParameter2 = typeParameters2[i]
151 if (typeParameter1.typeBounds() != typeParameter2.typeBounds()) {
152 return false
153 }
154 }
155 return true
156 }
157
158 override fun hashCodeForItem(): Int {
159 // Just use the callable name, containing class and number of parameters.
160 return Objects.hash(name(), containingClass(), parameters().size)
161 }
162
163 /**
164 * Returns true if this callable is a signature match for the given callable (e.g. can be
165 * overriding if it is a method). This checks that the name and parameter lists match, but
166 * ignores differences in parameter names, return value types and throws list types.
167 */
168 fun matches(other: CallableItem): Boolean {
169 if (this === other) return true
170
171 // The name check will ensure that methods and constructors do not compare equally to each
172 // other even if the rest of this method would return true. That is because constructor
173 // names MUST be the class' `simpleName`, whereas method names MUST NOT be the class'
174 // `simpleName`.
175 if (name() != other.name()) {
176 return false
177 }
178
179 val parameters1 = parameters()
180 val parameters2 = other.parameters()
181
182 if (parameters1.size != parameters2.size) {
183 return false
184 }
185
186 for (i in parameters1.indices) {
187 val parameter1Type = parameters1[i].type()
188 val parameter2Type = parameters2[i].type()
189 if (parameter1Type == parameter2Type) continue
190 if (parameter1Type.toErasedTypeString() == parameter2Type.toErasedTypeString()) continue
191
192 val convertedType =
193 parameter1Type.convertType(other.containingClass(), containingClass())
194 if (convertedType != parameter2Type) return false
195 }
196 return true
197 }
198
199 /**
200 * Returns whether this callable has any types in its signature that does not match the given
201 * filter.
202 */
203 fun hasHiddenType(filterReference: FilterPredicate): Boolean {
204 for (parameter in parameters()) {
205 if (parameter.type().hasHiddenType(filterReference)) return true
206 }
207
208 if (returnType().hasHiddenType(filterReference)) return true
209
210 for (typeParameter in typeParameterList) {
211 if (typeParameter.typeBounds().any { it.hasHiddenType(filterReference) }) return true
212 }
213
214 return false
215 }
216
217 /** Checks if there is a reference to a hidden class anywhere in the type. */
218 private fun TypeItem.hasHiddenType(filterReference: FilterPredicate): Boolean {
219 return when (this) {
220 is PrimitiveTypeItem -> false
221 is ArrayTypeItem -> componentType.hasHiddenType(filterReference)
222 is ClassTypeItem ->
223 asClass()?.let { !filterReference.test(it) } == true ||
224 outerClassType?.hasHiddenType(filterReference) == true ||
225 arguments.any { it.hasHiddenType(filterReference) }
226 is VariableTypeItem ->
227 // There is no need to check if a type variable contains a reference to a hidden
228 // class as it is only a reference to a type parameter, and they are checked above
229 // to make sure that their type bounds do not contain a reference to a hidden
230 // class.
231 false
232 is WildcardTypeItem ->
233 extendsBound?.hasHiddenType(filterReference) == true ||
234 superBound?.hasHiddenType(filterReference) == true
235 else -> throw IllegalStateException("Unrecognized type: $this")
236 }
237 }
238
239 companion object {
240 private fun compareCallables(
241 o1: CallableItem,
242 o2: CallableItem,
243 overloadsInSourceOrder: Boolean
244 ): Int {
245 val name1 = o1.name()
246 val name2 = o2.name()
247 if (name1 == name2) {
248 if (overloadsInSourceOrder) {
249 val rankDelta = o1.sortingRank - o2.sortingRank
250 if (rankDelta != 0) {
251 return rankDelta
252 }
253 }
254
255 // Compare by the rest of the signature to ensure stable output (we don't need to
256 // sort
257 // by return value or modifiers or modifiers or throws-lists since methods can't be
258 // overloaded
259 // by just those attributes
260 val p1 = o1.parameters()
261 val p2 = o2.parameters()
262 val p1n = p1.size
263 val p2n = p2.size
264 for (i in 0 until minOf(p1n, p2n)) {
265 val compareTypes =
266 p1[i]
267 .type()
268 .toTypeString()
269 .compareTo(p2[i].type().toTypeString(), ignoreCase = true)
270 if (compareTypes != 0) {
271 return compareTypes
272 }
273 // (Don't compare names; they're not part of the signatures)
274 }
275 return p1n.compareTo(p2n)
276 }
277
278 return name1.compareTo(name2)
279 }
280
281 val comparator: Comparator<CallableItem> = Comparator { o1, o2 ->
282 compareCallables(o1, o2, false)
283 }
284 val sourceOrderForOverloadedMethodsComparator: Comparator<CallableItem> =
285 Comparator { o1, o2 ->
286 compareCallables(o1, o2, true)
287 }
288 }
289 }
290
291 /**
292 * Get the JVM-like descriptor of this [CallableItem] for just parameters (not return type) and
293 * using dots ('.') instead of slash (`/`) and dollar sign (`$`) characters.
294 *
295 * Due to legacy reasons it will return `null` for the constructor of an inner class.
296 */
CallableItemnull297 fun CallableItem.getCallableParameterDescriptorUsingDots(): String? {
298 return if (
299 isConstructor() &&
300 containingClass().isNestedClass() &&
301 !containingClass().modifiers.isStatic()
302 )
303 null
304 else
305 buildString {
306 append("(")
307 for (parameter in parameters()) {
308 append(parameter.type().internalName().replace('/', '.').replace('$', '.'))
309 }
310 append(")")
311 }
312 }
313