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