[車輪の再発明]BeanをCSV形式で出力する

BeanをCSV形式にして出力するクラス。
s2csvとか使えないなどのどうしようもないときくらいしか使い道が無いかも。

```java:BeanCSVWriter.java
package tools;

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.lang.reflect.Field;
import java.nio.charset.Charset;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.regex.Pattern;

/**
 * Bean→CSV出力クラス
 *
 * @param <T> Bean
 */
public class BeanCSVWriter<T> implements AutoCloseable {

	private static final String EMPTY = "";
	private static final String QUOTATION = "\"";
	private static final String SEPARATOR = ",";
	private static final String LINE_SEPARATOR = "\r\n";

	private static final Pattern QUOTATION_PATTERN = Pattern.compile(QUOTATION);
	private static final Pattern SEPARATOR_PATTERN = Pattern.compile(SEPARATOR);

	private static final DateFormat DATE_FORMATER = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");

	private final BufferedWriter bw;

	/**
	 * コンストラクタ
	 * @param os 出力ストリーム
	 * @param charset 文字コード
	 */
	public BeanCSVWriter(OutputStream os, String charset) {
		OutputStreamWriter osw = new OutputStreamWriter(os, Charset.forName(charset));
		bw = new BufferedWriter(osw);
	}

	/**
	 * 出力ストリームへ書き込む
	 * @param bean Bean
	 */
	public void write(T bean) {
		try {
			StringBuilder line = new StringBuilder();
			Field[] fields = bean.getClass().getDeclaredFields();
			for (Field field : fields) {
				field.setAccessible(true);
				Object obj = field.get(bean);
				line.append(escape(obj)).append(SEPARATOR);
			}
			line.deleteCharAt(line.length() - 1);
			line.append(LINE_SEPARATOR);
			bw.write(line.toString());
		} catch (IllegalArgumentException | IllegalAccessException | IOException e) {
			throw new IORuntimeException(e);
		}
	}

	/**
	 * エスケープ処理
	 * @param obj 値
	 * @return 文字列
	 */
	private String escape(Object obj) {
		if (obj == null) {
			return EMPTY;
		}
		String str = null;
		if (obj instanceof Date) {
			str = DATE_FORMATER.format(obj);
		} else {
			str = obj.toString();
		}
		if (QUOTATION_PATTERN.matcher(str).find()) {
			str = str.replaceAll(QUOTATION, QUOTATION + QUOTATION);
		}
		if (SEPARATOR_PATTERN.matcher(str).find()) {
			str = QUOTATION + str + QUOTATION;
		}
		return str;
	}

	/**
	 * クローズ
	 */
	public void close() {
		try {
			bw.close();
		} catch (IOException e) {
			throw new IORuntimeException(e);
		}
	}
}
```

```java:IORuntimeException.java
package tools;

/**
 * IOExceptionが発生時にThrowされる実行時例外
 */
public class IORuntimeException extends RuntimeException {

	/**
	 * コンストラクタ
	 * @param t 例外
	 */
	public IORuntimeException(Throwable t) {
		super(t);
	}
}
```

使い方。

```java:Main.java

package tools;

import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

public class Main {

	public static void main(String[] args) {
		List<CSVBean> beans = new ArrayList<CSVBean>();
		beans.add(new CSVBean(1, "山田", new Date()));
		beans.add(new CSVBean(2, "田中", new Date()));
		beans.add(new CSVBean(3, "中山田", new Date()));
		beans.add(new CSVBean(4, "中\"山田", new Date()));
		beans.add(new CSVBean(5, "中山,田", new Date()));
		beans.add(new CSVBean(6, "中\"山,田", new Date()));

		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		try (BeanCSVWriter<CSVBean> writer = new BeanCSVWriter<CSVBean>(baos, "UTF-8")) {
			for (CSVBean bean : beans) {
				writer.write(bean);
			}
		}

		System.out.println(baos.toString());
	}

	public static class CSVBean {
		public Integer id;
		public String name;
		public Date date;

		public CSVBean(Integer id, String name, Date date) {
			this.id = id;
			this.name = name;
			this.date = date;
		}
	}
}
```