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.model.ANDROIDX_NONNULL
22 import com.android.tools.metalava.model.ANDROIDX_NULLABLE
23 import com.android.tools.metalava.model.AnnotationItem
24 import com.android.tools.metalava.model.AnnotationManager
25 import com.android.tools.metalava.model.CLASS_ESTIMATE
26 import com.android.tools.metalava.model.ClassItem
27 import com.android.tools.metalava.model.DefaultCodebase
28 import com.android.tools.metalava.model.FieldItem
29 import com.android.tools.metalava.model.Item
30 import com.android.tools.metalava.model.MethodItem
31 import com.android.tools.metalava.model.PackageItem
32 import com.android.tools.metalava.model.PackageList
33 import com.android.tools.metalava.model.TypeParameterScope
34 import com.android.tools.metalava.model.source.SourceCodebase
35 import com.android.tools.metalava.reporter.Issues
36 import com.android.tools.metalava.reporter.Reporter
37 import com.intellij.openapi.application.ApplicationManager
38 import com.intellij.openapi.project.Project
39 import com.intellij.psi.JavaPsiFacade
40 import com.intellij.psi.JavaRecursiveElementVisitor
41 import com.intellij.psi.PsiAnnotation
42 import com.intellij.psi.PsiArrayType
43 import com.intellij.psi.PsiClass
44 import com.intellij.psi.PsiClassOwner
45 import com.intellij.psi.PsiClassType
46 import com.intellij.psi.PsiCodeBlock
47 import com.intellij.psi.PsiElement
48 import com.intellij.psi.PsiErrorElement
49 import com.intellij.psi.PsiField
50 import com.intellij.psi.PsiFile
51 import com.intellij.psi.PsiImportStatement
52 import com.intellij.psi.PsiJavaFile
53 import com.intellij.psi.PsiMethod
54 import com.intellij.psi.PsiPackage
55 import com.intellij.psi.PsiSubstitutor
56 import com.intellij.psi.PsiType
57 import com.intellij.psi.PsiTypeParameter
58 import com.intellij.psi.TypeAnnotationProvider
59 import com.intellij.psi.javadoc.PsiDocComment
60 import com.intellij.psi.search.GlobalSearchScope
61 import com.intellij.psi.util.PsiTreeUtil
62 import java.io.File
63 import java.io.IOException
64 import java.util.zip.ZipFile
65 import org.jetbrains.kotlin.asJava.classes.KtLightClassForFacade
66 import org.jetbrains.kotlin.name.FqName
67 import org.jetbrains.uast.UClass
68 import org.jetbrains.uast.UFile
69 import org.jetbrains.uast.UMethod
70 import org.jetbrains.uast.UastFacade
71 import org.jetbrains.uast.kotlin.BaseKotlinUastResolveProviderService
72 
73 const val PACKAGE_ESTIMATE = 500
74 const val METHOD_ESTIMATE = 1000
75 
76 /**
77  * A codebase containing Java, Kotlin, or UAST PSI classes
78  *
79  * After creation, a list of PSI file is passed to [initializeFromSources] or a JAR file is passed
80  * to [initializeFromJar]. This creates package and class items along with their members. This
81  * process is broken into two phases:
82  *
83  * First, [initializing] is set to true, and class items are created from the supplied sources. If
84  * [fromClasspath] is false, these are main classes of the codebase and have [ClassItem.emit] set to
85  * true and [ClassItem.isFromClassPath] set to false. While creating these, package names are
86  * reserved and associated with their classes in [packageClasses].
87  *
88  * If [fromClasspath] is true, all classes are assumed to be from the classpath, so [ClassItem.emit]
89  * is set to false and [ClassItem.isFromClassPath] is set to true for all classes created.
90  *
91  * Next, package items are created for source classes based on the contents of [packageClasses] with
92  * [PackageItem.emit] set to true.
93  *
94  * Then [initializing] is set to false and the second pass begins. This path iteratively resolves
95  * supertypes of class items until all are fully resolved, creating new class and package items as
96  * needed. Since all the source class and package items have been created, new items are assumed to
97  * originate from the classpath and have [Item.emit] set to false and [Item.isFromClassPath] set to
98  * true.
99  */
100 open class PsiBasedCodebase(
101     location: File,
102     description: String = "Unknown",
103     annotationManager: AnnotationManager,
104     private val reporter: Reporter,
105     val allowReadingComments: Boolean,
106     val fromClasspath: Boolean = false,
107 ) : DefaultCodebase(location, description, false, annotationManager), SourceCodebase {
108     private lateinit var uastEnvironment: UastEnvironment
109     internal val project: Project
110         get() = uastEnvironment.ideaProject
111 
112     /**
113      * Returns the compilation units used in this codebase (may be empty when the codebase is not
114      * loaded from source, such as from .jar files or from signature files)
115      */
116     private var units: List<PsiFile> = emptyList()
117 
118     /**
119      * Printer which can convert PSI, UAST and constants into source code, with ability to filter
120      * out elements that are not part of a codebase etc
121      */
122     @Suppress("LeakingThis") internal val printer = CodePrinter(this, reporter)
123 
124     /** Supports fully qualifying Javadoc. */
125     internal val docQualifier = DocQualifier(reporter)
126 
127     /** Map from class name to class item. Classes are added via [registerClass] */
128     private val classMap: MutableMap<String, PsiClassItem> = HashMap(CLASS_ESTIMATE)
129 
130     /**
131      * Map from classes to the set of methods for each (but only for classes where we've called
132      * [findMethod]
133      */
134     private lateinit var methodMap: MutableMap<PsiClassItem, MutableMap<PsiMethod, PsiMethodItem>>
135 
136     /** Map from package name to the corresponding package item */
137     private lateinit var packageMap: MutableMap<String, PsiPackageItem>
138 
139     /**
140      * Map from package name to list of classes in that package. Initialized in [initializeFromJar]
141      * and [initializeFromSources], updated by [registerPackageClass], and used and cleared in
142      * [fixUpTypeNullability].
143      */
144     private var packageClasses: MutableMap<String, MutableList<PsiClassItem>>? = null
145 
146     /** A set of packages to hide */
147     private lateinit var hiddenPackages: MutableMap<String, Boolean?>
148 
149     /**
150      * A list of the top-level classes declared in the codebase's source (rather than on its
151      * classpath).
152      */
153     private lateinit var topLevelClassesFromSource: MutableList<PsiClassItem>
154 
155     /**
156      * Set to true in [initializeFromJar] and [initializeFromSources] for the first pass of creating
157      * class items for all classes in the codebase sources and false for the second pass of creating
158      * class items for the supertypes of the codebase classes. New class items created in the
159      * supertypes pass must come from the classpath (dependencies) since all source classes have
160      * been created.
161      *
162      * This information is used in [createClass] to set [ClassItem.emit] to true for source classes
163      * and [ClassItem.isFromClassPath] to true for classpath classes. It is also used in
164      * [registerPackage] to set [PackageItem.emit] to true for source packages.
165      */
166     private var initializing = false
167 
168     private var hideClassesFromJars = true
169 
170     /** [PsiTypeItemFactory] used to create [PsiTypeItem]s. */
171     internal val globalTypeItemFactory = PsiTypeItemFactory(this, TypeParameterScope.empty)
172 
173     private lateinit var emptyPackage: PsiPackageItem
174 
175     internal fun initializeFromSources(
176         uastEnvironment: UastEnvironment,
177         psiFiles: List<PsiFile>,
178         packages: PackageDocs,
179     ) {
180         initializing = true
181         this.units = psiFiles
182 
183         this.uastEnvironment = uastEnvironment
184         // there are currently ~230 packages in the public SDK, but here we need to account for
185         // internal ones too
186         val hiddenPackages: MutableSet<String> = packages.hiddenPackages
187         val packageDocs = packages.packageDocs
188         this.hiddenPackages = HashMap(100)
189         for (pkgName in hiddenPackages) {
190             this.hiddenPackages[pkgName] = true
191         }
192 
193         packageMap = HashMap(PACKAGE_ESTIMATE)
194         packageClasses = HashMap(PACKAGE_ESTIMATE)
195         packageClasses!![""] = ArrayList()
196         this.methodMap = HashMap(METHOD_ESTIMATE)
197         topLevelClassesFromSource = ArrayList(CLASS_ESTIMATE)
198 
199         // A set to track @JvmMultifileClasses that have already been added to
200         // [topLevelClassesFromSource]
201         val multifileClassNames = HashSet<FqName>()
202 
203         // Make sure we only process the files once; sometimes there's overlap in the source lists
204         for (psiFile in psiFiles.asSequence().distinct()) {
205             // Visiting psiFile directly would eagerly load the entire file even though we only need
206             // the importList here.
207             (psiFile as? PsiJavaFile)
208                 ?.importList
209                 ?.accept(
210                     object : JavaRecursiveElementVisitor() {
211                         override fun visitImportStatement(element: PsiImportStatement) {
212                             super.visitImportStatement(element)
213                             if (element.resolve() == null) {
214                                 reporter.report(
215                                     Issues.UNRESOLVED_IMPORT,
216                                     element,
217                                     "Unresolved import: `${element.qualifiedName}`"
218                                 )
219                             }
220                         }
221                     }
222                 )
223 
224             var classes = (psiFile as? PsiClassOwner)?.classes?.toList() ?: emptyList()
225             if (classes.isEmpty()) {
226                 val uFile =
227                     UastFacade.convertElementWithParent(psiFile, UFile::class.java) as? UFile?
228                 classes = uFile?.classes?.map { it }?.toList() ?: emptyList()
229             }
230             when {
231                 classes.isEmpty() && psiFile is PsiJavaFile -> {
232                     // package-info.java ?
233                     val packageStatement = psiFile.packageStatement
234                     // Look for javadoc on the package statement; this is NOT handed to us on
235                     // the PsiPackage!
236                     if (packageStatement != null) {
237                         val comment =
238                             PsiTreeUtil.getPrevSiblingOfType(
239                                 packageStatement,
240                                 PsiDocComment::class.java
241                             )
242                         if (comment != null) {
243                             val packageName = packageStatement.packageName
244                             val text = comment.text
245                             if (text.contains("@hide")) {
246                                 this.hiddenPackages[packageName] = true
247                             }
248                             if (packageDocs[packageName] != null) {
249                                 reporter.report(
250                                     Issues.BOTH_PACKAGE_INFO_AND_HTML,
251                                     psiFile,
252                                     "It is illegal to provide both a package-info.java file and " +
253                                         "a package.html file for the same package"
254                                 )
255                             }
256                             packageDocs[packageName] = text
257                         }
258                     }
259                 }
260                 else -> {
261                     for (psiClass in classes) {
262                         psiClass.accept(
263                             object : JavaRecursiveElementVisitor() {
264                                 override fun visitErrorElement(element: PsiErrorElement) {
265                                     super.visitErrorElement(element)
266                                     reporter.report(
267                                         Issues.INVALID_SYNTAX,
268                                         element,
269                                         "Syntax error: `${element.errorDescription}`"
270                                     )
271                                 }
272 
273                                 override fun visitCodeBlock(block: PsiCodeBlock) {
274                                     // Ignore to avoid eagerly parsing all method bodies.
275                                 }
276 
277                                 override fun visitDocComment(comment: PsiDocComment) {
278                                     // Ignore to avoid eagerly parsing all doc comments.
279                                     // Doc comments cannot contain error elements.
280                                 }
281                             }
282                         )
283 
284                         // Multifile classes appear identically from each file they're defined in,
285                         // don't add duplicates
286                         val ktLightClass = (psiClass as? UClass)?.javaPsi as? KtLightClassForFacade
287                         if (ktLightClass?.multiFileClass == true) {
288                             if (multifileClassNames.contains(ktLightClass.facadeClassFqName)) {
289                                 continue
290                             } else {
291                                 multifileClassNames.add(ktLightClass.facadeClassFqName)
292                             }
293                         }
294                         topLevelClassesFromSource += createTopLevelClassAndContents(psiClass)
295                     }
296                 }
297             }
298         }
299 
300         finishInitialization(packages)
301     }
302 
303     /**
304      * Finish initializing a [PsiClassItem].
305      *
306      * This must only be called when [initializing] is `false`.
307      */
308     private fun finishClassInitialization(classItem: PsiClassItem) {
309         if (initializing) {
310             error("incorrectly called on $classItem when initializing=`true`")
311         }
312 
313         val pkgName = getPackageName(classItem.psiClass)
314         val pkg = findPackage(pkgName)
315         if (pkg == null) {
316             val psiPackage = findPsiPackage(pkgName)
317             if (psiPackage != null) {
318                 val packageItem = registerPackage(pkgName, psiPackage, null)
319                 packageItem.addClass(classItem)
320             }
321         } else {
322             pkg.addClass(classItem)
323         }
324     }
325 
326     /**
327      * Finish initialising this codebase.
328      *
329      * Involves:
330      * * Constructing packages, setting [emptyPackage].
331      * * Finalizing [PsiClassItem]s which may involve creating some more, e.g. super classes and
332      *   interfaces referenced from the source code but provided on the class path.
333      */
334     private fun finishInitialization(packages: PackageDocs?) {
335 
336         // Next construct packages
337         val packageDocs = packages?.packageDocs ?: emptyMap()
338         val overviewDocs = packages?.overviewDocs ?: emptyMap()
339         for ((pkgName, classes) in packageClasses!!) {
340             val psiPackage = findPsiPackage(pkgName)
341             if (psiPackage == null) {
342                 println("Could not find package $pkgName")
343                 continue
344             }
345 
346             val sortedClasses = classes.toMutableList().sortedWith(ClassItem.fullNameComparator)
347             registerPackage(
348                 pkgName,
349                 psiPackage,
350                 sortedClasses,
351                 packageDocs[pkgName],
352                 overviewDocs[pkgName],
353             )
354         }
355 
356         // Not used after this point.
357         packageClasses = null
358 
359         initializing = false
360 
361         emptyPackage = findPackage("")!!
362 
363         // Resolve the super types of all the classes that have been loaded.
364         resolveSuperTypes()
365 
366         // Point to "parent" packages, since doclava treats packages as nested (e.g. an @hide on
367         // android.foo will also apply to android.foo.bar)
368         addParentPackages(packageMap.values)
369     }
370 
371     override fun dispose() {
372         uastEnvironment.dispose()
373         super.dispose()
374     }
375 
376     private fun addParentPackages(packages: Collection<PsiPackageItem>) {
377         val missingPackages =
378             packages
379                 .mapNotNull {
380                     val name = it.qualifiedName()
381                     val index = name.lastIndexOf('.')
382                     val parent =
383                         if (index != -1) {
384                             name.substring(0, index)
385                         } else {
386                             ""
387                         }
388                     if (packageMap.containsKey(parent)) {
389                         // Already registered
390                         null
391                     } else {
392                         parent
393                     }
394                 }
395                 .toSet()
396 
397         // Create PackageItems for any packages that weren't in the source
398         for (pkgName in missingPackages) {
399             val psiPackage = findPsiPackage(pkgName) ?: continue
400             val sortedClasses = emptyList<PsiClassItem>()
401             registerPackage(pkgName, psiPackage, sortedClasses)
402         }
403 
404         // Connect up all the package items
405         for (pkg in packageMap.values) {
406             var name = pkg.qualifiedName()
407             // Find parent package; we have to loop since we don't always find a PSI package
408             // for intermediate elements; e.g. we may jump from java.lang straight up to the default
409             // package
410             while (name.isNotEmpty()) {
411                 val index = name.lastIndexOf('.')
412                 name =
413                     if (index != -1) {
414                         name.substring(0, index)
415                     } else {
416                         ""
417                     }
418                 val parent = findPackage(name) ?: continue
419                 pkg.containingPackageField = parent
420                 break
421             }
422         }
423     }
424 
425     private fun registerPackage(
426         pkgName: String,
427         psiPackage: PsiPackage,
428         sortedClasses: List<PsiClassItem>?,
429         packageHtml: String? = null,
430         overviewHtml: String? = null,
431     ): PsiPackageItem {
432         val packageItem =
433             PsiPackageItem.create(
434                 this,
435                 psiPackage,
436                 packageHtml,
437                 overviewHtml,
438                 fromClassPath = fromClasspath || !initializing
439             )
440         packageItem.emit = !packageItem.isFromClassPath()
441 
442         packageMap[pkgName] = packageItem
443         if (isPackageHidden(pkgName)) {
444             packageItem.hidden = true
445         }
446 
447         sortedClasses?.let { packageItem.addClasses(it) }
448         return packageItem
449     }
450 
451     internal fun initializeFromJar(
452         uastEnvironment: UastEnvironment,
453         jarFile: File,
454     ) {
455         initializing = true
456         hideClassesFromJars = false
457 
458         this.uastEnvironment = uastEnvironment
459 
460         // Find all classes referenced from the class
461         val facade = JavaPsiFacade.getInstance(project)
462         val scope = GlobalSearchScope.allScope(project)
463 
464         hiddenPackages = HashMap(100)
465         packageMap = HashMap(PACKAGE_ESTIMATE)
466         packageClasses = HashMap(PACKAGE_ESTIMATE)
467         packageClasses!![""] = ArrayList()
468         this.methodMap = HashMap(1000)
469         val packageToClasses: MutableMap<String, MutableList<PsiClassItem>> =
470             HashMap(PACKAGE_ESTIMATE)
471         packageToClasses[""] = ArrayList() // ensure we construct one for the default package
472 
473         topLevelClassesFromSource = ArrayList(CLASS_ESTIMATE)
474 
475         try {
476             ZipFile(jarFile).use { jar ->
477                 val enumeration = jar.entries()
478                 while (enumeration.hasMoreElements()) {
479                     val entry = enumeration.nextElement()
480                     val fileName = entry.name
481                     if (fileName.contains("$")) {
482                         // skip inner classes
483                         continue
484                     }
485                     if (fileName.endsWith(SdkConstants.DOT_CLASS)) {
486                         val qualifiedName =
487                             fileName.removeSuffix(SdkConstants.DOT_CLASS).replace('/', '.')
488                         if (qualifiedName.endsWith(".package-info")) {
489                             // Ensure we register a package for this, even if empty
490                             val packageName = qualifiedName.removeSuffix(".package-info")
491                             var list = packageToClasses[packageName]
492                             if (list == null) {
493                                 list = mutableListOf()
494                                 packageToClasses[packageName] = list
495                             }
496                             continue
497                         } else {
498                             val psiClass = facade.findClass(qualifiedName, scope) ?: continue
499 
500                             val classItem = createTopLevelClassAndContents(psiClass)
501                             topLevelClassesFromSource.add(classItem)
502 
503                             val packageName = getPackageName(psiClass)
504                             var list = packageToClasses[packageName]
505                             if (list == null) {
506                                 list = mutableListOf(classItem)
507                                 packageToClasses[packageName] = list
508                             } else {
509                                 list.add(classItem)
510                             }
511                         }
512                     }
513                 }
514             }
515         } catch (e: IOException) {
516             reporter.report(Issues.IO_ERROR, jarFile, e.message ?: e.toString())
517         }
518 
519         hideClassesFromJars = true
520 
521         // When loading from a jar there is no package documentation.
522         finishInitialization(null)
523     }
524 
525     private fun registerPackageClass(packageName: String, cls: PsiClassItem) {
526         var list = packageClasses!![packageName]
527         if (list == null) {
528             list = ArrayList()
529             packageClasses!![packageName] = list
530         }
531 
532         list.add(cls)
533     }
534 
535     private fun isPackageHidden(packageName: String): Boolean {
536         val hidden = hiddenPackages[packageName]
537         if (hidden == true) {
538             return true
539         } else if (hidden == null) {
540             // Compute for all prefixes of this package
541             var pkg = packageName
542             while (true) {
543                 if (hiddenPackages[pkg] != null) {
544                     hiddenPackages[packageName] = hiddenPackages[pkg]
545                     if (hiddenPackages[pkg] == true) {
546                         return true
547                     }
548                 }
549                 val last = pkg.lastIndexOf('.')
550                 if (last == -1) {
551                     hiddenPackages[packageName] = false
552                     break
553                 } else {
554                     pkg = pkg.substring(0, last)
555                 }
556             }
557         }
558 
559         return false
560     }
561 
562     /**
563      * Create top level classes, their inner classes and all the other members.
564      *
565      * All the classes are registered by name and so can be found by [findOrCreateClass].
566      */
567     private fun createTopLevelClassAndContents(psiClass: PsiClass): PsiClassItem {
568         if (psiClass.containingClass != null) error("$psiClass is not a top level class")
569         return createClass(psiClass, null, globalTypeItemFactory)
570     }
571 
572     internal fun createClass(
573         psiClass: PsiClass,
574         containingClassItem: PsiClassItem?,
575         enclosingClassTypeItemFactory: PsiTypeItemFactory,
576     ): PsiClassItem {
577         // If initializing is true, this class is from source
578         val classItem =
579             PsiClassItem.create(
580                 this,
581                 psiClass,
582                 containingClassItem,
583                 enclosingClassTypeItemFactory,
584                 fromClassPath = fromClasspath || !initializing,
585             )
586         // Set emit to true for source classes but false for classpath classes
587         classItem.emit = !classItem.isFromClassPath()
588 
589         if (!initializing) {
590             // Workaround: we're pulling in .aidl files from .jar files. These are
591             // marked @hide, but since we only see the .class files we don't know that.
592             if (
593                 classItem.simpleName().startsWith("I") &&
594                     classItem.isFromClassPath() &&
595                     psiClass.interfaces.any { it.qualifiedName == "android.os.IInterface" }
596             ) {
597                 classItem.hidden = true
598             }
599         }
600 
601         if (initializing) {
602             // If initializing then keep track of the class in [packageClasses]. This is not needed
603             // after initializing as [packageClasses] is not needed then.
604             // TODO: Cache for adjacent files!
605             val packageName = getPackageName(psiClass)
606             registerPackageClass(packageName, classItem)
607         }
608 
609         return classItem
610     }
611 
612     override fun getPackages(): PackageList {
613         // TODO: Sorting is probably not necessary here!
614         return PackageList(
615             this,
616             packageMap.values.toMutableList().sortedWith(PackageItem.comparator)
617         )
618     }
619 
620     override fun size(): Int {
621         return packageMap.size
622     }
623 
624     override fun findPackage(pkgName: String): PsiPackageItem? {
625         return packageMap[pkgName]
626     }
627 
628     internal fun findPsiPackage(pkgName: String): PsiPackage? {
629         return JavaPsiFacade.getInstance(project).findPackage(pkgName)
630     }
631 
632     override fun findClass(className: String): PsiClassItem? {
633         return classMap[className]
634     }
635 
636     override fun resolveClass(className: String): ClassItem? = findOrCreateClass(className)
637 
638     open fun findClass(psiClass: PsiClass): PsiClassItem? {
639         val qualifiedName: String = psiClass.qualifiedName ?: psiClass.name!!
640         return classMap[qualifiedName]
641     }
642 
643     internal fun findOrCreateClass(qualifiedName: String): PsiClassItem? {
644         // Check to see if the class has already been seen and if so return it immediately.
645         findClass(qualifiedName)?.let {
646             return it
647         }
648 
649         // The following cannot find a class whose name does not correspond to the file name, e.g.
650         // in Java a class that is a second top level class.
651         val finder = JavaPsiFacade.getInstance(project)
652         val psiClass =
653             finder.findClass(qualifiedName, GlobalSearchScope.allScope(project)) ?: return null
654         return findOrCreateClass(psiClass)
655     }
656 
657     /**
658      * Identifies a point in the [PsiClassItem] nesting structure where new [PsiClassItem]s need
659      * inserting.
660      */
661     data class NewClassInsertionPoint(
662         /**
663          * The [PsiClass] that is the root of the nested classes that need creation, is a top level
664          * class if [containingClassItem] is `null`.
665          */
666         val missingPsiClass: PsiClass,
667 
668         /** The containing class item, or `null` if the top level. */
669         val containingClassItem: PsiClassItem?,
670     )
671 
672     /**
673      * Called when no [PsiClassItem] was found by [findClass]`([PsiClass]) when called on
674      * [psiClass].
675      *
676      * The purpose of this is to find where a new [PsiClassItem] should be inserted in the nested
677      * class structure. It finds the outermost [PsiClass] with no associated [PsiClassItem] but
678      * which is either a top level class or whose containing [PsiClass] does have an associated
679      * [PsiClassItem]. That is the point where new classes need to be created.
680      *
681      * e.g. if the nesting structure is `A.B.C` and `A` has already been created then the insertion
682      * point would consist of [PsiClassItem] for `A` (the containing class item) and the [PsiClass]
683      * for `B` (the outermost [PsiClass] with no associated item).
684      *
685      * If none had already been created then it would return an insertion point consisting of no
686      * containing class item and the [PsiClass] for `A`.
687      */
688     private fun findNewClassInsertionPoint(psiClass: PsiClass): NewClassInsertionPoint {
689         var current = psiClass
690         do {
691             // If the current has no containing class then it has reached the top level class so
692             // return an insertion point that has no containing class item and the current class.
693             val containing = current.containingClass ?: return NewClassInsertionPoint(current, null)
694 
695             // If the containing class has a matching class item then return an insertion point that
696             // uses that containing class item and the current class.
697             findClass(containing)?.let { containingClassItem ->
698                 return NewClassInsertionPoint(current, containingClassItem)
699             }
700             current = containing
701         } while (true)
702     }
703 
704     internal fun findOrCreateClass(psiClass: PsiClass): PsiClassItem {
705         if (psiClass is PsiTypeParameter) {
706             error(
707                 "Must not be called with PsiTypeParameter; call findOrCreateTypeParameter(...) instead"
708             )
709         }
710 
711         // If it has already been created then return it.
712         findClass(psiClass)?.let {
713             return it
714         }
715 
716         // Otherwise, find an insertion point at which new classes should be created.
717         val (missingPsiClass, containingClassItem) = findNewClassInsertionPoint(psiClass)
718 
719         // Create a top level or nested class as appropriate.
720         val createdClassItem =
721             if (containingClassItem == null) {
722                 createTopLevelClassAndContents(missingPsiClass)
723             } else {
724                 createClass(
725                     missingPsiClass,
726                     containingClassItem,
727                     globalTypeItemFactory.from(containingClassItem)
728                 )
729             }
730 
731         // Make sure that the created class has been properly initialized.
732         finishClassInitialization(createdClassItem)
733 
734         // Select the class item to return.
735         return if (missingPsiClass == psiClass) {
736             // The created class item was what was requested so just return it.
737             createdClassItem
738         } else {
739             // Otherwise, a nested class was requested so find it. It was created when its
740             // containing class was created.
741             findClass(psiClass)!!
742         }
743     }
744 
745     internal fun findClass(psiType: PsiType): PsiClassItem? {
746         if (psiType is PsiClassType) {
747             val cls = psiType.resolve() ?: return null
748             return findOrCreateClass(cls)
749         } else if (psiType is PsiArrayType) {
750             var componentType = psiType.componentType
751             // We repeatedly get the component type because the array may have multiple dimensions
752             while (componentType is PsiArrayType) {
753                 componentType = componentType.componentType
754             }
755             if (componentType is PsiClassType) {
756                 val cls = componentType.resolve() ?: return null
757                 return findOrCreateClass(cls)
758             }
759         }
760         return null
761     }
762 
763     internal fun getClassType(cls: PsiClass): PsiClassType =
764         getFactory().createType(cls, PsiSubstitutor.EMPTY)
765 
766     internal fun getComment(string: String, parent: PsiElement? = null): PsiDocComment =
767         getFactory().createDocCommentFromText(string, parent)
768 
769     private fun getPackageName(clz: PsiClass): String {
770         var top: PsiClass? = clz
771         while (top?.containingClass != null) {
772             top = top.containingClass
773         }
774         top ?: return ""
775 
776         val name = top.name
777         val fullName = top.qualifiedName ?: return ""
778 
779         if (name == fullName) {
780             return ""
781         }
782 
783         return fullName.substring(0, fullName.length - 1 - name!!.length)
784     }
785 
786     internal fun findMethod(method: PsiMethod): PsiMethodItem {
787         val containingClass = method.containingClass
788         val cls = findOrCreateClass(containingClass!!)
789 
790         // Ensure initialized/registered via [#registerMethods]
791         if (methodMap[cls] == null) {
792             val map = HashMap<PsiMethod, PsiMethodItem>(40)
793             registerMethods(cls.methods(), map)
794             registerMethods(cls.constructors(), map)
795             methodMap[cls] = map
796         }
797 
798         val methods = methodMap[cls]!!
799         val methodItem = methods[method]
800         if (methodItem == null) {
801             // Probably switched psi classes (e.g. used source PsiClass in registry but
802             // found duplicate class in .jar library and we're now pointing to it; in that
803             // case, find the equivalent method by signature
804             val psiClass = cls.psiClass
805             val updatedMethod = psiClass.findMethodBySignature(method, true)
806             val result = methods[updatedMethod!!]
807             if (result == null) {
808                 val extra =
809                     PsiMethodItem.create(this, cls, updatedMethod, globalTypeItemFactory.from(cls))
810                 methods[method] = extra
811                 methods[updatedMethod] = extra
812 
813                 return extra
814             }
815             return result
816         }
817 
818         return methodItem
819     }
820 
821     internal fun findField(field: PsiField): FieldItem? {
822         val containingClass = field.containingClass ?: return null
823         val cls = findOrCreateClass(containingClass)
824         return cls.findField(field.name)
825     }
826 
827     private fun registerMethods(
828         methods: List<MethodItem>,
829         map: MutableMap<PsiMethod, PsiMethodItem>
830     ) {
831         for (method in methods) {
832             val psiMethod = (method as PsiMethodItem).psiMethod
833             map[psiMethod] = method
834             if (psiMethod is UMethod) {
835                 // Register LC method as a key too
836                 // so that we can find the corresponding [MethodItem]
837                 // Otherwise, we will end up creating a new [MethodItem]
838                 // without source PSI, resulting in wrong modifier.
839                 map[psiMethod.javaPsi] = method
840             }
841         }
842     }
843 
844     override fun getTopLevelClassesFromSource(): List<ClassItem> {
845         return topLevelClassesFromSource
846     }
847 
848     internal fun createPsiMethod(s: String, parent: PsiElement? = null): PsiMethod =
849         getFactory().createMethodFromText(s, parent)
850 
851     internal fun createPsiType(s: String, parent: PsiElement? = null): PsiType =
852         getFactory().createTypeFromText(s, parent)
853 
854     private fun createPsiAnnotation(s: String, parent: PsiElement? = null): PsiAnnotation =
855         getFactory().createAnnotationFromText(s, parent)
856 
857     private fun getFactory() = JavaPsiFacade.getElementFactory(project)
858 
859     private var nonNullAnnotationProvider: TypeAnnotationProvider? = null
860     private var nullableAnnotationProvider: TypeAnnotationProvider? = null
861 
862     /** Type annotation provider which provides androidx.annotation.NonNull */
863     internal fun getNonNullAnnotationProvider(): TypeAnnotationProvider {
864         return nonNullAnnotationProvider
865             ?: run {
866                 val provider =
867                     TypeAnnotationProvider.Static.create(
868                         arrayOf(createPsiAnnotation("@$ANDROIDX_NONNULL"))
869                     )
870                 nonNullAnnotationProvider
871                 provider
872             }
873     }
874 
875     /** Type annotation provider which provides androidx.annotation.Nullable */
876     internal fun getNullableAnnotationProvider(): TypeAnnotationProvider {
877         return nullableAnnotationProvider
878             ?: run {
879                 val provider =
880                     TypeAnnotationProvider.Static.create(
881                         arrayOf(createPsiAnnotation("@$ANDROIDX_NULLABLE"))
882                     )
883                 nullableAnnotationProvider
884                 provider
885             }
886     }
887 
888     override fun createAnnotation(
889         source: String,
890         context: Item?,
891     ): AnnotationItem {
892         val psiAnnotation = createPsiAnnotation(source, (context as? PsiItem)?.psi())
893         return PsiAnnotationItem.create(this, psiAnnotation)
894     }
895 
896     override fun supportsDocumentation(): Boolean = true
897 
898     override fun toString(): String = description
899 
900     /** Add a class to the codebase. Called from [PsiClassItem.create]. */
901     internal fun registerClass(classItem: PsiClassItem) {
902         val qualifiedName = classItem.qualifiedName()
903         val existing = classMap.put(qualifiedName, classItem)
904         if (existing != null) {
905             reporter.report(
906                 Issues.DUPLICATE_SOURCE_CLASS,
907                 classItem,
908                 "Ignoring this duplicate definition of $qualifiedName; previous definition was loaded from ${existing.fileLocation.path}"
909             )
910             return
911         }
912 
913         addClass(classItem)
914     }
915 
916     internal val uastResolveService: BaseKotlinUastResolveProviderService? by lazy {
917         ApplicationManager.getApplication()
918             .getService(BaseKotlinUastResolveProviderService::class.java)
919     }
920 }
921