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