blob: be0d36c820102c7d9be8794745d995d35cec6e1e [file] [log] [blame]
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.tools.metalava.model.psi
import com.android.tools.lint.detector.api.getInternalName
import com.android.tools.metalava.compatibility
import com.android.tools.metalava.doclava1.ApiPredicate
import com.android.tools.metalava.model.AnnotationItem
import com.android.tools.metalava.model.ClassItem
import com.android.tools.metalava.model.Codebase
import com.android.tools.metalava.model.Item
import com.android.tools.metalava.model.MemberItem
import com.android.tools.metalava.model.SUPPORT_TYPE_USE_ANNOTATIONS
import com.android.tools.metalava.model.TypeItem
import com.android.tools.metalava.model.TypeParameterItem
import com.android.tools.metalava.model.text.TextTypeItem
import com.intellij.psi.JavaTokenType
import com.intellij.psi.PsiArrayType
import com.intellij.psi.PsiCapturedWildcardType
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiClassType
import com.intellij.psi.PsiCompiledElement
import com.intellij.psi.PsiDisjunctionType
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiEllipsisType
import com.intellij.psi.PsiIntersectionType
import com.intellij.psi.PsiJavaCodeReferenceElement
import com.intellij.psi.PsiJavaToken
import com.intellij.psi.PsiLambdaExpressionType
import com.intellij.psi.PsiPrimitiveType
import com.intellij.psi.PsiRecursiveElementVisitor
import com.intellij.psi.PsiReferenceList
import com.intellij.psi.PsiType
import com.intellij.psi.PsiTypeElement
import com.intellij.psi.PsiTypeParameter
import com.intellij.psi.PsiTypeParameterList
import com.intellij.psi.PsiTypeVisitor
import com.intellij.psi.PsiWhiteSpace
import com.intellij.psi.PsiWildcardType
import com.intellij.psi.util.PsiTypesUtil
import com.intellij.psi.util.TypeConversionUtil
/** Represents a type backed by PSI */
class PsiTypeItem private constructor(private val codebase: PsiBasedCodebase, private val psiType: PsiType) : TypeItem {
private var toString: String? = null
private var toAnnotatedString: String? = null
private var toInnerAnnotatedString: String? = null
private var toErasedString: String? = null
private var asClass: PsiClassItem? = null
override fun toString(): String {
return toTypeString()
}
override fun toTypeString(
outerAnnotations: Boolean,
innerAnnotations: Boolean,
erased: Boolean
): String {
assert(innerAnnotations || !outerAnnotations) // Can't supply outer=true,inner=false
return if (erased) {
if (SUPPORT_TYPE_USE_ANNOTATIONS && (innerAnnotations || outerAnnotations)) {
// Not cached: Not common
toTypeString(codebase, psiType, outerAnnotations, innerAnnotations, erased)
} else {
if (toErasedString == null) {
toErasedString = toTypeString(codebase, psiType, outerAnnotations, innerAnnotations, erased)
}
toErasedString!!
}
} else {
when {
SUPPORT_TYPE_USE_ANNOTATIONS && outerAnnotations -> {
if (toAnnotatedString == null) {
toAnnotatedString = TypeItem.formatType(
toTypeString(
codebase,
psiType,
outerAnnotations,
innerAnnotations,
erased
)
)
}
toAnnotatedString!!
}
SUPPORT_TYPE_USE_ANNOTATIONS && innerAnnotations -> {
if (toInnerAnnotatedString == null) {
toInnerAnnotatedString = TypeItem.formatType(
toTypeString(
codebase,
psiType,
outerAnnotations,
innerAnnotations,
erased
)
)
}
toInnerAnnotatedString!!
}
else -> {
if (toString == null) {
toString = TypeItem.formatType(getCanonicalText(psiType, annotated = false))
}
toString!!
}
}
}
}
override fun toErasedTypeString(): String {
return toTypeString(outerAnnotations = false, innerAnnotations = false, erased = true)
}
override fun arrayDimensions(): Int {
return psiType.arrayDimensions
}
override fun internalName(): String {
if (primitive) {
val signature = getPrimitiveSignature(toString())
if (signature != null) {
return signature
}
}
val sb = StringBuilder()
appendJvmSignature(sb, psiType)
return sb.toString()
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
return when (other) {
is TypeItem -> toTypeString().replace(" ", "") == other.toTypeString().replace(" ", "")
else -> false
}
}
override fun asClass(): PsiClassItem? {
if (asClass == null) {
asClass = codebase.findClass(psiType)
}
return asClass
}
override fun asTypeParameter(context: MemberItem?): TypeParameterItem? {
val cls = asClass() ?: return null
return cls as? PsiTypeParameterItem
}
override fun hashCode(): Int {
return psiType.hashCode()
}
override val primitive: Boolean
get() = psiType is PsiPrimitiveType
override fun defaultValue(): Any? {
return PsiTypesUtil.getDefaultValue(psiType)
}
override fun defaultValueString(): String {
return PsiTypesUtil.getDefaultValueOfType(psiType)
}
override fun typeArgumentClasses(): List<ClassItem> {
if (primitive) {
return emptyList()
}
val classes = mutableListOf<ClassItem>()
psiType.accept(object : PsiTypeVisitor<PsiType>() {
override fun visitType(type: PsiType?): PsiType? {
return type
}
override fun visitClassType(classType: PsiClassType): PsiType? {
codebase.findClass(classType)?.let {
if (!it.isTypeParameter && !classes.contains(it)) {
classes.add(it)
}
}
for (type in classType.parameters) {
type.accept(this)
}
return classType
}
override fun visitWildcardType(wildcardType: PsiWildcardType): PsiType? {
if (wildcardType.isExtends) {
wildcardType.extendsBound.accept(this)
}
if (wildcardType.isSuper) {
wildcardType.superBound.accept(this)
}
if (wildcardType.isBounded) {
wildcardType.bound?.accept(this)
}
return wildcardType
}
override fun visitPrimitiveType(primitiveType: PsiPrimitiveType): PsiType? {
return primitiveType
}
override fun visitEllipsisType(ellipsisType: PsiEllipsisType): PsiType? {
ellipsisType.componentType.accept(this)
return ellipsisType
}
override fun visitArrayType(arrayType: PsiArrayType): PsiType? {
arrayType.componentType.accept(this)
return arrayType
}
override fun visitLambdaExpressionType(lambdaExpressionType: PsiLambdaExpressionType): PsiType? {
for (superType in lambdaExpressionType.superTypes) {
superType.accept(this)
}
return lambdaExpressionType
}
override fun visitCapturedWildcardType(capturedWildcardType: PsiCapturedWildcardType): PsiType? {
capturedWildcardType.upperBound.accept(this)
return capturedWildcardType
}
override fun visitDisjunctionType(disjunctionType: PsiDisjunctionType): PsiType? {
for (type in disjunctionType.disjunctions) {
type.accept(this)
}
return disjunctionType
}
override fun visitIntersectionType(intersectionType: PsiIntersectionType): PsiType? {
for (type in intersectionType.conjuncts) {
type.accept(this)
}
return intersectionType
}
})
return classes
}
override fun convertType(replacementMap: Map<String, String>?, owner: Item?): TypeItem {
val s = convertTypeString(replacementMap)
return create(codebase, codebase.createPsiType(s, owner?.psi()))
}
override fun hasTypeArguments(): Boolean = psiType is PsiClassType && psiType.hasParameters()
override fun markRecent() {
toAnnotatedString = toTypeString(false, true, false).replace(".NonNull", ".RecentlyNonNull")
toInnerAnnotatedString = toTypeString(true, true, false).replace(".NonNull", ".RecentlyNonNull")
}
companion object {
private fun getPrimitiveSignature(typeName: String): String? = when (typeName) {
"boolean" -> "Z"
"byte" -> "B"
"char" -> "C"
"short" -> "S"
"int" -> "I"
"long" -> "J"
"float" -> "F"
"double" -> "D"
"void" -> "V"
else -> null
}
private fun appendJvmSignature(
buffer: StringBuilder,
type: PsiType?
): Boolean {
if (type == null) {
return false
}
val psiType = TypeConversionUtil.erasure(type)
when (psiType) {
is PsiArrayType -> {
buffer.append('[')
appendJvmSignature(buffer, psiType.componentType)
}
is PsiClassType -> {
val resolved = psiType.resolve() ?: return false
if (!appendJvmTypeName(buffer, resolved)) {
return false
}
}
is PsiPrimitiveType -> buffer.append(getPrimitiveSignature(psiType.canonicalText))
else -> return false
}
return true
}
private fun appendJvmTypeName(
signature: StringBuilder,
outerClass: PsiClass
): Boolean {
val className = getInternalName(outerClass) ?: return false
signature.append('L').append(className).append(';')
return true
}
fun toTypeString(
codebase: Codebase,
type: PsiType,
outerAnnotations: Boolean,
innerAnnotations: Boolean,
erased: Boolean
): String {
if (erased) {
// Recurse with raw type and erase=false
return toTypeString(
codebase,
TypeConversionUtil.erasure(type),
outerAnnotations,
innerAnnotations,
false
)
}
if (SUPPORT_TYPE_USE_ANNOTATIONS && (innerAnnotations || outerAnnotations)) {
val typeString = mapAnnotations(codebase, getCanonicalText(type, true))
if (!outerAnnotations && typeString.contains("@")) {
// Temporary hack: should use PSI type visitor instead
return TextTypeItem.eraseAnnotations(typeString, false, true)
}
return typeString
} else {
return type.canonicalText
}
}
/**
* Replace annotations in the given type string with the mapped qualified names
* to [AnnotationItem.mapName]
*/
private fun mapAnnotations(codebase: Codebase, string: String): String {
var s = string
var offset = s.length
while (true) {
val start = s.lastIndexOf('@', offset)
if (start == -1) {
return s
}
var index = start + 1
val length = string.length
while (index < length) {
val c = string[index]
if (c != '.' && !Character.isJavaIdentifierPart(c)) {
break
}
index++
}
val annotation = string.substring(start + 1, index)
val mapped = AnnotationItem.mapName(codebase, annotation, ApiPredicate(codebase))
if (mapped != null) {
if (mapped != annotation) {
s = string.substring(0, start + 1) + mapped + s.substring(index)
}
} else {
var balance = 0
// Find annotation end
while (index < length) {
val c = string[index]
if (c == '(') {
balance++
} else if (c == ')') {
balance--
if (balance == 0) {
index++
break
}
} else if (c != ' ' && balance == 0) {
break
}
index++
}
s = string.substring(0, start) + s.substring(index)
}
offset = start - 1
}
}
private fun getCanonicalText(type: PsiType, annotated: Boolean): String {
val typeString = type.getCanonicalText(annotated && SUPPORT_TYPE_USE_ANNOTATIONS)
if (!annotated || !SUPPORT_TYPE_USE_ANNOTATIONS) {
return typeString
}
val index = typeString.indexOf(".@")
if (index != -1) {
// Work around type bugs in PSI: when you have a type like this:
// @android.support.annotation.NonNull java.lang.Float)
// PSI returns
// @android.support.annotation.NonNull java.lang.@android.support.annotation.NonNull Float)
//
//
// ...but sadly it's less predictable; e.g. it can be
// java.util.List<@android.support.annotation.Nullable java.lang.String>
// PSI returns
// java.util.List<java.lang.@android.support.annotation.Nullable String>
// Here we try to reverse this:
val end = typeString.indexOf(' ', index)
if (end != -1) {
val annotation = typeString.substring(index + 1, end)
if (typeString.lastIndexOf(annotation, index) == -1) {
// Find out where to place it
var ci = index
while (ci > 0) {
val c = typeString[ci]
if (c != '.' && !Character.isJavaIdentifierPart(c)) {
ci++
break
}
ci--
}
return typeString.substring(0, ci) +
annotation + " " +
typeString.substring(ci, index + 1) +
typeString.substring(end + 1)
} else {
return typeString.substring(0, index + 1) + typeString.substring(end + 1)
}
}
}
return typeString
}
fun create(codebase: PsiBasedCodebase, psiType: PsiType): PsiTypeItem {
return PsiTypeItem(codebase, psiType)
}
fun create(codebase: PsiBasedCodebase, original: PsiTypeItem): PsiTypeItem {
return PsiTypeItem(codebase, original.psiType)
}
fun typeParameterList(typeList: PsiTypeParameterList?): String? {
if (typeList != null && typeList.typeParameters.isNotEmpty()) {
// TODO: Filter the type list classes? Try to construct a typelist of a private API!
// We can't just use typeList.text here, because that just
// uses the declaration from the source, which may not be
// fully qualified - e.g. we might get
// <T extends View> instead of <T extends android.view.View>
// Therefore, we'll need to compute it ourselves; I can't find
// a utility for this
val sb = StringBuilder()
typeList.accept(object : PsiRecursiveElementVisitor() {
override fun visitElement(element: PsiElement) {
if (element is PsiTypeParameterList) {
val typeParameters = element.typeParameters
if (typeParameters.isEmpty()) {
return
}
sb.append("<")
var first = true
for (parameter in typeParameters) {
if (!first) {
sb.append(", ")
}
first = false
visitElement(parameter)
}
sb.append(">")
return
} else if (element is PsiTypeParameter) {
sb.append(element.name)
// TODO: How do I get super -- e.g. "Comparable<? super T>"
val extendsList = element.extendsList
val refList = extendsList.referenceElements
if (refList.isNotEmpty()) {
sb.append(" extends ")
var first = true
for (refElement in refList) {
if (!first) {
sb.append(" & ")
} else {
first = false
}
if (refElement is PsiJavaCodeReferenceElement) {
visitElement(refElement)
continue
}
val resolved = refElement.resolve()
if (resolved is PsiClass) {
sb.append(resolved.qualifiedName ?: resolved.name)
resolved.typeParameterList?.accept(this)
} else {
sb.append(refElement.referenceName)
}
}
} else {
val extendsListTypes = element.extendsListTypes
if (extendsListTypes.isNotEmpty()) {
sb.append(" extends ")
var first = true
for (type in extendsListTypes) {
if (!first) {
sb.append(" & ")
} else {
first = false
}
val resolved = type.resolve()
if (resolved == null) {
sb.append(type.className)
} else {
sb.append(resolved.qualifiedName ?: resolved.name)
resolved.typeParameterList?.accept(this)
}
}
}
}
return
} else if (element is PsiJavaCodeReferenceElement) {
val resolved = element.resolve()
if (resolved is PsiClass) {
if (resolved.qualifiedName == null) {
sb.append(resolved.name)
} else {
sb.append(resolved.qualifiedName)
}
val typeParameters = element.parameterList
if (typeParameters != null) {
val typeParameterElements = typeParameters.typeParameterElements
if (typeParameterElements.isEmpty()) {
return
}
// When reading in this from bytecode, the order is sometimes wrong
// (for example, for
// public interface BaseStream<T, S extends BaseStream<T, S>>
// the extends type BaseStream<T, S> will return the typeParameterElements
// as [S,T] instead of [T,S]. However, the typeParameters.typeArguments
// list is correct, so order the elements by the typeArguments array instead
// Special case: just one type argument: no sorting issue
if (typeParameterElements.size == 1) {
sb.append("<")
var first = true
for (parameter in typeParameterElements) {
if (!first) {
sb.append(", ")
}
first = false
visitElement(parameter)
}
sb.append(">")
return
}
// More than one type argument
val typeArguments = typeParameters.typeArguments
if (typeArguments.isNotEmpty()) {
sb.append("<")
var first = true
for (parameter in typeArguments) {
if (!first) {
sb.append(", ")
}
first = false
// Try to match up a type parameter element
var found = false
for (typeElement in typeParameterElements) {
if (parameter == typeElement.type) {
found = true
visitElement(typeElement)
break
}
}
if (!found) {
// No type element matched: use type instead
val classType = PsiTypesUtil.getPsiClass(parameter)
if (classType != null) {
visitElement(classType)
} else {
sb.append(parameter.canonicalText)
}
}
}
sb.append(">")
}
}
return
}
} else if (element is PsiTypeElement) {
val type = element.type
if (type is PsiWildcardType) {
sb.append("?")
if (type.isBounded) {
if (type.isExtends) {
sb.append(" extends ")
sb.append(type.extendsBound.canonicalText)
}
if (type.isSuper) {
sb.append(" super ")
sb.append(type.superBound.canonicalText)
}
}
return
}
sb.append(type.canonicalText)
return
} else if (element is PsiJavaToken && element.tokenType == JavaTokenType.COMMA) {
sb.append(",")
if (compatibility.spaceAfterCommaInTypes) {
if (element.nextSibling == null || element.nextSibling !is PsiWhiteSpace) {
sb.append(" ")
}
}
return
}
if (element.firstChild == null) { // leaf nodes only
if (element is PsiCompiledElement) {
if (element is PsiReferenceList) {
val referencedTypes = element.referencedTypes
var first = true
for (referenceType in referencedTypes) {
if (first) {
first = false
} else {
sb.append(", ")
}
sb.append(referenceType.canonicalText)
}
}
} else {
sb.append(element.text)
}
}
super.visitElement(element)
}
})
val typeString = sb.toString()
return TypeItem.cleanupGenerics(typeString)
}
return null
}
fun typeParameterClasses(codebase: PsiBasedCodebase, typeList: PsiTypeParameterList?): List<ClassItem> {
if (typeList != null && typeList.typeParameters.isNotEmpty()) {
val list = mutableListOf<ClassItem>()
typeList.accept(object : PsiRecursiveElementVisitor() {
override fun visitElement(element: PsiElement) {
if (element is PsiTypeParameterList) {
val typeParameters = element.typeParameters
for (parameter in typeParameters) {
visitElement(parameter)
}
return
} else if (element is PsiTypeParameter) {
val extendsList = element.extendsList
val refList = extendsList.referenceElements
if (refList.isNotEmpty()) {
for (refElement in refList) {
if (refElement is PsiJavaCodeReferenceElement) {
visitElement(refElement)
continue
}
val resolved = refElement.resolve()
if (resolved is PsiClass) {
addRealClass(
list,
codebase.findOrCreateClass(resolved)
)
resolved.typeParameterList?.accept(this)
}
}
} else {
val extendsListTypes = element.extendsListTypes
if (extendsListTypes.isNotEmpty()) {
for (type in extendsListTypes) {
val resolved = type.resolve()
if (resolved != null) {
addRealClass(
list, codebase.findOrCreateClass(resolved)
)
resolved.typeParameterList?.accept(this)
}
}
}
}
return
} else if (element is PsiJavaCodeReferenceElement) {
val resolved = element.resolve()
if (resolved is PsiClass) {
addRealClass(
list,
codebase.findOrCreateClass(resolved)
)
element.parameterList?.accept(this)
return
}
} else if (element is PsiTypeElement) {
val type = element.type
if (type is PsiWildcardType) {
if (type.isBounded) {
addRealClass(
codebase,
list, type.bound
)
}
if (type.isExtends) {
addRealClass(
codebase,
list, type.extendsBound
)
}
if (type.isSuper) {
addRealClass(
codebase,
list, type.superBound
)
}
return
}
return
}
super.visitElement(element)
}
})
return list
} else {
return emptyList()
}
}
private fun addRealClass(codebase: PsiBasedCodebase, classes: MutableList<ClassItem>, type: PsiType?) {
codebase.findClass(type ?: return)?.let {
addRealClass(classes, it)
}
}
private fun addRealClass(classes: MutableList<ClassItem>, cls: ClassItem) {
if (!cls.isTypeParameter && !classes.contains(cls)) { // typically small number of items, don't need Set
classes.add(cls)
}
}
}
}