Commons DigesterでXMLをパースする(その3)

まだDigesterネタは続きます。
前回、Digester#addCallMethodの第三引数がゼロ以外の場合を「次回あたりに書く」と言いましたが、それはもうちょっと後にすることにして、今回は違うパターンについて書きます(^^;
まず、読み込むXMLは以下です。

<?xml version="1.0" encoding="UTF-8"?>
<aaa>
  <bbb xxx="111" yyy="222" zzz="333"/>
</aaa>

続いて、このXMLを格納するクラスです。

public class TestBean2 {
    private String xxx;
    private String yyy;
    private String zzz;
    
    public String getXxx() {
        return xxx;
    }
    public void setXxx(String xxx) {
        this.xxx = xxx;
    }
    public String getYyy() {
        return yyy;
    }
    public void setYyy(String yyy) {
        this.yyy = yyy;
    }
    public String getZzz() {
        return zzz;
    }
    public void setZzz(String zzz) {
        this.zzz = zzz;
    }
}

では、パースするクラスです。

import java.io.IOException;
import java.io.InputStream;

import org.apache.commons.digester.Digester;
import org.xml.sax.SAXException;

public class TestDigester2 {
    
    private TestDigester2() throws IOException, SAXException{
        InputStream in = this.getClass().getResourceAsStream("/test2.xml");
        Digester digester = new Digester();
        digester.addObjectCreate("aaa", "TestBean2");
        digester.addSetProperties("aaa/bbb");
        
        try {
            TestBean2 bean = (TestBean2)digester.parse(in);
            System.out.println("xxx=" + bean.getXxx());
            System.out.println("yyy=" + bean.getYyy());
            System.out.println("zzz=" + bean.getZzz());
        } finally {
            in.close();
        }
    }

    public static void main(String[] args) {
        try {
            new TestDigester2();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

これを実行するとこうなります。

xxx=111
yyy=222
zzz=333

今回はaddSetPropertiesというメソッドを使ってます。
これは属性の値を取得するときに使用します。
便利なのは1回メソッドを呼ぶだけで、xxx,yyy,zzzの3つの属性の値を取得できているところです。
1回のメソッドでまとめて属性の値を取得するのは、値を格納するBeanで同じ名前のプロパティを使っているときにしかできないのですが、とても簡単ですね!

Commons DigesterでXMLをパースする(その2)

さて、前回のコードについてです。

DigesterはXMLを先頭から読んでいきます。

digester.addObjectCreate("aaa", "TestBean1");

これはというタグが来たら、TestBean1というクラスのインスタンスをつくるという意味です。
で、次の

digester.addCallMethod("aaa/bbb", "setBbb", 0);

で、というタグが来たら、先ほどつくったTesbBean1クラスのsetBbbというメソッドを呼ぶことになります。
第三引数のゼロですが、ゼロだとこのタグの要素(タグの場合は"111")をパラメータに渡します。
じゃあ、ゼロ以外は?となりますが、それは次回あたりに書きます

最後にDigester#parseメソッドですが、これで実際にXMLを読み込みます。
今までののaddXXXメソッドはXMLを読み込むルールを設定しているだけなので、parseメソッドを呼んではじめてXMLを読みます。
ちなみにparseの引数はInputStreamでなくてもFileでもOKです。
そして、parseの戻りを取れば、XMLの中身が入ってます。

Commons DigesterでXMLをパースする(その1)

XMLをパースするときは、Apache CommonsのDigesterを使うと簡単らしいです。
「らしい」というのは使ったことないからわからないのです。。。( ̄▽ ̄;)
そんなわけで、少し試してみました。

なお、Digesterを実行するには、同じCommonsのLoggingとBeanUtilsが必要になるので、忘れずにダウンロードしましょう。


まずは下のXMLをパースしてみます。

<?xml version="1.0" encoding="UTF-8"?>
<aaa>
  <bbb>111</bbb>
  <ccc>222</ccc>
  <ddd>333</ddd>
</aaa>

最初にこのXMLの内容を格納するクラスをつくります。

public class TestBean1 {
    private String bbb;
    private String ccc;
    private String ddd;
    
    public String getBbb() {
        return bbb;
    }
    public void setBbb(String bbb) {
        this.bbb = bbb;
    }
    public String getCcc() {
        return ccc;
    }
    public void setCcc(String ccc) {
        this.ccc = ccc;
    }
    public String getDdd() {
        return ddd;
    }
    public void setDdd(String ddd) {
        this.ddd = ddd;
    }
}

次にメインの処理を書きます。

import java.io.IOException;
import java.io.InputStream;
import org.apache.commons.digester.Digester;
import org.xml.sax.SAXException;

public class TestDigester1 {
    
    private TestDigester1() throws IOException, SAXException{
        InputStream in = this.getClass().getResourceAsStream("/test1.xml");
        Digester digester = new Digester();
        digester.addObjectCreate("aaa", "TestBean1");
        digester.addCallMethod("aaa/bbb", "setBbb", 0);
        digester.addCallMethod("aaa/ccc", "setCcc", 0);
        digester.addCallMethod("aaa/ddd", "setDdd", 0);
        
        try {
            TestBean1 bean = (TestBean1)digester.parse(in);
            System.out.println("bbb=" + bean.getBbb());
            System.out.println("ccc=" + bean.getCcc());
            System.out.println("ddd=" + bean.getDdd());
        } finally {
            in.close();
        }
    }

    public static void main(String[] args) {
        try {
            new TestDigester1();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

これを実行すると次のように表示されます。

bbb=111
ccc=222
ddd=333

ちゃんとパースされてるようですね。
コードの説明は次回します。

eclipseから"-server"オプションをつけてプログラムを実行したい

あまり機会はないのですが、先日Javaでちょっとしたサーバーアプリケーションをつくっていました。
サーバアプリケーションだし、起動するときは

java -server XXX

と"-server"オプションをつけて起動したいと思って、eclipseの「実行」→「実行の構成」で

のようにして起動したら、次のように怒られました _l ̄l○

Error: no `server' JVM at `C:\Program Files\Java\jre6\bin\server\jvm.dll'.

たしかに、そんなDLLはないな。。。
というわけで、JREではなくJDKの"java.exe"を呼ぶように設定をしてみました。
設定の手順は以下。

  1. 「ウインドウ」→「設定」でダイアログを開き、左のペインで「Java」→「インストール済みのJRE」を選択
  2. 「追加」ボタンを押して、「JREの追加」ダイアログでは「標準VM」を選択して、「次へ」を押す
  3. JREホーム」のテキストボックスの横にある「ディレクトリー」ボタンを押して、JDKがインストールされているフォルダを選択





  4. フォルダが選択されると残りの欄は勝手に埋まるので、「完了」ボタンを押す。
  5. 追加したJDKをデフォルトで使用する。もしくは「実行」→「実行構成」で「JRE」タブを選択し、「代替JRE」を先ほど追加したJDKを設定する。(実行構成でJREを変更すると、ここでしかJDKを使用しない)
念のため、jconsoleを起動して、「VMの概要」タブを開いて確認したところ、ちゃんとServerVMになってました。

Webサービス・エクスプローラーを試してみる

Eclipse WTPにはWebサービスエクスプローラーというとっても便利なものがあることを知りました。
これを使うとWebサービスを簡単にテストすることが可能なのです。
なので、世の中で公開されているWebサービスでも自分でつくったWebサービスでも、非常に簡単に確認できます。

早速使ってみましょう。
なお、今回試すWebサービスRailGoで、MashupAward4用の認証情報を使います。
(この認証情報は2008/10/31まで有効なようです)


1.Eclipseを起動し、パースペクティブを「Java EE」にする
2.メニューで「実行」→「Webサービスエクスプローラーの起動」をクリック
3.右上のアイコンをクリックしてWSDLページを開く

4.左側のペインの「WSDLメイン」をクリックし、右側のペインに確認するWebサービスWSDLを入力して実行ボタンを押す

5.確認するバインディングをクリックする

6.確認するメソッドをクリックする(今回は「SearchStation」を使用)

7.メソッドのパラメータを入力して実行ボタンを押す

8.メソッドの結果が表示される

9.右上の「ソース」をクリックすると、生のSOAPメッセージが表示される


簡単ですね!

labelFieldとlabelFunction

前回はListの表示にitemRendererを使いましたが、こんなものを使わなくても実現可能でした。。。

まずはlabelField。

<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" creationComplete="initApp()">

  <mx:Script>
    <![CDATA[
      import mx.collections.ArrayCollection;
      private var directory:File = File.documentsDirectory;

      private function initApp():void {
          directory.addEventListener(Event.SELECT, directorySelected);
      }
    
      private   function onClick():void {
        try
        {
            directory.browseForDirectory("Select Directory");
        }
        catch (error:Error)
        {
            trace("Failed:", error.message)
        }

      }

      private function directorySelected(event:Event):void {
          directory = event.target as File;
          var files:Array = directory.getDirectoryListing();
          fileList.dataProvider = files;
      }  
      
    ]]>
  </mx:Script>

  <mx:Button label="open" click="onClick()"/>
  <mx:List id="fileList" width="100%" height="100%" labelField="name"/>
</mx:WindowedApplication>

ただ、labelFieldを指定しているだけですね。

<mx:List id="fileList" width="100%" height="100%" labelField="name"/>


次はlabelFunction。

<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" creationComplete="initApp()">

  <mx:Script>
    <![CDATA[
      import mx.collections.ArrayCollection;
      private var directory:File = File.documentsDirectory;

      private function initApp():void {
          directory.addEventListener(Event.SELECT, directorySelected);
      }
    
      private   function onClick():void {
        try
        {
            directory.browseForDirectory("Select Directory");
        }
        catch (error:Error)
        {
            trace("Failed:", error.message)
        }

      }

      private function directorySelected(event:Event):void {
          directory = event.target as File;
          var files:Array = directory.getDirectoryListing();
          fileList.dataProvider = files;
      }  
      
      private function myLabel(item:Object):String {
        return item.name;
      }
      
    ]]>
  </mx:Script>

  <mx:Button label="open" click="onClick()"/>
  <mx:List id="fileList" width="100%" height="100%" labelFunction="myLabel"/>  
</mx:WindowedApplication>

こっちはlabelFunctionを指定して、次のようなfunctionを使うのがポイントです。
private function XXX(item:Object):String


上のコード例では、labelFieldの場合もlabelFunctionの場合もどちらも同じように表示されます。
スクロールは相変わらず重い気がしますが。。

ListにitemRenderer

前回のAIRでディレクトリ選択ダイアログで、ディレクトリ内のファイルをListに表示させたんですが、

var files:Array = directory.getDirectoryListing();す
var filenames:ArrayCollection = new ArrayCollection();

for (var i:uint = 0; i < files.length; i++) {
  filenames.addItem(files[i].name);
}
fileList.dataProvider = filenames;

というように、Fileクラスの配列から、ファイル名の配列を作りなおしました。
これはFileクラスの配列をそのまま渡すと、こんな感じでファイル名の表示ができないからでした。

でも、せっかく取得したFileクラスの配列があるのに、それを捨てて別の配列を作るのが気に入らなかったので、別の方法を考えていたらitemRendererというものがあることを知りました。
で、itemRendererをつかった書き直したコードが以下です。

<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" creationComplete="initApp()">

  <mx:Script>
    <![CDATA[
      import mx.collections.ArrayCollection;
      private var directory:File = File.documentsDirectory;

      private function initApp():void {
          directory.addEventListener(Event.SELECT, directorySelected);
      }
    
      private   function onClick():void {
        try
        {
            directory.browseForDirectory("Select Directory");
        }
        catch (error:Error)
        {
            trace("Failed:", error.message)
        }

      }

      private function directorySelected(event:Event):void {
          directory = event.target as File;
          var files:Array = directory.getDirectoryListing();
          fileList.dataProvider = files;
      }  
      
    ]]>
  </mx:Script>

  <mx:Button label="open" click="onClick()"/>
  <mx:List id="fileList" width="100%" height="100%">
    <mx:itemRenderer>
      <mx:Component>
        <mx:Label text="{data.name}"/>
      </mx:Component>
    </mx:itemRenderer>
  </mx:List>
    
</mx:WindowedApplication>

これだとFileクラスの配列をdataProviderにそのまま渡してもファイル名を表示してくれます。
ポイントは以下のようにitemRendererを使っているところです。

<mx:List id="fileList" width="100%" height="100%">
  <mx:itemRenderer>
    <mx:Component>
      <mx:Label text="{data.name}"/>
    </mx:Component>
  </mx:itemRenderer>
</mx:List>

これで、めでたしめでたし、と思ったのですが、ファイル名の配列を作りなおしたときに比べてスクロールが重い。。。
まぁ、とりあえず今回はこれで良し、としておくことにします。