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