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 java.io.PrintStream 20 import java.util.concurrent.ConcurrentHashMap 21 import kotlin.math.abs 22 import kotlin.math.min 23 24 /** 25 * Represents a class or an interface and its methods/fields. This is used to write the simplified 26 * XML file containing all the public API. 27 */ 28 class ApiClass(name: String, version: Int, deprecated: Boolean) : 29 ApiElement(name, version, deprecated) { 30 private val mSuperClasses: MutableList<ApiElement> = ArrayList() 31 private val mInterfaces: MutableList<ApiElement> = ArrayList() 32 33 /** 34 * If negative, never seen as public. The absolute value is the last api level it is seen as 35 * hidden in. E.g. "-5" means a class that was hidden in api levels 1-5, then it was deleted, 36 * and "8" means a class that was hidden in api levels 1-8 then made public in 9. 37 */ 38 var hiddenUntil = 0 // Package private class? 39 private val mFields: MutableMap<String, ApiElement> = ConcurrentHashMap() 40 private val mMethods: MutableMap<String, ApiElement> = ConcurrentHashMap() 41 addFieldnull42 fun addField(name: String, version: Int, deprecated: Boolean): ApiElement { 43 return addToMap(mFields, name, version, deprecated) 44 } 45 46 val fields: Collection<ApiElement> 47 get() = mFields.values 48 addMethodnull49 fun addMethod(name: String, version: Int, deprecated: Boolean): ApiElement { 50 // Correct historical mistake in android.jar files 51 var correctedName = name 52 if (correctedName.endsWith(")Ljava/lang/AbstractStringBuilder;")) { 53 correctedName = 54 correctedName.substring( 55 0, 56 correctedName.length - ")Ljava/lang/AbstractStringBuilder;".length 57 ) + ")L" + this.name + ";" 58 } 59 return addToMap(mMethods, correctedName, version, deprecated) 60 } 61 62 val methods: Collection<ApiElement> 63 get() = mMethods.values 64 addSuperClassnull65 fun addSuperClass(superClass: String, since: Int): ApiElement { 66 return addToArray(mSuperClasses, superClass, since) 67 } 68 removeSuperClassnull69 fun removeSuperClass(superClass: String): ApiElement? { 70 val entry = findByName(mSuperClasses, superClass) 71 if (entry != null) { 72 mSuperClasses.remove(entry) 73 } 74 return entry 75 } 76 77 val superClasses: List<ApiElement> 78 get() = mSuperClasses 79 updateHiddennull80 fun updateHidden(api: Int, hidden: Boolean) { 81 hiddenUntil = if (hidden) -api else abs(api) 82 } 83 alwaysHiddennull84 private fun alwaysHidden(): Boolean { 85 return hiddenUntil < 0 86 } 87 addInterfacenull88 fun addInterface(interfaceClass: String, since: Int) { 89 addToArray(mInterfaces, interfaceClass, since) 90 } 91 92 val interfaces: List<ApiElement> 93 get() = mInterfaces 94 addToMapnull95 private fun addToMap( 96 elements: MutableMap<String, ApiElement>, 97 name: String, 98 version: Int, 99 deprecated: Boolean 100 ): ApiElement { 101 var element = elements[name] 102 if (element == null) { 103 element = ApiElement(name, version, deprecated) 104 elements[name] = element 105 } else { 106 element.update(version, deprecated) 107 } 108 return element 109 } 110 addToArraynull111 private fun addToArray( 112 elements: MutableCollection<ApiElement>, 113 name: String, 114 version: Int 115 ): ApiElement { 116 var element = findByName(elements, name) 117 if (element == null) { 118 element = ApiElement(name, version) 119 elements.add(element) 120 } else { 121 element.update(version) 122 } 123 return element 124 } 125 findByNamenull126 private fun findByName(collection: Collection<ApiElement>, name: String): ApiElement? { 127 for (element in collection) { 128 if (element.name == name) { 129 return element 130 } 131 } 132 return null 133 } 134 printnull135 override fun print( 136 tag: String?, 137 parentElement: ApiElement, 138 indent: String, 139 stream: PrintStream 140 ) { 141 if (hiddenUntil < 0) { 142 return 143 } 144 super.print(tag, false, parentElement, indent, stream) 145 val innerIndent = indent + '\t' 146 print(mSuperClasses, "extends", innerIndent, stream) 147 print(mInterfaces, "implements", innerIndent, stream) 148 print(mMethods.values, "method", innerIndent, stream) 149 print(mFields.values, "field", innerIndent, stream) 150 printClosingTag(tag, indent, stream) 151 } 152 153 /** 154 * Removes all interfaces that are also implemented by superclasses or extended by interfaces 155 * this class implements. 156 * 157 * @param allClasses all classes keyed by their names. 158 */ removeImplicitInterfacesnull159 fun removeImplicitInterfaces(allClasses: Map<String, ApiClass>) { 160 if (mInterfaces.isEmpty() || mSuperClasses.isEmpty()) { 161 return 162 } 163 val iterator = mInterfaces.iterator() 164 while (iterator.hasNext()) { 165 val interfaceElement = iterator.next() 166 for (superClass in mSuperClasses) { 167 if (superClass.introducedNotLaterThan(interfaceElement)) { 168 val cls = allClasses[superClass.name] 169 if (cls != null && cls.implementsInterface(interfaceElement, allClasses)) { 170 iterator.remove() 171 break 172 } 173 } 174 } 175 } 176 } 177 implementsInterfacenull178 private fun implementsInterface( 179 interfaceElement: ApiElement, 180 allClasses: Map<String, ApiClass> 181 ): Boolean { 182 for (localInterface in mInterfaces) { 183 if (localInterface.introducedNotLaterThan(interfaceElement)) { 184 if (interfaceElement.name == localInterface.name) { 185 return true 186 } 187 // Check parent interface. 188 val cls = allClasses[localInterface.name] 189 if (cls != null && cls.implementsInterface(interfaceElement, allClasses)) { 190 return true 191 } 192 } 193 } 194 for (superClass in mSuperClasses) { 195 if (superClass.introducedNotLaterThan(interfaceElement)) { 196 val cls = allClasses[superClass.name] 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 207 * class. 208 * 209 * @param allClasses all classes keyed by their names. 210 */ removeOverridingMethodsnull211 fun removeOverridingMethods(allClasses: Map<String, ApiClass>) { 212 val it: MutableIterator<Map.Entry<String, ApiElement>> = mMethods.entries.iterator() 213 while (it.hasNext()) { 214 val (_, method) = it.next() 215 if (!method.name.startsWith("<init>(") && isOverrideOfInherited(method, allClasses)) { 216 it.remove() 217 } 218 } 219 } 220 221 /** 222 * Checks if the given method overrides one of the methods defined by this class or its 223 * superclasses or interfaces. 224 * 225 * @param method the method to check 226 * @param allClasses the map containing all API classes 227 * @return true if the method is an override 228 */ isOverridenull229 private fun isOverride(method: ApiElement, allClasses: Map<String, ApiClass>): Boolean { 230 val name = method.name 231 val localMethod = mMethods[name] 232 return if (localMethod != null && localMethod.introducedNotLaterThan(method)) { 233 // This class has the method, and it was introduced in at the same api level 234 // as the child method, or before. 235 true 236 } else { 237 isOverrideOfInherited(method, allClasses) 238 } 239 } 240 241 /** 242 * Checks if the given method overrides one of the methods declared by ancestors of this class. 243 */ isOverrideOfInheritednull244 private fun isOverrideOfInherited( 245 method: ApiElement, 246 allClasses: Map<String, ApiClass> 247 ): Boolean { 248 // Check this class' parents. 249 for (parent in 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 val cls = allClasses[parent.name] 253 if (cls != null && cls.isOverride(method, allClasses)) { 254 return true 255 } 256 } 257 } 258 return false 259 } 260 toStringnull261 override fun toString(): String { 262 return name 263 } 264 265 private var haveInlined = false 266 inlineFromHiddenSuperClassesnull267 fun inlineFromHiddenSuperClasses(hidden: Map<String, ApiClass>) { 268 if (haveInlined) { 269 return 270 } 271 haveInlined = true 272 for (superClass in superClasses) { 273 val hiddenSuper = hidden[superClass.name] 274 if (hiddenSuper != null) { 275 hiddenSuper.inlineFromHiddenSuperClasses(hidden) 276 val myMethods = mMethods 277 val myFields = mFields 278 for ((name, value) in hiddenSuper.mMethods) { 279 if (!myMethods.containsKey(name)) { 280 myMethods[name] = value 281 } 282 } 283 for ((name, value) in hiddenSuper.mFields) { 284 if (!myFields.containsKey(name)) { 285 myFields[name] = value 286 } 287 } 288 } 289 } 290 } 291 removeHiddenSuperClassesnull292 fun removeHiddenSuperClasses(api: Map<String, ApiClass>) { 293 // If we've included a package private class in the super class map (from the older 294 // android.jar files) 295 // remove these here and replace with the filtered super classes, updating API levels in the 296 // process 297 val iterator = mSuperClasses.listIterator() 298 var min = Int.MAX_VALUE 299 while (iterator.hasNext()) { 300 val next = iterator.next() 301 min = min(min, next.since) 302 val extendsClass = api[next.name] 303 if (extendsClass != null && extendsClass.alwaysHidden()) { 304 val since = extendsClass.since 305 iterator.remove() 306 for (other in mSuperClasses) { 307 if (other.since >= since) { 308 other.update(min) 309 } 310 } 311 break 312 } 313 } 314 } 315 316 // Ensure this class doesn't extend/implement any other classes/interfaces that are 317 // not in the provided api. This can happen when a class in an android.jar file 318 // encodes the inheritance, but the class that is inherited is not present in any 319 // android.jar file. The class would instead be present in an apex's stub jar file. 320 // An example of this is the QosSessionAttributes interface being provided by the 321 // Connectivity apex, but being implemented by NrQosSessionAttributes from 322 // frameworks/base/telephony. removeMissingClassesnull323 fun removeMissingClasses(api: Map<String, ApiClass>) { 324 val superClassIter = mSuperClasses.iterator() 325 while (superClassIter.hasNext()) { 326 val scls = superClassIter.next() 327 if (!api.containsKey(scls.name)) { 328 superClassIter.remove() 329 } 330 } 331 val interfacesIter = mInterfaces.iterator() 332 while (interfacesIter.hasNext()) { 333 val intf = interfacesIter.next() 334 if (!api.containsKey(intf.name)) { 335 interfacesIter.remove() 336 } 337 } 338 } 339 340 // Returns the set of superclasses or interfaces are not present in the provided api map findMissingClassesnull341 fun findMissingClasses(api: Map<String, ApiClass>): Set<ApiElement> { 342 val result: MutableSet<ApiElement> = HashSet() 343 for (scls in mSuperClasses) { 344 if (!api.containsKey(scls.name)) { 345 result.add(scls) 346 } 347 } 348 for (intf in mInterfaces) { 349 if (!api.containsKey(intf.name)) { 350 result.add(intf) 351 } 352 } 353 return result 354 } 355 356 val fieldIterator: Iterator<ApiElement> 357 get() = mFields.values.iterator() 358 359 val methodIterator: Iterator<ApiElement> 360 get() = mMethods.values.iterator() 361 getMethodnull362 fun getMethod(name: String?): ApiElement? { 363 return mMethods[name] 364 } 365 } 366