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