JARファイルからresources内のディレクトリをコピー

はじめに

実行可能なJARファイル(Java 8)で src/main/resources 以下に格納したディレクトリを外部のディレクトリにコピーしたかったので方法を調べました。
対象がファイルであれば java.lang.ClassLoader.getResourceAsStream()InputStream を取得して処理すれば良いです。

やりたいこと

src/main/resources/my_resource_dir というパスのディレクトリをコピーしたいとします。

src
├── main
│  ├── resources
│  │  └── my_resource_dir
│  │     └── foo
│  │     └── bar
│  │     └── ...

うまくいかない方法

IDEから実行した場合には以下のコードは期待通りに動作します。
しかし、JARファイルから実行すると IllegalArgumentException 例外がスローされます。これはファイルシステム上にコピーするリソースが存在しないためです。JARは単一のファイルなので、ファイルの一部である my_resource_dir はストリームとして扱う必要があります。

String srcDirName = "my_resource_dir"; // コピーしたいリソース
File destDir = new File("/path/to/dest"); // コピー先のディレクトリ
File resource = new File(MyClass.class.getClassLoader().getResource(srcDirName).toURI());
FileUtils.copyDirectory(resource, destDir);
$ java -jar your-jar-with-dependencies.jar 
java.lang.IllegalArgumentException: URI is not hierarchical

うまくいく方法

リンク先の copyResourcesToDirectory に答えがありました。

www.java2s.com

実行環境の判定(JARか否か)は以下のコードで実現できます。
以下のリンク先を参考にしました。

final File jarFile = new File(getClass().getProtectionDomain().getCodeSource().getLocation().getPath());
if (jarFile.isFile()) {
    // JARの場合
} else {
    // IDEの場合
}

stackoverflow.com

これで実行方法によらず resources 以下に管理するディレクトリをコピーするコードが出来上がりました。
srcDirName に与えたディレクトリ名 + / から始まるJARのエントリ(ファイルのみ)から InputStream を取得し、一つずつコピーしていってます。

String srcDirName = "my_resource_dir"; // コピーしたいリソース
File destDir = new File("/path/to/dest"); // コピー先のディレクトリ

final File jarFile = new File(MyClass.class.getProtectionDomain().getCodeSource().getLocation().getPath());
if (jarFile.isFile()) {
    // JARで実行する場合
    final JarFile jar = new JarFile(jarFile);
    for (Enumeration<JarEntry> entries = jar.entries(); entries.hasMoreElements();) {
        JarEntry entry = entries.nextElement();
        if (entry.getName().startsWith(srcDirName + "/") && !entry.isDirectory()) {
            File dest = new File(destDir, entry.getName().substring(srcDirName.length() + 1));
            File parent = dest.getParentFile();
            if (parent != null) {
                parent.mkdirs();
            }
            FileOutputStream out = new FileOutputStream(dest);
            InputStream in = jar.getInputStream(entry);
            try {
                byte[] buffer = new byte[8 * 1024];
                int s = 0;
                while ((s = in.read(buffer)) > 0) {
                    out.write(buffer, 0, s);
                }
            } finally {
                in.close();
                out.close();
            }
        }
    }
    jar.close();
} else {
    // IDEで実行する場合
    final File resource = new File(MyClass.class.getClassLoader().getResource(srcDirName).toURI());
    FileUtils.copyDirectory(resource, destDir);
}