xref: /aosp_15_r20/tools/metalava/metalava-model/src/main/java/com/android/tools/metalava/model/BaseItemVisitor.kt (revision 115816f9299ab6ddd6b9673b81f34e707f6bacab)
1 /*
2  * Copyright (C) 2017 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
18 
19 open class BaseItemVisitor(
20     /**
21      * Whether nested classes should be visited "inside" a class; when this property is true, nested
22      * classes are visited before the [#afterVisitClass] method is called; when false, it's done
23      * afterwards. Defaults to false.
24      */
25     val preserveClassNesting: Boolean = false,
26 
27     /**
28      * Determines whether this will visit [ParameterItem]s or not.
29      *
30      * If this is `true` then [ParameterItem]s will be visited, and passed to [visitItem],
31      * [visitParameter] and [afterVisitItem] in that order. Otherwise, they will not be visited.
32      *
33      * Defaults to `true` as that is the safest option which avoids inadvertently ignoring them.
34      */
35     protected val visitParameterItems: Boolean = true,
36 ) : ItemVisitor {
37     /** Calls [visitItem] before invoking [body] after which it calls [afterVisitItem]. */
wrapBodyWithCallsToVisitMethodsForItemnull38     protected inline fun <T : Item> wrapBodyWithCallsToVisitMethodsForItem(
39         item: T,
40         body: () -> Unit
41     ) {
42         visitItem(item)
43         body()
44         afterVisitItem(item)
45     }
46 
47     /**
48      * Calls [visitItem], then [visitSelectableItem] before invoking [body] after which it calls
49      * [afterVisitSelectableItem] and finally [afterVisitItem].
50      */
wrapBodyWithCallsToVisitMethodsForSelectableItemnull51     protected inline fun <T : SelectableItem> wrapBodyWithCallsToVisitMethodsForSelectableItem(
52         item: T,
53         body: () -> Unit
54     ) {
55         wrapBodyWithCallsToVisitMethodsForItem(item) {
56             visitSelectableItem(item)
57             body()
58             afterVisitSelectableItem(item)
59         }
60     }
61 
visitnull62     override fun visit(cls: ClassItem) {
63         if (skip(cls)) {
64             return
65         }
66 
67         wrapBodyWithCallsToVisitMethodsForSelectableItem(cls) {
68             visitClass(cls)
69 
70             for (constructor in cls.constructors()) {
71                 constructor.accept(this)
72             }
73 
74             for (method in cls.methods()) {
75                 method.accept(this)
76             }
77 
78             for (property in cls.properties()) {
79                 property.accept(this)
80             }
81 
82             if (cls.isEnum()) {
83                 // In enums, visit the enum constants first, then the fields
84                 for (field in cls.fields()) {
85                     if (field.isEnumConstant()) {
86                         field.accept(this)
87                     }
88                 }
89                 for (field in cls.fields()) {
90                     if (!field.isEnumConstant()) {
91                         field.accept(this)
92                     }
93                 }
94             } else {
95                 for (field in cls.fields()) {
96                     field.accept(this)
97                 }
98             }
99 
100             if (preserveClassNesting) {
101                 for (nestedCls in cls.nestedClasses()) {
102                     nestedCls.accept(this)
103                 }
104             } // otherwise done in visit(PackageItem)
105 
106             afterVisitClass(cls)
107         }
108     }
109 
visitnull110     override fun visit(field: FieldItem) {
111         if (skip(field)) {
112             return
113         }
114 
115         wrapBodyWithCallsToVisitMethodsForSelectableItem(field) { visitField(field) }
116     }
117 
visitnull118     override fun visit(constructor: ConstructorItem) {
119         visitMethodOrConstructor(constructor) { visitConstructor(it) }
120     }
121 
visitnull122     override fun visit(method: MethodItem) {
123         visitMethodOrConstructor(method) { visitMethod(it) }
124     }
125 
visitMethodOrConstructornull126     private inline fun <T : CallableItem> visitMethodOrConstructor(
127         callable: T,
128         dispatch: (T) -> Unit
129     ) {
130         if (skip(callable)) {
131             return
132         }
133 
134         wrapBodyWithCallsToVisitMethodsForSelectableItem(callable) {
135             visitCallable(callable)
136 
137             // Call the specific visitX method for the CallableItem subclass.
138             dispatch(callable)
139 
140             if (visitParameterItems) {
141                 for (parameter in callable.parameters()) {
142                     parameter.accept(this)
143                 }
144             }
145         }
146     }
147 
148     /**
149      * Get the package's classes to visit directly.
150      *
151      * If nested classes are to appear as nested within their containing classes then this will just
152      * return the package's top level classes. It will then be the responsibility of
153      * `visit(ClassItem)` to visit the nested classes. Otherwise, this will return a flattened
154      * sequence of each class followed by its nested classes.
155      */
packageClassesAsSequencenull156     protected fun packageClassesAsSequence(pkg: PackageItem) =
157         if (preserveClassNesting) pkg.topLevelClasses().asSequence() else pkg.allClasses()
158 
159     override fun visit(codebase: Codebase) {
160         visitCodebase(codebase)
161         codebase.getPackages().packages.forEach { it.accept(this) }
162         afterVisitCodebase(codebase)
163     }
164 
visitnull165     override fun visit(pkg: PackageItem) {
166         // Ignore any packages whose `emit` property is `false`. That is basically any package that
167         // does not contain at least one class that could be emitted as part of the API.
168         if (!pkg.emit) {
169             return
170         }
171 
172         if (skip(pkg)) {
173             return
174         }
175 
176         wrapBodyWithCallsToVisitMethodsForSelectableItem(pkg) {
177             visitPackage(pkg)
178 
179             for (cls in packageClassesAsSequence(pkg)) {
180                 cls.accept(this)
181             }
182 
183             afterVisitPackage(pkg)
184         }
185     }
186 
visitnull187     override fun visit(parameter: ParameterItem) {
188         if (skip(parameter)) {
189             return
190         }
191 
192         wrapBodyWithCallsToVisitMethodsForItem(parameter) { visitParameter(parameter) }
193     }
194 
visitnull195     override fun visit(property: PropertyItem) {
196         if (skip(property)) {
197             return
198         }
199 
200         wrapBodyWithCallsToVisitMethodsForSelectableItem(property) { visitProperty(property) }
201     }
202 
skipnull203     open fun skip(item: Item): Boolean = false
204 
205     /**
206      * Visits any [Item].
207      *
208      * This is always called BEFORE other more specialized visit methods, such as [visitClass].
209      */
210     open fun visitItem(item: Item) {}
211 
212     /**
213      * Visits any [SelectableItem], i.e. everything for which [visitItem] is called except
214      * [ParameterItem]s.
215      *
216      * This is always called BEFORE other more specialized visit methods, such as [visitClass].
217      */
visitSelectableItemnull218     open fun visitSelectableItem(item: SelectableItem) {}
219 
visitCodebasenull220     open fun visitCodebase(codebase: Codebase) {}
221 
visitPackagenull222     open fun visitPackage(pkg: PackageItem) {}
223 
visitClassnull224     open fun visitClass(cls: ClassItem) {}
225 
visitCallablenull226     open fun visitCallable(callable: CallableItem) {}
227 
visitConstructornull228     open fun visitConstructor(constructor: ConstructorItem) {}
229 
visitMethodnull230     open fun visitMethod(method: MethodItem) {}
231 
visitFieldnull232     open fun visitField(field: FieldItem) {}
233 
234     /** Visits a [ParameterItem]. */
visitParameternull235     open fun visitParameter(parameter: ParameterItem) {}
236 
visitPropertynull237     open fun visitProperty(property: PropertyItem) {}
238 
239     /**
240      * Visits any [SelectableItem], i.e. everything for which [afterVisitItem] is called except
241      * [ParameterItem]s.
242      *
243      * This is always called AFTER other more specialized visit methods, such as [afterVisitClass].
244      */
afterVisitSelectableItemnull245     open fun afterVisitSelectableItem(item: SelectableItem) {}
246 
247     /**
248      * Visits any [Item], except for [TypeParameterItem].
249      *
250      * This is always called AFTER other more specialized visit methods, such as [afterVisitClass].
251      */
afterVisitItemnull252     open fun afterVisitItem(item: Item) {}
253 
afterVisitCodebasenull254     open fun afterVisitCodebase(codebase: Codebase) {}
255 
afterVisitPackagenull256     open fun afterVisitPackage(pkg: PackageItem) {}
257 
afterVisitClassnull258     open fun afterVisitClass(cls: ClassItem) {}
259 }
260