<|start_filename|>seeker-plugin/src/main/groovy/com/yeoh/seeker/plugin/utils/GenerateUtils.groovy<|end_filename|>
package com.yeoh.seeker.plugin.utils


import javassist.ClassPool
import javassist.CtClass
import javassist.CtMethod
import javassist.bytecode.AccessFlag

import java.util.regex.Matcher
import java.util.regex.Pattern

/**
 * 修改字节码文件的帮助类
 *
 * @author Yeoh @ Zhihu Inc.
 * @since 2018/10/4
 */
class GenerateUtils {

    static final String GROUP = "GenerateUtils"

    static int getModifier(CtMethod method, String modifier) {
        if (modifier == null) {
            return -1
        }
        switch (modifier.toLowerCase()) {
            case "default":
                return AccessFlag.setPrivate(method.getModifiers())
            case "public":
                return AccessFlag.setPublic(method.getModifiers())
            case "private":
                return AccessFlag.setPrivate(method.getModifiers())
            case "protected":
                return AccessFlag.setProtected(method.getModifiers())
            default:
                return -1
        }
    }

    static void changeModifier(CtMethod method, String modifier) {
        int targetModifier = getModifier(method, modifier)
        if (targetModifier < 0) {
            return
        }
        method.setModifiers(targetModifier)
    }

    /**
     * 通过方法名称获取方法
     * @param ctClass 方法所在的类
     * @param methodName 　方法名称
     * @param descriptor 　参数 descriptor
     * @return 方法
     */
    static CtMethod getMethod(ClassPool pool, CtClass ctClass, String methodName, String descriptor) {
        if (methodName == null || methodName == "<init>") {
            return null
        }
        CtMethod method = ctClass.getMethod(methodName, descriptor)
        if (method == null) {
            // 获取方法参数
            String reg = "(L.+?;)"
            Pattern pattern = Pattern.compile(reg)
            Matcher matcher = pattern.matcher(descriptor)
            List<String> paramsNames = new ArrayList<>()
            while (matcher.find()) {
                String className = matcher.group()
                className = className.substring(1, className.length() - 1)
                paramsNames.add(className)
            }
            if (!paramsNames.isEmpty()) {
                CtClass[] params = new CtClass[paramsNames.size()]
                for (int i = 0; i < paramsNames.size(); i++) {
                    params[i] = pool.getCtClass(paramsNames[i])
                }
                method = ctClass.getDeclaredMethod(methodName, params)
            }
        }
        return method
    }

    /**
     * 两个类是否相同
     * @param class0 类1
     * @param class1 类2
     * @return 是否相等
     */
    static boolean isClassEqual(String class0, String class1) {
        if (class0 == null || class1 == null) {
            return false
        }
        if (class0 == class1) {
            return true
        }
        if (class0 == class1.replace("\$", ".")) {
            return true
        }
        if (class1 == class0.replace("\$", ".")) {
            return true
        }
        if (class0.replace("\$", ".") == class1.replace("\$", ".")) {
            return true
        }
        return false
    }

    /**
     * 判断两个方法是否相等
     * @param methodName 方法名称
     * @param descriptor 方法 descriptor
     * @param hideMethod HideMethod 实体
     * @return 是否相等
     */
    static boolean equal(String methodName, String descriptor, def hideMethod) {
        if (methodName == null || methodName == "<init>") {
            return false
        }
        if (methodName == null || hideMethod == null) {
            return false
        }
        // 获取参数
        String reg = "(L.+?;)".intern()
        Pattern pattern = Pattern.compile(reg)
        Matcher matcher = pattern.matcher(descriptor)
        List<String> paramsClasses = new ArrayList<>()
        while (matcher.find()) {
            String className = matcher.group()
            className = className.substring(1, className.length() - 1)
            className.replace("/", ".")
            paramsClasses.add(className)
        }
        if (paramsClasses.isEmpty() && hideMethod.params == null) {
            return true
        } else if (hideMethod.params == null) {
            return false
        } else if (!paramsClasses.isEmpty()) {
            return false
        }
        if (paramsClasses.size() != hideMethod.params.size()) {
            return false
        }
        for (int i = 0; i < paramsClasses.size(); i++) {
            if (paramsClasses.get(i) != hideMethod.params[i]) {
                return false
            }
        }
        return true
    }
}

<|start_filename|>seeker-plugin/src/main/groovy/com/yeoh/seeker/plugin/processor/java/JavaReferencedClassParser.groovy<|end_filename|>
package com.yeoh.seeker.plugin.processor.java

import com.github.javaparser.ast.CompilationUnit
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration
import com.github.javaparser.ast.body.MethodDeclaration
import com.github.javaparser.ast.expr.MethodReferenceExpr
import com.github.javaparser.ast.visitor.VoidVisitorAdapter
import com.yeoh.seeker.plugin.DataSource
import com.yeoh.seeker.plugin.utils.Log

import java.util.function.Consumer

/**
 * hook java 方法的类
 *
 * @author yangjing @ Zhihu Inc.
 * @since 2018-11-19
 */
class JavaReferencedClassParser extends JavaMethodModifierParser {

    private Set<String> mImportHostClasses = []

    JavaReferencedClassParser(CompilationUnit compilationUnit, File javaPath) {
        super(compilationUnit, javaPath)
    }

    @Override
    void hook() {
        Log.i(LEVEL, GROUP, "parse java file ${mJavaPath}")

//        parseImports()
        mCompilationUnit.findAll(MethodDeclaration.class).stream()
                .forEach(new Consumer<MethodDeclaration>() {
            @Override
            void accept(MethodDeclaration methodDeclaration) {
                hookMethodBody(methodDeclaration)
            }
        })

        VoidVisitorAdapter<Object> adapter = new VoidVisitorAdapter<Object>() {
//            @Override
//            void visit(MethodReferenceExpr n, Object arg) {
//                Log.i(LEVEL + 1, GROUP, "refer ${n.name.identifier}")
//            }
//
//            @Override
//            void visit(MethodDeclaration n, Object arg) {
//                Log.i(LEVEL + 1, GROUP, "meth ${n.name.identifier}")
//            }
//
//            @Override
//            void visit(ClassOrInterfaceDeclaration n, Object arg) {
//                Log.i(LEVEL + 1, GROUP, "class ${n.name.identifier}")
//            }

            @Override
            void visit(MethodReferenceExpr n, Object arg) {
                Log.i(LEVEL + 1, GROUP, "class ${n.name.identifier}")
            }
        }
        adapter.visit(mCompilationUnit, null)


        writeToPath()
        Log.ln(LEVEL, GROUP)
    }

    /**
     * 遍历 import 的类，在遍历方法的时候再使用
     */
    private void parseImports() {
        def imports = mCompilationUnit.imports
        if (imports != null) {
            imports.each { importItem ->
                DataSource.seekerConfig.forEach { className, hideMethods ->
                    if (importItem.nameAsString == className) {
                        mImportHostClasses.add(className)
                    }
                }
            }
        }
        Log.i(LEVEL + 1, GROUP, "find host imports: ${mImportHostClasses}")
    }

    /**
     * hook 方法体
     * @param methodDeclaration
     */
    private void hookMethodBody(MethodDeclaration methodDeclaration) {
        VoidVisitorAdapter<Object> adapter = new VoidVisitorAdapter<Object>() {
//            @Override
//            void visit(MethodReferenceExpr n, Object arg) {
//                Log.i(LEVEL + 1, GROUP, "refer ${n.name.identifier}")
//            }
//
            @Override
            void visit(MethodDeclaration n, Object arg) {
                Log.i(LEVEL + 1, GROUP, "meth ${n.name.identifier}")
            }
//
            @Override
            void visit(ClassOrInterfaceDeclaration n, Object arg) {
                Log.i(LEVEL + 1, GROUP, "class ${n.name.identifier}")
            }
        }
        methodDeclaration.accept(adapter,null)
//        adapter.visit(methodDeclaration, null)

//        methodDeclaration.stream().each { lineCode ->
//            processHideHostTarget(lineCode)
//        }
    }

    /**
     * 是否含有持有 @Hide 注解方法的类
     * @param methodDeclaration
     * @return 是否含有
     */
    private boolean processHideHostTarget(String lineCode) {
        def nodes = lineCode.split(" ")
        DataSource.seekerConfig.forEach { className, hideMethods ->
            if (nodes.contains(className)) {
                Log.i(LEVEL + 1, GROUP, "find host target in line ${lineCode}")
            }
        }
    }
}

<|start_filename|>seeker-processor/src/main/java/com/yeoh/seeker/processor/HideRefDelegateGenerator.java<|end_filename|>
package com.yeoh.seeker.processor;

import com.squareup.javapoet.ArrayTypeName;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.MethodSpec.Builder;
import com.squareup.javapoet.TypeSpec;
import com.yeoh.seeker.HideMethod;
import com.yeoh.seeker.HideRefDelegate;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import javax.annotation.processing.Filer;
import javax.lang.model.element.Modifier;

/**
 * Used to generate HideRecBarrier class code .
 *
 * @author yangjing .
 * @since 2018-09-21
 */
class HideRefDelegateGenerator {

    private static final String PACKAGE = "com.yeoh.seeker";
    private static final String SUFFIX = "RefDelegate";
    private static final String PREFIX = "_";
    private static final String NAME_ARG = "arg";
    private static final String HIDE_REF_DELEGATE = HideRefDelegate.class.getSimpleName();
    private Map<String, List<HideMethod>> mHideMethodMap;
    private Filer mFiler;

    HideRefDelegateGenerator(Filer filer, Map<String, List<HideMethod>> hideMethodMap) {
        mFiler = filer;
        mHideMethodMap = hideMethodMap;
    }

    void generate() throws IOException {
        if (mHideMethodMap == null || mHideMethodMap.isEmpty()) {
            Log.w("hideMethodMap is empty...");
            return;
        }
        Log.title("========== Generate HideRefDelegate start ==========");
        for (String className: mHideMethodMap.keySet()) {
            generateHideRefBarrier(className, mHideMethodMap.get(className));
        }
        Log.title("========== Generate HideRefDelegate end ==========");
    }

    private void generateHideRefBarrier(String classFullName, List<HideMethod> hideMethods) throws IOException {
        String[] splitStr = classFullName.split("\\.");
        String className = splitStr[splitStr.length - 1];
        String packageName = classFullName.substring(0, classFullName.length() - className.length() - 1);

        Log.second("--------- generate class " + generateRefBarrierClassName(className) + " start ...");

        TypeSpec.Builder moduleBuilder = TypeSpec.classBuilder(generateRefBarrierClassName(className))
                .addModifiers(Modifier.PUBLIC, Modifier.FINAL);
        moduleBuilder.superclass(ClassName.bestGuess(PACKAGE + "." + HIDE_REF_DELEGATE));
        // 添加构造方法
        Builder constructorBuilder = MethodSpec.constructorBuilder()
                .addModifiers(Modifier.PUBLIC);
        appendMethodParams(constructorBuilder, classFullName);
        constructorBuilder.addStatement("super(" + NAME_ARG + "0)");
        moduleBuilder.addMethod(constructorBuilder.build());
        // 添加方法体
        for (HideMethod hideMethod: hideMethods) {
            Log.second("----- start to generate method");
            Log.i(hideMethod.toString());

            Builder methodBuilder = MethodSpec.methodBuilder(hideMethod.methodName)
                    .addModifiers(Modifier.PUBLIC, Modifier.FINAL);

            appendMethodParams(methodBuilder, hideMethod.params);
            appendMethodReturns(methodBuilder, hideMethod.returns);

            String methodArgs = buildArgs(hideMethod.params);
            if (methodArgs == null) {
                if (GeneratorUtils.isVoid(hideMethod.returns)) {
                    methodBuilder.addStatement("invokeMethod("
                            + buildInvokeMethod(hideMethod) + ")", HideMethod.class);
                } else {
                    methodBuilder.addStatement("return" + " (" + hideMethod.returns + ") "
                                    + "invokeMethod(" + buildInvokeMethod(hideMethod) + ")",
                            HideMethod.class);
                }
            } else {
                if (GeneratorUtils.isVoid(hideMethod.returns)) {
                    // 如果只有一个参数并且该参数为可变长度，则需要特殊处理
                    if (hideMethod.params.length > 1 || !hideMethod.params[0].endsWith("[]")) {
                        methodBuilder.addStatement("invokeMethod("
                                + buildInvokeMethod(hideMethod) + ", $N)", HideMethod.class, methodArgs);
                    } else {
                        methodBuilder.addStatement("invokeMethodForVarArgs("
                                + buildInvokeMethod(hideMethod) + ", $N)", HideMethod.class, methodArgs);
                    }
                } else {
                    if (hideMethod.params.length > 1 || !hideMethod.params[0].endsWith("[]")) {
                        methodBuilder.addStatement("return" + " (" + hideMethod.returns + ") "
                                        + "invokeMethod(" + buildInvokeMethod(hideMethod) + ", $N)",
                                HideMethod.class, methodArgs);
                    } else {
                        methodBuilder.addStatement("return" + " (" + hideMethod.returns + ") "
                                        + "invokeMethodForVarArgs(" + buildInvokeMethod(hideMethod) + ", $N)",
                                HideMethod.class, methodArgs);
                    }
                }
            }
            moduleBuilder.addMethod(methodBuilder.build());
        }

        JavaFile moduleFile = JavaFile.builder(packageName, moduleBuilder.build())
                .build();
        moduleFile.writeTo(mFiler);
        Log.second("--------- generate class " + className + SUFFIX + " end ...");
    }

    private String buildInvokeMethod(HideMethod hideMethod) {
        return "reflectMethod(" + hideMethod.generateCodeWithJavaPoet() + ")";
    }

    private String buildArgs(String[] args) {
        if (args == null || args.length == 0) {
            return null;
        }
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < args.length; i++) {
            builder.append(NAME_ARG)
                    .append(i);
            if (i != args.length - 1) {
                builder.append(", ");
            }
        }
        return builder.toString();
    }

    /**
     * 给方法添加参数
     *
     * @param methodBuilder 生成方法的 Builder
     * @param params        方法的的参数
     */
    private void appendMethodParams(Builder methodBuilder, String... params) {
        Log.second("----- append method params start ...");
        if (params == null || params.length == 0) {
            return;
        }
        for (int i = 0; i < params.length; i++) {
            String arg = params[i];
            String argName = NAME_ARG + i;
            String arrayClassName = GeneratorUtils.getArrayClassName(arg);
            if (arrayClassName != null) {
                try {
                    methodBuilder.addParameter(ArrayTypeName.of(Class.forName(arrayClassName)), argName);
                } catch (ClassNotFoundException e) {
                    methodBuilder.addParameter(ArrayTypeName.of(ClassName.bestGuess(arrayClassName)), argName);
                }
            } else {
                try {
                    Class argClass = GeneratorUtils.getPrimitiveTypeClass(arg);
                    if (argClass == null) {
                        methodBuilder.addParameter(Class.forName(arg), argName);
                    } else {
                        methodBuilder.addParameter(argClass, argName);
                    }
                } catch (ClassNotFoundException e) {
                    methodBuilder.addParameter(ClassName.bestGuess(arg), argName);
                }
            }
        }
        Log.second("----- append method params end ...");
    }

    /**
     * 给方法添加返回值
     *
     * @param methodBuilder 方法 Builder
     * @param returns       返回值类型的字符串
     */
    private void appendMethodReturns(Builder methodBuilder, String returns) {
        Log.second("----- append method returns start ...");
        if (returns == null || returns.length() == 0) {
            return;
        }
        if (GeneratorUtils.isVoid(returns)) {
            return;
        }
        String arrayClassName = GeneratorUtils.getArrayClassName(returns);
        if (arrayClassName != null) {
            try {
                methodBuilder.returns(ArrayTypeName.of(Class.forName(arrayClassName)));
            } catch (ClassNotFoundException e) {
                methodBuilder.returns(ArrayTypeName.of(ClassName.bestGuess(arrayClassName)));
            }
        } else {
            Class returnClass = GeneratorUtils.getPrimitiveTypeClass(returns);
            if (returnClass == null) {
                try {
                    methodBuilder.returns(Class.forName(returns));
                } catch (ClassNotFoundException e) {
                    methodBuilder.returns(ClassName.bestGuess(returns));
                }
            } else {
                methodBuilder.returns(returnClass);
            }
        }
        Log.second("----- append method returns end ...");
    }

    /**
     * 生成反射代理类的类名
     */
    private String generateRefBarrierClassName(String className) {
        return PREFIX + className + SUFFIX;
    }
}


<|start_filename|>seeker-plugin/src/main/groovy/com/yeoh/seeker/plugin/VariantProcessor.groovy<|end_filename|>
package com.yeoh.seeker.plugin

import com.yeoh.seeker.plugin.utils.AarUtils
import com.yeoh.seeker.plugin.utils.JarUtils
import com.yeoh.seeker.plugin.utils.Log
import javassist.ClassPool
import org.gradle.api.Project
import org.gradle.api.Task

/**
 * 具体的处理类，用于处理 Seeker 中各个 plugin 的执行
 *
 * @author Yeoh @ Zhihu Inc.
 * @since 2018/11/13
 */
class VariantProcessor {

    private static final int LEVEL = 2
    private static final String GROUP = "VariantProcessor"
    private final Project mProject
    private final ClassPool mClassPool
    private final def mVariant

    VariantProcessor(Project project, ClassPool classPool, variant) {
        mProject = project
        mClassPool = classPool
        mVariant = variant
    }

    /**
     * 处理 variant
     */
    void process() {
//        String taskPath = 'prepare' + mVariant.name.capitalize() + 'Dependencies'
//        Task prepareTask = mProject.tasks.findByPath(taskPath)
//        if (prepareTask == null) {
//            throw new RuntimeException("Can not find task ${taskPath}!")
//        }
        Log.i(LEVEL, GROUP, "----------- VariantProcessor ${mVariant.name} -----------")
        processClassesAndJars()
    }

    /**
     * 处理类和 jar 文件
     */
    private void processClassesAndJars() {
        // 如果使用混淆， bundleRelease 必须在混淆之前执行
        if (mVariant.getBuildType().isMinifyEnabled()) {
            Task javacTask = mVariant.getJavaCompile()
            if (javacTask == null) {
                // warn: can not find javaCompile task, jack compile might be on.
                return
            }
            javacTask.doLast {
                File dustDir = mProject.file(mProject.buildDir.path + '/intermediates/classes/' + mVariant.dirName)
                Log.i(LEVEL + 1, GROUP, "minify enable")
                Log.i(LEVEL + 1, GROUP, "dustDir = " + dustDir)
            }
        } else {
            String taskPath = 'bundle' + mVariant.name.capitalize()
            Task syncLibTask = mProject.tasks.findByPath(taskPath)
            if (syncLibTask == null) {
                throw new RuntimeException("Can not find task ${taskPath}!")
            }
            syncLibTask.doFirst {
                // 初始化 seeker 的配置
                DataSource.configureSeeker()
                configureClassPool()
                File dustDir = mProject.file(mProject.buildDir.path + '/intermediates/packaged-classes/' + mVariant.dirName)
                Log.i(LEVEL + 1, GROUP, "outputDir = " + dustDir)
                processJars(dustDir)
            }
        }
    }

    /**
     * 配置 ClassPool，注入依赖的库到 ClassPool 中
     */
    private void configureClassPool() {
        mClassPool.clearImportedPackages()
        //　解析依赖的第三方库
        DataSource.DEPENDENCIES_PATH.forEach({
            File file = new File(it)
            if (!file.exists()) {
                Log.i(LEVEL + 1, GROUP, "can not find file " + it)
                return
            }
            if (file.path.endsWith("aar")) {
                String extractPath = AarUtils.getExtractAarPath(file)
                DataSource.TEMP_DIRS.add(extractPath)

                File unAar = new File(extractPath)
                AarUtils.Aar aar = AarUtils.unAar(file, unAar)
                mClassPool.appendClassPath(aar.extractJarPath)
                DataSource.DEPENDENCIES_JARS_PATH.add(aar.extractJarPath)

                Log.i(LEVEL + 1, GROUP, "find class jar in aar: " + aar.jarPath)
            }
            if (file.path.endsWith("jar")) {
                String extractJarPath = JarUtils.getExtractJarPath(file)
                DataSource.TEMP_DIRS.add(extractJarPath)
                DataSource.DEPENDENCIES_JARS_PATH.add(extractJarPath)

                JarUtils.unJar(file, new File(extractJarPath))
                mClassPool.appendClassPath(extractJarPath)

                Log.i(LEVEL + 1, GROUP, "find class jar : " + file)
            }
        })
    }

    /**
     * 处理 jar 文件
     */
    private void processJars(File jarsDir) {
        mClassPool.clearImportedPackages()
        mClassPool.appendClassPath(mProject.android.bootClasspath[0].toString())
        if (jarsDir == null) {
            Log.i(LEVEL, GROUP, "${jarsDir} is not exist")
            return
        }
        for (file in jarsDir.listFiles()) {
            if (file.path.endsWith(".jar")) {
                Log.i(LEVEL + 1, GROUP, "find jar, path = " + file.path)
                mClassPool.appendClassPath(file.absolutePath)
            }
        }
        // inject jar ，重新注入代码
        JarInject jarInject = new JarInject(mClassPool)
        for (file in jarsDir.listFiles()) {
            if (file.path.endsWith(".jar")) {
                jarInject.appendJarPath(file.path)
            }
        }
        jarInject.inject()
    }
}

<|start_filename|>mock-lib-sub/src/main/java/com/yeoh/mock/sub/SubCaller.java<|end_filename|>
package com.yeoh.mock.sub;

import android.app.Activity;

public class SubCaller extends Activity {

    public static void mock() {
        SubMockModel mockModel = new SubMockModel();
        mockModel.subMockTestMethod();
        mockModel.subMockTestMethod(1);
        mockModel.subMockTestMethod("hello seeker");
        mockModel.subMockTestMethod(1,"hello",null);
    }

    private void test(String arg0) {
    }
}


<|start_filename|>seeker-plugin/src/main/groovy/com/yeoh/seeker/plugin/SeekerPlugin.groovy<|end_filename|>
package com.yeoh.seeker.plugin

import com.android.build.gradle.AppPlugin
import com.android.build.gradle.LibraryPlugin
import com.yeoh.seeker.plugin.extension.SeekerExtension
import com.yeoh.seeker.plugin.utils.Log
import javassist.ClassPool
import javassist.NotFoundException
import org.gradle.api.GradleException
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.artifacts.Configuration

/**
 * Seeker 插件
 *
 * @author Yeoh @ Zhihu Inc.
 * @since 2018/8/9
 */
class SeekerPlugin implements Plugin<Project> {

    private static final int LEVEL = 1
    private static final String GROUP = "SeekerPlugin"

    private Project mProject
    private SeekerExtension mSeekerExtension
    private static ClassPool mPool

    // copy 引入第三方库的依赖，直接使用原声的 api/implementation 等会抛出异常
    // Resolving configuration 'implementation' directly is not allowed
    private List<Configuration> mCopyDependencies

    @Override
    void apply(Project project) {
        mProject = project
        mPool = ClassPool.getDefault()

        checkAndroidPlugin()
        configureExtension()
        configureDependencies()

        mProject.afterEvaluate {
            Log.i(LEVEL, GROUP, "-------------- SEEKER PLUGIN --------------")
            readExtension()
            resolveArtifacts()
            doAction()
        }

        // 上传完毕后删除临时目录
        Task upload = mProject.tasks.findByName("uploadArchives")
        if (upload != null) {
            upload.doLast {
                DataSource.TEMP_DIRS.forEach({
                    "rm -rf ${it}".execute()
                })
            }
        }
    }

    /**
     *  Make sure the project is either an Android application or library
     */
    private void checkAndroidPlugin() {
        def isAndroidApp = mProject.plugins.withType(AppPlugin)
        def isAndroidLib = mProject.plugins.withType(LibraryPlugin)
        if (!isAndroidApp && !isAndroidLib) {
            throw new GradleException("'com.android.application' or 'com.android.library' plugin required.")
        }
    }

    /**
     * 配置 Extension
     */
    private void configureExtension() {
        mSeekerExtension = new SeekerExtension()
        mProject.extensions.create(SeekerExtension.NAME, SeekerExtension)
    }

    /**
     * 添加至依赖的集合中
     */
    private void copyDependencies(Configuration configuration) {
        if (configuration == null) {
            return
        }
        Configuration copyConf = null
        try {
            copyConf = mProject.configurations.getByName("${configuration.name}Copy")
        } catch (Exception ignore) {
        }
        if (copyConf == null) {
            copyConf = mProject.configurations.create("${configuration.name}Copy")
        }
        copyConf.visible = false
        copyConf.extendsFrom configuration
        mCopyDependencies.add(copyConf)
    }

    /**
     * 配置依赖的第三方库
     */
    private void configureDependencies() {
        mCopyDependencies = new ArrayList<>()
        copyDependencies(mProject.configurations.getByName("implementation"))
        copyDependencies(mProject.configurations.getByName("api"))
        copyDependencies(mProject.configurations.getByName("compile"))
        copyDependencies(mProject.configurations.getByName("compileOnly"))
        copyDependencies(mProject.configurations.getByName("provided"))
    }

    /**
     * 读取 Extension 配置
     */
    private void readExtension() {
        mSeekerExtension.copy(mProject[SeekerExtension.NAME])
        Log.i(LEVEL + 1, GROUP, mSeekerExtension.toString())
        Log.Debug = mSeekerExtension.debugEnable
    }

    /**
     * 处理引用的 aar 和 jar　包
     */
    private void resolveArtifacts() {
        def set = new HashSet<>()
        mCopyDependencies.forEach({
            it.each {
                Log.i(LEVEL + 1, GROUP, it.path)
                set.add(it.path)
            }
        })
        DataSource.DEPENDENCIES_PATH = Collections.unmodifiableSet(set)
    }

    /**
     * 开始执行
     */
    private void doAction() {
        if (!mSeekerExtension.enable) {
            Log.i(LEVEL + 1, GROUP, "seeker plugin is not enabled!")
            return
        }
        // 处理 java sources 源码
//        processSources()
        // 处理字节码
        processVariant()
    }

    /**
     * 执行处理 source 源码
     */
    private void processSources() {
        if (!mSeekerExtension.trickIDEA) {
            return
        }
        SourceProcessor processor = new SourceProcessor(mProject, mSeekerExtension.sourcesJarTask)
        processor.process()
    }

    /**
     * 开始执行处理过程
     */
    private void processVariant() throws NotFoundException {
        if (!mSeekerExtension.hookClass) {
            return
        }
        // 一般有 debug 和 release 两种 variant
        try {
            mProject.android.libraryVariants.all { variant ->
                // 处理 task
                def processor = new VariantProcessor(mProject, mPool, variant)
                processor.process()
            }
        } catch (NotFoundException e) {
            Log.i(LEVEL + 1, GROUP, e.message)
        }
    }
}

<|start_filename|>mock-lib-sub/src/main/java/com/yeoh/mock/sub/SubMockModel.java<|end_filename|>
package com.yeoh.mock.sub;

import android.content.Context;
import com.yeoh.seeker.annotation.Hide;
import com.yeoh.seeker.annotation.Modifier;

public class SubMockModel {

    @Hide
    public void subMockTestMethod() {
    }

    @Hide
    public void subMockTestMethod(String name) {
    }

    @Hide(Modifier.DEFAULT)
    public void subMockTestMethod(Context context) {
    }

    @Hide(Modifier.PUBLIC)
    public void subMockTestMethod(int num) {
    }

    @Hide(Modifier.PROTECTED)
    void subMockTestMethod(int num, String name, Context context) {
    }
}


<|start_filename|>seeker-processor/src/main/java/com/yeoh/seeker/processor/SeekerDelegateGenerator.java<|end_filename|>
package com.yeoh.seeker.processor;

import android.support.annotation.NonNull;

import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeSpec;
import com.yeoh.seeker.HideMethod;
import com.yeoh.seeker.HideSeekerDelegate;
import com.yeoh.seeker.annotation.Hide;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.annotation.processing.Filer;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeMirror;

/**
 * Used to generate SeekerDelegate class code .
 *
 * @author yangjing .
 * @since 2018-08-21
 */
class SeekerDelegateGenerator {

    private static final String PACKAGE = "com.yeoh.seeker";
    private static final String SEEKER_DELEGATE_IMPL = "HideSeekerDelegateImpl";
    private static final String SEEKER_DELEGATE = HideSeekerDelegate.class.getSimpleName();
    private static final String HIDE_METHOD = HideMethod.class.getSimpleName();

    private String mSubModuleNames[];
    private String mModuleName;
    private RoundEnvironment mRoundEnvironment;
    private Filer mFiler;
    private Map<String, List<HideMethod>> mHideMethodMap;

    SeekerDelegateGenerator(String subModules, String moduleName, Filer filer,
                            RoundEnvironment roundEnvironment) {
        mModuleName = moduleName;
        mRoundEnvironment = roundEnvironment;
        mFiler = filer;
        if (subModules != null && subModules.length() > 0) {
            mSubModuleNames = subModules.split(",");
        }
        mHideMethodMap = new HashMap<>();
    }

    boolean generate() throws IOException {
        if (mSubModuleNames == null && mModuleName == null) {
            Log.w("subModuleName and moduleName is null , don't process this module");
            return false;
        }
        HideMethodCacher.readFromCache();
        generateModuleClass();
        HideMethodCacher.putAll(mHideMethodMap);
        new HideRefDelegateGenerator(mFiler, mHideMethodMap).generate();
//        new SeekerDelegateJsonGenerator(mFiler, HideMethodCacher.getAll()).generate();
        HideMethodCacher.cache();
        return true;
    }

    // generate module class
    private void generateModuleClass() throws IOException {
        Log.title("========== Generate seeker delegate start ==========");
        TypeSpec.Builder moduleBuilder = TypeSpec.classBuilder(getModuleClassName(mModuleName))
                .addModifiers(Modifier.PUBLIC, Modifier.FINAL);
        moduleBuilder.superclass(ClassName.bestGuess(PACKAGE + "." + SEEKER_DELEGATE));
        MethodSpec.Builder moduleConstructor = MethodSpec.constructorBuilder();
        moduleConstructor.addStatement("super()");

        for (Element it : mRoundEnvironment.getElementsAnnotatedWith(Hide.class)) {
            if (it instanceof ExecutableElement) {
                appendMethodElement((ExecutableElement) it, moduleConstructor);
            }
        }
        if (mSubModuleNames != null && mSubModuleNames.length > 0) {
            for (String name : mSubModuleNames) {
                moduleConstructor.addStatement("mHideMethods.putAll(new "
                        + getModuleClassName(name)
                        + "().getHideMethods()"
                        + ")"
                );
            }
        }
        moduleBuilder.addMethod(moduleConstructor.build());
        // 不生成 java 文件
//        JavaFile moduleFile = JavaFile.builder(PACKAGE, moduleBuilder.build())
//                .build();
//        moduleFile.writeTo(mFiler);
//        Log.title("========== Generate seeker delegate end ==========");
    }

    private void appendMethodElement(ExecutableElement element, MethodSpec.Builder moduleConstructor) {
        TypeElement typeElement = (TypeElement) element.getEnclosingElement();

        String className = typeElement.getQualifiedName().toString();
        String methodName = element.getSimpleName().toString();
        String returnName = element.getReturnType().toString();
        Hide hide = element.getAnnotation(Hide.class);
        List<String> params = new ArrayList<>();

        Log.second("----- addHideMethod start...");
        Log.i("className = " + className);
        Log.i("methodName = " + methodName);
        Log.i("returnName = " + returnName);

        for (VariableElement it : element.getParameters()) {
            TypeMirror methodParameterType = it.asType();
            String paramClassName = methodParameterType.toString();
            params.add(paramClassName);
        }
        String hideMethodParams = buildHideMethodParams(params);
        moduleConstructor.addStatement("addHideMethod($S,new " + HIDE_METHOD + "($S,$S,$S,$S))", className,
                methodName, returnName, hide.value().toString(), hideMethodParams);
        Log.second("----- addHideMethod done...");
        putHideMethod(className, methodName, returnName, hide.value().toString(), hideMethodParams);
    }

    private String buildHideMethodParams(@NonNull List<String> params) {
        if (params.isEmpty()) {
            return null;
        }
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < params.size(); i++) {
            if (i != 0) {
                builder.append(",");
            }
            builder.append(params.get(i));
        }
        Log.i("params = " + builder.toString());
        return builder.toString();
    }

    private String getModuleClassName(String name) {
        if (name == null) {
            return SEEKER_DELEGATE_IMPL;
        }
        return SEEKER_DELEGATE_IMPL + Math.abs(name.trim().hashCode());
    }

    private void putHideMethod(String className, String methodName, String returnName, String modifier, String params) {
        List<HideMethod> hideMethods = mHideMethodMap.get(className);
        if (hideMethods == null) {
            hideMethods = new ArrayList<>();
        }
        HideMethod hideMethod = new HideMethod(methodName, returnName, modifier, params);
        hideMethods.add(hideMethod);
        mHideMethodMap.put(className, hideMethods);
    }
}


<|start_filename|>seeker/src/main/java/com/yeoh/seeker/HideMethod.java<|end_filename|>
package com.yeoh.seeker;

import com.yeoh.seeker.annotation.Modifier;
import java.util.Arrays;

/**
 * Used to save method info
 *
 * @author yangjing @ Zhihu Inc.
 * @since 2018-08-21
 */
public class HideMethod {

    public final String methodName;
    public final String[] params;
    private final String modifier;
    public final String returns;

    public HideMethod(String methodName, String returnValue, String modifier, String params) {
        this.methodName = methodName;
        this.modifier = modifier;
        this.returns = returnValue;
        if (params != null && params.length() > 0) {
            this.params = params.split(",");
        } else {
            this.params = null;
        }
    }

    public Modifier getModifier() {
        if (modifier.equals(Modifier.DEFAULT.toString())) {
            return Modifier.DEFAULT;
        } else if (modifier.equals(Modifier.PRIVATE.toString())) {
            return Modifier.PRIVATE;
        } else if (modifier.equals(Modifier.PUBLIC.toString())) {
            return Modifier.PUBLIC;
        } else if (modifier.equals(Modifier.PROTECTED.toString())) {
            return Modifier.PROTECTED;
        }
        return Modifier.DEFAULT;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (!(obj instanceof HideMethod)) {
            return false;
        }
        HideMethod target = (HideMethod) obj;
        if (target.methodName == null || !methodName.equals(target.methodName)) {
            return false;
        }
        if (params != null) {
            if (target.params == null) {
                return false;
            }
            if (params.length != target.params.length) {
                return false;
            }
            for (int i = 0; i < params.length; i++) {
                if (!params[i].equals(target.params[i])) {
                    return false;
                }
            }
        }
        return true;
    }

    @Override
    public String toString() {
        return "methodName = " + methodName + "\n"
                + "returns = " + returns + "\n"
                + "modifier = " + modifier + "\n"
                + "params = " + Arrays.toString(params);
    }

    public String generateCode() {
        return generateCodeStr(HideMethod.class.getSimpleName());
    }

    public String generateCodeStr(String name) {
        StringBuilder builder = new StringBuilder();
        builder.append("new ")
                .append(name)
                .append("(")
                .append("\"")
                .append(methodName)
                .append("\"")
                .append(",")
                .append("\"")
                .append(returns)
                .append("\"")
                .append(",")
                .append("\"")
                .append(modifier)
                .append("\"")
                .append(",");
        if (params != null && params.length > 0) {
            builder.append("\"");
            for (int i = 0; i < params.length; i++) {
                String arg = params[i];
                if (i != 0) {
                    builder.append(",");
                }
                builder.append(arg);
            }
            builder.append("\"");
        } else {
            builder.append("null");
        }
        builder.append(")");
        return builder.toString();
    }

    public String generateCodeWithJavaPoet() {
        return generateCodeStr("$T");
    }
}


<|start_filename|>seeker-plugin/src/main/groovy/com/yeoh/seeker/plugin/JarInject.groovy<|end_filename|>
package com.yeoh.seeker.plugin


import com.yeoh.seeker.plugin.processor.bytecode.MethodModifierProcessor
import com.yeoh.seeker.plugin.processor.bytecode.ReferencedClassProcessor
import com.yeoh.seeker.plugin.utils.JarUtils
import com.yeoh.seeker.plugin.utils.Log
import javassist.ClassPool
import org.apache.commons.io.FileUtils

/**
 * jar 包的注入类
 *
 * 过程
 * * 1. 解析 jar 包
 * * 2. 比对配置
 * * 3. 调用代码注入器
 * * 4. 重新打包
 *
 * @author Yeoh @ Zhihu Inc.
 * @since 2018/11/13
 */
class JarInject {

    private static final int LOG_LEVEL = 2
    private static final String GROUP = "JarInject"
    private boolean haveTarget = false

    private final ClassPool mClassPool
    private List<String> mJarPaths

    JarInject(ClassPool pool) {
        mClassPool = pool
        mJarPaths = new ArrayList<>()
    }

    /**
     * 添加 Jar 的路径
     * @param path jar 路径
     */
    void appendJarPath(String path) {
        if (!mJarPaths.contains(path)) {
            mJarPaths.add(path)
        }
    }

    /**
     * 开始注入
     */
    void inject() throws Exception {
        MethodModifierProcessor.setClassPool(mClassPool)
        ReferencedClassProcessor.setClassPool(mClassPool)
        mJarPaths.forEach({
            injectJar(it)
        })
    }

    /**
     * 修改 jar 包
     * @param path jar 的路径
     * @throws Exception 可能会抛出异常
     */
    private void injectJar(path) throws Exception {
        Log.i(LOG_LEVEL, GROUP, "inject jar " + path)

        File jarFile = new File(path)
        String jarZipDir = JarUtils.getExtractJarPath(jarFile)
        File unJar = new File(jarZipDir)
        List classNameList = JarUtils.unJar(jarFile, unJar)

        try {
            boolean haveTarget = traverseClassList(classNameList, jarZipDir)
            if (haveTarget) {
                Log.i(LOG_LEVEL + 1, GROUP, "found jar target :" + jarZipDir)
                jarFile.delete()
                JarUtils.jar(jarFile, unJar)
            } else {
                Log.i(LOG_LEVEL + 1, GROUP, "not found target class")
            }
        } catch (Exception e) {
            throw e
        } finally {
            FileUtils.deleteDirectory(new File(jarZipDir))
        }

        Log.ln(LOG_LEVEL, GROUP)
    }

    /**
     * 对比配置文件，调用代码注入器
     * @param classNameList jar 包中的类集合
     * @param jarZipDir jar zip 的目录
     * @return 该 jar 包中是否注入代码
     */
    private boolean traverseClassList(List classNameList, String jarZipDir) {
        haveTarget = false
        for (String className : classNameList) {
            if (className.endsWith(".class")
                    && !className.contains('R$')
                    && !className.contains('R.class')
                    && !className.contains("BuildConfig.class")) {
                className = className.substring(0, className.length() - 6)

                DataSource.seekerConfig.keySet().forEach({
                    // 寻找是否含有 @Hide 注解的类
                    if (MethodModifierProcessor.process(className, it, jarZipDir)) {
                        haveTarget = true
                    } else {
                        // 含有 @Hide 注解的类不进行反射处理，只处理调用的类
                        boolean result = ReferencedClassProcessor.process(className, it, jarZipDir)
                        // 不可以覆盖上面的查询结果，如果直接赋值的话可能改 jar 包中含有 @Hide 的类无法被打包
                        haveTarget = !haveTarget ? result : haveTarget
                    }
                })
            }
        }
        return haveTarget
    }
}

<|start_filename|>seeker-processor/src/main/java/com/yeoh/seeker/processor/HideMethodCacher.java<|end_filename|>
package com.yeoh.seeker.processor;

import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import com.google.common.base.Charsets;
import com.google.common.io.Files;
import com.google.gson.Gson;
import com.yeoh.seeker.HideMethod;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Writer;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Used to cache {@link com.yeoh.seeker.HideMethod} to memory .
 *
 * @author jingyeoh .
 * @since 2018-09-30
 */
class HideMethodCacher {

    private static Map<String, List<HideMethod>> mHideMethodMap = new HashMap<>();

    static void putAll(@Nullable Map<String, List<HideMethod>> hideMethodMap) {
        if (hideMethodMap == null || hideMethodMap.isEmpty()) {
            return;
        }
        for (String className: hideMethodMap.keySet()) {
            if (!mHideMethodMap.containsKey(className)) {
                mHideMethodMap.put(className, hideMethodMap.get(className));
            }
        }
    }

    static void readFromCache() throws IOException {
        File file = new File("./build/Seeker/seeker.json");
        if (!file.exists()) {
            return;
        }
        InputStreamReader reader = new InputStreamReader(new FileInputStream(file));
        BufferedReader br = new BufferedReader(reader);
        StringBuilder content = new StringBuilder();
        content.append(br.readLine());
        br.lines().forEach(content::append);
        String jsonStr = content.toString();
        if (jsonStr.isEmpty()) {
            return;
        }
        Gson gson = new Gson();
        Map map = gson.fromJson(jsonStr, Map.class);
        putAll(map);
    }

    static void cache() throws IOException {
        File file = new File("./build/Seeker/seeker.json");
        if (file.exists()) {
            file.delete();
        }
        Files.createParentDirs(file);
        Writer writer = Files.newWriter(file, Charsets.UTF_8);
        new Gson().toJson(mHideMethodMap, writer);
        writer.close();
    }

    @NonNull
    static Map<String, List<HideMethod>> getAll() {
        return mHideMethodMap;
    }
}


<|start_filename|>seeker-annotation/src/main/java/com/yeoh/seeker/annotation/Modifier.java<|end_filename|>
package com.yeoh.seeker.annotation;

/**
 * Represents a modifier on a method element .
 *
 * @author JingYeoh
 * @since 2018-09-13
 */
public enum Modifier {

    /** The modifier {@code public} */ PUBLIC,
    /** The modifier {@code protected} */ PROTECTED,
    /** The modifier {@code private} */ PRIVATE,
    /** The modifier with the default value */ DEFAULT;

    /**
     * Returns this modifier's name in lowercase.
     */
    public String toString() {
        return name().toLowerCase(java.util.Locale.US);
    }
}


<|start_filename|>seeker-plugin/src/main/groovy/com/yeoh/seeker/plugin/processor/bytecode/MethodModifierProcessor.groovy<|end_filename|>
package com.yeoh.seeker.plugin.processor.bytecode

import com.yeoh.seeker.plugin.DataSource
import com.yeoh.seeker.plugin.utils.GenerateUtils
import com.yeoh.seeker.plugin.utils.Log
import com.yeoh.seeker.plugin.utils.ThrowExecutionError
import javassist.CtClass
import javassist.CtMethod
import javassist.bytecode.AccessFlag
import javassist.bytecode.AnnotationsAttribute
import javassist.bytecode.Descriptor

/**
 * 修改方法的 modifier 为指定的 modifier.
 *
 * @author Yeoh @ Zhihu Inc.
 * @since 2018/10/4
 */
class MethodModifierProcessor extends BytecodeProcessor {

    private static final int LOG_LEVEL = 3
    static final String GROUP = "MethodModifier"

    /**
     * 处理方法的 modifier
     * @param className 要处理的类
     * @param modifierClass 含有 @Hide 注解的类
     * @param jarZipDir jar 的路径
     * @return 是否处理
     */
    static boolean process(String className, String modifierClass, String jarZipDir) {
        boolean hasTarget = false
        if (GenerateUtils.isClassEqual(className, modifierClass)) {
            startProcess(className, jarZipDir)
            hasTarget = true
        }
        return hasTarget
    }
    /**
     * 开始处理，含有字节码的重新写入
     * @param className 要处理的类
     * @param jarZipDir jar 的路径
     */
    private static void startProcess(String className, String jarZipDir) {
        mClassPool.appendClassPath(jarZipDir)
        CtClass c = mClassPool.getCtClass(className)
        if (c.isFrozen()) {
            c.defrost()
        }

        doProcess(c)

        c.writeFile(jarZipDir)
    }
    /**
     * 处理字节码
     * @param c 要处理的类
     */
    private static void doProcess(CtClass c) {
        if (c == null) {
            return
        }
        String className = c.name
        def hideMethods = DataSource.seekerConfig.get(className)
        if (hideMethods == null) {
            return
        }
        Log.i(LOG_LEVEL, GROUP, "begin to process class :" + className)
        Log.i(LOG_LEVEL + 1, GROUP, "hideMethods = " + hideMethods)

        c.setModifiers(AccessFlag.setPublic(c.getModifiers()))

        hideMethods.forEach({
            processTargetMethod(c, it)
        })
        Log.i(LOG_LEVEL, GROUP, "done")
    }

    /**
     * 对添加 @Hide 注解的方法进行处理
     */
    private static void processTargetMethod(CtClass c, def hideMethod) {
        Log.i(LOG_LEVEL + 1, GROUP, " start to change method: " + hideMethod.toString())

        CtClass returns = mClassPool.getCtClass(hideMethod.returns)
        Log.i(LOG_LEVEL + 2, GROUP, "returns get success...")

        CtClass[] params = null
        if (hideMethod.params != null) {
            params = new CtClass[hideMethod.params.size()]
            for (int i = 0; i < params.length; i++) {
                String className = hideMethod.params[i]
                params[i] = mClassPool.getCtClass(className)
            }
        }
        String descriptor = Descriptor.ofMethod(returns, params)
        Log.i(LOG_LEVEL + 2, GROUP, "descriptor get success...")

        CtMethod ctMethod = GenerateUtils.getMethod(mClassPool, c, hideMethod.methodName, descriptor)
        if (ctMethod == null) {
            ThrowExecutionError.throwError(c.name + " not found method:  " + hideMethod.methodName)
        }
        // 改变 modifier 的值
        GenerateUtils.changeModifier(ctMethod, hideMethod.modifier)
        Log.i(LOG_LEVEL + 1, GROUP, c.name + "#" + hideMethod.methodName + " modifier changed to " + hideMethod.modifier)
        // 删除 @Hide 注解
        ctMethod.methodInfo.attributes.forEach({
            if (it instanceof AnnotationsAttribute) {
                for (annotation in it.getAnnotations()) {
                    if (annotation.getTypeName() == DataSource.ANNOTATION_HIDE) {
                        it.removeAnnotation(DataSource.ANNOTATION_HIDE)
                        Log.i(LOG_LEVEL + 1, GROUP, c.name + " has removed @Hide")
                    }
                }
            }
        })
    }
}

<|start_filename|>seeker-plugin/src/main/groovy/com/yeoh/seeker/plugin/extension/SeekerExtension.groovy<|end_filename|>
package com.yeoh.seeker.plugin.extension
/**
 * SeekerExtension 的配置类
 *
 * @author Yeoh @ Zhihu Inc.
 * @since 2018/11/13
 */
class SeekerExtension {

    public static final String NAME = 'seeker'
    // 是否启用插件
    boolean enable = true
    // 是否打印日志
    boolean debugEnable = false
    // IDEA 提示的是 java 源码，如果有的话，所以可以直接 hook java 源码
    boolean trickIDEA = true
    // 是否 hook .class 文件
    boolean hookClass = true
    // 执行源码打包的 task 名称
    String sourcesJarTask = "sourcesJar"

    void copy(def seeker) {
        if (seeker == null) {
            return
        }
        enable = seeker.enable
        debugEnable = seeker.debugEnable
        trickIDEA = seeker.trickIDEA
        hookClass = seeker.hookClass
        if (seeker.sourcesJarTask != null && !seeker.sourcesJarTask.trim().isEmpty()) {
            sourcesJarTask = seeker.sourcesJarTask
        }
    }

    @Override
    String toString() {
        return "seeker: " +
                " | enable = ${enable}" +
                " | debugEnable = ${debugEnable}" +
                " | trickIDEA = ${trickIDEA}" +
                " | hookClass = ${hookClass}" +
                " | sourcesJarTask = ${sourcesJarTask}"

    }
}

<|start_filename|>seeker-plugin/src/main/groovy/com/yeoh/seeker/plugin/processor/java/SourceCodeProcessor.groovy<|end_filename|>
package com.yeoh.seeker.plugin.processor.java

import com.github.javaparser.JavaParser
import com.github.javaparser.ast.CompilationUnit
import com.yeoh.seeker.plugin.utils.Log
import groovy.io.FileType

/**
 * 用于 hook java　源码的处理类
 *
 * @author Yeoh @ Zhihu Inc.
 * @since 2018/11/18
 */
class SourceCodeProcessor {

    private static final int LEVEL = 3
    private static final String GROUP = "SourceCodeProcessor"

    // 具体到每个类的路径
    private Set<File> mSourcesFilePath = []

    SourceCodeProcessor(def sourcesPath) {
        if (sourcesPath as Collection) {
            sourcesPath.forEach {
                parseJavaSourcePath(it)
            }
        } else if (sourcesPath as String) {
            parseJavaSourcePath(sourcesPath)
        }
    }

    /**
     * 解析文件夹中的文件并且添加到集合中
     * @param folder 文件夹
     */
    private void parseJavaSourcePath(String folder) {
        File root = new File(folder)
        if (!root.exists()) {
            Log.i(LEVEL + 1, GROUP, "${folder} does not exist!")
            return
        }
        root.eachFileRecurse(FileType.FILES) {
            mSourcesFilePath.add(it)
        }
    }

    /**
     * 开始处理
     */
    void process() {
        Log.i(LEVEL, GROUP, "source code processor start...")

        mSourcesFilePath.forEach({
            hookJavaSourceCode(it)
        })

        Log.i(LEVEL, GROUP, "source code processor done")
        Log.ln(LEVEL, GROUP)
    }

    /**
     * 解析 java　源码
     * @param javaPath java　文件路径
     */
    private void hookJavaSourceCode(File javaPath) {
        if (!javaPath.path.endsWith(".java")) {
            return
        }
        Log.i(LEVEL + 1, GROUP, "hook java source ${javaPath}")
        CompilationUnit compilationUnit = JavaParser.parse(javaPath)
        // hook method body
        JavaReferencedClassParser referencedClassParser = new JavaReferencedClassParser(compilationUnit, javaPath)
        referencedClassParser.hook()
        // hook method modifier
//        JavaMethodModifierParser methodModifierParser = new JavaMethodModifierParser(compilationUnit, javaPath)
//        methodModifierParser.hook()
    }

}

<|start_filename|>seeker-plugin/src/main/groovy/com/yeoh/seeker/plugin/SourceProcessor.groovy<|end_filename|>
package com.yeoh.seeker.plugin

import com.yeoh.seeker.plugin.processor.java.SourceCodeProcessor
import com.yeoh.seeker.plugin.utils.Log
import com.yeoh.seeker.plugin.utils.ThrowExecutionError
import org.gradle.api.Project
import org.gradle.api.Task

/**
 * 处理 Java 源代码的处理器，hook 了 sourcesJar plugin 并 hook 了相关的 java 代码
 *
 * @author Yeoh @ Zhihu Inc.
 * @since 2018/11/16
 */
class SourceProcessor {

    private static final int LEVEL = 2
    private static final String GROUP = "SourceProcessor"
    private final Project mProject
    private String mSourceJarTaskName
    private Task mSourcesJar

    private static String TEMP_SOURCES_ROOT = "build/Seeker/sources/"

    private Set mHookSourcesPath = []
    private Set mSourcesPath = []

    SourceProcessor(Project project, String sourceJarTaskName) {
        mProject = project
        mSourceJarTaskName = sourceJarTaskName
    }

    /**
     * 开始处理
     */
    void process() {
        Log.i(LEVEL, GROUP, "----------- SourceProcessor -----------")
        File rootHookSources = new File(TEMP_SOURCES_ROOT)
        if (!rootHookSources.exists()) {
            rootHookSources.mkdir()
        }
        TEMP_SOURCES_ROOT = rootHookSources.absolutePath
        Log.i(LEVEL, GROUP, TEMP_SOURCES_ROOT)
        resolveSourcesJar()
        hookSourcesJarTask()
        processSources()
        Log.ln(LEVEL, GROUP)
    }

    /**
     * 解析 sourceJar task，获取 from 参数
     */
    private void resolveSourcesJar() {
        mSourcesJar = mProject.tasks.findByName(mSourceJarTaskName)
        if (mSourcesJar == null) {
            ThrowExecutionError.throwError("can not find ${mSourceJarTaskName} task, please check your gradle")
            return
        }
        Log.i(LEVEL + 1, GROUP, "find sourcesJar plugin")
        mSourcesJar.mainSpec.sourcePaths.forEach {
            Log.i(LEVEL + 1, GROUP, "source path = ${it}")
            if (it as Collection) {
                Set<String> innerSources = []
                it.forEach { innerIt ->
                    innerSources.add(innerIt)
                }
                mSourcesPath.add(innerSources)
            } else if (it as String) {
                mSourcesPath.add(it)
            }
        }
    }

    /**
     * hook sourcesJar task 并替换 from 路径
     */
    private void hookSourcesJarTask() {
        mSourcesPath.forEach({
            mSourcesJar.mainSpec.sourcePaths.remove(it)
            if (it as Collection) {
                Set<String> innerDir = []
                it.forEach { innerIt ->
                    def tempSourcePath = getTempSourcePath(innerIt)
                    innerDir.add(tempSourcePath)
                    // 添加至临时文件夹中，执行完毕后需要删除临时文件夹
                    mHookSourcesPath.add(innerDir)
                }
                mSourcesJar.mainSpec.sourcePaths.add(innerDir)
            } else if (it as String) {
                def tempSourcePath = getTempSourcePath(it)
                mSourcesJar.mainSpec.sourcePaths.add(tempSourcePath)
                // 添加至临时文件夹中，执行完毕后需要删除临时文件夹
                mHookSourcesPath.add(tempSourcePath)
            }
        })
        Log.i(LEVEL + 1, GROUP, "hookSourcesJarTask success")
        Log.i(LEVEL + 2, GROUP, "sources path changed to: ${mSourcesJar.mainSpec.sourcePaths}")
    }

    /**
     * hook sources 源码
     */
    private void processSources() {
        mSourcesJar.doFirst {
            // 读取 seeker.json 配置
            DataSource.configureSeeker()
            hookSources()
        }.doLast {
            deleteHookSourcesDir()
        }
    }

    /**
     * hook source 文件夹
     * 1. 复制原本的 Sources 文件到 hookSources　中
     * 2. hook java 源代码
     */
    private void hookSources() {
        Log.i(LEVEL + 1, GROUP, "hookSources")
        copySourcesToHookSources()
        hookSourcesFiles()
    }

    /**
     * 删除 hook 的临时文件夹
     */
    private void deleteHookSourcesDir() {
        Log.i(LEVEL + 1, GROUP, "deleteHookSourcesDir")
        mHookSourcesPath.forEach({
//            "rm -rf ${it}".execute()
        })
        mHookSourcesPath.clear()
    }

    /**
     * 返回需要 hook 的临时路径
     * @param path 原路径
     * @return 新的路径
     */
    private String getTempSourcePath(def path) {
        path = path.toString()
        String[] arr = path.split("/")
        return "${TEMP_SOURCES_ROOT}/${arr[arr.length - 1]}"
    }

    /**
     * 复制 sources 到 hook　sources 文件夹
     */
    private void copySourcesToHookSources() {
        for (int i = 0; i < mSourcesPath.size(); i++) {
            def source = mSourcesPath[i]
            if (source as Collection) {
                for (int j = 0; j < source.size(); j++) {
                    mProject.copy {
                        from(source)
                        into(mHookSourcesPath[i][j])
                    }
                }
            } else if (source as String) {
                mProject.copy {
                    from(source)
                    into(mHookSourcesPath[i])
                }
            }
        }
        Log.i(LEVEL + 1, GROUP, "copySourcesToHookSources done...")
    }

    /**
     * hook java　源码
     */
    private void hookSourcesFiles() {
        mHookSourcesPath.forEach({
            SourceCodeProcessor processor = new SourceCodeProcessor(it)
            processor.process()
        })
    }
}

<|start_filename|>seeker-plugin/src/main/groovy/com/yeoh/seeker/plugin/processor/bytecode/ReferencedClassProcessor.groovy<|end_filename|>
package com.yeoh.seeker.plugin.processor.bytecode

import com.yeoh.seeker.plugin.DataSource
import com.yeoh.seeker.plugin.utils.GenerateUtils
import com.yeoh.seeker.plugin.utils.Log
import javassist.CannotCompileException
import javassist.CtClass
import javassist.CtMethod
import javassist.bytecode.MethodInfo
import javassist.expr.ExprEditor
import javassist.expr.NewExpr

/**
 * 用于修改「调用添加 @Hide 注解的方法」的类.
 * 修改调用逻辑，把原来的正常调用的方法换为生成的通过反射调用的方法.
 *
 * @author Yeoh @ Zhihu Inc.
 * @since 2018/10/4
 */
class ReferencedClassProcessor extends BytecodeProcessor {

    private static final int LOG_LEVEL = 3
    static final String REF_DELEGATE_SUFFIX = "RefDelegate"
    static final String REF_DELEGATE_PREFIX = "_"
    static final String GROUP = "ReferencedClass"

    /**
     * 是否已经处理过该类
     * @param className 要处理的类
     * @param targetClass 含有 @Hide 注解的类
     * @return 是否已经处理过反射缓存
     */
    static boolean isProcessedInCache(String className, String referencedClass, String jarZipDir) {
        // 如果是否已经处理过该类了，则忽略
        if (DataSource.isProcessedRefDelegate(className, referencedClass)) {
            Log.i(LOG_LEVEL, GROUP, "class " + className + " has been proceed logger from memory cache")
            startProcess(className, referencedClass, jarZipDir, false)
            return true
        }
        return false
    }

    /**
     * 处理调用被 @Hide 注解标记的方法的类
     * @param className 要处理的类
     * @param targetClass 含有 @Hide 注解的类
     * @return 是否进行 jar 重新打包
     */
    static boolean process(String className, String referencedClass, String jarZipDir) {
        // 如果处理的类就是「反射缓存类」，则忽略
        if (GenerateUtils.isClassEqual(className, generateRefDelegateClassName(referencedClass))) {
            return false
        }
        // 如果是否已经处理过该类了（在缓存中判断），则忽略
        if (isProcessedInCache(className, referencedClass, jarZipDir)) {
            return true
        }
        CtClass c = mClassPool.getCtClass(className)
        if (c.isFrozen()) {
            c.defrost()
        }
        // 判断类是否已经被处理过（判断条件：含有「反射缓存代理类」）
        for (int i = 0; i < c.refClasses.size(); i++) {
            def it = c.refClasses[i]
            if (GenerateUtils.isClassEqual(it, generateRefDelegateClassName(referencedClass))) {
                Log.i(LOG_LEVEL, GROUP, "class " + className + " has been proceed")
                startProcess(className, referencedClass, jarZipDir, false)
                return true
            }
        }
        // 判断类中是否含有被 @Hide 标记的类的方法调用
        for (int i = 0; i < c.refClasses.size(); i++) {
            def it = c.refClasses[i]
            // 如果是否已经处理过该类了（在缓存中判断），则忽略，之所以在此处要再次判断是因为：有时候一个类会返回两次（可能是javassist问题）
            if (isProcessedInCache(className, referencedClass, jarZipDir)) {
                continue
            }
            if (GenerateUtils.isClassEqual(referencedClass, it)) {
                startProcess(className, referencedClass, jarZipDir, true)
                // 缓存到内存中
                DataSource.putToRefCache(className, referencedClass)
            }
        }
        return true
    }

    /**
     * 开始处理
     * @param hostClass 要处理的类
     * @param referencedClass 含有 @Hide 注解的类
     * @param path jar 包路径
     * @param process 是否对方法进行处理，必须要进行 jar 打包过程，否则已经替换过的会不生效
     */
    private static void startProcess(String hostClass, String referencedClass, String path, boolean process) {
        Log.i(LOG_LEVEL, GROUP, "start process " + hostClass)
        CtClass host = mClassPool.getCtClass(hostClass)
        if (host.isFrozen()) {
            host.defrost()
        }
        if (process) {
            doProcess(host, referencedClass)
        }
        host.writeFile(path)
        Log.i(LOG_LEVEL, GROUP, "process end ...")
        Log.ln(LOG_LEVEL, GROUP)
    }

    /**
     * 处理过程
     * @param c 要处理的类 CtClass 对象
     * @param referencedClass 含有 @Hide 注解的类
     */
    private static void doProcess(CtClass c, String referencedClass) {
        if (c == null) {
            return
        }
        String hostClass = c.name
        Log.i(LOG_LEVEL + 1, GROUP, "start to process referenced class :" + hostClass)

        // 遍历类中的所有方法，获取方法中涉及到的类，然后和 HideMethod 进行对比
        c.classFile.getMethods().forEach({
            extractReferencedClassFromMethod(c, referencedClass, it)
        })

        Log.i(LOG_LEVEL + 1, GROUP, "done")
        Log.ln(LOG_LEVEL + 1, GROUP)
    }

    /**
     * 从方法中解析出方法涉及到的类
     * @param c 要处理的类 CtClass 对象
     * @param referencedClassName 含有 @Hide 注解的类名
     * @orram info 方法的信息
     */
    private static void extractReferencedClassFromMethod(CtClass ctClass, String referencedClassName, MethodInfo info) {
        String methodName = info.name
        // 通过 descriptor　获取方法参数中的类
        String descriptor = info.descriptor
        Log.i(LOG_LEVEL + 2, GROUP, "methodName = " + methodName)
        CtMethod ctMethod = GenerateUtils.getMethod(mClassPool, ctClass, methodName, descriptor)
        Log.i(LOG_LEVEL + 2, GROUP, "method = " + ctMethod)

        Log.i(LOG_LEVEL + 2, GROUP, "findInSeeker start...")
        if (ctMethod != null) {
            ctMethod.instrument(new ExprEditor() {
                @Override
                void edit(NewExpr e) throws CannotCompileException {
                    findInSeeker(e, referencedClassName)
                }
            })
        }
        Log.i(LOG_LEVEL + 2, GROUP, "findInSeeker end...")
        Log.ln(LOG_LEVEL + 2, GROUP)
    }

    /**
     * 在 SeekerExtension 中寻找是否有匹配的类
     * @param e 方法中创建新对象的代码，在此处进行替换为反射代理类
     * @param referencedClassName 含有 @Hide 注解的类名
     */
    private static void findInSeeker(NewExpr e, String referencedClassName) {
        if (referencedClassName != e.className ||
                referencedClassName.replace("\$", ".") != e.className) {
            return
        }
        Log.i(LOG_LEVEL + 3, GROUP, "find referenced class: " + e.className)
        String replaceBody = "{ " + "\$_ = new " + generateRefDelegateClassName(referencedClassName) + "(\$proceed(\$\$)); " + "}"

        Log.i(LOG_LEVEL + 3, GROUP, replaceBody)
        e.replace(replaceBody)
    }

    /**
     * 生成反射代理类的完整名称
     * @param classFullName 包含报包名的类名
     */
    private static String generateRefDelegateClassName(String classFullName) {
        String[] splitStr = classFullName.split("\\.")
        String className = splitStr[splitStr.length - 1]
        String packageName = classFullName.substring(0, classFullName.length() - className.length() - 1)
        return packageName + "." + REF_DELEGATE_PREFIX + className + REF_DELEGATE_SUFFIX
    }
}

<|start_filename|>seeker-processor/src/main/java/com/yeoh/seeker/processor/Log.java<|end_filename|>
package com.yeoh.seeker.processor;

/**
 * Used to print log info
 *
 * @author yangjing @ Zhihu Inc.
 * @since 2018-08-21
 */
class Log {

    public static final String ANSI_RESET = "\u001B[0m";
    public static final String ANSI_BLACK = "\u001B[30m";
    public static final String ANSI_RED = "\u001B[31m";
    public static final String ANSI_GREEN = "\u001B[32m";
    public static final String ANSI_YELLOW = "\u001B[33m";
    public static final String ANSI_BLUE = "\u001B[34m";
    public static final String ANSI_PURPLE = "\u001B[35m";
    public static final String ANSI_CYAN = "\u001B[36m";
    public static final String ANSI_WHITE = "\u001B[37m";

    private static boolean ableDebug = false;

    private static void printWithColor(String info, String color) {
        if (ableDebug) {
            System.out.println(color + info + color);
        }
    }

    public static void print(String info) {
        System.out.println(ANSI_PURPLE + info + ANSI_PURPLE);
    }

    public static void title(String info) {
        printWithColor(info, ANSI_BLUE);
    }

    public static void second(String info) {
        printWithColor(info, ANSI_CYAN);
    }

    public static void i(String info) {
        printWithColor(info, ANSI_WHITE);
    }

    public static void d(String info) {
        printWithColor(info, ANSI_GREEN);
    }

    public static void w(String info) {
        printWithColor(info, ANSI_YELLOW);
    }

    public static void e(String info) {
        printWithColor(info, ANSI_RED);
    }
}


<|start_filename|>seeker/src/main/java/com/yeoh/seeker/HideRefDelegate.java<|end_filename|>
package com.yeoh.seeker;

import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * Used to process reflect method  .
 *
 * @author yangjing .
 * @since 2018-09-20
 */
public class HideRefDelegate {

    private final Object object;
    private final Class mClass;

    public HideRefDelegate(@NonNull Object object) {
        this.object = object;
        mClass = object.getClass();
    }

    protected Object invokeMethod(Method method, Object... args) {
        try {
            return method.invoke(object, args);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        return null;
    }

    protected Object invokeMethodForVarArgs(Method method, Object arg) {
        try {
            return method.invoke(object, (Object) arg);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        return null;
    }

    protected Method reflectMethod(HideMethod hideMethod) {
        Method method = HideRefFactory.getMethod(mClass, hideMethod);
        try {
            if (method == null) {
                method = mClass.getDeclaredMethod(hideMethod.methodName, getParams(hideMethod));
                method.setAccessible(true);
                HideRefFactory.putMethod(mClass, hideMethod, method);
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        return method;
    }

    @Nullable
    private Class[] getParams(HideMethod hideMethod) throws ClassNotFoundException {
        Class<?>[] params = null;
        if (hideMethod.params != null && hideMethod.params.length > 0) {
            params = new Class[hideMethod.params.length];
            for (int i = 0; i < hideMethod.params.length; i++) {
                params[i] = guessClassForName(hideMethod.params[i]);
            }
        }
        return params;
    }

    /**
     * 根据类名猜测 Class
     *
     * @param className 类名
     *
     * @return Class or null
     */
    @Nullable
    private static Class guessClassForName(String className) throws ClassNotFoundException {
        if (className == null || className.trim().isEmpty()) {
            return null;
        }
        switch (className.toLowerCase()) {
            case "void":
                return void.class;
            case "boolean":
                return boolean.class;
            case "byte":
                return byte.class;
            case "short":
                return short.class;
            case "int":
                return int.class;
            case "long":
                return long.class;
            case "char":
                return char.class;
            case "float":
                return float.class;
            case "double":
                return double.class;
            case "java.lang.object":
                return Object.class;
        }
        // 如果是数组
        if (className.endsWith("[]")) {
            String realClassName = className.substring(0, className.length() - 2);
            return getArrayTypeClass(realClassName);
        }
        return Class.forName(className);
    }

    /**
     * 返回数组类型的 Class
     */
    private static Class getArrayTypeClass(String className) throws ClassNotFoundException {
        if (className == null || className.trim().isEmpty()) {
            return null;
        }
        switch (className.toLowerCase()) {
            case "void":
                return void.class;
            case "boolean":
                return boolean[].class;
            case "byte":
                return byte[].class;
            case "short":
                return short[].class;
            case "int":
                return int[].class;
            case "long":
                return long[].class;
            case "char":
                return char[].class;
            case "float":
                return float[].class;
            case "double":
                return double[].class;
            case "java.lang.object":
                return Object[].class;
        }
        return Class.forName("[L" + className + ";");
    }
}


<|start_filename|>seeker/src/main/java/com/yeoh/seeker/HideRefFactory.java<|end_filename|>
package com.yeoh.seeker;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

/**
 * Used to save reflect object .
 *
 * @author yangjing .
 * @since 2018-09-15
 */
public class HideRefFactory {

    private static Map<Class, Map<String, Method>> methodMap;

    static Method getMethod(Class refName, HideMethod hideMethod) {
        if (methodMap == null) {
            return null;
        }
        Map<String, Method> methods = methodMap.get(refName);
        if (methods == null) {
            return null;
        }
        return methods.get(hideMethod.toString());
    }

    static void putMethod(Class refName, HideMethod hideMethod, Method method) {
        if (methodMap == null) {
            methodMap = new HashMap<>();
        }
        Map<String, Method> methods = methodMap.get(refName);
        if (methods == null) {
            methods = new HashMap<>();
            methodMap.put(refName, methods);
        }
        methods.put(hideMethod.toString(), method);
    }
}


<|start_filename|>seeker-plugin/src/main/groovy/com/yeoh/seeker/plugin/utils/ZipUtils.groovy<|end_filename|>
package com.yeoh.seeker.plugin.utils

import java.util.zip.ZipEntry
import java.util.zip.ZipFile
import java.util.zip.ZipOutputStream

/**
 * zip 的帮助类，用于解压、重新压缩等
 *
 * @author Yeoh @ Zhihu Inc.
 * @since 2018/11/15
 */
class ZipUtils {

    private static final int LEVEL = 6
    private static final String GROUP = "ZipUtils"

    /**
     * 解压 Zip 文件
     * @param zipPath zip 文件目录
     * @param unZipDir 解压的文件夹
     */
    static void unZip(String zipPath, File unZipDir) {
        unZip(new File(zipPath), unZipDir)
    }
    /**
     * 解压 Zip 文件
     * @param zipFile zip 文件
     * @param unZipDir 解压的文件夹
     */
    static void unZip(File zipFile, File unZipDir) {
//        "unzip -o ${zipFile.path} -d ${unZipDir.getParent()}".execute()

        if (!unZipDir.exists()) {
            unZipDir.mkdirs()
        }
        def zip = new ZipFile(zipFile)
        zip.entries().each {
            if (!it.isDirectory()) {
                def fOut = new File(unZipDir.path + File.separator + it.name)
                //create output dir if not exists
                new File(fOut.parent).mkdirs()
                def fos = new FileOutputStream(fOut)
                //println "name:${it.name}, size:${it.size}"
                def buf = new byte[it.size]
                def len = zip.getInputStream(it).read(buf) //println zip.getInputStream(it).text
                fos.write(buf, 0, len)
                fos.close()
            }
        }
        zip.close()
    }

    /**
     * 压缩 Zip 文件
     * @param unZipFile 要压缩的文件
     * @param zipDir 要压缩到的文件夹
     */
    static void zip(File unZipFile, File zipDir) {
        ZipOutputStream zipFile = new ZipOutputStream(new FileOutputStream(unZipFile))
        zipDir.eachFile() { file ->
            //check if file
            if (file.isFile()) {
                zipFile.putNextEntry(new ZipEntry(file.name))
                def buffer = new byte[file.size()]
                file.withInputStream {
                    zipFile.write(buffer, 0, it.read(buffer))
                }
                zipFile.closeEntry()
            }
        }
        zipFile.close()
    }
}

<|start_filename|>seeker-plugin/src/main/groovy/com/yeoh/seeker/plugin/utils/ParserUtils.groovy<|end_filename|>
package com.yeoh.seeker.plugin.utils

import com.github.javaparser.ast.Modifier
import com.github.javaparser.ast.Node
import com.github.javaparser.ast.body.MethodDeclaration

/**
 * JavaParser 的工具类
 *
 * @author yangjing @ Zhihu Inc.
 * @since 2018-11-21
 */
class ParserUtils {

    private static final String MODIFIER_NAME = "Modifier"

    /**
     * 返回 方法的 Modifier
     * @param methodDeclaration
     * @return 可空
     */
    static Modifier getMethodModifier(MethodDeclaration methodDeclaration) {
        if (methodDeclaration == null) {
            return null
        }
        def annotations = methodDeclaration.getAnnotations()
        if (annotations == null) {
            return null
        }
        Modifier targetModifier
        for (int i = 0; i < annotations.size(); i++) {
            def annotationExpr = annotations.get(i)
            if (annotationExpr.singleMemberAnnotationExpr) {
                annotationExpr.asSingleMemberAnnotationExpr().getChildNodes().forEach({
                    if (targetModifier == null) {
                        targetModifier = getModifier(it)
                    }
                })
            }
        }
        if (targetModifier == null) {
            targetModifier = Modifier.PRIVATE
        }
        return targetModifier
    }

    /**
     * 返回 方法的 Modifier
     * @param node
     * @return 默认返回 private
     */
    private static Modifier getModifier(Node node) {
        String modifierStr = node.toString()
        if (!modifierStr.startsWith(MODIFIER_NAME)) {
            return null
        }
        modifierStr = modifierStr.substring(MODIFIER_NAME.length() + 1).toLowerCase()
        switch (modifierStr) {
            case "default":
                return Modifier.PRIVATE
            case "public":
                return Modifier.PUBLIC
            case "private":
                return Modifier.PRIVATE
            case "protected":
                return Modifier.PROTECTED
        }
        return Modifier.PRIVATE
    }
}

<|start_filename|>mock-lib/src/main/java/com/yeoh/mock/MockModel2.java<|end_filename|>
package com.yeoh.mock;

import android.content.Context;
import com.yeoh.seeker.annotation.Hide;
import com.yeoh.seeker.annotation.Modifier;

public class MockModel2 {

    @Hide
    public String test() {
        return "test";
    }

    @Hide
    public String test(String value) {
        return value;
    }

    @Hide(Modifier.PROTECTED)
    public int add(int a, int b) {
        return a + b;
    }

    @Hide(Modifier.DEFAULT)
    public String[] test(String... value) {
        return value;
    }

    @Hide(Modifier.PUBLIC)
    public Context getContext(Context context) {
        return context;
    }

    @Hide
    public Context[] getContexts(Context... contexts) {
        return contexts;
    }

    @Hide
    public String getFirst(String... strings) {
        if (strings == null || strings.length == 0) {
            return null;
        }
        return strings[0];
    }

    @Hide
    public String[] abc() {
        return new String[]{"a", "b", "c"};
    }
}


<|start_filename|>seeker-plugin/src/main/groovy/com/yeoh/seeker/plugin/utils/Log.groovy<|end_filename|>
package com.yeoh.seeker.plugin.utils

import java.util.logging.Logger

/**
 * Used to print log info
 *
 * @author JingYeoh
 * @since 2018-08-09
 */
class Log {
    static final String TAG = "seeker"
    public static Debug = true
    public static Logger logger = Logger.getLogger("seeker")

    static void d(Object msg) {
        if (Debug) {
            println("$TAG: " + msg.toString())
        }
    }

    static void i(int level, String group, Object msg) {
        if (Debug) {
            def start = new StringBuffer()
            for (int i = 0; i < getStartSpace(level); i++) {
                start.append(" ")
            }
            for (int i = 0; i < level; i++) {
                start.append(">")
            }
            start.append(" ")
            start.append(group)
            start.append(": ")
            start.append(msg.toString())
            println(start.toString())
        }
    }

    static void ln(int level, String group) {
        if (Debug) {
            def start = new StringBuffer()
            for (int i = 0; i < getStartSpace(level); i++) {
                start.append(" ")
            }
            for (int i = 0; i < level; i++) {
                start.append(">")
            }
            start.append(" ")
            start.append(group)
            start.append(": ----------")
            println(start.toString())
        }
    }

    /**
     * 返回开始的空格个数
     */
    private static int getStartSpace(int level) {
        int sum = 0
        for (int i = level - 1; i > 0; i--) {
            sum += i
            sum += i - 1
        }
        return sum
    }

    static void error(Object msg) {
        logger.error("$TAG: " + msg.toString())
    }
}

<|start_filename|>seeker-plugin/src/main/groovy/com/yeoh/seeker/plugin/utils/AarUtils.groovy<|end_filename|>
package com.yeoh.seeker.plugin.utils
/**
 * aar 的帮助类，用于解压、重新压缩等
 *
 * @author Yeoh @ Zhihu Inc.
 * @since 2018/11/14
 */
class AarUtils {

    private static final int LEVEL = 6
    private static final String GROUP = "AarUtils"

    /**
     * 返回推荐的 aar　解压目录
     *
     * @param path aar　文件路径
     * @return 解压的 aar　路径
     */
    static String getExtractAarPath(String path) {
        return getExtractAarPath(new File(path))
    }

    /**
     * 返回推荐的 aar　解压目录
     *
     * @param aarFile aar　文件
     * @return 解压的 aar　路径
     */
    static String getExtractAarPath(File aarFile) {
        return aarFile.getParent() + "/" + aarFile.getName().replace(".aar", "")
    }

    /**
     * 解压 aar
     * @param path aar 路径
     * @param unAarDir 解压的目录
     */
    static Aar unAar(String path, File unAarDir) {
        return unAar(new File(path), unAarDir)
    }

    /**
     * 解压 aar
     * @param inputStream aar 的输入流
     * @param unAarDir 解压的目录
     */
    static Aar unAar(File file, File unAarDir) {
        if (!unAarDir.exists()) {
            unAarDir.mkdirs()
        }
        Aar aar = new Aar()
        aar.rootPath = file.path

        // 1. 解压 aar
        ZipUtils.unZip(file, unAarDir)

        // 2. 取出相应的信息
        unAarDir.eachFile {
            if (it.path.endsWith(".jar")) {
                aar.jarPath = it.path
            }
        }

        // 3. 解压 jar　包
        String extractJarPath = JarUtils.getExtractJarPath(aar.jarPath)
        JarUtils.unJar(new File(aar.jarPath), new File(extractJarPath))
        aar.extractJarPath = extractJarPath
        return aar
    }

    /**
     * 存储 aar 解压后的信息
     */
    static class Aar {
        String rootPath // .aar 根目录
        String jarPath // .jar　所在的路径
        String extractJarPath //解压后的 jar　路径
    }
}

<|start_filename|>seeker-plugin/src/main/groovy/com/yeoh/seeker/plugin/DataSource.groovy<|end_filename|>
package com.yeoh.seeker.plugin

import com.yeoh.seeker.plugin.utils.Log
import com.yeoh.seeker.plugin.utils.ThrowExecutionError
import groovy.json.JsonSlurper

/**
 * 用于存储 SeekerExtension 的配置和临时的一些缓存
 *
 * @author yangjing @ Zhihu Inc.
 * @since 2018-09-27
 */
class DataSource {

    static final String PATH_SEEKER_JSON = "./build/Seeker/seeker.json"

    public static final String ANNOTATION_HIDE = "com.yeoh.seeker.annotation.Hide"
    public static final String ENUM_MODIFIER = "com.yeoh.seeker.annotation.Modifier"

    def static seekerConfig = [:]
    private static Map<String, List<String>> processedRef = [:]
    // aar/jar　等解压后的路径，task　执行完毕后需要删除这些临时文件
    static Set<String> TEMP_DIRS = new HashSet<>()
    // 依赖的第三方库 jar 解压后的路径，用于在编译时生效
    static Set<String> DEPENDENCIES_JARS_PATH = new HashSet<>()
    // 依赖的第三方库
    static Set<String> DEPENDENCIES_PATH = []

    def static clear() {
        seekerConfig = [:]
        processedRef = [:]
    }

    /**
     * 标记类已经处理过反射缓存了
     * @param className 要处理的类名
     * @param referencedClass 缓存代理类名
     */
    static void putToRefCache(String className, String referencedClass) {
        List<String> referencedClasses = processedRef.get(className)
        if (referencedClasses == null) {
            referencedClasses = new ArrayList<>()
        }
        if (!referencedClasses.contains(referencedClass)) {
            referencedClasses.add(referencedClass)
        }
        processedRef.put(className, referencedClasses)
    }

    /**
     * 是否已经处理过反射缓存了
     * @param className 要处理的类名
     * @param referencedClass 缓存代理类
     * @return 是否缓存过
     */
    static boolean isProcessedRefDelegate(String className, String referencedClass) {
        List<String> referencedClasses = processedRef.get(className)
        if (referencedClasses == null) {
            return false
        }
        return referencedClasses.contains(referencedClass)
    }

    /**
     * 配置 Seeker ，读取本地 json 文件
     */
    static void configureSeeker() {
        if (!seekerConfig.isEmpty()) {
            return
        }
        File configFile = new File(PATH_SEEKER_JSON)
        if (configFile.exists()) {
            def content = new StringBuilder()
            configFile.eachLine("UTF-8") {
                content.append(it)
            }
            Map data = new JsonSlurper().parseText(content.toString())
            data.keySet().forEach {
                seekerConfig.put(it, data.get(it))
            }
            Log.i(2, "Seeker", "read seeker config success...")
        } else {
            ThrowExecutionError.throwError("seeker.json does not exist")
        }
    }
}

<|start_filename|>seeker-processor/src/main/java/com/yeoh/seeker/processor/SeekerProcessor.java<|end_filename|>
package com.yeoh.seeker.processor;

import com.google.auto.service.AutoService;

import java.io.IOException;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedOptions;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.TypeElement;

/**
 * The processor used to process {@link com.yeoh.seeker.annotation.Hide} annotation .
 *
 * @author yangjing @ Zhihu Inc.
 * @since 2018-08-21
 */
@SupportedAnnotationTypes("com.yeoh.seeker.annotation.Hide")
@SupportedOptions({SeekerProcessor.OPTION_MODULE_NAME, SeekerProcessor.OPTION_SUB_MODULES})
@AutoService(Processor.class)
public class SeekerProcessor extends AbstractProcessor {

    static final String OPTION_MODULE_NAME = "moduleNameOfSeeker";
    static final String OPTION_SUB_MODULES = "subModulesOfSeeker";

    private Filer mFiler;
    private boolean mHasProcess;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        Log.print("========================= SEEKER PROCESSOR ===================================");
        mFiler = processingEnvironment.getFiler();
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        String moduleName = processingEnv.getOptions().get(OPTION_MODULE_NAME);
        String subModules = processingEnv.getOptions().get(OPTION_SUB_MODULES);
        if (mHasProcess) {
            return false;
        }
        try {
            mHasProcess = new SeekerDelegateGenerator(subModules, moduleName, mFiler, roundEnvironment).generate();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return mHasProcess;
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.RELEASE_8;
    }
}


<|start_filename|>seeker-processor/src/main/java/com/yeoh/seeker/processor/GeneratorUtils.java<|end_filename|>
package com.yeoh.seeker.processor;

class GeneratorUtils {

    static Class getPrimitiveTypeClass(String className) {
        switch (className) {
            case "void":
                return void.class;
            case "boolean":
                return boolean.class;
            case "byte":
                return byte.class;
            case "short":
                return short.class;
            case "int":
                return int.class;
            case "long":
                return long.class;
            case "char":
                return char.class;
            case "float":
                return float.class;
            case "double":
                return double.class;
            case "java.lang.Object":
                return Object.class;
        }
        return null;
    }

    public static Class<?>[] getClasses(String className) {
        if (className.trim().endsWith("[]")) {
            String realClass = className.substring(0, className.length() - 2);
            try {
                Class<?> clazz = Class.forName(realClass);
                return clazz.getClasses();
            } catch (ClassNotFoundException e) {
                return null;
            }
        }
        return null;
    }

    static String getArrayClassName(String className) {
        if (className.trim().endsWith("[]")) {
            return className.substring(0, className.length() - 2);
        }
        return null;
    }

    static boolean isVoid(String className) {
        if (className == null || className.length() == 0) {
            return true;
        }
        if (className.toLowerCase().equals("void")) {
            return true;
        }
        return false;
    }
}


<|start_filename|>seeker/src/main/java/com/yeoh/seeker/HideSeekerDelegate.java<|end_filename|>
package com.yeoh.seeker;

import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Used to save object annotated with {@link com.yeoh.seeker.annotation.Hide} .
 *
 * @author yangjing @ Zhihu Inc.
 * @since 2018-09-06
 */
public class HideSeekerDelegate {

    protected Map<String, List<HideMethod>> mHideMethods;

    public HideSeekerDelegate() {
        mHideMethods = new HashMap<>();
    }

    @NonNull
    public Map<String, List<HideMethod>> getHideMethods() {
        return mHideMethods;
    }

    @Nullable
    public List<HideMethod> getHideMethods(@NonNull String className) {
        return mHideMethods.get(className);
    }

    public boolean isAnnotatedByHide(@NonNull String className, @NonNull HideMethod hideMethod) {
        List<HideMethod> hideMethods = getHideMethods(className);
        if (hideMethods == null) {
            return false;
        }
        for (HideMethod it: hideMethods) {
             if (it.equals(hideMethod)) {
                return true;
            }
        }
        return false;
    }

    public void addHideMethod(@NonNull String className, @NonNull HideMethod hideMethod) {
        List<HideMethod> hideMethods = getHideMethods(className);
        if (hideMethods == null) {
            hideMethods = new ArrayList<>();
        }
        hideMethods.add(hideMethod);
        if (!mHideMethods.containsKey(className)) {
            mHideMethods.put(className, hideMethods);
        }
    }
}


<|start_filename|>seeker-annotation/src/main/java/com/yeoh/seeker/annotation/Hide.java<|end_filename|>
package com.yeoh.seeker.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * The method annotated with {@link Hide} will be packed out to jar/aar after method's modifier is changed .
 *
 * @author JingYeoh
 * @since 2018-08-09
 */
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.METHOD)
public @interface Hide {

    /**
     * Allow or not processed .
     *
     * @return default value is {@link Modifier#PRIVATE} .
     *
     * @see Modifier#PRIVATE
     * @see Modifier#DEFAULT
     * @see Modifier#PUBLIC
     * @see Modifier#PROTECTED
     */
    Modifier value() default Modifier.PRIVATE;
}


<|start_filename|>seeker-plugin/src/main/groovy/com/yeoh/seeker/plugin/processor/java/BaseJavaParser.groovy<|end_filename|>
package com.yeoh.seeker.plugin.processor.java

import com.github.javaparser.ast.CompilationUnit
import com.yeoh.seeker.plugin.utils.Log
import com.yeoh.seeker.plugin.utils.ThrowExecutionError

/**
 * hook java 源码的基类
 *
 * @author yangjing @ Zhihu Inc.
 * @since 2018-11-19
 */
abstract class BaseJavaParser {

    protected static final int LEVEL = 4
    protected String GROUP = getClass().getSimpleName()

    protected CompilationUnit mCompilationUnit
    protected File mJavaPath
    protected boolean mHasHookTarget

    BaseJavaParser(CompilationUnit compilationUnit, File javaPath) {
        mCompilationUnit = compilationUnit
        mJavaPath = javaPath
    }

    /**
     * 开始执行
     */
    abstract void hook()

    /**
     * 寻找到了 hook 的目标
     */
    protected void findHookTarget() {
        if (!mHasHookTarget) {
            mHasHookTarget = true
        }
    }

    /**
     * 重新写入文件
     */
    protected void writeToPath() {
        if (!mHasHookTarget) {
            return
        }
        if (!mJavaPath.exists()) {
            ThrowExecutionError.throwError("${mJavaPath} does not exist!")
            return
        }
        mJavaPath.write(mCompilationUnit.toString())
        Log.i(LEVEL, GROUP, "write file ${mJavaPath} success")
    }
}

<|start_filename|>seeker-plugin/src/main/groovy/com/yeoh/seeker/plugin/processor/bytecode/BytecodeProcessor.groovy<|end_filename|>
package com.yeoh.seeker.plugin.processor.bytecode

import javassist.ClassPool

/**
 * 用于注入代码字节码的处理基类
 *
 * @author Yeoh @ Zhihu Inc.
 * @since 2018/11/13
 */
class BytecodeProcessor {

    protected static ClassPool mClassPool

    /**
     * 设置 ClassPool
     * @param pool ClassPool 对象
     */
    static void setClassPool(ClassPool pool) {
        mClassPool = pool
    }
}

<|start_filename|>seeker-plugin/src/main/groovy/com/yeoh/seeker/plugin/processor/java/JavaMethodModifierParser.groovy<|end_filename|>
package com.yeoh.seeker.plugin.processor.java

import com.github.javaparser.ast.CompilationUnit
import com.github.javaparser.ast.Modifier
import com.github.javaparser.ast.body.MethodDeclaration
import com.yeoh.seeker.plugin.DataSource
import com.yeoh.seeker.plugin.utils.Log
import com.yeoh.seeker.plugin.utils.ParserUtils

import java.util.function.Consumer

/**
 * hook Java method modifier 的类
 *
 * @author yangjing @ Zhihu Inc.
 * @since 2018-11-19
 */
class JavaMethodModifierParser extends BaseJavaParser {


    JavaMethodModifierParser(CompilationUnit compilationUnit, File javaPath) {
        super(compilationUnit, javaPath)
    }

    @Override
    void hook() {
        Log.i(LEVEL, GROUP, "parse java file ${mJavaPath}")
        mCompilationUnit.findAll(MethodDeclaration.class).stream()
                .forEach(new Consumer<MethodDeclaration>() {
            @Override
            void accept(MethodDeclaration methodDeclaration) {
                //检验是否有@Hide注解
                if (!checkHideAnnotation(methodDeclaration)) {
                    return
                }

                hookMethodModifier(methodDeclaration)
                hookMethodAnnotation(methodDeclaration)
            }
        })
        hookClassImports()
        writeToPath()
        Log.ln(LEVEL, GROUP)
    }

    /**
     * hook method modifier
     * @param methodDeclaration
     */
    private void hookMethodModifier(MethodDeclaration methodDeclaration) {
        final Modifier targetModifier = ParserUtils.getMethodModifier(methodDeclaration)
        if (targetModifier == null) {
            return
        }
        methodDeclaration.getModifiers().stream().forEach(new Consumer<Modifier>() {
            @Override
            void accept(Modifier modifier) {
                switch (modifier) {
                    case Modifier.PUBLIC:
                    case Modifier.PROTECTED:
                    case Modifier.PRIVATE:
                    case Modifier.DEFAULT:
                        methodDeclaration.getModifiers().remove(modifier)
                        methodDeclaration.setModifier(targetModifier, true)
                        findHookTarget()
                        Log.i(LEVEL + 1, GROUP, "hook method ${methodDeclaration.nameAsString} modifier to ${targetModifier}")
                        break
                    default:
                        break
                }
            }
        })
    }

    /**
     * hook method annotation and remove @Hide
     * @param methodDeclaration
     */
    private void hookMethodAnnotation(MethodDeclaration methodDeclaration) {
        //删除注解
        def annotations = methodDeclaration.getAnnotations()
        if (annotations != null) {
            int count = annotations.size()
            for (int i = count - 1; i >= 0; i--) {
                def annotationExpr = annotations.get(i)
                methodDeclaration.getAnnotations().remove(annotationExpr)

                findHookTarget()
                Log.i(LEVEL + 1, GROUP, "remove @Hide for method ${methodDeclaration.name}")
            }
        }
    }

    /**
     * hook 类的 import，删除 @Hide　的引用
     */
    private void hookClassImports() {
        def imports = mCompilationUnit.imports
        // 之所以要套一层，因为 直接在 forEach 中删除元素会打断当前循环... 未确认
        Set seekerImports = []
        if (imports != null) {
            imports.each {
                if (it.nameAsString == DataSource.ANNOTATION_HIDE || it.nameAsString == DataSource.ENUM_MODIFIER) {
                    seekerImports.add(it)
                }
            }
            seekerImports.forEach({
                imports.remove(it)
                Log.i(LEVEL + 1, GROUP, "remove import ${it.nameAsString} success")
            })
        }
    }

    /**
     * 检查方法中是否含有 @Hide
     * @param methodDeclaration
     * @return
     */
    private boolean checkHideAnnotation(MethodDeclaration methodDeclaration) {
        boolean hasHideAnnotation = false
        // 是否 import @Hide
        boolean isImportHideAnnotation = false
        def imports = mCompilationUnit.imports
        if (imports != null) {
            imports.forEach({
                if (it.nameAsString == DataSource.ANNOTATION_HIDE) {
                    isImportHideAnnotation = true
                }
            })
        }
        // JavaParser 中返回的都是 String，所以可能无法定位到完整包名，所以需要和 import 一起判断
        def annotations = methodDeclaration.getAnnotations()
        for (int i = 0; i < annotations.size(); i++) {
            def annotationExpr = annotations.get(i)
            if (annotationExpr.nameAsString == DataSource.ANNOTATION_HIDE) {
                hasHideAnnotation = true
                break
            } else if (annotationExpr.nameAsString == "Hide" && isImportHideAnnotation) {
                hasHideAnnotation = true
                break
            }
        }
        return hasHideAnnotation
    }
}

<|start_filename|>seeker-processor/src/main/java/com/yeoh/seeker/processor/SeekerDelegateJsonGenerator.java<|end_filename|>
package com.yeoh.seeker.processor;

import com.google.common.base.Charsets;
import com.google.common.io.Files;
import com.google.gson.Gson;
import com.yeoh.seeker.HideMethod;
import java.io.File;
import java.io.IOException;
import java.io.Writer;
import java.util.List;
import java.util.Map;
import javax.annotation.processing.Filer;
import javax.tools.FileObject;
import javax.tools.StandardLocation;

/**
 * Used to generate seeker.json file
 *
 * @author yangjing .
 * @since 2018-09-27
 */
class SeekerDelegateJsonGenerator {

    private Filer mFiler;
    private Map<String, List<HideMethod>> mHideMethodMap;

    SeekerDelegateJsonGenerator(Filer filer,
            Map<String, List<HideMethod>> hideMethodMap) {
        this.mFiler = filer;
        this.mHideMethodMap = hideMethodMap;
    }

    void generate() throws IOException {
        if (mHideMethodMap.isEmpty()) {
            Log.w("hideMethodMap is empty...");
        }
        Log.title("========== Generate JSON start ==========");
        FileObject fileObject = mFiler.getResource(StandardLocation.CLASS_OUTPUT, "",
                "com/yeoh/seeker/seeker.json");
        File file = new File(fileObject.toUri());
        if (file.exists()) {
            file.delete();
        }
        Files.createParentDirs(file);
        Writer writer = Files.newWriter(file, Charsets.UTF_8);
        new Gson().toJson(mHideMethodMap, writer);
        writer.close();
        Log.title("========== Generate JSON end ==========");
    }
}


<|start_filename|>mock-lib/src/main/java/com/yeoh/mock/Caller.java<|end_filename|>
package com.yeoh.mock;

import android.app.Fragment;

public class Caller extends Fragment {

    private static void callMockModel() {
        MockModel mockModel = new MockModel();
        mockModel.mockTestMethod();
        mockModel.mockTestMethod(1);
        mockModel.mockTestMethod("mock");
        mockModel.mockTestMethod();
    }

    private static void callMockModel2() {
        MockModel2 mockModel2 = new MockModel2();
        mockModel2.abc();
        System.out.print(mockModel2.add(1, 2) + "");
        String first = mockModel2.getFirst("a", "b", "c");
        System.out.print(first + "");
    }

    public static void mock() {
        callMockModel();
        callMockModel2();
    }

    private void test(String arg0) {
    }
}


<|start_filename|>seeker-plugin/src/main/groovy/com/yeoh/seeker/plugin/utils/ThrowExecutionError.groovy<|end_filename|>
package com.yeoh.seeker.plugin.utils

import com.google.common.util.concurrent.ExecutionError

/**
 * 用于抛出异常
 *
 * @author Yeoh @ Zhihu Inc.
 * @since 2018/10/4
 */
class ThrowExecutionError {

    static void throwError(String msg) {
        throw new ExecutionError(msg, new Error(msg))
    }

}


<|start_filename|>mock-lib/src/main/java/com/yeoh/mock/MockModel.java<|end_filename|>
package com.yeoh.mock;

import android.content.Context;
import com.yeoh.seeker.annotation.Hide;
import com.yeoh.seeker.annotation.Modifier;

public class MockModel {

    @Hide
    public void mockTestMethod() {
        System.out.println("mockTestMethod()");
    }

    @Hide
    public void mockTestMethod(String name) {
        System.out.println("mockTestMethod (" + name + ")");
    }

    @Hide
    public void mockTestMethod(Context context) {
        System.out.println("mockTestMethod with context params");
    }

    @Hide(Modifier.PROTECTED)
    public void mockTestMethod(int num) {
        System.out.println("mockTestMethod (" + num + ")");
    }
}


<|start_filename|>seeker-plugin/src/main/groovy/com/yeoh/seeker/plugin/utils/FileUtils.groovy<|end_filename|>
package com.yeoh.seeker.plugin.utils
/**
 * 文件的帮助类，用于复制、重命名等
 *
 * @author Yeoh @ Zhihu Inc.
 * @since 2018/11/15
 */
class FileUtils {

    private static final int LEVEL = 6
    private static final String GROUP = "FileUtils"

    /**
     * 复制文件
     * @param from 要复制的文件
     * @param toDir 要移动的目标文件夹
     */
    static void copy(File from, File toDir) {
        copy(from, toDir, from.name)
    }

    /**
     * 复制文件并重命名
     * @param from 要复制的文件
     * @param toDir 要移动的目标文件夹
     * @param reName 重命名的名字
     */
    static void copy(File from, File toDir, String reName) {
        String targetFilePath = toDir.path
        targetFilePath = !targetFilePath.endsWith("/") ? "${targetFilePath}/" : targetFilePath
        File targetFile = new File("${targetFilePath}${reName}")
        targetFile.write(from.text)
    }
}