All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy