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で扱ったら間違いないようです。