1 /* 2 * 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.tools.lint.detector.api.getInternalName 20 import com.android.tools.metalava.compatibility 21 import com.android.tools.metalava.doclava1.ApiPredicate 22 import com.android.tools.metalava.model.AnnotationItem 23 import com.android.tools.metalava.model.ClassItem 24 import com.android.tools.metalava.model.Codebase 25 import com.android.tools.metalava.model.Item 26 import com.android.tools.metalava.model.MemberItem 27 import com.android.tools.metalava.model.TypeItem 28 import com.android.tools.metalava.model.TypeParameterItem 29 import com.android.tools.metalava.model.text.TextTypeItem 30 import com.intellij.psi.JavaTokenType 31 import com.intellij.psi.PsiArrayType 32 import com.intellij.psi.PsiCapturedWildcardType 33 import com.intellij.psi.PsiClass 34 import com.intellij.psi.PsiClassType 35 import com.intellij.psi.PsiCompiledElement 36 import com.intellij.psi.PsiDisjunctionType 37 import com.intellij.psi.PsiElement 38 import com.intellij.psi.PsiEllipsisType 39 import com.intellij.psi.PsiIntersectionType 40 import com.intellij.psi.PsiJavaCodeReferenceElement 41 import com.intellij.psi.PsiJavaToken 42 import com.intellij.psi.PsiLambdaExpressionType 43 import com.intellij.psi.PsiPrimitiveType 44 import com.intellij.psi.PsiRecursiveElementVisitor 45 import com.intellij.psi.PsiReferenceList 46 import com.intellij.psi.PsiType 47 import com.intellij.psi.PsiTypeElement 48 import com.intellij.psi.PsiTypeParameter 49 import com.intellij.psi.PsiTypeParameterList 50 import com.intellij.psi.PsiTypeVisitor 51 import com.intellij.psi.PsiWhiteSpace 52 import com.intellij.psi.PsiWildcardType 53 import com.intellij.psi.util.PsiTypesUtil 54 import com.intellij.psi.util.TypeConversionUtil 55 56 /** Represents a type backed by PSI */ 57 class PsiTypeItem private constructor(private val codebase: PsiBasedCodebase, private val psiType: PsiType) : TypeItem { 58 private var toString: String? = null 59 private var toAnnotatedString: String? = null 60 private var toInnerAnnotatedString: String? = null 61 private var toErasedString: String? = null 62 private var asClass: PsiClassItem? = null 63 64 override fun toString(): String { 65 return toTypeString() 66 } 67 68 override fun toTypeString( 69 outerAnnotations: Boolean, 70 innerAnnotations: Boolean, 71 erased: Boolean 72 ): String { 73 assert(innerAnnotations || !outerAnnotations) // Can't supply outer=true,inner=false 74 75 return if (erased) { 76 if (innerAnnotations || outerAnnotations) { 77 // Not cached: Not common 78 toTypeString(codebase, psiType, outerAnnotations, innerAnnotations, erased) 79 } else { 80 if (toErasedString == null) { 81 toErasedString = toTypeString(codebase, psiType, outerAnnotations, innerAnnotations, erased) 82 } 83 toErasedString!! 84 } 85 } else { 86 when { 87 outerAnnotations -> { 88 if (toAnnotatedString == null) { 89 toAnnotatedString = TypeItem.formatType( 90 toTypeString( 91 codebase, 92 psiType, 93 outerAnnotations, 94 innerAnnotations, 95 erased 96 ) 97 ) 98 } 99 toAnnotatedString!! 100 } 101 innerAnnotations -> { 102 if (toInnerAnnotatedString == null) { 103 toInnerAnnotatedString = TypeItem.formatType( 104 toTypeString( 105 codebase, 106 psiType, 107 outerAnnotations, 108 innerAnnotations, 109 erased 110 ) 111 ) 112 } 113 toInnerAnnotatedString!! 114 } 115 else -> { 116 if (toString == null) { 117 toString = TypeItem.formatType(getCanonicalText(psiType, annotated = false)) 118 } 119 toString!! 120 } 121 } 122 } 123 } 124 125 override fun toErasedTypeString(): String { 126 return toTypeString(outerAnnotations = false, innerAnnotations = false, erased = true) 127 } 128 129 override fun arrayDimensions(): Int { 130 return psiType.arrayDimensions 131 } 132 133 override fun internalName(): String { 134 if (primitive) { 135 val signature = getPrimitiveSignature(toString()) 136 if (signature != null) { 137 return signature 138 } 139 } 140 val sb = StringBuilder() 141 appendJvmSignature(sb, psiType) 142 return sb.toString() 143 } 144 145 override fun equals(other: Any?): Boolean { 146 if (this === other) return true 147 148 return when (other) { 149 is TypeItem -> toTypeString().replace(" ", "") == other.toTypeString().replace(" ", "") 150 else -> false 151 } 152 } 153 154 override fun asClass(): PsiClassItem? { 155 if (asClass == null) { 156 asClass = codebase.findClass(psiType) 157 } 158 return asClass 159 } 160 161 override fun asTypeParameter(context: MemberItem?): TypeParameterItem? { 162 val cls = asClass() ?: return null 163 return cls as? PsiTypeParameterItem 164 } 165 166 override fun hashCode(): Int { 167 return psiType.hashCode() 168 } 169 170 override val primitive: Boolean 171 get() = psiType is PsiPrimitiveType 172 173 override fun defaultValue(): Any? { 174 return PsiTypesUtil.getDefaultValue(psiType) 175 } 176 177 override fun defaultValueString(): String { 178 return PsiTypesUtil.getDefaultValueOfType(psiType) 179 } 180 181 override fun typeArgumentClasses(): List<ClassItem> { 182 if (primitive) { 183 return emptyList() 184 } 185 186 val classes = mutableListOf<ClassItem>() 187 psiType.accept(object : PsiTypeVisitor<PsiType>() { 188 override fun visitType(type: PsiType?): PsiType? { 189 return type 190 } 191 192 override fun visitClassType(classType: PsiClassType): PsiType? { 193 codebase.findClass(classType)?.let { 194 if (!it.isTypeParameter && !classes.contains(it)) { 195 classes.add(it) 196 } 197 } 198 for (type in classType.parameters) { 199 type.accept(this) 200 } 201 return classType 202 } 203 204 override fun visitWildcardType(wildcardType: PsiWildcardType): PsiType? { 205 if (wildcardType.isExtends) { 206 wildcardType.extendsBound.accept(this) 207 } 208 if (wildcardType.isSuper) { 209 wildcardType.superBound.accept(this) 210 } 211 if (wildcardType.isBounded) { 212 wildcardType.bound?.accept(this) 213 } 214 return wildcardType 215 } 216 217 override fun visitPrimitiveType(primitiveType: PsiPrimitiveType): PsiType? { 218 return primitiveType 219 } 220 221 override fun visitEllipsisType(ellipsisType: PsiEllipsisType): PsiType? { 222 ellipsisType.componentType.accept(this) 223 return ellipsisType 224 } 225 226 override fun visitArrayType(arrayType: PsiArrayType): PsiType? { 227 arrayType.componentType.accept(this) 228 return arrayType 229 } 230 231 override fun visitLambdaExpressionType(lambdaExpressionType: PsiLambdaExpressionType): PsiType? { 232 for (superType in lambdaExpressionType.superTypes) { 233 superType.accept(this) 234 } 235 return lambdaExpressionType 236 } 237 238 override fun visitCapturedWildcardType(capturedWildcardType: PsiCapturedWildcardType): PsiType? { 239 capturedWildcardType.upperBound.accept(this) 240 return capturedWildcardType 241 } 242 243 override fun visitDisjunctionType(disjunctionType: PsiDisjunctionType): PsiType? { 244 for (type in disjunctionType.disjunctions) { 245 type.accept(this) 246 } 247 return disjunctionType 248 } 249 250 override fun visitIntersectionType(intersectionType: PsiIntersectionType): PsiType? { 251 for (type in intersectionType.conjuncts) { 252 type.accept(this) 253 } 254 return intersectionType 255 } 256 }) 257 258 return classes 259 } 260 261 override fun convertType(replacementMap: Map<String, String>?, owner: Item?): TypeItem { 262 val s = convertTypeString(replacementMap) 263 return create(codebase, codebase.createPsiType(s, owner?.psi())) 264 } 265 266 override fun hasTypeArguments(): Boolean = psiType is PsiClassType && psiType.hasParameters() 267 268 override fun markRecent() { 269 toAnnotatedString = toTypeString(false, true, false).replace(".NonNull", ".RecentlyNonNull") 270 toInnerAnnotatedString = toTypeString(true, true, false).replace(".NonNull", ".RecentlyNonNull") 271 } 272 273 companion object { 274 private fun getPrimitiveSignature(typeName: String): String? = when (typeName) { 275 "boolean" -> "Z" 276 "byte" -> "B" 277 "char" -> "C" 278 "short" -> "S" 279 "int" -> "I" 280 "long" -> "J" 281 "float" -> "F" 282 "double" -> "D" 283 "void" -> "V" 284 else -> null 285 } 286 287 private fun appendJvmSignature( 288 buffer: StringBuilder, 289 type: PsiType? 290 ): Boolean { 291 if (type == null) { 292 return false 293 } 294 295 val psiType = TypeConversionUtil.erasure(type) 296 297 when (psiType) { 298 is PsiArrayType -> { 299 buffer.append('[') 300 appendJvmSignature(buffer, psiType.componentType) 301 } 302 is PsiClassType -> { 303 val resolved = psiType.resolve() ?: return false 304 if (!appendJvmTypeName(buffer, resolved)) { 305 return false 306 } 307 } 308 is PsiPrimitiveType -> buffer.append(getPrimitiveSignature(psiType.canonicalText)) 309 else -> return false 310 } 311 return true 312 } 313 314 private fun appendJvmTypeName( 315 signature: StringBuilder, 316 outerClass: PsiClass 317 ): Boolean { 318 val className = getInternalName(outerClass) ?: return false 319 signature.append('L').append(className).append(';') 320 return true 321 } 322 323 fun toTypeString( 324 codebase: Codebase, 325 type: PsiType, 326 outerAnnotations: Boolean, 327 innerAnnotations: Boolean, 328 erased: Boolean 329 ): String { 330 331 if (erased) { 332 // Recurse with raw type and erase=false 333 return toTypeString( 334 codebase, 335 TypeConversionUtil.erasure(type), 336 outerAnnotations, 337 innerAnnotations, 338 false 339 ) 340 } 341 342 if (outerAnnotations || innerAnnotations) { 343 val typeString = mapAnnotations(codebase, getCanonicalText(type, true)) 344 if (!outerAnnotations && typeString.contains("@")) { 345 // Temporary hack: should use PSI type visitor instead 346 return TextTypeItem.eraseAnnotations(typeString, false, true) 347 } 348 return typeString 349 } else { 350 return type.canonicalText 351 } 352 } 353 354 /** 355 * Replace annotations in the given type string with the mapped qualified names 356 * to [AnnotationItem.mapName] 357 */ 358 private fun mapAnnotations(codebase: Codebase, string: String): String { 359 var s = string 360 var offset = s.length 361 while (true) { 362 val start = s.lastIndexOf('@', offset) 363 if (start == -1) { 364 return s 365 } 366 var index = start + 1 367 val length = string.length 368 while (index < length) { 369 val c = string[index] 370 if (c != '.' && !Character.isJavaIdentifierPart(c)) { 371 break 372 } 373 index++ 374 } 375 val annotation = string.substring(start + 1, index) 376 377 val mapped = AnnotationItem.mapName(codebase, annotation, ApiPredicate(codebase)) 378 if (mapped != null) { 379 if (mapped != annotation) { 380 s = string.substring(0, start + 1) + mapped + s.substring(index) 381 } 382 } else { 383 var balance = 0 384 // Find annotation end 385 while (index < length) { 386 val c = string[index] 387 if (c == '(') { 388 balance++ 389 } else if (c == ')') { 390 balance-- 391 if (balance == 0) { 392 index++ 393 break 394 } 395 } else if (c != ' ' && balance == 0) { 396 break 397 } 398 index++ 399 } 400 s = string.substring(0, start) + s.substring(index) 401 } 402 offset = start - 1 403 } 404 } 405 406 private fun getCanonicalText(type: PsiType, annotated: Boolean): String { 407 val typeString = type.getCanonicalText(annotated) 408 if (!annotated) { 409 return typeString 410 } 411 412 val index = typeString.indexOf(".@") 413 if (index != -1) { 414 // Work around type bugs in PSI: when you have a type like this: 415 // @android.support.annotation.NonNull java.lang.Float) 416 // PSI returns 417 // @android.support.annotation.NonNull java.lang. (at) android.support.annotation.NonNull Float) 418 // 419 // 420 // ...but sadly it's less predictable; e.g. it can be 421 // java.util.List<@android.support.annotation.Nullable java.lang.String> 422 // PSI returns 423 // java.util.List<java.lang. (at) android.support.annotation.Nullable String> 424 425 // Here we try to reverse this: 426 val end = typeString.indexOf(' ', index) 427 if (end != -1) { 428 val annotation = typeString.substring(index + 1, end) 429 if (typeString.lastIndexOf(annotation, index) == -1) { 430 // Find out where to place it 431 var ci = index 432 while (ci > 0) { 433 val c = typeString[ci] 434 if (c != '.' && !Character.isJavaIdentifierPart(c)) { 435 ci++ 436 break 437 } 438 ci-- 439 } 440 return typeString.substring(0, ci) + 441 annotation + " " + 442 typeString.substring(ci, index + 1) + 443 typeString.substring(end + 1) 444 } else { 445 return typeString.substring(0, index + 1) + typeString.substring(end + 1) 446 } 447 } 448 } 449 450 return typeString 451 } 452 453 fun create(codebase: PsiBasedCodebase, psiType: PsiType): PsiTypeItem { 454 return PsiTypeItem(codebase, psiType) 455 } 456 457 fun create(codebase: PsiBasedCodebase, original: PsiTypeItem): PsiTypeItem { 458 return PsiTypeItem(codebase, original.psiType) 459 } 460 461 fun typeParameterList(typeList: PsiTypeParameterList?): String? { 462 if (typeList != null && typeList.typeParameters.isNotEmpty()) { 463 // TODO: Filter the type list classes? Try to construct a typelist of a private API! 464 // We can't just use typeList.text here, because that just 465 // uses the declaration from the source, which may not be 466 // fully qualified - e.g. we might get 467 // <T extends View> instead of <T extends android.view.View> 468 // Therefore, we'll need to compute it ourselves; I can't find 469 // a utility for this 470 val sb = StringBuilder() 471 typeList.accept(object : PsiRecursiveElementVisitor() { 472 override fun visitElement(element: PsiElement) { 473 if (element is PsiTypeParameterList) { 474 val typeParameters = element.typeParameters 475 if (typeParameters.isEmpty()) { 476 return 477 } 478 sb.append("<") 479 var first = true 480 for (parameter in typeParameters) { 481 if (!first) { 482 sb.append(", ") 483 } 484 first = false 485 visitElement(parameter) 486 } 487 sb.append(">") 488 return 489 } else if (element is PsiTypeParameter) { 490 sb.append(element.name) 491 // TODO: How do I get super -- e.g. "Comparable<? super T>" 492 val extendsList = element.extendsList 493 val refList = extendsList.referenceElements 494 if (refList.isNotEmpty()) { 495 sb.append(" extends ") 496 var first = true 497 for (refElement in refList) { 498 if (!first) { 499 sb.append(" & ") 500 } else { 501 first = false 502 } 503 504 if (refElement is PsiJavaCodeReferenceElement) { 505 visitElement(refElement) 506 continue 507 } 508 val resolved = refElement.resolve() 509 if (resolved is PsiClass) { 510 sb.append(resolved.qualifiedName ?: resolved.name) 511 resolved.typeParameterList?.accept(this) 512 } else { 513 sb.append(refElement.referenceName) 514 } 515 } 516 } else { 517 val extendsListTypes = element.extendsListTypes 518 if (extendsListTypes.isNotEmpty()) { 519 sb.append(" extends ") 520 var first = true 521 for (type in extendsListTypes) { 522 if (!first) { 523 sb.append(" & ") 524 } else { 525 first = false 526 } 527 val resolved = type.resolve() 528 if (resolved == null) { 529 sb.append(type.className) 530 } else { 531 sb.append(resolved.qualifiedName ?: resolved.name) 532 resolved.typeParameterList?.accept(this) 533 } 534 } 535 } 536 } 537 return 538 } else if (element is PsiJavaCodeReferenceElement) { 539 val resolved = element.resolve() 540 if (resolved is PsiClass) { 541 if (resolved.qualifiedName == null) { 542 sb.append(resolved.name) 543 } else { 544 sb.append(resolved.qualifiedName) 545 } 546 val typeParameters = element.parameterList 547 if (typeParameters != null) { 548 val typeParameterElements = typeParameters.typeParameterElements 549 if (typeParameterElements.isEmpty()) { 550 return 551 } 552 553 // When reading in this from bytecode, the order is sometimes wrong 554 // (for example, for 555 // public interface BaseStream<T, S extends BaseStream<T, S>> 556 // the extends type BaseStream<T, S> will return the typeParameterElements 557 // as [S,T] instead of [T,S]. However, the typeParameters.typeArguments 558 // list is correct, so order the elements by the typeArguments array instead 559 560 // Special case: just one type argument: no sorting issue 561 if (typeParameterElements.size == 1) { 562 sb.append("<") 563 var first = true 564 for (parameter in typeParameterElements) { 565 if (!first) { 566 sb.append(", ") 567 } 568 first = false 569 visitElement(parameter) 570 } 571 sb.append(">") 572 return 573 } 574 575 // More than one type argument 576 577 val typeArguments = typeParameters.typeArguments 578 if (typeArguments.isNotEmpty()) { 579 sb.append("<") 580 var first = true 581 for (parameter in typeArguments) { 582 if (!first) { 583 sb.append(", ") 584 } 585 first = false 586 // Try to match up a type parameter element 587 var found = false 588 for (typeElement in typeParameterElements) { 589 if (parameter == typeElement.type) { 590 found = true 591 visitElement(typeElement) 592 break 593 } 594 } 595 if (!found) { 596 // No type element matched: use type instead 597 val classType = PsiTypesUtil.getPsiClass(parameter) 598 if (classType != null) { 599 visitElement(classType) 600 } else { 601 sb.append(parameter.canonicalText) 602 } 603 } 604 } 605 sb.append(">") 606 } 607 } 608 return 609 } 610 } else if (element is PsiTypeElement) { 611 val type = element.type 612 if (type is PsiWildcardType) { 613 sb.append("?") 614 if (type.isBounded) { 615 if (type.isExtends) { 616 sb.append(" extends ") 617 sb.append(type.extendsBound.canonicalText) 618 } 619 if (type.isSuper) { 620 sb.append(" super ") 621 sb.append(type.superBound.canonicalText) 622 } 623 } 624 return 625 } 626 sb.append(type.canonicalText) 627 return 628 } else if (element is PsiJavaToken && element.tokenType == JavaTokenType.COMMA) { 629 sb.append(",") 630 if (compatibility.spaceAfterCommaInTypes) { 631 if (element.nextSibling == null || element.nextSibling !is PsiWhiteSpace) { 632 sb.append(" ") 633 } 634 } 635 return 636 } 637 if (element.firstChild == null) { // leaf nodes only 638 if (element is PsiCompiledElement) { 639 if (element is PsiReferenceList) { 640 val referencedTypes = element.referencedTypes 641 var first = true 642 for (referenceType in referencedTypes) { 643 if (first) { 644 first = false 645 } else { 646 sb.append(", ") 647 } 648 sb.append(referenceType.canonicalText) 649 } 650 } 651 } else { 652 sb.append(element.text) 653 } 654 } 655 super.visitElement(element) 656 } 657 }) 658 659 val typeString = sb.toString() 660 return TypeItem.cleanupGenerics(typeString) 661 } 662 663 return null 664 } 665 666 fun typeParameterClasses(codebase: PsiBasedCodebase, typeList: PsiTypeParameterList?): List<ClassItem> { 667 if (typeList != null && typeList.typeParameters.isNotEmpty()) { 668 val list = mutableListOf<ClassItem>() 669 typeList.accept(object : PsiRecursiveElementVisitor() { 670 override fun visitElement(element: PsiElement) { 671 if (element is PsiTypeParameterList) { 672 val typeParameters = element.typeParameters 673 for (parameter in typeParameters) { 674 visitElement(parameter) 675 } 676 return 677 } else if (element is PsiTypeParameter) { 678 val extendsList = element.extendsList 679 val refList = extendsList.referenceElements 680 if (refList.isNotEmpty()) { 681 for (refElement in refList) { 682 if (refElement is PsiJavaCodeReferenceElement) { 683 visitElement(refElement) 684 continue 685 } 686 val resolved = refElement.resolve() 687 if (resolved is PsiClass) { 688 addRealClass( 689 list, 690 codebase.findOrCreateClass(resolved) 691 ) 692 resolved.typeParameterList?.accept(this) 693 } 694 } 695 } else { 696 val extendsListTypes = element.extendsListTypes 697 if (extendsListTypes.isNotEmpty()) { 698 for (type in extendsListTypes) { 699 val resolved = type.resolve() 700 if (resolved != null) { 701 addRealClass( 702 list, codebase.findOrCreateClass(resolved) 703 ) 704 resolved.typeParameterList?.accept(this) 705 } 706 } 707 } 708 } 709 return 710 } else if (element is PsiJavaCodeReferenceElement) { 711 val resolved = element.resolve() 712 if (resolved is PsiClass) { 713 addRealClass( 714 list, 715 codebase.findOrCreateClass(resolved) 716 ) 717 element.parameterList?.accept(this) 718 return 719 } 720 } else if (element is PsiTypeElement) { 721 val type = element.type 722 if (type is PsiWildcardType) { 723 if (type.isBounded) { 724 addRealClass( 725 codebase, 726 list, type.bound 727 ) 728 } 729 if (type.isExtends) { 730 addRealClass( 731 codebase, 732 list, type.extendsBound 733 ) 734 } 735 if (type.isSuper) { 736 addRealClass( 737 codebase, 738 list, type.superBound 739 ) 740 } 741 return 742 } 743 return 744 } 745 super.visitElement(element) 746 } 747 }) 748 749 return list 750 } else { 751 return emptyList() 752 } 753 } 754 755 private fun addRealClass(codebase: PsiBasedCodebase, classes: MutableList<ClassItem>, type: PsiType?) { 756 codebase.findClass(type ?: return)?.let { 757 addRealClass(classes, it) 758 } 759 } 760 761 private fun addRealClass(classes: MutableList<ClassItem>, cls: ClassItem) { 762 if (!cls.isTypeParameter && !classes.contains(cls)) { // typically small number of items, don't need Set 763 classes.add(cls) 764 } 765 } 766 } 767 }