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

Go to download

Lightweight Aop for Android platform, you deserve it, action is worse than your heartbeat

There is a newer version: 2.2.8
Show newest version
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.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.ReplaceBaseClassVisitor
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.slashToDot
import javassist.Modifier
import org.gradle.api.Project
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassWriter
import org.objectweb.asm.MethodVisitor
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) {
    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()){
            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 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 - 2024 Weber Informatics LLC | Privacy Policy