私、仕事でいまだにjava6が生きている環境を扱っております。維持だけでなく時折補助ツールを作ったりもします。
言うてもjavaなので新しい書式を使ったり新しい機能を使おうと思わない限りは問題はほとんど起きないものです。そんな中で ZipOutputStream
で意外な原因で以下の例外が発生することを発見しました。
java.util.zip.ZipException: ZIP file must have at least one entry
せっかくなのでメモしておきます。
そもそも ZipOutputStream
は FileOutputStream
などのストリームに接続して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年の時点で存在しているわけでして。あちこちから集めた情報をもとに解決した内容をここに記しておきました。
例外処理とかは適当です。引用するときはちゃんと書きましょう。以上。