• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2025 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.model.AnnotationItem
20 import com.android.tools.metalava.model.BaseItemVisitor
21 import com.android.tools.metalava.model.CallableItem
22 import com.android.tools.metalava.model.ClassItem
23 import com.android.tools.metalava.model.Codebase
24 import com.android.tools.metalava.model.CodebaseFragment
25 import com.android.tools.metalava.model.FieldItem
26 import com.android.tools.metalava.model.Item
27 import com.android.tools.metalava.model.MemberItem
28 import com.android.tools.metalava.model.ModifierList
29 import com.android.tools.metalava.model.PackageItem
30 import com.android.tools.metalava.model.PropertyItem
31 import com.android.tools.metalava.model.SelectableItem
32 import com.android.tools.metalava.model.snapshot.EmittableDelegatingVisitor
33 import com.android.tools.metalava.model.snapshot.NonFilteringDelegatingVisitor
34 
35 /**
36  * Creates a snapshot that is a delta between two [Codebase]s.
37  *
38  * This effectively does the opposite of what [ApiFile] does when creating a [Codebase] from
39  * multiple signature files, where the first is a standalone surface and each subsequent file is for
40  * a surface that extends the surface in the preceding file.
41  *
42  * This can be used to create deltas that can be used when class nesting is not maintained as it
43  * does not emit a class just because a nested class needs emitting.
44  */
45 class SnapshotDeltaMaker private constructor(private val base: Codebase) :
46     BaseItemVisitor(
47         preserveClassNesting = true,
48         visitParameterItems = false,
49     ) {
50     /**
51      * Mark the package to emit.
52      *
53      * The containing package is not marked to emit as packages are flattened before visiting.
54      */
55     private fun PackageItem.markEmit() {
56         if (!emit) {
57             emit = true
58         }
59     }
60 
61     /**
62      * Mark the class to emit.
63      *
64      * The containing package is marked to emit as otherwise its contents will not usually be
65      * visited. The containing class of nested classes is not marked to emit as this is used for
66      * files that flatten nested classes so nested classes can be visited without checking the
67      * [SelectableItem.emit] of the containing class.
68      */
69     private fun ClassItem.markEmit() {
70         if (!emit) {
71             emit = true
72             containingPackage().markEmit()
73         }
74     }
75 
76     /**
77      * Mark a member to emit.
78      *
79      * The containing class is marked to emit as otherwise its members will not be visited.
80      */
81     private fun MemberItem.markEmit() {
82         if (!emit) {
83             emit = true
84             containingClass().markEmit()
85         }
86     }
87 
88     /** Override to visit all packages. */
89     override fun skipPackage(pkg: PackageItem) = false
90 
91     /** Override to skip any non-public or protected items. */
92     override fun skip(item: Item): Boolean = !item.modifiers.isPublicOrProtected()
93 
94     /** Convert a list of [AnnotationItem]s into a list of [String]s for comparison. */
95     // TODO(b/354633349): Use equality once value abstraction provides consistent behavior across
96     //   models.
97     private fun List<AnnotationItem>.normalize() = map { it.toString() }.sorted()
98 
99     override fun visitClass(cls: ClassItem) {
100         cls.findCorrespondingItemIn(base)?.let { baseClass ->
101             // If super class type is set and is different to the base class then drop out to emit
102             // this class.
103             val superClassType = cls.superClassType()
104             if (superClassType != null && baseClass.superClassType() != superClassType) {
105                 return@let
106             }
107 
108             // If this class has different annotations to the base class then drop out to emit
109             // this class.
110             val annotations = cls.modifiers.annotations().normalize()
111             val baseAnnotations = baseClass.modifiers.annotations().normalize()
112             if (annotations != baseAnnotations) {
113                 return@let
114             }
115 
116             // The class is not different so do not emit it.
117             return
118         }
119 
120         // The class is new or different.
121         cls.markEmit()
122     }
123 
124     override fun visitCallable(callable: CallableItem) {
125         callable.findCorrespondingItemIn(base)?.let {
126             return
127         }
128 
129         // The callable is new.
130         callable.markEmit()
131     }
132 
133     override fun visitField(field: FieldItem) {
134         field.findCorrespondingItemIn(base)?.let {
135             return
136         }
137 
138         // The field is new.
139         field.markEmit()
140     }
141 
142     override fun visitProperty(property: PropertyItem) {
143         property.findCorrespondingItemIn(base)?.let {
144             return
145         }
146 
147         // The property is new.
148         property.markEmit()
149     }
150 
151     companion object {
152         /**
153          * Create a text [Codebase] that is a delta between [base] and [codebaseFragment], i.e. it
154          * includes all the [Item] that are in [codebaseFragment] but not in [base].
155          *
156          * This is expected to be used where [codebaseFragment] is a super set of [base] but that is
157          * not enforced. If [base] contains [Item]s which are not present in [codebaseFragment] then
158          * they will not appear in the delta.
159          *
160          * [ClassItem]s are treated specially. If [codebaseFragment] and [base] have [ClassItem]s
161          * with the same name and [codebaseFragment]'s has members which are not present in [base]'s
162          * then a [ClassItem] containing the additional [codebaseFragment] members will appear in
163          * the delta, otherwise it will not unless the two [ClassItem]s differ in one of the
164          * following ways:
165          * * The modifiers are not [ModifierList.equivalentTo] each other.
166          * * The [ClassItem.superClassType]s are not the same.
167          *
168          * Note: A [MemberItem] that exists in both will not be emitted even if they differ in some
169          * way, e.g. annotations, extends list. That is because [ApiFile] has no mechanism to
170          * combine them and does not even throw an error if it encounters duplicates.
171          */
172         fun createDelta(
173             base: Codebase,
174             codebaseFragment: CodebaseFragment,
175         ): CodebaseFragment {
176             // Take a snapshot.
177             val snapshotFragment =
178                 codebaseFragment.snapshotIncludingRevertedItems(
179                     referenceVisitorFactory = ::NonFilteringDelegatingVisitor,
180                 )
181 
182             val snapshot = snapshotFragment.codebase
183 
184             // Assume that none of it will be emitted.
185             snapshot.accept(
186                 object : BaseItemVisitor() {
187                     override fun visitSelectableItem(item: SelectableItem) {
188                         item.emit = false
189                     }
190                 }
191             )
192 
193             // Mark those items that are new (or different) to be emitted. Also, marks their
194             // containers, e.g. class members and nested classes will mark their containing class,
195             // classes will mark their containing package.
196             val deltaMaker = SnapshotDeltaMaker(base)
197             snapshot.accept(deltaMaker)
198 
199             return CodebaseFragment.create(snapshot, ::EmittableDelegatingVisitor)
200         }
201     }
202 }
203