• 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 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