XStreamを使う

一時期お世話になったXStreamについて紹介したいと思う。
XStreamは、オブジェクトをXMLシリアライズしたり、逆にデシリアライズするシンプルなXML操作ライブラリである。

XStreamは標準ではkXML2という軽量で高速なXML parserを使用する。
しかし依存関係を作りたくない場合はJavaのDOM parser(javax.xml.parsers.DocumentBuilder)や、
Java6から追加されたStreaming API for XML、通称 StAXも利用できる(javax.xml.stream.XMLStreamReader)。
※公式チュートリアルではxstream-[version].jar と kxml2-min-[version].jarをクラスパスに入れること、とあるが、
 それではExceptionが発生してしまうので、kxml2-min-[version].jarの代わりに kxml2-[version].jarを使えばいい。
 それについては別記事とした。

理解しやすいエイリアスを使ったシリアライズ・デシリアライズを実際にやってみる。

シリアライズする

○課題XML
作りたいXMLはxstream-distribution-1.4.1-src.zipに入っていた"settings-template.xml"にする。

<?xml version="1.0" encoding="UTF-8"?>
    <!--
     Copyright (C) 2006, 2007, 2010 XStream committers.
     All rights reserved.
     
     The software in this package is published under the terms of the BSD
     style license a copy of which has been included with this distribution in
     the LICENSE.txt file.
     
     Created on 30. July 2006 by Mauro Talevi
     -->
<settings>
  <servers>
    <server>
      <id>codehaus-nexus-snapshots</id>
      <username>your-xircles-id</username>
      <password>your-xircles-pwd</password>
    </server>
    <server>
      <id>codehaus-nexus-staging</id>
      <username>your-xircles-id</username>
      <password>your-xircles-pwd</password>
    </server>      
    <server>
      <id>codehaus.org</id>
      <username></username>
      <password></password>
    </server>
  </servers>
</settings>

○モデルを考える
問題のXMLはsettingsの中にserversという項目があり、serversは複数のserverで構成されている。
モデルとして作成するのはList型のserversを持つsettingsと、3つの項目を持つserverになる。
Settings.java

package xml.xstream.step1;

import java.util.ArrayList;
import java.util.List;

public class Settings {
    private List<Server> servers = new ArrayList<>();
    
    public void add(Server srv){
        servers.add(srv);
    }
    public List<Server> get(){
        return servers;
    }
    @Override
    public String toString(){
        StringBuilder builder = new StringBuilder();
        for(Server server : servers) builder.append(server+"\n");
        return builder.toString();
    }
}

Server.java

package xml.xstream.step1;

public class Server {
    private String id;
    private String username;
    private String password;
    
    public Server(String id,String username, String password){
        this.id = id;
        this.username = username;
        this.password = password;
    }
    @Override
    public String toString(){
        return "id:"+id+", username:"+username+", password:"+password;
    }
}

※コンソール出力しやすいようにtoString()をOverrideしている。

○シンプルなテスト(シリアライズ)
まずは簡単にシリアライズできることを確認する。
シリアライズはXStream#toXML(Object)で簡単にできる。

public class SerializingXML {
    public static void main(String[] args) {
        Settings settings = new Settings();
        settings.add(new Server("codehaus-nexus-snapshots", "your-xircles-id", "your-xircles-pwd"));
        settings.add(new Server("codehaus-nexus-staging", "your-xircles-id", "your-xircles-pwd"));
        settings.add(new Server("codehaus.org", "", ""));

        XStream xstream = new XStream();
        System.out.println(xstream.toXML(settings));
    }
}

処理結果

<xml.xstream.step1.Settings>
  <servers>
    <xml.xstream.step1.Server>
      <id>codehaus-nexus-snapshots</id>
      <username>your-xircles-id</username>
      <password>your-xircles-pwd</password>
    </xml.xstream.step1.Server>
    <xml.xstream.step1.Server>
      <id>codehaus-nexus-staging</id>
      <username>your-xircles-id</username>
      <password>your-xircles-pwd</password>
    </xml.xstream.step1.Server>
    <xml.xstream.step1.Server>
      <id>codehaus.org</id>
      <username></username>
      <password></password>
    </xml.xstream.step1.Server>
  </servers>
</xml.xstream.step1.Settings>

○Class エイリアス
しかしこのままでは"xml.xstream.step1.Server"などクラス名そのままTag名になってしまっている。
これをClassエイリアスを使って任意のTag名へ変える必要がある。

        XStream xstream = new XStream();
        xstream.alias("settings", Settings.class);
        xstream.alias("server", Server.class);

処理結果

<settings>
  <servers>
    <server>
      <id>codehaus-nexus-snapshots</id>
      <username>your-xircles-id</username>
      <password>your-xircles-pwd</password>
    </server>
 ...

  </servers>
</settings>

Tag名が変わった。
目標としてはこれで完成した。

○暗黙のコレクション
しかしこれだけでは物足りないのでImplicit Collections(暗黙的コレクション)も使ってみる。
この機能は問題で言う所の"servers"に対する機能となる。
"servers"はモデルでも定義している通り、List型の構造である。
"servers"Tagを無くすことができる。

        xstream.addImplicitCollection(Settings.class, "servers");

処理結果

<settings>
  <server>
    <id>codehaus-nexus-snapshots</id>
    <username>your-xircles-id</username>
    <password>your-xircles-pwd</password>
  </server>
  <server>
    ...
  </server>
  <server>
    ...
  </server>
</settings>

ちなみに、ver1.3まではList以外の配列やMapは使用できなかった。
ver1.4より上記2つも利用できるようになったようだ。

○Field エイリアス
ここでsettingsへファイル名の項目を追加することにする。
Settings.java へ下記を追加

    private String configName;
    public Settings(String confName){
        this.configName = confName;
    }

そしてmainはこうなる

public class SerializingXML {
    public static void main(String[] args) {
        Settings settings = new Settings("settings-template.xml");
        settings.add(new Server("codehaus-nexus-snapshots", "your-xircles-id", "your-xircles-pwd"));
        settings.add(new Server("codehaus-nexus-staging", "your-xircles-id", "your-xircles-pwd"));
        settings.add(new Server("codehaus.org", "", ""));

        XStream xstream = new XStream();
        xstream.alias("settings", Settings.class);
        xstream.alias("server", Server.class);
        xstream.addImplicitCollection(Settings.class, "servers");
        System.out.println(xstream.toXML(settings));
    }
}

処理結果

<settings>
  <configName>settings-template.xml</configName>
  <server>
   ...
  </server>
</settings>

しかし、Tag名を"configName"ではなく"config"としたいため、
Fieldエイリアスを使う。
mainに下記を追加

        xstream.aliasField("config", Settings.class, "configName");

処理結果

<settings>
  <config>settings-template.xml</config>
  <server>
    ...
  </server>
</settings>

ちゃんと"config"になった。

○Attribute エイリアス
XMLにはValueだけでなく、Attribute(属性)値も存在する。
ここではさきほど追加した"config"を"settings"の属性としたい。

        xstream.useAttributeFor(Settings.class, "configName");

処理結果

<settings config="settings-template.xml">
  ...
</settings>

しっかり属性となった。
簡単なXMLならばここまでの機能を使って作成することができるだろう。

また、ここではSettingsクラスのFieldであるconfigNameを属性に変換したが、
モデル自体を属性としたい場合は独自のConverterを利用することもできる。
この機能についてはまた次の機会に紹介することとする。

シリアライズする

今度は逆にXMLファイルをオブジェクトとして持ってみる。
今まで作ってきたモデルを使う。
問題のXMLへconfig属性を追加する。

<settings config="settings-hogehoge.xml">

main

    public static void main(String[] args) {
        Path fullpath = FileSystems.getDefault().getPath(
                "src","xml","xstream","step1","settings-template.xml");
        File full = fullpath.toFile();

        XStream xstream = new XStream();
        xstream.alias("settings", Settings.class);
        xstream.alias("server", Server.class);
        xstream.aliasAttribute(Settings.class, "configName", "config");

        Settings settings = (Settings) xstream.fromXML(full);
        System.out.println("servers.size="+settings.get().size());
        System.out.println("settings.configName="+settings.getConfigName());
        System.out.println(settings);
        System.out.println(xstream.toXML(settings));
    }

処理結果

servers.size=3
settings.configName=settings-hogehoge.xml
id:codehaus-nexus-snapshots, username:your-xircles-id, password:your-xircles-pwd
id:codehaus-nexus-staging, username:your-xircles-id, password:your-xircles-pwd
id:codehaus.org, username:, password:

<settings config="settings-hogehoge.xml">
  <servers>
    <server>
      <id>codehaus-nexus-snapshots</id>
      <username>your-xircles-id</username>
      <password>your-xircles-pwd</password>
    </server>
    <server>
      <id>codehaus-nexus-staging</id>
      <username>your-xircles-id</username>
      <password>your-xircles-pwd</password>
    </server>
    <server>
      <id>codehaus.org</id>
      <username></username>
      <password></password>
    </server>
  </servers>
</settings>

いろいろと端折るが、特筆すべきはaliasAttributeだろう。
属性を読み込む際は、aliasAttributeをしなくてはならない。


長かった紹介もここでおしまい。
お疲れ様でした。