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