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

com.flyjingfish.android_aop_plugin.utils.AopTaskUtils.kt Maven / Gradle / Ivy

package com.flyjingfish.android_aop_plugin.utils

import com.flyjingfish.android_aop_plugin.beans.AopMatchCut
import com.flyjingfish.android_aop_plugin.beans.ClassMethodRecord
import com.flyjingfish.android_aop_plugin.beans.CutInfo
import com.flyjingfish.android_aop_plugin.beans.CutMethodJson
import com.flyjingfish.android_aop_plugin.beans.MethodRecord
import com.flyjingfish.android_aop_plugin.beans.ReplaceMethodInfo
import com.flyjingfish.android_aop_plugin.beans.TmpFile
import com.flyjingfish.android_aop_plugin.beans.WovenResult
import com.flyjingfish.android_aop_plugin.config.AndroidAopConfig
import com.flyjingfish.android_aop_plugin.ex.AndroidAOPOverrideMethodException
import com.flyjingfish.android_aop_plugin.scanner_visitor.ClassSuperScanner
import com.flyjingfish.android_aop_plugin.scanner_visitor.MethodReplaceInvokeVisitor
import com.flyjingfish.android_aop_plugin.scanner_visitor.RegisterMapWovenInfoCode
import com.flyjingfish.android_aop_plugin.scanner_visitor.ReplaceBaseClassVisitor
import com.flyjingfish.android_aop_plugin.scanner_visitor.ReplaceInvokeMethodVisitor
import com.flyjingfish.android_aop_plugin.scanner_visitor.SearchAOPConfigVisitor
import com.flyjingfish.android_aop_plugin.scanner_visitor.SearchAopMethodVisitor
import com.flyjingfish.android_aop_plugin.scanner_visitor.SuspendReturnScanner
import com.flyjingfish.android_aop_plugin.scanner_visitor.WovenIntoCode
import com.flyjingfish.android_aop_plugin.utils.Utils._CLASS
import com.flyjingfish.android_aop_plugin.utils.Utils.slashToDot
import javassist.Modifier
import org.gradle.api.Project
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.ClassWriter
import org.objectweb.asm.MethodVisitor
import org.objectweb.asm.Opcodes
import java.io.File
import java.io.FileInputStream
import java.util.UUID
import java.util.jar.JarFile

class AopTaskUtils(
    private val project: Project,
    private val variantName: String,
    private val isAndroidModule: Boolean = true
) {
    fun processFileForConfig(file: File, directory: File, directoryPath: String) {
        if (file.isFile) {
            val className = file.getFileClassname(directory)
            WovenInfoUtils.addClassName(className)
            if (file.name.endsWith(Utils.AOP_CONFIG_END_NAME)) {
                FileInputStream(file).use { inputs ->
                    val classReader = ClassReader(inputs.readAllBytes())
                    classReader.accept(
                        SearchAOPConfigVisitor(), ClassReader.EXPAND_FRAMES
                    )
                }
            } else if (file.absolutePath.endsWith(Utils._CLASS)) {
                if (AndroidAopConfig.verifyLeafExtends && !className.startsWith("kotlinx/") && !className.startsWith(
                        "kotlin/"
                    )
                ) {
                    FileInputStream(file).use { inputs ->
                        val bytes = inputs.readAllBytes()
                        if (bytes.isNotEmpty()) {
                            val inAsm = FileHashUtils.isAsmScan(file.absolutePath, bytes, 1)
                            if (inAsm) {
                                val classReader = ClassReader(bytes)
                                classReader.accept(
                                    ClassSuperScanner(file.absolutePath),
                                    ClassReader.SKIP_CODE or ClassReader.SKIP_DEBUG or ClassReader.SKIP_FRAMES
                                )
                            }
                        }
                    }
                }
            }

        }
    }

    fun processJarForConfig(file: File) {
        WovenInfoUtils.addClassPath(file.absolutePath)
        val jarFile = JarFile(file)
        val enumeration = jarFile.entries()
        while (enumeration.hasMoreElements()) {
            val jarEntry = enumeration.nextElement()
            try {
                val entryName = jarEntry.name
                if (jarEntry.isDirectory || jarEntry.name.isEmpty()) {
                    continue
                }
                if (entryName.endsWith(Utils._CLASS)) {
                    WovenInfoUtils.addClassName(entryName)
                }
//                    logger.error("entryName="+entryName)
                if (entryName.endsWith(Utils.AOP_CONFIG_END_NAME)) {
                    jarFile.getInputStream(jarEntry).use { inputs ->
                        val classReader = ClassReader(inputs.readAllBytes())
                        classReader.accept(
                            SearchAOPConfigVisitor(), ClassReader.EXPAND_FRAMES
                        )
                    }
                } else if (entryName.endsWith(Utils._CLASS)) {
                    if (AndroidAopConfig.verifyLeafExtends && !entryName.startsWith("kotlinx/") && !entryName.startsWith(
                            "kotlin/"
                        )
                    ) {
                        jarFile.getInputStream(jarEntry).use { inputs ->
                            val bytes = inputs.readAllBytes()
                            if (bytes.isNotEmpty()) {
                                val inAsm = FileHashUtils.isAsmScan(entryName, bytes, 1)
                                if (inAsm) {
                                    val classReader = ClassReader(bytes)
                                    classReader.accept(
                                        ClassSuperScanner(entryName),
                                        ClassReader.SKIP_CODE or ClassReader.SKIP_DEBUG or ClassReader.SKIP_FRAMES
                                    )
                                }
                            }
                        }
                    }
                }
            } catch (e: Exception) {
                e.printStackTrace()
//                    if (!(e is ZipException && e.message?.startsWith("duplicate entry:") == true)) {
//                        logger.warn("Merge jar error entry:[${jarEntry.name}], error message:$e")
//                    }
            }
        }
        jarFile.close()
    }

    fun loadJoinPointConfigEnd(isApp: Boolean) {
        WovenInfoUtils.removeDeletedClass()
//        logger.error(""+WovenInfoUtils.aopMatchCuts)
//        InitConfig.saveBuildConfig()
        ClassPoolUtils.initClassPool()
//        ClassPoolUtils.initClassPool(project,variantName)
        FileHashUtils.isChangeAopMatch = WovenInfoUtils.aopMatchsChanged()
        WovenInfoUtils.aopCollectChanged(FileHashUtils.isChangeAopMatch)

        WovenInfoUtils.checkLeafConfig(isApp)
    }

    fun searchJoinPointLocationStart(project: Project) {
        if (WovenInfoUtils.isHasExtendsReplace() && isAndroidModule) {
            val androidConfig = AndroidConfig(project)
            val list: List = androidConfig.getBootClasspath()
            for (file in list) {
                try {
                    val jarFile = JarFile(file)
                    val enumeration = jarFile.entries()
                    while (enumeration.hasMoreElements()) {
                        val jarEntry = enumeration.nextElement()
                        try {
                            val entryName = jarEntry.name
                            if (entryName.endsWith(Utils._CLASS)) {
                                val className = entryName.replace(".class", "")
                                WovenInfoUtils.addExtendsReplace(slashToDot(className))
                            }
                        } catch (_: Exception) {

                        }
                    }
                    jarFile.close()
                } catch (e: Exception) {
                    e.printStackTrace()
                }
            }
        }
    }

    fun processFileForSearch(
        file: File,
        directory: File,
        directoryPath: String,
        addClassMethodRecords: MutableMap,
        deleteClassMethodRecords: MutableSet
    ) {
        if (file.isFile) {
            val isClassFile = file.name.endsWith(Utils._CLASS)
            val entryName = file.getFileClassname(directory)
            val thisClassName = Utils.slashToDotClassName(entryName).replace(Utils._CLASS, "")
            val className = file.getFileClassname(directory).replace(".class", "")
            if (isClassFile && AndroidAopConfig.inRules(thisClassName)) {
                FileInputStream(file).use { inputs ->
                    val bytes = inputs.readAllBytes()

                    if (bytes.isNotEmpty()) {
                        val inAsm = FileHashUtils.isAsmScan(file.absolutePath, bytes, 2)
                        if (inAsm) {

                            WovenInfoUtils.deleteClassMethodRecord(file.absolutePath)
                            WovenInfoUtils.deleteReplaceMethodInfo(file.absolutePath)
                            try {
                                val classReader = ClassReader(bytes)
                                var matchAopMatchCuts: List? = null
                                classReader.accept(SearchAopMethodVisitor(
                                    object : SearchAopMethodVisitor.OnCallBackMethod {
                                        override fun onBackMatch(aopMatchCuts: List) {
                                            matchAopMatchCuts = aopMatchCuts
                                        }

                                        override fun onBackMethodRecord(methodRecord: MethodRecord) {
                                            val record = ClassMethodRecord(
                                                file.absolutePath,
                                                methodRecord
                                            )
//                                                    WovenInfoUtils.addClassMethodRecords(record)
                                            addClassMethodRecords[file.absolutePath + methodRecord.getKey()] =
                                                record
                                        }

                                        override fun onDeleteMethodRecord(methodRecord: MethodRecord) {
                                            deleteClassMethodRecords.add(file.absolutePath + methodRecord.getKey())
                                        }

                                        override fun onBackReplaceMethodInfo(replaceMethodInfo: ReplaceMethodInfo) {
                                            WovenInfoUtils.addReplaceMethodInfo(
                                                file.absolutePath,
                                                replaceMethodInfo
                                            )
                                        }

                                        override fun onThrowOverrideMethod(className: String) {
                                            throwOverride(className)
                                        }
                                    }
                                ), ClassReader.EXPAND_FRAMES)
                                processOverride(
                                    slashToDot(className),
                                    matchAopMatchCuts,
                                    entryName
                                ) {
                                    val record = ClassMethodRecord(file.absolutePath, it)
                                    addClassMethodRecords[file.absolutePath + it.getKey()] = record
                                }
                            } catch (e: Exception) {
                                if (e is AndroidAOPOverrideMethodException) {
                                    throw e
                                }
                                e.printStackTrace()
                            }
                        }
                    }
                }
            }

            if (file.absolutePath.endsWith(Utils._CLASS)) {
                WovenInfoUtils.addExtendsReplace(slashToDot(className))

                val isAopCutClass =
                    WovenInfoUtils.isAopMethodCutClass(className) || WovenInfoUtils.isAopMatchCutClass(
                        className
                    )
                if (isAopCutClass) {
                    FileInputStream(file).use { inputs ->
                        val bytes = inputs.readAllBytes()
                        if (bytes.isNotEmpty()) {
                            val classReader = ClassReader(bytes)
                            classReader.accept(
                                SuspendReturnScanner(), 0
                            )
                        }
                    }
                }

            }
        }

    }

    private fun processOverride(
        className: String,
        matchAopMatchCuts: List?,
        entryName: String,
        addCut: (MethodRecord) -> Unit
    ) {
        matchAopMatchCuts?.filter { it.overrideMethod && !it.isMatchAllMethod() && !it.isMatchPackageName() }
            ?.let {
                val ctClass = try {
                    ClassPoolUtils.classPool?.getCtClass(className) ?: return
                } catch (e: Exception) {
                    return
                }
                val isInterface = Modifier.isInterface(ctClass.modifiers)
                if (isInterface) {
                    return@let
                }
                val allMethods = ctClass.methods.filter { ct ->
                    !Modifier.isStatic(ct.modifiers)
                            && !Modifier.isFinal(ct.modifiers)
                            && !Modifier.isPrivate(ct.modifiers)
                }

                val ctClassName = ctClass.name
                for (aopMatchCut in it) {
                    for (methodName in aopMatchCut.methodNames) {
                        if (methodName != "@null") {
                            val matchMethodInfo =
                                Utils.getMethodInfo(methodName)
                            for (allMethod in allMethods) {
                                val name = allMethod.name
                                val descriptor = allMethod.signature
                                if (matchMethodInfo != null && name == matchMethodInfo.name) {
                                    val isBack = try {
                                        Utils.verifyMatchCut(descriptor, matchMethodInfo)
                                    } catch (e: Exception) {
                                        true
                                    }
                                    val superMethodClass = allMethod.declaringClass
                                    val ctSuperClassName = superMethodClass.name
                                    val isInnerClass = ctSuperClassName.contains("$")
                                    val isSamePackageName = ctSuperClassName.substring(
                                        0,
                                        ctSuperClassName.lastIndexOf(".")
                                    ) == ctClassName.substring(0, ctClassName.lastIndexOf("."))
                                    val isBackMethod = if (isInnerClass) {
                                        if (Modifier.isStatic(superMethodClass.modifiers) || Modifier.isInterface(
                                                superMethodClass.modifiers
                                            )
                                        ) {
                                            isSamePackageName
                                        } else {
                                            false
                                        }
                                    } else {
                                        isSamePackageName
                                    }

                                    if (isBack && (!Modifier.isPackage(allMethod.modifiers) || isBackMethod)) {
//                                    printLog("processOverride=name=$name,descriptor=$descriptor")
                                        val cutInfo = CutInfo(
                                            "匹配切面",
                                            slashToDot(
                                                entryName.replace(Utils._CLASS, "")
                                            ),
                                            aopMatchCut.cutClassName,
                                            CutMethodJson(name, descriptor, false)
                                        )
                                        val methodRecord = MethodRecord(
                                            name,
                                            descriptor,
                                            mutableSetOf(aopMatchCut.cutClassName),
                                            false,
                                            mutableMapOf().apply {
                                                put(
                                                    UUID.randomUUID().toString(), cutInfo
                                                )
                                            },
                                            true,
                                            superMethodClass.name
                                        )

                                        addCut(methodRecord)
                                    }
                                }
                            }

                        }
                    }
                }
//            for (allMethod in allMethods) {
//                allMethod.declaringClass.detach()
//            }
//            ctClass.detach()
            }
    }

    fun processJarForSearch(
        file: File,
        addClassMethodRecords: MutableMap,
        deleteClassMethodRecords: MutableSet
    ) {
        val jarFile = JarFile(file)
        val enumeration = jarFile.entries()
        while (enumeration.hasMoreElements()) {
            val jarEntry = enumeration.nextElement()
            try {
                val entryName = jarEntry.name
                if (jarEntry.isDirectory || entryName.isEmpty() || entryName.startsWith("META-INF/") || "module-info.class" == entryName) {
                    continue
                }
                val isClassFile = entryName.endsWith(Utils._CLASS)
//                    printLog("tranEntryName="+tranEntryName)
                val thisClassName = Utils.slashToDotClassName(entryName).replace(Utils._CLASS, "")
                val className = entryName.replace(".class", "")
                if (isClassFile && AndroidAopConfig.inRules(thisClassName)) {

                    jarFile.getInputStream(jarEntry).use { inputs ->
                        val bytes = inputs.readAllBytes();
                        if (bytes.isNotEmpty()) {
                            val inAsm = FileHashUtils.isAsmScan(entryName, bytes, 2)
                            if (inAsm) {
                                WovenInfoUtils.deleteClassMethodRecord(entryName)
                                WovenInfoUtils.deleteReplaceMethodInfo(entryName)
                                try {
                                    val classReader = ClassReader(bytes)
                                    var matchAopMatchCuts: List? = null
                                    classReader.accept(SearchAopMethodVisitor(
                                        object : SearchAopMethodVisitor.OnCallBackMethod {
                                            override fun onBackMatch(aopMatchCuts: List) {
                                                matchAopMatchCuts = aopMatchCuts
                                            }

                                            override fun onBackMethodRecord(methodRecord: MethodRecord) {
                                                val record =
                                                    ClassMethodRecord(entryName, methodRecord)
//                                                    WovenInfoUtils.addClassMethodRecords(record)
                                                addClassMethodRecords[entryName + methodRecord.getKey()] =
                                                    record
                                            }

                                            override fun onDeleteMethodRecord(methodRecord: MethodRecord) {
                                                deleteClassMethodRecords.add(entryName + methodRecord.getKey())
                                            }

                                            override fun onBackReplaceMethodInfo(
                                                replaceMethodInfo: ReplaceMethodInfo
                                            ) {
                                                WovenInfoUtils.addReplaceMethodInfo(
                                                    entryName,
                                                    replaceMethodInfo
                                                )
                                            }

                                            override fun onThrowOverrideMethod(className: String) {
                                                throwOverride(className)
                                            }
                                        }
                                    ), ClassReader.EXPAND_FRAMES)
                                    processOverride(
                                        slashToDot(className),
                                        matchAopMatchCuts,
                                        entryName
                                    ) {
                                        val record = ClassMethodRecord(entryName, it)
                                        addClassMethodRecords[entryName + it.getKey()] = record
                                    }
                                } catch (e: Exception) {
                                    if (e is AndroidAOPOverrideMethodException) {
                                        throw e
                                    }
                                    e.printStackTrace()
                                }
                            }
                        }
                    }
                }
                if (entryName.endsWith(Utils._CLASS)) {

                    WovenInfoUtils.addExtendsReplace(slashToDot(className))

                    val isAopCutClass =
                        WovenInfoUtils.isAopMethodCutClass(className) || WovenInfoUtils.isAopMatchCutClass(
                            className
                        )
                    if (isAopCutClass) {
                        jarFile.getInputStream(jarEntry).use { inputs ->
                            val bytes = inputs.readAllBytes()
                            if (bytes.isNotEmpty()) {
                                val classReader = ClassReader(bytes)
                                classReader.accept(
                                    SuspendReturnScanner(), 0
                                )
                            }
                        }
                    }
                }
            } catch (e: Exception) {
                if (e is AndroidAOPOverrideMethodException) {
                    throw e
                }
                printLog("Merge jar error entry:[${jarEntry.name}], error message:$e")
            }
        }
        jarFile.close()
    }

    private fun throwOverride(className: String) {
        val hintFilePath = Utils.overrideClassFile(project, variantName)
        InitConfig.exportOverrideClassFile(File(hintFilePath), mutableListOf(className))
        throw AndroidAOPOverrideMethodException("重写$className 的相关方法已经改变,需要 clean 后重新编译")
    }


    fun processFileForSearchSuspend(file: File, directory: File, directoryPath: String) {
        if (file.isFile) {
            val entryName = file.getFileClassname(directory)
            if (entryName.isEmpty() || entryName.startsWith("META-INF/") || "module-info.class" == entryName) {
                return
            }
            val realMethodsRecord: HashMap? = WovenInfoUtils.getClassMethodRecord(file.absolutePath)


            if (realMethodsRecord != null){
                FileInputStream(file).use { inputs ->
                    WovenIntoCode.searchSuspendClass(inputs.readAllBytes(),realMethodsRecord)
                }
            }
        }

    }

    fun processJarForSearchSuspend(
        file: File
    ) {
        val jarFile = JarFile(file)
        val enumeration = jarFile.entries()
        while (enumeration.hasMoreElements()) {
            val jarEntry = enumeration.nextElement()
            try {
                val entryName = jarEntry.name
                if (jarEntry.isDirectory || entryName.isEmpty() || entryName.startsWith("META-INF/") || "module-info.class" == entryName) {
                    continue
                }
                val realMethodsRecord: HashMap? = WovenInfoUtils.getClassMethodRecord(entryName)

                if (realMethodsRecord != null){
                    jarFile.getInputStream(jarEntry).use { inputs ->
                        WovenIntoCode.searchSuspendClass(inputs.readAllBytes(),realMethodsRecord)
                    }
                }
            } catch (e: Exception) {
                if (e is AndroidAOPOverrideMethodException) {
                    throw e
                }
                printLog("Merge jar error entry:[${jarEntry.name}], error message:$e")
            }
        }
        jarFile.close()
    }

    fun searchJoinPointLocationEnd(
        addClassMethodRecords: MutableMap,
        deleteClassMethodRecords: MutableSet
    ) {
        for (deleteClassMethodRecordKey in deleteClassMethodRecords) {
            addClassMethodRecords.remove(deleteClassMethodRecordKey)
        }
        for (addClassMethodRecord in addClassMethodRecords) {
            WovenInfoUtils.addClassMethodRecords(addClassMethodRecord.value)
        }
        WovenInfoUtils.removeDeletedClassMethodRecord()
        WovenInfoUtils.verifyModifyExtendsClassInfo()

    }

    fun wovenIntoCodeForReplace(byteArray: ByteArray): WovenResult {
        val cr = ClassReader(byteArray)
        val cw = ClassWriter(cr, 0)
        var thisHasCollect = false
        var thisHasStaticClock = false
        var thisCollectClassName: String? = null
        var replaceResult = false
        val cv = object : MethodReplaceInvokeVisitor(cw) {
            override fun visit(
                version: Int,
                access: Int,
                name: String,
                signature: String?,
                superName: String,
                interfaces: Array?
            ) {
                super.visit(version, access, name, signature, superName, interfaces)
                thisHasCollect = hasCollect
                thisCollectClassName = thisClassName
            }

            override fun visitMethod(
                access: Int,
                name: String,
                descriptor: String,
                signature: String?,
                exceptions: Array?
            ): MethodVisitor? {
                val mv = super.visitMethod(
                    access,
                    name,
                    descriptor,
                    signature,
                    exceptions
                )
                thisHasStaticClock = isHasStaticClock
                return mv
            }

            override fun visitEnd() {
                super.visitEnd()
                replaceResult = replaced
                if (modifyed) {
                    replaceResult = true
                }
            }
        }

        cr.accept(cv, 0)

        thisCollectClassName?.let {
            if (thisHasCollect && !thisHasStaticClock) {
                WovenIntoCode.wovenStaticCode(cw, it)
                replaceResult = true
            }
        }
        return WovenResult(cw.toByteArray(), replaceResult)
    }

    fun wovenIntoCodeForExtendsClass(byteArray: ByteArray): WovenResult {
        val cr = ClassReader(byteArray)
        val cw = ClassWriter(cr, 0)
        var thisHasCollect = false
        var thisHasStaticClock = false
        var thisCollectClassName: String? = null
        var replaceResult = false
        val cv = object : ReplaceBaseClassVisitor(cw) {
            override fun visit(
                version: Int,
                access: Int,
                name: String,
                signature: String?,
                superName: String,
                interfaces: Array?
            ) {
                super.visit(version, access, name, signature, superName, interfaces)
                thisHasCollect = hasCollect
                thisCollectClassName = thisClassName
                replaceResult = modifyExtendsClassName != null
            }

            override fun visitMethod(
                access: Int,
                name: String,
                descriptor: String,
                signature: String?,
                exceptions: Array?
            ): MethodVisitor? {
                val mv = super.visitMethod(
                    access,
                    name,
                    descriptor,
                    signature,
                    exceptions
                )
                thisHasStaticClock = isHasStaticClock
                return mv
            }

            override fun visitEnd() {
                super.visitEnd()
                if (modifyed) {
                    replaceResult = true
                }
            }
        }

        cr.accept(cv, 0)

        thisCollectClassName?.let {
            if (thisHasCollect && !thisHasStaticClock) {
                WovenIntoCode.wovenStaticCode(cw, it)
                replaceResult = true
            }
        }

        return WovenResult(cw.toByteArray(), replaceResult)
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy