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