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

XMLで書かれたものからSchoolオブジェクトツリーを構築できるようにしてみた。

SchoolBuilder.java
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.IOException;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import school.*;

public class SchoolBuilder {
    public School build(InputStream in) throws SchoolBuilderException {
        try {
            SAXParser p = SAXParserFactory.newInstance().newSAXParser();
            Handler h = new Handler();
            p.parse(in, h);
            return h.getSchool();
        } catch (ParserConfigurationException e) {
            throw new SchoolBuilderException(e);
        } catch (SAXException e) {
            throw new SchoolBuilderException(e);
        } 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 class Handler extends DefaultHandler {
        static final int INIT= 0;
        static final int SCHOOL= 1;
        static final int GRADE= 2;
        static final int GROUP= 3;
        static final int STUDENT= 4;
        static final int FIN= 5;
        int level;
        School school;
        Grade grade;
        Group group;

        @Override
        public void startDocument() {
            level = INIT;
        }

        @Override
        public void endDocument() throws SAXException {
            if (level != FIN) throw new SAXException("unexpected end of document");
        }

        @Override
        public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
            if (qName.equals("school")) {
                if (level != INIT) throw new SAXException("unexpected start of school");
                school = new School();
                level = SCHOOL;
            } else if (qName.equals("grade")) {
                if (level != SCHOOL) throw new SAXException("unexpected start of grade");
                String n = attributes.getValue("name");
                if (n == null) throw new SAXException("grade has no name attribute");
                grade = school.add(n);
                level = GRADE;
            } else if (qName.equals("group")) {
                if (level != GRADE) throw new SAXException("unexpected start of group");
                String n = attributes.getValue("name");
                if (n == null) throw new SAXException("group has no name attribute");
                group = grade.add(n);
                level = GROUP;
            } else if (qName.equals("student")) {
                if (level != GROUP) throw new SAXException("unexpected start of student");
                String n = attributes.getValue("name");
                if (n == null) throw new SAXException("student has no name attribute");
                String h = attributes.getValue("height");
                if (h == null) throw new SAXException("student has no height attribute");
                int he;
                try {
                    he = Integer.parseInt(h);
                } catch (NumberFormatException e) {
                    throw new SAXException("student's height attribute isn't a number: " + h);
                }
                group.add(n, he);
                level = STUDENT;
            } else {
                throw new SAXException("unexpected start of " + qName);
            }
        }

        @Override
        public void endElement(String uri, String localName, String qName) throws SAXException {
            if (qName.equals("school")) {
                if (level != SCHOOL) throw new SAXException("unexpected end of school");
                level = FIN;
            } else if (qName.equals("grade")) {
                if (level != GRADE) throw new SAXException("unexpected end of grade");
                level = SCHOOL;
            } else if (qName.equals("group")) {
                if (level != GROUP) throw new SAXException("unexpected end of group");
                level = GRADE;
            } else if (qName.equals("student")) {
                if (level != STUDENT) throw new SAXException("unexpected end of student");
                level = GROUP;
            } else {
                throw new SAXException("unexpected end of " + qName);
            }
        }

        School getSchool() {
            return school;
        }
    }
}
SchoolBuilderException.java
public class SchoolBuilderException extends Exception {
    public SchoolBuilderException(Exception e) {
        super(e);
    }
}

初めはenum Levelであったわけだが内部に隠れたちょっとした状態表現ならintで十分だ。
SchoolBuilderはschoolパッケージのクラスのpublicな部分しか使用していないので、
無駄にたくさんのクラスを作る前のパッケージでも後のパッケージでも同様に使える。

SchoolUser5.java
import school.*;

public class SchoolUser5 {
    public static void main(String[] args) throws SchoolBuilderException {
        for (Grade ge : new SchoolBuilder().build("school.xml")) {
            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]");
                }
            }
        }
    }
}