1 /*
<lambda>null2  * 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
18 
19 import com.android.tools.metalava.manifest.Manifest
20 import com.android.tools.metalava.manifest.emptyManifest
21 import com.android.tools.metalava.model.ANDROID_ANNOTATION_PREFIX
22 import com.android.tools.metalava.model.ANDROID_DEPRECATED_FOR_SDK
23 import com.android.tools.metalava.model.ANNOTATION_ATTR_VALUE
24 import com.android.tools.metalava.model.AnnotationAttributeValue
25 import com.android.tools.metalava.model.AnnotationItem
26 import com.android.tools.metalava.model.BaseItemVisitor
27 import com.android.tools.metalava.model.BaseTypeVisitor
28 import com.android.tools.metalava.model.ClassItem
29 import com.android.tools.metalava.model.ClassTypeItem
30 import com.android.tools.metalava.model.Codebase
31 import com.android.tools.metalava.model.ConstructorItem
32 import com.android.tools.metalava.model.FieldItem
33 import com.android.tools.metalava.model.Item
34 import com.android.tools.metalava.model.JAVA_LANG_DEPRECATED
35 import com.android.tools.metalava.model.MethodItem
36 import com.android.tools.metalava.model.PackageItem
37 import com.android.tools.metalava.model.PackageList
38 import com.android.tools.metalava.model.ParameterItem
39 import com.android.tools.metalava.model.PropertyItem
40 import com.android.tools.metalava.model.TypeItem
41 import com.android.tools.metalava.model.TypeParameterList
42 import com.android.tools.metalava.model.VariableTypeItem
43 import com.android.tools.metalava.model.VisibilityLevel
44 import com.android.tools.metalava.model.findAnnotation
45 import com.android.tools.metalava.model.psi.PsiClassItem
46 import com.android.tools.metalava.model.psi.isKotlin
47 import com.android.tools.metalava.model.source.SourceParser
48 import com.android.tools.metalava.model.visitors.ApiVisitor
49 import com.android.tools.metalava.reporter.Issues
50 import com.android.tools.metalava.reporter.Reporter
51 import java.io.File
52 import java.util.Collections
53 import java.util.IdentityHashMap
54 import java.util.Locale
55 import java.util.function.Predicate
56 import org.jetbrains.kotlin.asJava.classes.KtLightClassForFacade
57 import org.jetbrains.uast.UClass
58 
59 /**
60  * The [ApiAnalyzer] is responsible for walking over the various classes and members and compute
61  * visibility etc. of the APIs
62  */
63 class ApiAnalyzer(
64     private val sourceParser: SourceParser,
65     /** The code to analyze */
66     private val codebase: Codebase,
67     private val reporter: Reporter,
68     private val config: Config = Config(),
69 ) {
70 
71     data class Config(
72         val manifest: Manifest = emptyManifest,
73 
74         /** Packages to exclude/hide */
75         val hidePackages: List<String> = emptyList(),
76 
77         /**
78          * Packages that we should skip generating even if not hidden; typically only used by tests
79          */
80         val skipEmitPackages: List<String> = emptyList(),
81 
82         /**
83          * External annotation files that contain non-inclusion annotations which will appear in the
84          * generated API.
85          *
86          * These will be merged into the codebase.
87          */
88         val mergeQualifierAnnotations: List<File> = emptyList(),
89 
90         /**
91          * External annotation files that contain annotations which affect inclusion of items in the
92          * API.
93          *
94          * These will be merged into the codebase.
95          */
96         val mergeInclusionAnnotations: List<File> = emptyList(),
97 
98         /** Packages to import (if empty, include all) */
99         val stubImportPackages: Set<String> = emptySet(),
100 
101         /** The filter for all the show annotations. */
102         val allShowAnnotations: AnnotationFilter = AnnotationFilter.emptyFilter(),
103 
104         /** Configuration for any [ApiPredicate] instances this needs to create. */
105         val apiPredicateConfig: ApiPredicate.Config = ApiPredicate.Config()
106     )
107 
108     /** All packages in the API */
109     private val packages: PackageList = codebase.getPackages()
110 
111     fun computeApi() {
112         if (codebase.trustedApi()) {
113             // The codebase is already an API; no consistency checks to be performed
114             return
115         }
116 
117         skipEmitPackages()
118         // Suppress kotlin file facade classes with no public api
119         hideEmptyKotlinFileFacadeClasses()
120 
121         // Propagate visibility down into individual elements -- if a class is hidden,
122         // then the methods and fields are hidden etc
123         propagateHiddenRemovedAndDocOnly()
124     }
125 
126     fun addConstructors(filter: Predicate<Item>) {
127         // Let's say we have
128         //  class GrandParent { public GrandParent(int) {} }
129         //  class Parent {  Parent(int) {} }
130         //  class Child { public Child(int) {} }
131         //
132         // Here Parent's constructor is not public. For normal stub generation we'd end up with
133         // this:
134         //  class GrandParent { public GrandParent(int) {} }
135         //  class Parent { }
136         //  class Child { public Child(int) {} }
137         //
138         // This doesn't compile - Parent can't have a default constructor since there isn't
139         // one for it to invoke on GrandParent.
140         //
141         // we can generate a fake constructor instead, such as
142         //   Parent() { super(0); }
143         //
144         // But it's hard to do this lazily; what if we're generating the Child class first?
145         // Therefore, we'll instead walk over the hierarchy and insert these constructors into the
146         // Item hierarchy such that code generation can find them.
147         //
148         // We also need to handle the throws list, so we can't just unconditionally insert package
149         // private constructors
150 
151         // Keep track of all the ClassItems that have been visited so classes are only visited once.
152         val visited = Collections.newSetFromMap(IdentityHashMap<ClassItem, Boolean>())
153 
154         // Add constructors to the classes by walking up the super hierarchy and recursively add
155         // constructors; we'll do it recursively to make sure that the superclass has had its
156         // constructors initialized first (such that we can match the parameter lists and throws
157         // signatures), and we use the tag fields to avoid looking at all the internal classes more
158         // than once.
159         packages
160             .allClasses()
161             .filter { filter.test(it) }
162             .forEach { addConstructors(it, filter, visited) }
163     }
164 
165     /**
166      * Handle computing constructor hierarchy.
167      *
168      * We'll be setting several attributes: [ClassItem.stubConstructor] : The default constructor to
169      * invoke in this class from subclasses. **NOTE**: This constructor may not be part of the
170      * [ClassItem.constructors] list, e.g. for package private default constructors we've inserted
171      * (because there were no public constructors or constructors not using hidden parameter types.)
172      *
173      * [ConstructorItem.superConstructor] : The default constructor to invoke.
174      *
175      * @param visited contains the [ClassItem]s that have already been visited; this method adds
176      *   [cls] to it so [cls] will not be visited again.
177      */
178     private fun addConstructors(
179         cls: ClassItem,
180         filter: Predicate<Item>,
181         visited: MutableSet<ClassItem>
182     ) {
183         // What happens if we have
184         //  package foo:
185         //     public class A { public A(int) }
186         //  package bar
187         //     public class B extends A { public B(int) }
188         // If we just try inserting package private constructors here things will NOT work:
189         //  package foo:
190         //     public class A { public A(int); A() {} }
191         //  package bar
192         //     public class B extends A { public B(int); B() }
193         // because A <() is not accessible from B() -- it's outside the same package.
194         //
195         // So, we'll need to model the real constructors for all the scenarios where that works.
196         //
197         // The remaining challenge is that there will be some gaps: when we don't have a default
198         // constructor, subclass constructors will have to have an explicit super(args) call to pick
199         // the parent constructor to use. And which one? It generally doesn't matter; just pick one,
200         // but unfortunately, the super constructor can throw exceptions, and in that case the
201         // subclass constructor must also throw all those exceptions (you can't surround a super
202         // call with try/catch.)
203         //
204         // Luckily, this does not seem to be an actual problem with any of the source code that
205         // metalava currently processes. If it did become a problem then the solution would be to
206         // pick super constructors with a compatible set of throws.
207 
208         if (cls in visited) {
209             return
210         }
211 
212         // Don't add constructors to interfaces, enums, annotations, etc
213         if (!cls.isClass()) {
214             return
215         }
216 
217         // Remember that we have visited this class so that it is not visited again. This does not
218         // strictly need to be done before visiting the super classes as there should not be cycles
219         // in the class hierarchy. However, if due to some invalid input there is then doing this
220         // here will prevent those cycles from causing a stack overflow.
221         visited.add(cls)
222 
223         // First handle its super class hierarchy to make sure that we've already constructed super
224         // classes.
225         val superClass = cls.filteredSuperclass(filter)
226         superClass?.let { addConstructors(it, filter, visited) }
227 
228         val superDefaultConstructor = superClass?.stubConstructor
229         if (superDefaultConstructor != null) {
230             cls.constructors().forEach { constructor ->
231                 constructor.superConstructor = superDefaultConstructor
232             }
233         }
234 
235         // Find default constructor, if one doesn't exist
236         val filteredConstructors = cls.filteredConstructors(filter).toList()
237         cls.stubConstructor =
238             if (filteredConstructors.isNotEmpty()) {
239                 // Try to pick the constructor, select first by fewest throwables,
240                 // then fewest parameters, then based on order in listFilter.test(cls)
241                 filteredConstructors.reduce { first, second -> pickBest(first, second) }
242             } else if (
243                 cls.constructors().isNotEmpty() ||
244                     // For text based codebase, stub constructor needs to be generated even if
245                     // cls.constructors() is empty, so that public default constructor is not
246                     // created.
247                     cls.codebase.preFiltered
248             ) {
249 
250                 // No accessible constructors are available so a package private constructor is
251                 // created. Technically, the stub now has a constructor that isn't available at
252                 // runtime, but apps creating subclasses inside the android.* package is not
253                 // supported.
254                 cls.createDefaultConstructor().also {
255                     it.mutableModifiers().setVisibilityLevel(VisibilityLevel.PACKAGE_PRIVATE)
256                     it.hidden = false
257                     it.superConstructor = superDefaultConstructor
258                 }
259             } else {
260                 null
261             }
262     }
263 
264     // TODO: Annotation test: @ParameterName, if present, must be supplied on *all* the arguments!
265     // Warn about @DefaultValue("null"); they probably meant @DefaultNull
266     // Supplying default parameter in override is not allowed!
267 
268     private fun pickBest(current: ConstructorItem, next: ConstructorItem): ConstructorItem {
269         val currentThrowsCount = current.throwsTypes().size
270         val nextThrowsCount = next.throwsTypes().size
271 
272         return if (currentThrowsCount < nextThrowsCount) {
273             current
274         } else if (currentThrowsCount > nextThrowsCount) {
275             next
276         } else {
277             val currentParameterCount = current.parameters().size
278             val nextParameterCount = next.parameters().size
279             if (currentParameterCount <= nextParameterCount) {
280                 current
281             } else next
282         }
283     }
284 
285     fun generateInheritedStubs(filterEmit: Predicate<Item>, filterReference: Predicate<Item>) {
286         // When analyzing libraries we may discover some new classes during traversal; these aren't
287         // part of the API but may be super classes or interfaces; these will then be added into the
288         // package class lists, which could trigger a concurrent modification, so create a snapshot
289         // of the class list and iterate over it:
290         val allClasses = packages.allClasses().toList()
291         allClasses.forEach {
292             if (filterEmit.test(it)) {
293                 generateInheritedStubs(it, filterEmit, filterReference)
294             }
295         }
296     }
297 
298     private fun generateInheritedStubs(
299         cls: ClassItem,
300         filterEmit: Predicate<Item>,
301         filterReference: Predicate<Item>
302     ) {
303         if (!cls.isClass()) return
304         if (cls.superClass() == null) return
305         val allSuperClasses = cls.allSuperClasses()
306         val hiddenSuperClasses =
307             allSuperClasses.filter { !filterReference.test(it) && !it.isJavaLangObject() }
308 
309         if (hiddenSuperClasses.none()) { // not missing any implementation methods
310             return
311         }
312 
313         addInheritedStubsFrom(cls, hiddenSuperClasses, allSuperClasses, filterEmit, filterReference)
314         addInheritedInterfacesFrom(cls, hiddenSuperClasses, filterReference)
315     }
316 
317     private fun addInheritedInterfacesFrom(
318         cls: ClassItem,
319         hiddenSuperClasses: Sequence<ClassItem>,
320         filterReference: Predicate<Item>
321     ) {
322         var interfaceTypes: MutableList<ClassTypeItem>? = null
323         var interfaceTypeClasses: MutableList<ClassItem>? = null
324         for (hiddenSuperClass in hiddenSuperClasses) {
325             for (hiddenInterface in hiddenSuperClass.interfaceTypes()) {
326                 val hiddenInterfaceClass = hiddenInterface.asClass()
327                 if (filterReference.test(hiddenInterfaceClass ?: continue)) {
328                     if (interfaceTypes == null) {
329                         interfaceTypes = cls.interfaceTypes().toMutableList()
330                         interfaceTypeClasses =
331                             interfaceTypes.mapNotNull { it.asClass() }.toMutableList()
332                         if (cls.isInterface()) {
333                             cls.superClass()?.let { interfaceTypeClasses.add(it) }
334                         }
335                         cls.setInterfaceTypes(interfaceTypes)
336                     }
337                     if (interfaceTypeClasses!!.any { it == hiddenInterfaceClass }) {
338                         continue
339                     }
340 
341                     interfaceTypeClasses.add(hiddenInterfaceClass)
342 
343                     if (hiddenInterfaceClass.hasTypeVariables()) {
344                         val mapping = cls.mapTypeVariables(hiddenSuperClass)
345                         if (mapping.isNotEmpty()) {
346                             val mappedType = hiddenInterface.convertType(mapping)
347                             interfaceTypes.add(mappedType)
348                             continue
349                         }
350                     }
351 
352                     interfaceTypes.add(hiddenInterface)
353                 }
354             }
355         }
356     }
357 
358     private fun addInheritedStubsFrom(
359         cls: ClassItem,
360         hiddenSuperClasses: Sequence<ClassItem>,
361         superClasses: Sequence<ClassItem>,
362         filterEmit: Predicate<Item>,
363         filterReference: Predicate<Item>
364     ) {
365         // Also generate stubs for any methods we would have inherited from abstract parents
366         // All methods from super classes that (1) aren't overridden in this class already, and
367         // (2) are overriding some method that is in a public interface accessible from this class.
368         val interfaces: Set<TypeItem> = cls.allInterfaceTypes(filterReference).toSet()
369 
370         // Note that we can't just call method.superMethods() to and see whether any of their
371         // containing classes are among our target APIs because it's possible that the super class
372         // doesn't actually implement the interface, but still provides a matching signature for the
373         // interface. Instead, we'll look through all of our interface methods and look for
374         // potential overrides.
375         val interfaceNames = mutableMapOf<String, MutableList<MethodItem>>()
376         for (interfaceType in interfaces) {
377             val interfaceClass = interfaceType.asClass() ?: continue
378             for (method in interfaceClass.methods()) {
379                 val name = method.name()
380                 val list =
381                     interfaceNames[name]
382                         ?: run {
383                             val list = ArrayList<MethodItem>()
384                             interfaceNames[name] = list
385                             list
386                         }
387                 list.add(method)
388             }
389         }
390 
391         // Also add in any abstract methods from public super classes
392         val publicSuperClasses =
393             superClasses.filter { filterEmit.test(it) && !it.isJavaLangObject() }
394         for (superClass in publicSuperClasses) {
395             for (method in superClass.methods()) {
396                 if (!method.modifiers.isAbstract() || !method.modifiers.isPublicOrProtected()) {
397                     continue
398                 }
399                 val name = method.name()
400                 val list =
401                     interfaceNames[name]
402                         ?: run {
403                             val list = ArrayList<MethodItem>()
404                             interfaceNames[name] = list
405                             list
406                         }
407                 list.add(method)
408             }
409         }
410 
411         // Also add in any concrete public methods from hidden super classes
412         for (superClass in hiddenSuperClasses) {
413             // Determine if there is a non-hidden class between the superClass and this class.
414             // If non-hidden classes are found, don't include the methods for this hiddenSuperClass,
415             // as it will already have been included in a previous super class
416             val includeHiddenSuperClassMethods =
417                 !cls.allSuperClasses()
418                     // Search from this class up to, but not including the superClass.
419                     .takeWhile { currentClass -> currentClass != superClass }
420                     // Find any class that is not hidden.
421                     .any { currentClass -> !hiddenSuperClasses.contains(currentClass) }
422 
423             if (!includeHiddenSuperClassMethods) {
424                 continue
425             }
426 
427             for (method in superClass.methods()) {
428                 if (method.modifiers.isAbstract() || !method.modifiers.isPublic()) {
429                     continue
430                 }
431 
432                 if (method.hasHiddenType(filterReference)) {
433                     continue
434                 }
435 
436                 val name = method.name()
437                 val list =
438                     interfaceNames[name]
439                         ?: run {
440                             val list = ArrayList<MethodItem>()
441                             interfaceNames[name] = list
442                             list
443                         }
444                 list.add(method)
445             }
446         }
447 
448         // Find all methods that are inherited from these classes into our class
449         // (making sure that we don't have duplicates, e.g. a method defined by one
450         // inherited class and then overridden by another closer one).
451         // map from method name to super methods overriding our interfaces
452         val map = HashMap<String, MutableList<MethodItem>>()
453 
454         for (superClass in hiddenSuperClasses) {
455             for (method in superClass.methods()) {
456                 val modifiers = method.modifiers
457                 if (!modifiers.isPrivate() && !modifiers.isAbstract()) {
458                     val name = method.name()
459                     val candidates = interfaceNames[name] ?: continue
460                     val parameterCount = method.parameters().size
461                     for (superMethod in candidates) {
462                         if (parameterCount != superMethod.parameters().count()) {
463                             continue
464                         }
465                         if (method.matches(superMethod)) {
466                             val list =
467                                 map[name]
468                                     ?: run {
469                                         val newList = ArrayList<MethodItem>()
470                                         map[name] = newList
471                                         newList
472                                     }
473                             list.add(method)
474                             break
475                         }
476                     }
477                 }
478             }
479         }
480 
481         // Remove any methods that are overriding any of our existing methods
482         for (method in cls.methods()) {
483             val name = method.name()
484             val candidates = map[name] ?: continue
485             val iterator = candidates.listIterator()
486             while (iterator.hasNext()) {
487                 val inheritedMethod = iterator.next()
488                 if (method.matches(inheritedMethod)) {
489                     iterator.remove()
490                 }
491             }
492         }
493 
494         // Next remove any overrides among the remaining super methods (e.g. one method from a
495         // hidden parent is
496         // overriding another method from a more distant hidden parent).
497         map.values.forEach { methods ->
498             if (methods.size >= 2) {
499                 for (candidate in ArrayList(methods)) {
500                     for (superMethod in candidate.allSuperMethods()) {
501                         methods.remove(superMethod)
502                     }
503                 }
504             }
505         }
506 
507         val existingMethodMap = HashMap<String, MutableList<MethodItem>>()
508         for (method in cls.methods()) {
509             val name = method.name()
510             val list =
511                 existingMethodMap[name]
512                     ?: run {
513                         val newList = ArrayList<MethodItem>()
514                         existingMethodMap[name] = newList
515                         newList
516                     }
517             list.add(method)
518         }
519 
520         // We're now left with concrete methods in hidden parents that are implementing methods in
521         // public
522         // interfaces that are listed in this class. Create stubs for them:
523         map.values.flatten().forEach {
524             val method = cls.inheritMethodFromNonApiAncestor(it)
525             /* Insert comment marker: This is useful for debugging purposes but doesn't
526                belong in the stub
527             method.documentation = "// Inlined stub from hidden parent class ${it.containingClass().qualifiedName()}\n" +
528                     method.documentation
529              */
530 
531             val name = method.name()
532             val candidates = existingMethodMap[name]
533             if (candidates != null) {
534                 val iterator = candidates.listIterator()
535                 while (iterator.hasNext()) {
536                     val inheritedMethod = iterator.next()
537                     if (method.matches(inheritedMethod)) {
538                         // If we already have an override of this method, do not add it to the
539                         // methods list
540                         return@forEach
541                     }
542                 }
543             }
544 
545             cls.addMethod(method)
546         }
547     }
548 
549     /** Apply package filters listed in [Options.skipEmitPackages] */
550     private fun skipEmitPackages() {
551         for (pkgName in config.skipEmitPackages) {
552             val pkg = codebase.findPackage(pkgName) ?: continue
553             pkg.emit = false
554         }
555     }
556 
557     /** If a file facade class has no public members, don't add it to the api */
558     private fun hideEmptyKotlinFileFacadeClasses() {
559         codebase.getPackages().allClasses().forEach { cls ->
560             val psi = (cls as? PsiClassItem)?.psi()
561             if (
562                 psi != null &&
563                     psi.isKotlin() &&
564                     psi is UClass &&
565                     psi.javaPsi is KtLightClassForFacade &&
566                     // a facade class needs to be emitted if it has any top-level fun/prop to emit
567                     cls.members().none { member ->
568                         // a member needs to be emitted if
569                         //  1) it doesn't have a hide annotation;
570                         //  2) it is either public or has a show annotation;
571                         //  3) it is not `expect`
572                         !member.hasHideAnnotation() &&
573                             (member.isPublic || member.hasShowAnnotation()) &&
574                             !member.modifiers.isExpect()
575                     }
576             ) {
577                 cls.emit = false
578             }
579         }
580     }
581 
582     /**
583      * Merge in external qualifier annotations (i.e. ones intended to be included in the API written
584      * from all configured sources).
585      */
586     fun mergeExternalQualifierAnnotations() {
587         val mergeQualifierAnnotations = config.mergeQualifierAnnotations
588         if (mergeQualifierAnnotations.isNotEmpty()) {
589             AnnotationsMerger(sourceParser, codebase, reporter)
590                 .mergeQualifierAnnotations(mergeQualifierAnnotations)
591         }
592     }
593 
594     /** Merge in external show/hide annotations from all configured sources */
595     fun mergeExternalInclusionAnnotations() {
596         val mergeInclusionAnnotations = config.mergeInclusionAnnotations
597         if (mergeInclusionAnnotations.isNotEmpty()) {
598             AnnotationsMerger(sourceParser, codebase, reporter)
599                 .mergeInclusionAnnotations(mergeInclusionAnnotations)
600         }
601     }
602 
603     /**
604      * Propagate the hidden flag down into individual elements -- if a class is hidden, then the
605      * methods and fields are hidden etc
606      */
607     private fun propagateHiddenRemovedAndDocOnly() {
608         packages.accept(
609             object : BaseItemVisitor(visitConstructorsAsMethods = true, nestInnerClasses = true) {
610                 override fun visitPackage(pkg: PackageItem) {
611                     when {
612                         config.hidePackages.contains(pkg.qualifiedName()) -> pkg.hidden = true
613                         else -> {
614                             val showability = pkg.showability
615                             when {
616                                 showability.show() -> pkg.hidden = false
617                                 showability.hide() -> pkg.hidden = true
618                             }
619                         }
620                     }
621                     val containingPackage = pkg.containingPackage()
622                     if (containingPackage != null) {
623                         if (containingPackage.hidden && !containingPackage.isDefault) {
624                             pkg.hidden = true
625                         }
626                         if (containingPackage.docOnly) {
627                             pkg.docOnly = true
628                         }
629                     }
630                 }
631 
632                 override fun visitClass(cls: ClassItem) {
633                     val containingClass = cls.containingClass()
634                     val showability = cls.showability
635                     if (showability.show()) {
636                         cls.hidden = false
637                         // Make containing package non-hidden if it contains a show-annotation
638                         // class. Doclava does this in PackageInfo.isHidden().
639                         cls.containingPackage().hidden = false
640                         if (containingClass != null) {
641                             ensureParentVisible(cls)
642                         }
643                     } else if (showability.hide()) {
644                         cls.hidden = true
645                     } else if (containingClass != null) {
646                         if (containingClass.hidden) {
647                             cls.hidden = true
648                         } else if (
649                             containingClass.originallyHidden &&
650                                 containingClass.hasShowSingleAnnotation()
651                         ) {
652                             // See explanation in visitMethod
653                             cls.hidden = true
654                         }
655                         if (containingClass.docOnly) {
656                             cls.docOnly = true
657                         }
658                         if (containingClass.removed) {
659                             cls.removed = true
660                         }
661                     } else {
662                         val containingPackage = cls.containingPackage()
663                         if (containingPackage.hidden && !containingPackage.isDefault) {
664                             cls.hidden = true
665                         } else if (containingPackage.originallyHidden) {
666                             // Package was marked hidden; it's been unhidden by some other
667                             // classes (marked with show annotations) but this class
668                             // should continue to default.
669                             cls.hidden = true
670                         }
671                         if (containingPackage.docOnly && !containingPackage.isDefault) {
672                             cls.docOnly = true
673                         }
674                         if (containingPackage.removed && !showability.show()) {
675                             cls.removed = true
676                         }
677                     }
678                 }
679 
680                 override fun visitMethod(method: MethodItem) {
681                     val showability = method.showability
682                     if (showability.show()) {
683                         method.hidden = false
684                         ensureParentVisible(method)
685                     } else if (showability.hide()) {
686                         method.hidden = true
687                     } else {
688                         val containingClass = method.containingClass()
689                         if (containingClass.hidden) {
690                             method.hidden = true
691                         } else if (
692                             containingClass.originallyHidden &&
693                                 containingClass.hasShowSingleAnnotation()
694                         ) {
695                             // This is a member in a class that was hidden but then unhidden;
696                             // but it was unhidden by a non-recursive (single) show annotation, so
697                             // don't inherit the show annotation into this item.
698                             method.hidden = true
699                         }
700                         if (containingClass.docOnly) {
701                             method.docOnly = true
702                         }
703                         if (containingClass.removed) {
704                             method.removed = true
705                         }
706                     }
707                 }
708 
709                 override fun visitField(field: FieldItem) {
710                     val showability = field.showability
711                     if (showability.show()) {
712                         field.hidden = false
713                         ensureParentVisible(field)
714                     } else if (showability.hide()) {
715                         field.hidden = true
716                     } else {
717                         val containingClass = field.containingClass()
718                         if (
719                             containingClass.originallyHidden &&
720                                 containingClass.hasShowSingleAnnotation()
721                         ) {
722                             // See explanation in visitMethod
723                             field.hidden = true
724                         }
725                         if (containingClass.docOnly) {
726                             field.docOnly = true
727                         }
728                         if (containingClass.removed) {
729                             field.removed = true
730                         }
731                     }
732                 }
733 
734                 private fun ensureParentVisible(item: Item) {
735                     val parent = item.parent() ?: return
736                     if (!parent.hidden) {
737                         return
738                     }
739                     item.modifiers.findAnnotation(AnnotationItem::isShowAnnotation)?.let {
740                         violatingAnnotation ->
741                         reporter.report(
742                             Issues.SHOWING_MEMBER_IN_HIDDEN_CLASS,
743                             item,
744                             "Attempting to unhide ${item.describe()}, but surrounding ${parent.describe()} is " +
745                                 "hidden and should also be annotated with $violatingAnnotation"
746                         )
747                     }
748                 }
749             }
750         )
751     }
752 
753     private fun checkSystemPermissions(method: MethodItem) {
754         if (
755             method.isImplicitConstructor()
756         ) { // Don't warn on non-source elements like implicit default constructors
757             return
758         }
759 
760         val annotation = method.modifiers.findAnnotation(ANDROID_REQUIRES_PERMISSION)
761         var hasAnnotation = false
762 
763         if (annotation != null) {
764             hasAnnotation = true
765             for (attribute in annotation.attributes) {
766                 var values: List<AnnotationAttributeValue>? = null
767                 var any = false
768                 when (attribute.name) {
769                     "value",
770                     "allOf" -> {
771                         values = attribute.leafValues()
772                     }
773                     "anyOf" -> {
774                         any = true
775                         values = attribute.leafValues()
776                     }
777                 }
778 
779                 values ?: continue
780 
781                 val system = ArrayList<String>()
782                 val nonSystem = ArrayList<String>()
783                 val missing = ArrayList<String>()
784                 for (value in values) {
785                     val perm = (value.value() ?: value.toSource()).toString()
786                     val level = config.manifest.getPermissionLevel(perm)
787                     if (level == null) {
788                         if (any) {
789                             missing.add(perm)
790                             continue
791                         }
792 
793                         reporter.report(
794                             Issues.REQUIRES_PERMISSION,
795                             method,
796                             "Permission '$perm' is not defined by manifest ${config.manifest}."
797                         )
798                         continue
799                     }
800                     if (
801                         level.contains("normal") ||
802                             level.contains("dangerous") ||
803                             level.contains("ephemeral")
804                     ) {
805                         nonSystem.add(perm)
806                     } else {
807                         system.add(perm)
808                     }
809                 }
810                 if (any && missing.size == values.size) {
811                     reporter.report(
812                         Issues.REQUIRES_PERMISSION,
813                         method,
814                         "None of the permissions ${missing.joinToString()} are defined by manifest " +
815                             "${config.manifest}."
816                     )
817                 }
818 
819                 if (system.isEmpty() && nonSystem.isEmpty()) {
820                     hasAnnotation = false
821                 } else if (any && nonSystem.isNotEmpty() || !any && system.isEmpty()) {
822                     reporter.report(
823                         Issues.REQUIRES_PERMISSION,
824                         method,
825                         "Method '" +
826                             method.name() +
827                             "' must be protected with a system permission; it currently" +
828                             " allows non-system callers holding " +
829                             nonSystem.toString()
830                     )
831                 }
832             }
833         }
834 
835         if (!hasAnnotation) {
836             reporter.report(
837                 Issues.REQUIRES_PERMISSION,
838                 method,
839                 "Method '" + method.name() + "' must be protected with a system permission."
840             )
841         }
842     }
843 
844     fun performChecks() {
845         if (codebase.trustedApi()) {
846             // The codebase is already an API; no consistency checks to be performed
847             return
848         }
849 
850         val checkSystemApi =
851             !reporter.isSuppressed(Issues.REQUIRES_PERMISSION) &&
852                 config.allShowAnnotations.matches(ANDROID_SYSTEM_API) &&
853                 !config.manifest.isEmpty()
854         val checkHiddenShowAnnotations =
855             !reporter.isSuppressed(Issues.UNHIDDEN_SYSTEM_API) &&
856                 config.allShowAnnotations.isNotEmpty()
857 
858         packages.accept(
859             object :
860                 ApiVisitor(
861                     config = @Suppress("DEPRECATION") options.apiVisitorConfig,
862                 ) {
863                 override fun visitParameter(parameter: ParameterItem) {
864                     checkTypeReferencesHidden(parameter, parameter.type())
865                 }
866 
867                 override fun visitItem(item: Item) {
868                     // None of the checks in this apply to [ParameterItem]. The deprecation checks
869                     // do not apply as there is no way to provide an `@deprecation` tag in Javadoc
870                     // for parameters. The unhidden showability annotation check
871                     // ('UnhiddemSystemApi`) does not apply as you cannot annotation a
872                     // [ParameterItem] with a showability annotation.
873                     if (item is ParameterItem) return
874 
875                     if (
876                         item.originallyDeprecated &&
877                             !item.documentationContainsDeprecated() &&
878                             // Don't warn about this in Kotlin; the Kotlin deprecation annotation
879                             // includes deprecation
880                             // messages (unlike java.lang.Deprecated which has no attributes).
881                             // Instead, these
882                             // are added to the documentation by the [DocAnalyzer].
883                             !item.isKotlin() &&
884                             // @DeprecatedForSdk will show up as an alias for @Deprecated, but it's
885                             // correct
886                             // and expected to *not* combine this with @deprecated in the text;
887                             // here,
888                             // the text comes from an annotation attribute.
889                             item.modifiers.isAnnotatedWith(JAVA_LANG_DEPRECATED)
890                     ) {
891                         reporter.report(
892                             Issues.DEPRECATION_MISMATCH,
893                             item,
894                             "${item.toString().capitalize()}: @Deprecated annotation (present) and @deprecated doc tag (not present) do not match"
895                         )
896                         // TODO: Check opposite (doc tag but no annotation)
897                     } else {
898                         val deprecatedForSdk =
899                             item.modifiers.findAnnotation(ANDROID_DEPRECATED_FOR_SDK)
900                         if (deprecatedForSdk != null) {
901                             if (item.documentation.contains("@deprecated")) {
902                                 reporter.report(
903                                     Issues.DEPRECATION_MISMATCH,
904                                     item,
905                                     "${item.toString().capitalize()}: Documentation contains `@deprecated` which implies this API is fully deprecated, not just @DeprecatedForSdk"
906                                 )
907                             } else {
908                                 val value = deprecatedForSdk.findAttribute(ANNOTATION_ATTR_VALUE)
909                                 val message = value?.value?.value()?.toString() ?: ""
910                                 item.appendDocumentation(message, "@deprecated")
911                             }
912                         }
913                     }
914 
915                     if (
916                         checkHiddenShowAnnotations &&
917                             item.hasShowAnnotation() &&
918                             !item.originallyHidden &&
919                             !item.hasShowSingleAnnotation()
920                     ) {
921                         item.modifiers
922                             .annotations()
923                             // Find the first show annotation. Just because item.hasShowAnnotation()
924                             // is true does not mean that there must be one show annotation as a
925                             // revert annotation could be treated as a show annotation on one item
926                             // and a hide annotation on another but is neither a show or hide
927                             // annotation.
928                             .firstOrNull(AnnotationItem::isShowAnnotation)
929                             // All show annotations must have a non-null string otherwise they
930                             // would not have been matched.
931                             ?.qualifiedName
932                             ?.removePrefix(ANDROID_ANNOTATION_PREFIX)
933                             ?.let { annotationName ->
934                                 reporter.report(
935                                     Issues.UNHIDDEN_SYSTEM_API,
936                                     item,
937                                     "@$annotationName APIs must also be marked @hide: ${item.describe()}"
938                                 )
939                             }
940                     }
941                 }
942 
943                 override fun visitClass(cls: ClassItem) {
944                     if (checkSystemApi) {
945                         // Look for Android @SystemApi exposed outside the normal SDK; we require
946                         // that they're protected with a system permission.
947                         // Also flag @SystemApi apis not annotated with @hide.
948 
949                         // This class is a system service if it's annotated with @SystemService,
950                         // or if it's android.content.pm.PackageManager
951                         if (
952                             cls.modifiers.isAnnotatedWith("android.annotation.SystemService") ||
953                                 cls.qualifiedName() == "android.content.pm.PackageManager"
954                         ) {
955                             // Check permissions on system services
956                             for (method in cls.filteredMethods(filterEmit)) {
957                                 checkSystemPermissions(method)
958                             }
959                         }
960                     }
961                 }
962 
963                 override fun visitField(field: FieldItem) {
964                     checkTypeReferencesHidden(field, field.type())
965                 }
966 
967                 override fun visitProperty(property: PropertyItem) {
968                     checkTypeReferencesHidden(property, property.type())
969                 }
970 
971                 override fun visitMethod(method: MethodItem) {
972                     if (!method.isConstructor()) {
973                         checkTypeReferencesHidden(
974                             method,
975                             method.returnType()
976                         ) // returnType is nullable only for constructors
977                     }
978 
979                     // Make sure we don't annotate findViewById & getSystemService as @Nullable.
980                     // See for example b/68914170.
981                     val name = method.name()
982                     if (
983                         (name == "findViewById" || name == "getSystemService") &&
984                             method.parameters().size == 1 &&
985                             method.returnType().modifiers.isNullable
986                     ) {
987                         reporter.report(
988                             Issues.EXPECTED_PLATFORM_TYPE,
989                             method,
990                             "$method should not be annotated @Nullable; it should be left unspecified to make it a platform type"
991                         )
992                         val annotation = method.modifiers.findAnnotation(AnnotationItem::isNullable)
993                         annotation?.let { method.mutableModifiers().removeAnnotation(it) }
994                         // Have to also clear the annotation out of the return type itself, if it's
995                         // a type use annotation
996                         val typeAnnotation =
997                             method.returnType().modifiers.annotations().singleOrNull {
998                                 it.isNullnessAnnotation()
999                             }
1000                         typeAnnotation?.let { method.returnType().modifiers.removeAnnotation(it) }
1001                     }
1002                 }
1003 
1004                 /** Check that the type doesn't refer to any hidden classes. */
1005                 private fun checkTypeReferencesHidden(item: Item, type: TypeItem) {
1006                     type.accept(
1007                         object : BaseTypeVisitor() {
1008                             override fun visitClassType(classType: ClassTypeItem) {
1009                                 val cls = classType.asClass() ?: return
1010                                 if (!filterReference.test(cls) && !cls.isFromClassPath()) {
1011                                     reporter.report(
1012                                         Issues.HIDDEN_TYPE_PARAMETER,
1013                                         item,
1014                                         "${item.toString().capitalize()} references hidden type $classType."
1015                                     )
1016                                 }
1017                             }
1018                         }
1019                     )
1020                 }
1021             }
1022         )
1023     }
1024 
1025     // TODO: Switch to visitor iteration
1026     fun handleStripping() {
1027         val notStrippable = HashSet<ClassItem>(5000)
1028 
1029         val filter = ApiPredicate(config = config.apiPredicateConfig.copy(ignoreShown = true))
1030 
1031         // If a class is public or protected, not hidden, not imported and marked as included,
1032         // then we can't strip it
1033         val allTopLevelClasses = codebase.getPackages().allTopLevelClasses().toList()
1034         allTopLevelClasses
1035             .filter { it.isApiCandidate() && it.emit && !it.hidden() }
1036             .forEach { cantStripThis(it, filter, notStrippable, it, "self") }
1037 
1038         // complain about anything that looks includeable but is not supposed to
1039         // be written, e.g. hidden things
1040         for (cl in notStrippable) {
1041             if (!cl.isHiddenOrRemoved()) {
1042                 val publiclyConstructable =
1043                     !cl.modifiers.isSealed() && cl.constructors().any { it.isApiCandidate() }
1044                 for (m in cl.methods()) {
1045                     if (!m.isApiCandidate()) {
1046                         if (publiclyConstructable && m.modifiers.isAbstract()) {
1047                             reporter.report(
1048                                 Issues.HIDDEN_ABSTRACT_METHOD,
1049                                 m,
1050                                 "${m.name()} cannot be hidden and abstract when " +
1051                                     "${cl.simpleName()} has a visible constructor, in case a " +
1052                                     "third-party attempts to subclass it."
1053                             )
1054                         }
1055                         continue
1056                     }
1057                     if (m.isHiddenOrRemoved()) {
1058                         reporter.report(
1059                             Issues.UNAVAILABLE_SYMBOL,
1060                             m,
1061                             "Reference to unavailable method " + m.name()
1062                         )
1063                     } else if (m.originallyDeprecated) {
1064                         // don't bother reporting deprecated methods unless they are public and
1065                         // explicitly marked as deprecated.
1066                         reporter.report(
1067                             Issues.DEPRECATED,
1068                             m,
1069                             "Method " + cl.qualifiedName() + "." + m.name() + " is deprecated"
1070                         )
1071                     }
1072 
1073                     checkTypeReferencesHiddenOrDeprecated(m.returnType(), m, cl, "Return type")
1074                     for (p in m.parameters()) {
1075                         checkTypeReferencesHiddenOrDeprecated(p.type(), m, cl, "Parameter")
1076                     }
1077                 }
1078 
1079                 if (!cl.effectivelyDeprecated) {
1080                     val s = cl.superClass()
1081                     if (s?.effectivelyDeprecated == true) {
1082                         reporter.report(
1083                             Issues.EXTENDS_DEPRECATED,
1084                             cl,
1085                             "Extending deprecated super class $s from ${cl.qualifiedName()}: this class should also be deprecated"
1086                         )
1087                     }
1088 
1089                     for (t in cl.interfaceTypes()) {
1090                         if (t.asClass()?.effectivelyDeprecated == true) {
1091                             reporter.report(
1092                                 Issues.EXTENDS_DEPRECATED,
1093                                 cl,
1094                                 "Implementing interface of deprecated type $t in ${cl.qualifiedName()}: this class should also be deprecated"
1095                             )
1096                         }
1097                     }
1098                 }
1099             } else if (cl.originallyDeprecated) {
1100                 // not hidden, but deprecated
1101                 reporter.report(Issues.DEPRECATED, cl, "Class ${cl.qualifiedName()} is deprecated")
1102             }
1103         }
1104     }
1105 
1106     private fun cantStripThis(
1107         cl: ClassItem,
1108         filter: Predicate<Item>,
1109         notStrippable: MutableSet<ClassItem>,
1110         from: Item,
1111         usage: String
1112     ) {
1113         if (config.stubImportPackages.contains(cl.containingPackage().qualifiedName())) {
1114             // if the package is imported then it does not need stubbing.
1115             return
1116         }
1117 
1118         if (cl.isFromClassPath()) {
1119             return
1120         }
1121 
1122         if (cl.isHiddenOrRemoved() || cl.isPackagePrivate && !cl.isApiCandidate()) {
1123             reporter.report(
1124                 Issues.REFERENCES_HIDDEN,
1125                 from,
1126                 "Class ${cl.qualifiedName()} is ${if (cl.isHiddenOrRemoved()) "hidden" else "not public"} but was referenced ($usage) from public ${from.describe(
1127                     false
1128                 )}"
1129             )
1130         }
1131 
1132         if (!notStrippable.add(cl)) {
1133             // slight optimization: if it already contains cl, it already contains
1134             // all of cl's parents
1135             return
1136         }
1137 
1138         // cant strip any public fields or their generics
1139         for (field in cl.fields()) {
1140             if (!filter.test(field)) {
1141                 continue
1142             }
1143             cantStripThis(field.type(), field, filter, notStrippable, "in field type")
1144         }
1145         // cant strip any of the type's generics
1146         cantStripThis(cl.typeParameterList, filter, notStrippable, cl)
1147         // cant strip any of the annotation elements
1148         // cantStripThis(cl.annotationElements(), notStrippable);
1149         // take care of methods
1150         cantStripThis(cl.methods(), filter, notStrippable)
1151         cantStripThis(cl.constructors(), filter, notStrippable)
1152         // blow the outer class open if this is an inner class
1153         val containingClass = cl.containingClass()
1154         if (containingClass != null) {
1155             cantStripThis(containingClass, filter, notStrippable, cl, "as containing class")
1156         }
1157         // all visible inner classes will be included in stubs
1158         cl.innerClasses()
1159             .filter { it.isApiCandidate() }
1160             .forEach { cantStripThis(it, filter, notStrippable, cl, "as inner class") }
1161         // blow open super class and interfaces
1162         // TODO: Consider using val superClass = cl.filteredSuperclass(filter)
1163         val superItems = cl.allInterfaces().toMutableSet()
1164         cl.superClass()?.let { superClass -> superItems.add(superClass) }
1165 
1166         for (superItem in superItems) {
1167             // allInterfaces includes cl itself if cl is an interface
1168             if (superItem.isHiddenOrRemoved() && superItem != cl) {
1169                 // cl is a public class declared as extending a hidden superclass.
1170                 // this is not a desired practice, but it's happened, so we deal
1171                 // with it by finding the first super class which passes checkLevel for purposes of
1172                 // generating the doc & stub information, and proceeding normally.
1173                 if (!superItem.isFromClassPath()) {
1174                     reporter.report(
1175                         Issues.HIDDEN_SUPERCLASS,
1176                         cl,
1177                         "Public class " +
1178                             cl.qualifiedName() +
1179                             " stripped of unavailable superclass " +
1180                             superItem.qualifiedName()
1181                     )
1182                 }
1183             } else {
1184                 // doclava would also mark the package private super classes as unhidden, but that's
1185                 // not
1186                 // right (this was just done for its stub handling)
1187                 //   cantStripThis(superClass, filter, notStrippable, stubImportPackages, cl, "as
1188                 // super class")
1189 
1190                 if (superItem.isPrivate && !superItem.isFromClassPath()) {
1191                     reporter.report(
1192                         Issues.PRIVATE_SUPERCLASS,
1193                         cl,
1194                         "Public class " +
1195                             cl.qualifiedName() +
1196                             " extends private class " +
1197                             superItem.qualifiedName()
1198                     )
1199                 }
1200             }
1201         }
1202     }
1203 
1204     private fun cantStripThis(
1205         methods: List<MethodItem>,
1206         filter: Predicate<Item>,
1207         notStrippable: MutableSet<ClassItem>,
1208     ) {
1209         // for each method, blow open the parameters, throws and return types. also blow open their
1210         // generics
1211         for (method in methods) {
1212             if (!filter.test(method)) {
1213                 continue
1214             }
1215             cantStripThis(method.typeParameterList, filter, notStrippable, method)
1216             for (parameter in method.parameters()) {
1217                 cantStripThis(
1218                     parameter.type(),
1219                     parameter,
1220                     filter,
1221                     notStrippable,
1222                     "in parameter type"
1223                 )
1224             }
1225             for (thrown in method.throwsTypes()) {
1226                 if (thrown is VariableTypeItem) continue
1227                 val classItem = thrown.erasedClass ?: continue
1228                 cantStripThis(classItem, filter, notStrippable, method, "as exception")
1229             }
1230             cantStripThis(method.returnType(), method, filter, notStrippable, "in return type")
1231         }
1232     }
1233 
1234     private fun cantStripThis(
1235         typeParameterList: TypeParameterList,
1236         filter: Predicate<Item>,
1237         notStrippable: MutableSet<ClassItem>,
1238         context: Item
1239     ) {
1240         for (typeParameter in typeParameterList) {
1241             for (bound in typeParameter.typeBounds()) {
1242                 cantStripThis(bound, context, filter, notStrippable, "as type parameter")
1243             }
1244         }
1245     }
1246 
1247     private fun cantStripThis(
1248         type: TypeItem,
1249         context: Item,
1250         filter: Predicate<Item>,
1251         notStrippable: MutableSet<ClassItem>,
1252         usage: String,
1253     ) {
1254         type.accept(
1255             object : BaseTypeVisitor() {
1256                 override fun visitClassType(classType: ClassTypeItem) {
1257                     val asClass = classType.asClass() ?: return
1258                     cantStripThis(asClass, filter, notStrippable, context, usage)
1259                 }
1260             }
1261         )
1262     }
1263 
1264     /**
1265      * Checks if the type (method parameter or return type) references a hidden or deprecated class.
1266      */
1267     private fun checkTypeReferencesHiddenOrDeprecated(
1268         type: TypeItem,
1269         containingMethod: MethodItem,
1270         containingClass: ClassItem,
1271         usage: String
1272     ) {
1273         if (!containingMethod.effectivelyDeprecated) {
1274             type.accept(
1275                 object : BaseTypeVisitor() {
1276                     override fun visitClassType(classType: ClassTypeItem) {
1277                         if (classType.asClass()?.effectivelyDeprecated == true) {
1278                             reporter.report(
1279                                 Issues.REFERENCES_DEPRECATED,
1280                                 containingMethod,
1281                                 "$usage references deprecated type $classType in ${containingClass.qualifiedName()}.${containingMethod.name()}(): this method should also be deprecated"
1282                             )
1283                         }
1284                     }
1285                 }
1286             )
1287         }
1288 
1289         val hiddenClasses = findHiddenClasses(type)
1290         val typeClassName = (type as? ClassTypeItem)?.qualifiedName
1291         for (hiddenClass in hiddenClasses) {
1292             if (hiddenClass.isFromClassPath()) continue
1293             if (hiddenClass.qualifiedName() == typeClassName) {
1294                 // The type itself is hidden
1295                 reporter.report(
1296                     Issues.UNAVAILABLE_SYMBOL,
1297                     containingMethod,
1298                     "$usage of unavailable type $type in ${containingClass.qualifiedName()}.${containingMethod.name()}()"
1299                 )
1300             } else {
1301                 // The type contains a hidden type
1302                 reporter.report(
1303                     Issues.HIDDEN_TYPE_PARAMETER,
1304                     containingMethod,
1305                     "$usage uses type parameter of unavailable type $type in ${containingClass.qualifiedName()}.${containingMethod.name()}()"
1306                 )
1307             }
1308         }
1309     }
1310 
1311     /**
1312      * Find references to hidden classes.
1313      *
1314      * This finds hidden classes that are used by public parts of the API in order to ensure the API
1315      * is self-consistent and does not reference classes that are not included in the stubs. Any
1316      * such references cause an error to be reported.
1317      *
1318      * A reference to an imported class is not treated as an error, even though imported classes are
1319      * hidden from the stub generation. That is because imported classes are, by definition,
1320      * excluded from the set of classes for which stubs are required.
1321      *
1322      * @param ti the type information to examine for references to hidden classes.
1323      * @return all references to hidden classes referenced by the type
1324      */
1325     private fun findHiddenClasses(ti: TypeItem): Set<ClassItem> {
1326         val hiddenClasses = mutableSetOf<ClassItem>()
1327         ti.accept(
1328             object : BaseTypeVisitor() {
1329                 override fun visitClassType(classType: ClassTypeItem) {
1330                     val asClass = classType.asClass() ?: return
1331                     if (
1332                         config.stubImportPackages.contains(
1333                             asClass.containingPackage().qualifiedName()
1334                         )
1335                     ) {
1336                         return
1337                     }
1338                     if (asClass.isHiddenOrRemoved()) {
1339                         hiddenClasses.add(asClass)
1340                     }
1341                 }
1342             }
1343         )
1344         return hiddenClasses
1345     }
1346 }
1347 
Stringnull1348 private fun String.capitalize(): String {
1349     return this.replaceFirstChar {
1350         if (it.isLowerCase()) {
1351             it.titlecase(Locale.getDefault())
1352         } else {
1353             it.toString()
1354         }
1355     }
1356 }
1357 
1358 /** Returns true if this item is public or protected and so a candidate for inclusion in an API. */
Itemnull1359 private fun Item.isApiCandidate(): Boolean {
1360     return !isHiddenOrRemoved() && (modifiers.isPublic() || modifiers.isProtected())
1361 }
1362 
1363 /**
1364  * Whether documentation for the [Item] has the `@deprecated` tag -- for inherited methods, this
1365  * also looks at any inherited documentation.
1366  */
Itemnull1367 private fun Item.documentationContainsDeprecated(): Boolean {
1368     if (documentation.contains("@deprecated")) return true
1369     if (this is MethodItem && (documentation == "" || documentation.contains("@inheritDoc"))) {
1370         return superMethods().any { it.documentationContainsDeprecated() }
1371     }
1372     return false
1373 }
1374