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