argius note

プログラミング関連

AntAPIの"tar"と"bzip2"で圧縮&解凍

Javaでアーカイバ(java.util.zip関連) - argius note」のAntAPIから派生。
AntAPIには、前述エントリで使った"org.apache.tools.zip.*"の他に、"tar","bzip2"があります。GZIPが無いのは、標準APIで良いということのようです。
使ってみたので、サンプルを置いておきます。使っているAntのバージョンは、ちょっと古めの"1.6.5"です。

生成されたファイルの確認に使用したツールは、LhazとCygwinコマンド(tar,gzip,bzip2)です。
また、準備として、以下の共通メソッドとインポートを示します。"ant.jar"をクラスパスに追加するのを忘れないようにしてください。

import java.io.*;
import java.util.zip.*;

import org.apache.tools.bzip2.*;
import org.apache.tools.tar.*;

static void transfer(InputStream is, OutputStream os) throws IOException {
    byte[] buffer = new byte[8192]; // 何故8192なのでしょうか
    for (int readLength; (readLength = is.read(buffer)) >= 0;) {
        os.write(buffer, 0, readLength);
    }
    os.flush();
}

Tar日本語対応

ZIPはエンコーディングが指定できるのに、Tarはできません。
日本語ファイル名を使えるようにするには、以下の暫定パッチを使います。あくまで暫定なので注意。Windows以外でどうなるかは試してません。
SVN"ANT_165 278395"タグのパッチです。

Index: src/main/org/apache/tools/tar/TarUtils.java
===================================================================
--- src/main/org/apache/tools/tar/TarUtils.java	(revision 705959)
+++ src/main/org/apache/tools/tar/TarUtils.java	(working copy)
@@ -76,6 +76,7 @@
         StringBuffer result = new StringBuffer(length);
         int          end = offset + length;
 
+        int size = 0;
         for (int i = offset; i < end; ++i) {
             if (header[i] == 0) {
                 break;
@@ -81,8 +82,9 @@
                 break;
             }
 
-            result.append((char) header[i]);
+            ++size;
         }
+        result.append(new String(header, offset, size));
 
         return result;
     }
@@ -98,8 +100,9 @@
     public static int getNameBytes(StringBuffer name, byte[] buf, int offset, int length) {
         int i;
 
-        for (i = 0; i < length && i < name.length(); ++i) {
-            buf[offset + i] = (byte) name.charAt(i);
+        byte[] bytes = name.toString().getBytes(); // default encoding
+        for (i = 0; i < length && i < bytes.length; ++i) {
+            buf[offset + i] = bytes[i];
         }
 
         for (; i < length; ++i) {

Tar+Gzip形式

カレントディレクトリには、"dir/a.txt","dir/b.bin"があります。
Tarだけにしたい場合は、GZIPストリームを外すだけです。

// write tar+gz
File srcdir = new File("dir/");
File dstfile = new File("test.tar.gz");
TarOutputStream tos = new TarOutputStream(new GZIPOutputStream(new FileOutputStream(dstfile)));
try {
    for (File file : srcdir.listFiles()) {
        TarEntry entry = new TarEntry(file);
        /*
         * // Fileオブジェクトを使わない場合
         * TarEntry entry = new TarEntry(entryName);
         * entry.setSize(length);
         * entry.setModTime(lastModifiedTime);
         */
        tos.putNextEntry(entry);
        InputStream is = new FileInputStream(file);
        try {
            transfer(is, tos);
        } finally {
            is.close();
        }
        tos.closeEntry();
    }
} finally {
    tos.close();   
}
// read tar+gz
File srcfile = new File("test.tar.gz");
TarInputStream tis = new TarInputStream(new GZIPInputStream(new FileInputStream(srcfile)));
try {
    for (TarEntry entry; (entry = tis.getNextEntry()) != null;) {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        transfer(tis, bos);
        System.out.printf("%s : %s%n",
                          entry.getName(),
                          new String(bos.toByteArray()));
    }
} finally {
    tis.close();
}

Bzip2形式

GZIP系と同じと思いきや、ちょっとだけ面倒です。先頭のIDを自分でつけてあげないといけないようです。この辺の事情は知りません。
読み込みの方は省略します。tar+bzip2にしたい場合は、tar+gzip版のGZIPをこれに置き換えてください。単純に置き換えだけで済まないのが厄介ですが.....

// write 
File srcfile = new File("dir", "a.txt");
File dstfile = new File("./", srcfile.getName() + ".bz2");
FileOutputStream fos = new FileOutputStream(dstfile);
try {
    fos.write(new byte[]{(byte)0x42, (byte)0x5A}); // id=BZ
    // blockSize指定もできる CBZip2OutputStream(OutputStream os, int blockSize)
    CBZip2OutputStream os = new CBZip2OutputStream(fos);
    try {
        InputStream is = new FileInputStream(srcfile);
        try {
            transfer(is, os);
        } finally {
            is.close();
        }
    } finally {
        os.close();
    }
} finally {
    fos.close();
}