
dagger.hilt.android.plugin.root.Aggregator.kt Maven / Gradle / Ivy
/*
* Copyright (C) 2021 The Dagger Authors.
*
* 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 dagger.hilt.android.plugin.task
import com.squareup.javapoet.ClassName
import dagger.hilt.android.plugin.root.AggregatedAnnotation
import dagger.hilt.android.plugin.util.forEachZipEntry
import dagger.hilt.android.plugin.util.isClassFile
import dagger.hilt.android.plugin.util.isJarFile
import dagger.hilt.processor.internal.root.ir.AggregatedDepsIr
import dagger.hilt.processor.internal.root.ir.AggregatedEarlyEntryPointIr
import dagger.hilt.processor.internal.root.ir.AggregatedElementProxyIr
import dagger.hilt.processor.internal.root.ir.AggregatedRootIr
import dagger.hilt.processor.internal.root.ir.AggregatedUninstallModulesIr
import dagger.hilt.processor.internal.root.ir.AliasOfPropagatedDataIr
import dagger.hilt.processor.internal.root.ir.DefineComponentClassesIr
import dagger.hilt.processor.internal.root.ir.ProcessedRootSentinelIr
import java.io.File
import java.io.InputStream
import java.util.zip.ZipInputStream
import org.objectweb.asm.AnnotationVisitor
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.Opcodes
import org.objectweb.asm.Type
import org.slf4j.Logger
/** Aggregates Hilt dependencies. */
internal class Aggregator private constructor(
private val logger: Logger,
private val asmApiVersion: Int,
) {
private val classVisitor = AggregatedDepClassVisitor(logger, asmApiVersion)
val aggregatedRoots: Set
get() = classVisitor.aggregatedRoots
val processedRoots: Set
get() = classVisitor.processedRoots
val defineComponentDeps: Set
get() = classVisitor.defineComponentDeps
val aliasOfDeps: Set
get() = classVisitor.aliasOfDeps
val aggregatedDeps: Set
get() = classVisitor.aggregatedDeps
val aggregatedDepProxies: Set
get() = classVisitor.aggregatedDepProxies
val allAggregatedDepProxies: Set
get() = classVisitor.allAggregatedDepProxies
val uninstallModulesDeps: Set
get() = classVisitor.uninstallModulesDeps
val earlyEntryPointDeps: Set
get() = classVisitor.earlyEntryPointDeps
private class AggregatedDepClassVisitor(
private val logger: Logger,
private val asmApiVersion: Int,
) : ClassVisitor(asmApiVersion) {
val aggregatedRoots = mutableSetOf()
val processedRoots = mutableSetOf()
val defineComponentDeps = mutableSetOf()
val aliasOfDeps = mutableSetOf()
val aggregatedDeps = mutableSetOf()
val aggregatedDepProxies = mutableSetOf()
val allAggregatedDepProxies = mutableSetOf()
val uninstallModulesDeps = mutableSetOf()
val earlyEntryPointDeps = mutableSetOf()
var accessCode: Int = Opcodes.ACC_PUBLIC
lateinit var annotatedClassName: ClassName
override fun visit(
version: Int,
access: Int,
name: String,
signature: String?,
superName: String?,
interfaces: Array?
) {
accessCode = access
annotatedClassName = Type.getObjectType(name).toClassName()
super.visit(version, access, name, signature, superName, interfaces)
}
override fun visitAnnotation(descriptor: String, visible: Boolean): AnnotationVisitor? {
val nextAnnotationVisitor = super.visitAnnotation(descriptor, visible)
val aggregatedAnnotation = AggregatedAnnotation.fromString(descriptor)
val isHiltAnnotated = aggregatedAnnotation != AggregatedAnnotation.NONE
// For non-public deps, a proxy might be needed, make a note of it.
if (isHiltAnnotated && (accessCode and Opcodes.ACC_PUBLIC) != Opcodes.ACC_PUBLIC) {
allAggregatedDepProxies.add(
AggregatedElementProxyIr(
fqName = annotatedClassName.peerClass("_" + annotatedClassName.simpleName()),
value = annotatedClassName
)
)
}
when (aggregatedAnnotation) {
AggregatedAnnotation.AGGREGATED_ROOT -> {
return object : AnnotationVisitor(asmApiVersion, nextAnnotationVisitor) {
lateinit var rootClass: String
lateinit var originatingRootClass: String
lateinit var rootAnnotationClassName: Type
override fun visit(name: String, value: Any?) {
when (name) {
"root" -> rootClass = value as String
"originatingRoot" -> originatingRootClass = value as String
"rootAnnotation" -> rootAnnotationClassName = (value as Type)
}
super.visit(name, value)
}
override fun visitEnd() {
aggregatedRoots.add(
AggregatedRootIr(
fqName = annotatedClassName,
root = rootClass.toClassName(),
originatingRoot = originatingRootClass.toClassName(),
rootAnnotation = rootAnnotationClassName.toClassName()
)
)
super.visitEnd()
}
}
}
AggregatedAnnotation.PROCESSED_ROOT_SENTINEL -> {
return object : AnnotationVisitor(asmApiVersion, nextAnnotationVisitor) {
val rootClasses = mutableListOf()
override fun visitArray(name: String): AnnotationVisitor? {
return when (name) {
"roots" -> visitValue { value -> rootClasses.add(value as String) }
else -> super.visitArray(name)
}
}
override fun visitEnd() {
processedRoots.add(
ProcessedRootSentinelIr(
fqName = annotatedClassName,
roots = rootClasses.map { it.toClassName() }
)
)
super.visitEnd()
}
}
}
AggregatedAnnotation.DEFINE_COMPONENT -> {
return object : AnnotationVisitor(asmApiVersion, nextAnnotationVisitor) {
lateinit var componentClass: String
override fun visit(name: String, value: Any?) {
when (name) {
"component", "builder" -> componentClass = value as String
}
super.visit(name, value)
}
override fun visitEnd() {
defineComponentDeps.add(
DefineComponentClassesIr(
fqName = annotatedClassName,
component = componentClass.toClassName()
)
)
super.visitEnd()
}
}
}
AggregatedAnnotation.ALIAS_OF -> {
return object : AnnotationVisitor(asmApiVersion, nextAnnotationVisitor) {
val defineComponentScopeClassNames = mutableSetOf()
lateinit var aliasClassName: Type
// visit() handles both array and non-array values.
// For array values, each value in the array will be visited individually.
override fun visit(name: String, value: Any?) {
when (name) {
// Older versions of AliasOfPropagatedData only passed a single defineComponentScope
// class value. Fall back on reading the single value if we get old propagated data.
"defineComponentScope",
"defineComponentScopes" -> defineComponentScopeClassNames.add(value as Type)
"alias" -> aliasClassName = (value as Type)
}
super.visit(name, value)
}
override fun visitEnd() {
aliasOfDeps.add(
AliasOfPropagatedDataIr(
fqName = annotatedClassName,
defineComponentScopes =
defineComponentScopeClassNames.map({ it.toClassName() }).toList(),
alias = aliasClassName.toClassName(),
)
)
super.visitEnd()
}
}
}
AggregatedAnnotation.AGGREGATED_DEP -> {
return object : AnnotationVisitor(asmApiVersion, nextAnnotationVisitor) {
val componentClasses = mutableListOf()
var testClass: String? = null
val replacesClasses = mutableListOf()
var moduleClass: String? = null
var entryPoint: String? = null
var componentEntryPoint: String? = null
override fun visit(name: String, value: Any?) {
when (name) {
"test" -> testClass = value as String
}
super.visit(name, value)
}
override fun visitArray(name: String): AnnotationVisitor? {
return when (name) {
"components" ->
visitValue { value -> componentClasses.add(value as String) }
"replaces" ->
visitValue { value -> replacesClasses.add(value as String) }
"modules" ->
visitValue { value -> moduleClass = value as String }
"entryPoints" ->
visitValue { value -> entryPoint = value as String }
"componentEntryPoints" ->
visitValue { value -> componentEntryPoint = value as String }
else -> super.visitArray(name)
}
}
override fun visitEnd() {
aggregatedDeps.add(
AggregatedDepsIr(
fqName = annotatedClassName,
components = componentClasses.map { it.toClassName() },
test = testClass?.toClassName(),
replaces = replacesClasses.map { it.toClassName() },
module = moduleClass?.toClassName(),
entryPoint = entryPoint?.toClassName(),
componentEntryPoint = componentEntryPoint?.toClassName()
)
)
super.visitEnd()
}
}
}
AggregatedAnnotation.AGGREGATED_DEP_PROXY -> {
return object : AnnotationVisitor(asmApiVersion, nextAnnotationVisitor) {
lateinit var valueClassName: Type
override fun visit(name: String, value: Any?) {
when (name) {
"value" -> valueClassName = (value as Type)
}
super.visit(name, value)
}
override fun visitEnd() {
aggregatedDepProxies.add(
AggregatedElementProxyIr(
fqName = annotatedClassName,
value = valueClassName.toClassName(),
)
)
super.visitEnd()
}
}
}
AggregatedAnnotation.AGGREGATED_UNINSTALL_MODULES -> {
return object : AnnotationVisitor(asmApiVersion, nextAnnotationVisitor) {
lateinit var testClass: String
val uninstallModulesClasses = mutableListOf()
override fun visit(name: String, value: Any?) {
when (name) {
"test" -> testClass = value as String
}
super.visit(name, value)
}
override fun visitArray(name: String): AnnotationVisitor? {
return when (name) {
"uninstallModules" ->
visitValue { value -> uninstallModulesClasses.add(value as String) }
else -> super.visitArray(name)
}
}
override fun visitEnd() {
uninstallModulesDeps.add(
AggregatedUninstallModulesIr(
fqName = annotatedClassName,
test = testClass.toClassName(),
uninstallModules = uninstallModulesClasses.map { it.toClassName() }
)
)
super.visitEnd()
}
}
}
AggregatedAnnotation.AGGREGATED_EARLY_ENTRY_POINT -> {
return object : AnnotationVisitor(asmApiVersion, nextAnnotationVisitor) {
lateinit var earlyEntryPointClass: String
override fun visit(name: String, value: Any?) {
when (name) {
"earlyEntryPoint" -> earlyEntryPointClass = value as String
}
super.visit(name, value)
}
override fun visitEnd() {
earlyEntryPointDeps.add(
AggregatedEarlyEntryPointIr(
fqName = annotatedClassName,
earlyEntryPoint = earlyEntryPointClass.toClassName()
)
)
super.visitEnd()
}
}
}
else -> {
logger.warn("Found an unknown annotation in Hilt aggregated packages: $descriptor")
}
}
return nextAnnotationVisitor
}
fun visitValue(block: (value: Any) -> Unit) =
object : AnnotationVisitor(asmApiVersion) {
override fun visit(nullName: String?, value: Any) {
block(value)
}
}
}
private fun process(files: Iterable) {
files.forEach { file ->
when {
file.isFile -> visitFile(file)
file.isDirectory -> file.walkTopDown().filter { it.isFile }.forEach { visitFile(it) }
else -> logger.warn("Can't process file/directory that doesn't exist: $file")
}
}
}
private fun visitFile(file: File) {
when {
file.isJarFile() -> ZipInputStream(file.inputStream()).forEachZipEntry { inputStream, entry ->
if (entry.isClassFile()) {
visitClass(inputStream)
}
}
file.isClassFile() -> file.inputStream().use { visitClass(it) }
else -> logger.debug("Don't know how to process file: $file")
}
}
private fun visitClass(classFileInputStream: InputStream) {
ClassReader(classFileInputStream).accept(
classVisitor,
ClassReader.SKIP_CODE and ClassReader.SKIP_DEBUG and ClassReader.SKIP_FRAMES
)
}
companion object {
fun from(
logger: Logger,
asmApiVersion: Int,
input: Iterable
) = Aggregator(logger, asmApiVersion).apply { process(input) }
// Converts this Type to a ClassName, used instead of ClassName.bestGuess() because ASM class
// names are based off descriptors and uses 'reflection' naming, i.e. inner classes are split
// by '$' instead of '.'
fun Type.toClassName(): ClassName {
val binaryName = this.className
val packageNameEndIndex = binaryName.lastIndexOf('.')
val packageName = if (packageNameEndIndex != -1) {
binaryName.substring(0, packageNameEndIndex)
} else {
""
}
val shortNames = binaryName.substring(packageNameEndIndex + 1).split('$')
return ClassName.get(packageName, shortNames.first(), *shortNames.drop(1).toTypedArray())
}
// Converts this String representing the canonical name of a class to a ClassName.
fun String.toClassName(): ClassName {
return ClassName.bestGuess(this)
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy