Javaアプリ開発では、クラスファイルやリソースファイルを一つのJarにまとめてパッケージングするのが一般的です。
MavenやGradleプロジェクトであれば、 src/main/resources ディレクトリに置くのが一般的でしょうか。
時々、リソースに含めたファイルをJar外にコピーして使いたいことがあります。
(設定ファイルのテンプレートを外に出したり、配布の都合で同梱していた実行ファイルを実行したいときなど)
resources ディレクトリに配置したファイル hoge.txt のURIは以下のコードで取得できます。
ClassLoader.getSystemResource("hoge.txt").toURI()
URIがわかればあとはこれを所望の場所にコピーすればいいのですが、Jarにした場合とそうでない場合で違いがあってはまりました。
Jarにパッケージングしないでリソースを参照した時のURI
動作試験のために ./gradlew :run とかした場合ですね。
Jarにパッケージングせずに実行した場合は、リソースのURIは file: プロトコルで、リソースファイルの場所を指します。
file:/path/to/project/build/resources/main/hoge.txt
このURIだと File クラスのコンストラクタに渡すことができるので、Kotlinの拡張メソッド File.copyTo() でコピーが可能です。
File(ClassLoader.getSystemResource("hoge.txt").toURI()).copyTo(destination)
しかしJarにパッケージングしてしまうと、URIをFileクラスコンストラクタに渡した途端に例外が出ます。
Jarにパッケージングされたリソースを参照した時のURI
jar: スキームのURIが得られます。
jar:file:/path/to/jar/file/MyApp.jar!/hoge.txt
これをそのままFileのコンストラクタに渡すと java.lang.IllegalArgumentException: URI is not hierarchical が発生します。
コピー元Fileが得られればコピーが楽なのですが、得られないのでそうはいきません。
どうすればいいかと言うと、リソースを開くInputStreamから内容を取り出して、コピー先ファイルに書き込んでしまえば良さそうでした。
自力でコピーメソッドを書くこともできますが、便利なメソッドがすでにあるので使わせてもらいましょう。
JDK付属の Files.copy() (Java 1.7から実装)を使うか、Commons IOの FileUtils.copyURLToFile() か FileUtils.copyToFile() を使うのがお手軽でしょう。
val srcIs: InputStream = ClassLoader.getSystemResourceAsStream("hoge.txt")
val srcUrl: URL = ClassLoader.getSystemResource("hoge.txt")
// Files.copy() の場合
Files.copy(srcIs, destination.toPath())
// FileUtils.copyURLToFile()の場合
FileUtils.copyURLToFile(srcUrl, destination)
// FileUtils.copyToFile()の場合
FileUtils.copyToFile(srcIs, destination)
とりあえずリソースファイルはURLやURIで扱うのではなく、Streamで扱ったら間違いないようです。