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.classes_processing.processors.build_time_constants.BuildTimeConstantsClassesProcessor.kt Maven / Gradle / Ivy
package name.remal.gradle_plugins.plugins.classes_processing.processors.build_time_constants
import name.remal.*
import name.remal.gradle_plugins.api.BuildTimeConstants
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.ProcessContext
import name.remal.gradle_plugins.dsl.extensions.findAndUnwrapProperties
import name.remal.gradle_plugins.dsl.extensions.toStringSmart
import name.remal.gradle_plugins.dsl.extensions.unwrapProviders
import name.remal.gradle_plugins.dsl.utils.getGradleLogger
import org.gradle.api.Project
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassWriter
import org.objectweb.asm.ClassWriter.COMPUTE_MAXS
import org.objectweb.asm.Opcodes.*
import org.objectweb.asm.Type
import org.objectweb.asm.tree.*
import kotlin.reflect.jvm.javaMethod
class BuildTimeConstantsClassesProcessor(private val project: Project) : ClassesProcessor {
companion object {
private val skipPackages = setOf(
BuildTimeConstantsClassesProcessor::class.java.packageName,
BuildTimeConstantsClassesValidation::class.java.packageName
).toList()
}
override fun process(bytecode: ByteArray, bytecodeModifier: BytecodeModifier, className: String, resourceName: String, context: ProcessContext) {
if (skipPackages.any { className.startsWith("$it.") }) return
var isChanged = false
val classNode = ClassNode()
val classReader = ClassReader(bytecode)
classReader.accept(classNode)
classNode.methods?.forEach forEachMethodNode@{ methodNode ->
listOfNotNull(
returnNullOnError {
BuildTimeConstants::getClassName to Type::getClassName
},
returnNullOnError {
BuildTimeConstants::getClassSimpleName to { type: Type -> type.className.substringAfterLast('.') }
},
returnNullOnError {
BuildTimeConstants::getClassPackageName to { type: Type -> type.className.substringBeforeLast('.', "") }
},
returnNullOnError {
BuildTimeConstants::getClassInternalName to { type: Type -> type.internalName }
},
returnNullOnError {
BuildTimeConstants::getClassDescriptor to { type: Type -> type.descriptor }
}
)
.forEach { (method, typeConverter) ->
val javaMethod = returnNullOnError { method.javaMethod!! } ?: return@forEach
methodNode.replaceInstructions(
InstructionNodeFilter(LdcInsnNode::class.java, { it.node.cst is Type }),
InstructionNodeFilter(MethodInsnNode::class.java, { it.node.matches(javaMethod) })
) { ldcInsnNode, _ ->
isChanged = true
return@replaceInstructions InsnList().apply {
val type = ldcInsnNode.cst as Type
add(LdcInsnNode(typeConverter(type)))
}
}
}
listOfNotNull(
returnNullOnError {
BuildTimeConstants::getStringProperty to String::toString
},
returnNullOnError {
BuildTimeConstants::getIntegerProperty to { string: String -> string.toInt() }
},
returnNullOnError {
BuildTimeConstants::getLongProperty to { string: String -> string.toLong() }
},
returnNullOnError {
BuildTimeConstants::getBooleanProperty to String::toBoolean
}
).forEach { (method, stringConverter) ->
val javaMethod = returnNullOnError { method.javaMethod!! } ?: return@forEach
methodNode.replaceInstructions(
InstructionNodeFilter(LdcInsnNode::class.java, { it.node.cst is String }),
InstructionNodeFilter(MethodInsnNode::class.java, { it.node.matches(javaMethod) })
) { ldcInsnNode, _ ->
isChanged = true
return@replaceInstructions InsnList().apply {
val propertyName = ldcInsnNode.cst.toString()
add(LdcInsnNode(stringConverter(
project.property(propertyName).unwrapProviders()?.toStringSmart()
?: throw IllegalStateException("Project property '$propertyName' can't be found or null")
)))
}
}
}
listOfNotNull(
returnNullOnError {
BuildTimeConstants::getStringProperties to String::toString
},
returnNullOnError {
BuildTimeConstants::getIntegerProperties to { string: String -> string.toInt() }
},
returnNullOnError {
BuildTimeConstants::getLongProperties to { string: String -> string.toLong() }
},
returnNullOnError {
BuildTimeConstants::getBooleanProperties to String::toBoolean
}
).forEach { (method, stringConverter) ->
val javaMethod = returnNullOnError { method.javaMethod!! } ?: return@forEach
methodNode.replaceInstructions(
InstructionNodeFilter(LdcInsnNode::class.java, { it.node.cst is String }),
InstructionNodeFilter(MethodInsnNode::class.java, { it.node.matches(javaMethod) })
) { ldcInsnNode, _ ->
isChanged = true
return@replaceInstructions InsnList().apply {
val properties = project.findAndUnwrapProperties(ldcInsnNode.cst.toString()).asSequence()
.map {
try {
it.key to stringConverter(it.value)
} catch (ignored: Exception) {
null
}
}
.filterNotNull()
.toMap()
if (properties.isEmpty()) {
add(MethodInsnNode(INVOKESTATIC, "java/util/Collections", "emptyMap", "()Ljava/util/Map;", false))
} else if (properties.size == 1) {
val entry = properties.entries.iterator().next()
add(LdcInsnNode(entry.key))
add(LdcInsnNode(entry.value))
add(MethodInsnNode(INVOKESTATIC, "java/util/Collections", "singletonMap", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/Map;", false))
} else {
add(TypeInsnNode(NEW, "java/util/HashMap"))
add(InsnNode(DUP))
add(MethodInsnNode(INVOKESPECIAL, "java/util/HashMap", "", "()V", false))
properties.forEach { name, value ->
add(InsnNode(DUP))
add(LdcInsnNode(name))
add(LdcInsnNode(value))
add(MethodInsnNode(INVOKEVIRTUAL, "java/util/HashMap", "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", false))
add(InsnNode(POP))
}
add(MethodInsnNode(INVOKESTATIC, "java/util/Collections", "unmodifiableMap", "(Ljava/util/Map;)Ljava/util/Map;", false))
}
}
}
}
}
if (isChanged) {
val classWriter = ClassWriter(classReader, COMPUTE_MAXS)
classNode.accept(classWriter)
bytecodeModifier.modify(classWriter.toByteArray())
}
}
}
private inline fun returnNullOnError(callback: () -> R) = try {
callback()
} catch (error: Error) {
getGradleLogger(BuildTimeConstantsClassesProcessor::class.java).warn("{}", error.toString())
null
}