車輪の再発明大会

日曜プログラマがjava8で色々試してみるブログです

JAXBのユーティリティークラスを作る

JAXBは便利だけどおまじないが長いのです

JAXBはJAVAオブジェクトとXML要素をバインドしてくれる超便利なAPI群です。基本的な操作であれば、ほとんど定型のコードでオブジェクトとXMLを変換してくれます。
JAXBのより詳しい使用方法などはJava技術最前線 - 「Java SE 6完全攻略」第73回 JAXB その1:ITpro以下のシリーズ記事を参照していただくとして、読み取り、書き出しに必要な基本的なコードは以下のような感じになります。

読み取り
try {
    JAXBContext context =  JAXBContext.newInstance(ClassName.class);
    //クラスオブジェクトで生成
    Unmarshaller unmarshaller = context.createUnmarshaller();
    Object obj = unmarshaller.unmarshal(new File("fileName"));

    ClassName classInstance = (ClassName) obj;

} catch(Exception e){
    throw new IOException("オブジェクトの読み取りに失敗しました。", e);
}
書き出し
ClassName obj = new ClassName();
//普通はわざわざ書き込むときに生成はしませんが、存在してますよ、という意味で
try {
    JAXBContext context = JAXBContext.newInstance(ClassName.class);
    Marshaller marshaller = context.createMarshaller();
    marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);

    OutputStreamWriter os = new FileWriter(new File("fileName"));
    marshaller.marshal(obj, os);
} catch (Exception e) {
    throw new IOException("オブジェクトの書き込みに失敗しました。", e);
}

上記のコードは最低限の処理しかしていませんが、ほぼ定型で使いまわすことができるコードです。
クラス(パッケージ)の変換情報をJAXBContextとして取得して、そこからXML→オブジェクト変換を行うUnmarshaller、オブジェクト→XML変換を行うMarshallerを生成。あとはファイルを指定して変換を実行するメソッドを呼ぶだけです。
本当はJAXBContextの生成ではクラスオブジェクトに限らず完全修飾のクラス名やパッケージを指定したり、XMLのリソースもファイルに限らずHTTPを通じたストリームにできたりしますが、ここでは簡略のために目的の型のクラスオブジェクトを指定して、ファイルへ読み書きする処理についてに焦点を絞ります。

上記のようなシンプルなコードでオブジェクトとXMLの変換ができるのはとてもありがたいことなのですが、このままでは変換を行う型ごとに手続きを手書きしなくてはなりません。そこで、車輪の再発明を行い、この部分を省略できるユーティリティークラスを作りたいと思います。

肝は型パラメータ<T>なのです

上記のとおり、これらの操作を不特定のクラスで行う時に変化するのは、クラスオブジェクトとファイルの場所だけです。
そこで、こんな風にクラスを設計したくなります。

public class JAXBFileUtil<T> {
    private String fileName;
    private T object;

    public JAXBFileUtil(String fileName);
    public void write();//書き出し
    public void read();//読み取り
    public void setObject(T object);
    public T getObject();
}

ぱっと見だとこれで良さそうなんですが、実際にそれぞれの処理のコードを書き出すと問題に直面してしまいます。

    public void read(){//読み取りの定型文を当て込んでみます
        JAXBContect context = JAXBContext.newInstance(T.class);
        //コンパイルエラー
        .
        .
    }

これは型パラメーターTがジェネリクスのお約束事である「イレージャ」の処理にによって削除され、実際にはTが参照できないために起こります。
この問題はここで使用しているJAXBContext.newInstance()と同様に、Classコンストラクタへ引数として渡してしまうことで解決できます。

public class JAXBFileUtil<T> {
    private Class<T> type;
    private T object;
    private String fileName;

    public JAXBFileUtil(Class<T> type, String fileName){
        //引数のnullチェックは冗長なのでここでは省略
        this.type = type;
        this.fileName = fileName;
    }

    public void read() throws IOException {
        try {
            JAXBContext context =  JAXBContext.newInstance(type.class);
            //参照を持ってるので問題なし
            Unmarshaller unmarshaller = context.createUnmarshaller();
            Object obj = unmarshaller.unmarshal(new File(this.fileName));
            this.object = type.cast(obj);//キャストも安全にできる!

        } catch(Exception e){
            throw new IOException("オブジェクトの読み取りに失敗しました。", e);
        }
    }
}

劇的に変化しているわけではありませんが、副次的にキャストを演算子ではなくClass.cast()で行うことができるようになります。これでIDEに「無検査キャストです」と怒られることもなくなります。
同様に、書き出しもコーディングします。

    public void write() throws IOException {
        if(this.object == null){
            throw new IllegalStateException("objectがセットされていません");
        }

        try {
            JAXBContext context = JAXBContext.newInstance(type.class);
            Marshaller marshaller = context.createMarshaller();
            marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
            //おまじないの中のおまじない

            OutputStreamWriter os = new FileWriter(new File(this.fileName));
            marshaller.marshal(this.objct, os);
        } catch (Exception e) {
            throw new IOException("オブジェクトの書き込みに失敗しました。", e);
        }
    }

書き出しはこれといった修正点はありませんので、おまじないについて触れておきます。
Marshaller.setProperty()でMarshllerのプロパティを設定していますが、ここではファイルに書き出す以上人が読むだろうということで、XMLの整形を指定しています。HTTPで通信するストリームに書き出すような柔軟なユーティリティを考えるなら、外から変更できるほうが良いでしょう。

非常にシンプルな例ですが、これで一応Class<T>のインスタンスについて、読み書きできるユーティティクラスができました。
とはいえこの例ではあまりにも限定的な利用法になってしまうので、さらにいろいろできるできるユーティリティについてはどうすれば実現できるかを、別途考えてみたいと思います。