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

スキーマ言語からxjcを介してJAXBを使うなら結局XML Schemaの利用が王道であるわけで。
でも生のXMLなんてできれば扱いたくない。ということで再びRelaxerの出番。

relaxer -xsd school.rnc
xjc -p school school.xsd

生成されたschool.xsdのルート要素のtargetNamespace属性が空であることで警告が出た。
が、問題なくxjcは働いた。ソースにちゃんと中身がある。
JAXBを利用する側のコードはRelaxerが生成したコードを利用するものとあまり変わらない。
最初のSchoolインスタンスを取得する部分が異なることと、ゲッタの名前が短くなったことか。

SchoolUser2.java
import java.io.File;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import school.*;

public class SchoolUser2 {
    public static void main(String[] args) throws JAXBException {
        for (SchoolGrade sg: ((School)(JAXBContext.newInstance(School.class).createUnmarshaller().unmarshal(new File("school.xml")))).getGrade()) {
            System.out.println("[" + sg.getName() + "]");
            for (SchoolGradeGroup sgg : sg.getGroup()) {
                System.out.println("  [" + sgg.getName() + "]");
                for (SchoolGradeGroupStudent sggs : sgg.getStudent()) {
                    System.out.println("    [" + sggs.getName() + ", " + sggs.getHeight() + "cm]");
                }
            }
        }
    }
}

ところがこれを実行するとUnmarshalExceptionが投げられる。

Exception in thread "main" javax.xml.bind.UnmarshalException: unexpected element (uri:"", local:"school"). Expected elements are (none)

どうやらschool要素が現れるのがお気に召さないとのこと。
xjcが生成したSchool.javaをよく見るとルート要素であることを示す注釈XmlRootElementが抜けている。
このためXMLにschool要素が現れた途端にそんなものが出てきては困るということらしい。

School.java (extract)
...snip
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "school", propOrder = {
    "grade"
})
@javax.xml.bind.annotation.XmlRootElement // この注釈を加える
public class School {
...snip

のように変更すればSchoolUserと同様に動作する。
XmlRootElementに(name = "school")を与えるべきかもしれないが、マーカーとして注釈を与えるだけでもこの例では問題なかった。
しかし、自動生成される物に手動で変更を加えるのは問題があると考える向きもある。
そこで、xjcが作成したschool.Schoolクラスを継承し、XmlRootElementの注釈付きのクラスを自前で作る。

School.java (unnamed package)
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "_school")
@XmlRootElement
public class School extends school.School {
}

このSchoolクラスはschoolパッケージではなくSchoolUser2と同じ無名パッケージに属する。
SchoolUser2におけるSchoolクラスは無名パッケージの方のSchoolとなる。
これによりSchoolUser2.javaもschoolパッケージの生成物も変更することなく、再コンパイルし実行するときちんと動作する。