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.ApiPredicate
20 import com.android.tools.metalava.PackageFilter
21 import com.android.tools.metalava.model.BaseItemVisitor
22 import com.android.tools.metalava.model.ClassItem
23 import com.android.tools.metalava.model.FieldItem
24 import com.android.tools.metalava.model.Item
25 import com.android.tools.metalava.model.MethodItem
26 import com.android.tools.metalava.model.PackageItem
27 import com.android.tools.metalava.model.PropertyItem
28 import java.util.function.Predicate
29 
30 open class ApiVisitor(
31     /**
32      * Whether constructors should be visited as part of a [#visitMethod] call instead of just a
33      * [#visitConstructor] call. Helps simplify visitors that don't care to distinguish between the
34      * two cases. Defaults to true.
35      */
36     visitConstructorsAsMethods: Boolean = true,
37     /**
38      * Whether inner classes should be visited "inside" a class; when this property is true, inner
39      * classes are visited before the [#afterVisitClass] method is called; when false, it's done
40      * afterwards. Defaults to false.
41      */
42     nestInnerClasses: Boolean = false,
43 
44     /** Whether to include inherited fields too */
45     val inlineInheritedFields: Boolean = true,
46 
47     /** Comparator to sort methods with. */
48     val methodComparator: Comparator<MethodItem> = MethodItem.comparator,
49 
50     /** The filter to use to determine if we should emit an item */
51     val filterEmit: Predicate<Item>,
52 
53     /** The filter to use to determine if we should emit a reference to an item */
54     val filterReference: Predicate<Item>,
55 
56     /**
57      * Whether the visitor should include visiting top-level classes that have nothing other than
58      * non-empty inner classes within. Typically these are not included in signature files, but when
59      * generating stubs we need to include them.
60      */
61     val includeEmptyOuterClasses: Boolean = false,
62 
63     /**
64      * Whether this visitor should visit elements that have not been annotated with one of the
65      * annotations passed in using the --show-annotation flag. This is normally true, but signature
66      * files sometimes sets this to false so the signature file only contains the "diff" of the
67      * annotated API relative to the base API.
68      */
69     val showUnannotated: Boolean = true,
70 
71     /** Configuration that may come from the command line. */
72     config: Config,
73 ) : BaseItemVisitor(visitConstructorsAsMethods, nestInnerClasses) {
74 
75     private val packageFilter: PackageFilter? = config.packageFilter
76 
77     /**
78      * Contains configuration for [ApiVisitor] that can, or at least could, come from command line
79      * options.
80      */
81     data class Config(
82         val packageFilter: PackageFilter? = null,
83 
84         /** Configuration for any [ApiPredicate] instances this needs to create. */
85         val apiPredicateConfig: ApiPredicate.Config = ApiPredicate.Config()
86     )
87 
88     constructor(
89         /**
90          * Whether constructors should be visited as part of a [#visitMethod] call instead of just a
91          * [#visitConstructor] call. Helps simplify visitors that don't care to distinguish between
92          * the two cases. Defaults to true.
93          */
94         visitConstructorsAsMethods: Boolean = true,
95         /**
96          * Whether inner classes should be visited "inside" a class; when this property is true,
97          * inner classes are visited before the [#afterVisitClass] method is called; when false,
98          * it's done afterwards. Defaults to false.
99          */
100         nestInnerClasses: Boolean = false,
101 
102         /** Whether to ignore APIs with annotations in the --show-annotations list */
103         ignoreShown: Boolean = true,
104 
105         /** Whether to match APIs marked for removal instead of the normal API */
106         remove: Boolean = false,
107 
108         /** Comparator to sort methods with. */
109         methodComparator: Comparator<MethodItem> = MethodItem.comparator,
110 
111         /**
112          * The filter to use to determine if we should emit an item. If null, the default value is
113          * an [ApiPredicate] based on the values of [remove], [includeApisForStubPurposes],
114          * [config], and [ignoreShown].
115          */
116         filterEmit: Predicate<Item>? = null,
117 
118         /**
119          * The filter to use to determine if we should emit a reference to an item. If null, the
120          * default value is an [ApiPredicate] based on the values of [remove] and [config].
121          */
122         filterReference: Predicate<Item>? = null,
123 
124         /**
125          * Whether to include "for stub purposes" APIs.
126          *
127          * See [ApiPredicate.includeOnlyForStubPurposes]
128          */
129         includeApisForStubPurposes: Boolean = true,
130 
131         /** Configuration that may come from the command line. */
132         config: Config,
133     ) : this(
134         visitConstructorsAsMethods = visitConstructorsAsMethods,
135         nestInnerClasses = nestInnerClasses,
136         inlineInheritedFields = true,
137         methodComparator = methodComparator,
138         filterEmit = filterEmit
139                 ?: ApiPredicate(
140                     matchRemoved = remove,
141                     includeApisForStubPurposes = includeApisForStubPurposes,
142                     config = config.apiPredicateConfig.copy(ignoreShown = ignoreShown),
143                 ),
144         filterReference = filterReference
145                 ?: ApiPredicate(
146                     ignoreRemoved = remove,
147                     config = config.apiPredicateConfig.copy(ignoreShown = true),
148                 ),
149         config = config,
150     )
151 
152     // The API visitor lazily visits packages only when there's a match within at least one class;
153     // this property keeps track of whether we've already visited the current package
154     var visitingPackage = false
155 
visitnull156     override fun visit(cls: ClassItem) {
157         if (!include(cls)) {
158             return
159         }
160 
161         // We build up a separate data structure such that we can compute the
162         // sets of fields, methods, etc even for inner classes (recursively); that way
163         // we can easily and up front determine whether we have any matches for
164         // inner classes (which is vital for computing the removed-api for example, where
165         // only something like the appearance of a removed method inside an inner class
166         // results in the outer class being described in the signature file.
167         val candidate = VisitCandidate(cls)
168         candidate.accept()
169     }
170 
visitnull171     override fun visit(pkg: PackageItem) {
172         if (!pkg.emit) {
173             return
174         }
175 
176         // For the API visitor packages are visited lazily; only when we encounter
177         // an unfiltered item within the class
178         pkg.topLevelClasses().asSequence().sortedWith(ClassItem.classNameSorter()).forEach {
179             it.accept(this)
180         }
181 
182         if (visitingPackage) {
183             visitingPackage = false
184             afterVisitPackage(pkg)
185             afterVisitItem(pkg)
186         }
187     }
188 
189     /** @return Whether this class is generally one that we want to recurse into */
includenull190     open fun include(cls: ClassItem): Boolean {
191         if (skip(cls)) {
192             return false
193         }
194         if (packageFilter != null && !packageFilter.matches(cls.containingPackage())) {
195             return false
196         }
197 
198         return cls.emit || cls.codebase.preFiltered
199     }
200 
201     /**
202      * @return Whether the given VisitCandidate's visitor should recurse into the given
203      *   VisitCandidate's class
204      */
includenull205     fun include(vc: VisitCandidate): Boolean {
206         if (!include(vc.cls)) {
207             return false
208         }
209         return shouldEmitClassBody(vc) || shouldEmitInnerClasses(vc)
210     }
211 
212     /**
213      * @return Whether this class should be visited Note that if [include] returns true then we will
214      *   still visit classes that are contained by this one
215      */
shouldEmitClassnull216     open fun shouldEmitClass(vc: VisitCandidate): Boolean {
217         return vc.cls.emit && (includeEmptyOuterClasses || shouldEmitClassBody(vc))
218     }
219 
220     /**
221      * @return Whether the body of this class (everything other than the inner classes) emits
222      *   anything
223      */
shouldEmitClassBodynull224     private fun shouldEmitClassBody(vc: VisitCandidate): Boolean {
225         return when {
226             filterEmit.test(vc.cls) -> true
227             vc.nonEmpty() -> filterReference.test(vc.cls)
228             else -> false
229         }
230     }
231 
232     /** @return Whether the inner classes of this class will emit anything */
shouldEmitInnerClassesnull233     fun shouldEmitInnerClasses(vc: VisitCandidate): Boolean {
234         return vc.innerClasses.any { shouldEmitAnyClass(it) }
235     }
236 
237     /** @return Whether this class will emit anything */
shouldEmitAnyClassnull238     private fun shouldEmitAnyClass(vc: VisitCandidate): Boolean {
239         return shouldEmitClassBody(vc) || shouldEmitInnerClasses(vc)
240     }
241 
242     companion object {
243         /**
244          * Comparator that will order [FieldItem]s such that those for which
245          * [FieldItem.isEnumConstant] returns `true` will come before those for which it is `false`.
246          */
247         private val fieldComparatorEnumConstantFirst =
248             Comparator.comparing(FieldItem::isEnumConstant)
249                 .reversed()
250                 .thenComparing(FieldItem.comparator)
251     }
252 
253     inner class VisitCandidate(val cls: ClassItem) {
254         val innerClasses by
<lambda>null255             lazy(LazyThreadSafetyMode.NONE) {
256                 val clsInnerClasses = cls.innerClasses()
257                 if (clsInnerClasses.isEmpty()) {
258                     emptyList()
259                 } else {
260                     clsInnerClasses
261                         .asSequence()
262                         .sortedWith(ClassItem.classNameSorter())
263                         .map { VisitCandidate(it) }
264                         .toList()
265                 }
266             }
267 
268         private val constructors by
<lambda>null269             lazy(LazyThreadSafetyMode.NONE) {
270                 val clsConstructors = cls.constructors()
271                 if (clsConstructors.isEmpty()) {
272                     emptyList()
273                 } else {
274                     clsConstructors
275                         .asSequence()
276                         .filter { filterEmit.test(it) }
277                         .sortedWith(methodComparator)
278                         .toList()
279                 }
280             }
281 
282         private val methods by
<lambda>null283             lazy(LazyThreadSafetyMode.NONE) {
284                 val clsMethods = cls.methods()
285                 if (clsMethods.isEmpty()) {
286                     emptyList()
287                 } else {
288                     clsMethods
289                         .asSequence()
290                         .filter { filterEmit.test(it) }
291                         .sortedWith(methodComparator)
292                         .toList()
293                 }
294             }
295 
296         private val fields by
<lambda>null297             lazy(LazyThreadSafetyMode.NONE) {
298                 val fieldSequence =
299                     if (inlineInheritedFields) {
300                         cls.filteredFields(filterEmit, showUnannotated).asSequence()
301                     } else {
302                         cls.fields().asSequence().filter { filterEmit.test(it) }
303                     }
304 
305                 // Sort the fields so that enum constants come first.
306                 fieldSequence.sortedWith(fieldComparatorEnumConstantFirst)
307             }
308 
309         private val properties by
<lambda>null310             lazy(LazyThreadSafetyMode.NONE) {
311                 val clsProperties = cls.properties()
312                 if (clsProperties.isEmpty()) {
313                     emptyList()
314                 } else {
315                     clsProperties
316                         .asSequence()
317                         .filter { filterEmit.test(it) }
318                         .sortedWith(PropertyItem.comparator)
319                         .toList()
320                 }
321             }
322 
323         /** Whether the class body contains any Item's (other than inner Classes) */
nonEmptynull324         fun nonEmpty(): Boolean {
325             return !(constructors.none() && methods.none() && fields.none() && properties.none())
326         }
327 
acceptnull328         fun accept() {
329             if (!include(this)) {
330                 return
331             }
332 
333             val emitThis = shouldEmitClass(this)
334             if (emitThis) {
335                 if (!visitingPackage) {
336                     visitingPackage = true
337                     val pkg = cls.containingPackage()
338                     visitItem(pkg)
339                     visitPackage(pkg)
340                 }
341 
342                 visitItem(cls)
343                 visitClass(cls)
344 
345                 for (constructor in constructors) {
346                     constructor.accept(this@ApiVisitor)
347                 }
348 
349                 for (method in methods) {
350                     method.accept(this@ApiVisitor)
351                 }
352 
353                 for (property in properties) {
354                     property.accept(this@ApiVisitor)
355                 }
356                 for (field in fields) {
357                     field.accept(this@ApiVisitor)
358                 }
359             }
360 
361             if (nestInnerClasses) { // otherwise done below
362                 innerClasses.forEach { it.accept() }
363             }
364 
365             if (emitThis) {
366                 afterVisitClass(cls)
367                 afterVisitItem(cls)
368             }
369 
370             if (!nestInnerClasses) {
371                 innerClasses.forEach { it.accept() }
372             }
373         }
374     }
375 }
376