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