簡単な入れ子構造を扱う #17

XMLは手段であって目的ではないと書くと他のデータ提示方式もあった方がいいのか。

school.csv
ねんちょうさん,さくら,Arlene,154
,,Bret,170
,,Cindy,157
,もも,Don,161
,,Emily,153
ねんしょうさん,ひまわり,Franklin,167
,ちゅうりっぷ,Gert,164
,,Harvey,169
,,Irene,158
,あさがお,Jose,154
,,Katia,159
おませさん,すみれ,Lee,171
,たんぽぽ,Maria,168

のようなCSVっぽいものからSchoolオブジェクトツリーを構築する。
空のフィールドは同上の意味とする仕様。
文字セットはプラットフォームデフォルトで固定ということに。
どうせ書くならSchoolBuilderを再定義してインタフェイス部分だけに抽象化し、
XMLSchoolBuilderとかCSVSchoolBuilderとかをその実働部隊にするのも悪くないがここではやらない。

CSVSchoolBuilder.java
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.IOException;
import java.io.LineNumberReader;
import school.*;

public class CSVSchoolBuilder {
    public School build(InputStream in) throws SchoolBuilderException {
        try {
            return parse(in);
        } catch (IOException e) {
            throw new SchoolBuilderException(e);
        }
    }

    public School build(String name) throws SchoolBuilderException {
        BufferedInputStream in = null;
        try {
            try {
                in = new BufferedInputStream(new FileInputStream(name));
                return build(in);
            } finally {
                if (in != null) in.close();
            }
        } catch (IOException e) {
            throw new SchoolBuilderException(e);
        }
    }

    private School parse(InputStream in) throws IOException {
        LineNumberReader r = null;
        try {
            r = new LineNumberReader(new InputStreamReader(in));
            School sc = new School();
            Grade ge = null;
            Group gp = null;
            while (true) {
                String s = r.readLine();
                if (s == null) break;
                String[] ss = s.split(",", 5);
                if (ss.length != 4) throw new IOException("at line " + r.getLineNumber() + ", invalid number of fields: " + s);
                if (ss[0].isEmpty()) {
                    if (ge == null) throw new IOException("at line " + r.getLineNumber() + ", void grade: " + s);
                } else {
                    ge = sc.add(ss[0]);
                }
                if (ss[1].isEmpty()) {
                    if (gp == null) throw new IOException("at line " + r.getLineNumber() + ", void group: " + s);
                } else {
                    gp = ge.add(ss[1]);
                }
                if (ss[2].isEmpty() || ss[3].isEmpty()) {
                    throw new IOException("at line " + r.getLineNumber() + ", void student: " + s);
                }
                try {
                    gp.add(ss[2], Integer.parseInt(ss[3]));
                } catch (NumberFormatException e) {
                    throw new IOException("at line " + r.getLineNumber() + ", invalid height: " + s);
                }
            }
            return sc;
        } finally {
            if (r != null) r.close();
        }
    }
}

本当はSchoolBuilderExceptionにメッセージ用のStringを受け入れるコンストラクタを持たせて、
CSVSchoolBuilder#parse()内のCSV形式に関わるエラーはSchoolBuilderExceptionを直接投げるようにするべきか。
他にもいろいろいい加減だ。

SchoolUser6.java
import school.*;

public class SchoolUser6 {
    public static void main(String[] args) throws SchoolBuilderException {
        for (Grade ge : new CSVSchoolBuilder().build("school.csv")) {
            System.out.println("[" + ge.getName() + "]");
            for (Group gp : ge) {
                System.out.println("  [" + gp.getName() + "]");
                for (Student s : gp) {
                    System.out.println("    [" + s.getName() + ", " + s.getHeight() + "cm]");
                }
            }
        }
    }
}