JavaでZIPからFileSystemを呼ぼうとして　てこずった話

Java7以降で「ZIP」や「JAR」形式のファイルを「FileSystem」として開けます。
（Java上ではZIPも「JAR」として扱われます。）

ところがあるときどうやってもうまくいかなかった。
　　　　原因というか結論：「実行環境次第で使えない時がある（？）」

備忘録として記事にします。
（環境：Win10で古めのJava8）

#### 2020/6/29：Java9以降のjlinkについて追記　

###　1.　FileSystems.newFileSystem の使い方
まず、前提となる使用法を記しときます。
　・FileSystems.newFileSystem()というメソッドを使います。
　　― 対象ファイルの指定は Path か URI が使えます。
　　― 既存ファイル、新規ファイルどちらでもいけます。

#### 1.1 Path で指定する方法

```java

    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 で指定する方法
```java
    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.　実行したら失敗した（環境依存）
```:Path指定のときのエラーメッセージ
java.nio.file.ProviderNotFoundException: Provider not found
```

```:URI指定のときのエラーメッセージ
java.nio.file.ProviderNotFoundException: Provider "jar" not found
```

特に URI で指定していると（URIの使い方なんて自信ないし）
どこでミスってるかわけわからない。。。
諦めがちです。


### 2.1　なんで失敗したのか？
原因がわかって後から見直すと、エラーメッセージを素直に読んだその通りなのでした。
「jar (すなわち zip) を扱えるプロバイダ」が無い。

ここで、「プロバイダ」として実際になにが存在しているのか確認してみます。

```java:ファイルシステムプロバイダを表示する 
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());
		}
	}
}
```

まず、エラーがでない環境の実行結果を示します。

```java:実行結果　jdk1.8.0_121
    provider = sun.nio.fs.WindowsFileSystemProvider@28d93b30
        scheme = file

    provider = com.sun.nio.zipfs.ZipFileSystemProvider@1b6d3586
        scheme = jar
```
↑　これが正常。

つぎにエラーが出る環境の実行結果：

```java:実行結果　jre1.8.0_121
    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」と追加することで解決。**