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]?.add(iface) 108 } 109 implementsInterfacenull110 fun implementsInterface(classInfo: TextClassItem, iface: String): Boolean { 111 return mClassToInterface[classInfo]?.contains(iface) ?: false 112 } 113 addPackagenull114 fun addPackage(pInfo: TextPackageItem) { 115 // track the set of organized packages in the API 116 mPackages[pInfo.name()] = pInfo 117 118 // accumulate a direct map of all the classes in the API 119 for (cl in pInfo.allClasses()) { 120 mAllClasses[cl.qualifiedName()] = cl as TextClassItem 121 } 122 } 123 resolveSuperclassesnull124 private fun resolveSuperclasses(allClasses: List<TextClassItem>) { 125 for (cl in allClasses) { 126 // java.lang.Object has no superclass 127 if (cl.isJavaLangObject()) { 128 continue 129 } 130 var scName: String? = mClassToSuper[cl] 131 if (scName == null) { 132 scName = when { 133 cl.isEnum() -> JAVA_LANG_ENUM 134 cl.isAnnotationType() -> JAVA_LANG_ANNOTATION 135 else -> { 136 val existing = cl.superClassType()?.toTypeString() 137 val s = existing ?: JAVA_LANG_OBJECT 138 s // unnecessary variable, works around current compiler believing the expression to be nullable 139 } 140 } 141 } 142 143 val superclass = getOrCreateClass(scName) 144 cl.setSuperClass(superclass, obtainTypeFromString(scName)) 145 } 146 } 147 resolveThrowsClassesnull148 private fun resolveThrowsClasses(all: List<TextClassItem>) { 149 for (cl in all) { 150 for (methodItem in cl.constructors()) { 151 resolveThrowsClasses(methodItem) 152 } 153 for (methodItem in cl.methods()) { 154 resolveThrowsClasses(methodItem) 155 } 156 } 157 } 158 resolveThrowsClassesnull159 private fun resolveThrowsClasses(methodItem: MethodItem) { 160 val methodInfo = methodItem as TextMethodItem 161 val names = methodInfo.throwsTypeNames() 162 if (names.isNotEmpty()) { 163 val result = ArrayList<TextClassItem>() 164 for (exception in names) { 165 var exceptionClass: TextClassItem? = mAllClasses[exception] 166 if (exceptionClass == null) { 167 // Exception not provided by this codebase. Inject a stub. 168 exceptionClass = getOrCreateClass(exception) 169 // Set super class to throwable? 170 if (exception != JAVA_LANG_THROWABLE) { 171 exceptionClass.setSuperClass( 172 getOrCreateClass(JAVA_LANG_THROWABLE), 173 TextTypeItem(this, JAVA_LANG_THROWABLE) 174 ) 175 } 176 } 177 result.add(exceptionClass) 178 } 179 methodInfo.setThrowsList(result) 180 } 181 } 182 resolveInnerClassesnull183 private fun resolveInnerClasses(packages: List<TextPackageItem>) { 184 for (pkg in packages) { 185 // make copy: we'll be removing non-top level classes during iteration 186 val classes = ArrayList(pkg.classList()) 187 for (cls in classes) { 188 val cl = cls as TextClassItem 189 val name = cl.name 190 var index = name.lastIndexOf('.') 191 if (index != -1) { 192 cl.name = name.substring(index + 1) 193 val qualifiedName = cl.qualifiedName 194 index = qualifiedName.lastIndexOf('.') 195 assert(index != -1) { qualifiedName } 196 val outerClassName = qualifiedName.substring(0, index) 197 val outerClass = getOrCreateClass(outerClassName) 198 cl.containingClass = outerClass 199 outerClass.addInnerClass(cl) 200 } 201 } 202 } 203 204 for (pkg in packages) { 205 pkg.pruneClassList() 206 } 207 } 208 registerClassnull209 fun registerClass(cls: TextClassItem) { 210 mAllClasses[cls.qualifiedName] = cls 211 } 212 getOrCreateClassnull213 fun getOrCreateClass(name: String, isInterface: Boolean = false): TextClassItem { 214 val erased = TextTypeItem.eraseTypeArguments(name) 215 val cls = mAllClasses[erased] 216 if (cls != null) { 217 return cls 218 } 219 val newClass = if (isInterface) { 220 TextClassItem.createInterfaceStub(this, name) 221 } else { 222 TextClassItem.createClassStub(this, name) 223 } 224 mAllClasses[erased] = newClass 225 newClass.emit = false 226 227 val fullName = newClass.fullName() 228 if (fullName.contains('.')) { 229 // We created a new inner class stub. We need to fully initialize it with outer classes, themselves 230 // possibly stubs 231 val outerName = erased.substring(0, erased.lastIndexOf('.')) 232 val outerClass = getOrCreateClass(outerName, false) 233 newClass.containingClass = outerClass 234 outerClass.addInnerClass(newClass) 235 } else { 236 // Add to package 237 val endIndex = erased.lastIndexOf('.') 238 val pkgPath = if (endIndex != -1) erased.substring(0, endIndex) else "" 239 val pkg = findPackage(pkgPath) ?: run { 240 val newPkg = TextPackageItem( 241 this, 242 pkgPath, 243 TextModifiers(this, DefaultModifierList.PUBLIC), 244 SourcePositionInfo.UNKNOWN 245 ) 246 addPackage(newPkg) 247 newPkg.emit = false 248 newPkg 249 } 250 newClass.setContainingPackage(pkg) 251 pkg.addClass(newClass) 252 } 253 254 return newClass 255 } 256 postProcessnull257 fun postProcess() { 258 val classes = mAllClasses.values.toList() 259 val packages = mPackages.values.toList() 260 resolveSuperclasses(classes) 261 resolveInterfaces(classes) 262 resolveThrowsClasses(classes) 263 resolveInnerClasses(packages) 264 } 265 findPackagenull266 override fun findPackage(pkgName: String): TextPackageItem? { 267 return mPackages[pkgName] 268 } 269 acceptnull270 override fun accept(visitor: ItemVisitor) { 271 getPackages().accept(visitor) 272 } 273 acceptTypesnull274 override fun acceptTypes(visitor: TypeVisitor) { 275 getPackages().acceptTypes(visitor) 276 } 277 compareWithnull278 override fun compareWith(visitor: ComparisonVisitor, other: Codebase, filter: Predicate<Item>?) { 279 CodebaseComparator().compare(visitor, this, other, filter) 280 } 281 createAnnotationnull282 override fun createAnnotation(source: String, context: Item?, mapName: Boolean): AnnotationItem { 283 return TextBackedAnnotationItem(this, source, mapName) 284 } 285 toStringnull286 override fun toString(): String { 287 return description 288 } 289 unsupportednull290 override fun unsupported(desc: String?): Nothing { 291 error(desc ?: "Not supported for a signature-file based codebase") 292 } 293 obtainTypeFromStringnull294 fun obtainTypeFromString( 295 type: String, 296 cl: TextClassItem, 297 methodTypeParameterList: TypeParameterList 298 ): TextTypeItem { 299 if (TextTypeItem.isLikelyTypeParameter(type)) { 300 val length = type.length 301 var nameEnd = length 302 for (i in 0 until length) { 303 val c = type[i] 304 if (c == '<' || c == '[' || c == '!' || c == '?') { 305 nameEnd = i 306 break 307 } 308 } 309 val name = if (nameEnd == length) { 310 type 311 } else { 312 type.substring(0, nameEnd) 313 } 314 315 val isMethodTypeVar = methodTypeParameterList.typeParameterNames().contains(name) 316 val isClassTypeVar = cl.typeParameterList().typeParameterNames().contains(name) 317 318 if (isMethodTypeVar || isClassTypeVar) { 319 // Confirm that it's a type variable 320 // If so, create type variable WITHOUT placing it into the 321 // cache, since we can't cache these; they can have different 322 // inherited bounds etc 323 return TextTypeItem(this, type) 324 } 325 } 326 327 return obtainTypeFromString(type) 328 } 329 330 companion object { computeDeltanull331 fun computeDelta( 332 baseFile: File, 333 baseApi: Codebase, 334 signatureApi: Codebase 335 ): TextCodebase { 336 // Compute just the delta 337 val delta = 338 TextCodebase(baseFile) 339 delta.description = "Delta between $baseApi and $signatureApi" 340 341 CodebaseComparator().compare( 342 object : ComparisonVisitor() { 343 override fun added(new: PackageItem) { 344 delta.addPackage(new as TextPackageItem) 345 } 346 347 override fun added(new: ClassItem) { 348 val pkg = getOrAddPackage(new.containingPackage().qualifiedName()) 349 pkg.addClass(new as TextClassItem) 350 } 351 352 override fun added(new: ConstructorItem) { 353 val cls = getOrAddClass(new.containingClass()) 354 cls.addConstructor(new as TextConstructorItem) 355 } 356 357 override fun added(new: MethodItem) { 358 val cls = getOrAddClass(new.containingClass()) 359 cls.addMethod(new as TextMethodItem) 360 } 361 362 override fun added(new: FieldItem) { 363 val cls = getOrAddClass(new.containingClass()) 364 cls.addField(new as TextFieldItem) 365 } 366 367 override fun added(new: PropertyItem) { 368 val cls = getOrAddClass(new.containingClass()) 369 cls.addProperty(new as TextPropertyItem) 370 } 371 372 private fun getOrAddClass(fullClass: ClassItem): TextClassItem { 373 val cls = delta.findClass(fullClass.qualifiedName()) 374 if (cls != null) { 375 return cls 376 } 377 val textClass = fullClass as TextClassItem 378 val newClass = TextClassItem( 379 delta, 380 SourcePositionInfo.UNKNOWN, 381 textClass.modifiers, 382 textClass.isInterface(), 383 textClass.isEnum(), 384 textClass.isAnnotationType(), 385 textClass.qualifiedName, 386 textClass.qualifiedName, 387 textClass.name, 388 textClass.annotations 389 ) 390 val pkg = getOrAddPackage(fullClass.containingPackage().qualifiedName()) 391 pkg.addClass(newClass) 392 newClass.setContainingPackage(pkg) 393 delta.registerClass(newClass) 394 return newClass 395 } 396 397 private fun getOrAddPackage(pkgName: String): TextPackageItem { 398 val pkg = delta.findPackage(pkgName) 399 if (pkg != null) { 400 return pkg 401 } 402 val newPkg = TextPackageItem( 403 delta, 404 pkgName, 405 TextModifiers(delta, DefaultModifierList.PUBLIC), 406 SourcePositionInfo.UNKNOWN 407 ) 408 delta.addPackage(newPkg) 409 return newPkg 410 } 411 }, 412 baseApi, signatureApi, ApiType.ALL.getReferenceFilter() 413 ) 414 415 delta.postProcess() 416 return delta 417 } 418 } 419 420 // Copied from Converter: 421 obtainTypeFromStringnull422 fun obtainTypeFromString(type: String): TextTypeItem { 423 return mTypesFromString.obtain(type) as TextTypeItem 424 } 425 426 private val mTypesFromString = object : Cache(this) { makenull427 override fun make(o: Any): Any { 428 val name = o as String 429 430 // Reverse effect of TypeItem.shortenTypes(...) 431 if (implicitJavaLangType(name)) { 432 return TextTypeItem(codebase, "java.lang.$name") 433 } 434 435 return TextTypeItem(codebase, name) 436 } 437 implicitJavaLangTypenull438 private fun implicitJavaLangType(s: String): Boolean { 439 if (s.length <= 1) { 440 return false // Usually a type variable 441 } 442 if (s[1] == '[') { 443 return false // Type variable plus array 444 } 445 446 val dotIndex = s.indexOf('.') 447 val array = s.indexOf('[') 448 val generics = s.indexOf('<') 449 if (array == -1 && generics == -1) { 450 return dotIndex == -1 && !TextTypeItem.isPrimitive(s) 451 } 452 val typeEnd = 453 if (array != -1) { 454 if (generics != -1) { 455 min(array, generics) 456 } else { 457 array 458 } 459 } else { 460 generics 461 } 462 463 // Allow dotted type in generic parameter, e.g. "Iterable<java.io.File>" -> return true 464 return (dotIndex == -1 || dotIndex > typeEnd) && !TextTypeItem.isPrimitive(s.substring(0, typeEnd).trim()) 465 } 466 } 467 468 private abstract class Cache(val codebase: TextCodebase) { 469 470 protected var mCache = HashMap<Any, Any>() 471 obtainnull472 internal fun obtain(o: Any?): Any? { 473 if (o == null) { 474 return null 475 } 476 var r: Any? = mCache[o] 477 if (r == null) { 478 r = make(o) 479 mCache[o] = r 480 } 481 return r 482 } 483 makenull484 protected abstract fun make(o: Any): Any 485 } 486 } 487