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