• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.visitors
18 
19 import com.android.tools.metalava.model.BaseItemVisitor
20 import com.android.tools.metalava.model.CallableItem
21 import com.android.tools.metalava.model.ClassItem
22 import com.android.tools.metalava.model.FieldItem
23 import com.android.tools.metalava.model.ItemVisitor
24 import com.android.tools.metalava.model.MemberItem
25 import com.android.tools.metalava.model.PackageItem
26 import com.android.tools.metalava.model.PropertyItem
27 
28 open class ApiVisitor(
29     /** @see BaseItemVisitor.preserveClassNesting */
30     preserveClassNesting: Boolean = false,
31 
32     /** @see BaseItemVisitor.visitParameterItems */
33     visitParameterItems: Boolean = true,
34 
35     /** Whether to include inherited fields too */
36     private val inlineInheritedFields: Boolean = true,
37 
38     /** Comparator to sort callables with. */
39     private val callableComparator: Comparator<CallableItem> = CallableItem.comparator,
40 
41     /** The filters to use to determine what parts of the API will be visited. */
42     private val apiFilters: ApiFilters,
43 
44     /**
45      * Whether this visitor should visit elements that have not been annotated with one of the
46      * annotations passed in using the --show-annotation flag. This is normally true, but signature
47      * files sometimes sets this to false so the signature file only contains the "diff" of the
48      * annotated API relative to the base API.
49      */
50     protected val showUnannotated: Boolean = true,
51 ) : BaseItemVisitor(preserveClassNesting, visitParameterItems) {
52 
53     constructor(
54         /** @see BaseItemVisitor.visitParameterItems */
55         visitParameterItems: Boolean = true,
56 
57         /** Configuration that may come from the command line. */
58         apiPredicateConfig: ApiPredicate.Config,
59     ) : this(
60         visitParameterItems = visitParameterItems,
61         apiFilters = defaultFilters(apiPredicateConfig),
62     )
63 
64     /** The filter to use to determine if we should emit an item */
65     protected val filterEmit = apiFilters.emit
66 
67     /** The filter to use to determine if we should emit a reference to an item */
68     protected val filterReference = apiFilters.reference
69 
70     companion object {
71         /** Get the default [ApiFilters] to use with [ApiVisitor]. */
defaultFiltersnull72         fun defaultFilters(
73             apiPredicateConfig: ApiPredicate.Config,
74         ): ApiFilters {
75             return ApiFilters(
76                 emit = defaultEmitFilter(apiPredicateConfig),
77                 reference =
78                     ApiPredicate(
79                         ignoreRemoved = false,
80                         config = apiPredicateConfig.copy(ignoreShown = true),
81                     ),
82             )
83         }
84 
85         /** Get the default emit filter to use with [ApiVisitor]. */
defaultEmitFilternull86         fun defaultEmitFilter(apiPredicateConfig: ApiPredicate.Config) =
87             ApiPredicate(
88                 matchRemoved = false,
89                 includeApisForStubPurposes = true,
90                 config = apiPredicateConfig.copy(ignoreShown = true),
91             )
92     }
93 
94     /**
95      * Visit a [List] of [ClassItem]s after sorting it into order defined by
96      * [ClassItem.classNameSorter].
97      */
98     private fun visitClassList(classes: List<ClassItem>) {
99         classes.sortedWith(ClassItem.classNameSorter()).forEach { it.accept(this) }
100     }
101 
102     /**
103      * Implement to redirect to [VisitCandidate.accept] if necessary,
104      *
105      * This is not called by this [ApiVisitor]. Instead, it calls [VisitCandidate.accept] which does
106      * not delegate to this method but visits the class and its members itself so that it can access
107      * the filtered and sorted members. However, this may be called by some other code calling
108      * [ClassItem.accept] directly on this [ApiVisitor]. In that case this creates and then
109      * delegates through to the [VisitCandidate.visitWrappedClassAndFilteredMembers]
110      */
visitnull111     override fun visit(cls: ClassItem) {
112         // Get a VisitCandidate and visit it, if needed.
113         getVisitCandidateIfNeeded(cls)?.visitWrappedClassAndFilteredMembers()
114     }
115 
visitnull116     override fun visit(pkg: PackageItem) {
117         if (!pkg.emit) {
118             return
119         }
120 
121         // Get the list of classes to visit directly. If nested classes are to appear as nested
122         // then just visit the top level classes directly and then the nested classes will be
123         // visited
124         // by their containing classes. Otherwise, flatten the nested classes and treat them all as
125         // top level classes.
126         val classesToVisitDirectly: List<ClassItem> =
127             packageClassesAsSequence(pkg).mapNotNull { getVisitCandidateIfNeeded(it) }.toList()
128 
129         // If none of the classes in this package will be visited them ignore the package entirely.
130         // TODO (b/135191699): also check if there are type aliases before returning
131         if (classesToVisitDirectly.isEmpty()) return
132 
133         wrapBodyWithCallsToVisitMethodsForSelectableItem(pkg) {
134             visitPackage(pkg)
135 
136             visitClassList(classesToVisitDirectly)
137 
138             pkg.typeAliases().sortedBy { it.simpleName }.forEach { it.accept(this) }
139 
140             afterVisitPackage(pkg)
141         }
142     }
143 
144     /** @return Whether this class is generally one that we want to recurse into */
includenull145     open fun include(cls: ClassItem): Boolean {
146         if (skip(cls)) {
147             return false
148         }
149 
150         return cls.emit
151     }
152 
153     /**
154      * Returns a [VisitCandidate] if the [cls] needs to be visited, otherwise return `null`.
155      *
156      * The [cls] needs to be visited if it passes the various checks that determine whether it
157      * should be emitted as part of an API surface as determined by [filterEmit] and
158      * [filterReference].
159      */
getVisitCandidateIfNeedednull160     private fun getVisitCandidateIfNeeded(cls: ClassItem): VisitCandidate? {
161         if (!include(cls)) return null
162 
163         // Check to see whether this class should be emitted in its entirety. If not then it may
164         // still be emitted if it contains emittable members.
165         val emit = filterEmit.test(cls)
166 
167         // If the class is emitted then create a VisitCandidate immediately.
168         if (emit) return VisitCandidate(cls)
169 
170         // Check to see if the class could be emitted if it contains emittable members. If not then
171         // return `null` to ignore this class. This will happen for a hidden class, e.g. package
172         // private, that implements/overrides methods from the API.
173         if (!filterReference.test(cls)) return null
174 
175         // Create a VisitCandidate to encapsulate the emittable members, if any.
176         val vc = VisitCandidate(cls)
177 
178         // Check to see if the class has any emittable members, if not return `null` to ignore this
179         // class.
180         if (vc.containsNoEmittableMembers()) return null
181 
182         // The class is emittable so return it.
183         return vc
184     }
185 
186     /**
187      * Encapsulates a [ClassItem] that is being visited and its members, filtered by [filterEmit],
188      * and sorted by various members specific comparators.
189      *
190      * The purpose of this is to store the lists of filtered and sorted members that were created
191      * during filtering of the classes in the [PackageItem] visit method. They need to be stored as
192      * they can take a long time to generate and will be needed again when visiting the class
193      * contents.
194      *
195      * Note: This implements [ClassItem] to allow visiting code to be more easily shared between
196      * this and [BaseItemVisitor]. It must not escape out of this class, e.g. be passed to
197      * `visitClass(...)`.
198      */
<lambda>null199     private inner class VisitCandidate(val cls: ClassItem) : ClassItem by cls {
200 
201         /**
202          * If the list this is called upon is empty then just return [emptyList], else apply the
203          * [transform] to the list and return that.
204          */
205         private inline fun <T> List<T>.mapIfNotEmpty(transform: List<T>.() -> List<T>) =
206             if (isEmpty()) emptyList() else transform(this)
207 
208         /**
209          * Sort the sequence into a [List].
210          *
211          * The standard [Sequence.sortedWith] will sort it into a list and then return a sequence
212          * wrapper which would then have to be converted back into a list. Instead, this just sorts
213          * it into a [List] and returns that.
214          */
215         private fun <T> Sequence<T>.sortToList(comparator: Comparator<in T>) =
216             if (none()) emptyList()
217             else
218                 toMutableList().let {
219                     // Sort the list in place.
220                     it.sortWith(comparator)
221                     // Return the sorter list.
222                     it
223                 }
224 
225         private val constructors =
226             cls.constructors().mapIfNotEmpty {
227                 asSequence().filter { filterEmit.test(it) }.sortToList(callableComparator)
228             }
229 
230         private val methods =
231             cls.methods().mapIfNotEmpty {
232                 asSequence().filter { filterEmit.test(it) }.sortToList(callableComparator)
233             }
234 
235         private val fields by
236             lazy(LazyThreadSafetyMode.NONE) {
237                 val fieldSequence =
238                     if (inlineInheritedFields) {
239                         cls.filteredFields(filterEmit, showUnannotated).asSequence()
240                     } else {
241                         cls.fields().asSequence().filter { filterEmit.test(it) }
242                     }
243 
244                 // Sort the fields so that enum constants come first.
245                 fieldSequence.sortToList(FieldItem.comparatorEnumConstantFirst)
246             }
247 
248         private val properties =
249             cls.properties().mapIfNotEmpty {
250                 asSequence().filter { filterEmit.test(it) }.sortToList(PropertyItem.comparator)
251             }
252 
253         /** Whether the class body contains any emmittable [MemberItem]s. */
254         fun containsNoEmittableMembers() =
255             constructors.isEmpty() && methods.isEmpty() && fields.isEmpty() && properties.isEmpty()
256 
257         /**
258          * Intercepts the call to visit this class and instead of using the default implementation
259          * which delegate to the appropriate method in [visitor] calls
260          */
261         override fun accept(visitor: ItemVisitor) {
262             if (visitor !== this@ApiVisitor)
263                 error(
264                     "VisitCandidate instance must only be visited by its creating ApiVisitor, not $visitor"
265                 )
266             visitWrappedClassAndFilteredMembers()
267         }
268 
269         fun visitWrappedClassAndFilteredMembers() {
270             wrapBodyWithCallsToVisitMethodsForSelectableItem(cls) {
271                 visitClass(cls)
272 
273                 for (constructor in constructors) {
274                     constructor.accept(this@ApiVisitor)
275                 }
276 
277                 for (method in methods) {
278                     method.accept(this@ApiVisitor)
279                 }
280 
281                 for (property in properties) {
282                     property.accept(this@ApiVisitor)
283                 }
284                 for (field in fields) {
285                     field.accept(this@ApiVisitor)
286                 }
287 
288                 if (preserveClassNesting) { // otherwise done in visit(PackageItem)
289                     visitClassList(cls.nestedClasses().mapNotNull { getVisitCandidateIfNeeded(it) })
290                 }
291 
292                 afterVisitClass(cls)
293             }
294         }
295     }
296 }
297