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