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.model.AnnotationItem 28 import com.android.tools.metalava.model.ClassItem 29 import com.android.tools.metalava.model.Codebase 30 import com.android.tools.metalava.model.ConstructorItem 31 import com.android.tools.metalava.model.DefaultCodebase 32 import com.android.tools.metalava.model.DefaultModifierList 33 import com.android.tools.metalava.model.FieldItem 34 import com.android.tools.metalava.model.Item 35 import com.android.tools.metalava.model.MethodItem 36 import com.android.tools.metalava.model.PackageItem 37 import com.android.tools.metalava.model.PackageList 38 import com.android.tools.metalava.model.PropertyItem 39 import com.android.tools.metalava.model.TypeParameterList 40 import com.android.tools.metalava.model.visitors.ItemVisitor 41 import com.android.tools.metalava.model.visitors.TypeVisitor 42 import java.io.File 43 import java.util.ArrayList 44 import java.util.HashMap 45 import java.util.function.Predicate 46 import kotlin.math.min 47 48 // Copy of ApiInfo in doclava1 (converted to Kotlin + some cleanup to make it work with metalava's data structures. 49 // (Converted to Kotlin such that I can inherit behavior via interfaces, in particular Codebase.) 50 class TextCodebase(location: File) : DefaultCodebase(location) { 51 /** 52 * Whether types should be interpreted to be in Kotlin format (e.g. ? suffix means nullable, 53 * ! suffix means unknown, and absence of a suffix means not nullable. 54 */ 55 var kotlinStyleNulls = false 56 57 private val mPackages = HashMap<String, TextPackageItem>(300) 58 private val mAllClasses = HashMap<String, TextClassItem>(30000) 59 private val mClassToSuper = HashMap<TextClassItem, String>(30000) 60 private val mClassToInterface = HashMap<TextClassItem, ArrayList<String>>(10000) 61 62 override var description = "Codebase" 63 override var preFiltered: Boolean = true 64 trustedApinull65 override fun trustedApi(): Boolean = true 66 67 /** 68 * Signature file format version, if found. Type "GradleVersion" is misleading; it's just a convenient 69 * version class. 70 */ 71 var format: FileFormat = FileFormat.V1 // not specifying format: assumed to be doclava, 1.0 72 73 override fun getPackages(): PackageList { 74 val list = ArrayList<PackageItem>(mPackages.values) 75 list.sortWith(PackageItem.comparator) 76 return PackageList(this, list) 77 } 78 sizenull79 override fun size(): Int { 80 return mPackages.size 81 } 82 findClassnull83 override fun findClass(className: String): TextClassItem? { 84 return mAllClasses[className] 85 } 86 resolveInterfacesnull87 private fun resolveInterfaces(all: List<TextClassItem>) { 88 for (cl in all) { 89 val interfaces = mClassToInterface[cl] ?: continue 90 for (interfaceName in interfaces) { 91 getOrCreateClass(interfaceName, isInterface = true) 92 cl.addInterface(obtainTypeFromString(interfaceName)) 93 } 94 } 95 } 96 supportsDocumentationnull97 override fun supportsDocumentation(): Boolean = false 98 99 fun mapClassToSuper(classInfo: TextClassItem, superclass: String?) { 100 superclass?.let { mClassToSuper.put(classInfo, superclass) } 101 } 102 mapClassToInterfacenull103 fun mapClassToInterface(classInfo: TextClassItem, iface: String) { 104 if (!mClassToInterface.containsKey(classInfo)) { 105 mClassToInterface[classInfo] = ArrayList() 106 } 107 mClassToInterface[classInfo]?.let { 108 if (!it.contains(iface)) it.add(iface) 109 } 110 } 111 implementsInterfacenull112 fun implementsInterface(classInfo: TextClassItem, iface: String): Boolean { 113 return mClassToInterface[classInfo]?.contains(iface) ?: false 114 } 115 addPackagenull116 fun addPackage(pInfo: TextPackageItem) { 117 // track the set of organized packages in the API 118 mPackages[pInfo.name()] = pInfo 119 120 // accumulate a direct map of all the classes in the API 121 for (cl in pInfo.allClasses()) { 122 mAllClasses[cl.qualifiedName()] = cl as TextClassItem 123 } 124 } 125 resolveSuperclassesnull126 private fun resolveSuperclasses(allClasses: List<TextClassItem>) { 127 for (cl in allClasses) { 128 // java.lang.Object has no superclass 129 if (cl.isJavaLangObject()) { 130 continue 131 } 132 var scName: String? = mClassToSuper[cl] 133 if (scName == null) { 134 scName = when { 135 cl.isEnum() -> JAVA_LANG_ENUM 136 cl.isAnnotationType() -> JAVA_LANG_ANNOTATION 137 else -> { 138 val existing = cl.superClassType()?.toTypeString() 139 val s = existing ?: JAVA_LANG_OBJECT 140 s // unnecessary variable, works around current compiler believing the expression to be nullable 141 } 142 } 143 } 144 145 val superclass = getOrCreateClass(scName) 146 cl.setSuperClass(superclass, obtainTypeFromString(scName)) 147 } 148 } 149 resolveThrowsClassesnull150 private fun resolveThrowsClasses(all: List<TextClassItem>) { 151 for (cl in all) { 152 for (methodItem in cl.constructors()) { 153 resolveThrowsClasses(methodItem) 154 } 155 for (methodItem in cl.methods()) { 156 resolveThrowsClasses(methodItem) 157 } 158 } 159 } 160 resolveThrowsClassesnull161 private fun resolveThrowsClasses(methodItem: MethodItem) { 162 val methodInfo = methodItem as TextMethodItem 163 val names = methodInfo.throwsTypeNames() 164 if (names.isNotEmpty()) { 165 val result = ArrayList<TextClassItem>() 166 for (exception in names) { 167 var exceptionClass: TextClassItem? = mAllClasses[exception] 168 if (exceptionClass == null) { 169 // Exception not provided by this codebase. Inject a stub. 170 exceptionClass = getOrCreateClass(exception) 171 // Set super class to throwable? 172 if (exception != JAVA_LANG_THROWABLE) { 173 exceptionClass.setSuperClass( 174 getOrCreateClass(JAVA_LANG_THROWABLE), 175 TextTypeItem(this, JAVA_LANG_THROWABLE) 176 ) 177 } 178 } 179 result.add(exceptionClass) 180 } 181 methodInfo.setThrowsList(result) 182 } 183 } 184 resolveInnerClassesnull185 private fun resolveInnerClasses(packages: List<TextPackageItem>) { 186 for (pkg in packages) { 187 // make copy: we'll be removing non-top level classes during iteration 188 val classes = ArrayList(pkg.classList()) 189 for (cls in classes) { 190 val cl = cls as TextClassItem 191 val name = cl.name 192 var index = name.lastIndexOf('.') 193 if (index != -1) { 194 cl.name = name.substring(index + 1) 195 val qualifiedName = cl.qualifiedName 196 index = qualifiedName.lastIndexOf('.') 197 assert(index != -1) { qualifiedName } 198 val outerClassName = qualifiedName.substring(0, index) 199 val outerClass = getOrCreateClass(outerClassName) 200 cl.containingClass = outerClass 201 outerClass.addInnerClass(cl) 202 } 203 } 204 } 205 206 for (pkg in packages) { 207 pkg.pruneClassList() 208 } 209 } 210 registerClassnull211 fun registerClass(cls: TextClassItem) { 212 mAllClasses[cls.qualifiedName] = cls 213 } 214 getOrCreateClassnull215 fun getOrCreateClass(name: String, isInterface: Boolean = false): TextClassItem { 216 val erased = TextTypeItem.eraseTypeArguments(name) 217 val cls = mAllClasses[erased] 218 if (cls != null) { 219 return cls 220 } 221 val newClass = if (isInterface) { 222 TextClassItem.createInterfaceStub(this, name) 223 } else { 224 TextClassItem.createClassStub(this, name) 225 } 226 mAllClasses[erased] = newClass 227 newClass.emit = false 228 229 val fullName = newClass.fullName() 230 if (fullName.contains('.')) { 231 // We created a new inner class stub. We need to fully initialize it with outer classes, themselves 232 // possibly stubs 233 val outerName = erased.substring(0, erased.lastIndexOf('.')) 234 val outerClass = getOrCreateClass(outerName, false) 235 newClass.containingClass = outerClass 236 outerClass.addInnerClass(newClass) 237 } else { 238 // Add to package 239 val endIndex = erased.lastIndexOf('.') 240 val pkgPath = if (endIndex != -1) erased.substring(0, endIndex) else "" 241 val pkg = findPackage(pkgPath) ?: run { 242 val newPkg = TextPackageItem( 243 this, 244 pkgPath, 245 TextModifiers(this, DefaultModifierList.PUBLIC), 246 SourcePositionInfo.UNKNOWN 247 ) 248 addPackage(newPkg) 249 newPkg.emit = false 250 newPkg 251 } 252 newClass.setContainingPackage(pkg) 253 pkg.addClass(newClass) 254 } 255 256 return newClass 257 } 258 postProcessnull259 fun postProcess() { 260 val classes = mAllClasses.values.toList() 261 val packages = mPackages.values.toList() 262 resolveSuperclasses(classes) 263 resolveInterfaces(classes) 264 resolveThrowsClasses(classes) 265 resolveInnerClasses(packages) 266 } 267 findPackagenull268 override fun findPackage(pkgName: String): TextPackageItem? { 269 return mPackages[pkgName] 270 } 271 acceptnull272 override fun accept(visitor: ItemVisitor) { 273 getPackages().accept(visitor) 274 } 275 acceptTypesnull276 override fun acceptTypes(visitor: TypeVisitor) { 277 getPackages().acceptTypes(visitor) 278 } 279 compareWithnull280 override fun compareWith(visitor: ComparisonVisitor, other: Codebase, filter: Predicate<Item>?) { 281 CodebaseComparator().compare(visitor, this, other, filter) 282 } 283 createAnnotationnull284 override fun createAnnotation(source: String, context: Item?, mapName: Boolean): AnnotationItem { 285 return TextBackedAnnotationItem(this, source, mapName) 286 } 287 toStringnull288 override fun toString(): String { 289 return description 290 } 291 unsupportednull292 override fun unsupported(desc: String?): Nothing { 293 error(desc ?: "Not supported for a signature-file based codebase") 294 } 295 obtainTypeFromStringnull296 fun obtainTypeFromString( 297 type: String, 298 cl: TextClassItem, 299 methodTypeParameterList: TypeParameterList 300 ): TextTypeItem { 301 if (TextTypeItem.isLikelyTypeParameter(type)) { 302 val length = type.length 303 var nameEnd = length 304 for (i in 0 until length) { 305 val c = type[i] 306 if (c == '<' || c == '[' || c == '!' || c == '?') { 307 nameEnd = i 308 break 309 } 310 } 311 val name = if (nameEnd == length) { 312 type 313 } else { 314 type.substring(0, nameEnd) 315 } 316 317 val isMethodTypeVar = methodTypeParameterList.typeParameterNames().contains(name) 318 val isClassTypeVar = cl.typeParameterList().typeParameterNames().contains(name) 319 320 if (isMethodTypeVar || isClassTypeVar) { 321 // Confirm that it's a type variable 322 // If so, create type variable WITHOUT placing it into the 323 // cache, since we can't cache these; they can have different 324 // inherited bounds etc 325 return TextTypeItem(this, type) 326 } 327 } 328 329 return obtainTypeFromString(type) 330 } 331 332 companion object { computeDeltanull333 fun computeDelta( 334 baseFile: File, 335 baseApi: Codebase, 336 signatureApi: Codebase 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( 344 object : ComparisonVisitor() { 345 override fun added(new: PackageItem) { 346 delta.addPackage(new as TextPackageItem) 347 } 348 349 override fun added(new: ClassItem) { 350 val pkg = getOrAddPackage(new.containingPackage().qualifiedName()) 351 pkg.addClass(new as TextClassItem) 352 } 353 354 override fun added(new: ConstructorItem) { 355 val cls = getOrAddClass(new.containingClass()) 356 cls.addConstructor(new as TextConstructorItem) 357 } 358 359 override fun added(new: MethodItem) { 360 val cls = getOrAddClass(new.containingClass()) 361 cls.addMethod(new as TextMethodItem) 362 } 363 364 override fun added(new: FieldItem) { 365 val cls = getOrAddClass(new.containingClass()) 366 cls.addField(new as TextFieldItem) 367 } 368 369 override fun added(new: PropertyItem) { 370 val cls = getOrAddClass(new.containingClass()) 371 cls.addProperty(new as TextPropertyItem) 372 } 373 374 private fun getOrAddClass(fullClass: ClassItem): TextClassItem { 375 val cls = delta.findClass(fullClass.qualifiedName()) 376 if (cls != null) { 377 return cls 378 } 379 val textClass = fullClass as TextClassItem 380 val newClass = TextClassItem( 381 delta, 382 SourcePositionInfo.UNKNOWN, 383 textClass.modifiers, 384 textClass.isInterface(), 385 textClass.isEnum(), 386 textClass.isAnnotationType(), 387 textClass.qualifiedName, 388 textClass.qualifiedName, 389 textClass.name, 390 textClass.annotations 391 ) 392 val pkg = getOrAddPackage(fullClass.containingPackage().qualifiedName()) 393 pkg.addClass(newClass) 394 newClass.setContainingPackage(pkg) 395 delta.registerClass(newClass) 396 return newClass 397 } 398 399 private fun getOrAddPackage(pkgName: String): TextPackageItem { 400 val pkg = delta.findPackage(pkgName) 401 if (pkg != null) { 402 return pkg 403 } 404 val newPkg = TextPackageItem( 405 delta, 406 pkgName, 407 TextModifiers(delta, DefaultModifierList.PUBLIC), 408 SourcePositionInfo.UNKNOWN 409 ) 410 delta.addPackage(newPkg) 411 return newPkg 412 } 413 }, 414 baseApi, signatureApi, ApiType.ALL.getReferenceFilter() 415 ) 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