com.taobao.android.JassitMojo.java Source code

Java tutorial

Introduction

Here is the source code for com.taobao.android.JassitMojo.java

Source

package com.taobao.android;

/*
 * Copyright 2001-2005 The Apache Software Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import com.taobao.android.mapping.InstrumentMappings;
import com.taobao.android.mapping.MappingFormatError;
import com.taobao.android.mapping.TargetMethodDesc;
import javassist.*;
import javassist.expr.ExprEditor;
import javassist.expr.MethodCall;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;

import org.apache.maven.plugin.logging.Log;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.project.MavenProject;

import java.io.*;
import java.util.Collection;

/**
 * Goal which makes instrumentation according to map file.
 */
@Mojo(name = "kee", defaultPhase = LifecyclePhase.PROCESS_CLASSES, requiresDependencyResolution = ResolutionScope.COMPILE)
public class JassitMojo extends AbstractMojo {

    @Parameter(defaultValue = "${project}", readonly = true, required = true)
    private MavenProject project;

    @Parameter(defaultValue = "${project.build.directory}", readonly = true)
    protected File targetDirectory;

    @Parameter(defaultValue = "${project.build.outputDirectory}", readonly = true)
    protected File projectOutputDirectory;

    private InstrumentMappings instrumentMappings = new InstrumentMappings();

    public void execute() throws MojoExecutionException {
        Log log = getLog();
        log.warn("kee start execution ......");

        try {
            doExec(log);
        } catch (Throwable e) {
            throw new MojoExecutionException("failed to execute kee-jassist", e);
        }
    }

    private void printInfo(Log log) {
        printArtifacts(project.getArtifacts(), log, "");
        log.warn(String.format("kee: PROJ=%s, FILE=%s, BUILD.DIR=%s, BUUILD.OUTPUT=%s", project.getName(),
                project.getFile(), project.getBuild().getDirectory(), project.getBuild().getOutputDirectory()));

        printArtifacts(project.getCompileArtifacts(), log, "COMPILE");
    }

    private static void printArtifacts(Collection<Artifact> artifacts, Log log, String prefix) {
        for (Artifact artifact : artifacts) {
            File artFile = artifact.getFile();
            String name = artFile.getName();
            log.warn(String.format("kee: %s_ARTIFACT=%s, FILE=%s", prefix, name, artFile));
        }
    }

    private void doExec(final Log log)
            throws NotFoundException, CannotCompileException, IOException, MappingFormatError {
        log.info("kee: config ClassPool ...");
        ClassPool classPool = ClassPool.getDefault();
        log.info(String.format("kee: append class path %s", projectOutputDirectory.getAbsolutePath()));
        classPool.appendClassPath(projectOutputDirectory.getAbsolutePath());

        for (Artifact artifact : project.getArtifacts()) {
            File artFile = artifact.getFile();
            log.info(String.format("kee: inserting class path %s", artFile.getAbsolutePath()));
            classPool.insertClassPath(artFile.getAbsolutePath());
        }

        log.info("kee: obtain and defrost MyActivity CtClass");
        CtClass activityCtClz = classPool.getCtClass("com.example.testapp.MyActivity");
        activityCtClz.defrost();

        CtClass bundleCtClz = classPool.getCtClass("android.os.Bundle");
        CtMethod method = activityCtClz.getDeclaredMethod("onCreate", new CtClass[] { bundleCtClz });

        log.info("kee: load mappings ...");
        instrumentMappings.load(log);

        log.info("kee: start instrumentation ...");
        long startTime = System.currentTimeMillis();
        doInstrumentExpEditor(classPool, activityCtClz, method);

        log.info(String.format("kee: instrument cost %d ms", System.currentTimeMillis() - startTime));
        byte[] byteCode = activityCtClz.toBytecode();

        log.info(String.format("kee: generated bytecode length = %d", byteCode.length));
        log.info("kee: writting to class file ...");
        toClassFile(byteCode);
    }

    private void doInstrumentCodeConvertor(ClassPool classPool, CtClass activityClz, CtMethod onCreateMethod)
            throws NotFoundException, CannotCompileException {
        CodeConverter codeConverter = new CodeConverter();
        //CtClass instrumentUtilClz = classPool.get("com.example.testapp.InstrumentUtil");
        CtMethod targetsetUiMethod = activityClz.getDeclaredMethod("setUIUI");
        // setUI method
        CtMethod orgSetUiMethod = activityClz.getDeclaredMethod("setUI");
        codeConverter.redirectMethodCall(orgSetUiMethod, targetsetUiMethod);
        onCreateMethod.instrument(codeConverter);
    }

    private void doInstrumentExpEditor(ClassPool classPool, CtClass activityClz, CtMethod onCreateMethod)
            throws CannotCompileException, IOException, MappingFormatError {
        onCreateMethod.instrument(new ExprEditor() {
            @Override
            public void edit(MethodCall m) throws CannotCompileException {
                /*if (m.getMethodName().equals("setContentView")) {
                m.replace("com.example.testapp.InstrumentUtil.setContentView($0);$_ = $proceed($$);");
                }*/

                TargetMethodDesc targetMethodDesc = null;
                try {
                    targetMethodDesc = instrumentMappings.getMappingTarget(m, getLog());
                } catch (NotFoundException e) {
                    e.printStackTrace();
                }

                if (null != targetMethodDesc) {
                    m.replace(String.format("%s.%s($0,$$);$_ = $proceed($$);", targetMethodDesc.getClassName(),
                            targetMethodDesc.getMethodName()));
                }
            }
        });
    }

    private void toClassFile(byte[] byteCode) throws IOException {
        File clzFile = new File(projectOutputDirectory, "com/example/testapp/MyActivity.class");
        getLog().info(String.format("kee: save class file to %s", clzFile.getAbsolutePath()));
        OutputStream outputStream = new FileOutputStream(clzFile);
        BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outputStream);
        bufferedOutputStream.write(byteCode);
        bufferedOutputStream.flush();
        bufferedOutputStream.close();
    }
}