java6のzip問題

  java

私、仕事でいまだにjava6が生きている環境を扱っております。維持だけでなく時折補助ツールを作ったりもします。

言うてもjavaなので新しい書式を使ったり新しい機能を使おうと思わない限りは問題はほとんど起きないものです。そんな中で ZipOutputStreamで意外な原因で以下の例外が発生することを発見しました。

せっかくなのでメモしておきます。

そもそも ZipOutputStreamFileOutputStreamなどのストリームに接続してZip圧縮したバイナリを出力するものです。ZipOutputStreamオブジェクトに対して putNextEntry(ZipEntry e) メソッドでファイルやディレクトリを追加していく感じで使います。で、最後にcloseすると無事Zipアーカイブが完成するというわけです。

意外な原因とは

どこら辺が問題かというと、ZipOutputStreamオブジェクトを生成しておきながら一件も putNextEntryしないで closeすると例外が発生するという。。。

つまり中身が空っぽのZipファイルを作成できないということなのです。

えー、そんなことが想定されていなかったの?という感じでした。さすがにjava7からは修正されていますので、私のようなjava6縛りプレイを強いられている方以外は新しいjavaを使うことで回避できます。。

解決策

ではどうするか。まず、圧縮対象が見つかったタイミングで ZipOutputStreamオブジェクトを生成するようなラップクラスを作ります。(ZipOutputStreamクラス自体は継承できないので新たに包含したクラスを作成するイメージです。デザインパターンでいうところのBridgeパターンでしょうか、知らんけど)

ここでは ZipCreaterJava6 というクラスを作成しました。こんな感じです。

public class ZipCreaterJava6 {
    FileOutputStream os = null;
    ZipOutputStream zos = null;
    
    // 空ZIPバイナリ
    int[] emptyzip = { 'P', 'K', 5, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
    
    public ZipCreaterJava6(File zipFile) throws Exception {
        this.os = new FileOutputStream(zipFile);
    }
    
    public void putNextEntry(ZipEntry e) throws IOException {
        getZos().putNextEntry(e);
    }
    
    public void write(byte[] b, int off, int len) throws IOException {
        getZos().write(b, off, len);
    }

    void close() throws IOException {
        if (this.zos == null) {
            // Create an empty ZIP file.
            for (int e : emptyzip) {
                os.write(e);
            }
        } else {
            zos.close();
        }
        if (os != null) os.close();
    }
    
    ZipOutputStream getZos() {
        if (zos == null) zos = new ZipOutputStream(os);
        return zos;
    }
}

putNextEntry メソッド、writeメソッド、 closeメソッド を実装しますと、ZipOutputStreamっぽく使えます。 putNextEntryメソッド、writeメソッド内では getZos()経由することで初回呼び出しを検出してそのタイミングでZipOutputStreamオブジェクト生成しています。
closeメソッド内ではZipOutputStreamオブジェクトが生成されていれば close処理する。生成されてなかったら空っぽのZIPを表すバイナリ列を返すようにします。

使用サンプルは以下のようになります。

public class Main {

	public static void main(String[] args) throws Exception {
		File dir = args.length > 0 ? new File(args[0]) : null;
		if (dir != null && dir.exists() && dir.isDirectory()) {
            ZipCreaterJava6 zos = new ZipCreaterJava6(new File("archive.zip"));
            //ZipOutputStream zos = new ZipOutputStream(new FileOutputStream("archive.zip"));
		    
    		for (File item : dir.listFiles()) {
    		    FileInputStream fis = new FileInputStream(item);
    		    ZipEntry entry = new ZipEntry(item.getName());
    		    zos.putNextEntry(entry);
    		    
    		    byte[] buffer = new byte[4096];
    		    for (int n; (n=fis.read(buffer)) != -1; ) {
                    zos.write(buffer, 0, n);
                }
    		    fis.close();
    		}
    		zos.close();
		} else {
		    System.out.println("第一引数にソースとなるディレクトリを指定してください.");
		}
	}
}

最後に

どうしてもjava6でも動くようにしたい、という奇特なケースでない限りは不要なコードはありますが、実際私のような人間も2024年の時点で存在しているわけでして。あちこちから集めた情報をもとに解決した内容をここに記しておきました。

例外処理とかは適当です。引用するときはちゃんと書きましょう。以上。

LEAVE A COMMENT