package org.phosphoresce.commons.util;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.apache.tools.zip.ZipEntry;
import org.apache.tools.zip.ZipFile;
import org.apache.tools.zip.ZipOutputStream;

/**
 * Zipファイル操作ユーティリティクラス。<br>
 * 当クラスはApache(Antプロジェクト)のZIPモジュールを使用します。<br>
 * 2バイト文字のエントリを扱う場合は当クラスを使用してください。<br>
 * また、当クラスを使用する場合はant.jarが必須となります。<br>
 *
 * @author Kitagawa<br>
 *
 *<!--
 * 更新日		更新者			更新内容
 * 2007/01/16	Kitagawa		新規作成
 * 2007/05/24	Kitagawa		Zipファイル解凍インタフェースの追加
 *-->
 */
public final class ZipApacheUtil {

	/** 入出力バイト長 */
	public static int BYTE_LENGTH = 1024;

	/** ディフォルトキャラクタセット */
	public static final String DEFAULT_CHARSET = "MS932";

	/** コンテナエントリ名マップキー */
	private static final String KEY_ENTRY_NAME = ZipContainer.class.getName() + ".KEY_ENTRY_NAME";

	/** コンテナファイルマップキー */
	private static final String KEY_ENTRY_FILE = ZipContainer.class.getName() + ".KEY_ENTRY_FILE";

	/**
	 * Zipファイル作成時のリソース保持クラス<br>
	 *
	 * @author Kitagawa<br>
	 *
	 *<!--
	 * 更新日		更新者			更新内容
	 * 2007/01/16	Kitagawa		新規作成
	 *-->
	 */
	public static final class ZipContainer {

		/** Zipファイルエントリリスト */
		private List list = new LinkedList();

		/**
		 * コンストラクタ<br>
		 */
		public ZipContainer() {
			//
		}

		/**
		 * 指定されたエントリファイル名でファイルを追加します。<br>
		 * @param name エントリファイル名
		 * @param file ファイル
		 */
		public void add(String name, File file) {
			Map map = new HashMap();
			map.put(KEY_ENTRY_NAME, name);
			map.put(KEY_ENTRY_FILE, file);
			list.add(map);
		}

		/**
		 * 保持している情報リストのIteratorオブジェクトを取得します。<br>
		 * @return 保持している情報リストのIteratorオブジェクト
		 */
		private Iterator iterator() {
			return list.iterator();
		}
	}

	/**
	 * コンストラクタ<br>
	 */
	private ZipApacheUtil() {
		super();
	}

	/**
	 * 指定されたファイルに対してZipContainerオブジェクトの内容をエントリしてZipファイルを生成する。<br>
	 * @param file 作成対象Zipファイルオブジェクト
	 * @param container エントリ内容保持オブジェクト
	 * @throws IOException 正常にZipファイル作成が行えなかった場合に発生
	 */
	public static void create(File file, ZipContainer container) throws IOException {
		create(file, container, DEFAULT_CHARSET);
	}

	/**
	 * 指定されたファイルに対してZipContainerオブジェクトの内容をエントリしてZipファイルを生成する。<br>
	 * @param file 作成対象Zipファイルオブジェクト
	 * @param container エントリ内容保持オブジェクト
	 * @param charset エントリファイルのキャラクタセット
	 * @throws IOException 正常にZipファイル作成が行えなかった場合に発生
	 */
	public static void create(File file, ZipContainer container, String charset) throws IOException {
		ZipOutputStream os = new ZipOutputStream(new FileOutputStream(file));

		os.setEncoding(charset);

		for (Iterator iterator = container.iterator(); iterator.hasNext();) {
			Map map = (Map) iterator.next();
			String name = (String) map.get(KEY_ENTRY_NAME);
			File resource = (File) map.get(KEY_ENTRY_FILE);

			ZipEntry entry = new ZipEntry(name);
			os.putNextEntry(entry);

			int count;
			byte[] bytes = new byte[BYTE_LENGTH];

			BufferedInputStream is = new BufferedInputStream(new FileInputStream(resource));
			while ((count = is.read(bytes, 0, BYTE_LENGTH)) != -1) {
				os.write(bytes, 0, count);
			}
			is.close();
			os.closeEntry();
		}

		os.close();
	}

	/**
	 * 指定されたZipファイルを指定されたディレクトリに対して解凍します。<br>
	 * @param file Zipファイル
	 * @param directory 解凍先ディレクトリ
	 * @throws IOException 正常にZipファイルの解凍が行えなかった場合に発生
	 */
	public static void unzip(File file, File directory) throws IOException {
		String target = directory == null ? "" : //
				directory.getAbsolutePath().endsWith(File.separator) ? directory.getAbsolutePath() : directory.getAbsolutePath() + File.separator;
		ZipFile zipFile = new ZipFile(file);
		Enumeration enumeration = zipFile.getEntries();
		while (enumeration.hasMoreElements()) {
			ZipEntry entry = (ZipEntry) enumeration.nextElement();
			if (entry.isDirectory()) {
				new File(target + entry.getName()).mkdirs();
			} else {
				File parent = new File(target + entry.getName()).getParentFile();
				parent.mkdirs();
				FileOutputStream out = new FileOutputStream(target + entry.getName());
				InputStream in = zipFile.getInputStream(entry);
				byte[] buf = new byte[1024];
				int size = 0;
				while ((size = in.read(buf)) != -1) {
					out.write(buf, 0, size);
				}
				out.close();
			}
		}
	}

	/**
	 * 指定されたZipファイルをカレントディレクトリに対して解凍します。<br>
	 * @param file Zipファイル
	 * @throws IOException 正常にZipファイルの解凍が行えなかった場合に発生
	 */
	public static void unzip(File file) throws IOException {
		unzip(file, null);
	}
}

