A library to help other librairies getting rid of boiler plate via byte code manipulation. Works on Android too.
AfterBurner can be used to inject byte code, in an easy way (via javassist), into a given class and target method. The code is added via byte code manipulation after javac has compiled a class.
Mutliple plugins can be used to trigger AfterBurner on maven and gradle :
An annotation based equivalent of AfterBurner is available on GitHub : Mimic.
Let say we a class A
:
public class A {
private int foo;
public void bar() {}
public void foo() {
bar();
}
}
We can change the method foo()
, for instance to change the value of the member foo
, right after a call to bar()
:
InsertableMethod.Builder builder = new InsertableMethod.Builder( new AfterBurner() );
CtClass classToInsertInto = ClassPool.getDefaultPool().get(A.class.getName());
String targetMethod = "foo";
String insertionAfterMethod = "bar";
String fullMethod = "public void foo() { this.foo = 2; }";
String body = "this.foo = 2;";
builder
.insertIntoClass(classToInsertInto)
.inMethodIfExists(targetMethod)
.afterACallTo(insertionAfterMethod)
.withBody(body)
.elseCreateMethodIfNotExists(fullMethod)
.doIt();
This will result in modifying the class A
as if it had been written :
public class A {
private int foo;
public void bar() {}
public void foo() {
bar();
//ADDED by AfterBurner
this.foo = 2;
}
}
The fullMethod
attribute of the builder is used if class A
doesn't have a method foo()
, otherwise, the body
attribute of the builder is compiled and injected into the method foo()
right after a call to method bar()
.
The InsertableMethod.Builder
is used to provide a "fluent API/DSL" to AfterBurner. But it is also possible to use AfterBurner in a more verbose way. See below
afterBurner.addOrInsertMethod(new InsertableMethod(ClassPool.getDefaultPool().get("A")) {
@Override
public String getFullMethod() throws AfterBurnerImpossibleException {
return "public void foo() { foo = 2; }";
}
@Override
public String getBody() throws AfterBurnerImpossibleException {
return "foo = 2;";
}
@Override
public String getTargetMethodName() throws AfterBurnerImpossibleException {
return "foo";
}
@Override
public String getInsertionAfterMethod() {
return "bar";
}
});
there is even a small trick you can use to create a full method and recycle its body :
afterBurner.addOrInsertMethod(new InsertableMethod(ClassPool.getDefaultPool().get("A")) {
@Override
public String getFullMethod() throws AfterBurnerImpossibleException {
return "public void foo() { ___BODY___ }";
}
...
});
the token ___BODY___
will be replaced by the result of getBody()
.
Let's say you got an activity class ActivityA
:
public class ActivityA extends Activity {
}
if you want to log "HelloWorld" in its onCreate
method, just do (with a builder) :
builder
.insertIntoClass(ActivityA.class)
.afterOverrideMethod("onCreate")
.withBody("System.out.println(\"Hello World\");")
.doIt();
AfterBurner is a simple byte code weaving library. To create powerful byte code weaving gradle plugins based on AfterBurner, use :
A more detailed example, using a gradle build can be found at :
If you want to combine an annotation with byte code insertion of your favorite library, to avoid boiler plate, use AfterBurner.
The AfterBurner logo is a courtesy of Hitoshi Mitani.