blob: 438a386e9892df927b99a15577685e310b80c4d5 [file] [log] [blame]
/*
* Copyright (C) 2023 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.turbine
import com.android.tools.metalava.model.DefaultModifierList
import com.google.common.collect.ImmutableList
import com.google.common.collect.ImmutableMap
import com.google.turbine.binder.Binder
import com.google.turbine.binder.Binder.BindingResult
import com.google.turbine.binder.ClassPathBinder
import com.google.turbine.binder.bound.SourceTypeBoundClass
import com.google.turbine.binder.sym.ClassSymbol
import com.google.turbine.tree.Tree
import com.google.turbine.tree.Tree.CompUnit
import com.google.turbine.tree.Tree.Kind.TY_DECL
import com.google.turbine.tree.Tree.Kind.VAR_DECL
import com.google.turbine.tree.Tree.TyDecl
import com.google.turbine.tree.Tree.VarDecl
import java.io.File
import java.util.Optional
/**
* This initializer acts as adaptor between codebase and the output from Turbine parser.
*
* This is used for populating all the classes,packages and other items from the data present in the
* parsed Tree
*/
open class TurbineCodebaseInitialiser(
val units: List<CompUnit>,
val codebase: TurbineBasedCodebase,
val classpath: List<File>,
) {
/** Map items (classitems, methoditems, etc.) with their qualified name. */
private var itemMap: MutableMap<Tree, String> = mutableMapOf<Tree, String>()
/** The output from Turbine Binder */
private lateinit var bindingResult: BindingResult
/**
* Initialize uses two passes through units. One pass is for creating all the items and the
* other pass to set up all the hierarchy.
*
* The hierarchy is set up in two parts: one with the use of Turbine binder and other without
*/
fun initialize() {
codebase.initialize()
// First pass for creating items
for (unit in units) {
createItems(unit)
}
// Create root package
findOrCreatePackage("")
// This method sets up hierarchy using only parsed source files
setInnerClassHierarchy(units)
try {
bindingResult =
Binder.bind(
ImmutableList.copyOf(units),
ClassPathBinder.bindClasspath(classpath.map { it.toPath() }),
ClassPathBinder.bindClasspath(listOf()),
Optional.empty()
)!!
} catch (e: Throwable) {
throw e
}
setSuperClassHierarchy(bindingResult.units())
}
/** Extracts data from the compilation units. A unit corresponds to one parsed source file. */
private fun createItems(unit: CompUnit) {
val optPkg = unit.pkg()
val pkg = if (optPkg.isPresent()) optPkg.get() else null
var pkgName = ""
if (pkg != null) {
val pkgNameList = pkg.name().map { it.value() }
pkgName = pkgNameList.joinToString(separator = ".")
}
val pkgItem = findOrCreatePackage(pkgName)
val typeDecls = unit.decls()
for (typeDecl in typeDecls) {
populateClass(typeDecl, pkgItem, null, true)
}
}
/**
* Searches for the package with supplied name in the codebase's package map and if not found
* creates the corresponding TurbinePackageItem and adds it to the package map.
*/
private fun findOrCreatePackage(name: String): TurbinePackageItem {
val pkgItem = codebase.findPackage(name)
if (pkgItem != null) {
return pkgItem as TurbinePackageItem
} else {
val modifers = DefaultModifierList(codebase)
val turbinePkgItem = TurbinePackageItem(codebase, name, modifers)
codebase.addPackage(turbinePkgItem)
return turbinePkgItem
}
}
/** Creates a TurbineClassItem and adds the classitem to the various maps in codebase. */
private fun populateClass(
typeDecl: TyDecl,
pkgItem: TurbinePackageItem,
containingClass: TurbineClassItem?,
isTopClass: Boolean,
) {
val className = typeDecl.name().value()
val fullName = if (isTopClass) className else containingClass?.fullName() + "." + className
val qualifiedName = pkgItem.qualifiedName() + "." + fullName
val modifers = DefaultModifierList(codebase)
val classItem =
TurbineClassItem(
codebase,
className,
fullName,
qualifiedName,
containingClass,
modifers,
TurbineClassType.getClassType(typeDecl.tykind()),
)
val members = typeDecl.members()
val fields = mutableListOf<TurbineFieldItem>()
for (member in members) {
when (member.kind()) {
// A class or an interface declaration
TY_DECL -> {
populateClass(member as TyDecl, pkgItem, classItem, false)
}
// A field declaration
VAR_DECL -> {
val field = member as VarDecl
val fieldItem =
TurbineFieldItem(codebase, field.name().value(), classItem, modifers)
fields.add(fieldItem)
}
else -> {
// Do nothing for now
}
}
}
classItem.fields = fields
codebase.addClass(classItem, isTopClass)
itemMap.put(typeDecl, qualifiedName)
if (isTopClass) {
classItem.containingPackage = pkgItem
pkgItem.addTopClass(classItem)
}
}
/** This method sets up inner class hierarchy without using binder. */
private fun setInnerClassHierarchy(units: List<CompUnit>) {
for (unit in units) {
val typeDecls = unit.decls()
for (typeDecl in typeDecls) {
setInnerClasses(typeDecl)
}
}
}
/** Method to setup innerclasses for a single class */
private fun setInnerClasses(typeDecl: TyDecl) {
val className = itemMap[typeDecl]!!
val classItem = codebase.findClass(className)!!
val innerClasses =
typeDecl
.members()
.filter { it.kind() == TY_DECL }
.mapNotNull { it ->
val memberName = itemMap[it]!!
codebase.findClass(memberName)
}
classItem.innerClasses = innerClasses
}
/** This method uses output from binder to setup superclass and implemented interfaces */
private fun setSuperClassHierarchy(units: ImmutableMap<ClassSymbol, SourceTypeBoundClass>) {
for ((sym, cls) in units) {
val classItem = codebase.findClass(sym.toString())
if (classItem != null) {
// Set superclass
val superClassItem =
cls.superclass()?.let { superClass ->
codebase.findClass(superClass.toString())
}
classItem.setSuperClass(superClassItem, null)
// Set direct interfaces
val interfaces =
cls.interfaces().mapNotNull { itf -> codebase.findClass(itf.toString()) }
classItem.directInterfaces = interfaces
}
}
}
}