Javassist: ClassPoolの親子関係とgetAndRename (2)
Javassistのソースを追っていったら、CtClassの取得まではうまく動作していたことが判明。問題(?)はCtClassを取得した後、所属ClassPoolの変更、およびクラス名の変更を行っている箇所にあった。
... public CtClass getAndRename(String orgName, String newName) throws NotFoundException { CtClass clazz = get0(orgName, false); if (clazz == null) throw new NotFoundException(orgName); if (clazz instanceof CtClassType) ((CtClassType)clazz).setClassPool(this); // ← コレ clazz.setName(newName); // indirectly calls // classNameChanged() in this class return clazz; } public CtClass get(String classname) throws NotFoundException { CtClass clazz; if (classname == null) clazz = null; else clazz = get0(classname, true); if (clazz == null) throw new NotFoundException(classname); else { clazz.incGetCounter(); return clazz; } }
CtClassType#setName メソッドは、自身のフィールド classPool が指すClassPoolの検索パスに、そのクラスが存在していなければ失敗してしまうようだ (正確には CtClassType#getClassFile2 )。get系メソッドと異なり、親のClassPoolを参照したりしてくれない。であるにも関わらず、ClassPool を差し替えている故の問題(?)だということか。まあ、一番の原因は setName だろうけれど。つうかこれがまさに作者様の意図どおりの動作だったりするのかな?
なお、下記のようなコードで、無理やりClassPoolを差し替えてやると CtClassType#setName の当該動作を再現させられる。
import javassist.*; import java.lang.reflect.*; public class TestJavassist { public static void main(String[] args) throws Exception { ClassPool parent = new ClassPool(true); ClassPool child = new ClassPool(parent); // 取得した時点で t.classPool は parent になっている CtClass t = child.get(InnerClass.class.getName()); // t.classPoolを 無理やり child に再設定 Method setClassPool = t.getClass().getDeclaredMethod("setClassPool",new Class[]{ClassPool.class}); setClassPool.setAccessible(true); setClassPool.invoke(t, new Object[]{child}); t.setName("hoge"); } }
とりあえず、子ClassPoolを作った時は、常に ClassPool#appendSystemPath を呼んでデフォルトPATHを設定しておくというか、そんなに複雑なClassPoolの親子関係はやめておこうぜぃということで、ひとつ。でも自前でPATHを追加設定しないと駄目っていうシチュエーションだと、この制限はウザイな。