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.android.tools.metalava.SdkIdentifier 19 import java.io.PrintStream 20 import java.util.Collections 21 import java.util.TreeMap 22 import java.util.TreeSet 23 24 /** Represents the whole Android API. */ 25 class Api(private val mMin: Int) : ApiElement("Android API") { 26 private val mClasses: MutableMap<String, ApiClass> = HashMap() 27 28 /** 29 * Prints the whole API definition to a stream. 30 * 31 * @param stream the stream to print the XML elements to 32 */ printnull33 fun print(stream: PrintStream, sdkIdentifiers: Set<SdkIdentifier>) { 34 stream.print("<api version=\"3\"") 35 if (mMin > 1) { 36 stream.print(" min=\"$mMin\"") 37 } 38 stream.println(">") 39 for ((id, shortname, name, reference) in sdkIdentifiers) { 40 stream.println( 41 String.format( 42 "\t<sdk id=\"%d\" shortname=\"%s\" name=\"%s\" reference=\"%s\"/>", 43 id, 44 shortname, 45 name, 46 reference 47 ) 48 ) 49 } 50 print(mClasses.values, "class", "\t", stream) 51 printClosingTag("api", "", stream) 52 } 53 54 /** 55 * Adds or updates a class. 56 * 57 * @param name the name of the class 58 * @param version an API version in which the class existed 59 * @param deprecated whether the class was deprecated in the API version 60 * @return the newly created or a previously existed class 61 */ addClassnull62 fun addClass(name: String, version: Int, deprecated: Boolean): ApiClass { 63 var classElement = mClasses[name] 64 if (classElement == null) { 65 classElement = ApiClass(name, version, deprecated) 66 mClasses[name] = classElement 67 } else { 68 classElement.update(version, deprecated) 69 } 70 return classElement 71 } 72 findClassnull73 fun findClass(name: String?): ApiClass? { 74 return if (name == null) null else mClasses[name] 75 } 76 77 /** Cleans up the API surface for printing after all elements have been added. */ cleannull78 fun clean() { 79 inlineFromHiddenSuperClasses() 80 removeImplicitInterfaces() 81 removeOverridingMethods() 82 prunePackagePrivateClasses() 83 } 84 85 val classes: Collection<ApiClass> 86 get() = Collections.unmodifiableCollection(mClasses.values) 87 backfillHistoricalFixesnull88 fun backfillHistoricalFixes() { 89 backfillSdkExtensions() 90 } 91 backfillSdkExtensionsnull92 private fun backfillSdkExtensions() { 93 // SdkExtensions.getExtensionVersion was added in 30/R, but was a SystemApi 94 // to avoid publishing the versioning API publicly before there was any 95 // valid use for it. 96 // getAllExtensionsVersions was added as part of 31/S 97 // The class and its APIs were made public between S and T, but we pretend 98 // here like it was always public, for maximum backward compatibility. 99 val sdkExtensions = findClass("android/os/ext/SdkExtensions") 100 if (sdkExtensions != null && sdkExtensions.since != 30 && sdkExtensions.since != 33) { 101 throw AssertionError("Received unexpected historical data") 102 } else if (sdkExtensions == null || sdkExtensions.since == 30) { 103 // This is the system API db (30), or module-lib/system-server dbs (null) 104 // They don't need patching. 105 return 106 } 107 sdkExtensions.update(30, false) 108 sdkExtensions.addSuperClass("java/lang/Object", 30) 109 sdkExtensions.getMethod("getExtensionVersion(I)I")!!.update(30, false) 110 sdkExtensions.getMethod("getAllExtensionVersions()Ljava/util/Map;")!!.update(31, false) 111 } 112 113 /** 114 * The bytecode visitor registers interfaces listed for a class. However, a class will **also** 115 * implement interfaces implemented by the super classes. This isn't available in the class 116 * file, so after all classes have been read in, we iterate through all classes, and for those 117 * that have interfaces, we check up the inheritance chain to see if it has already been 118 * introduced in a super class at an earlier API level. 119 */ removeImplicitInterfacesnull120 fun removeImplicitInterfaces() { 121 for (classElement in mClasses.values) { 122 classElement.removeImplicitInterfaces(mClasses) 123 } 124 } 125 126 /** @see ApiClass.removeOverridingMethods */ removeOverridingMethodsnull127 fun removeOverridingMethods() { 128 for (classElement in mClasses.values) { 129 classElement.removeOverridingMethods(mClasses) 130 } 131 } 132 inlineFromHiddenSuperClassesnull133 fun inlineFromHiddenSuperClasses() { 134 val hidden: MutableMap<String, ApiClass> = HashMap() 135 for (classElement in mClasses.values) { 136 if ( 137 classElement.hiddenUntil < 0 138 ) { // hidden in the .jar files? (mMax==codebase, -1: jar files) 139 hidden[classElement.name] = classElement 140 } 141 } 142 for (classElement in mClasses.values) { 143 classElement.inlineFromHiddenSuperClasses(hidden) 144 } 145 } 146 prunePackagePrivateClassesnull147 fun prunePackagePrivateClasses() { 148 for (cls in mClasses.values) { 149 cls.removeHiddenSuperClasses(mClasses) 150 } 151 } 152 removeMissingClassesnull153 fun removeMissingClasses() { 154 for (cls in mClasses.values) { 155 cls.removeMissingClasses(mClasses) 156 } 157 } 158 verifyNoMissingClassesnull159 fun verifyNoMissingClasses() { 160 val results: MutableMap<String?, MutableSet<String?>> = TreeMap() 161 for (cls in mClasses.values) { 162 val missing = cls.findMissingClasses(mClasses) 163 // Have the missing classes as keys, and the referencing classes as values. 164 for (missingClass in missing) { 165 val missingName = missingClass.name 166 if (!results.containsKey(missingName)) { 167 results[missingName] = TreeSet() 168 } 169 results[missingName]!!.add(cls.name) 170 } 171 } 172 if (results.isNotEmpty()) { 173 var message = "" 174 for ((key, value) in results) { 175 message += """ 176 $key referenced by:""" 177 for (referencer in value) { 178 message += "\n $referencer" 179 } 180 } 181 throw IllegalStateException( 182 "There are classes in this API that reference other " + 183 "classes that do not exist in this API. " + 184 "This can happen when an api is provided by an apex, but referenced " + 185 "from non-updatable platform code. Use --remove-missing-classes-in-api-levels to " + 186 "make metalava remove these references instead of erroring out." + 187 message 188 ) 189 } 190 } 191 } 192