argius note

プログラミング関連

参照可能なクラスのリストを取得したい

「Java1.4&ライブラリ非依存」という条件で。そうでなければ、「Java クラス 検索 OR 一覧」あたりで検索するといくつか見つかります。BeanShellを使うのは、試していませんが楽そうですね。


Java1.4で大きなライブラリを使いたくない場合は、こんなのは如何でしょうか。

final List classes = new ArrayList();
String[] paths = System.getProperty("java.class.path").split(File.pathSeparator);
for (int i = 0; i < paths.length; i++) {
    String path = paths[i];
    ClassFinder finder = new ClassFinder(path) {

        public void filter(Class c) {
            if (Map.class.isAssignableFrom(c)) {
                classes.add(c);
            }
        }

        public void fail(Object o, Throwable cause) {
            System.err.println("warn: " + cause);
        }

    };
    // クラスローダがシステムクラスローダ以外の場合は setClassLoaderを使う
    finder.setFailMode(true);
    finder.find(new File(path));
}

この例は、アプリケーションで追加しているクラスパスの中から、Mapインタフェースを実装しているクラスを検索してリストに格納します。
全てのクラスをリストなどに格納してしまうとメモリを大量に消費するので、格納しないで処理できるように、コールバック方式にしています。


ClassFinderの実装。

  • (11/28) FailModeで例外を渡すように修正
  • (08/31) 拡張子判定部分を修正
  • (08/29) 新規作成
/**
 * クラス検索。
 */
class ClassFinder {

    private String rootPath;
    private ClassLoader classLoader;
    private boolean failMode;

    /**
     * ClassFinderの生成。
     * @param rootPath パス
     */
    public ClassFinder(String rootPath) {
        this.rootPath = normalizePath(rootPath);
        this.classLoader = ClassLoader.getSystemClassLoader();
    }

    /**
     * クラスローダの設定。
     * @param classLoader クラスローダ
     */
    public void setClassLoader(ClassLoader classLoader) {
        this.classLoader = classLoader;
    }

    /**
     * "FailMode"の設定。
     * FailModeを有効にした場合、例外をスローせずに <code>fail(Object)</code> を呼び出す。
     * @param failMode FailModeを有効にする場合は <code>true</code>
     */
    public void setFailMode(boolean failMode) {
        this.failMode = failMode;
    }

    /**
     * クラスを検索する。
     * @param file 検索を開始するファイルまたはディレクトリ
     */
    public final void find(File file) {
        try {
            String name = file.getName();
            if (file.isDirectory()) {
                File[] files = file.listFiles();
                for (int i = 0; i < files.length; i++) {
                    find(files[i]);
                }
            } else if (name.matches("(?i).+\\.class")) {
                try {
                    filter(resolveClass(file.getCanonicalPath()));
                } catch (Throwable ex) {
                    if (failMode) {
                        fail(file, ex);
                    } else {
                        throw new RuntimeException(ex);
                    }
                }
            } else if (name.matches("(?i).+\\.(jar|zip)")) {
                find(new ZipFile(file));
            }
        } catch (IOException ex) {
            if (failMode) {
                fail(file, ex);
            } else {
                throw new RuntimeException(ex);
            }
        }
    }

    /**
     * クラスを検索する。
     * @param zipFile 検索するZIP形式ファイル(Jarファイルを含む)
     */
    public final void find(ZipFile zipFile) {
        Enumeration en = zipFile.entries();
        while (en.hasMoreElements()) {
            ZipEntry entry = (ZipEntry)en.nextElement();
            String name = entry.getName();
            if (name.matches("(?i).+\\.class")) {
                try {
                    filter(resolveClass(name));
                } catch (Throwable ex) {
                    if (failMode) {
                        fail(name, ex);
                    } else {
                        throw new RuntimeException(ex);
                    }
                }
            }
        }
    }

    /**
     * オーバーライドして、クラスをフィルタする。
     * @param c クラス
     */
    public void filter(Class c) {
        // do nothing
    }

    /**
     * オーバーライドして、FailMode時の処理を行う。
     * @param object FailModeで処理されるオブジェクト
     * @param cause 失敗の原因
     */
    public void fail(Object object, Throwable cause) {
        // do nothing
    }

    /**
     * クラスを解決する。
     * ファイルシステム内で見つかったクラスを
     * クラスローダから取得する。
     * @param path ファイルシステム(ZIP内エントリ含む)のクラスファイル
     * @return Class
     * @throws Throwable クラスの解決に失敗した場合
     */
    protected final Class resolveClass(String path) throws Throwable {
        String s = normalizePath(path);
        if (s.startsWith(rootPath)) {
            s = s.substring(rootPath.length() + 1);
        }
        s = s.replaceFirst("\\.class$", "").replace('/', '.');
        return Class.forName(s, false, classLoader);
    }

    /**
     * パスを正規化する。
     * @param path パス
     * @return 正規化されたパス
     */
    private static String normalizePath(String path) {
        return path.replaceAll("\\\\", "/");
    }

}