Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
name.remal.gradle_plugins.plugins.environment_variables.EnvironmentVariableClassesProcessor.kt Maven / Gradle / Ivy
package name.remal.gradle_plugins.plugins.environment_variables
import name.remal.*
import name.remal.gradle_plugins.api.AutoService
import name.remal.gradle_plugins.api.classes_processing.BytecodeModifier
import name.remal.gradle_plugins.api.classes_processing.ClassesProcessor
import name.remal.gradle_plugins.api.classes_processing.ClassesProcessorsGradleTaskFactory
import name.remal.gradle_plugins.api.classes_processing.ProcessContext
import name.remal.gradle_plugins.dsl.*
import name.remal.gradle_plugins.dsl.EnvironmentVariable.EnvironmentVariables
import name.remal.gradle_plugins.dsl.extensions.javaPackageName
import name.remal.gradle_plugins.dsl.extensions.toHasEntries
import name.remal.gradle_plugins.dsl.utils.forEachAnnotationNode
import org.gradle.api.Project
import org.gradle.api.tasks.compile.AbstractCompile
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassWriter
import org.objectweb.asm.Opcodes.*
import org.objectweb.asm.Type
import org.objectweb.asm.Type.*
import org.objectweb.asm.tree.*
import kotlin.reflect.jvm.javaMethod
class EnvironmentVariableClassesProcessor(val project: Project) : ClassesProcessor {
companion object {
private val notDefinedPluginType: Type = getType(NotDefinedProjectPlugin::class.java)
private val notDefinedEnvironmentVariableConditionType: Type = getType(NotDefinedEnvironmentVariableCondition::class.java)
private val pluginDesc: String = getDescriptor(Plugin::class.java)
private val outerClassInternalNameRegex = Regex("\\\$[^\$/]*\$")
}
private data class FallbackPluginInfo(
val pluginId: String,
val pluginType: Type
)
@Suppress("ComplexMethod", "LongMethod")
override fun process(bytecode: ByteArray, bytecodeModifier: BytecodeModifier, className: String, resourceName: String, context: ProcessContext) {
val classNode = ClassNode().also { ClassReader(bytecode).accept(it) }
val envVarNodes = buildList {
fun AnnotationNode.processEnvironmentVariable() {
this[EnvironmentVariable::description].nullIfEmpty().let { this[EnvironmentVariable::description] = it }
this[EnvironmentVariable::pluginId].nullIfEmpty().let { this[EnvironmentVariable::pluginId] = it }
this[EnvironmentVariable::pluginClass].nullIf { this == notDefinedPluginType }.let { this[EnvironmentVariable::pluginClass] = it }
this[EnvironmentVariable::scope].nullIfEmpty().let { this[EnvironmentVariable::scope] = it }
this[EnvironmentVariable::conditionClass].nullIf { this == notDefinedEnvironmentVariableConditionType }.let { this[EnvironmentVariable::conditionClass] = it }
}
forEachAnnotationNode(classNode, EnvironmentVariable::class.java) {
it.processEnvironmentVariable()
add(it)
}
forEachAnnotationNode(classNode, EnvironmentVariables::class.java) { envVars ->
envVars[EnvironmentVariables::description].nullIfEmpty().let { envVars[EnvironmentVariables::description] = it }
envVars[EnvironmentVariables::pluginId].nullIfEmpty().let { envVars[EnvironmentVariables::pluginId] = it }
envVars[EnvironmentVariables::pluginClass].nullIf { this == notDefinedPluginType }.let { envVars[EnvironmentVariables::pluginClass] = it }
envVars[EnvironmentVariables::scope].nullIfEmpty().let { envVars[EnvironmentVariables::scope] = it }
envVars[EnvironmentVariables::conditionClass].nullIf { this == notDefinedEnvironmentVariableConditionType }.let { envVars[EnvironmentVariables::conditionClass] = it }
@Suppress("UNCHECKED_CAST")
(envVars[EnvironmentVariables::value] as? List).nullIfEmpty()?.forEach { envVar ->
envVar.processEnvironmentVariable()
envVar[EnvironmentVariable::description] = envVar[EnvironmentVariable::description] ?: envVars[EnvironmentVariables::description]
envVar[EnvironmentVariable::pluginId] = envVar[EnvironmentVariable::pluginId] ?: envVars[EnvironmentVariables::pluginId]
envVar[EnvironmentVariable::pluginClass] = envVar[EnvironmentVariable::pluginClass] ?: envVars[EnvironmentVariables::pluginClass]
envVar[EnvironmentVariable::scope] = envVar[EnvironmentVariable::scope] ?: envVars[EnvironmentVariables::scope]
envVar[EnvironmentVariable::conditionClass] = envVar[EnvironmentVariable::conditionClass] ?: envVars[EnvironmentVariables::conditionClass]
add(envVar)
}
}
if (isEmpty()) return
}
val fallbackPluginInfo: FallbackPluginInfo? = run {
if (envVarNodes.all { null != it[EnvironmentVariable::pluginId] }) return@run null
var currentClassNode = classNode
while (true) {
val pluginAnnotationNode = currentClassNode.visibleAnnotations?.firstOrNull { it.desc == pluginDesc } ?: currentClassNode.invisibleAnnotations?.firstOrNull { it.desc == pluginDesc }
if (pluginAnnotationNode != null) {
val pluginId = pluginAnnotationNode[Plugin::id]
if (pluginId != null) {
return@run FallbackPluginInfo(pluginId, getType("L${currentClassNode.name};"))
}
}
val outerClassInternalName = outerClassInternalNameRegex.replace(currentClassNode.name, "")
if (outerClassInternalName == currentClassNode.name) break
val outerClassResourceName = outerClassInternalName + CLASS_FILE_NAME_SUFFIX
val outerClassBytecode = context.readBinaryResource(outerClassResourceName) ?: break
currentClassNode = ClassNode().also { ClassReader(outerClassBytecode).accept(it) }
}
return@run null
}
envVarNodes.forEach { envVarNode ->
val infoClassName = buildString {
append(project.javaPackageName)
append(".").append(EnvironmentVariableInfo::class.java.simpleName)
append("$$").append(sha256(arrayOf(
classNode.name,
envVarNode[EnvironmentVariable::value],
envVarNode[EnvironmentVariable::pluginId] ?: fallbackPluginInfo?.pluginId,
envVarNode[EnvironmentVariable::pluginClass] ?: fallbackPluginInfo?.pluginType,
envVarNode[EnvironmentVariable::scope]
).joinToString(":")))
}
writeInfoClass(infoClassName, envVarNode, fallbackPluginInfo, context)
context.writeService(EnvironmentVariableInfo::class.java, infoClassName)
}
}
@Suppress("ComplexMethod", "LongMethod")
private fun writeInfoClass(infoClassName: String, envVarNode: AnnotationNode, fallbackPluginInfo: FallbackPluginInfo?, context: ProcessContext) {
val classNode = ClassNode()
classNode.version = V1_8
classNode.access = ACC_PUBLIC
classNode.name = infoClassName.replace('.', '/')
classNode.superName = getInternalName(Any::class.java)
classNode.interfaces = mutableListOf(getInternalName(EnvironmentVariableInfo::class.java))
classNode.methods = mutableListOf()
classNode.methods.add(MethodNode(ACC_PUBLIC, "", "()V", null, null).also { method ->
method.instructions = InsnList().also {
it.add(LabelNode())
it.add(VarInsnNode(ALOAD, 0))
it.add(MethodInsnNode(INVOKESPECIAL, classNode.superName, method.name, method.desc, false))
it.add(InsnNode(RETURN))
}
method.maxLocals = 1
method.maxStack = 1
})
fun Any?.toInsnNode(): AbstractInsnNode {
return if (this == null) InsnNode(ACONST_NULL) else LdcInsnNode(this)
}
run {
val javaMethod = EnvironmentVariableInfo::getVariableName.javaMethod!!
classNode.methods.add(MethodNode(ACC_PUBLIC, javaMethod.name, getMethodDescriptor(javaMethod), null, null).also {
it.instructions = InsnList().also {
it.add(LabelNode())
it.add(envVarNode[EnvironmentVariable::value].toInsnNode())
it.add(InsnNode(ARETURN))
}
it.maxLocals = 1
it.maxStack = 1
})
}
run {
val javaMethod = EnvironmentVariableInfo::getDescription.javaMethod!!
classNode.methods.add(MethodNode(ACC_PUBLIC, javaMethod.name, getMethodDescriptor(javaMethod), null, null).also {
it.instructions = InsnList().also {
it.add(LabelNode())
it.add(envVarNode[EnvironmentVariable::description].toInsnNode())
it.add(InsnNode(ARETURN))
}
it.maxLocals = 1
it.maxStack = 1
})
}
run {
val javaMethod = EnvironmentVariableInfo::getPluginId.javaMethod!!
classNode.methods.add(MethodNode(ACC_PUBLIC, javaMethod.name, getMethodDescriptor(javaMethod), null, null).also {
it.instructions = InsnList().also {
it.add(LabelNode())
it.add((envVarNode[EnvironmentVariable::pluginId] ?: fallbackPluginInfo?.pluginId).toInsnNode())
it.add(InsnNode(ARETURN))
}
it.maxLocals = 1
it.maxStack = 1
})
}
run {
val javaMethod = EnvironmentVariableInfo::getPluginClass.javaMethod!!
classNode.methods.add(MethodNode(ACC_PUBLIC, javaMethod.name, getMethodDescriptor(javaMethod), null, null).also {
it.instructions = InsnList().also {
it.add(LabelNode())
it.add((envVarNode[EnvironmentVariable::pluginClass] ?: fallbackPluginInfo?.pluginType).toInsnNode())
it.add(InsnNode(ARETURN))
}
it.maxLocals = 1
it.maxStack = 1
})
}
run {
val javaMethod = EnvironmentVariableInfo::getScope.javaMethod!!
classNode.methods.add(MethodNode(ACC_PUBLIC, javaMethod.name, getMethodDescriptor(javaMethod), null, null).also {
it.instructions = InsnList().also {
it.add(LabelNode())
it.add(envVarNode[EnvironmentVariable::scope].toInsnNode())
it.add(InsnNode(ARETURN))
}
it.maxLocals = 1
it.maxStack = 1
})
}
run {
val javaMethod = EnvironmentVariableInfo::getConditionClass.javaMethod!!
classNode.methods.add(MethodNode(ACC_PUBLIC, javaMethod.name, getMethodDescriptor(javaMethod), null, null).also {
it.instructions = InsnList().also {
it.add(LabelNode())
it.add(envVarNode[EnvironmentVariable::conditionClass].toInsnNode())
it.add(InsnNode(ARETURN))
}
it.maxLocals = 1
it.maxStack = 1
})
}
val classWriter = ClassWriter(0)
classNode.accept(classWriter)
context.writeBinaryResource(classNode.name + CLASS_FILE_NAME_SUFFIX, classWriter.toByteArray())
}
}
@AutoService
class EnvironmentVariableClassesProcessorFactory : ClassesProcessorsGradleTaskFactory {
override fun createClassesProcessors(compileTask: AbstractCompile): List {
if (!compileTask.classpath.toHasEntries().containsClass(EnvironmentVariable::class.java)) return emptyList()
return listOf(EnvironmentVariableClassesProcessor(compileTask.project))
}
}