• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 java.util.Collections
19 import java.util.TreeMap
20 import java.util.TreeSet
21 
22 /**
23  * Represents the whole Android API.
24  *
25  * @param useInternalNames `true` if JVM internal names should be used, `false` otherwise.
26  */
27 class Api(val useInternalNames: Boolean) : ParentApiElement {
28     /**
29      * This has to behave as if it exists since before any specific version (so that every class
30      * always specifies its `since` attribute.
31      */
32     override val since: ApiVersion = ApiVersion.LOWEST
33 
34     override var lastPresentIn = since
35         private set
36 
37     override val sdks: String? = null
38 
39     override val deprecatedIn: ApiVersion? = null
40 
41     private val mClasses: MutableMap<String, ApiClass> = HashMap()
42 
43     /**
44      * Updates this with information for a specific API version.
45      *
46      * @param apiVersion an API version that this contains.
47      */
updatenull48     fun update(apiVersion: ApiVersion) {
49         // Track the last version added to this.
50         if (lastPresentIn < apiVersion) {
51             lastPresentIn = apiVersion
52         }
53     }
54 
toStringnull55     override fun toString() = "Android Api"
56 
57     /**
58      * Updates the [ApiClass] for the class called [name], creating and adding one if necessary.
59      *
60      * @param name the name of the class
61      * @param updater the [ApiHistoryUpdater] that will update the element with information about
62      *   the version to which it belongs.
63      * @param deprecated whether the class was deprecated in the API version
64      * @return the newly created or a previously existed class
65      */
66     fun updateClass(
67         name: String,
68         updater: ApiHistoryUpdater,
69         deprecated: Boolean,
70     ): ApiClass {
71         val existing = mClasses[name]
72         val classElement = existing ?: ApiClass(name).apply { mClasses[name] = this }
73         updater.update(classElement, deprecated)
74         return classElement
75     }
76 
findClassnull77     fun findClass(name: String?): ApiClass? {
78         return if (name == null) null else mClasses[name]
79     }
80 
81     /** Cleans up the API surface for printing after all elements have been added. */
cleannull82     fun clean() {
83         inlineFromHiddenSuperClasses()
84         removeImplicitInterfaces()
85         removeOverridingMethods()
86         prunePackagePrivateClasses()
87     }
88 
89     val classes: Collection<ApiClass>
90         get() = Collections.unmodifiableCollection(mClasses.values)
91 
92     /**
93      * Patch up the `android.os.ext.SdkExtensions` history to improve backward compatibility.
94      *
95      * This does nothing if the class is not defined in this [Api].
96      */
patchSdkExtensionsHistorynull97     fun patchSdkExtensionsHistory() {
98         val sdkExtensions =
99             findClass("android/os/ext/SdkExtensions")
100             // This is either for the module-lib/system-server (null) or for a non-Android API.
101             // Either way it does not need patching.
102             ?: return
103 
104         val sdk30 = ApiVersion.fromLevel(30)
105         val sdk31 = ApiVersion.fromLevel(31)
106         val sdk33 = ApiVersion.fromLevel(33)
107         val sdkExtensionsSince = sdkExtensions.since
108         if (sdkExtensionsSince != sdk30 && sdkExtensionsSince != sdk33) {
109             throw AssertionError("Received unexpected historical data")
110         } else if (sdkExtensionsSince == sdk30) {
111             // This is the system API db (30). The class does not need patching but the members do.
112             // Drop through.
113         } else {
114             // The class was added in 30/R, but was a SystemApi to avoid publishing the versioning
115             // API publicly before there was any valid use for it. It was made public between S and
116             // T, but we pretend here like it was always public, for maximum backward compatibility.
117             sdkExtensions.update(sdk30, false)
118         }
119 
120         val sdk30Updater = ApiHistoryUpdater.forApiVersion(sdk30)
121         val sdk31Updater = ApiHistoryUpdater.forApiVersion(sdk31)
122 
123         // Remove the sdks attribute from the extends for public and system.
124         sdkExtensions.updateSuperClass("java/lang/Object", sdk30Updater).apply {
125             // Pretend this was not added in any extension.
126             clearSdkExtensionInfo()
127         }
128 
129         // getExtensionVersion was added in 30/R along with the class, and just like the class we
130         // pretend it was always public.
131         sdkExtensions.updateMethod("getExtensionVersion(I)I", sdk30Updater, false)
132 
133         // getAllExtensionsVersions was added as part of 31/S SystemApi. Just like for the class
134         // we pretend it was always public.
135         sdkExtensions
136             .updateMethod("getAllExtensionVersions()Ljava/util/Map;", sdk31Updater, false)
137             .apply {
138                 // Pretend this was not added in any extension.
139                 clearSdkExtensionInfo()
140             }
141     }
142 
143     /**
144      * The bytecode visitor registers interfaces listed for a class. However, a class will **also**
145      * implement interfaces implemented by the super classes. This isn't available in the class
146      * file, so after all classes have been read in, we iterate through all classes, and for those
147      * that have interfaces, we check up the inheritance chain to see if it has already been
148      * introduced in a super class at an earlier API level.
149      */
removeImplicitInterfacesnull150     private fun removeImplicitInterfaces() {
151         for (classElement in mClasses.values) {
152             classElement.removeImplicitInterfaces(mClasses)
153         }
154     }
155 
156     /** @see ApiClass.removeOverridingMethods */
removeOverridingMethodsnull157     private fun removeOverridingMethods() {
158         for (classElement in mClasses.values) {
159             classElement.removeOverridingMethods(mClasses)
160         }
161     }
162 
inlineFromHiddenSuperClassesnull163     private fun inlineFromHiddenSuperClasses() {
164         val hidden: MutableMap<String, ApiClass> = HashMap()
165         for (classElement in mClasses.values) {
166             if (classElement.alwaysHidden) {
167                 // hidden in the .jar files? (mMax==codebase, -1: jar files)
168                 hidden[classElement.name] = classElement
169             }
170         }
171         for (classElement in mClasses.values) {
172             classElement.inlineFromHiddenSuperClasses(hidden)
173         }
174     }
175 
prunePackagePrivateClassesnull176     private fun prunePackagePrivateClasses() {
177         for (cls in mClasses.values) {
178             cls.removeHiddenSuperClasses(mClasses)
179         }
180     }
181 
removeMissingClassesnull182     fun removeMissingClasses() {
183         for (cls in mClasses.values) {
184             cls.removeMissingClasses(mClasses)
185         }
186     }
187 
verifyNoMissingClassesnull188     fun verifyNoMissingClasses() {
189         val results: MutableMap<String?, MutableSet<String?>> = TreeMap()
190         for (cls in mClasses.values) {
191             val missing = cls.findMissingClasses(mClasses)
192             // Have the missing classes as keys, and the referencing classes as values.
193             for (missingClass in missing) {
194                 val missingName = missingClass.name
195                 if (!results.containsKey(missingName)) {
196                     results[missingName] = TreeSet()
197                 }
198                 results[missingName]!!.add(cls.name)
199             }
200         }
201         if (results.isNotEmpty()) {
202             var message = ""
203             for ((key, value) in results) {
204                 message += """
205   $key referenced by:"""
206                 for (referencer in value) {
207                     message += "\n    $referencer"
208                 }
209             }
210             throw IllegalStateException(
211                 "There are classes in this API that reference other " +
212                     "classes that do not exist in this API. " +
213                     "This can happen when an api is provided by an apex, but referenced " +
214                     "from non-updatable platform code. Use --remove-missing-classes-in-api-levels to " +
215                     "make metalava remove these references instead of erroring out." +
216                     message
217             )
218         }
219     }
220 }
221