import javassist.compiler.Javac; | import javassist.compiler.Javac; | ||||
import javassist.compiler.CompileError; | import javassist.compiler.CompileError; | ||||
import javassist.expr.ExprEditor; | import javassist.expr.ExprEditor; | ||||
import javassist.tools.Callback; | |||||
/** | /** | ||||
* <code>CtBehavior</code> represents a method, a constructor, | * <code>CtBehavior</code> represents a method, a constructor, | ||||
declaringClass.checkModify(); | declaringClass.checkModify(); | ||||
} | } | ||||
/** | |||||
* Inserts callback at the beginning of the body. | |||||
* | |||||
* <p>If this object represents a constructor, | |||||
* the callback is inserted before | |||||
* a constructor in the super class or this class is called. | |||||
* Therefore, the inserted callback is subject to constraints described | |||||
* in Section 4.8.2 of The Java Virtual Machine Specification (2nd ed). | |||||
* For example, it cannot access instance fields or methods although | |||||
* it may assign a value to an instance field directly declared in this | |||||
* class. Accessing static fields and methods is allowed. | |||||
* Use <code>insertBeforeBody()</code> in <code>CtConstructor</code>. | |||||
* | |||||
* @param callback the source code representing the inserted bytecode. | |||||
* It must be a single statement or block. | |||||
* @see CtConstructor#insertBeforeBody(String) | |||||
*/ | |||||
public void insertBefore(Callback callback) | |||||
throws CannotCompileException | |||||
{ | |||||
insertBefore(callback.toString(), true); | |||||
} | |||||
/** | /** | ||||
* Inserts bytecode at the beginning of the body. | * Inserts bytecode at the beginning of the body. | ||||
* | * | ||||
} | } | ||||
} | } | ||||
/** | |||||
* Inserts callback bytecode at the end of the body. | |||||
* The callback is inserted just before every return insturction. | |||||
* It is not executed when an exception is thrown. | |||||
* | |||||
* @param callback the source code representing the inserted bytecode. | |||||
* It must be a single statement or block. | |||||
*/ | |||||
public void insertAfter(Callback callback) | |||||
throws CannotCompileException | |||||
{ | |||||
insertAfter(callback.toString(), false); | |||||
} | |||||
/** | |||||
* Inserts callback bytecode at the end of the body. | |||||
* The callback is inserted just before every return insturction. | |||||
* It is not executed when an exception is thrown. | |||||
* | |||||
* @param callback the callback source code representing the inserted bytecode. | |||||
* It must be a single statement or block. | |||||
* @param asFinally true if the inserted bytecode is executed | |||||
* not only when the control normally returns | |||||
* but also when an exception is thrown. | |||||
* If this parameter is true, the inserted code cannot | |||||
* access local variables. | |||||
*/ | |||||
public void insertAfter(Callback callback, boolean asFinally) | |||||
throws CannotCompileException | |||||
{ | |||||
insertAfter(callback.toString(), asFinally); | |||||
} | |||||
/** | /** | ||||
* Inserts bytecode at the end of the body. | * Inserts bytecode at the end of the body. | ||||
* The bytecode is inserted just before every return insturction. | * The bytecode is inserted just before every return insturction. | ||||
return insertAt(lineNum, true, src); | return insertAt(lineNum, true, src); | ||||
} | } | ||||
/** | |||||
* Inserts callback bytecode at the specified line in the body. | |||||
* It is equivalent to: | |||||
* | |||||
* <br><code>insertAt(lineNum, true, src)</code> | |||||
* | |||||
* <br>See this method as well. | |||||
* | |||||
* @param lineNum the line number. The bytecode is inserted at the | |||||
* beginning of the code at the line specified by this | |||||
* line number. | |||||
* @param callback the callback source code representing the inserted bytecode. | |||||
* It must be a single statement or block. | |||||
* @return the line number at which the callback bytecode has been inserted. | |||||
* | |||||
* @see CtBehavior#insertAt(int,boolean,String) | |||||
*/ | |||||
public int insertAt(int lineNum, Callback callback) | |||||
throws CannotCompileException | |||||
{ | |||||
return insertAt(lineNum, true, callback.toString()); | |||||
} | |||||
/** | /** | ||||
* Inserts bytecode at the specified line in the body. | * Inserts bytecode at the specified line in the body. | ||||
* | * | ||||
throw new CannotCompileException(e); | throw new CannotCompileException(e); | ||||
} | } | ||||
} | } | ||||
/** | |||||
* Inserts callback bytecode at the specified line in the body. | |||||
* | |||||
* <p>If there is not | |||||
* a statement at the specified line, the bytecode might be inserted | |||||
* at the line including the first statement after that line specified. | |||||
* For example, if there is only a closing brace at that line, the | |||||
* bytecode would be inserted at another line below. | |||||
* To know exactly where the bytecode will be inserted, call with | |||||
* <code>modify</code> set to <code>false</code>. | |||||
* | |||||
* @param lineNum the line number. The bytecode is inserted at the | |||||
* beginning of the code at the line specified by this | |||||
* line number. | |||||
* @param modify if false, this method does not insert the bytecode. | |||||
* It instead only returns the line number at which | |||||
* the bytecode would be inserted. | |||||
* @param callback the callback source code representing the inserted bytecode. | |||||
* It must be a single statement or block. | |||||
* If modify is false, the value of src can be null. | |||||
* @return the line number at which the bytecode has been inserted. | |||||
*/ | |||||
public int insertAt(int lineNum, boolean modify, Callback callback) | |||||
throws CannotCompileException | |||||
{ | |||||
return insertAt(lineNum,modify,callback.toString()); | |||||
} | |||||
} | } |
package javassist.tools; | package javassist.tools; | ||||
import javassist.CannotCompileException; | |||||
import javassist.CtBehavior; | |||||
import java.util.HashMap; | import java.util.HashMap; | ||||
import java.util.UUID; | import java.util.UUID; | ||||
/** | /** | ||||
* Creates bytecode that when executed calls back to the objects result method. | |||||
* Creates bytecode that when executed calls back to the instance's result method. | |||||
* | |||||
* Inserts callbacks in <code>CtBehaviour</code> | |||||
* | |||||
*/ | */ | ||||
public abstract class Callback { | public abstract class Callback { | ||||
public static HashMap<String, Callback> callbacks = new HashMap<String, Callback>(); | public static HashMap<String, Callback> callbacks = new HashMap<String, Callback>(); | ||||
private final String callbackCode; | |||||
private final String sourceCode; | |||||
/** | /** | ||||
* Constructs a new <code>Callback</code> object. | * Constructs a new <code>Callback</code> object. | ||||
* | * | ||||
* @param src java code that returns one or many objects. if many objects | |||||
* are returned they should be comma separated | |||||
* @param src The source code representing the inserted callback bytecode. | |||||
* Can be one or many single statements or blocks each returning one object. | |||||
* If many single statements or blocks are used they must be comma separated. | |||||
*/ | */ | ||||
public Callback(String src){ | public Callback(String src){ | ||||
String uuid = UUID.randomUUID().toString(); | String uuid = UUID.randomUUID().toString(); | ||||
callbacks.put(uuid, this); | callbacks.put(uuid, this); | ||||
callbackCode = "((javassist.tools.Callback) javassist.tools.Callback.callbacks.get(\""+uuid+"\")).result(new Object[]{"+src+"});"; | |||||
sourceCode = "((javassist.tools.Callback) javassist.tools.Callback.callbacks.get(\""+uuid+"\")).result(new Object[]{"+src+"});"; | |||||
} | } | ||||
/** | /** | ||||
* Gets called when bytecode is executed | * Gets called when bytecode is executed | ||||
* | * | ||||
* @param objects java code that returns one or many objects. if many objects | |||||
* are returned they should be comma separated | |||||
* @param objects Objects that the bytecode in callback returns | |||||
*/ | */ | ||||
public abstract void result(Object... objects); | public abstract void result(Object... objects); | ||||
@Override | @Override | ||||
public String toString(){ | public String toString(){ | ||||
return callbackCode; | |||||
return sourceCode(); | |||||
} | |||||
public String sourceCode(){ | |||||
return sourceCode; | |||||
} | |||||
/** | |||||
* Inserts callback at the beginning of the body. | |||||
* | |||||
* @param callback The callback | |||||
* | |||||
* @see CtBehavior#insertBefore(String) | |||||
*/ | |||||
public static void insertBefore(CtBehavior behavior, Callback callback) | |||||
throws CannotCompileException | |||||
{ | |||||
behavior.insertBefore(callback.toString()); | |||||
} | |||||
/** | |||||
* Inserts callback at the end of the body. | |||||
* The callback is inserted just before every return instruction. | |||||
* It is not executed when an exception is thrown. | |||||
* | |||||
* @param behavior The behaviour to insert callback in | |||||
* @param callback The callback | |||||
* | |||||
* @see CtBehavior#insertAfter(String, boolean) | |||||
*/ | |||||
public static void insertAfter(CtBehavior behavior,Callback callback) | |||||
throws CannotCompileException | |||||
{ | |||||
behavior.insertAfter(callback.toString(), false); | |||||
} | |||||
/** | |||||
* Inserts callback at the end of the body. | |||||
* The callback is inserted just before every return instruction. | |||||
* It is not executed when an exception is thrown. | |||||
* | |||||
* @param behavior The behaviour to insert callback in | |||||
* @param callback The callback representing the inserted. | |||||
* @param asFinally True if the inserted is executed | |||||
* Not only when the control normally returns | |||||
* but also when an exception is thrown. | |||||
* If this parameter is true, the inserted code cannot | |||||
* access local variables. | |||||
* | |||||
* @see CtBehavior#insertAfter(String, boolean) | |||||
*/ | |||||
public static void insertAfter(CtBehavior behavior, Callback callback, boolean asFinally) | |||||
throws CannotCompileException | |||||
{ | |||||
behavior.insertAfter(callback.toString(), asFinally); | |||||
} | |||||
/** | |||||
* Inserts callback at the specified line in the body. | |||||
* | |||||
* @param behavior The behaviour to insert callback in | |||||
* @param callback The callback representing. | |||||
* @param lineNum The line number. The callback is inserted at the | |||||
* beginning of the code at the line specified by this | |||||
* line number. | |||||
* | |||||
* @return The line number at which the callback has been inserted. | |||||
* | |||||
* @see CtBehavior#insertAt(int, String) | |||||
*/ | |||||
public static int insertAt(CtBehavior behavior, Callback callback, int lineNum) | |||||
throws CannotCompileException | |||||
{ | |||||
return behavior.insertAt(lineNum, callback.toString()); | |||||
} | } | ||||
} | } |
package javassist.tools; | |||||
import javassist.*; | |||||
import junit.framework.TestCase; | |||||
import test.javassist.tools.DummyClass; | |||||
import static javassist.tools.Callback.*; | |||||
public class CallbackTest extends TestCase { | |||||
public void testSomeCallbacks() throws Exception { | |||||
// Get class and method to change | |||||
ClassPool pool = ClassPool.getDefault(); | |||||
CtClass classToChange = pool.get("test.javassist.tools.DummyClass"); | |||||
CtMethod methodToChange = classToChange.getDeclaredMethod("dummyMethod"); | |||||
// Insert after | |||||
methodToChange.insertAfter(new Callback("Thread.currentThread(), dummyString") { | |||||
@Override | |||||
public void result(Object... objects) { | |||||
assertEquals(objects[0], Thread.currentThread()); | |||||
assertEquals(objects[1], "dummyStringValue"); | |||||
} | |||||
}.sourceCode()); | |||||
// Insert after using utility method | |||||
insertAfter(methodToChange, new Callback("Thread.currentThread(), dummyString") { | |||||
@Override | |||||
public void result(Object... objects) { | |||||
assertEquals(objects[0], Thread.currentThread()); | |||||
assertEquals(objects[1], "dummyStringValue"); | |||||
} | |||||
}); | |||||
// Change class and invoke method(); | |||||
classToChange.toClass(); | |||||
new DummyClass().dummyMethod(); | |||||
} | |||||
} |
package test.javassist.tools; | |||||
public class DummyClass { | |||||
private String dummyString = "dummyStringValue"; | |||||
public void dummyMethod(){} | |||||
} |