1 /* <lambda>null2 * Copyright (C) 2024 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.snapshot 18 19 import com.android.tools.metalava.model.ApiVariantSelectors 20 import com.android.tools.metalava.model.CallableItem 21 import com.android.tools.metalava.model.ClassItem 22 import com.android.tools.metalava.model.ClassTypeItem 23 import com.android.tools.metalava.model.Codebase 24 import com.android.tools.metalava.model.ConstructorItem 25 import com.android.tools.metalava.model.DefaultTypeParameterList 26 import com.android.tools.metalava.model.DelegatedVisitor 27 import com.android.tools.metalava.model.FieldItem 28 import com.android.tools.metalava.model.Item 29 import com.android.tools.metalava.model.ItemDocumentationFactory 30 import com.android.tools.metalava.model.ItemLanguage 31 import com.android.tools.metalava.model.ItemVisitor 32 import com.android.tools.metalava.model.MethodItem 33 import com.android.tools.metalava.model.ModifierList 34 import com.android.tools.metalava.model.PackageItem 35 import com.android.tools.metalava.model.ParameterItem 36 import com.android.tools.metalava.model.PropertyItem 37 import com.android.tools.metalava.model.SelectableItem 38 import com.android.tools.metalava.model.Showability 39 import com.android.tools.metalava.model.TypeAliasItem 40 import com.android.tools.metalava.model.TypeItem 41 import com.android.tools.metalava.model.TypeParameterList 42 import com.android.tools.metalava.model.TypeParameterListAndFactory 43 import com.android.tools.metalava.model.item.DefaultClassItem 44 import com.android.tools.metalava.model.item.DefaultCodebase 45 import com.android.tools.metalava.model.item.DefaultCodebaseAssembler 46 import com.android.tools.metalava.model.item.DefaultItemFactory 47 import com.android.tools.metalava.model.item.DefaultPackageItem 48 import com.android.tools.metalava.model.item.DefaultTypeParameterItem 49 import com.android.tools.metalava.model.item.MutablePackageDoc 50 import com.android.tools.metalava.model.item.PackageDoc 51 import com.android.tools.metalava.model.item.PackageDocs 52 53 /** Constructs a [Codebase] by taking a snapshot of another [Codebase] that is being visited. */ 54 class CodebaseSnapshotTaker 55 private constructor(referenceVisitorFactory: (DelegatedVisitor) -> ItemVisitor) : 56 DefaultCodebaseAssembler(), DelegatedVisitor { 57 58 /** 59 * The [Codebase] that is under construction. 60 * 61 * Initialized in [visitCodebase]. 62 */ 63 private lateinit var snapshotCodebase: DefaultCodebase 64 65 /** 66 * The [ItemVisitor] to use in [createClassFromUnderlyingModel] to create a [ClassItem] that is 67 * not emitted as part of the snapshot but is included because it is referenced from a 68 * [ClassItem] that is emitted from the snapshot. 69 */ 70 private val referenceVisitor = referenceVisitorFactory(this) 71 72 override val itemFactory: DefaultItemFactory by 73 lazy(LazyThreadSafetyMode.NONE) { 74 DefaultItemFactory( 75 snapshotCodebase, 76 // Snapshots currently only support java. 77 defaultItemLanguage = ItemLanguage.JAVA, 78 // Snapshots have already been separated by API surface variants, so they can use 79 // the same immutable ApiVariantSelectors. 80 ApiVariantSelectors.IMMUTABLE_FACTORY, 81 ) 82 } 83 84 /** 85 * The original [Codebase] that is being snapshotted construction. 86 * 87 * Initialized in [visitCodebase]. 88 */ 89 private lateinit var originalCodebase: Codebase 90 91 private val globalTypeItemFactory by 92 lazy(LazyThreadSafetyMode.NONE) { SnapshotTypeItemFactory(snapshotCodebase) } 93 94 /** Take a snapshot of this [ModifierList] for [snapshotCodebase]. */ 95 private fun ModifierList.snapshot() = snapshot(snapshotCodebase) 96 97 /** 98 * Snapshots need to preserve class nesting when visiting otherwise [ClassItem.containingClass] 99 * will not be initialized correctly. 100 */ 101 override val requiresClassNesting: Boolean 102 get() = false 103 104 override fun visitCodebase(codebase: Codebase) { 105 this.originalCodebase = codebase 106 val newCodebase = 107 DefaultCodebase( 108 location = codebase.location, 109 description = "snapshot of ${codebase.description}", 110 preFiltered = true, 111 config = codebase.config, 112 trustedApi = true, 113 // Supports documentation if the copied codebase does. 114 supportsDocumentation = codebase.supportsDocumentation(), 115 assembler = this, 116 ) 117 118 this.snapshotCodebase = newCodebase 119 } 120 121 /** 122 * Construct a [PackageDocs] that contains a [PackageDoc] that in turn contains information 123 * extracted from [packageItem] that can be used to create a new [PackageItem] that is a 124 * snapshot of [packageItem]. 125 */ 126 private fun packageDocsForPackageItem(packageItem: PackageItem) = 127 MutablePackageDoc( 128 qualifiedName = packageItem.qualifiedName(), 129 fileLocation = packageItem.fileLocation, 130 modifiers = packageItem.modifiers.snapshot(), 131 commentFactory = packageItem.documentation::snapshot, 132 overview = packageItem.overviewDocumentation, 133 ) 134 .let { PackageDocs(mapOf(it.qualifiedName to it)) } 135 136 /** Get the [PackageItem] corresponding to this [PackageItem] in the snapshot codebase. */ 137 private fun PackageItem.getSnapshotPackage(): PackageItem { 138 // Check to see if the package already exists to avoid unnecessarily creating PackageDocs. 139 val packageName = qualifiedName() 140 snapshotCodebase.findPackage(packageName)?.let { 141 return it 142 } 143 144 // Get a PackageDocs that contains a PackageDoc that contains information extracted from the 145 // PackageItem being visited. This is needed to ensure that the findOrCreatePackage(...) 146 // call below will use the correct information when creating the package. As only a single 147 // PackageDoc is provided for this package it means that if findOrCreatePackage(...) had to 148 // created a containing package that package would not have a PackageDocs and might be 149 // incorrect. However, that should not be a problem as the packages are visited in order 150 // such that a containing package is visited before any contained packages. 151 val packageDocs = packageDocsForPackageItem(this) 152 val newPackageItem = snapshotCodebase.findOrCreatePackage(packageName, packageDocs) 153 newPackageItem.copySelectedApiVariants(this) 154 return newPackageItem 155 } 156 157 /** 158 * Take a snapshot of the documentation. 159 * 160 * If necessary revert the documentation change that accompanied a deprecation change. 161 * 162 * Deprecating an API requires adding an `@Deprecated` annotation and an `@deprecated` Javadoc 163 * tag with text that explains why it is being deprecated and what will replace it. When the 164 * deprecation change is being reverted then this will remove the `@deprecated` tag and its 165 * associated text to avoid warnings when compiling and misleading information being written 166 * into the Javadoc. 167 */ 168 private fun snapshotDocumentation( 169 itemToSnapshot: Item, 170 documentedItem: Item, 171 ): ItemDocumentationFactory { 172 // The documentation does not need to be reverted if... 173 if ( 174 // the item is not being reverted 175 itemToSnapshot === documentedItem 176 // or if the deprecation status has not changed 177 || 178 itemToSnapshot.effectivelyDeprecated == documentedItem.effectivelyDeprecated 179 // or if the item was previously deprecated 180 || 181 itemToSnapshot.effectivelyDeprecated 182 ) 183 return documentedItem.documentation::snapshot 184 185 val documentation = documentedItem.documentation 186 return { item -> documentation.snapshot(item).apply { removeDeprecatedSection() } } 187 } 188 189 /** Get the [ClassItem] corresponding to this [ClassItem] in the [snapshotCodebase]. */ 190 private fun ClassItem.getSnapshotClass(): DefaultClassItem = 191 snapshotCodebase.resolveClass(qualifiedName()) as DefaultClassItem 192 193 /** Copy [SelectableItem.selectedApiVariants] from [original] to this. */ 194 private fun <T : SelectableItem> T.copySelectedApiVariants(original: T) { 195 selectedApiVariants = original.selectedApiVariants 196 } 197 198 override fun visitClass(cls: ClassItem) { 199 val classToSnapshot = cls.actualItemToSnapshot 200 201 // Get the snapshot of the containing package. 202 val containingPackage = cls.containingPackage().getSnapshotPackage() 203 204 // Get the snapshot of the containing class, if any. 205 val containingClass = cls.containingClass()?.getSnapshotClass() 206 207 // Create a TypeParameterList and SnapshotTypeItemFactory for the class. 208 val (typeParameterList, classTypeItemFactory) = 209 globalTypeItemFactory.from(containingClass).inScope { 210 classToSnapshot.typeParameterList.snapshot( 211 "class ${classToSnapshot.qualifiedName()}" 212 ) 213 } 214 215 // Snapshot the super class type, if any. 216 val snapshotSuperClassType = 217 classToSnapshot.superClassType()?.let { superClassType -> 218 classTypeItemFactory.getSuperClassType(superClassType) 219 } 220 val snapshotInterfaceTypes = 221 classToSnapshot.interfaceTypes().map { classTypeItemFactory.getInterfaceType(it) } 222 223 // Create the class and register it in the codebase. 224 val newClass = 225 itemFactory.createClassItem( 226 fileLocation = classToSnapshot.fileLocation, 227 itemLanguage = classToSnapshot.itemLanguage, 228 modifiers = classToSnapshot.modifiers.snapshot(), 229 documentationFactory = snapshotDocumentation(classToSnapshot, cls), 230 source = cls.sourceFile(), 231 classKind = classToSnapshot.classKind, 232 containingClass = containingClass, 233 containingPackage = containingPackage, 234 qualifiedName = classToSnapshot.qualifiedName(), 235 typeParameterList = typeParameterList, 236 origin = classToSnapshot.origin, 237 superClassType = snapshotSuperClassType, 238 interfaceTypes = snapshotInterfaceTypes, 239 ) 240 newClass.copySelectedApiVariants(classToSnapshot) 241 } 242 243 /** Execute [body] within [SnapshotTypeItemFactoryContext]. */ 244 private inline fun <T> SnapshotTypeItemFactory.inScope( 245 body: SnapshotTypeItemFactoryContext.() -> T 246 ) = SnapshotTypeItemFactoryContext(this).body() 247 248 override fun visitConstructor(constructor: ConstructorItem) { 249 val constructorToSnapshot = constructor.actualItemToSnapshot 250 251 val containingClass = constructor.containingClass().getSnapshotClass() 252 253 // Create a TypeParameterList and SnapshotTypeItemFactory for the constructor. 254 val (typeParameterList, constructorTypeItemFactory) = 255 globalTypeItemFactory.from(containingClass).inScope { 256 constructorToSnapshot.typeParameterList.snapshot(constructorToSnapshot.describe()) 257 } 258 259 val newConstructor = 260 // Resolve any type parameters used in the constructor's return type and parameter items 261 // within the scope of the constructor's SnapshotTypeItemFactory. 262 constructorTypeItemFactory.inScope { 263 itemFactory.createConstructorItem( 264 fileLocation = constructorToSnapshot.fileLocation, 265 itemLanguage = constructorToSnapshot.itemLanguage, 266 modifiers = constructorToSnapshot.modifiers.snapshot(), 267 documentationFactory = 268 snapshotDocumentation(constructorToSnapshot, constructor), 269 name = constructorToSnapshot.name(), 270 containingClass = containingClass, 271 typeParameterList = typeParameterList, 272 returnType = constructorToSnapshot.returnType().snapshot(), 273 parameterItemsFactory = { containingCallable -> 274 constructorToSnapshot.parameters().snapshot(containingCallable, constructor) 275 }, 276 throwsTypes = 277 constructorToSnapshot.throwsTypes().map { 278 typeItemFactory.getExceptionType(it) 279 }, 280 callableBodyFactory = constructorToSnapshot.body::snapshot, 281 implicitConstructor = constructorToSnapshot.isImplicitConstructor(), 282 isPrimary = constructorToSnapshot.isPrimary, 283 ) 284 } 285 newConstructor.copySelectedApiVariants(constructorToSnapshot) 286 287 containingClass.addConstructor(newConstructor) 288 } 289 290 override fun visitMethod(method: MethodItem) { 291 val methodToSnapshot = method.actualItemToSnapshot 292 293 val containingClass = method.containingClass().getSnapshotClass() 294 295 // Create a TypeParameterList and SnapshotTypeItemFactory for the method. 296 val (typeParameterList, methodTypeItemFactory) = 297 globalTypeItemFactory.from(containingClass).inScope { 298 methodToSnapshot.typeParameterList.snapshot(methodToSnapshot.describe()) 299 } 300 301 val newMethod = 302 // Resolve any type parameters used in the method's return type and parameter items 303 // within the scope of the method's SnapshotTypeItemFactory. 304 methodTypeItemFactory.inScope { 305 itemFactory.createMethodItem( 306 fileLocation = methodToSnapshot.fileLocation, 307 itemLanguage = methodToSnapshot.itemLanguage, 308 modifiers = methodToSnapshot.modifiers.snapshot(), 309 documentationFactory = snapshotDocumentation(methodToSnapshot, method), 310 name = methodToSnapshot.name(), 311 containingClass = containingClass, 312 typeParameterList = typeParameterList, 313 returnType = methodToSnapshot.returnType().snapshot(), 314 parameterItemsFactory = { containingCallable -> 315 methodToSnapshot.parameters().snapshot(containingCallable, method) 316 }, 317 throwsTypes = 318 methodToSnapshot.throwsTypes().map { typeItemFactory.getExceptionType(it) }, 319 callableBodyFactory = methodToSnapshot.body::snapshot, 320 annotationDefault = methodToSnapshot.legacyDefaultValue(), 321 ) 322 } 323 newMethod.copySelectedApiVariants(methodToSnapshot) 324 325 containingClass.addMethod(newMethod) 326 } 327 328 override fun visitField(field: FieldItem) { 329 val fieldToSnapshot = field.actualItemToSnapshot 330 331 val containingClass = field.containingClass().getSnapshotClass() 332 val newField = 333 // Resolve any type parameters used in the field's type within the scope of the 334 // containing class's SnapshotTypeItemFactory. 335 globalTypeItemFactory.from(containingClass).inScope { 336 itemFactory.createFieldItem( 337 fileLocation = fieldToSnapshot.fileLocation, 338 itemLanguage = fieldToSnapshot.itemLanguage, 339 modifiers = fieldToSnapshot.modifiers.snapshot(), 340 documentationFactory = snapshotDocumentation(fieldToSnapshot, field), 341 name = fieldToSnapshot.name(), 342 containingClass = containingClass, 343 type = fieldToSnapshot.type().snapshot(), 344 isEnumConstant = fieldToSnapshot.isEnumConstant(), 345 fieldValue = fieldToSnapshot.legacyFieldValue?.snapshot(), 346 ) 347 } 348 newField.copySelectedApiVariants(fieldToSnapshot) 349 350 containingClass.addField(newField) 351 } 352 353 override fun visitProperty(property: PropertyItem) { 354 val propertyToSnapshot = property.actualItemToSnapshot 355 val containingClass = property.containingClass().getSnapshotClass() 356 357 // Create a TypeParameterList and SnapshotTypeItemFactory for the property. 358 val (typeParameterList, propertyTypeItemFactory) = 359 globalTypeItemFactory.from(containingClass).inScope { 360 propertyToSnapshot.typeParameterList.snapshot(propertyToSnapshot.describe()) 361 } 362 363 val newProperty = 364 // Resolve any type parameters used in the property's type within the scope of the 365 // containing class's SnapshotTypeItemFactory. 366 propertyTypeItemFactory.inScope { 367 itemFactory.createPropertyItem( 368 fileLocation = propertyToSnapshot.fileLocation, 369 itemLanguage = propertyToSnapshot.itemLanguage, 370 modifiers = propertyToSnapshot.modifiers.snapshot(), 371 documentationFactory = snapshotDocumentation(propertyToSnapshot, property), 372 name = propertyToSnapshot.name(), 373 containingClass = containingClass, 374 type = propertyToSnapshot.type().snapshot(), 375 getter = property.getter, 376 setter = property.setter, 377 constructorParameter = property.constructorParameter, 378 backingField = property.backingField, 379 receiver = property.receiver?.snapshot(), 380 typeParameterList = typeParameterList 381 ) 382 } 383 newProperty.copySelectedApiVariants(propertyToSnapshot) 384 385 containingClass.addProperty(newProperty) 386 } 387 388 override fun visitTypeAlias(typeAlias: TypeAliasItem) { 389 val typeAliasToSnapshot = typeAlias.actualItemToSnapshot 390 val containingPackage = typeAlias.containingPackage().getSnapshotPackage() 391 392 val (typeParameterList, typeAliasTypeItemFactory) = 393 globalTypeItemFactory.inScope { 394 typeAliasToSnapshot.typeParameterList.snapshot(typeAliasToSnapshot.describe()) 395 } 396 397 val newTypeAlias = 398 typeAliasTypeItemFactory.inScope { 399 itemFactory.createTypeAliasItem( 400 fileLocation = typeAliasToSnapshot.fileLocation, 401 modifiers = typeAliasToSnapshot.modifiers.snapshot(), 402 qualifiedName = typeAliasToSnapshot.qualifiedName, 403 containingPackage = containingPackage as DefaultPackageItem, 404 aliasedType = typeAliasToSnapshot.aliasedType.snapshot(), 405 typeParameterList = typeParameterList, 406 documentationFactory = snapshotDocumentation(typeAliasToSnapshot, typeAlias), 407 ) 408 } 409 newTypeAlias.copySelectedApiVariants(typeAliasToSnapshot) 410 } 411 412 /** Take a snapshot of [qualifiedName]. */ 413 override fun createClassFromUnderlyingModel(qualifiedName: String): ClassItem? { 414 // Resolve the class in the original codebase, if possible. 415 val originalClass = originalCodebase.resolveClass(qualifiedName) ?: return null 416 417 // Take a snapshot of a class that is referenced from, but not defined within, the snapshot. 418 originalClass.accept(referenceVisitor) 419 420 // Find the newly added class. 421 val classItem = 422 snapshotCodebase.findClass(originalClass.qualifiedName()) 423 ?: error("Could not snapshot class $qualifiedName") 424 425 // Any class that is created only when resolving references is by definition not part of the 426 // codebase and so will not be emitted. 427 classItem.emit = false 428 429 return classItem 430 } 431 432 companion object { 433 /** 434 * Take a snapshot of [codebase]. 435 * 436 * @param definitionVisitorFactory a factory for creating an [ItemVisitor] that delegates to 437 * a [DelegatedVisitor]. The [ItemVisitor] is used to determine which parts of [codebase] 438 * will be defined within and emitted from the snapshot. 439 * @param referenceVisitorFactory a factory for creating an [ItemVisitor] that delegates to 440 * a [DelegatedVisitor]. The [ItemVisitor] is used to determine which parts of [codebase] 441 * will be referenced from within but not emitted from the snapshot. 442 */ 443 fun takeSnapshot( 444 codebase: Codebase, 445 definitionVisitorFactory: (DelegatedVisitor) -> ItemVisitor, 446 referenceVisitorFactory: (DelegatedVisitor) -> ItemVisitor, 447 ): Codebase { 448 // Create a snapshot taker that will construct the snapshot. Pass in the 449 // referenceVisitorFactory so it can create the reference visitor for use in creating 450 // Items that are referenced from the snapshot. 451 val taker = CodebaseSnapshotTaker(referenceVisitorFactory) 452 453 // Wrap it in a visitor that will determine which Items are defined in the snapshot and 454 // then apply that visitor to the input codebase. 455 val definitionVisitor = definitionVisitorFactory(taker) 456 codebase.accept(definitionVisitor) 457 458 // Return the constructed snapshot. 459 return taker.snapshotCodebase 460 } 461 } 462 463 /** Encapsulates state and methods needed to take a snapshot of [TypeItem]s. */ 464 internal inner class SnapshotTypeItemFactoryContext( 465 val typeItemFactory: SnapshotTypeItemFactory 466 ) { 467 /** 468 * Create a snapshot of this [TypeParameterList] and an associated 469 * [SnapshotTypeItemFactory]. 470 * 471 * @param description the description to use when failing to resolve a type parameter by 472 * name. 473 */ 474 internal fun TypeParameterList.snapshot(description: String) = 475 if (this == TypeParameterList.NONE) TypeParameterListAndFactory(this, typeItemFactory) 476 else 477 DefaultTypeParameterList.createTypeParameterItemsAndFactory( 478 typeItemFactory, 479 description, 480 this, 481 { typeParameterItem -> 482 DefaultTypeParameterItem( 483 codebase = snapshotCodebase, 484 modifiers = typeParameterItem.modifiers.snapshot(), 485 name = typeParameterItem.name(), 486 isReified = typeParameterItem.isReified() 487 ) 488 }, 489 // Create, set and return the [BoundsTypeItem] list. 490 { typeItemFactory, typeParameterItem -> 491 typeParameterItem.typeBounds().map { typeItemFactory.getBoundsType(it) } 492 }, 493 ) 494 /** General [TypeItem] specific snapshot. */ 495 internal fun TypeItem.snapshot() = typeItemFactory.getGeneralType(this) 496 497 /** [ClassTypeItem] specific snapshot. */ 498 internal fun ClassTypeItem.snapshot() = 499 typeItemFactory.getGeneralType(this) as ClassTypeItem 500 501 /** Create a snapshot of this list of [ParameterItem]s. */ 502 internal fun List<ParameterItem>.snapshot( 503 containingCallable: CallableItem, 504 currentCallable: CallableItem 505 ): List<ParameterItem> { 506 return map { parameterItem -> 507 // Retrieve the public name immediately to remove any dependencies on this in the 508 // lambda passed to publicNameProvider. 509 val publicName = parameterItem.publicName() 510 511 // The parameter being snapshot may be from a previously released API, which may not 512 // track parameter names and so may have to auto-generate them. This code tries to 513 // avoid using the auto-generated names if possible. If the `publicName()` of the 514 // parameter being snapshot is not `null` then get its `name()` as that will either 515 // be set to the public name or another developer supplied name. Either way it will 516 // not be auto-generated. However, if its `publicName()` is `null` then its `name()` 517 // will be auto-generated so try and avoid that is possible. Instead, use the name 518 // of the corresponding parameter from `currentCallable` as that is more likely to 519 // have a developer supplied name, although it will be the same as `parameterItem` 520 // if `currentCallable` is not being reverted. 521 val name = 522 if (publicName != null) parameterItem.name() 523 else { 524 val namedParameter = 525 currentCallable.parameters()[parameterItem.parameterIndex] 526 namedParameter.name() 527 } 528 529 itemFactory.createParameterItem( 530 fileLocation = parameterItem.fileLocation, 531 itemLanguage = parameterItem.itemLanguage, 532 modifiers = parameterItem.modifiers.snapshot(), 533 name = name, 534 publicNameProvider = { publicName }, 535 containingCallable = containingCallable, 536 parameterIndex = parameterItem.parameterIndex, 537 type = parameterItem.type().snapshot(), 538 defaultValueFactory = parameterItem.defaultValue::snapshot, 539 ) 540 } 541 } 542 } 543 } 544 545 /** 546 * Get the actual item to snapshot, this takes into account whether the item has been reverted. 547 * 548 * The [Showability.revertItem] is only set to a non-null value if changes to this [SelectableItem] 549 * have been reverted AND this [SelectableItem] existed in the previously released API. 550 * 551 * This casts the [Showability.revertItem] to the same type as this is called upon. That is safe as, 552 * if set to a non-null value the [Showability.revertItem] will always point to a [SelectableItem] 553 * of the same type. 554 */ 555 private val <reified T : SelectableItem> T.actualItemToSnapshot: T 556 inline get() = (showability.revertItem ?: this) as T 557