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