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