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