• 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.model.psi
18 
19 import com.android.SdkConstants
20 import com.android.tools.lint.UastEnvironment
21 import com.android.tools.metalava.ANDROIDX_NONNULL
22 import com.android.tools.metalava.ANDROIDX_NULLABLE
23 import com.android.tools.metalava.Issues
24 import com.android.tools.metalava.model.ClassItem
25 import com.android.tools.metalava.model.DefaultCodebase
26 import com.android.tools.metalava.model.FieldItem
27 import com.android.tools.metalava.model.Item
28 import com.android.tools.metalava.model.MethodItem
29 import com.android.tools.metalava.model.PackageDocs
30 import com.android.tools.metalava.model.PackageItem
31 import com.android.tools.metalava.model.PackageList
32 import com.android.tools.metalava.options
33 import com.android.tools.metalava.reporter
34 import com.android.tools.metalava.tick
35 import com.intellij.openapi.project.Project
36 import com.intellij.psi.JavaPsiFacade
37 import com.intellij.psi.JavaRecursiveElementVisitor
38 import com.intellij.psi.PsiAnnotation
39 import com.intellij.psi.PsiArrayType
40 import com.intellij.psi.PsiClass
41 import com.intellij.psi.PsiClassOwner
42 import com.intellij.psi.PsiClassType
43 import com.intellij.psi.PsiCodeBlock
44 import com.intellij.psi.PsiElement
45 import com.intellij.psi.PsiErrorElement
46 import com.intellij.psi.PsiField
47 import com.intellij.psi.PsiFile
48 import com.intellij.psi.PsiImportStatement
49 import com.intellij.psi.PsiJavaCodeReferenceElement
50 import com.intellij.psi.PsiJavaFile
51 import com.intellij.psi.PsiMethod
52 import com.intellij.psi.PsiPackage
53 import com.intellij.psi.PsiSubstitutor
54 import com.intellij.psi.PsiType
55 import com.intellij.psi.TypeAnnotationProvider
56 import com.intellij.psi.javadoc.PsiDocComment
57 import com.intellij.psi.javadoc.PsiDocTag
58 import com.intellij.psi.search.GlobalSearchScope
59 import com.intellij.psi.util.PsiTreeUtil
60 import org.jetbrains.kotlin.psi.KtElement
61 import org.jetbrains.kotlin.resolve.BindingContext
62 import org.jetbrains.uast.UFile
63 import org.jetbrains.uast.UastFacade
64 import org.jetbrains.uast.kotlin.KotlinUastResolveProviderService
65 import java.io.File
66 import java.io.IOException
67 import java.util.zip.ZipFile
68 
69 const val PACKAGE_ESTIMATE = 500
70 const val CLASS_ESTIMATE = 15000
71 const val METHOD_ESTIMATE = 1000
72 
73 /**
74  * A codebase containing Java, Kotlin, or UAST PSI classes
75  *
76  * After creation, a list of PSI file or a JAR file is passed to [initialize]. This creates package
77  * and class items along with their members. This process is broken into two phases:
78  *
79  * First, [initializing] is set to true, and class items are created from the supplied sources.
80  * These are main classes of the codebase and have [ClassItem.emit] set to true and
81  * [ClassItem.isFromClassPath] set to false. While creating these, package names are reserved and
82  * associated with their classes in [packageClasses].
83  *
84  * Next, package items are created for source classes based on the contents of [packageClasses]
85  * with [PackageItem.emit] set to true.
86  *
87  * Then [initializing] is set to false and the second pass begins. This path iteratively resolves
88  * supertypes of class items until all are fully resolved, creating new class and package items as
89  * needed. Since all the source class and package items have been created, new items are assumed to
90  * originate from the classpath and have [Item.emit] set to false and [Item.isFromClassPath] set to
91  * true.
92  */
93 open class PsiBasedCodebase(
94     location: File,
95     override var description: String = "Unknown"
96 ) : DefaultCodebase(location) {
97     lateinit var uastEnvironment: UastEnvironment
98     val project: Project
99         get() = uastEnvironment.ideaProject
100 
101     /** Map from class name to class item. Classes are added via [registerClass] */
102     private val classMap: MutableMap<String, PsiClassItem> = HashMap(CLASS_ESTIMATE)
103 
104     /**
105      * Map from classes to the set of methods for each (but only for classes where we've
106      * called [findMethod]
107      */
108     private lateinit var methodMap: MutableMap<PsiClassItem, MutableMap<PsiMethod, PsiMethodItem>>
109 
110     /** Map from package name to the corresponding package item */
111     private lateinit var packageMap: MutableMap<String, PsiPackageItem>
112 
113     /** Map from package name to list of classes in that package. Only used during [initialize]. */
114     private lateinit var packageClasses: MutableMap<String, MutableList<PsiClassItem>>
115 
116     /** A set of packages to hide */
117     private lateinit var hiddenPackages: MutableMap<String, Boolean?>
118 
119     /**
120      * A list of the top-level classes declared in the codebase's source (rather than on its
121      * classpath).
122      */
123     private lateinit var topLevelClassesFromSource: MutableList<PsiClassItem>
124 
125     /**
126      * Set to true in [initialize] for the first pass of creating class items for all classes in
127      * the codebase sources and false for the second pass of creating class items for the
128      * supertypes of the codebase classes. New class items created in the supertypes pass must come
129      * from the classpath (dependencies) since all source classes have been created.
130      *
131      * This information is used in [createClass] to set [ClassItem.emit] to true for source classes
132      * and [ClassItem.isFromClassPath] to true for classpath classes. It is also used in
133      * [registerPackage] to set [PackageItem.emit] to true for source packages.
134      */
135     private var initializing = false
136 
137     override fun trustedApi(): Boolean = false
138 
139     private var packageDocs: PackageDocs? = null
140 
141     private var hideClassesFromJars = true
142 
143     private lateinit var emptyPackage: PsiPackageItem
144 
145     fun initialize(
146         uastEnvironment: UastEnvironment,
147         psiFiles: List<PsiFile>,
148         packages: PackageDocs,
149     ) {
150         initializing = true
151         this.units = psiFiles
152         packageDocs = packages
153 
154         this.uastEnvironment = uastEnvironment
155         // there are currently ~230 packages in the public SDK, but here we need to account for internal ones too
156         val hiddenPackages: MutableSet<String> = packages.hiddenPackages
157         val packageDocs: MutableMap<String, String> = packages.packageDocs
158         this.hiddenPackages = HashMap(100)
159         for (pkgName in hiddenPackages) {
160             this.hiddenPackages[pkgName] = true
161         }
162 
163         packageMap = HashMap(PACKAGE_ESTIMATE)
164         packageClasses = HashMap(PACKAGE_ESTIMATE)
165         packageClasses[""] = ArrayList()
166         this.methodMap = HashMap(METHOD_ESTIMATE)
167         topLevelClassesFromSource = ArrayList(CLASS_ESTIMATE)
168 
169         // Make sure we only process the files once; sometimes there's overlap in the source lists
170         for (psiFile in psiFiles.asSequence().distinct()) {
171             tick() // show progress
172 
173             // Visiting psiFile directly would eagerly load the entire file even though we only need
174             // the importList here.
175             (psiFile as? PsiJavaFile)?.importList?.accept(object : JavaRecursiveElementVisitor() {
176                 override fun visitImportStatement(element: PsiImportStatement) {
177                     super.visitImportStatement(element)
178                     if (element.resolve() == null) {
179                         reporter.report(
180                             Issues.UNRESOLVED_IMPORT,
181                             element,
182                             "Unresolved import: `${element.qualifiedName}`"
183                         )
184                     }
185                 }
186             })
187 
188             var classes = (psiFile as? PsiClassOwner)?.classes?.toList() ?: emptyList()
189             if (classes.isEmpty()) {
190                 val uFile = UastFacade.convertElementWithParent(psiFile, UFile::class.java) as? UFile?
191                 classes = uFile?.classes?.map { it }?.toList() ?: emptyList()
192             }
193             when {
194                 classes.isEmpty() && psiFile is PsiJavaFile -> {
195                     // package-info.java ?
196                     val packageStatement = psiFile.packageStatement
197                     // Look for javadoc on the package statement; this is NOT handed to us on
198                     // the PsiPackage!
199                     if (packageStatement != null) {
200                         val comment = PsiTreeUtil.getPrevSiblingOfType(
201                             packageStatement,
202                             PsiDocComment::class.java
203                         )
204                         if (comment != null) {
205                             val packageName = packageStatement.packageName
206                             val text = comment.text
207                             if (text.contains("@hide")) {
208                                 this.hiddenPackages[packageName] = true
209                             }
210                             if (packageDocs[packageName] != null) {
211                                 reporter.report(
212                                     Issues.BOTH_PACKAGE_INFO_AND_HTML,
213                                     psiFile,
214                                     "It is illegal to provide both a package-info.java file and " +
215                                         "a package.html file for the same package"
216                                 )
217                             }
218                             packageDocs[packageName] = text
219                         }
220                     }
221                 }
222                 else -> {
223                     for (psiClass in classes) {
224                         psiClass.accept(object : JavaRecursiveElementVisitor() {
225                             override fun visitErrorElement(element: PsiErrorElement) {
226                                 super.visitErrorElement(element)
227                                 reporter.report(
228                                     Issues.INVALID_SYNTAX,
229                                     element,
230                                     "Syntax error: `${element.errorDescription}`"
231                                 )
232                             }
233 
234                             override fun visitCodeBlock(block: PsiCodeBlock?) {
235                                 // Ignore to avoid eagerly parsing all method bodies.
236                             }
237 
238                             override fun visitDocComment(comment: PsiDocComment?) {
239                                 // Ignore to avoid eagerly parsing all doc comments.
240                                 // Doc comments cannot contain error elements.
241                             }
242                         })
243 
244                         topLevelClassesFromSource += createClass(psiClass)
245                     }
246                 }
247             }
248         }
249 
250         // Next construct packages
251         for ((pkgName, classes) in packageClasses) {
252             tick() // show progress
253             val psiPackage = JavaPsiFacade.getInstance(project).findPackage(pkgName)
254             if (psiPackage == null) {
255                 println("Could not find package $pkgName")
256                 continue
257             }
258 
259             val sortedClasses = classes.toMutableList().sortedWith(ClassItem.fullNameComparator)
260             registerPackage(psiPackage, sortedClasses, packageDocs[pkgName], pkgName)
261         }
262         initializing = false
263 
264         emptyPackage = findPackage("")!!
265 
266         // Finish initialization
267         val initialPackages = ArrayList(packageMap.values)
268         var registeredCount = packageMap.size // classes added after this point will have indices >= original
269         for (cls in initialPackages) {
270             cls.finishInitialization()
271         }
272 
273         // Finish initialization of any additional classes that were registered during
274         // the above initialization (recursively)
275         while (registeredCount < packageMap.size) {
276             val added = packageMap.values.minus(initialPackages)
277             registeredCount = packageMap.size
278             for (pkg in added) {
279                 pkg.finishInitialization()
280             }
281         }
282 
283         // Point to "parent" packages, since doclava treats packages as nested (e.g. an @hide on
284         // android.foo will also apply to android.foo.bar)
285         addParentPackages(packageMap.values)
286 
287         packageClasses.clear() // Not used after this point
288     }
289 
290     override fun dispose() {
291         uastEnvironment.dispose()
292         super.dispose()
293     }
294 
295     private fun addParentPackages(packages: Collection<PsiPackageItem>) {
296         val missingPackages = packages.mapNotNull {
297             val name = it.qualifiedName()
298             val index = name.lastIndexOf('.')
299             val parent = if (index != -1) {
300                 name.substring(0, index)
301             } else {
302                 ""
303             }
304             if (packageMap.containsKey(parent)) {
305                 // Already registered
306                 null
307             } else {
308                 parent
309             }
310         }.toSet()
311 
312         // Create PackageItems for any packages that weren't in the source
313         for (pkgName in missingPackages) {
314             val psiPackage = JavaPsiFacade.getInstance(project).findPackage(pkgName) ?: continue
315             val sortedClasses = emptyList<PsiClassItem>()
316             val packageHtml = null
317             registerPackage(psiPackage, sortedClasses, packageHtml, pkgName)
318         }
319 
320         // Connect up all the package items
321         for (pkg in packageMap.values) {
322             var name = pkg.qualifiedName()
323             // Find parent package; we have to loop since we don't always find a PSI package
324             // for intermediate elements; e.g. we may jump from java.lang straight up to the default
325             // package
326             while (name.isNotEmpty()) {
327                 val index = name.lastIndexOf('.')
328                 name = if (index != -1) {
329                     name.substring(0, index)
330                 } else {
331                     ""
332                 }
333                 val parent = findPackage(name) ?: continue
334                 pkg.containingPackageField = parent
335                 break
336             }
337         }
338     }
339 
340     private fun registerPackage(
341         psiPackage: PsiPackage,
342         sortedClasses: List<PsiClassItem>?,
343         packageHtml: String?,
344         pkgName: String
345     ): PsiPackageItem {
346         val packageItem = PsiPackageItem
347             .create(this, psiPackage, packageHtml, fromClassPath = !initializing)
348         packageItem.emit = initializing
349 
350         packageMap[pkgName] = packageItem
351         if (isPackageHidden(pkgName)) {
352             packageItem.hidden = true
353         }
354 
355         sortedClasses?.let { packageItem.addClasses(it) }
356         return packageItem
357     }
358 
359     fun initialize(uastEnvironment: UastEnvironment, jarFile: File, preFiltered: Boolean = false) {
360         this.preFiltered = preFiltered
361         initializing = true
362         hideClassesFromJars = false
363 
364         this.uastEnvironment = uastEnvironment
365 
366         // Find all classes referenced from the class
367         val facade = JavaPsiFacade.getInstance(project)
368         val scope = GlobalSearchScope.allScope(project)
369 
370         hiddenPackages = HashMap(100)
371         packageMap = HashMap(PACKAGE_ESTIMATE)
372         packageClasses = HashMap(PACKAGE_ESTIMATE)
373         packageClasses[""] = ArrayList()
374         this.methodMap = HashMap(1000)
375         val packageToClasses: MutableMap<String, MutableList<PsiClassItem>> = HashMap(
376             PACKAGE_ESTIMATE
377         )
378         packageToClasses[""] = ArrayList() // ensure we construct one for the default package
379 
380         topLevelClassesFromSource = ArrayList(CLASS_ESTIMATE)
381 
382         try {
383             ZipFile(jarFile).use { jar ->
384                 val enumeration = jar.entries()
385                 while (enumeration.hasMoreElements()) {
386                     val entry = enumeration.nextElement()
387                     val fileName = entry.name
388                     if (fileName.contains("$")) {
389                         // skip inner classes
390                         continue
391                     }
392                     if (fileName.endsWith(SdkConstants.DOT_CLASS)) {
393                         val qualifiedName = fileName.removeSuffix(SdkConstants.DOT_CLASS).replace('/', '.')
394                         if (qualifiedName.endsWith(".package-info")) {
395                             // Ensure we register a package for this, even if empty
396                             val packageName = qualifiedName.removeSuffix(".package-info")
397                             var list = packageToClasses[packageName]
398                             if (list == null) {
399                                 list = mutableListOf()
400                                 packageToClasses[packageName] = list
401                             }
402                             continue
403                         } else {
404                             val psiClass = facade.findClass(qualifiedName, scope) ?: continue
405 
406                             val classItem = createClass(psiClass)
407                             topLevelClassesFromSource.add(classItem)
408 
409                             val packageName = getPackageName(psiClass)
410                             var list = packageToClasses[packageName]
411                             if (list == null) {
412                                 list = mutableListOf(classItem)
413                                 packageToClasses[packageName] = list
414                             } else {
415                                 list.add(classItem)
416                             }
417                         }
418                     }
419                 }
420             }
421         } catch (e: IOException) {
422             reporter.report(Issues.IO_ERROR, jarFile, e.message ?: e.toString())
423         }
424 
425         // Next construct packages
426         for ((pkgName, packageClasses) in packageToClasses) {
427             val psiPackage = JavaPsiFacade.getInstance(project).findPackage(pkgName)
428             if (psiPackage == null) {
429                 println("Could not find package $pkgName")
430                 continue
431             }
432 
433             packageClasses.sortWith(ClassItem.fullNameComparator)
434             // TODO: How do we obtain the package docs? We generally don't have them, but it *would* be
435             // nice if we picked up "overview.html" bundled files and added them. But since the docs
436             // are generally missing for all elements *anyway*, let's not bother.
437             val docs = packageDocs?.packageDocs
438             val packageHtml: String? =
439                 if (docs != null) {
440                     docs[pkgName]
441                 } else {
442                     null
443                 }
444             registerPackage(psiPackage, packageClasses, packageHtml, pkgName)
445         }
446 
447         emptyPackage = findPackage("")!!
448 
449         initializing = false
450         hideClassesFromJars = true
451 
452         // Finish initialization
453         for (pkg in packageMap.values) {
454             pkg.finishInitialization()
455         }
456 
457         packageClasses.clear() // Not used after this point
458     }
459 
460     fun dumpStats() {
461         options.stdout.println(
462             "INTERNAL STATS: Size of classMap=${classMap.size} and size of " +
463                 "methodMap=${methodMap.size} and size of packageMap=${packageMap.size}, and the " +
464                 "size of packageClasses=${packageClasses.size} "
465         )
466     }
467 
468     private fun registerPackageClass(packageName: String, cls: PsiClassItem) {
469         var list = packageClasses[packageName]
470         if (list == null) {
471             list = ArrayList()
472             packageClasses[packageName] = list
473         }
474 
475         list.add(cls)
476     }
477 
478     private fun isPackageHidden(packageName: String): Boolean {
479         val hidden = hiddenPackages[packageName]
480         if (hidden == true) {
481             return true
482         } else if (hidden == null) {
483             // Compute for all prefixes of this package
484             var pkg = packageName
485             while (true) {
486                 if (hiddenPackages[pkg] != null) {
487                     hiddenPackages[packageName] = hiddenPackages[pkg]
488                     if (hiddenPackages[pkg] == true) {
489                         return true
490                     }
491                 }
492                 val last = pkg.lastIndexOf('.')
493                 if (last == -1) {
494                     hiddenPackages[packageName] = false
495                     break
496                 } else {
497                     pkg = pkg.substring(0, last)
498                 }
499             }
500         }
501 
502         return false
503     }
504 
505     private fun createClass(clz: PsiClass): PsiClassItem {
506         // If initializing is true, this class is from source
507         val classItem = PsiClassItem.create(this, clz, fromClassPath = !initializing)
508         // Set emit to true for source classes but false for classpath classes
509         classItem.emit = initializing
510 
511         if (!initializing) {
512             // Workaround: we're pulling in .aidl files from .jar files. These are
513             // marked @hide, but since we only see the .class files we don't know that.
514             if (classItem.simpleName().startsWith("I") &&
515                 classItem.isFromClassPath() &&
516                 clz.interfaces.any { it.qualifiedName == "android.os.IInterface" }
517             ) {
518                 classItem.hidden = true
519             }
520         }
521 
522         if (classItem.classType == ClassType.TYPE_PARAMETER) {
523             // Don't put PsiTypeParameter classes into the registry; e.g. when we're visiting
524             //  java.util.stream.Stream<R>
525             // we come across "R" and would try to place it here.
526             classItem.containingPackage = emptyPackage
527             classItem.finishInitialization()
528             return classItem
529         }
530 
531         // TODO: Cache for adjacent files!
532         val packageName = getPackageName(clz)
533         registerPackageClass(packageName, classItem)
534 
535         if (!initializing) {
536             classItem.finishInitialization()
537             val pkgName = getPackageName(clz)
538             val pkg = findPackage(pkgName)
539             if (pkg == null) {
540                 // val packageHtml: String? = packageDocs?.packageDocs!![pkgName]
541                 // dynamically discovered packages should NOT be included
542                 // val packageHtml = "/** @hide */"
543                 val packageHtml = null
544                 val psiPackage = JavaPsiFacade.getInstance(project).findPackage(pkgName)
545                 if (psiPackage != null) {
546                     val packageItem = registerPackage(psiPackage, null, packageHtml, pkgName)
547                     packageItem.addClass(classItem)
548                 }
549             } else {
550                 pkg.addClass(classItem)
551             }
552         }
553 
554         return classItem
555     }
556 
557     override fun getPackages(): PackageList {
558         // TODO: Sorting is probably not necessary here!
559         return PackageList(this, packageMap.values.toMutableList().sortedWith(PackageItem.comparator))
560     }
561 
562     override fun getPackageDocs(): PackageDocs? {
563         return packageDocs
564     }
565 
566     override fun size(): Int {
567         return packageMap.size
568     }
569 
570     override fun findPackage(pkgName: String): PsiPackageItem? {
571         return packageMap[pkgName]
572     }
573 
574     override fun findClass(className: String): PsiClassItem? {
575         return classMap[className]
576     }
577 
578     open fun findClass(psiClass: PsiClass): PsiClassItem? {
579         val qualifiedName: String = psiClass.qualifiedName ?: psiClass.name!!
580         return classMap[qualifiedName]
581     }
582 
583     open fun findOrCreateClass(qualifiedName: String): PsiClassItem? {
584         val finder = JavaPsiFacade.getInstance(project)
585         val psiClass = finder.findClass(qualifiedName, GlobalSearchScope.allScope(project)) ?: return null
586         return findOrCreateClass(psiClass)
587     }
588 
589     open fun findOrCreateClass(psiClass: PsiClass): PsiClassItem {
590         val existing = findClass(psiClass)
591         if (existing != null) {
592             return existing
593         }
594 
595         var curr = psiClass.containingClass
596         if (curr != null && findClass(curr) == null) {
597             // Make sure we construct outer/top level classes first
598             if (findClass(curr) == null) {
599                 while (true) {
600                     val containing = curr?.containingClass
601                     if (containing == null) {
602                         break
603                     } else {
604                         curr = containing
605                     }
606                 }
607                 curr!!
608                 createClass(curr) // this will also create inner classes, which should now be in the map
609                 val inner = findClass(psiClass)
610                 inner!! // should be there now
611                 return inner
612             }
613         }
614 
615         return existing ?: return createClass(psiClass)
616     }
617 
618     fun findClass(psiType: PsiType): PsiClassItem? {
619         if (psiType is PsiClassType) {
620             val cls = psiType.resolve() ?: return null
621             return findOrCreateClass(cls)
622         } else if (psiType is PsiArrayType) {
623             var componentType = psiType.componentType
624             // We repeatedly get the component type because the array may have multiple dimensions
625             while (componentType is PsiArrayType) {
626                 componentType = componentType.componentType
627             }
628             if (componentType is PsiClassType) {
629                 val cls = componentType.resolve() ?: return null
630                 return findOrCreateClass(cls)
631             }
632         }
633         return null
634     }
635 
636     fun getClassType(cls: PsiClass): PsiClassType = getFactory().createType(cls, PsiSubstitutor.EMPTY)
637 
638     fun getComment(string: String, parent: PsiElement? = null): PsiDocComment =
639         getFactory().createDocCommentFromText(string, parent)
640 
641     fun getType(psiType: PsiType): PsiTypeItem {
642         // Note: We do *not* cache these; it turns out that storing PsiType instances
643         // in a map is bad for performance; it has a very expensive equals operation
644         // for some type comparisons (and we sometimes end up with unexpected results,
645         // e.g. where we fetch an "equals" type from the map but its representation
646         // is slightly different than we intended
647         return PsiTypeItem.create(this, psiType)
648     }
649 
650     fun getType(psiClass: PsiClass): PsiTypeItem {
651         return PsiTypeItem.create(this, getFactory().createType(psiClass))
652     }
653 
654     private fun getPackageName(clz: PsiClass): String {
655         var top: PsiClass? = clz
656         while (top?.containingClass != null) {
657             top = top.containingClass
658         }
659         top ?: return ""
660 
661         val name = top.name
662         val fullName = top.qualifiedName ?: return ""
663 
664         if (name == fullName) {
665             return ""
666         }
667 
668         return fullName.substring(0, fullName.length - 1 - name!!.length)
669     }
670 
671     fun findMethod(method: PsiMethod): PsiMethodItem {
672         val containingClass = method.containingClass
673         val cls = findOrCreateClass(containingClass!!)
674 
675         // Ensure initialized/registered via [#registerMethods]
676         if (methodMap[cls] == null) {
677             val map = HashMap<PsiMethod, PsiMethodItem>(40)
678             registerMethods(cls.methods(), map)
679             registerMethods(cls.constructors(), map)
680             methodMap[cls] = map
681         }
682 
683         val methods = methodMap[cls]!!
684         val methodItem = methods[method]
685         if (methodItem == null) {
686             // Probably switched psi classes (e.g. used source PsiClass in registry but
687             // found duplicate class in .jar library and we're now pointing to it; in that
688             // case, find the equivalent method by signature
689             val psiClass = cls.psiClass
690             val updatedMethod = psiClass.findMethodBySignature(method, true)
691             val result = methods[updatedMethod!!]
692             if (result == null) {
693                 val extra = PsiMethodItem.create(this, cls, updatedMethod)
694                 methods[method] = extra
695                 methods[updatedMethod] = extra
696                 if (!initializing) {
697                     extra.finishInitialization()
698                 }
699 
700                 return extra
701             }
702             return result
703         }
704 
705         return methodItem
706     }
707 
708     fun findField(field: PsiField): FieldItem? {
709         val containingClass = field.containingClass ?: return null
710         val cls = findOrCreateClass(containingClass)
711         return cls.findField(field.name)
712     }
713 
714     private fun registerMethods(methods: List<MethodItem>, map: MutableMap<PsiMethod, PsiMethodItem>) {
715         for (method in methods) {
716             val psiMethod = (method as PsiMethodItem).psiMethod
717             map[psiMethod] = method
718         }
719     }
720 
721     /**
722      * Returns a list of the top-level classes declared in the codebase's source (rather than on its
723      * classpath).
724      */
725     fun getTopLevelClassesFromSource(): List<ClassItem> {
726         return topLevelClassesFromSource
727     }
728 
729     fun createReferenceFromText(s: String, parent: PsiElement? = null): PsiJavaCodeReferenceElement =
730         getFactory().createReferenceFromText(s, parent)
731 
732     fun createPsiMethod(s: String, parent: PsiElement? = null): PsiMethod =
733         getFactory().createMethodFromText(s, parent)
734 
735     fun createConstructor(s: String, parent: PsiElement? = null): PsiMethod =
736         getFactory().createConstructor(s, parent)
737 
738     fun createPsiType(s: String, parent: PsiElement? = null): PsiType =
739         getFactory().createTypeFromText(s, parent)
740 
741     fun createPsiAnnotation(s: String, parent: PsiElement? = null): PsiAnnotation =
742         getFactory().createAnnotationFromText(s, parent)
743 
744     fun createDocTagFromText(s: String): PsiDocTag = getFactory().createDocTagFromText(s)
745 
746     private fun getFactory() = JavaPsiFacade.getElementFactory(project)
747 
748     private var nonNullAnnotationProvider: TypeAnnotationProvider? = null
749     private var nullableAnnotationProvider: TypeAnnotationProvider? = null
750 
751     /** Type annotation provider which provides androidx.annotation.NonNull */
752     fun getNonNullAnnotationProvider(): TypeAnnotationProvider {
753         return nonNullAnnotationProvider ?: run {
754             val provider = TypeAnnotationProvider.Static.create(arrayOf(createPsiAnnotation("@$ANDROIDX_NONNULL")))
755             nonNullAnnotationProvider
756             provider
757         }
758     }
759 
760     /** Type annotation provider which provides androidx.annotation.Nullable */
761     fun getNullableAnnotationProvider(): TypeAnnotationProvider {
762         return nullableAnnotationProvider ?: run {
763             val provider = TypeAnnotationProvider.Static.create(arrayOf(createPsiAnnotation("@$ANDROIDX_NULLABLE")))
764             nullableAnnotationProvider
765             provider
766         }
767     }
768 
769     override fun createAnnotation(
770         source: String,
771         context: Item?,
772         mapName: Boolean
773     ): PsiAnnotationItem {
774         val psiAnnotation = createPsiAnnotation(source, context?.psi())
775         return PsiAnnotationItem.create(this, psiAnnotation)
776     }
777 
778     override fun supportsDocumentation(): Boolean = true
779 
780     override fun toString(): String = description
781 
782     /** Add a class to the codebase. Called from [createClass] and [PsiClassItem.create]. */
783     internal fun registerClass(cls: PsiClassItem) {
784         classMap[cls.qualifiedName()] = cls
785     }
786 
787     /** Get a Kotlin [BindingContext] at [element]
788      *
789      * Do not cache returned binding context for longer than the lifetime of this codebase
790      */
791     fun bindingContext(element: KtElement): BindingContext {
792         return checkNotNull(project.getService(KotlinUastResolveProviderService::class.java))
793             .getBindingContext(element)
794     }
795 }
796