Javaのクラス内が依存しているフィールド、メソッドの一覧を表示する

世の中には javassist というとても便利なライブラリがある。通常は、バイトコードの書き換えに利用することが多いが、バイトコードを調べることで依存関係やメタ情報を取得することもできる。

単に依存しているクラスを取得するだけであれば、CtClass#getRefClasses() を使って簡単に取得できるが、フィールドやメソッドとなるとそうもいかない。次のようにバイトコードをひとつひとつ解析する必要がある。

ClassPool cp = ClassPool.getDefault();
CtClass cc = cp.get(クラス名);

for (CtBehavior cb : cc.getDeclaredBehaviors()) {
    MethodInfo info = cb.getMethodInfo2();
    ConstPool pool = info.getConstPool();
    CodeAttribute code = info.getCodeAttribute();
    if (code == null)
        return;

    CodeIterator i = code.iterator();
    while (i.hasNext()) {
        int pos = i.next();
        int opecode = i.byteAt(pos);

        switch (opecode) {
        case Opcode.NEW:
        case Opcode.ANEWARRAY:
        case Opcode.MULTIANEWARRAY: {
            int index = i.u16bitAt(pos + 1);
            
            System.out.print(cc.getName());
            System.out.print('\t');
            System.out.print(cb.getLongName());
            System.out.print('\t');
            System.out.print("new");
            System.out.print('\t');
            
            String className = pool.getClassInfo(index);
            if (opecode == Opcode.ANEWARRAY || opecode == Opcode.MULTIANEWARRAY) {
                className = "new " + className + "[]";
            }
            
            System.out.print(className);
            System.out.println();
            break;
        }
        case Opcode.NEWARRAY: {
            System.out.print(cc.getName());
            System.out.print('\t');
            System.out.print(cb.getLongName());
            System.out.print('\t');
            System.out.print("new");
            System.out.print('\t');
            
            String className = null;
            switch (i.byteAt(pos + 1)) {
            case Opcode.T_BOOLEAN: className = "new boolean[]"; break;
            case Opcode.T_CHAR: className = "new char[]"; break;
            case Opcode.T_BYTE: className = "new byte[]"; break;
            case Opcode.T_SHORT: className = "new short[]"; break;
            case Opcode.T_INT: className = "new int[]"; break;
            case Opcode.T_LONG: className = "new long[]"; break;
            case Opcode.T_FLOAT: className = "new float[]"; break;
            case Opcode.T_DOUBLE: className = "new double[]"; break;
            }
            
            System.out.print(className);
            System.out.println();
            break;
        }
        case Opcode.CHECKCAST: {
            int index = i.u16bitAt(pos + 1);
            
            System.out.print(cc.getName());
            System.out.print('\t');
            System.out.print(cb.getLongName());
            System.out.print('\t');
            System.out.print("cast");
            System.out.print('\t');
            
            String className = "(" + pool.getClassInfo(index) + ")";
            
            System.out.print(className);
            System.out.println();
            break;
        }
        case Opcode.GETSTATIC:
        case Opcode.PUTSTATIC:
        case Opcode.GETFIELD:
        case Opcode.PUTFIELD: {
            int index = i.u16bitAt(pos + 1);
            
            System.out.print(cc.getName());
            System.out.print('\t');
            System.out.print(cb.getLongName());
            System.out.print('\t');
            System.out.print("field");
            
            String className = pool.getFieldrefClassName(index);
            String fieldName = pool.getFieldrefName(index);
            String desc = pool.getFieldrefType(index);
            String typeName = Descriptor.toClassName(desc);
            
            System.out.print('\t');
            System.out.print(typeName);
            System.out.print(' ');
            System.out.print(className);
            System.out.print('.');
            System.out.print(fieldName);
            System.out.println();
            
            break;
        }
        case Opcode.INVOKEVIRTUAL:
        case Opcode.INVOKESPECIAL:
        case Opcode.INVOKESTATIC:
        case Opcode.INVOKEINTERFACE: {
            int index = i.u16bitAt(pos + 1);
            
            System.out.print(cc.getName());
            System.out.print('\t');
            System.out.print(cb.getLongName());
            System.out.print('\t');
            System.out.print("method");
            
            String className;
            if (opecode == Opcode.INVOKEINTERFACE) {
                className = pool.getInterfaceMethodrefClassName(index);
            } else {
                className = pool.getMethodrefClassName(index);
            }
            
            String methodName;
            if (opecode == Opcode.INVOKEINTERFACE) {
                methodName = pool.getInterfaceMethodrefName(index);
            } else {
                methodName = pool.getMethodrefName(index);
            }
            
            String desc;
            if (opecode == Opcode.INVOKEINTERFACE) {
                desc = pool.getInterfaceMethodrefType(index);
            } else {
                desc = pool.getMethodrefType(index);
            }
            String ret = null;
            List<String> params = new ArrayList<String>();
            if (desc.charAt(0) == '(') {
                int start = 1;
                boolean inClass = false;
                for (int j = start; j < desc.length(); j++) {
                    char c = desc.charAt(j);
                    if (inClass) {
                        if (c == ';') {
                            params.add(Descriptor.toClassName(desc.substring(start, j + 1)));
                            start = j + 1;
                            inClass = false;
                        }
                    } else if (c == ')') {
                        ret = Descriptor.toClassName(desc.substring(j + 1));
                        break;
                    } else if (c == 'L') {
                        inClass = true;
                    } else if (c != '[') {
                        params.add(Descriptor.toClassName(desc.substring(start, j + 1)));
                        start = j + 1;
                    }
                }
            } else {
                ret = Descriptor.toClassName(desc);
            }
            
            System.out.print('\t');
            System.out.print(ret);
            System.out.print(' ');
            System.out.print(className);
            System.out.print('.');
            System.out.print(methodName);
            System.out.print('(');
            for (int j = 0; j < params.size(); j++) {
                if (j > 0) System.out.print(',');
                System.out.print(params.get(j));
            }
            System.out.println(')');
            break;
        }
        }
    }
}