blob: a26b71b3448366b400bb3fd6c0f4ea6b59e5a204 [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
import com.android.SdkConstants
import com.android.tools.metalava.ApiAnalyzer
import com.android.tools.metalava.JAVA_LANG_ANNOTATION
import com.android.tools.metalava.JAVA_LANG_ENUM
import com.android.tools.metalava.JAVA_LANG_OBJECT
import com.android.tools.metalava.model.visitors.ApiVisitor
import com.android.tools.metalava.model.visitors.ItemVisitor
import com.android.tools.metalava.model.visitors.TypeVisitor
import com.google.common.base.Splitter
import java.util.ArrayList
import java.util.LinkedHashSet
import java.util.function.Predicate
interface ClassItem : Item {
/** The simple name of a class. In class foo.bar.Outer.Inner, the simple name is "Inner" */
fun simpleName(): String
/** The full name of a class. In class foo.bar.Outer.Inner, the full name is "Outer.Inner" */
fun fullName(): String
/** The qualified name of a class. In class foo.bar.Outer.Inner, the qualified name is the whole thing. */
fun qualifiedName(): String
/** Is this an innerclass? */
fun isInnerClass(): Boolean = containingClass() != null
/** Is this a top level class? */
fun isTopLevelClass(): Boolean = containingClass() == null
/** This [ClassItem] and all of its inner classes, recursively */
fun allClasses(): Sequence<ClassItem> {
return sequenceOf(this).plus(innerClasses().asSequence().flatMap { it.allClasses() })
}
override fun parent(): Item? = containingClass() ?: containingPackage()
/**
* The qualified name where inner classes use $ as a separator.
* In class foo.bar.Outer.Inner, this method will return foo.bar.Outer$Inner.
* (This is the name format used in ProGuard keep files for example.)
*/
fun qualifiedNameWithDollarInnerClasses(): String {
var curr: ClassItem? = this
while (curr?.containingClass() != null) {
curr = curr.containingClass()
}
if (curr == null) {
return fullName().replace('.', '$')
}
return curr.containingPackage().qualifiedName() + "." + fullName().replace('.', '$')
}
/** Returns the internal name of the class, as seen in bytecode */
fun internalName(): String {
var curr: ClassItem? = this
while (curr?.containingClass() != null) {
curr = curr.containingClass()
}
if (curr == null) {
return fullName().replace('.', '$')
}
return curr.containingPackage().qualifiedName().replace('.', '/') + "/" +
fullName().replace('.', '$')
}
/** The super class of this class, if any */
fun superClass(): ClassItem?
/** The super class type of this class, if any. The difference between this and [superClass] is
* that the type reference can include type arguments; e.g. in "class MyList extends List<String>"
* the super class is java.util.List and the super class type is java.util.List<java.lang.String>.
* */
fun superClassType(): TypeItem?
/** Finds the public super class of this class, if any */
fun publicSuperClass(): ClassItem? {
var superClass = superClass()
while (superClass != null && !superClass.checkLevel()) {
superClass = superClass.superClass()
}
return superClass
}
/** Returns true if this class extends the given class (includes self) */
fun extends(qualifiedName: String): Boolean {
if (qualifiedName() == qualifiedName) {
return true
}
val superClass = superClass()
return superClass?.extends(qualifiedName) ?: when {
isEnum() -> qualifiedName == JAVA_LANG_ENUM
isAnnotationType() -> qualifiedName == JAVA_LANG_ANNOTATION
else -> qualifiedName == JAVA_LANG_OBJECT
}
}
/** Returns true if this class implements the given interface (includes self) */
fun implements(qualifiedName: String): Boolean {
if (qualifiedName() == qualifiedName) {
return true
}
interfaceTypes().forEach {
val cls = it.asClass()
if (cls != null && cls.implements(qualifiedName)) {
return true
}
}
// Might be implementing via superclass
if (superClass()?.implements(qualifiedName) == true) {
return true
}
return false
}
/** Returns true if this class extends or implements the given class or interface */
fun extendsOrImplements(qualifiedName: String): Boolean = extends(qualifiedName) || implements(qualifiedName)
/** Any interfaces implemented by this class */
fun interfaceTypes(): List<TypeItem>
/** All classes and interfaces implemented (by this class and its super classes and the interfaces themselves) */
fun allInterfaces(): Sequence<ClassItem>
/** Any inner classes of this class */
fun innerClasses(): List<ClassItem>
/** The constructors in this class */
fun constructors(): List<ConstructorItem>
/** Whether this class has an implicit default constructor */
fun hasImplicitDefaultConstructor(): Boolean
/** The non-constructor methods in this class */
fun methods(): List<MethodItem>
/** The properties in this class */
fun properties(): List<PropertyItem>
/** The fields in this class */
fun fields(): List<FieldItem>
/** The members in this class: constructors, methods, fields/enum constants */
fun members(): Sequence<MemberItem> {
return fields().asSequence().plus(constructors().asSequence()).plus(methods().asSequence())
}
/** Whether this class is an interface */
fun isInterface(): Boolean
/** Whether this class is an annotation type */
fun isAnnotationType(): Boolean
/** Whether this class is an enum */
fun isEnum(): Boolean
/** Whether this class is a regular class (not an interface, not an enum, etc) */
fun isClass(): Boolean = !isInterface() && !isAnnotationType() && !isEnum()
/** The containing class, for inner classes */
fun containingClass(): ClassItem?
/** The containing package */
fun containingPackage(): PackageItem
override fun containingPackage(strict: Boolean): PackageItem = containingPackage()
override fun containingClass(strict: Boolean): ClassItem? {
return if (strict) containingClass() else this
}
/** Gets the type for this class */
fun toType(): TypeItem
override fun type(): TypeItem? = null
/** Returns true if this class has type parameters */
fun hasTypeVariables(): Boolean
/** Any type parameters for the class, if any, as a source string (with fully qualified class names) */
fun typeParameterList(): TypeParameterList
/** Returns the classes that are part of the type parameters of this method, if any */
fun typeArgumentClasses(): List<ClassItem> = TODO("Not yet implemented")
fun isJavaLangObject(): Boolean {
return qualifiedName() == JAVA_LANG_OBJECT
}
// Mutation APIs: Used to "fix up" the API hierarchy (in [ApiAnalyzer]) to only expose
// visible parts of the API)
// This replaces the "real" super class
fun setSuperClass(superClass: ClassItem?, superClassType: TypeItem? = superClass?.toType())
// This replaces the interface types implemented by this class
fun setInterfaceTypes(interfaceTypes: List<TypeItem>)
val isTypeParameter: Boolean
var hasPrivateConstructor: Boolean
/** If true, this is an invisible element that was referenced by a public API. */
var notStrippable: Boolean
/**
* Maven artifact of this class, if any. (Not used for the Android SDK, but used in
* for example support libraries.
*/
var artifact: String?
override fun accept(visitor: ItemVisitor) {
if (visitor is ApiVisitor) {
accept(visitor)
return
}
if (visitor.skip(this)) {
return
}
visitor.visitItem(this)
visitor.visitClass(this)
for (constructor in constructors()) {
constructor.accept(visitor)
}
for (method in methods()) {
method.accept(visitor)
}
for (property in properties()) {
property.accept(visitor)
}
if (isEnum()) {
// In enums, visit the enum constants first, then the fields
for (field in fields()) {
if (field.isEnumConstant()) {
field.accept(visitor)
}
}
for (field in fields()) {
if (!field.isEnumConstant()) {
field.accept(visitor)
}
}
} else {
for (field in fields()) {
field.accept(visitor)
}
}
if (visitor.nestInnerClasses) {
for (cls in innerClasses()) {
cls.accept(visitor)
}
} // otherwise done below
visitor.afterVisitClass(this)
visitor.afterVisitItem(this)
if (!visitor.nestInnerClasses) {
for (cls in innerClasses()) {
cls.accept(visitor)
}
}
}
fun accept(visitor: ApiVisitor) {
if (visitor.skip(this)) {
return
}
if (!visitor.include(this)) {
return
}
// We build up a separate data structure such that we can compute the
// sets of fields, methods, etc even for inner classes (recursively); that way
// we can easily and up front determine whether we have any matches for
// inner classes (which is vital for computing the removed-api for example, where
// only something like the appearance of a removed method inside an inner class
// results in the outer class being described in the signature file.
val candidate = VisitCandidate(this, visitor)
candidate.accept()
}
override fun acceptTypes(visitor: TypeVisitor) {
if (visitor.skip(this)) {
return
}
val type = toType()
visitor.visitType(type, this)
// TODO: Visit type parameter list (at least the bounds types, e.g. View in <T extends View>
superClass()?.let {
visitor.visitType(it.toType(), it)
}
if (visitor.includeInterfaces) {
for (itf in interfaceTypes()) {
val owner = itf.asClass()
owner?.let { visitor.visitType(itf, it) }
}
}
for (constructor in constructors()) {
constructor.acceptTypes(visitor)
}
for (field in fields()) {
field.acceptTypes(visitor)
}
for (method in methods()) {
method.acceptTypes(visitor)
}
for (cls in innerClasses()) {
cls.acceptTypes(visitor)
}
visitor.afterVisitType(type, this)
}
companion object {
/** Looks up the retention policy for the given class */
fun findRetention(cls: ClassItem): AnnotationRetention {
val modifiers = cls.modifiers
val annotation = modifiers.findAnnotation("java.lang.annotation.Retention")
?: modifiers.findAnnotation("kotlin.annotation.Retention")
val value = annotation?.findAttribute(SdkConstants.ATTR_VALUE)
val source = value?.value?.toSource()
return when {
source == null -> AnnotationRetention.CLASS // default
source.contains("RUNTIME") -> AnnotationRetention.RUNTIME
source.contains("SOURCE") -> AnnotationRetention.SOURCE
else -> AnnotationRetention.CLASS // default
}
}
// Same as doclava1 (modulo the new handling when class names match)
val comparator: Comparator<in ClassItem> = Comparator { o1, o2 ->
val delta = o1.fullName().compareTo(o2.fullName())
if (delta == 0) {
o1.qualifiedName().compareTo(o2.qualifiedName())
} else {
delta
}
}
val nameComparator: Comparator<ClassItem> = Comparator { a, b ->
a.simpleName().compareTo(b.simpleName())
}
val fullNameComparator: Comparator<ClassItem> = Comparator { a, b -> a.fullName().compareTo(b.fullName()) }
val qualifiedComparator: Comparator<ClassItem> = Comparator { a, b ->
a.qualifiedName().compareTo(b.qualifiedName())
}
fun classNameSorter(): Comparator<in ClassItem> = ClassItem.qualifiedComparator
}
fun findMethod(
template: MethodItem,
includeSuperClasses: Boolean = false,
includeInterfaces: Boolean = false
): MethodItem? {
if (template.isConstructor()) {
return findConstructor(template as ConstructorItem)
}
methods().asSequence()
.filter { it.matches(template) }
.forEach { return it }
if (includeSuperClasses) {
superClass()?.findMethod(template, true, includeInterfaces)?.let { return it }
}
if (includeInterfaces) {
for (itf in interfaceTypes()) {
val cls = itf.asClass() ?: continue
cls.findMethod(template, includeSuperClasses, true)?.let { return it }
}
}
return null
}
/** Finds a given method in this class matching the VM name signature */
fun findMethodByDesc(
name: String,
desc: String,
includeSuperClasses: Boolean = false,
includeInterfaces: Boolean = false
): MethodItem? {
if (desc.startsWith("<init>")) {
constructors().asSequence()
.filter { it.internalDesc() == desc }
.forEach { return it }
return null
} else {
methods().asSequence()
.filter { it.name() == name && it.internalDesc() == desc }
.forEach { return it }
}
if (includeSuperClasses) {
superClass()?.findMethodByDesc(name, desc, true, includeInterfaces)?.let { return it }
}
if (includeInterfaces) {
for (itf in interfaceTypes()) {
val cls = itf.asClass() ?: continue
cls.findMethodByDesc(name, desc, includeSuperClasses, true)?.let { return it }
}
}
return null
}
fun findConstructor(template: ConstructorItem): ConstructorItem? {
constructors().asSequence()
.filter { it.matches(template) }
.forEach { return it }
return null
}
fun findField(
fieldName: String,
includeSuperClasses: Boolean = false,
includeInterfaces: Boolean = false
): FieldItem? {
val field = fields().firstOrNull { it.name() == fieldName }
if (field != null) {
return field
}
if (includeSuperClasses) {
superClass()?.findField(fieldName, true, includeInterfaces)?.let { return it }
}
if (includeInterfaces) {
for (itf in interfaceTypes()) {
val cls = itf.asClass() ?: continue
cls.findField(fieldName, includeSuperClasses, true)?.let { return it }
}
}
return null
}
fun findMethod(methodName: String, parameters: String): MethodItem? {
if (methodName == simpleName()) {
// Constructor
constructors()
.filter { parametersMatch(it, parameters) }
.forEach { return it }
} else {
methods()
.filter { it.name() == methodName && parametersMatch(it, parameters) }
.forEach { return it }
}
return null
}
private fun parametersMatch(method: MethodItem, description: String): Boolean {
val parameterStrings = Splitter.on(",").trimResults().omitEmptyStrings().splitToList(description)
val parameters = method.parameters()
if (parameters.size != parameterStrings.size) {
return false
}
for (i in 0 until parameters.size) {
var parameterString = parameterStrings[i]
val index = parameterString.indexOf('<')
if (index != -1) {
parameterString = parameterString.substring(0, index)
}
val parameter = parameters[i].type().toErasedTypeString(method)
if (parameter != parameterString) {
return false
}
}
return true
}
/** Returns the corresponding compilation unit, if any */
fun getCompilationUnit(): CompilationUnit? = null
/** If this class is an annotation type, returns the retention of this class */
fun getRetention(): AnnotationRetention
/**
* Return superclass matching the given predicate. When a superclass doesn't
* match, we'll keep crawling up the tree until we find someone who matches.
*/
fun filteredSuperclass(predicate: Predicate<Item>): ClassItem? {
val superClass = superClass() ?: return null
return if (predicate.test(superClass)) {
superClass
} else {
superClass.filteredSuperclass(predicate)
}
}
fun filteredSuperClassType(predicate: Predicate<Item>): TypeItem? {
var superClassType: TypeItem? = superClassType() ?: return null
var prev: ClassItem? = null
while (superClassType != null) {
val superClass = superClassType.asClass() ?: return null
if (predicate.test(superClass)) {
if (prev == null || superClass == superClass()) {
// Direct reference; no need to map type variables
return superClassType
}
if (!superClassType.hasTypeArguments()) {
// No type variables - also no need for mapping
return superClassType
}
return superClassType.convertType(this, prev)
}
prev = superClass
superClassType = superClass.superClassType()
}
return null
}
/**
* Return methods matching the given predicate. Forcibly includes local
* methods that override a matching method in an ancestor class.
*/
fun filteredMethods(predicate: Predicate<Item>): Collection<MethodItem> {
val methods = LinkedHashSet<MethodItem>()
for (method in methods()) {
if (predicate.test(method) || method.findPredicateSuperMethod(predicate) != null) {
// val duplicated = method.duplicate(this)
// methods.add(duplicated)
methods.remove(method)
methods.add(method)
}
}
return methods
}
/** Returns the constructors that match the given predicate */
fun filteredConstructors(predicate: Predicate<Item>): Sequence<ConstructorItem> {
return constructors().asSequence().filter { predicate.test(it) }
}
/**
* Return fields matching the given predicate. Also clones fields from
* ancestors that would match had they been defined in this class.
*/
fun filteredFields(predicate: Predicate<Item>, showUnannotated: Boolean): List<FieldItem> {
val fields = LinkedHashSet<FieldItem>()
if (showUnannotated) {
for (clazz in allInterfaces()) {
if (!clazz.isInterface()) {
continue
}
for (field in clazz.fields()) {
if (!predicate.test(field)) {
val duplicated = field.duplicate(this)
if (predicate.test(duplicated)) {
fields.remove(duplicated)
fields.add(duplicated)
}
}
}
}
val superClass = superClass()
if (superClass != null && !predicate.test(superClass) && predicate.test(this)) {
// Include constants from hidden super classes.
for (field in superClass.fields()) {
val fieldModifiers = field.modifiers
if (!fieldModifiers.isStatic() || !fieldModifiers.isFinal() || !fieldModifiers.isPublic()) {
continue
}
if (!field.originallyHidden) {
val duplicated = field.duplicate(this)
if (predicate.test(duplicated)) {
duplicated.inheritedField = true
fields.remove(duplicated)
fields.add(duplicated)
}
}
}
}
}
for (field in fields()) {
if (predicate.test(field)) {
fields.remove(field)
fields.add(field)
}
}
if (fields.isEmpty()) {
return emptyList()
}
val list = fields.toMutableList()
list.sortWith(FieldItem.comparator)
return list
}
fun filteredInterfaceTypes(predicate: Predicate<Item>): Collection<TypeItem> {
val interfaceTypes = filteredInterfaceTypes(
predicate, LinkedHashSet(),
includeSelf = false, includeParents = false, target = this
)
if (interfaceTypes.isEmpty()) {
return interfaceTypes
}
return interfaceTypes
}
fun allInterfaceTypes(predicate: Predicate<Item>): Collection<TypeItem> {
val interfaceTypes = filteredInterfaceTypes(
predicate, LinkedHashSet(),
includeSelf = false, includeParents = true, target = this
)
if (interfaceTypes.isEmpty()) {
return interfaceTypes
}
return interfaceTypes
}
private fun filteredInterfaceTypes(
predicate: Predicate<Item>,
types: LinkedHashSet<TypeItem>,
includeSelf: Boolean,
includeParents: Boolean,
target: ClassItem
): LinkedHashSet<TypeItem> {
val superClassType = superClassType()
if (superClassType != null) {
val superClass = superClassType.asClass()
if (superClass != null) {
if (!predicate.test(superClass)) {
superClass.filteredInterfaceTypes(predicate, types, true, includeParents, target)
} else if (includeSelf && superClass.isInterface()) {
types.add(superClassType)
if (includeParents) {
superClass.filteredInterfaceTypes(predicate, types, true, includeParents, target)
}
}
}
}
for (type in interfaceTypes()) {
val cls = type.asClass() ?: continue
if (predicate.test(cls)) {
if (hasTypeVariables() && type.hasTypeArguments()) {
val replacementMap = target.mapTypeVariables(this)
if (replacementMap.isNotEmpty()) {
val mapped = type.convertType(replacementMap)
types.add(mapped)
continue
}
}
types.add(type)
if (includeParents) {
cls.filteredInterfaceTypes(predicate, types, true, includeParents, target)
}
} else {
cls.filteredInterfaceTypes(predicate, types, true, includeParents, target)
}
}
return types
}
fun allInnerClasses(includeSelf: Boolean = false): Sequence<ClassItem> {
if (!includeSelf && innerClasses().isEmpty()) {
return emptySequence()
}
val list = ArrayList<ClassItem>()
if (includeSelf) {
list.add(this)
}
addInnerClasses(list, this)
return list.asSequence()
}
private fun addInnerClasses(list: MutableList<ClassItem>, cls: ClassItem) {
for (innerClass in cls.innerClasses()) {
list.add(innerClass)
addInnerClasses(list, innerClass)
}
}
/**
* The default constructor to invoke on this class from subclasses; initially null
* but populated by [ApiAnalyzer.addConstructors]. (Note that in some cases
* [defaultConstructor] may not be in [constructors], e.g. when we need to
* create a constructor to match a public parent class with a non-default constructor
* and the one in the code is not a match, e.g. is marked @hide etc.)
*/
var defaultConstructor: ConstructorItem?
/**
* Creates a map of type variables from this class to the given target class.
* If class A<X,Y> extends B<X,Y>, and B is declared as class B<M,N>,
* this returns the map {"X"->"M", "Y"->"N"}. There could be multiple intermediate
* classes between this class and the target class, and in some cases we could
* be substituting in a concrete class, e.g. class MyClass extends B<String,Number>
* would return the map {"java.lang.String"->"M", "java.lang.Number"->"N"}.
*
* If [reverse] is true, compute the reverse map: keys are the variables in
* the target and the values are the variables in the source.
*/
fun mapTypeVariables(target: ClassItem): Map<String, String> = codebase.unsupported()
/**
* Creates a constructor in this class
*/
fun createDefaultConstructor(): ConstructorItem = codebase.unsupported()
/**
* Creates a method corresponding to the given method signature in this class
*/
fun createMethod(template: MethodItem): MethodItem = codebase.unsupported()
fun addMethod(method: MethodItem): Unit = codebase.unsupported()
}
class VisitCandidate(private val cls: ClassItem, private val visitor: ApiVisitor) {
private val innerClasses: Sequence<VisitCandidate>
private val constructors: Sequence<MethodItem>
private val methods: Sequence<MethodItem>
private val fields: Sequence<FieldItem>
private val enums: Sequence<FieldItem>
private val properties: Sequence<PropertyItem>
init {
val filterEmit = visitor.filterEmit
constructors = cls.constructors().asSequence()
.filter { filterEmit.test(it) }
.sortedWith(MethodItem.comparator)
methods = cls.methods().asSequence()
.filter { filterEmit.test(it) }
.sortedWith(MethodItem.comparator)
val fieldSequence =
if (visitor.inlineInheritedFields) {
cls.filteredFields(filterEmit, visitor.showUnannotated).asSequence()
} else {
cls.fields().asSequence()
.filter { filterEmit.test(it) }
}
if (cls.isEnum()) {
fields = fieldSequence
.filter { !it.isEnumConstant() }
.sortedWith(FieldItem.comparator)
enums = fieldSequence
.filter { it.isEnumConstant() }
.filter { filterEmit.test(it) }
.sortedWith(FieldItem.comparator)
} else {
fields = fieldSequence.sortedWith(FieldItem.comparator)
enums = emptySequence()
}
properties = if (cls.properties().isEmpty()) {
emptySequence()
} else {
cls.properties().asSequence()
.filter { filterEmit.test(it) }
.sortedWith(PropertyItem.comparator)
}
innerClasses = cls.innerClasses()
.asSequence()
.sortedWith(ClassItem.classNameSorter())
.map { VisitCandidate(it, visitor) }
}
/** Will this class emit anything? */
private fun emit(): Boolean {
val emit = emitClass()
if (emit) {
return true
}
return emitInner()
}
private fun emitInner(): Boolean {
return innerClasses.any { it.emit() }
}
/** Does the body of this class (everything other than the inner classes) emit anything? */
private fun emitClass(): Boolean {
val classEmpty = (constructors.none() && methods.none() && enums.none() && fields.none() && properties.none())
return if (visitor.filterEmit.test(cls)) {
true
} else if (!classEmpty) {
visitor.filterReference.test(cls)
} else {
false
}
}
fun accept() {
if (visitor.skip(cls)) {
return
}
if (!visitor.include(cls)) {
return
}
val emitClass = emitClass()
val emit = emitClass || emitInner()
if (!emit) {
return
}
val emitThis = cls.emit && if (visitor.includeEmptyOuterClasses) emit else emitClass
if (emitThis) {
if (!visitor.visitingPackage) {
visitor.visitingPackage = true
val pkg = cls.containingPackage()
visitor.visitItem(pkg)
visitor.visitPackage(pkg)
}
visitor.visitItem(cls)
visitor.visitClass(cls)
val sortedConstructors = if (visitor.methodComparator != null) {
constructors.sortedWith(visitor.methodComparator)
} else {
constructors
}
val sortedMethods = if (visitor.methodComparator != null) {
methods.sortedWith(visitor.methodComparator)
} else {
methods
}
val sortedFields = if (visitor.fieldComparator != null) {
fields.sortedWith(visitor.fieldComparator)
} else {
fields
}
for (constructor in sortedConstructors) {
constructor.accept(visitor)
}
for (method in sortedMethods) {
method.accept(visitor)
}
for (property in properties) {
property.accept(visitor)
}
for (enumConstant in enums) {
enumConstant.accept(visitor)
}
for (field in sortedFields) {
field.accept(visitor)
}
}
if (visitor.nestInnerClasses) { // otherwise done below
innerClasses.forEach { it.accept() }
}
if (emitThis) {
visitor.afterVisitClass(cls)
visitor.afterVisitItem(cls)
}
if (!visitor.nestInnerClasses) {
innerClasses.forEach { it.accept() }
}
}
}