Skip to content

Commit

Permalink
Merge pull request #4 from BryanSharp/extend_params_for_aop_0306
Browse files Browse the repository at this point in the history
support regEx/wildcard class match
test lightweight aop passed
  • Loading branch information
BryanSharp authored Mar 7, 2017
2 parents 2d8aed4 + 35e2ab2 commit 64e6fbc
Show file tree
Hide file tree
Showing 9 changed files with 343 additions and 66 deletions.
78 changes: 62 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
#HiBeaver

By applying the regular expression and wildcard feature, HiBeaver now has been upgraded to an Android lightweight AOP design tool.

![cute animals always busy in building their river dam](https://github.com/BryanSharp/hibeaver/blob/master/beaver.jpeg?raw=true)

Beaver means 河狸 in Chinese, cute animals always busy in building their cute river dam.

HiBeaver is an Android plugin for modifying your library jars byte code.
Basically, HiBeaver is an Android plugin for modifying your java byte code during the building of your package.

Dress them as if they are naked! Yeah~

Expand All @@ -16,7 +18,6 @@ This plugin has been uploaded to jcenter. You can use this by adding the followi

and then add this to you app build scripts:

apply plugin: 'hiBeaver'
import com.bryansharp.gradle.hibeaver.utils.MethodLogAdapter
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.MethodVisitor
Expand All @@ -32,30 +33,75 @@ and then add this to you app build scripts:
keepQuiet = false
//this is a kit feature of the plugin, set it true to see the time consume of this build
watchTimeConsume = false

//this is the most important part
//structure is like ['class':[[:],[:]],'class':[[:],[:]]], type is Map<String, List<Map<String, Object>>>
modifyMatchMaps = [
'the classname of which you want to modify': [
'classname of which to be modified': [
// you can use javap -s command to get the description of one method
['methodName': 'name of the method', 'methodDesc': 'method description', 'adapter': {
// the adapter is a closure
['methodName': 'the name of the method', 'methodDesc': 'javap -s to get the description', 'adapter': {
//the below args cannot be changed, to copy them entirely with nothing changed is recommended
ClassVisitor cv, int access, String name, String desc, String signature, String[] exceptions ->
//return null to modify nothing
return null;
}]
,
['methodName': 'the name of the method2', 'methodDesc': 'javap -s to get the description', 'adapter': {
ClassVisitor cv, int access, String name, String desc, String signature, String[] exceptions ->
//return the following part to check the method byte code
return new MethodLogAdapter(cv.visitMethod(access, name, desc, signature, exceptions));
return null;
}]
],
]
,
//the latter ones are advanced cases
'*Activity' : [
//the value of classMatchType can either be one of the three: all,regEx,wildcard
//default value is all
'classMatchType': 'wildcard',
'modifyMethods' : [
//methodMatchType会同时对methodName和methodDesc的匹配生效
//methodDesc设置为空代表对methodDesc不进行限制
['methodName': 'on**', 'methodMatchType': 'wildcard', 'methodDesc': null, 'adapter': {
ClassVisitor cv, int access, String name, String desc, String signature, String[] exceptions ->
MethodVisitor methodVisitor = cv.visitMethod(access, name, desc, signature, exceptions);
MethodVisitor adapter = new MethodLogAdapter(methodVisitor) {
@Override
void visitCode() {
super.visitCode();
methodVisitor.visitLdcInsn(desc);
methodVisitor.visitLdcInsn(name);
methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "bruce/com/testhibeaver/MainActivity", "hookXM", "(Ljava/lang/Object;Ljava/lang/Object;)V");
}
}
return adapter;
}]
]
]
,
'.*D[a-zA-Z]*Receiver' : [
'classMatchType': 'regEx',
'modifyMethods' : [
['methodName': 'on**', 'methodMatchType': 'wildcard', 'methodDesc': null, 'adapter': {
ClassVisitor cv, int access, String name, String desc, String signature, String[] exceptions ->
MethodVisitor methodVisitor = cv.visitMethod(access, name, desc, signature, exceptions);
MethodVisitor adapter = new MethodLogAdapter(methodVisitor) {
@Override
void visitCode() {
super.visitCode();
methodVisitor.visitLdcInsn(desc);
methodVisitor.visitLdcInsn(name);
methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "bruce/com/testhibeaver/MainActivity", "hookXM", "(Ljava/lang/Object;Ljava/lang/Object;)V");
}
}
return adapter;
}]
]
]
]
}

You can also see the content above in the build log outputs.

There is also a demo showing how to use it. You can either get it through git submodule and add a settings.gradle file to include the module, or get it by checking out [hiBeaverDemo](https://github.com/BryanSharp/hiBeaverDemo).

This plugin is also uploaded to jitpack. You can use jitpack version by adding the following code to your buildScripts:

classpath 'com.github.BryanSharp:hibeaver:1.2.1'

using jitpack version, its Maven Repo is needed, add this as well:

maven { url 'https://jitpack.io' }

Hope you can enjoy it! Any comment and suggestion is welcomed.
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ public class HiBeaverParams {
boolean watchTimeConsume = false
boolean keepQuiet = false
boolean showHelp = true
Map<String, List<Map<String, Object>>> modifyMatchMaps = [:]
Map<String, Object> modifyMatchMaps = [:]
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ import com.android.annotations.Nullable
import com.android.build.api.transform.*
import com.android.build.gradle.AppExtension
import com.android.build.gradle.internal.pipeline.TransformManager
import com.bryansharp.gradle.hibeaver.utils.Const
import com.bryansharp.gradle.hibeaver.utils.DataHelper
import com.bryansharp.gradle.hibeaver.utils.Log
import com.bryansharp.gradle.hibeaver.utils.ModifyClassUtil
import com.bryansharp.gradle.hibeaver.utils.Util
import groovy.io.FileType
import org.apache.commons.codec.digest.DigestUtils
import org.apache.commons.io.FileUtils
Expand All @@ -27,8 +29,9 @@ import java.util.zip.ZipEntry
* introduction:
*/
public class InjectTransform extends Transform {

static AppExtension android
static HashSet<String> targetClasses = [];
static Map<String, Integer> targetClasses = [:];
private static Project project;

public InjectTransform(Project project) {
Expand Down Expand Up @@ -66,13 +69,25 @@ public class InjectTransform extends Transform {
android = project.extensions.getByType(AppExtension)
// String flavorAndBuildType = context.name.split("For")[1]
// Log.info("flavorAndBuildType ${flavorAndBuildType}")
targetClasses = [];
Map<String, List<Map<String, Object>>> modifyMatchMaps = project.hiBeaver.modifyMatchMaps;
targetClasses = [:];
Map<String, Object> modifyMatchMaps = project.hiBeaver.modifyMatchMaps;
if (modifyMatchMaps != null) {
targetClasses.addAll(modifyMatchMaps.keySet());
def set = modifyMatchMaps.entrySet();
for (Map.Entry<String, Map<String, Object>> entry : set) {
def value = entry.getValue()
if (value) {
int type;
if (value instanceof Map) {
type = Util.typeString2Int(value.get(Const.KEY_CLASSMATCHTYPE));
} else {
type = Const.MT_FULL;
}
targetClasses.put(entry.getKey(), type)
}
}
}
/**
* 获取所有依赖的classPaths
* 获取所有依赖的classPaths,仅做备用
*/
def classPaths = []
String buildTypes
Expand Down Expand Up @@ -155,6 +170,7 @@ public class InjectTransform extends Transform {
}
}


private static void saveModifiedJarForCheck(File optJar) {
File dir = DataHelper.ext.hiBeaverDir;
File checkJarFile = new File(dir, optJar.getName());
Expand All @@ -164,11 +180,33 @@ public class InjectTransform extends Transform {
FileUtils.copyFile(optJar, checkJarFile);
}

static boolean shouldModifyClass(String className) {
static String shouldModifyClass(String className) {
if (project.hiBeaver.enableModify) {
return targetClasses.contains(className)
def set = targetClasses.entrySet();
for (Map.Entry<String, Integer> entry : set) {
def mt = entry.getValue();
String key = entry.getKey()
switch (mt) {
case Const.MT_FULL:
if (className.equals(key)) {
return key;
}
break;
case Const.MT_REGEX:
if (Util.regMatch(key, className)) {
return key;
}
break;
case Const.MT_WILDCARD:
if (Util.wildcardMatch(key, className)) {
return key;
}
break;
}
}
return null;
} else {
return false;
return null;
}
}

Expand All @@ -187,7 +225,7 @@ public class InjectTransform extends Transform {
* 读取原jar
*/
def file = new JarFile(jarFile);
def modifyMatchMaps = project.hiBeaver.modifyMatchMaps
Map<String, Object> modifyMatchMaps = project.hiBeaver.modifyMatchMaps
Enumeration enumeration = file.entries();
while (enumeration.hasMoreElements()) {
JarEntry jarEntry = (JarEntry) enumeration.nextElement();
Expand All @@ -204,8 +242,9 @@ public class InjectTransform extends Transform {
byte[] sourceClassBytes = IOUtils.toByteArray(inputStream);
if (entryName.endsWith(".class")) {
className = path2Classname(entryName)
if (modifyMatchMaps != null && shouldModifyClass(className)) {
modifiedClassBytes = ModifyClassUtil.modifyClasses(className, sourceClassBytes, modifyMatchMaps.get(className));
String key = shouldModifyClass(className)
if (modifyMatchMaps != null && key != null) {
modifiedClassBytes = ModifyClassUtil.modifyClasses(className, sourceClassBytes, modifyMatchMaps.get(key));
}
}
if (modifiedClassBytes == null) {
Expand All @@ -231,10 +270,11 @@ public class InjectTransform extends Transform {
File modified;
try {
String className = path2Classname(classFile.absolutePath.replace(dir.absolutePath + File.separator, ""));
def modifyMatchMaps = project.hiBeaver.modifyMatchMaps
Map<String, Object> modifyMatchMaps = project.hiBeaver.modifyMatchMaps
byte[] sourceClassBytes = IOUtils.toByteArray(new FileInputStream(classFile));
if (shouldModifyClass(className)) {
byte[] modifiedClassBytes = ModifyClassUtil.modifyClasses(className, sourceClassBytes, modifyMatchMaps.get(className));
String key = shouldModifyClass(className)
if (key != null) {
byte[] modifiedClassBytes = ModifyClassUtil.modifyClasses(className, sourceClassBytes, modifyMatchMaps.get(key));
if (modifiedClassBytes) {
modified = new File(tempDir, className.replace('.', '') + '.class')
if (modified.exists()) {
Expand Down Expand Up @@ -269,7 +309,7 @@ public class InjectTransform extends Transform {
String className
if (entryName.endsWith(".class")) {
className = entryName.replace("/", ".").replace(".class", "")
if (shouldModifyClass(className)) {
if (shouldModifyClass(className) != null) {
modified = true;
}
}
Expand Down
20 changes: 20 additions & 0 deletions src/main/groovy/com/bryansharp/gradle/hibeaver/utils/Const.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.bryansharp.gradle.hibeaver.utils;

/**
* Created by bsp on 17/3/6.
*/

public interface Const {
int MT_FULL = 0;
int MT_WILDCARD = 1;
int MT_REGEX = 2;
String KEY_CLASSMATCHTYPE="classMatchType";
String KEY_MODIFYMETHODS="modifyMethods";
String KEY_METHODNAME="methodName";
String KEY_METHODMATCHTYPE="methodMatchType";
String KEY_METHODDESC="methodDesc";
String KEY_ADAPTER="adapter";
String VALUE_WILDCARD="wildcard";
String VALUE_REGEX="regEx";
String VALUE_ALL="all";
}
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,8 @@ public class Log {
map.entrySet().each {
entry ->
if ((entry.getKey().intValue() & access) > 0) {
builder.append('|' + entry.getValue() + '|');
//此处如果使用|作为分隔符会导致编译报错 因此改用斜杠
builder.append('\\' + entry.getValue() + '/ ');
}
}
return builder.toString();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,35 @@ import org.objectweb.asm.*
public class ModifyClassUtil {

public
static byte[] modifyClasses(String className, byte[] srcByteCode, List<Map<String, Object>> methodMatchMaps) {
static byte[] modifyClasses(String className, byte[] srcByteCode, Object container) {
List<Map<String, Object>> methodMatchMaps = getList(container);
byte[] classBytesCode = null;
try {
Log.info("====start modifying ${className}====");
classBytesCode = modifyClass(srcByteCode, methodMatchMaps);
Log.info("====revisit modified ${className}====");
onlyVisitClassMethod(classBytesCode, methodMatchMaps);
Log.info("====finish modifying ${className}====");
return classBytesCode;
} catch (Exception e) {
e.printStackTrace();
if (methodMatchMaps) {
try {
Log.info("====start modifying ${className}====");
classBytesCode = modifyClass(srcByteCode, methodMatchMaps);
Log.info("====revisit modified ${className}====");
onlyVisitClassMethod(classBytesCode, methodMatchMaps);
Log.info("====finish modifying ${className}====");
return classBytesCode;
} catch (Exception e) {
e.printStackTrace();
}
}
if (classBytesCode == null) {
classBytesCode = srcByteCode;
}
return classBytesCode;
}

static List<Map<String, Object>> getList(Object container) {
if (container instanceof List) {
return container;
} else if (container instanceof Map) {
return (List<Map<String, Object>>) container.get(Const.KEY_MODIFYMETHODS);
}
return null;
}

private
static byte[] modifyClass(byte[] srcClass, List<Map<String, Object>> modifyMatchMaps) throws IOException {
Expand Down Expand Up @@ -120,13 +131,15 @@ public class ModifyClassUtil {
}
methodMatchMaps.each {
Map<String, Object> map ->
String metName = map.get('methodName');
String methodDesc = map.get('methodDesc');
if (name.equals(metName)) {
Closure visit = map.get('adapter');
String metName = map.get(Const.KEY_METHODNAME);
String metMatchType = map.get(Const.KEY_METHODMATCHTYPE);
String methodDesc = map.get(Const.KEY_METHODDESC);
if (Util.isPatternMatch(metName, metMatchType, name)) {
Closure visit = map.get(Const.KEY_ADAPTER);
if (visit != null) {
//methodDesc 不设置,为空,即代表对methodDesc不限制
if (methodDesc != null) {
if (methodDesc.equals(desc)) {
if (Util.isPatternMatch(methodDesc, metMatchType, desc)) {
if (onlyVisit) {
myMv = new MethodLogAdapter(cv.visitMethod(access, name, desc, signature, exceptions));
} else {
Expand Down
Loading

0 comments on commit 64e6fbc

Please sign in to comment.