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