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