cglibを使用しているプログラムでdjUnitを使うための適当パッチ

Hibernateは内部でcglibを用いています。
SpringFrameworkもクラスに対してAOPを適用しようとするとcglibを使おうとします。
cglibは、クラス生成を行うためにASMの1.5.3を内部的に使用しています。
1.5.3は二世代前のバージョンなのですがこれまではさしたる不備も感じなかったので無視していました。


今度のお仕事はdjUnitを使うことになりました。
djUnitは、テスト実行時にコードカバレッジも一緒にとってくれる、JUnit使いにとってはういヤツなのです。
djUnitは、cglibなんてのは使わずにASMを直接触ってます。
バージョンは最新の3.xです。


バージョンがあってないですね。2世代も。。。
つまり、Hibernateとか、SpringAOPを使ってるとdjUnitは使えないんですね。
こいつはやばいぜ!

という訳で、超強引な方法ですが、cglibにパッチを当てることにします。
動作確認をした際の主要なライブラリのバージョンはこんな感じ

Eclipse 3.2.2
djUnit 0.8.2
hibernate 3.1.3
SpringFramework 1.2.6
cglib(ベース) 2.1.3(Hibernateにくっついてるヤツで可)
asm 3.0

以下、怒涛のようにパッチをさらします。ちなみに、asm-3.0.jarではなく、asm-all-3.0.jarを利用しないとコンパイルが通らないのでご用心くださいませ。
また、ソースコードの行数は、2.1.3の行数をベースにしています。

net.sf.cglib.core.AbstractClassGenerator

:
032:abstract public class AbstractClassGenerator
:
186:    protected Object create(Object key) {
:
209:                            try {
210:                                gen = loader.loadClass(getClassName());
211:                            } catch (ClassNotFoundException e) {
212:                                // ignore
213:                            } catch(Error e) {  //1
214:                            	//ignore
215:                            }
:
  1. djUnitで用意されているClassLoaderは、loadClassに失敗した際にClassNotFoundExceptionではなく、Errorを送出するので無視しないと動作しないのであえてキャッチ

net.sf.cglib.core.ClassEmitter

:
025:public class ClassEmitter extends ClassAdapter {
:
030:    private MethodVisitor rawStaticInit;  //1
:
061:    public void begin_class(int version, final int access, String className, final Type superType, final Type interfaces, String sourceFile) {
:
077:        cv.visit(version,
078:                 access,
079:                 classInfo.getType().getInternalName(),
080:                 null,
081:                 classInfo.getSuperType().getInternalName(),
082:                 TypeUtils.toInternalNames(interfaces)); //2
:
134:    public CodeEmitter begin_method(int access, Signature sig, Type exceptions, Attribute attrs) {
135:        if (classInfo == null)
136:            throw new IllegalStateException("classInfo is null! " + this);
137:        MethodVisitor v = cv.visitMethod(access,
138:                                       sig.getName(),
139:                                       sig.getDescriptor(),
140:                                       null, 
141:                                       TypeUtils.toInternalNames(exceptions));  //3
:
144:            MethodVisitor wrapped = new MethodAdapter(v) {  //4
:
177:    public FieldVisitor declare_field(int access, String name, Type type, Object value, Attribute attrs) {
:
186:            cv.visitField(access, name, type.getDescriptor(), null, value);  //5
:
243:    public void visit(int version, int access, String name, String signature, String superName, String interfaces) {
244:        begin_class(version,
245:                    access,
246:                    name.replace('/', '.'),
247:                    TypeUtils.fromInternalName(superName),
248:                    TypeUtils.fromInternalNames(interfaces),
249:                    null);
250:    }  //6
:
262:    public MethodVisitor visitMethod(int access, String name, String desc, String exceptions, Attribute attrs) {  //7
:
  1. org.objectweb.asm.CodeVisitorは廃止されて、org.objectweb.asm.MethodVisitorに置き換えられているのでタイプを変更
  2. org.objectweb.asm.ClassVisitor#visitメソッドの引数変更に対応。signatureはよーわからんのでとりあえずnull
  3. org.objectweb.asm.ClassVisitor#visitMethodの引数変更に対応。signatureはよーわからんのでnull。逆にattrsを渡せていないのが気色悪いのですが、とりあえず全部のテストがパスしているのでまずは気にしないことにする
  4. org.objectweb.asm.CodeVisitorは廃止されて、org.objectweb.asm.MethodVisitorに置き換えられているのでタイプを変更。あわせてorg.objectweb.asm.CodeAdapterも廃止されているので、org.objectweb.asm.MethodAdapterに置き換え
  5. org.objectweb.asm.ClassVisitor#visitFieldの引数変更に対応。signatureはよーわからんのでnull。逆にattrsを渡せていないのが気色悪いのですが、とりあえず全部のテストがパスしているのでまずは気にしないことにする
  6. org.objectweb.asm.ClassVisitor#visitメソッドの引数変更に対応。sourceFileがなくなったのでbegin_classにはnullを渡す。本当は、begin_classから、sourceFlileを取り除くほうがよいのでしょうが影響範囲が広がるのでまずは放置
  7. org.objectweb.asm.CodeVisitorは廃止されて、org.objectweb.asm.MethodVisitorに置き換えられているので戻り値のタイプを変更

あと、visitFieldメソッドは本当は、FieldVisitorを返却するように変更されているので厳密には引数もあわせて修正しなければいけないと思われるのですが、現時点でテストは全部パスしているので、かなぁり気持ち悪いですが無視します

net.sf.cglib.core.ClassNameReader

:
023:public class ClassNameReader {
:
036:    public static String getClassInfo(ClassReader r) {
:
039:            r.accept(new ClassAdapter(null) {
040:                public void visit(int version,
041:                                  int access,
042:                                  String name,
043:                                  String signature,
044:                                  String superName,
045:                                  String interfaces) {  /1
:
  1. org.objectweb.asm.ClassAdapter#visitメソッドの引数変更に対応

net.sf.cglib.core.CodeEmitter

:
025:public class CodeEmitter extends RemappingCodeVisitor {
:
105:    CodeEmitter(ClassEmitter ce, MethodVisitor cv, int access, Signature sig, Type[] exceptionTypes) {  //1
:
  1. org.objectweb.asm.CodeVisitorは廃止されて、org.objectweb.asm.MethodVisitorに置き換えられているので引数タイプを変更

net.sf.cglib.core.Constants

:
024:public interface Constants extends org.objectweb.asm.Opcodes {  //1
:
  1. org.objectweb.asm.Constantsは廃止されて、org.objectweb.asm.Opcodesに置き換えられているので実装インタフェースタイプを変更

net.sf.cglib.core.DebuggingClassWriter

:
024:public class DebuggingClassWriter extends ClassWriter {
:
046:    public DebuggingClassWriter(boolean computeMaxs) {
047:        super((computeMaxs) ? 1 : 0);  //1
048:    }
:
053:    public DebuggingClassWriter(boolean computeMaxs, int major, int minor) {
054:        super((computeMaxs) ? 1 : 0);  //2
055:    }
056:
057:    public void visit(int version, int access, String name, String signature, String superName, String interfaces) {  //3
058:        className = name.replace('/', '.');
059:        this.superName = superName.replace('/', '.');
060:        super.visit(version, access, name, signature, superName, interfaces);  //4
061:    }
:
071:	public byte toByteArray() {
:
099:                                cr.accept(tcv, 0);  //5
:
  1. org.objectweb.asm.ClassWriterのコンストラクタの引数が変更になっているので変更
  2. org.objectweb.asm.ClassWriterのコンストラクタの引数が変更になっているので変更
  3. org.objectweb.asm.ClassWriter#visitメソッドの引数が変更になっているので変更
  4. org.objectweb.asm.ClassWriter#visitメソッドの引数が変更になっているので変更
  5. org.objectweb.asm.ClassReader#acceptメソッドの引数が変更になっているので変更

net.sf.cglib.core.RemappingCodeVisitor

:
021:public class RemappingCodeVisitor extends MethodAdapter {  //1
022:    protected MethodVisitor cv;  //2
:
049:    public RemappingCodeVisitor(MethodVisitor v, int access, Type[] args) {  //3
050:        super(v);
051:        state = new State(access, args);
052:        cv = v;  //4
053:    }
054:
055:    public RemappingCodeVisitor(RemappingCodeVisitor wrap) {
056:        super(wrap.cv);
057:        this.state = wrap.state;
058:        cv = wrap.cv;  //5
059:    }
:
099:    public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) {
100:        cv.visitLocalVariable(name, desc, signature, start, end, remap(index, 0));  //6
101:    }
:
  1. org.objectweb.asm.CodeAdapterは廃止されているので、継承元クラスをorg.objectweb.asm.MethodAdapterに置き換え
  2. org.objectweb.asm.MethodAdapterでは、protectedメンバcvが削除されていたので、影響範囲を減らすためにcvを(冗長だけど)追加
  3. org.objectweb.asm.CodeVisitorは廃止されているので、引数タイプをorg.objectweb.asm.MethodVisitorに置き換え
  4. メンバ変数cvにMethodVisitorオブジェクトをセットする
  5. メンバ変数cvにMethodVisitorオブジェクトをセットする
  6. org.objectweb.asm.MethodAdapter#visitLocalVariableメソッドの引数変更に対応

以上、これだけ修正すればとりあえず、Hibernateとか、SpringAOPとかはなんとか動いてくれそうです。
駄目な場合はコメントくだせぇ。