1 /* 2 * Copyright (C) 2020 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 17 package com.android.tools.metalava.model.text 18 19 import com.android.tools.metalava.ApiType 20 import com.android.tools.metalava.CodebaseComparator 21 import com.android.tools.metalava.ComparisonVisitor 22 import com.android.tools.metalava.FileFormat 23 import com.android.tools.metalava.JAVA_LANG_ANNOTATION 24 import com.android.tools.metalava.JAVA_LANG_ENUM 25 import com.android.tools.metalava.JAVA_LANG_OBJECT 26 import com.android.tools.metalava.JAVA_LANG_THROWABLE 27 import com.android.tools.metalava.compatibility 28 import com.android.tools.metalava.model.AnnotationItem 29 import com.android.tools.metalava.model.ClassItem 30 import com.android.tools.metalava.model.Codebase 31 import com.android.tools.metalava.model.ConstructorItem 32 import com.android.tools.metalava.model.DefaultCodebase 33 import com.android.tools.metalava.model.DefaultModifierList 34 import com.android.tools.metalava.model.FieldItem 35 import com.android.tools.metalava.model.Item 36 import com.android.tools.metalava.model.MethodItem 37 import com.android.tools.metalava.model.PackageItem 38 import com.android.tools.metalava.model.PackageList 39 import com.android.tools.metalava.model.PropertyItem 40 import com.android.tools.metalava.model.TypeParameterList 41 import com.android.tools.metalava.model.visitors.ItemVisitor 42 import com.android.tools.metalava.model.visitors.TypeVisitor 43 import java.io.File 44 import java.util.ArrayList 45 import java.util.HashMap 46 import java.util.function.Predicate 47 import kotlin.math.min 48 49 // Copy of ApiInfo in doclava1 (converted to Kotlin + some cleanup to make it work with metalava's data structures. 50 // (Converted to Kotlin such that I can inherit behavior via interfaces, in particular Codebase.) 51 class TextCodebase(location: File) : DefaultCodebase(location) { 52 /** 53 * Whether types should be interpreted to be in Kotlin format (e.g. ? suffix means nullable, 54 * ! suffix means unknown, and absence of a suffix means not nullable. 55 */ 56 var kotlinStyleNulls = false 57 58 private val mPackages = HashMap<String, TextPackageItem>(300) 59 private val mAllClasses = HashMap<String, TextClassItem>(30000) 60 private val mClassToSuper = HashMap<TextClassItem, String>(30000) 61 private val mClassToInterface = HashMap<TextClassItem, ArrayList<String>>(10000) 62 63 override var description = "Codebase" 64 override var preFiltered: Boolean = true 65 trustedApinull66 override fun trustedApi(): Boolean = true 67 68 /** 69 * Signature file format version, if found. Type "GradleVersion" is misleading; it's just a convenient 70 * version class. 71 */ 72 var format: FileFormat = FileFormat.V1 // not specifying format: assumed to be doclava, 1.0 73 74 override fun getPackages(): PackageList { 75 val list = ArrayList<PackageItem>(mPackages.values) 76 list.sortWith(PackageItem.comparator) 77 return PackageList(this, list) 78 } 79 sizenull80 override fun size(): Int { 81 return mPackages.size 82 } 83 findClassnull84 override fun findClass(className: String): TextClassItem? { 85 return mAllClasses[className] 86 } 87 resolveInterfacesnull88 private fun resolveInterfaces(all: List<TextClassItem>) { 89 for (cl in all) { 90 val interfaces = mClassToInterface[cl] ?: continue 91 for (interfaceName in interfaces) { 92 getOrCreateClass(interfaceName, isInterface = true) 93 cl.addInterface(obtainTypeFromString(interfaceName)) 94 } 95 } 96 } 97 supportsDocumentationnull98 override fun supportsDocumentation(): Boolean = false 99 100 fun mapClassToSuper(classInfo: TextClassItem, superclass: String?) { 101 superclass?.let { mClassToSuper.put(classInfo, superclass) } 102 } 103 mapClassToInterfacenull104 fun mapClassToInterface(classInfo: TextClassItem, iface: String) { 105 if (!mClassToInterface.containsKey(classInfo)) { 106 mClassToInterface[classInfo] = ArrayList() 107 } 108 mClassToInterface[classInfo]?.add(iface) 109 } 110 implementsInterfacenull111 fun implementsInterface(classInfo: TextClassItem, iface: String): Boolean { 112 return mClassToInterface[classInfo]?.contains(iface) ?: false 113 } 114 addPackagenull115 fun addPackage(pInfo: TextPackageItem) { 116 // track the set of organized packages in the API 117 mPackages[pInfo.name()] = pInfo 118 119 // accumulate a direct map of all the classes in the API 120 for (cl in pInfo.allClasses()) { 121 mAllClasses[cl.qualifiedName()] = cl as TextClassItem 122 } 123 } 124 resolveSuperclassesnull125 private fun resolveSuperclasses(allClasses: List<TextClassItem>) { 126 for (cl in allClasses) { 127 // java.lang.Object has no superclass 128 if (cl.isJavaLangObject()) { 129 continue 130 } 131 var scName: String? = mClassToSuper[cl] 132 if (scName == null) { 133 scName = when { 134 cl.isEnum() -> JAVA_LANG_ENUM 135 cl.isAnnotationType() -> JAVA_LANG_ANNOTATION 136 else -> { 137 val existing = cl.superClassType()?.toTypeString() 138 val s = existing ?: JAVA_LANG_OBJECT 139 s // unnecessary variable, works around current compiler believing the expression to be nullable 140 } 141 } 142 } 143 144 val superclass = getOrCreateClass(scName) 145 cl.setSuperClass(superclass, obtainTypeFromString(scName)) 146 } 147 } 148 resolveThrowsClassesnull149 private fun resolveThrowsClasses(all: List<TextClassItem>) { 150 for (cl in all) { 151 for (methodItem in cl.constructors()) { 152 resolveThrowsClasses(methodItem) 153 } 154 for (methodItem in cl.methods()) { 155 resolveThrowsClasses(methodItem) 156 } 157 } 158 } 159 resolveThrowsClassesnull160 private fun resolveThrowsClasses(methodItem: MethodItem) { 161 val methodInfo = methodItem as TextMethodItem 162 val names = methodInfo.throwsTypeNames() 163 if (names.isNotEmpty()) { 164 val result = ArrayList<TextClassItem>() 165 for (exception in names) { 166 var exceptionClass: TextClassItem? = mAllClasses[exception] 167 if (exceptionClass == null) { 168 // Exception not provided by this codebase. Inject a stub. 169 exceptionClass = getOrCreateClass(exception) 170 // Set super class to throwable? 171 if (exception != JAVA_LANG_THROWABLE) { 172 exceptionClass.setSuperClass( 173 getOrCreateClass(JAVA_LANG_THROWABLE), 174 TextTypeItem(this, JAVA_LANG_THROWABLE) 175 ) 176 } 177 } 178 result.add(exceptionClass) 179 } 180 methodInfo.setThrowsList(result) 181 } 182 } 183 resolveInnerClassesnull184 private fun resolveInnerClasses(packages: List<TextPackageItem>) { 185 for (pkg in packages) { 186 // make copy: we'll be removing non-top level classes during iteration 187 val classes = ArrayList(pkg.classList()) 188 for (cls in classes) { 189 val cl = cls as TextClassItem 190 val name = cl.name 191 var index = name.lastIndexOf('.') 192 if (index != -1) { 193 cl.name = name.substring(index + 1) 194 val qualifiedName = cl.qualifiedName 195 index = qualifiedName.lastIndexOf('.') 196 assert(index != -1) { qualifiedName } 197 val outerClassName = qualifiedName.substring(0, index) 198 val outerClass = getOrCreateClass(outerClassName) 199 cl.containingClass = outerClass 200 outerClass.addInnerClass(cl) 201 } 202 } 203 } 204 205 for (pkg in packages) { 206 pkg.pruneClassList() 207 } 208 } 209 registerClassnull210 fun registerClass(cls: TextClassItem) { 211 mAllClasses[cls.qualifiedName] = cls 212 } 213 getOrCreateClassnull214 fun getOrCreateClass(name: String, isInterface: Boolean = false): TextClassItem { 215 val erased = TextTypeItem.eraseTypeArguments(name) 216 val cls = mAllClasses[erased] 217 if (cls != null) { 218 return cls 219 } 220 val newClass = if (isInterface) { 221 TextClassItem.createInterfaceStub(this, name) 222 } else { 223 TextClassItem.createClassStub(this, name) 224 } 225 mAllClasses[erased] = newClass 226 newClass.emit = false 227 228 val fullName = newClass.fullName() 229 if (fullName.contains('.')) { 230 // We created a new inner class stub. We need to fully initialize it with outer classes, themselves 231 // possibly stubs 232 val outerName = erased.substring(0, erased.lastIndexOf('.')) 233 val outerClass = getOrCreateClass(outerName, false) 234 newClass.containingClass = outerClass 235 outerClass.addInnerClass(newClass) 236 } else { 237 // Add to package 238 val endIndex = erased.lastIndexOf('.') 239 val pkgPath = if (endIndex != -1) erased.substring(0, endIndex) else "" 240 val pkg = findPackage(pkgPath) ?: run { 241 val newPkg = TextPackageItem( 242 this, 243 pkgPath, 244 TextModifiers(this, DefaultModifierList.PUBLIC), 245 SourcePositionInfo.UNKNOWN 246 ) 247 addPackage(newPkg) 248 newPkg.emit = false 249 newPkg 250 } 251 newClass.setContainingPackage(pkg) 252 pkg.addClass(newClass) 253 } 254 255 return newClass 256 } 257 postProcessnull258 fun postProcess() { 259 val classes = mAllClasses.values.toList() 260 val packages = mPackages.values.toList() 261 resolveSuperclasses(classes) 262 resolveInterfaces(classes) 263 resolveThrowsClasses(classes) 264 resolveInnerClasses(packages) 265 } 266 findPackagenull267 override fun findPackage(pkgName: String): TextPackageItem? { 268 return mPackages[pkgName] 269 } 270 acceptnull271 override fun accept(visitor: ItemVisitor) { 272 getPackages().accept(visitor) 273 } 274 acceptTypesnull275 override fun acceptTypes(visitor: TypeVisitor) { 276 getPackages().acceptTypes(visitor) 277 } 278 compareWithnull279 override fun compareWith(visitor: ComparisonVisitor, other: Codebase, filter: Predicate<Item>?) { 280 CodebaseComparator().compare(visitor, this, other, filter) 281 } 282 createAnnotationnull283 override fun createAnnotation(source: String, context: Item?, mapName: Boolean): AnnotationItem { 284 return TextBackedAnnotationItem(this, source, mapName) 285 } 286 toStringnull287 override fun toString(): String { 288 return description 289 } 290 unsupportednull291 override fun unsupported(desc: String?): Nothing { 292 error(desc ?: "Not supported for a signature-file based codebase") 293 } 294 obtainTypeFromStringnull295 fun obtainTypeFromString( 296 type: String, 297 cl: TextClassItem, 298 methodTypeParameterList: TypeParameterList 299 ): TextTypeItem { 300 if (TextTypeItem.isLikelyTypeParameter(type)) { 301 val length = type.length 302 var nameEnd = length 303 for (i in 0 until length) { 304 val c = type[i] 305 if (c == '<' || c == '[' || c == '!' || c == '?') { 306 nameEnd = i 307 break 308 } 309 } 310 val name = if (nameEnd == length) { 311 type 312 } else { 313 type.substring(0, nameEnd) 314 } 315 316 val isMethodTypeVar = methodTypeParameterList.typeParameterNames().contains(name) 317 val isClassTypeVar = cl.typeParameterList().typeParameterNames().contains(name) 318 319 if (isMethodTypeVar || isClassTypeVar) { 320 // Confirm that it's a type variable 321 // If so, create type variable WITHOUT placing it into the 322 // cache, since we can't cache these; they can have different 323 // inherited bounds etc 324 return TextTypeItem(this, type) 325 } 326 } 327 328 return obtainTypeFromString(type) 329 } 330 331 companion object { computeDeltanull332 fun computeDelta( 333 baseFile: File, 334 baseApi: Codebase, 335 signatureApi: Codebase, 336 includeFieldsInApiDiff: Boolean = compatibility.includeFieldsInApiDiff 337 ): TextCodebase { 338 // Compute just the delta 339 val delta = 340 TextCodebase(baseFile) 341 delta.description = "Delta between $baseApi and $signatureApi" 342 343 CodebaseComparator().compare(object : ComparisonVisitor() { 344 override fun added(new: PackageItem) { 345 delta.addPackage(new as TextPackageItem) 346 } 347 348 override fun added(new: ClassItem) { 349 val pkg = getOrAddPackage(new.containingPackage().qualifiedName()) 350 pkg.addClass(new as TextClassItem) 351 } 352 353 override fun added(new: ConstructorItem) { 354 val cls = getOrAddClass(new.containingClass()) 355 cls.addConstructor(new as TextConstructorItem) 356 } 357 358 override fun added(new: MethodItem) { 359 val cls = getOrAddClass(new.containingClass()) 360 cls.addMethod(new as TextMethodItem) 361 } 362 363 override fun added(new: FieldItem) { 364 if (!includeFieldsInApiDiff) { 365 return 366 } 367 val cls = getOrAddClass(new.containingClass()) 368 cls.addField(new as TextFieldItem) 369 } 370 371 override fun added(new: PropertyItem) { 372 val cls = getOrAddClass(new.containingClass()) 373 cls.addProperty(new as TextPropertyItem) 374 } 375 376 private fun getOrAddClass(fullClass: ClassItem): TextClassItem { 377 val cls = delta.findClass(fullClass.qualifiedName()) 378 if (cls != null) { 379 return cls 380 } 381 val textClass = fullClass as TextClassItem 382 val newClass = TextClassItem( 383 delta, 384 SourcePositionInfo.UNKNOWN, 385 textClass.modifiers, 386 textClass.isInterface(), 387 textClass.isEnum(), 388 textClass.isAnnotationType(), 389 textClass.qualifiedName, 390 textClass.qualifiedName, 391 textClass.name, 392 textClass.annotations 393 ) 394 val pkg = getOrAddPackage(fullClass.containingPackage().qualifiedName()) 395 pkg.addClass(newClass) 396 newClass.setContainingPackage(pkg) 397 delta.registerClass(newClass) 398 return newClass 399 } 400 401 private fun getOrAddPackage(pkgName: String): TextPackageItem { 402 val pkg = delta.findPackage(pkgName) 403 if (pkg != null) { 404 return pkg 405 } 406 val newPkg = TextPackageItem( 407 delta, 408 pkgName, 409 TextModifiers(delta, DefaultModifierList.PUBLIC), 410 SourcePositionInfo.UNKNOWN 411 ) 412 delta.addPackage(newPkg) 413 return newPkg 414 } 415 }, baseApi, signatureApi, ApiType.ALL.getReferenceFilter()) 416 417 delta.postProcess() 418 return delta 419 } 420 } 421 422 // Copied from Converter: 423 obtainTypeFromStringnull424 fun obtainTypeFromString(type: String): TextTypeItem { 425 return mTypesFromString.obtain(type) as TextTypeItem 426 } 427 428 private val mTypesFromString = object : Cache(this) { makenull429 override fun make(o: Any): Any { 430 val name = o as String 431 432 // Reverse effect of TypeItem.shortenTypes(...) 433 if (implicitJavaLangType(name)) { 434 return TextTypeItem(codebase, "java.lang.$name") 435 } 436 437 return TextTypeItem(codebase, name) 438 } 439 implicitJavaLangTypenull440 private fun implicitJavaLangType(s: String): Boolean { 441 if (s.length <= 1) { 442 return false // Usually a type variable 443 } 444 if (s[1] == '[') { 445 return false // Type variable plus array 446 } 447 448 val dotIndex = s.indexOf('.') 449 val array = s.indexOf('[') 450 val generics = s.indexOf('<') 451 if (array == -1 && generics == -1) { 452 return dotIndex == -1 && !TextTypeItem.isPrimitive(s) 453 } 454 val typeEnd = 455 if (array != -1) { 456 if (generics != -1) { 457 min(array, generics) 458 } else { 459 array 460 } 461 } else { 462 generics 463 } 464 465 // Allow dotted type in generic parameter, e.g. "Iterable<java.io.File>" -> return true 466 return (dotIndex == -1 || dotIndex > typeEnd) && !TextTypeItem.isPrimitive(s.substring(0, typeEnd).trim()) 467 } 468 } 469 470 private abstract class Cache(val codebase: TextCodebase) { 471 472 protected var mCache = HashMap<Any, Any>() 473 obtainnull474 internal fun obtain(o: Any?): Any? { 475 if (o == null) { 476 return null 477 } 478 var r: Any? = mCache[o] 479 if (r == null) { 480 r = make(o) 481 mCache[o] = r 482 } 483 return r 484 } 485 makenull486 protected abstract fun make(o: Any): Any 487 } 488 } 489