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 package com.android.tools.metalava.apilevels
17 
18 import com.google.common.collect.Iterables
19 import java.io.PrintStream
20 import java.util.concurrent.ConcurrentHashMap
21 import kotlin.math.abs
22 import kotlin.math.min
23 
24 /**
25  * Represents a class or an interface and its methods/fields. This is used to write the simplified
26  * XML file containing all the public API.
27  */
28 class ApiClass(name: String, version: Int, deprecated: Boolean) :
29     ApiElement(name, version, deprecated) {
30     private val mSuperClasses: MutableList<ApiElement> = ArrayList()
31     private val mInterfaces: MutableList<ApiElement> = ArrayList()
32 
33     /**
34      * If negative, never seen as public. The absolute value is the last api level it is seen as
35      * hidden in. E.g. "-5" means a class that was hidden in api levels 1-5, then it was deleted,
36      * and "8" means a class that was hidden in api levels 1-8 then made public in 9.
37      */
38     var hiddenUntil = 0 // Package private class?
39     private val mFields: MutableMap<String, ApiElement> = ConcurrentHashMap()
40     private val mMethods: MutableMap<String, ApiElement> = ConcurrentHashMap()
41 
addFieldnull42     fun addField(name: String, version: Int, deprecated: Boolean): ApiElement {
43         return addToMap(mFields, name, version, deprecated)
44     }
45 
46     val fields: Collection<ApiElement>
47         get() = mFields.values
48 
addMethodnull49     fun addMethod(name: String, version: Int, deprecated: Boolean): ApiElement {
50         // Correct historical mistake in android.jar files
51         var correctedName = name
52         if (correctedName.endsWith(")Ljava/lang/AbstractStringBuilder;")) {
53             correctedName =
54                 correctedName.substring(
55                     0,
56                     correctedName.length - ")Ljava/lang/AbstractStringBuilder;".length
57                 ) + ")L" + this.name + ";"
58         }
59         return addToMap(mMethods, correctedName, version, deprecated)
60     }
61 
62     val methods: Collection<ApiElement>
63         get() = mMethods.values
64 
addSuperClassnull65     fun addSuperClass(superClass: String, since: Int): ApiElement {
66         return addToArray(mSuperClasses, superClass, since)
67     }
68 
removeSuperClassnull69     fun removeSuperClass(superClass: String): ApiElement? {
70         val entry = findByName(mSuperClasses, superClass)
71         if (entry != null) {
72             mSuperClasses.remove(entry)
73         }
74         return entry
75     }
76 
77     val superClasses: List<ApiElement>
78         get() = mSuperClasses
79 
updateHiddennull80     fun updateHidden(api: Int, hidden: Boolean) {
81         hiddenUntil = if (hidden) -api else abs(api)
82     }
83 
alwaysHiddennull84     private fun alwaysHidden(): Boolean {
85         return hiddenUntil < 0
86     }
87 
addInterfacenull88     fun addInterface(interfaceClass: String, since: Int) {
89         addToArray(mInterfaces, interfaceClass, since)
90     }
91 
92     val interfaces: List<ApiElement>
93         get() = mInterfaces
94 
addToMapnull95     private fun addToMap(
96         elements: MutableMap<String, ApiElement>,
97         name: String,
98         version: Int,
99         deprecated: Boolean
100     ): ApiElement {
101         var element = elements[name]
102         if (element == null) {
103             element = ApiElement(name, version, deprecated)
104             elements[name] = element
105         } else {
106             element.update(version, deprecated)
107         }
108         return element
109     }
110 
addToArraynull111     private fun addToArray(
112         elements: MutableCollection<ApiElement>,
113         name: String,
114         version: Int
115     ): ApiElement {
116         var element = findByName(elements, name)
117         if (element == null) {
118             element = ApiElement(name, version)
119             elements.add(element)
120         } else {
121             element.update(version)
122         }
123         return element
124     }
125 
findByNamenull126     private fun findByName(collection: Collection<ApiElement>, name: String): ApiElement? {
127         for (element in collection) {
128             if (element.name == name) {
129                 return element
130             }
131         }
132         return null
133     }
134 
printnull135     override fun print(
136         tag: String?,
137         parentElement: ApiElement,
138         indent: String,
139         stream: PrintStream
140     ) {
141         if (hiddenUntil < 0) {
142             return
143         }
144         super.print(tag, false, parentElement, indent, stream)
145         val innerIndent = indent + '\t'
146         print(mSuperClasses, "extends", innerIndent, stream)
147         print(mInterfaces, "implements", innerIndent, stream)
148         print(mMethods.values, "method", innerIndent, stream)
149         print(mFields.values, "field", innerIndent, stream)
150         printClosingTag(tag, indent, stream)
151     }
152 
153     /**
154      * Removes all interfaces that are also implemented by superclasses or extended by interfaces
155      * this class implements.
156      *
157      * @param allClasses all classes keyed by their names.
158      */
removeImplicitInterfacesnull159     fun removeImplicitInterfaces(allClasses: Map<String, ApiClass>) {
160         if (mInterfaces.isEmpty() || mSuperClasses.isEmpty()) {
161             return
162         }
163         val iterator = mInterfaces.iterator()
164         while (iterator.hasNext()) {
165             val interfaceElement = iterator.next()
166             for (superClass in mSuperClasses) {
167                 if (superClass.introducedNotLaterThan(interfaceElement)) {
168                     val cls = allClasses[superClass.name]
169                     if (cls != null && cls.implementsInterface(interfaceElement, allClasses)) {
170                         iterator.remove()
171                         break
172                     }
173                 }
174             }
175         }
176     }
177 
implementsInterfacenull178     private fun implementsInterface(
179         interfaceElement: ApiElement,
180         allClasses: Map<String, ApiClass>
181     ): Boolean {
182         for (localInterface in mInterfaces) {
183             if (localInterface.introducedNotLaterThan(interfaceElement)) {
184                 if (interfaceElement.name == localInterface.name) {
185                     return true
186                 }
187                 // Check parent interface.
188                 val cls = allClasses[localInterface.name]
189                 if (cls != null && cls.implementsInterface(interfaceElement, allClasses)) {
190                     return true
191                 }
192             }
193         }
194         for (superClass in mSuperClasses) {
195             if (superClass.introducedNotLaterThan(interfaceElement)) {
196                 val cls = allClasses[superClass.name]
197                 if (cls != null && cls.implementsInterface(interfaceElement, allClasses)) {
198                     return true
199                 }
200             }
201         }
202         return false
203     }
204 
205     /**
206      * Removes all methods that override method declared by superclasses and interfaces of this
207      * class.
208      *
209      * @param allClasses all classes keyed by their names.
210      */
removeOverridingMethodsnull211     fun removeOverridingMethods(allClasses: Map<String, ApiClass>) {
212         val it: MutableIterator<Map.Entry<String, ApiElement>> = mMethods.entries.iterator()
213         while (it.hasNext()) {
214             val (_, method) = it.next()
215             if (!method.name.startsWith("<init>(") && isOverrideOfInherited(method, allClasses)) {
216                 it.remove()
217             }
218         }
219     }
220 
221     /**
222      * Checks if the given method overrides one of the methods defined by this class or its
223      * superclasses or interfaces.
224      *
225      * @param method the method to check
226      * @param allClasses the map containing all API classes
227      * @return true if the method is an override
228      */
isOverridenull229     private fun isOverride(method: ApiElement, allClasses: Map<String, ApiClass>): Boolean {
230         val name = method.name
231         val localMethod = mMethods[name]
232         return if (localMethod != null && localMethod.introducedNotLaterThan(method)) {
233             // This class has the method, and it was introduced in at the same api level
234             // as the child method, or before.
235             true
236         } else {
237             isOverrideOfInherited(method, allClasses)
238         }
239     }
240 
241     /**
242      * Checks if the given method overrides one of the methods declared by ancestors of this class.
243      */
isOverrideOfInheritednull244     private fun isOverrideOfInherited(
245         method: ApiElement,
246         allClasses: Map<String, ApiClass>
247     ): Boolean {
248         // Check this class' parents.
249         for (parent in Iterables.concat(mSuperClasses, mInterfaces)) {
250             // Only check the parent if it was a parent class at the introduction of the method.
251             if (parent!!.introducedNotLaterThan(method)) {
252                 val cls = allClasses[parent.name]
253                 if (cls != null && cls.isOverride(method, allClasses)) {
254                     return true
255                 }
256             }
257         }
258         return false
259     }
260 
toStringnull261     override fun toString(): String {
262         return name
263     }
264 
265     private var haveInlined = false
266 
inlineFromHiddenSuperClassesnull267     fun inlineFromHiddenSuperClasses(hidden: Map<String, ApiClass>) {
268         if (haveInlined) {
269             return
270         }
271         haveInlined = true
272         for (superClass in superClasses) {
273             val hiddenSuper = hidden[superClass.name]
274             if (hiddenSuper != null) {
275                 hiddenSuper.inlineFromHiddenSuperClasses(hidden)
276                 val myMethods = mMethods
277                 val myFields = mFields
278                 for ((name, value) in hiddenSuper.mMethods) {
279                     if (!myMethods.containsKey(name)) {
280                         myMethods[name] = value
281                     }
282                 }
283                 for ((name, value) in hiddenSuper.mFields) {
284                     if (!myFields.containsKey(name)) {
285                         myFields[name] = value
286                     }
287                 }
288             }
289         }
290     }
291 
removeHiddenSuperClassesnull292     fun removeHiddenSuperClasses(api: Map<String, ApiClass>) {
293         // If we've included a package private class in the super class map (from the older
294         // android.jar files)
295         // remove these here and replace with the filtered super classes, updating API levels in the
296         // process
297         val iterator = mSuperClasses.listIterator()
298         var min = Int.MAX_VALUE
299         while (iterator.hasNext()) {
300             val next = iterator.next()
301             min = min(min, next.since)
302             val extendsClass = api[next.name]
303             if (extendsClass != null && extendsClass.alwaysHidden()) {
304                 val since = extendsClass.since
305                 iterator.remove()
306                 for (other in mSuperClasses) {
307                     if (other.since >= since) {
308                         other.update(min)
309                     }
310                 }
311                 break
312             }
313         }
314     }
315 
316     // Ensure this class doesn't extend/implement any other classes/interfaces that are
317     // not in the provided api. This can happen when a class in an android.jar file
318     // encodes the inheritance, but the class that is inherited is not present in any
319     // android.jar file. The class would instead be present in an apex's stub jar file.
320     // An example of this is the QosSessionAttributes interface being provided by the
321     // Connectivity apex, but being implemented by NrQosSessionAttributes from
322     // frameworks/base/telephony.
removeMissingClassesnull323     fun removeMissingClasses(api: Map<String, ApiClass>) {
324         val superClassIter = mSuperClasses.iterator()
325         while (superClassIter.hasNext()) {
326             val scls = superClassIter.next()
327             if (!api.containsKey(scls.name)) {
328                 superClassIter.remove()
329             }
330         }
331         val interfacesIter = mInterfaces.iterator()
332         while (interfacesIter.hasNext()) {
333             val intf = interfacesIter.next()
334             if (!api.containsKey(intf.name)) {
335                 interfacesIter.remove()
336             }
337         }
338     }
339 
340     // Returns the set of superclasses or interfaces are not present in the provided api map
findMissingClassesnull341     fun findMissingClasses(api: Map<String, ApiClass>): Set<ApiElement> {
342         val result: MutableSet<ApiElement> = HashSet()
343         for (scls in mSuperClasses) {
344             if (!api.containsKey(scls.name)) {
345                 result.add(scls)
346             }
347         }
348         for (intf in mInterfaces) {
349             if (!api.containsKey(intf.name)) {
350                 result.add(intf)
351             }
352         }
353         return result
354     }
355 
356     val fieldIterator: Iterator<ApiElement>
357         get() = mFields.values.iterator()
358 
359     val methodIterator: Iterator<ApiElement>
360         get() = mMethods.values.iterator()
361 
getMethodnull362     fun getMethod(name: String?): ApiElement? {
363         return mMethods[name]
364     }
365 }
366