blob: 0aa9359038429cab0db182ffe6d85aae846926d4 [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.text
import com.android.tools.metalava.model.AnnotationRetention
import com.android.tools.metalava.model.ClassItem
import com.android.tools.metalava.model.ConstructorItem
import com.android.tools.metalava.model.DefaultModifierList
import com.android.tools.metalava.model.FieldItem
import com.android.tools.metalava.model.Item
import com.android.tools.metalava.model.MethodItem
import com.android.tools.metalava.model.PackageItem
import com.android.tools.metalava.model.ParameterItem
import com.android.tools.metalava.model.PropertyItem
import com.android.tools.metalava.model.TypeItem
import com.android.tools.metalava.model.TypeParameterItem
import com.android.tools.metalava.model.TypeParameterList
import com.android.tools.metalava.model.TypeParameterListOwner
import java.util.function.Predicate
open class TextClassItem(
override val codebase: TextCodebase,
position: SourcePositionInfo = SourcePositionInfo.UNKNOWN,
modifiers: DefaultModifierList,
private var isInterface: Boolean = false,
private var isEnum: Boolean = false,
internal var isAnnotation: Boolean = false,
val qualifiedName: String = "",
val qualifiedTypeName: String = qualifiedName,
var name: String = qualifiedName.substring(qualifiedName.lastIndexOf('.') + 1),
val annotations: List<String>? = null,
val typeParameterList: TypeParameterList = TypeParameterList.NONE
) :
TextItem(codebase = codebase, position = position, modifiers = modifiers),
ClassItem,
TypeParameterListOwner {
init {
@Suppress("LeakingThis") modifiers.setOwner(this)
if (typeParameterList is TextTypeParameterList) {
@Suppress("LeakingThis") typeParameterList.setOwner(this)
}
}
override val isTypeParameter: Boolean = false
override var artifact: String? = null
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is ClassItem) return false
return qualifiedName == other.qualifiedName()
}
override fun hashCode(): Int {
return qualifiedName.hashCode()
}
override fun interfaceTypes(): List<TypeItem> = interfaceTypes
override fun allInterfaces(): Sequence<ClassItem> {
return interfaceTypes.asSequence().map { it.asClass() }.filterNotNull()
}
private var innerClasses: MutableList<ClassItem> = mutableListOf()
override var stubConstructor: ConstructorItem? = null
override var hasPrivateConstructor: Boolean = false
override fun innerClasses(): List<ClassItem> = innerClasses
override fun hasImplicitDefaultConstructor(): Boolean {
return false
}
override fun isInterface(): Boolean = isInterface
override fun isAnnotationType(): Boolean = isAnnotation
override fun isEnum(): Boolean = isEnum
var containingClass: ClassItem? = null
override fun containingClass(): ClassItem? = containingClass
private var containingPackage: PackageItem? = null
fun setContainingPackage(containingPackage: TextPackageItem) {
this.containingPackage = containingPackage
}
fun setIsAnnotationType(isAnnotation: Boolean) {
this.isAnnotation = isAnnotation
}
fun setIsEnum(isEnum: Boolean) {
this.isEnum = isEnum
}
override fun containingPackage(): PackageItem =
containingClass?.containingPackage() ?: containingPackage ?: error(this)
override fun hasTypeVariables(): Boolean = typeParameterList.typeParameterCount() > 0
override fun typeParameterList(): TypeParameterList = typeParameterList
override fun typeParameterListOwnerParent(): TypeParameterListOwner? {
return containingClass as? TypeParameterListOwner
}
override fun resolveParameter(variable: String): TypeParameterItem? {
if (hasTypeVariables()) {
for (t in typeParameterList().typeParameters()) {
if (t.simpleName() == variable) {
return t
}
}
}
return null
}
private var superClass: ClassItem? = null
private var superClassType: TypeItem? = null
override fun superClass(): ClassItem? = superClass
override fun superClassType(): TypeItem? = superClassType
internal fun setSuperClass(superClass: ClassItem?, superClassType: TypeItem?) {
this.superClass = superClass
this.superClassType = superClassType
}
override fun setInterfaceTypes(interfaceTypes: List<TypeItem>) {
this.interfaceTypes = interfaceTypes.toMutableList()
}
private var typeInfo: TextTypeItem? = null
override fun toType(): TextTypeItem {
if (typeInfo == null) {
typeInfo = codebase.typeResolver.obtainTypeFromClass(this)
}
return typeInfo!!
}
private var interfaceTypes = mutableListOf<TypeItem>()
private val constructors = mutableListOf<ConstructorItem>()
private val methods = mutableListOf<MethodItem>()
private val fields = mutableListOf<FieldItem>()
private val properties = mutableListOf<PropertyItem>()
override fun constructors(): List<ConstructorItem> = constructors
override fun methods(): List<MethodItem> = methods
override fun fields(): List<FieldItem> = fields
override fun properties(): List<PropertyItem> = properties
fun addInterface(itf: TypeItem) {
interfaceTypes.add(itf)
}
fun addConstructor(constructor: TextConstructorItem) {
constructors += constructor
}
fun addMethod(method: TextMethodItem) {
methods += method
}
fun addField(field: TextFieldItem) {
fields += field
}
fun addProperty(property: TextPropertyItem) {
properties += property
}
fun addEnumConstant(field: TextFieldItem) {
field.setEnumConstant(true)
fields += field
}
override fun addInnerClass(cls: ClassItem) {
innerClasses.add(cls)
}
override fun filteredSuperClassType(predicate: Predicate<Item>): TypeItem? {
// No filtering in signature files: we assume signature APIs
// have already been filtered and all items should match.
// This lets us load signature files and rewrite them using updated
// output formats etc.
return superClassType
}
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!!
}
private var fullName: String = name
override fun simpleName(): String = name.substring(name.lastIndexOf('.') + 1)
override fun fullName(): String = fullName
override fun qualifiedName(): String = qualifiedName
override fun isDefined(): Boolean {
assert(emit == (position != SourcePositionInfo.UNKNOWN))
return emit
}
override fun toString(): String = "class ${qualifiedName()}"
override fun mapTypeVariables(target: ClassItem): Map<String, String> {
return emptyMap()
}
override fun createDefaultConstructor(): ConstructorItem {
return TextConstructorItem.createDefaultConstructor(codebase, this, position)
}
private fun getParentAndInterfaces(): List<TextClassItem> {
val classes = interfaceTypes().map { it.asClass() as TextClassItem }.toMutableList()
superClass()?.let { classes.add(0, it as TextClassItem) }
return classes
}
private var allSuperClassesAndInterfaces: List<TextClassItem>? = null
/**
* Returns all super classes and interfaces in the class hierarchy the class item inherits. The
* returned list is sorted by the proximity of the classes to the class item in the hierarchy
* chain. If an interface appears multiple time in the hierarchy chain, it is ordered based on
* the furthest distance to the class item.
*/
fun getAllSuperClassesAndInterfaces(): List<TextClassItem> {
allSuperClassesAndInterfaces?.let {
return it
}
val classLevelMap = mutableMapOf<TextClassItem, Int>()
// Stores the parent class and interfaces to be iterated.
// Since a class can inherit multiple class and interfaces, queue is two-dimensional.
// Each inner lists represents all super class and interfaces in the same hierarchy level.
val queue = ArrayDeque<List<TextClassItem>>()
queue.add(getParentAndInterfaces())
// We need to visit the hierarchy starting from the greatest ancestor,
// but we cannot naively reverse-iterate based on the order the hierarchy is discovered
// because a class/interface can appear multiple times in the hierarchy graph
// (i.e. a vertex can have multiple outgoing edges).
// Thus, we keep track of the furthest distances from each hierarchy vertices to the
// destination vertex (cl) and reverse iterate from the vertices that are
// farthest from the destination.
var hierarchyLevel = 1
while (queue.isNotEmpty()) {
val superClasses = queue.removeFirst()
val parentClasses = ArrayList<TextClassItem>()
for (superClass in superClasses) {
// Every class extends java.lang.Object and thus not need to be
// included in the hierarchy
if (!superClass.isJavaLangObject()) {
classLevelMap[superClass] = hierarchyLevel
parentClasses.addAll(superClass.getParentAndInterfaces())
}
}
if (parentClasses.isNotEmpty()) {
queue.add(parentClasses)
}
hierarchyLevel += 1
}
allSuperClassesAndInterfaces =
classLevelMap.toList().sortedWith(compareBy { it.second }).map { it.first }
return allSuperClassesAndInterfaces!!
}
companion object {
internal fun createStubClass(
codebase: TextCodebase,
name: String,
isInterface: Boolean
): TextClassItem {
val index = if (name.endsWith(">")) name.indexOf('<') else -1
val qualifiedName = if (index == -1) name else name.substring(0, index)
val typeParameterList =
if (index == -1) {
TypeParameterList.NONE
} else {
TextTypeParameterList.create(codebase, name.substring(index))
}
val fullName = getFullName(qualifiedName)
val cls =
TextClassItem(
codebase = codebase,
name = fullName,
qualifiedName = qualifiedName,
isInterface = isInterface,
modifiers = DefaultModifierList(codebase, DefaultModifierList.PUBLIC),
typeParameterList = typeParameterList
)
cls.emit = false // it's a stub
return cls
}
private fun getFullName(qualifiedName: String): String {
var end = -1
val length = qualifiedName.length
var prev = qualifiedName[length - 1]
for (i in length - 2 downTo 0) {
val c = qualifiedName[i]
if (c == '.' && prev.isUpperCase()) {
end = i + 1
}
prev = c
}
if (end != -1) {
return qualifiedName.substring(end)
}
return qualifiedName.substring(qualifiedName.lastIndexOf('.') + 1)
}
/**
* Determines whether if [thisClassType] is covariant type with [otherClassType]. If
* [thisClassType] does not belong to this class, return false.
*
* @param thisClass [ClassItem] that [thisClassType] belongs to
* @param thisClassType [TypeItem] that belongs to this class
* @param otherClassType [TypeItem] that belongs to other class
* @return Boolean that indicates whether if the two types are covariant
*/
private fun isCovariantType(
thisClass: ClassItem,
thisClassType: TypeItem,
otherClassType: TypeItem
): Boolean {
// TypeItem.asClass() returns null for primitive types.
// Since primitive types are not covariant with anything, return false
val otherClass = otherClassType.asClass() ?: return false
val otherSuperClassNames =
(otherClass as TextClassItem).getAllSuperClassesAndInterfaces().map {
it.qualifiedName()
}
val thisClassTypeErased = thisClassType.toErasedTypeString()
val typeArgIndex =
thisClass.toType().typeArguments(simplified = true).indexOf(thisClassTypeErased)
// thisClassSuperType is the super type of thisClassType retrieved from the type
// arguments.
// e.g. when type arguments are <K, V extends some.arbitrary.Class>,
// thisClassSuperType will be "some.arbitrary.Class" when the thisClassType is "V"
// If thisClassType is not included in the type arguments or
// if thisClassType does not have a super type specified in the type argument,
// thisClassSuperType will be thisClassType.
val thisClassSuperType =
if (typeArgIndex == -1) thisClassTypeErased
else
thisClass.toType().typeArguments()[typeArgIndex].substringAfterLast(" extends ")
return thisClassSuperType in otherSuperClassNames
}
private fun hasEqualTypeVar(
type1: TypeItem,
class1: ClassItem,
type2: TypeItem,
class2: ClassItem
): Boolean {
// Given a type and its containing class,
// find the interface types that contains the type.
// For instance, for a method that looks like:
// class SomeClass implements InterfaceA<some.return.Type>, InterfaceB<some.return.Type>
// Type foo()
// this function will return [InterfaceA, InterfaceB] when Type and SomeClass
// are passed as inputs.
val typeContainingInterfaces = { t: TypeItem, cl: ClassItem ->
val interfaceTypes =
cl.interfaceTypes().plus(cl.toType()).plus(cl.superClassType()).filterNotNull()
interfaceTypes.filter {
val typeArgs = it.typeArguments(simplified = true)
t.toString() in typeArgs ||
t.toElementType() in typeArgs ||
t.asClass()?.superClass()?.qualifiedName() in typeArgs
}
}
val typeContainingInterfaces1 = typeContainingInterfaces(type1, class1)
val typeContainingInterfaces2 = typeContainingInterfaces(type2, class2)
if (typeContainingInterfaces1.isEmpty() || typeContainingInterfaces2.isEmpty()) {
return false
}
val interfaceTypesAreCovariant = { t1: TypeItem, t2: TypeItem ->
t1.toErasedTypeString() == t2.toErasedTypeString() ||
t1.asClass()?.superClass()?.qualifiedName() == t2.asClass()?.qualifiedName() ||
t2.asClass()?.superClass()?.qualifiedName() == t1.asClass()?.qualifiedName()
}
// Check if the return type containing interfaces of the two methods have an
// intersection.
return typeContainingInterfaces1.any { typeInterface1 ->
typeContainingInterfaces2.any { typeInterface2 ->
interfaceTypesAreCovariant(typeInterface1, typeInterface2)
}
}
}
private fun hasEqualTypeBounds(method1: MethodItem, method2: MethodItem): Boolean {
val typeInTypeParams = { t: TypeItem, m: MethodItem ->
t in m.typeParameterList().typeParameters().map { it.toType() }
}
val getTypeBounds = { t: TypeItem, m: MethodItem ->
m.typeParameterList()
.typeParameters()
.single { it.toType() == t }
.typeBounds()
.toSet()
}
val returnType1 = method1.returnType()
val returnType2 = method2.returnType()
// The following two methods are considered equal:
// method public <A extends some.package.SomeClass> A foo (Class<A>);
// method public <T extends some.package.SomeClass> T foo (Class<T>);
// This can be verified by converting return types to bounds ([some.package.SomeClass])
// and compare equivalence.
return typeInTypeParams(returnType1, method1) &&
typeInTypeParams(returnType2, method2) &&
getTypeBounds(returnType1, method1) == getTypeBounds(returnType2, method2)
}
private fun hasCovariantTypes(
type1: TypeItem,
class1: ClassItem,
type2: TypeItem,
class2: ClassItem
): Boolean {
val types = listOf(type1, type2)
val type1Erased = type1.toErasedTypeString()
val type2Erased = type2.toErasedTypeString()
// The return type of the following two methods are considered equal:
// when SomeReturnSubClass extends SomeReturnClass:
// method SomeReturnClass foo() and method SomeReturnSubClass foo()
// Likewise, the return type of the two methods are also considered equal:
// method T foo() in SomeClass<T extends SomeReturnClass> and
// method SomeReturnSubClass foo() in SomeOtherClass
// This can be verified by checking if a method's return type exists in
// another method return type's super classes
// Since this method is only used to compare methods with same name and parameters count
// within same hierarchy tree, it is unlikely that
// two methods have same generic return type.
// However, not comparing erased type strings may lead to false negatives
// (e.g. comparing java.util.Iterator<E> and java.util.Iterator<T>)
// Thus erased type strings equivalence must be evaluated.
return type1Erased == type2Erased ||
isCovariantType(class1, type1, type2) ||
isCovariantType(class2, type2, type1) ||
types.any { it.isJavaLangObject() }
}
/**
* Compares two [MethodItem]s and determines if the two methods have equal return types. The
* two methods' return types are considered equal even if the two are not identical, but are
* compatible in compiler level. For instance, return types in a same hierarchy tree are
* considered equal.
*
* @param method1 first [MethodItem] to compare the return type
* @param method2 second [MethodItem] to compare the return type
* @return a [Boolean] value representing if the two methods' return types are equal
*/
fun hasEqualReturnType(method1: MethodItem, method2: MethodItem): Boolean {
val returnType1 = method1.returnType()
val returnType2 = method2.returnType()
val class1 = method1.containingClass()
val class2 = method2.containingClass()
if (returnType1 == returnType2) return true
if (hasEqualTypeVar(returnType1, class1, returnType2, class2)) return true
if (hasEqualTypeBounds(method1, method2)) return true
if (hasCovariantTypes(returnType1, class1, returnType2, class2)) return true
return false
}
/**
* Compares two [MethodItem] and determines if the two are considered equal based on the
* context of the containing classes of the methods. To be specific, for the two methods in
* which the coexistence in a class would lead to a method already defined compiler error,
* this method returns true.
*
* @param method1 first [MethodItem] to compare
* @param method2 second [MethodItem] to compare
* @return a [Boolean] value representing if the two methods are equal or not with respect
* to the classes contexts.
*/
fun equalMethodInClassContext(method1: MethodItem, method2: MethodItem): Boolean {
if (method1 == method2) return true
if (method1.name() != method2.name()) return false
if (method1.parameters().size != method2.parameters().size) return false
val hasEqualParams =
method1.parameters().zip(method2.parameters()).all {
(param1, param2): Pair<ParameterItem, ParameterItem> ->
val type1 = param1.type()
val type2 = param2.type()
val class1 = method1.containingClass()
val class2 = method2.containingClass()
// At this point, two methods' return types equivalence would have been checked.
// i.e. If hasEqualReturnType(method1, method2) is true,
// we know that the two methods return types are equal.
// In other words, if the two compared param types are both method return types,
// we transitively know that the two param types are equal as well.
val bothAreMethodReturnType =
type1 == method1.returnType() && type2 == method2.returnType()
type1 == type2 ||
bothAreMethodReturnType ||
hasEqualTypeVar(type1, class1, type2, class2) ||
hasCovariantTypes(type1, class1, type2, class2)
}
return hasEqualReturnType(method1, method2) && hasEqualParams
}
}
}