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