blob: 93da31c0e233c9e23a966878e2b594c562ba3476 [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.metalava.compatibility
import com.android.tools.metalava.model.AnnotationRetention
import com.android.tools.metalava.model.ClassItem
import com.android.tools.metalava.model.CompilationUnit
import com.android.tools.metalava.model.ConstructorItem
import com.android.tools.metalava.model.FieldItem
import com.android.tools.metalava.model.MethodItem
import com.android.tools.metalava.model.PackageItem
import com.android.tools.metalava.model.PropertyItem
import com.android.tools.metalava.model.TypeItem
import com.android.tools.metalava.model.TypeParameterList
import com.android.tools.metalava.options
import com.intellij.lang.jvm.types.JvmReferenceType
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiClassType
import com.intellij.psi.PsiCompiledFile
import com.intellij.psi.PsiModifier
import com.intellij.psi.PsiModifierListOwner
import com.intellij.psi.PsiType
import com.intellij.psi.PsiTypeParameter
import com.intellij.psi.impl.source.PsiClassReferenceType
import com.intellij.psi.util.PsiUtil
import org.jetbrains.kotlin.psi.KtProperty
import org.jetbrains.uast.UMethod
open class PsiClassItem(
override val codebase: PsiBasedCodebase,
val psiClass: PsiClass,
private val name: String,
private val fullName: String,
private val qualifiedName: String,
private val hasImplicitDefaultConstructor: Boolean,
val classType: ClassType,
modifiers: PsiModifierItem,
documentation: String
) :
PsiItem(
codebase = codebase,
modifiers = modifiers,
documentation = documentation,
element = psiClass
), ClassItem {
lateinit var containingPackage: PsiPackageItem
override fun containingPackage(): PackageItem = containingClass?.containingPackage() ?: containingPackage
override fun simpleName(): String = name
override fun fullName(): String = fullName
override fun qualifiedName(): String = qualifiedName
override fun isInterface(): Boolean = classType == ClassType.INTERFACE
override fun isAnnotationType(): Boolean = classType == ClassType.ANNOTATION_TYPE
override fun isEnum(): Boolean = classType == ClassType.ENUM
override fun hasImplicitDefaultConstructor(): Boolean = hasImplicitDefaultConstructor
private var superClass: ClassItem? = null
private var superClassType: TypeItem? = null
override fun superClass(): ClassItem? = superClass
override fun superClassType(): TypeItem? = superClassType
override fun setSuperClass(superClass: ClassItem?, superClassType: TypeItem?) {
this.superClass = superClass
this.superClassType = superClassType
}
override var defaultConstructor: ConstructorItem? = null
override var artifact: String? = null
private var containingClass: PsiClassItem? = null
override fun containingClass(): PsiClassItem? = containingClass
// TODO: Come up with a better scheme for how to compute this
override var included: Boolean = true
override var hasPrivateConstructor: Boolean = false
override fun interfaceTypes(): List<TypeItem> = interfaceTypes
override fun setInterfaceTypes(interfaceTypes: List<TypeItem>) {
@Suppress("UNCHECKED_CAST")
setInterfaces(interfaceTypes as List<PsiTypeItem>)
}
private fun setInterfaces(interfaceTypes: List<PsiTypeItem>) {
this.interfaceTypes = interfaceTypes
}
private var allInterfaces: List<ClassItem>? = null
override fun allInterfaces(): Sequence<ClassItem> {
if (allInterfaces == null) {
val classes = mutableSetOf<PsiClass>()
var curr: PsiClass? = psiClass
while (curr != null) {
if (curr.isInterface && !classes.contains(curr)) {
classes.add(curr)
}
addInterfaces(classes, curr.interfaces)
curr = curr.superClass
}
val result = mutableListOf<ClassItem>()
for (cls in classes) {
val item = codebase.findOrCreateClass(cls)
result.add(item)
}
allInterfaces = result
}
return allInterfaces!!.asSequence()
}
private fun addInterfaces(result: MutableSet<PsiClass>, interfaces: Array<out PsiClass>) {
for (itf in interfaces) {
if (itf.isInterface && !result.contains(itf)) {
result.add(itf)
addInterfaces(result, itf.interfaces)
val superClass = itf.superClass
if (superClass != null) {
addInterfaces(result, arrayOf(superClass))
}
}
}
}
private lateinit var innerClasses: List<PsiClassItem>
private lateinit var interfaceTypes: List<TypeItem>
private lateinit var constructors: List<PsiConstructorItem>
private lateinit var methods: List<PsiMethodItem>
private lateinit var properties: List<PsiPropertyItem>
private lateinit var fields: List<FieldItem>
/**
* If this item was created by filtering down a different codebase, this temporarily
* points to the original item during construction. This is used to let us initialize
* for example throws lists later, when all classes in the codebase have been
* initialized.
*/
internal var source: PsiClassItem? = null
override fun innerClasses(): List<PsiClassItem> = innerClasses
override fun constructors(): List<PsiConstructorItem> = constructors
override fun methods(): List<PsiMethodItem> = methods
override fun properties(): List<PropertyItem> = properties
override fun fields(): List<FieldItem> = fields
override fun toType(): TypeItem {
return PsiTypeItem.create(codebase, codebase.getClassType(psiClass))
}
override fun hasTypeVariables(): Boolean = psiClass.hasTypeParameters()
override fun typeParameterList(): TypeParameterList {
if (psiClass.hasTypeParameters()) {
return PsiTypeParameterList(
codebase, psiClass.typeParameterList
?: return TypeParameterList.NONE
)
} else {
return TypeParameterList.NONE
}
}
override fun typeArgumentClasses(): List<ClassItem> {
return PsiTypeItem.typeParameterClasses(
codebase,
psiClass.typeParameterList
)
}
override val isTypeParameter: Boolean
get() = psiClass is PsiTypeParameter
override fun getCompilationUnit(): CompilationUnit? {
if (isInnerClass()) {
return null
}
val containingFile = psiClass.containingFile ?: return null
if (containingFile is PsiCompiledFile) {
return null
}
return PsiCompilationUnit(codebase, containingFile)
}
override fun finishInitialization() {
super.finishInitialization()
for (method in methods) {
method.finishInitialization()
}
for (method in constructors) {
method.finishInitialization()
}
for (field in fields) {
// There may be non-Psi fields here later (thanks to addField) but not during construction
(field as PsiFieldItem).finishInitialization()
}
for (inner in innerClasses) {
inner.finishInitialization()
}
// Delay initializing super classes and implemented interfaces for all inner classes: they may refer
// to *other* inner classes in this class, which would lead to an attempt to construct
// recursively. Instead, we wait until all the inner classes have been constructed, and at
// the very end, initialize super classes and interfaces recursively.
if (psiClass.containingClass == null) {
initializeSuperClasses()
}
}
private fun initializeSuperClasses() {
val extendsListTypes = psiClass.extendsListTypes
if (!extendsListTypes.isEmpty()) {
val type = PsiTypeItem.create(codebase, extendsListTypes[0])
this.superClassType = type
this.superClass = type.asClass()
} else {
val superType = psiClass.superClassType
if (superType is PsiType) {
this.superClassType = PsiTypeItem.create(codebase, superType)
this.superClass = this.superClassType?.asClass()
}
}
// Add interfaces. If this class is an interface, it can implement both
// classes from the extends clause and from the implements clause.
val interfaces = psiClass.implementsListTypes
setInterfaces(if (interfaces.isEmpty() && extendsListTypes.size <= 1) {
emptyList()
} else {
val result = ArrayList<PsiTypeItem>(interfaces.size + extendsListTypes.size - 1)
val create: (PsiClassType) -> PsiTypeItem = {
val type = PsiTypeItem.create(codebase, it)
type.asClass() // ensure that we initialize classes eagerly too such that they're registered etc
type
}
(1 until extendsListTypes.size).mapTo(result) { create(extendsListTypes[it]) }
interfaces.mapTo(result) { create(it) }
result
})
for (inner in innerClasses) {
inner.initializeSuperClasses()
}
}
protected fun initialize(
innerClasses: List<PsiClassItem>,
interfaceTypes: List<TypeItem>,
constructors: List<PsiConstructorItem>,
methods: List<PsiMethodItem>,
fields: List<FieldItem>
) {
this.innerClasses = innerClasses
this.interfaceTypes = interfaceTypes
this.constructors = constructors
this.methods = methods
this.fields = fields
}
override fun mapTypeVariables(target: ClassItem): Map<String, String> {
val targetPsi = target.psi() as PsiClass
val maps = mapTypeVariablesToSuperclass(
psiClass, targetPsi, considerSuperClasses = true,
considerInterfaces = targetPsi.isInterface
) ?: return emptyMap()
if (maps.isEmpty()) {
return emptyMap()
}
if (maps.size == 1) {
return maps[0]
}
val first = maps[0]
val flattened = mutableMapOf<String, String>()
for (key in first.keys) {
var variable: String? = key
for (map in maps) {
val value = map[variable]
variable = value
if (value == null) {
break
} else {
flattened[key] = value
}
}
}
return flattened
}
override fun equals(other: Any?): Boolean {
if (this === other) {
return true
}
return other is ClassItem && qualifiedName == other.qualifiedName()
}
/**
* Creates a constructor in this class
*/
override fun createDefaultConstructor(): ConstructorItem {
return PsiConstructorItem.createDefaultConstructor(codebase, this, psiClass)
}
override fun createMethod(template: MethodItem): MethodItem {
val method = template as PsiMethodItem
val replacementMap = mapTypeVariables(template.containingClass())
val newMethod: PsiMethodItem
if (replacementMap.isEmpty()) {
newMethod = PsiMethodItem.create(codebase, this, method)
} else {
val stub = method.toStub(replacementMap)
val psiMethod = codebase.createPsiMethod(stub, psiClass)
newMethod = PsiMethodItem.create(codebase, this, psiMethod)
newMethod.inheritedMethod = method.inheritedMethod
newMethod.documentation = method.documentation
}
if (template.throwsTypes().isEmpty()) {
newMethod.setThrowsTypes(emptyList())
} else {
val throwsTypes = mutableListOf<ClassItem>()
for (type in template.throwsTypes()) {
if (type.codebase === codebase) {
throwsTypes.add(type)
} else {
throwsTypes.add(codebase.findOrCreateClass(((type as PsiClassItem).psiClass)))
}
}
newMethod.setThrowsTypes(throwsTypes)
}
return newMethod
}
override fun addMethod(method: MethodItem) {
(methods as MutableList<PsiMethodItem>).add(method as PsiMethodItem)
}
private var retention: AnnotationRetention? = null
override fun getRetention(): AnnotationRetention {
retention?.let { return it }
if (!isAnnotationType()) {
error("getRetention() should only be called on annotation classes")
}
retention = ClassItem.findRetention(this)
return retention!!
}
override fun hashCode(): Int = qualifiedName.hashCode()
override fun toString(): String = "class ${qualifiedName()}"
companion object {
fun create(codebase: PsiBasedCodebase, psiClass: PsiClass): PsiClassItem {
if (psiClass is PsiTypeParameter) {
return PsiTypeParameterItem.create(codebase, psiClass)
}
val simpleName = psiClass.name!!
val fullName = computeFullClassName(psiClass)
val qualifiedName = psiClass.qualifiedName ?: simpleName
val hasImplicitDefaultConstructor = hasImplicitDefaultConstructor(psiClass)
val classType = ClassType.getClassType(psiClass)
val commentText = PsiItem.javadoc(psiClass)
val modifiers = modifiers(codebase, psiClass, commentText)
val item = PsiClassItem(
codebase = codebase,
psiClass = psiClass,
name = simpleName,
fullName = fullName,
qualifiedName = qualifiedName,
classType = classType,
hasImplicitDefaultConstructor = hasImplicitDefaultConstructor,
documentation = commentText,
modifiers = modifiers
)
codebase.registerClass(item)
item.modifiers.setOwner(item)
// Construct the children
val psiMethods = psiClass.methods
val methods: MutableList<PsiMethodItem> = ArrayList(psiMethods.size)
if (classType == ClassType.ENUM) {
addEnumMethods(codebase, item, psiClass, methods)
} else if (classType == ClassType.ANNOTATION_TYPE && !options.compatOutput &&
modifiers.findAnnotation("java.lang.annotation.Retention") == null
) {
// By policy, include explicit retention policy annotation if missing
modifiers.addAnnotation(
codebase.createAnnotation(
"@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS)",
item, false
)
)
}
val constructors: MutableList<PsiConstructorItem> = ArrayList(5)
for (psiMethod in psiMethods) {
if (psiMethod.isPrivate() || psiMethod.isPackagePrivate()) {
item.hasPrivateConstructor = true
}
if (psiMethod.isConstructor) {
val constructor = PsiConstructorItem.create(codebase, item, psiMethod)
constructors.add(constructor)
} else {
val method = PsiMethodItem.create(codebase, item, psiMethod)
methods.add(method)
}
}
if (hasImplicitDefaultConstructor) {
assert(constructors.isEmpty())
constructors.add(PsiConstructorItem.createDefaultConstructor(codebase, item, psiClass))
}
val fields: MutableList<FieldItem> = mutableListOf()
val psiFields = psiClass.fields
if (!psiFields.isEmpty()) {
psiFields.asSequence()
.mapTo(fields) {
PsiFieldItem.create(codebase, item, it)
}
}
if (classType == ClassType.INTERFACE) {
// All members are implicitly public, fields are implicitly static, non-static methods are abstract
// (except in Java 1.9, where they can be private
for (method in methods) {
if (!method.isPrivate) {
method.mutableModifiers().setPublic(true)
}
}
for (method in fields) {
val m = method.mutableModifiers()
m.setPublic(true)
m.setStatic(true)
}
}
item.constructors = constructors
item.methods = methods
item.fields = fields
item.properties = emptyList()
if (isKotlin(psiClass)) {
// Try to initialize the Kotlin properties
val properties = mutableListOf<PsiPropertyItem>()
for (method in psiMethods) {
if (method is UMethod) {
if (method.modifierList.hasModifierProperty(PsiModifier.STATIC)) {
// Skip extension properties
continue
}
val sourcePsi = method.sourcePsi
if (sourcePsi is KtProperty) {
if (method.name.startsWith("set")) {
continue
}
val name = sourcePsi.name ?: continue
val psiType = method.returnType ?: continue
properties.add(PsiPropertyItem.create(codebase, item, name, psiType, method))
}
}
}
item.properties = properties
}
val psiInnerClasses = psiClass.innerClasses
item.innerClasses = if (psiInnerClasses.isEmpty()) {
emptyList()
} else {
val result = psiInnerClasses.asSequence()
.map {
val inner = codebase.findOrCreateClass(it)
inner.containingClass = item
inner
}
.toMutableList()
result
}
return item
}
private fun addEnumMethods(
codebase: PsiBasedCodebase,
classItem: PsiClassItem,
psiClass: PsiClass,
result: MutableList<PsiMethodItem>
) {
// Add these two methods as overrides into the API; this isn't necessary but is done in the old
// API generator
// method public static android.graphics.ColorSpace.Adaptation valueOf(java.lang.String);
// method public static final android.graphics.ColorSpace.Adaptation[] values();
if (compatibility.defaultAnnotationMethods) {
// TODO: Skip if we already have these methods here (but that shouldn't happen; nobody would
// type this by hand)
addEnumMethod(
codebase, classItem,
psiClass, result,
"public static ${psiClass.qualifiedName} valueOf(java.lang.String s) { return null; }"
)
addEnumMethod(
codebase, classItem,
psiClass, result,
"public static final ${psiClass.qualifiedName}[] values() { return null; }"
)
// Also add a private constructor; used when emitting the private API
val psiMethod = codebase.createConstructor("private ${psiClass.name}", psiClass)
result.add(PsiConstructorItem.create(codebase, classItem, psiMethod))
}
}
private fun addEnumMethod(
codebase: PsiBasedCodebase,
classItem: PsiClassItem,
psiClass: PsiClass,
result: MutableList<PsiMethodItem>,
source: String
) {
val psiMethod = codebase.createPsiMethod(source, psiClass)
result.add(PsiMethodItem.create(codebase, classItem, psiMethod))
}
/**
* Computes the "full" class name; this is not the qualified class name (e.g. with package)
* but for an inner class it includes all the outer classes
*/
private fun computeFullClassName(cls: PsiClass): String {
if (cls.containingClass == null) {
val name = cls.name
return name!!
} else {
val list = mutableListOf<String>()
var curr: PsiClass? = cls
while (curr != null) {
val name = curr.name
curr = if (name != null) {
list.add(name)
curr.containingClass
} else {
break
}
}
return list.asReversed().asSequence().joinToString(separator = ".") { it }
}
}
private fun hasImplicitDefaultConstructor(psiClass: PsiClass): Boolean {
if (psiClass.name?.startsWith("-") == true) {
// Deliberately hidden; see examples like
// @file:JvmName("-ViewModelExtensions") // Hide from Java sources in the IDE.
return false
}
val constructors = psiClass.constructors
if (constructors.isEmpty() && !psiClass.isInterface && !psiClass.isAnnotationType && !psiClass.isEnum) {
if (PsiUtil.hasDefaultConstructor(psiClass)) {
return true
}
// The above method isn't always right; for example, for the ContactsContract.Presence class
// in the framework, which looks like this:
// @Deprecated
// public static final class Presence extends StatusUpdates {
// }
// javac makes a default constructor:
// public final class android.provider.ContactsContract$Presence extends android.provider.ContactsContract$StatusUpdates {
// public android.provider.ContactsContract$Presence();
// }
// but the above method returns false. So add some of our own heuristics:
if (psiClass.hasModifierProperty(PsiModifier.FINAL) && !psiClass.hasModifierProperty(
PsiModifier.ABSTRACT
) &&
psiClass.hasModifierProperty(PsiModifier.PUBLIC)
) {
return true
}
}
return false
}
fun mapTypeVariablesToSuperclass(
psiClass: PsiClass,
targetClass: PsiClass,
considerSuperClasses: Boolean = true,
considerInterfaces: Boolean = psiClass.isInterface
): MutableList<Map<String, String>>? {
// TODO: Prune search if type doesn't have type arguments!
if (considerSuperClasses) {
val list = mapTypeVariablesToSuperclass(
psiClass.superClassType, targetClass,
considerSuperClasses, considerInterfaces
)
if (list != null) {
return list
}
}
if (considerInterfaces) {
for (interfaceType in psiClass.interfaceTypes) {
val list = mapTypeVariablesToSuperclass(
interfaceType, targetClass,
considerSuperClasses, considerInterfaces
)
if (list != null) {
return list
}
}
}
return null
}
private fun mapTypeVariablesToSuperclass(
type: JvmReferenceType?,
targetClass: PsiClass,
considerSuperClasses: Boolean = true,
considerInterfaces: Boolean = true
): MutableList<Map<String, String>>? {
// TODO: Prune search if type doesn't have type arguments!
val superType = type as? PsiClassReferenceType
val superClass = superType?.resolve()
if (superClass != null) {
if (superClass == targetClass) {
val map = mapTypeVariablesToSuperclass(superType)
return if (map != null) {
mutableListOf(map)
} else {
null
}
} else {
val list = mapTypeVariablesToSuperclass(
superClass, targetClass, considerSuperClasses,
considerInterfaces
)
if (list != null) {
val map = mapTypeVariablesToSuperclass(superType)
if (map != null) {
list.add(map)
}
return list
}
}
}
return null
}
private fun mapTypeVariablesToSuperclass(superType: PsiClassReferenceType?): Map<String, String>? {
superType ?: return null
val map = mutableMapOf<String, String>()
val superClass = superType.resolve()
if (superClass != null && superType.hasParameters()) {
val superTypeParameters = superClass.typeParameters
superType.parameters.forEachIndexed { index, parameter ->
if (parameter is PsiClassReferenceType) {
val parameterClass = parameter.resolve()
if (parameterClass != null) {
val parameterName = parameterClass.qualifiedName ?: parameterClass.name ?: parameter.name
if (index < superTypeParameters.size) {
val superTypeParameter = superTypeParameters[index]
val superTypeName = superTypeParameter.qualifiedName ?: superTypeParameter.name
if (superTypeName != null) {
map[superTypeName] = parameterName
}
}
}
}
}
}
return map
}
}
}
fun PsiModifierListOwner.isPrivate(): Boolean = modifierList?.hasExplicitModifier(PsiModifier.PRIVATE) == true
fun PsiModifierListOwner.isPackagePrivate(): Boolean {
val modifiers = modifierList ?: return false
return !(modifiers.hasModifierProperty(PsiModifier.PUBLIC) ||
modifiers.hasModifierProperty(PsiModifier.PROTECTED))
}