[java]CsvBeanReader, CsvBeanWriter, ParseDateを併用するには

前回、Beanによる読み込みとMapによる書き込みを取り上げましたが、元サイトのサンプルソースに手を加えての紹介、という趣旨だったので、Beanによる書き込みについては割愛しました。
なので、今回はBeanによる書き込みに焦点を当ててみましょう。


配布サイト:http://supercsv.sourceforge.net/
前回記事:[java]CSVの読み書きを快適に〜「Super CSV」ノススメ - Undead mode 忘備録


早速ですが、Bean内容を出力するmain実装をしてみると、↓のような感じになります。
ファイル名:WritingObjects.java

package write;

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

import org.supercsv.io.CsvBeanWriter;
import org.supercsv.io.ICsvBeanWriter;
import org.supercsv.prefs.CsvPreference;

import bean.UserBean;

/**
 * SuperCSVによるCSVファイル書き込み(Bean)
 * @author kazuki.kido
 */
public class WritingObjects {
    public static void main(String[] args) throws Exception {
        // ヘッダ情報定義
        String[] header = new String[]{"username", "password", "date", "zip", "town"};
        List<UserBean> list = new ArrayList<UserBean>();
        // 1行目データを作成
        UserBean bean1 = new UserBean();
        bean1.setUsername("Klaus");
        bean1.setPassword("qwexyKiks");
        bean1.setDate(new Date());
        bean1.setZip(1111);
        bean1.setTown("Tokyo");
        list.add(bean1);
        // 2行目データを作成
        UserBean bean2 = new UserBean();
        bean2.setUsername("Oufu");
        bean2.setPassword("bobilop");
        bean2.setDate(new Date());
        bean2.setZip(4555);
        bean2.setTown("Tokyo");
        list.add(bean2);
        ICsvBeanWriter writer = new CsvBeanWriter(new FileWriter("./data/boo.csv"), CsvPreference.EXCEL_PREFERENCE);
        try {
            // ファイルへ出力
            writer.writeHeader(header);
            // エラーログ取得用バッファ
            StringBuilder errorLog = new StringBuilder();
            // ファイルへ書き出し
            for(UserBean bean : list){
                writer.write(bean, header, UserBean.processors, errorLog);
                if(errorLog.length() > 0){
                    System.err.println(errorLog);
                    break;
                }
            }
        } finally {
            writer.close();
        }
    }
}

UserBeanについては前回作成したものを流用します。が、実はこのままだとエラーが発生してしまいます。エラーが発生する原因は、UserBean.javaの以下の部分。

ファイル名:UserBean.java

    /** 各要素フォーマット定義 */
    public static final CellProcessor[] processors = new CellProcessor[] {
            new Unique(new StrMinMax(4, 20)),    // username
            new StrMinMax(7, 35),                // password
            new ParseDate("dd/MM/yyyy"),         // date
            new Optional(new ParseInt()),        // zip
            null                                 // town
    };

date要素の定義にParseDateを使用していますが、このクラスは「CSVから取得した文字列 ⇒ 引数のフォーマットとして解釈し、Date型に変換して保持する」ことを指示しています。
しかし、CsvBeanWrite.write(Object, String, CsvPreference, StringBuilder)で必要となるのは「Date型⇒引数のフォーマットで変換し、文字列として出力する」指示です。探してみても、この2つを満たすハイブリットなクラスは用意されていません。
ないんじゃ仕方ないですよね〜というわけで、作ってみました。

ファイル名:ParseDateEx.java

package cell;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

import org.supercsv.cellprocessor.ParseDate;
import org.supercsv.cellprocessor.ift.DateCellProcessor;
import org.supercsv.exception.SuperCSVException;
import org.supercsv.util.CSVContext;

/**
 * write時の処理を考慮したParseDate拡張
 * @author kazuki.kido
 */
public class ParseDateEx extends ParseDate{
    /** 日付フォーマット */
    DateFormat formatter;
    /**
     * コンストラクタ
     * @param s
     */
    public ParseDateEx(String s) {
        super(s);
        formatter = new SimpleDateFormat(s);
    }
    /**
     * コンストラクタ
     * @param s
     * @param datecellprocessor
     */
    public ParseDateEx(String s, DateCellProcessor datecellprocessor) {
        super(s, datecellprocessor);
        formatter = new SimpleDateFormat(s);
    }
    /**
     * フォーマットチェック処理
     * @param obj
     * @param csvcontext
     * @throws SuperCSVException
     */
    public Object execute(Object obj, CSVContext csvcontext) throws SuperCSVException {
        // 出力時処理
        if(obj instanceof Date){
            return formatter.format((Date)obj);
        }
        // 入力時処理
        return super.execute(obj, csvcontext);
    }
}

ParseDateを継承し、executeメソッドのみ小細工しています。他メソッドは親メソッドを呼んでいるだけです。executeメソッドの引数objにDate型が渡された場合、ファイル出力時にメソッドが呼ばれているということになるので、formatterに基づくString型の値を返します。他の型が渡された場合は親メソッドに処理を引き継ぎます。

早速これをUserBeanに適用してみます。

ファイル名:UserBean.java

package bean;

import java.util.Date;

import org.supercsv.cellprocessor.Optional;
import org.supercsv.cellprocessor.ParseInt;
import org.supercsv.cellprocessor.constraint.StrMinMax;
import org.supercsv.cellprocessor.constraint.Unique;
import org.supercsv.cellprocessor.ift.CellProcessor;

import cell.ParseDateEx;

/**
 * CSVファイルの要素定義(Bean)
 * @author kazuki.kido
 */
public class UserBean {
    
    /** 各要素フォーマット定義 */
    public static final CellProcessor[] processors = new CellProcessor[] {
            new Unique(new StrMinMax(4, 20)),    // username
            new StrMinMax(7, 35),                // password
            new ParseDateEx("dd/MM/yyyy"),       // date
            new Optional(new ParseInt()),        // zip
            null                                 // town
    };
    
    /* 各要素の Getter/Setter 定義 */
    
    private String username, password, town;
    private Date date;
    private int zip;

    public String getPassword() { return password; }
    public Date getDate() { return date; }
    public String getTown() { return town; }
    public String getUsername() { return username; }
    public int getZip() { return zip; }
    public void setPassword(String password) { this.password = password; }
    public void setDate(Date date) { this.date = date; }
    public void setTown(String town) { this.town = town; }
    public void setUsername(String username) { this.username = username; }
    public void setZip(int zip) { this.zip = zip; }
}

ParseDate ⇒ ParseDateExに変わったのと、importが変更になっただけですね。
これで準備OKです。
実行してみると、以下のような内容が出力されます。

ファイル名:boo.csv

username,password,date,zip,town
Klaus,qwexyKiks,30/09/2007,1111,Tokyo
Oufu,bobilop,30/09/2007,4555,Tokyo

ちゃんと日付も意図した形式で出力されました。


尚、今回のプロジェクト構成は以下の通りです。