1 /*
<lambda>null2 * Copyright (C) 2023 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.item
18
19 import com.android.tools.metalava.model.ApiVariantSelectorsFactory
20 import com.android.tools.metalava.model.BaseModifierList
21 import com.android.tools.metalava.model.CallableBodyFactory
22 import com.android.tools.metalava.model.ClassItem
23 import com.android.tools.metalava.model.Codebase
24 import com.android.tools.metalava.model.ExceptionTypeItem
25 import com.android.tools.metalava.model.ItemDocumentationFactory
26 import com.android.tools.metalava.model.ItemLanguage
27 import com.android.tools.metalava.model.MethodItem
28 import com.android.tools.metalava.model.TypeItem
29 import com.android.tools.metalava.model.TypeParameterList
30 import com.android.tools.metalava.reporter.FileLocation
31
32 open class DefaultMethodItem(
33 codebase: Codebase,
34 fileLocation: FileLocation,
35 itemLanguage: ItemLanguage,
36 modifiers: BaseModifierList,
37 documentationFactory: ItemDocumentationFactory,
38 variantSelectorsFactory: ApiVariantSelectorsFactory,
39 name: String,
40 containingClass: ClassItem,
41 typeParameterList: TypeParameterList,
42 returnType: TypeItem,
43 parameterItemsFactory: ParameterItemsFactory,
44 throwsTypes: List<ExceptionTypeItem>,
45 callableBodyFactory: CallableBodyFactory,
46 private val annotationDefault: String = "",
47 ) :
48 DefaultCallableItem(
49 codebase,
50 fileLocation,
51 itemLanguage,
52 modifiers,
53 documentationFactory,
54 variantSelectorsFactory,
55 name,
56 containingClass,
57 typeParameterList,
58 returnType,
59 parameterItemsFactory,
60 throwsTypes,
61 callableBodyFactory,
62 ),
63 MethodItem {
64
65 final override var inheritedFrom: ClassItem? = null
66
67 override fun isExtensionMethod(): Boolean = false // java does not support extension methods
68
69 override fun defaultValue() = annotationDefault
70
71 private lateinit var superMethodList: List<MethodItem>
72
73 /**
74 * Super methods for a given method M with containing class C are calculated as follows:
75 * 1) Superclass Search: Traverse the class hierarchy, starting from C's direct superclass, and
76 * add the first method that matches M's signature to the list.
77 * 2) Interface Supermethod Search: For each direct interface implemented by C, check if it
78 * contains a method matching M's signature. If found, return that method. If not,
79 * recursively apply this method to the direct interfaces of the current interface.
80 *
81 * Note: This method's implementation is based on MethodItem.matches method which only checks
82 * that name and parameter list types match. Parameter names, Return types and Throws list types
83 * are not matched
84 */
85 final override fun superMethods(): List<MethodItem> {
86 return if (containingClass().frozen) {
87 if (!::superMethodList.isInitialized) {
88 superMethodList = computeSuperMethods()
89 }
90 superMethodList
91 } else {
92 computeSuperMethods()
93 }
94 }
95
96 @Deprecated("This property should not be accessed directly.")
97 final override var _requiresOverride: Boolean? = null
98
99 override fun duplicate(targetContainingClass: ClassItem): MethodItem {
100 val typeVariableMap = targetContainingClass.mapTypeVariables(containingClass())
101
102 return DefaultMethodItem(
103 codebase = codebase,
104 fileLocation = fileLocation,
105 itemLanguage = itemLanguage,
106 modifiers = modifiers,
107 documentationFactory = documentation::duplicate,
108 variantSelectorsFactory = variantSelectors::duplicate,
109 name = name(),
110 containingClass = targetContainingClass,
111 typeParameterList = typeParameterList,
112 returnType = returnType.convertType(typeVariableMap),
113 parameterItemsFactory = { containingCallable ->
114 // Duplicate the parameters
115 parameters.map { it.duplicate(containingCallable, typeVariableMap) }
116 },
117 throwsTypes = throwsTypes,
118 annotationDefault = annotationDefault,
119 callableBodyFactory = body::duplicate,
120 )
121 .also { duplicated ->
122 duplicated.inheritedFrom = containingClass()
123
124 duplicated.updateCopiedMethodState()
125 }
126 }
127
128 /**
129 * Compute the super methods of this method.
130 *
131 * A super method is a method from a super class or super interface that is directly overridden
132 * by this method.
133 */
134 private fun computeSuperMethods(): List<MethodItem> {
135 // Methods that are not overrideable will have no super methods.
136 if (!isOverrideable()) {
137 return emptyList()
138 }
139
140 // TODO(b/321216636): Remove this awful hack.
141 // For some reason `psiMethod.findSuperMethods()` would return an empty list for this
142 // specific method. That is incorrect as it clearly overrides a method in `DrawScope` in the
143 // same package. However, it is unclear what makes this method distinct from any other
144 // method including overloaded methods in the same class that also override methods
145 // in`DrawScope`. Returning a correct non-empty list for that method results in the method
146 // being removed from an API signature file even though the super method is abstract and
147 // this is concrete. That is because AndroidX does not yet set
148 // `add-additional-overrides=yes`. When it does then this hack can be removed.
149 if (
150 containingClass().qualifiedName() ==
151 "androidx.compose.ui.graphics.drawscope.CanvasDrawScope" &&
152 name() == "drawImage" &&
153 toString() ==
154 "method androidx.compose.ui.graphics.drawscope.CanvasDrawScope.drawImage(androidx.compose.ui.graphics.ImageBitmap, long, long, long, long, float, androidx.compose.ui.graphics.drawscope.DrawStyle, androidx.compose.ui.graphics.ColorFilter, int)"
155 ) {
156 return emptyList()
157 }
158
159 // Ideally, the search for super methods would start from this method's ClassItem.
160 // Unfortunately, due to legacy reasons for methods that were inherited from another
161 // ClassItem it is necessary to start the search from the original ClassItem. That is
162 // because the psi model's implementation behaved this way and the code that is built of top
163 // of superMethods, like the code to determine if overriding methods should be elided from
164 // the API signature file relied on that behavior.
165 val startingClass = inheritedFrom ?: containingClass()
166 return buildSet { appendSuperMethods(this, startingClass) }.toList()
167 }
168
169 /**
170 * Append the super methods of this method from the [cls] hierarchy to the [methods] set.
171 *
172 * @param methods the mutable, order preserving set of super [MethodItem].
173 * @param cls the [ClassItem] whose super class and implemented interfaces will be searched for
174 * matching methods.
175 */
176 private fun appendSuperMethods(methods: MutableSet<MethodItem>, cls: ClassItem) {
177 // Method from SuperClass or its ancestors
178 cls.superClass()?.let { superClass ->
179 // Search for a matching method in the super class.
180 val superMethod = superClass.findMethod(this)
181 if (superMethod == null) {
182 // No matching method was found so continue searching in the super class.
183 appendSuperMethods(methods, superClass)
184 } else {
185 // Matching does not check modifiers match so make sure that the matched method is
186 // overrideable.
187 if (superMethod.isOverrideable()) {
188 methods.add(superMethod)
189 }
190 }
191 }
192
193 // Methods implemented from direct interfaces or its ancestors
194 appendSuperMethodsFromInterfaces(methods, cls)
195 }
196
197 /**
198 * Append the super methods of this method from the interface hierarchy of [cls] to the
199 * [methods] set.
200 *
201 * @param methods the mutable, order preserving set of super [MethodItem].
202 * @param cls the [ClassItem] whose implemented interfaces will be searched for matching
203 * methods.
204 */
205 private fun appendSuperMethodsFromInterfaces(methods: MutableSet<MethodItem>, cls: ClassItem) {
206 for (itf in cls.interfaceTypes()) {
207 val itfClass = itf.asClass() ?: continue
208
209 // Find the method in the interface.
210 itfClass.findMethod(this)?.let { superMethod ->
211 // A matching method was found so add it to the super methods if it is overrideable.
212 if (superMethod.isOverrideable()) {
213 methods.add(superMethod)
214 }
215 }
216 // A method could not be found in this interface so search its interfaces.
217 ?: appendSuperMethodsFromInterfaces(methods, itfClass)
218 }
219 }
220
221 /**
222 * Update the state of a [MethodItem] that has been copied from one [ClassItem] to another.
223 *
224 * This will update the [MethodItem] on which it is called to ensure that it is consistent with
225 * the [ClassItem] to which it now belongs. Called from the implementations of
226 * [MethodItem.duplicate].
227 */
228 protected fun updateCopiedMethodState() {
229 if (modifiers.isDefault() && !containingClass().isInterface()) {
230 mutateModifiers { setDefault(false) }
231 }
232 }
233 }
234
235 /**
236 * Check to see if the method is overrideable.
237 *
238 * Private and static methods cannot be overridden.
239 */
MethodItemnull240 private fun MethodItem.isOverrideable(): Boolean = !modifiers.isPrivate() && !modifiers.isStatic()
241