JavaでWindows環境で文字化けしないZip圧縮を行う

JavaでファイルのZip圧縮を行う場合に、圧縮対象のファイル名に日本語文字が使われていると
一部のWindows環境で解凍時に文字化けが発生していたので、その回避方法を整理しました。

## そもそも
Mac/Linuxで圧縮したファイルをWindowsで開いたりすると同様の問題が発生するケースがあります。
具体的にはWindows7 or WindowsServer2008R2でパッチが当たっていない場合に発生します。[^1]

~~頼むから各種のパッチを当てて最新を使ってくれという気持ちになりますが~~ 
環境によっては容易に更新が行えないようなケースもあるため、その場合はプログラム側で対応しましょう。

## 実装
引数で指定されたファイル(またはディレクトリ)を`packed.zip`というZipファイルに圧縮するサンプルです。

Java7からは`ZipOutputStream`のコンストラクタに`Charset`を渡せるため
`Charset.forName("Shift-JIS")`を渡せばWindows環境でも文字化けしないZipファイルを作ることができます。

似た問題を探すと`org.apache.tools.zip`パッケージを入れる必要があるという記事がいくつか見つかりますが
Java7からは標準ライブラリのみで実現できます。

```java:ZipFile.java
package com.uskey512;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

public class ZipFile implements AutoCloseable {

    private byte[] buffer;
    private List<File> targetFiles;
    private ZipOutputStream zipOutputStream;

    private static final int DEFAULT_BUFFER_SIZE = 1024;

    public ZipFile(String fileName, String path) throws IOException {
        this(fileName, path, Charset.defaultCharset(), DEFAULT_BUFFER_SIZE);
    }

    public ZipFile(String fileName, String path, int bufferSize) throws IOException {
        this(fileName, path, Charset.defaultCharset(), bufferSize);
    }

    public ZipFile(String fileName, String path, Charset charset) throws IOException {
        this(fileName, path, charset, DEFAULT_BUFFER_SIZE);
    }

    public ZipFile(String fileName, String path, Charset charset, int bufferSize) throws IOException {
        File zipFile = new File(path + File.separator + fileName);
        buffer = new byte[bufferSize];
        targetFiles = new ArrayList<File>();
        zipOutputStream = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(zipFile)), charset);
    }

    public void addFile(String filePath) {
        targetFiles.add(new File(filePath));
    }

    public void build() {
        writeFiles(targetFiles.toArray(new File[0]));
    }

    private void writeFiles(File[] files) {
        for (File file : files) {
            if (file.isDirectory()) {
                writeFiles(file.listFiles());
            } else {
                try {
                    ZipEntry entry = new ZipEntry(file.getPath().replace('\\', '/'));
                    zipOutputStream.putNextEntry(entry);
                    try (InputStream is = new BufferedInputStream(new FileInputStream(file))) {
                        int readLength = 0;
                        while (0 < (readLength = is.read(buffer))) {
                            zipOutputStream.write(buffer, 0, readLength);
                        }
                    }
                } catch (IOException ioe) {
                    throw new RuntimeException("", ioe);
                }
            }
        }
    }

    @Override
    public void close() throws IOException {
        zipOutputStream.close();
    }
}
```

```java:Main.java
package com.uskey512;

import java.io.IOException;
import java.nio.charset.Charset;

public class Main {

    public static void main(String[] args) {
        // try (ZipFile zipFile = new ZipFile("packed.zip", ".") { // 化ける
        try (ZipFile zipFile = new ZipFile("packed.zip", ".", Charset.forName("Shift-JIS"))) { // 化けない
            zipFile.addFile(args[0]);
            zipFile.build();
        } catch (IOException ioe) {
        }
    }
}
```

[^1]: [Japanese characters in file names are displayed as garbled text after you decompress a .zip file in Windows 7 or in Windows Server 2008 R2](https://support.microsoft.com/en-us/help/2704299/japanese-characters-in-file-names-are-displayed-as-garbled-text-after)