/*
* Copyright (C) 2018 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.apilevels
import com.android.tools.metalava.model.ClassItem
import com.android.tools.metalava.model.Codebase
import com.android.tools.metalava.model.FieldItem
import com.android.tools.metalava.model.MethodItem
import com.android.tools.metalava.model.visitors.ApiVisitor
/** Visits the API codebase and inserts into the [Api] the classes, methods and fields */
fun addApisFromCodebase(api: Api, apiLevel: Int, codebase: Codebase) {
codebase.accept(object : ApiVisitor(
visitConstructorsAsMethods = true,
nestInnerClasses = false
) {
var currentClass: ApiClass? = null
override fun afterVisitClass(cls: ClassItem) {
currentClass = null
}
override fun visitClass(cls: ClassItem) {
val newClass = api.addClass(cls.internalName(), apiLevel, cls.deprecated)
currentClass = newClass
if (cls.isClass()) {
// The jar files historically contain package private parents instead of
// the real API so we need to correct the data we've already read in
val filteredSuperClass = cls.filteredSuperclass(filterReference)
val superClass = cls.superClass()
if (filteredSuperClass != superClass && filteredSuperClass != null) {
val existing = newClass.superClasses.firstOrNull()?.name
val superInternalName = superClass?.internalName()
if (existing == superInternalName) {
// The bytecode used to point to the old hidden super class. Point
// to the real one (that the signature files referenced) instead.
val removed = newClass.removeSuperClass(superInternalName)
val since = removed?.since ?: apiLevel
val entry = newClass.addSuperClass(filteredSuperClass.internalName(), since)
// Show that it's also seen here
entry.update(apiLevel)
// Also inherit the interfaces from that API level, unless it was added later
val superClassEntry = api.findClass(superInternalName)
if (superClassEntry != null) {
for (interfaceType in superClass!!.filteredInterfaceTypes(filterReference)) {
val interfaceClass = interfaceType.asClass() ?: return
var mergedSince = since
val interfaceName = interfaceClass.internalName()
for (itf in superClassEntry.interfaces) {
val currentInterface = itf.name
if (interfaceName == currentInterface) {
mergedSince = itf.since
break
}
}
newClass.addInterface(interfaceClass.internalName(), mergedSince)
}
}
} else {
newClass.addSuperClass(filteredSuperClass.internalName(), apiLevel)
}
} else if (superClass != null) {
newClass.addSuperClass(superClass.internalName(), apiLevel)
}
} else if (cls.isInterface()) {
val superClass = cls.superClass()
if (superClass != null && !superClass.isJavaLangObject()) {
newClass.addInterface(superClass.internalName(), apiLevel)
}
} else if (cls.isEnum()) {
// Implicit super class; match convention from bytecode
if (newClass.name != "java/lang/Enum") {
newClass.addSuperClass("java/lang/Enum", apiLevel)
}
// Mimic doclava enum methods
newClass.addMethod("valueOf(Ljava/lang/String;)L" + newClass.name + ";", apiLevel, false)
newClass.addMethod("values()[L" + newClass.name + ";", apiLevel, false)
} else if (cls.isAnnotationType()) {
// Implicit super class; match convention from bytecode
if (newClass.name != "java/lang/annotation/Annotation") {
newClass.addSuperClass("java/lang/Object", apiLevel)
newClass.addInterface("java/lang/annotation/Annotation", apiLevel)
}
}
// Ensure we don't end up with
// -
// +
// which can happen because the bytecode always explicitly contains extends java.lang.Object
// but in the source code we don't see it, and the lack of presence of this shouldn't be
// taken as a sign that we no longer extend object. But only do this if the class didn't
// previously extend object and now extends something else.
if ((cls.isClass() || cls.isInterface()) &&
newClass.superClasses.size == 1 &&
newClass.superClasses[0].name == "java/lang/Object"
) {
newClass.addSuperClass("java/lang/Object", apiLevel)
}
for (interfaceType in cls.filteredInterfaceTypes(filterReference)) {
val interfaceClass = interfaceType.asClass() ?: return
newClass.addInterface(interfaceClass.internalName(), apiLevel)
}
}
override fun visitMethod(method: MethodItem) {
if (method.isPrivate || method.isPackagePrivate) {
return
}
val name = method.internalName() +
// Use "V" instead of the type of the constructor for backwards compatibility
// with the older bytecode
method.internalDesc(voidConstructorTypes = true)
currentClass?.addMethod(name, apiLevel, method.deprecated)
}
override fun visitField(field: FieldItem) {
if (field.isPrivate || field.isPackagePrivate) {
return
}
currentClass?.addField(field.internalName(), apiLevel, field.deprecated)
}
})
}