Instrumentation that uses a generated class
yanic May 16, 2006 7:00 PMI'm trying to instrument an existing class with code that uses a generated class. I've prepared a small example to demonstrate what I mean and show you where I got stuck.
Assume the following class exists :
public class InstrumentMe { public InstrumentMe() { super(); } public void printHelloWorld() { System.out.println("HelloWorld"); } }
The main-method in the class below generates a new class 'DynamicallyGenerated' on-the-fly and tries to instrument InstrumentMe#printHelloWorld() with a simple statement 'System.out.println(DynamicallyGenerated.class);'
import javassist.CannotCompileException; import javassist.ClassPool; import javassist.CtClass; import javassist.CtMethod; import javassist.CtNewClass; import javassist.NotFoundException; public final class AttemptGeneration { private AttemptGeneration() { super(); } public static void main (String[] ignore) throws NotFoundException, CannotCompileException { ClassPool pool=ClassPool.getDefault(); // step 1 : generate a new class String generatedName="DynamicallyGenerated"; CtClass superclass=pool.get("java.lang.Object"); CtNewClass generated = new CtNewClass(generatedName, pool, false, superclass); generated.toClass(); // (*) see below : CtClass generatedClassAgain=pool.get(generatedName); // step 2 : try to load the generated class try { Class c = Class.forName(generatedName); System.out.println("class "+c.getName()+" loaded."); } catch (ClassNotFoundException e) { System.out.println("class "+generatedName+" not found"); System.exit(1); } // step 3 : instrument an existing class to use the generated class CtClass instrumented = pool.get("InstrumentMe"); CtMethod printHelloWorld = instrumented.getDeclaredMethod("printHelloWorld"); String instrumentation = "System.out.println("+generatedName+".class);"; // .. the line below causes a CannotCompileException printHelloWorld.insertAfter(instrumentation); } }
The call to CtBehavior#insertAfter(String) in the last statement causes a CannotCompileException with the following stack trace :
Exception in thread "main" javassist.CannotCompileException: [source error] no such class: DynamicallyGenerated at javassist.CtBehavior.insertAfter(CtBehavior.java:613) at javassist.CtBehavior.insertAfter(CtBehavior.java:538) at AttemptGeneration.main(AttemptGeneration.java:40) Caused by: compile error: no such class: DynamicallyGenerated at javassist.compiler.MemberResolver.searchImports(MemberResolver.java:406) at javassist.compiler.MemberResolver.lookupClass(MemberResolver.java:382) at javassist.compiler.MemberResolver.lookupClassByJvmName(MemberResolver.java:310) at javassist.compiler.MemberResolver.resolveJvmClassName(MemberResolver.java:450) at javassist.compiler.MemberCodeGen.resolveClassName(MemberCodeGen.java:1064) at javassist.compiler.CodeGen.atClassObject(CodeGen.java:1546) at javassist.compiler.CodeGen.atExpr(CodeGen.java:1441) at javassist.compiler.ast.Expr.accept(Expr.java:67) at javassist.compiler.JvstCodeGen.atMethodArgs(JvstCodeGen.java:357) at javassist.compiler.MemberCodeGen.atMethodCallCore(MemberCodeGen.java:486) at javassist.compiler.MemberCodeGen.atCallExpr(MemberCodeGen.java:454) at javassist.compiler.JvstCodeGen.atCallExpr(JvstCodeGen.java:243) at javassist.compiler.ast.CallExpr.accept(CallExpr.java:45) at javassist.compiler.CodeGen.atStmnt(CodeGen.java:331) at javassist.compiler.ast.Stmnt.accept(Stmnt.java:49) at javassist.compiler.Javac.compileStmnt(Javac.java:558) at javassist.CtBehavior.insertAfter(CtBehavior.java:581) ... 2 more
Note that before the exception occurs, the output 'class DynamicallyGenerated loaded.' appears, which means that the generated class can be loaded by Class#forName(String).
I've stepped through some of the Javassist classes involved and noticed that the ClassPool doesn't seem to know the generated class. For example, when the statement marked with (*) is uncommented, it causes a NotFoundException with the following stack trace :
Exception in thread "main" javassist.NotFoundException: DynamicallyGenerated at javassist.ClassPool.get(ClassPool.java:389) at AttemptGeneration.main(AttemptGeneration.java:31)
The fact that the ClassPool doesn't know the generated class is a bit surprising, as its documentation mentions "...ClassPool objects hold all the CtClasses that have been created so that the consistency among modified classes can be guaranteed...".
The javassist tutorial states "...A ClassPool object is a container of CtClass objects. Once a CtClass object is created, it is recorded in a ClassPool for ever. This is because a compiler may need to access the CtClass object later when it compiles source code that refers to the class represented by that CtClass....
So my question is why the above example doesn't work and how it can be made to work.
Thanks in advance for any help!