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