ディレクトリ内のクラスをリフレクションを用いて列挙する

トランスレータの実装方法を模索していた時にリフレクションを用いてクラスの内部構造を書き出すという事を考えていました。
これだとメソッドの実装に手を入れるのが困難なため採用しなかったんですけど、折角なので出来たところまで書いておこうと思います。


特定のディレクトリにあるクラス(classファイル)とそのフィールドと初期値を列挙します。
./classesはクラスファイルが出力されているディレクトリ。
今回はDoja5.0のSDKで作成したアプリだったのでURLClassLoaderで必要なライブラリを追加でロードするようにしています。

File classdir = new File("./classes");
URLClassLoader loader = new URLClassLoader(
    new URL[]{ classdir.toURI().toURL(),
        new File("C:/iDKDoJa5.0/lib/profile/DoJa-5.0/classes.zip"
).toURI().toURL(),
        new File("C:/iDKDoJa5.0/lib/profile/DoJa-5.0/doja_classes.zip"
).toURI().toURL()
    }
);

for( File f : classdir.listFiles() ){
    String name = f.getName();
    int dot;
    if( (dot = name.indexOf(".")) != -1 ){
        name = name.substring(0, dot);
    }
    Class<?> listcls = Class.forName(name, true, loader);
    if( listcls.isMemberClass() ) continue;
    
    Object obj = null;
    
    try{
        obj = listcls.newInstance();
    }catch(Exception e){
        
    }
    
    if( obj == null &&
        listcls.getDeclaredConstructors().length > 0 &&
        !listcls.getDeclaredConstructors()[0].isAccessible() ){
        //まず自分のクラスの型の変数を探す。
        //次にgetInstanceをはじめpublic staticメソッドを片っ端から実行する
        //ここで自分のクラスの型の変数が初期化されればOK
        try{
            Method creater = listcls.getMethod("create");
            obj = creater.invoke(null);
            if( obj == null )
            for( Field search : listcls.getDeclaredFields() ){
                if( search.getType() == listcls ){
                    obj = search.get(null);
                    break;
                }
            }
        }catch(Exception e){
            System.err.println(e.getMessage());
        }
        if( obj == null ){
            try{
                Method creater = listcls.getMethod("getInstance");
                obj = creater.invoke(null);
                if( obj == null )
                for( Field search : listcls.getDeclaredFields() ){
                    if( search.getType() == listcls ){
                        obj = search.get(null);
                        break;
                    }
                }
            }catch(Exception e){
                System.err.println(e.getMessage());
            }                        
        }
        
        //このクラスインスタンス作れません
        if( obj == null ){
            System.out.println("クラス" + listcls.getName() +
                "はインスタンス化出来ません");
            continue;
        }
    }        
    
    System.out.println(listcls.getName());
    for( Field _f : listcls.getDeclaredFields() ){
        System.out.print(Modifier.toString(_f.getModifiers()) + " ");
        Object value;
        if( _f.isAccessible() ){
            value = _f.get(obj);
        }else{
            _f.setAccessible(true);
            value = _f.get(obj);
            _f.setAccessible(false);
        }
        System.out.print(_f.getName() + " = ");
        if( value == null )
            System.out.println("null");
        else if( value.getClass() == String.class )
            System.out.println((String)value);
        else if( value.getClass() == Integer.class )
            System.out.println((Integer)value);
        else if( value.getClass() == Float.class )
            System.out.println((Float)value);
        else if( value.getClass() == Boolean.class )
            System.out.println((Boolean)value);
        else
            System.out.println(value);
    }
}


シングルトンパターンで作成されたクラスも簡易的ですが対応しています。
フィールドの初期値を得るにはインスタンスを作成する必要があるのですが、private宣言されたコンストラクタを持つクラスでは対応できません。
シングルトンパターンの場合は多くの場合getInstance()などといった名前のメソッドでインスタンスを取得できるので、それを利用しインスタンスを作成します。


参考
JavaSE Jarファイルに含まれるクラスを列挙する - @//メモ
chamaeleonmannのはてなダイアリー
Javaリフレクションメモ(Hishidama's Java Reflection Memo)