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