1 /* <lambda>null2 * Copyright (C) 2018 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.tools.metalava.JAVA_LANG_OBJECT 20 import com.android.tools.metalava.compatibility 21 import com.android.tools.metalava.model.AnnotationItem 22 import com.android.tools.metalava.model.Codebase 23 import com.android.tools.metalava.model.Item 24 import com.android.tools.metalava.model.SUPPORT_TYPE_USE_ANNOTATIONS 25 import com.android.tools.metalava.model.isNonNullAnnotation 26 import com.android.tools.metalava.model.isNullableAnnotation 27 import com.android.tools.metalava.options 28 import com.intellij.openapi.util.text.StringUtil 29 import com.intellij.psi.PsiAnnotatedJavaCodeReferenceElement 30 import com.intellij.psi.PsiAnnotation 31 import com.intellij.psi.PsiAnonymousClass 32 import com.intellij.psi.PsiArrayType 33 import com.intellij.psi.PsiCapturedWildcardType 34 import com.intellij.psi.PsiClass 35 import com.intellij.psi.PsiDisjunctionType 36 import com.intellij.psi.PsiEllipsisType 37 import com.intellij.psi.PsiFile 38 import com.intellij.psi.PsiIntersectionType 39 import com.intellij.psi.PsiModifier 40 import com.intellij.psi.PsiPackage 41 import com.intellij.psi.PsiPrimitiveType 42 import com.intellij.psi.PsiSubstitutor 43 import com.intellij.psi.PsiType 44 import com.intellij.psi.PsiWildcardType 45 import com.intellij.psi.PsiWildcardType.EXTENDS_PREFIX 46 import com.intellij.psi.PsiWildcardType.SUPER_PREFIX 47 import com.intellij.psi.impl.PsiImplUtil 48 import com.intellij.psi.impl.compiled.ClsJavaCodeReferenceElementImpl 49 import com.intellij.psi.impl.source.PsiClassReferenceType 50 import com.intellij.psi.impl.source.PsiImmediateClassType 51 import com.intellij.psi.impl.source.PsiJavaCodeReferenceElementImpl 52 import com.intellij.psi.impl.source.tree.JavaSourceUtil 53 import com.intellij.psi.impl.source.tree.java.PsiReferenceExpressionImpl 54 import com.intellij.psi.util.PsiTreeUtil 55 import com.intellij.psi.util.PsiUtil 56 import com.intellij.psi.util.PsiUtilCore 57 import java.util.function.Predicate 58 59 /** 60 * Type printer which can take a [PsiType] and print it to a fully canonical 61 * string, in one of two formats: 62 * <li> 63 * <li> Kotlin syntax, e.g. java.lang.Object? 64 * <li> Java syntax, e.g. java.lang.@androidx.annotation.Nullable Object 65 * </li> 66 * 67 * The main features of this class relative to PsiType.getCanonicalText(annotated) 68 * is that it can perform filtering (to remove annotations not part of the API) 69 * and Kotlin style printing which cannot be done by simple replacements 70 * of @Nullable->? etc since the annotations and the suffixes appear in different 71 * places. 72 */ 73 class PsiTypePrinter( 74 private val codebase: Codebase, 75 private val filter: Predicate<Item>? = null, 76 private val mapAnnotations: Boolean = false, 77 private val kotlinStyleNulls: Boolean = options.outputKotlinStyleNulls, 78 private val supportTypeUseAnnotations: Boolean = SUPPORT_TYPE_USE_ANNOTATIONS 79 ) { 80 // This class inlines a lot of methods from IntelliJ, but with (a) annotated=true, (b) calling local 81 // getCanonicalText methods instead of instance methods, and (c) deferring annotations if kotlinStyleNulls 82 // is true and instead printing it out as a suffix. Dead code paths are also removed. 83 84 fun getAnnotatedCanonicalText(type: PsiType, elementAnnotations: List<AnnotationItem>? = null): String { 85 return getCanonicalText(type, elementAnnotations) 86 } 87 88 private fun appendNullnessSuffix( 89 annotations: Array<PsiAnnotation>, 90 sb: StringBuilder, 91 elementAnnotations: List<AnnotationItem>? 92 ) { 93 val nullable = getNullable(annotations, elementAnnotations) 94 appendNullnessSuffix(nullable, sb) // else: non null 95 } 96 97 private fun appendNullnessSuffix( 98 list: List<PsiAnnotation>?, 99 buffer: StringBuilder, 100 elementAnnotations: List<AnnotationItem>? 101 102 ) { 103 val nullable: Boolean? = getNullable(list, elementAnnotations) 104 appendNullnessSuffix(nullable, buffer) // else: not null: no suffix 105 } 106 107 private fun appendNullnessSuffix(nullable: Boolean?, sb: StringBuilder) { 108 if (nullable == true) { 109 sb.append('?') 110 } else if (nullable == null) { 111 sb.append('!') 112 } 113 } 114 115 private fun getCanonicalText( 116 type: PsiType, 117 elementAnnotations: List<AnnotationItem>? 118 ): String { 119 when (type) { 120 is PsiClassReferenceType -> return getCanonicalText(type, elementAnnotations) 121 is PsiPrimitiveType -> return getCanonicalText(type, elementAnnotations) 122 is PsiImmediateClassType -> return getCanonicalText(type, elementAnnotations) 123 is PsiEllipsisType -> return getText( 124 type, 125 getCanonicalText(type.componentType, null), 126 "..." 127 ) 128 is PsiArrayType -> return getCanonicalText(type, elementAnnotations) 129 is PsiWildcardType -> { 130 val bound = type.bound 131 // Don't include ! in type bounds 132 val suffix = if (bound == null) null else getCanonicalText(bound, elementAnnotations).removeSuffix("!") 133 return getText(type, suffix, elementAnnotations) 134 } 135 is PsiCapturedWildcardType -> 136 // Based on PsiCapturedWildcardType.getCanonicalText(true) 137 return getCanonicalText(type.wildcard, elementAnnotations) 138 is PsiDisjunctionType -> 139 // Based on PsiDisjunctionType.getCanonicalText(true) 140 return StringUtil.join(type.disjunctions, { psiType -> 141 getCanonicalText( 142 psiType, 143 elementAnnotations 144 ) 145 }, " | ") 146 is PsiIntersectionType -> return getCanonicalText(type.conjuncts[0], elementAnnotations) 147 else -> return type.getCanonicalText(true) 148 } 149 } 150 151 // From PsiWildcardType.getText, with qualified always true 152 private fun getText( 153 type: PsiWildcardType, 154 suffix: String?, 155 elementAnnotations: List<AnnotationItem>? 156 ): String { 157 val annotations = type.annotations 158 if (annotations.isEmpty() && suffix == null) return "?" 159 160 val sb = StringBuilder() 161 appendAnnotations(sb, annotations, elementAnnotations) 162 if (suffix == null) { 163 sb.append('?') 164 } else { 165 if (suffix == JAVA_LANG_OBJECT && 166 !compatibility.includeExtendsObjectInWildcard && 167 type.isExtends 168 ) { 169 sb.append('?') 170 } else { 171 sb.append(if (type.isExtends) EXTENDS_PREFIX else SUPER_PREFIX) 172 sb.append(suffix) 173 } 174 } 175 return sb.toString() 176 } 177 178 // From PsiEllipsisType.getText, with qualified always true 179 private fun getText( 180 type: PsiEllipsisType, 181 prefix: String, 182 suffix: String 183 ): String { 184 val sb = StringBuilder(prefix.length + suffix.length) 185 sb.append(prefix) 186 val annotations = type.annotations 187 if (annotations.isNotEmpty()) { 188 appendAnnotations(sb, annotations, null) 189 } 190 sb.append(suffix) 191 192 // No kotlin style suffix here: vararg parameters aren't nullable 193 194 return sb.toString() 195 } 196 197 // From PsiArrayType.getCanonicalText(true)) 198 private fun getCanonicalText(type: PsiArrayType, elementAnnotations: List<AnnotationItem>?): String { 199 return getText(type, getCanonicalText(type.componentType, null), "[]", elementAnnotations) 200 } 201 202 // From PsiArrayType.getText(String,String,boolean,boolean), with qualified = true 203 private fun getText( 204 type: PsiArrayType, 205 prefix: String, 206 suffix: String, 207 elementAnnotations: List<AnnotationItem>? 208 ): String { 209 val sb = StringBuilder(prefix.length + suffix.length) 210 sb.append(prefix) 211 val annotations = type.annotations 212 213 if (annotations.isNotEmpty()) { 214 val originalLength = sb.length 215 sb.append(' ') 216 appendAnnotations(sb, annotations, elementAnnotations) 217 if (sb.length == originalLength + 1) { 218 // Didn't emit any annotations (e.g. skipped because only null annotations and replacing with ?) 219 sb.setLength(originalLength) 220 } 221 } 222 sb.append(suffix) 223 224 if (kotlinStyleNulls) { 225 appendNullnessSuffix(annotations, sb, elementAnnotations) 226 } 227 228 return sb.toString() 229 } 230 231 // Copied from PsiPrimitiveType.getCanonicalText(true)) 232 private fun getCanonicalText(type: PsiPrimitiveType, elementAnnotations: List<AnnotationItem>?): String { 233 return getText(type, elementAnnotations) 234 } 235 236 // Copied from PsiPrimitiveType.getText(boolean, boolean), with annotated = true and qualified = true 237 private fun getText( 238 type: PsiPrimitiveType, 239 elementAnnotations: List<AnnotationItem>? 240 ): String { 241 val annotations = type.annotations 242 if (annotations.isEmpty()) return type.name 243 244 val sb = StringBuilder() 245 appendAnnotations(sb, annotations, elementAnnotations) 246 sb.append(type.name) 247 return sb.toString() 248 } 249 250 private fun getCanonicalText(type: PsiClassReferenceType, elementAnnotations: List<AnnotationItem>?): String { 251 val reference = type.reference 252 if (reference is PsiAnnotatedJavaCodeReferenceElement) { 253 val annotations = type.annotations 254 255 when (reference) { 256 is ClsJavaCodeReferenceElementImpl -> { 257 // From ClsJavaCodeReferenceElementImpl.getCanonicalText(boolean PsiAnnotation[]) 258 val text = reference.getCanonicalText() 259 260 val sb = StringBuilder() 261 262 val prefix = getOuterClassRef(text) 263 var tailStart = 0 264 if (!StringUtil.isEmpty(prefix)) { 265 sb.append(prefix).append('.') 266 tailStart = prefix.length + 1 267 } 268 269 appendAnnotations(sb, listOf(*annotations), elementAnnotations) 270 271 sb.append(text, tailStart, text.length) 272 273 if (kotlinStyleNulls) { 274 appendNullnessSuffix(annotations, sb, elementAnnotations) 275 } 276 277 return sb.toString() 278 } 279 is PsiJavaCodeReferenceElementImpl -> return getCanonicalText( 280 reference, 281 annotations, 282 reference.containingFile, 283 elementAnnotations, 284 kotlinStyleNulls 285 ) 286 else -> // Unexpected implementation: fallback 287 return reference.getCanonicalText(true, if (annotations.isEmpty()) null else annotations) 288 } 289 } 290 return reference.canonicalText 291 } 292 293 // From PsiJavaCodeReferenceElementImpl.getCanonicalText(bool PsiAnnotation[], PsiFile) 294 private fun getCanonicalText( 295 reference: PsiJavaCodeReferenceElementImpl, 296 annotations: Array<PsiAnnotation>?, 297 containingFile: PsiFile, 298 elementAnnotations: List<AnnotationItem>?, 299 allowKotlinSuffix: Boolean 300 ): String { 301 var remaining = annotations 302 when (val kind = reference.getKindEnum(containingFile)) { 303 PsiJavaCodeReferenceElementImpl.Kind.CLASS_NAME_KIND, 304 PsiJavaCodeReferenceElementImpl.Kind.CLASS_OR_PACKAGE_NAME_KIND, 305 PsiJavaCodeReferenceElementImpl.Kind.CLASS_IN_QUALIFIED_NEW_KIND -> { 306 val results = PsiImplUtil.multiResolveImpl( 307 containingFile.project, 308 containingFile, 309 reference, 310 false, 311 PsiReferenceExpressionImpl.OurGenericsResolver.INSTANCE 312 ) 313 when (val target = if (results.size == 1) results[0].element else null) { 314 is PsiClass -> { 315 val buffer = StringBuilder() 316 val qualifier = reference.qualifier 317 var prefix: String? = null 318 if (qualifier is PsiJavaCodeReferenceElementImpl) { 319 prefix = getCanonicalText( 320 qualifier, 321 remaining, 322 containingFile, 323 null, 324 false 325 ) 326 remaining = null 327 } else { 328 val fqn = target.qualifiedName 329 if (fqn != null) { 330 prefix = StringUtil.getPackageName(fqn) 331 } 332 } 333 334 if (!StringUtil.isEmpty(prefix)) { 335 buffer.append(prefix) 336 buffer.append('.') 337 } 338 339 val list = if (remaining != null) listOf(*remaining) else getAnnotations(reference) 340 appendAnnotations(buffer, list, elementAnnotations) 341 342 buffer.append(target.name) 343 344 appendTypeArgs( 345 buffer, 346 reference.typeParameters, 347 null 348 ) 349 350 if (allowKotlinSuffix && kotlinStyleNulls) { 351 appendNullnessSuffix(list, buffer, elementAnnotations) 352 } 353 354 return buffer.toString() 355 } 356 is PsiPackage -> return target.qualifiedName 357 else -> return JavaSourceUtil.getReferenceText(reference) 358 } 359 } 360 361 PsiJavaCodeReferenceElementImpl.Kind.PACKAGE_NAME_KIND, 362 PsiJavaCodeReferenceElementImpl.Kind.CLASS_FQ_NAME_KIND, 363 PsiJavaCodeReferenceElementImpl.Kind.CLASS_FQ_OR_PACKAGE_NAME_KIND -> 364 return JavaSourceUtil.getReferenceText(reference) 365 366 else -> { 367 error("Unexpected kind $kind") 368 } 369 } 370 } 371 372 private fun getNullable(list: List<PsiAnnotation>?, elementAnnotations: List<AnnotationItem>?): Boolean? { 373 if (elementAnnotations != null) { 374 for (annotation in elementAnnotations) { 375 if (annotation.isNullable()) { 376 return true 377 } else if (annotation.isNonNull()) { 378 return false 379 } 380 } 381 } 382 383 list ?: return null 384 385 for (annotation in list) { 386 val name = annotation.qualifiedName ?: continue 387 if (isNullableAnnotation(name)) { 388 return true 389 } else if (isNonNullAnnotation(name)) { 390 return false 391 } 392 } 393 394 return null 395 } 396 397 private fun getNullable(list: Array<PsiAnnotation>?, elementAnnotations: List<AnnotationItem>?): Boolean? { 398 if (elementAnnotations != null) { 399 for (annotation in elementAnnotations) { 400 if (annotation.isNullable()) { 401 return true 402 } else if (annotation.isNonNull()) { 403 return false 404 } 405 } 406 } 407 408 list ?: return null 409 410 for (annotation in list) { 411 val name = annotation.qualifiedName ?: continue 412 if (isNullableAnnotation(name)) { 413 return true 414 } else if (isNonNullAnnotation(name)) { 415 return false 416 } 417 } 418 419 return null 420 } 421 422 // From PsiNameHelper.appendTypeArgs, but with annotated = true and canonical = true 423 private fun appendTypeArgs( 424 sb: StringBuilder, 425 types: Array<PsiType>, 426 elementAnnotations: List<AnnotationItem>? 427 ) { 428 if (types.isEmpty()) return 429 430 sb.append('<') 431 for (i in types.indices) { 432 if (i > 0) { 433 sb.append(if (!compatibility.spaceAfterCommaInTypes) "," else ", ") 434 } 435 436 val type = types[i] 437 sb.append(getCanonicalText(type, elementAnnotations)) 438 } 439 sb.append('>') 440 } 441 442 // From PsiJavaCodeReferenceElementImpl.getAnnotations() 443 private fun getAnnotations(reference: PsiJavaCodeReferenceElementImpl): List<PsiAnnotation> { 444 val annotations = PsiTreeUtil.getChildrenOfTypeAsList(reference, PsiAnnotation::class.java) 445 446 if (!reference.isQualified) { 447 val modifierList = PsiImplUtil.findNeighbourModifierList(reference) 448 if (modifierList != null) { 449 PsiImplUtil.collectTypeUseAnnotations(modifierList, annotations) 450 } 451 } 452 453 return annotations 454 } 455 456 // From ClsJavaCodeReferenceElementImpl 457 private fun getOuterClassRef(ref: String): String { 458 var stack = 0 459 for (i in ref.length - 1 downTo 0) { 460 when (ref[i]) { 461 '<' -> stack-- 462 '>' -> stack++ 463 '.' -> if (stack == 0) return ref.substring(0, i) 464 } 465 } 466 467 return "" 468 } 469 470 // From PsiNameHelper.appendAnnotations 471 472 private fun appendAnnotations( 473 sb: StringBuilder, 474 annotations: Array<PsiAnnotation>, 475 elementAnnotations: List<AnnotationItem>? 476 ): Boolean { 477 return appendAnnotations(sb, listOf(*annotations), elementAnnotations) 478 } 479 480 private fun mapAnnotation(qualifiedName: String?): String? { 481 qualifiedName ?: return null 482 if (kotlinStyleNulls && (isNullableAnnotation(qualifiedName) || isNonNullAnnotation(qualifiedName))) { 483 return null 484 } 485 if (!supportTypeUseAnnotations) { 486 return null 487 } 488 489 val mapped = 490 if (mapAnnotations) { 491 AnnotationItem.mapName(codebase, qualifiedName) ?: return null 492 } else { 493 qualifiedName 494 } 495 496 if (filter != null) { 497 val item = codebase.findClass(mapped) 498 if (item == null || !filter.test(item)) { 499 return null 500 } 501 } 502 503 return mapped 504 } 505 506 // From PsiNameHelper.appendAnnotations, with deltas to optionally map names 507 508 private fun appendAnnotations( 509 sb: StringBuilder, 510 annotations: List<PsiAnnotation>, 511 elementAnnotations: List<AnnotationItem>? 512 ): Boolean { 513 var updated = false 514 for (annotation in annotations) { 515 val name = mapAnnotation(annotation.qualifiedName) 516 if (name != null) { 517 sb.append('@').append(name).append(annotation.parameterList.text).append(' ') 518 updated = true 519 } 520 } 521 522 if (elementAnnotations != null) { 523 for (annotation in elementAnnotations) { 524 val name = mapAnnotation(annotation.qualifiedName()) 525 if (name != null) { 526 sb.append(annotation.toSource()).append(' ') 527 updated = true 528 } 529 } 530 } 531 return updated 532 } 533 534 // From PsiImmediateClassType 535 536 private fun getCanonicalText(type: PsiImmediateClassType, elementAnnotations: List<AnnotationItem>?): String { 537 return getText(type, elementAnnotations) 538 } 539 540 private fun getText( 541 type: PsiImmediateClassType, 542 elementAnnotations: List<AnnotationItem>? 543 ): String { 544 val cls = type.resolve() ?: return "" 545 val buffer = StringBuilder() 546 buildText(type, cls, PsiSubstitutor.EMPTY, buffer, elementAnnotations) 547 return buffer.toString() 548 } 549 550 private fun buildText( 551 type: PsiImmediateClassType, 552 aClass: PsiClass, 553 substitutor: PsiSubstitutor, 554 buffer: StringBuilder, 555 elementAnnotations: List<AnnotationItem>? 556 ) { 557 if (aClass is PsiAnonymousClass) { 558 val baseResolveResult = aClass.baseClassType.resolveGenerics() 559 val baseClass = baseResolveResult.element 560 if (baseClass != null) { 561 buildText(type, baseClass, baseResolveResult.substitutor, buffer, null) 562 } else { 563 buffer.append(aClass.baseClassReference.canonicalText) 564 } 565 return 566 } 567 568 var enclosingClass: PsiClass? = null 569 if (!aClass.hasModifierProperty(PsiModifier.STATIC)) { 570 val parent = aClass.parent 571 if (parent is PsiClass && parent !is PsiAnonymousClass) { 572 enclosingClass = parent 573 } 574 } 575 if (enclosingClass != null) { 576 buildText(type, enclosingClass, substitutor, buffer, null) 577 buffer.append('.') 578 } else { 579 val fqn = aClass.qualifiedName 580 if (fqn != null) { 581 val prefix = StringUtil.getPackageName(fqn) 582 if (!StringUtil.isEmpty(prefix)) { 583 buffer.append(prefix) 584 buffer.append('.') 585 } 586 } 587 } 588 589 val annotations = type.annotations 590 appendAnnotations(buffer, annotations, elementAnnotations) 591 592 buffer.append(aClass.name) 593 594 val typeParameters = aClass.typeParameters 595 if (typeParameters.isNotEmpty()) { 596 var pos = buffer.length 597 buffer.append('<') 598 599 for (i in typeParameters.indices) { 600 val typeParameter = typeParameters[i] 601 PsiUtilCore.ensureValid(typeParameter) 602 603 if (i > 0) { 604 buffer.append(',') 605 } 606 607 val substitutionResult = substitutor.substitute(typeParameter) 608 if (substitutionResult == null) { 609 buffer.setLength(pos) 610 pos = -1 611 break 612 } 613 PsiUtil.ensureValidType(substitutionResult) 614 615 buffer.append(getCanonicalText(substitutionResult, null)) // not passing in merge annotations here 616 } 617 618 if (pos >= 0) { 619 buffer.append('>') 620 } 621 } 622 623 if (kotlinStyleNulls) { 624 appendNullnessSuffix(annotations, buffer, elementAnnotations) 625 } 626 } 627 }