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

com.xml.guard.tasks.DeclarationChangeTask.kt Maven / Gradle / Ivy

package com.xml.guard.tasks

import com.xml.guard.data.ResDefType
import com.xml.guard.entensions.GuardExtension
import com.xml.guard.model.MappingParser
import com.xml.guard.utils.allDependencyAndroidProjects
import com.xml.guard.utils.findClassByLayoutXml
import com.xml.guard.utils.findLayoutDirs
import com.xml.guard.utils.findPackage
import com.xml.guard.utils.getClassName
import com.xml.guard.utils.isJava
import com.xml.guard.utils.isKt
import com.xml.guard.utils.isXml
import com.xml.guard.utils.javaDirs
import com.xml.guard.utils.replaceWords
import com.xml.guard.utils.replaceWordsByPattern
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.TaskAction
import java.io.File
import javax.inject.Inject

open class DeclarationChangeTask @Inject constructor(
    private val guardExtension: GuardExtension,
    private val variantName: String,
) : DefaultTask() {

    init {
        group = "guard"
    }

    private val mappingFile by lazy { project.file(MappingParser.MAPPING_DECLARATION) }
    private val mapping by lazy {
        MappingParser.parseDeclaration(
            project,
            guardExtension,
            mappingFile,
            isSelfParse = true
        )
    }

    @Suppress("RegExpUnnecessaryNonCapturingGroup")
    private val resDefNameRegex by lazy {
        val defTypes = ResDefType.entries.joinToString("|") { it.defType }
        val pattern = "R\\.(?:${defTypes})\\.([a-zA-Z0-9_]+)"
        Regex(pattern)
    }

    @TaskAction
    fun execute() {
        val androidProjects = allDependencyAndroidProjects(project)

        // 1、字符串混淆
        val mappingInfoMap = mapping.findAllDeclaration(androidProjects, variantName)

        // 2、替换 Java/kotlin 文件里引用到的声明字符
        if (mappingInfoMap.isNotEmpty()) {
            androidProjects.forEach { project ->
                val fileDirs = mutableListOf()
                fileDirs.addAll(project.javaDirs(variantName))
                fileDirs.addAll(project.findLayoutDirs(variantName))

                val packageName = project.findPackage()
                project.files(fileDirs).asFileTree.forEach asFileTree@{ file ->
                    when {
                        file.isJava || file.isKt -> replaceJavaText(file, mappingInfoMap)
                        file.isXml -> replaceLayoutText(file, packageName, mappingInfoMap)
                    }
                }
            }
        }

        // 3、混淆映射写出到文件
        mapping.writeMappingToFile(mappingFile)
    }

    private fun replaceLayoutText(
        layoutFile: File,
        packageName: String,
        mappingInfoList: Map
    ) {
        var xmlText = layoutFile.readText()
        val classInfos = findClassByLayoutXml(xmlText, packageName)
        val classNames = classInfos.map { it.classPath.getClassName() }

        mappingInfoList.forEach { (oldName, newName) ->
            var oldValue = """
                android:onClick="$oldName"
            """.trimIndent()
            var newValue = """
                android:onClick="$newName"
            """.trimIndent()
            xmlText = xmlText.replaceWords(oldValue, newValue)

            classNames.forEach { clsName ->
                oldValue = """
                    ${clsName}.${oldName}
                """.trimIndent()
                newValue = """
                    ${clsName}.${newName}
                """.trimIndent()
                xmlText = xmlText.replaceWords(oldValue, newValue)
            }
        }
        layoutFile.writeText(xmlText)
    }

    private fun replaceJavaText(
        file: File,
        mappingInfoList: Map
    ) {
        val builder = StringBuilder()
        file.forEachLine { line ->
            if (line.trim().startsWith("package")) {
                builder.append(line).append("\n")
                return@forEachLine
            }
            var newLine = line
            mappingInfoList.forEach { (oldName, newName) ->
                newLine = newLine.replaceWordsByPattern(oldName, newName)

                if (newLine == line) {
                    return@forEach
                }
                extractResStrings(line).forEach extractResStrings@{ (fullName, defName) ->
                    if (defName != oldName) return@extractResStrings
                    // 若资源相关名称被混淆,需还原;例如:R.id.ivIcon 被混淆为 R.id.xsgf
                    val oldValue = fullName.replace(oldName, newName)
                    newLine = newLine.replaceWordsByPattern(oldValue, fullName)
                }
            }
            builder.append(newLine).append("\n")
        }
        if (builder.isNotEmpty()) {
            builder.deleteCharAt(builder.length - 1)
        }
        file.writeText(builder.toString())
    }

    private fun extractResStrings(input: String): Map {
        val map = mutableMapOf()
        val matches = resDefNameRegex.findAll(input)
        matches.forEach { match ->
            val result = match.groupValues.getOrNull(1)
            if (!result.isNullOrBlank()) {
                map[match.value] = result
            }
        }
        return map
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy