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