Java7以降で「ZIP」や「JAR」形式のファイルを「FileSystem」として開けます。
(Java上ではZIPも「JAR」として扱われます。)
ところがあるときどうやってもうまくいかなかった。
原因というか結論:「実行環境次第で使えない時がある(?)」
備忘録として記事にします。
(環境:Win10で古めのJava8)
2020/6/29:Java9以降のjlinkについて追記
### 1. FileSystems.newFileSystem の使い方
まず、前提となる使用法を記しときます。
・FileSystems.newFileSystem()というメソッドを使います。
― 対象ファイルの指定は Path か URI が使えます。
― 既存ファイル、新規ファイルどちらでもいけます。
1.1 Path で指定する方法
final Path zipfilePath = Paths.get("C:/a.zip");
try{
FileSystem fs = FileSystems.newFileSystem(zipfilePath, null);
}catch(Exception e){
e.printStackTrace();
}
ネットで探すと、
↓ このように第2引数にクラスローダを渡すサンプルが多い。
FileSystem fs = FileSystems.newFileSystem(zipfilePath, ClassLoader.getSystemClassLoader() );
この「ClassLoader.getSystemClassLoader()」がわけわからないからあきらめてURI指定に走る使用者が多い様子。
けれどソースを覗いた感じでは「null」で問題ない。
1.2 URI で指定する方法
final Path zipfilePath = Paths.get("C:/a.zip");
Map<String, Object> env = new HashMap<>();
//env.put("create", "true"); //// Enable creation for no-exists path.
//env.put("encoding", "UTF-8");
try{
FileSystem fs = FileSystems.newFileSystem( URI.create("jar:" + zipfilePath.toUri().toString() ), env ); // スキーム「jar」を指定する
}catch(Exception e){
e.printStackTrace();
}
URI による指定の場合は第2引数に Map インスタンスを渡します。ターゲットが既存ファイルの場合は空のMapでよい。
それでもって URI のほうでスキーム「jar」を指定しとく。
### 2. 実行したら失敗した(環境依存)
java.nio.file.ProviderNotFoundException: Provider not found
java.nio.file.ProviderNotFoundException: Provider "jar" not found
特に URI で指定していると(URIの使い方なんて自信ないし)
どこでミスってるかわけわからない。。。
諦めがちです。
2.1 なんで失敗したのか?
原因がわかって後から見直すと、エラーメッセージを素直に読んだその通りなのでした。
「jar (すなわち zip) を扱えるプロバイダ」が無い。
ここで、「プロバイダ」として実際になにが存在しているのか確認してみます。
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.spi.FileSystemProvider;
class ZipAsFileSystem{
public static final void main(String[] args) {
for (FileSystemProvider provider: FileSystemProvider.installedProviders()) {
System.out.println("provider = "+provider);
System.out.println("scheme = "+provider.getScheme());
}
}
}
まず、エラーがでない環境の実行結果を示します。
provider = sun.nio.fs.WindowsFileSystemProvider@28d93b30
scheme = file
provider = com.sun.nio.zipfs.ZipFileSystemProvider@1b6d3586
scheme = jar
↑ これが正常。
つぎにエラーが出る環境の実行結果:
provider = sun.nio.fs.WindowsFileSystemProvider@45ee12a7
scheme = file
↑ こちらは「ZipFileSystemProvider」が見つかってない。
だからZIPをハンドルできない。
どうりですね。
上の2つの環境は どちらも同じバージョンのOracleのjavaです。
違いは
・JDK同梱のランタイム(正常動作)
・JREとして配布されているランタイム(失敗)
どうして JRE 側に「ZipFileSystemProvider」ないのかはわからない。
このバージョン特有なのか、Oracle特有の事情なのか、あるいはこの記事の筆者が何か余計なことをしたか不明です。
### 3. 結論
とにかく
・「ZipFileSystemProvider」が実行環境中に存在していないときがある。
・そのせいでZIPをFileSystemできないときがある。
ということでした。
2020/6/29 追記
### 4. 対処法
Java8
ZipFileSystemProviderは「zipfs.jar」に含まれます。
Java8の場合はランタイムのlib/ext/zipfs.jar
があるかどうか確認、あるいは入れ替えなどで対策になるかも(未確認)
Java9以降
(注:実際に試した環境はLibericaJDK11です。)
Java9以降のランタイムで
「Provider "jar" not found」
が出る場合は
C:\Program Files\BellSoft\LibericaJDK-11\jmods\jdk.zipfs.jmod
のように「*.zipfs.jmod」があるか確認するとよい(はず。)
Jdeps および Jlink 使用時
jdepsとjlinkでランタイム(JRE)を抽出作成する際のはなし。
ZipFileSystemProviderを使用するJARをターゲットとして
jdepsで依存モジュールをリストした際、
「*.zipfs.jmod」がリストに載りませんでした。
そのままjlinkでランタイムを作ってターゲットJARを動かした使ったところ「Provider "jar" not found」エラー。
↓
jlink 実行時引数のモジュールリストに「jdk.zipfs」と追加することで解決。