2010年5月23日日曜日

開発環境メモ

WindowsXP環境開発環境メモ

GAE, git, svn, maven を前提とした環境。
今のところ、GAE系やGITの環境の試行錯誤中。

TODO: とりあえずメモなので、あとでちゃんと書きましょう。
  • 7zip
    • http://www.7-zip.org/ からダウンロードしてすべてデフォルトでインストール。
    • 以降、アーカイブの展開は7zipを使います。(Windows付属のはどうもバギーなので)
  • ブラウザのインストール
    各種ブラウザの動作確認を行うため、複数のブラウザをインストールする。
    • Firefoxのインストール
      • http://mozilla.jp/firefox/ から最新版をダウンロードしてすべてデフォルト設定でインストールする。
    • Google Chrome のインストール
  • Java
    • http://java.sun.com/ から Java SE の最新版をダウンロードして、すべてデフォルト設定でインストールする。
    • 環境変数 JAVA_HOME を適切に設定する。(C:\Program Files\Java\jdk1.6.0_20 等)
    • 環境変数 PATH に %JAVA_HOME%\bin を設定する。
  • maven
    • http://maven.apache.org/ から安定版の最新のバイナリをダウンロード。(ex. apache-maven-2.2.1-bin.zip)
    • C:\Program Files 直下にコピー。(ex. C:\Program Files\apache-maven-2.2.1)
    • 環境変数 MAVEN_HOME を設定。
    • 環境変数 Path に %MAVEN_HOME%\bin を追加。
  • Eclipse
    • http://www.eclipse.org/downloads/ から Eclipse IDE for Java EE Developers の galileo の最新版をダウンロード。
    • C:\Program Files\eclipse として展開する。
    • 文字コードの設定
      • Window -> Preferences -> General -> Workspace -> Text file encoding を utf-8 に設定。
    • xmlのwidthを999、インデントをスペース2つに変更する。
    • eclipseにjdkのソースをアタッチ
      • Window -> Preferences... -> Java -> Installed JREs に移動。
      • jre を選択して、Edit... ボタンを押下。
      • C:\tmp\devenv\jdk\jre\lib\rt.jar を選択。
      • Source Attachement... ボタンを押下。
      • C:/Program Files/Java/jdk1.6.0_20/src.zip をアタッチ。
    • eclipseにJavaDocをアタッチ
      • Sunのサイトからドキュメントをダウンロードして、src.zipと同じフォルダに置く。
      • Window -> Preferences... -> Java -> Installed JREs に移動。
      • jre を選択して、Edit... ボタンを押
      • C:\tmp\devenv\eclipse\jre\lib\rt.jar を選択。
      • JavaDoc Location... ボタンを押下。
      • Javadoc in archive と Workspace file を選択。
      • Archive path を設定先ほどダウンロードしたドキュメントのzipに設定。
      • Path within archive を設定。(ex. docs/api)
      • ソース上の対象を選択してShift+F2キーを押せば、システム設定のブラウザ経由でJavaDocが起動します。
    • m2eclipseの設定
      • アップデートサイト http://m2eclipse.sonatype.org/sites/m2e からインストールする。
      • [Window] -> [Preferences...] -> [Maven] にて、以下のオプションをチェック。
        • Download Artifact Sources
        • Download Artifact JavaDoc
      • デフォルトのmavenが3.0系のSNAPSHOT版の場合は、[Window] -> [Preferences...] -> [Maven]  -> [Installations] -> [Add] から Program Files 以下にインストールした maven を指定する。
    • Google 関連プラグインの設定
    • subclipse プラグインの設定
      • アップデートサイト http://subclipse.tigris.org/update_1.6.x からインストールする。その際、オプショナルコンポーネントはすべて無視する。
      • SVN インタフェースを SVNKit (Pure Java) に設定する。
        • [Window] -> [Preferences...] -> [Team] -> [SVN] -> [SVNインタフェース] -> [SVNKit (Pure Java)]
    • EGit プラグインの設定。


    2010年5月11日火曜日

    Flex のコンポーネント遅延ロードとデータバインディングとモデリングに関する考察

    Flexではコンポーネントの遅延ローディングがデフォルトで行われるようになっています。そのため、mxml ファイル内で定義されたコンポーネントに対して同一の mxml ファイル内の ActionScript からアクセスしても、null が返る可能性があります。

    そのような場合、内部コンポーネントを定義するXML要素の属性として、ActionScript 内の変数をバインディングするというワークアラウンドで乗り切ることができます。しかし、データバインディングをする場合、モデルが既にあったとしても、バインディング用の変数をわざわざ用意する必要があります。モデルの中で配列で100要素バインディングするとしたら、100種類の変数を用意する必要があります。

    これも、ArrayCollection 型を使用することで解決できますが、既に Vector でデータを持っているモデルがあるとしたら、毎回移し変えなければなりません。

    このように、ワークアラウンドで乗り切ることもできますが、あまり建設的ではないように思えます。まだまだ落とし穴があるように思えますし、、、。

    ということで、今後の作業効率や成果物の品質にも大きく影響すると考えられるので、この機会に確認しておこうと思います。


    例1:ある state で生成された情報を、これから初めて遷移される state に表示する場合。

    ソース
    思惑
    • State 1 が初期状態。
    • State 1 上の入力フォームから結果表示用ラベルに A と B の値を入力し、add ボタンを押下する。
    • State 2 に遷移し、A + B の結果が表示される。
    結果
    • State 2 で表示される結果表示用ラベルの値が null となり、エラーが発生。
    考察
    • creationPolicy を all にすることにより遅延ロードを抑制できないか?
    • ドキュメントによると、単一ビューコンテナでは、auto でも all でも同じ動作となり、『デフォルトでは、アプリケーションが最初に起動されたときに単一ビューコンテナのすべての子が生成されます。』となるらしい。生成されてるのに null なのはなぜ? (´・ω・`)
    • State が絡む場合はダメらしい。結局、State の遷移を先に行うしかないらしい。
    解決法
    • ある State に含まれるコンポーネントへのアクセスは、その State に遷移してから行う。
    解決後のソース

    例2:複雑なモデル内部のデータを参照する場合。

    バインディング版
    • ソース:Ex2.mxml
    • 特に問題なく動作する。


    例3:配列へバインディングしようとして失敗する版
    • ソース:Ex2_1.mxml
    • 最初の1回目はうまく行くが、2回目以降も最初の結果が出続ける。

    例4:ArrayCollection を用いて対応する版
    • ソース:Ex2_2.mxml
    • 問題なく動作するが、バインディングのために設計を崩すことになった。バインディングするよりも、特定のタイミングでデータをリロードしてくれるような設計の方がありがたいのだが。例えば状態遷移のタイミングとか。
    例5:ボタンクリックのタイミングでデータを格納する版
    • ソース:Ex2_3.mxml
    • サブコンポーネントに対する text の指定を、親コンポーネント側で決定することが自然な設計であれば、無理にバインディングする必要はない。そのため、クリックのハンドラでラベルの表示を行っている。
    例6:Stateに入るタイミングでデータを格納する版
    • ソース:Ex2_4.mxml
    • サブコンポーネントに対する text の指定を、親コンポーネント側で決定することが自然な設計であれば、無理にバインディングする必要はない。状態遷移をする箇所が一箇所であればその場所にラベルにデータを書き込む処理を埋め込めば良いのだが、ソースの様々な箇所から状態遷移が発生する場合には同様の処理を複数回記述することになる。その際、状態遷移をトリガーにした方がシンプルになる場合は、State に入るイベントを用いた方がよい。
    付録:ソース


    Ex1.mxml
    <?xml version="1.0" encoding="utf-8"?>
    <s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
          xmlns:s="library://ns.adobe.com/flex/spark" 
          xmlns:mx="library://ns.adobe.com/flex/mx" width="100%" height="100%">
     <s:layout>
      <s:VerticalLayout horizontalAlign="center" verticalAlign="middle"/>
     </s:layout>
    
     <fx:Script>
      <![CDATA[
       import mx.controls.Alert;
       <!--- A + B の計算をして resultLabel に値を設定し、State2 に遷移するメソッド -->
       private function addButton_clickHandler(event:MouseEvent):void {
        var a:String = aNs.value.toString();
        var b:String = bNs.value.toString();
        var result:String = (aNs.value + bNs.value).toString();
        Alert.show("resultLabel = " + resultLabel); // ここで、resultLabel が null になる。
        resultLabel.text = a + " + " + b + " = " + result;
        currentState = "State2";
       }
      ]]>
     </fx:Script>
    
     <!-- state を用いて画面遷移を模倣 -->
     <s:states>
      <!-- A と B の値を入力する画面 -->
      <s:State name="State1"/>
      <!-- A + B の結果を表示する画面 -->
      <s:State name="State2"/>
     </s:states>
     
     <!-- State1用コンポーネント群 -->
     <mx:Form includeIn="State1">
      <mx:FormItem label="A">
       <!--- A の値入力用 NumericStepper -->
       <s:NumericStepper id="aNs"/>
      </mx:FormItem>
      <mx:FormItem label="B">
       <!--- B の値入力用 NumericStepper -->
       <s:NumericStepper id="bNs"/>
      </mx:FormItem>
     </mx:Form>
     <s:Button includeIn="State1" label="add" id="addButton" click="addButton_clickHandler(event)"/>
     
     <!-- State2用コンポーネント群 -->
     <!--- A + B = (A+Bの結果) という文字列を表示するためのラベル -->
     <s:Label id="resultLabel" includeIn="State2"/>
     
    </s:Application>
    


    Ex1_1.mxml
    <?xml version="1.0" encoding="utf-8"?>
    <s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
          xmlns:s="library://ns.adobe.com/flex/spark" 
          xmlns:mx="library://ns.adobe.com/flex/mx" width="100%" height="100%">
     <s:layout>
      <s:VerticalLayout horizontalAlign="center" verticalAlign="middle"/>
     </s:layout>
    
     <fx:Script>
      <![CDATA[
       import mx.controls.Alert;
       <!--- A + B の計算をして resultLabel に値を設定し、State2 に遷移するメソッド -->
       private function addButton_clickHandler(event:MouseEvent):void {
        var a:String = aNs.value.toString();
        var b:String = bNs.value.toString();
        var result:String = (aNs.value + bNs.value).toString();
        currentState = "State2";
        Alert.show("resultLabel = " + resultLabel); // ここで、resultLabel が null ではなく、きちんと表示される。
        resultLabel.text = a + " + " + b + " = " + result;
       }
      ]]>
     </fx:Script>
    
     <!-- state を用いて画面遷移を模倣 -->
     <s:states>
      <!-- A と B の値を入力する画面 -->
      <s:State name="State1"/>
      <!-- A + B の結果を表示する画面 -->
      <s:State name="State2"/>
     </s:states>
     
     <!-- State1用コンポーネント群 -->
     <mx:Form includeIn="State1">
      <mx:FormItem label="A">
       <!--- A の値入力用 NumericStepper -->
       <s:NumericStepper id="aNs"/>
      </mx:FormItem>
      <mx:FormItem label="B">
       <!--- B の値入力用 NumericStepper -->
       <s:NumericStepper id="bNs"/>
      </mx:FormItem>
     </mx:Form>
     <s:Button includeIn="State1" label="add" id="addButton" click="addButton_clickHandler(event)"/>
     
     <!-- State2用コンポーネント群 -->
     <!--- A + B = (A+Bの結果) という文字列を表示するためのラベル -->
     <s:Label id="resultLabel" includeIn="State2"/>
     
    </s:Application>
    


    Ex2.mxml
    <?xml version="1.0" encoding="utf-8"?>
    <s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
          xmlns:s="library://ns.adobe.com/flex/spark" 
          xmlns:mx="library://ns.adobe.com/flex/mx" width="100%" height="100%">
     <s:layout>
      <s:VerticalLayout horizontalAlign="center" verticalAlign="middle"/>
     </s:layout>
    
     <fx:Script>
      <![CDATA[
       import mx.controls.Alert;
       <!--- A + B の計算をして resultLabel に値を設定し、State2 に遷移するメソッド -->
       private function addButton_clickHandler(event:MouseEvent):void {
        currentState = "State2";
       }
    
       <!--- State2 から State1 に戻るボタン -->
       private function backButton_clickHandler(event:MouseEvent):void {
        currentState = "State1";
       }
    
      ]]>
     </fx:Script>
    
     <!-- state を用いて画面遷移を模倣 -->
     <s:states>
      <!-- A と B の値を入力する画面 -->
      <s:State name="State1"/>
      <!-- A + B の結果を表示する画面 -->
      <s:State name="State2"/>
     </s:states>
     
     <!-- State1用コンポーネント群 -->
     <mx:Form includeIn="State1">
      <mx:FormItem label="Value 1">
       <!--- 値1 の値入力用 NumericStepper -->
       <s:NumericStepper id="v1Ns"/>
      </mx:FormItem>
      <mx:FormItem label="Value 2">
       <!--- 値2 の値入力用 NumericStepper -->
       <s:NumericStepper id="v2Ns"/>
      </mx:FormItem>
      <mx:FormItem label="Value 3">
       <!--- 値3 の値入力用 NumericStepper -->
       <s:NumericStepper id="v3Ns"/>
      </mx:FormItem>
     </mx:Form>
     <s:Button includeIn="State1" label="add" id="addButton" click="addButton_clickHandler(event)"/>
     
     <!-- State2用コンポーネント群 -->
     <!--- v1 + v2 + v3 = (v1+v2+v3の結果) という文字列を表示するためのラベル -->
     <s:Label id="resultLabel" includeIn="State2" text="{v1Ns.value} + {v2Ns.value} + {v3Ns.value} = {v1Ns.value + v2Ns.value + v3Ns.value}"/>
     <s:Button includeIn="State2" label="back" id="backButton" click="backButton_clickHandler(event)"/>
     
    </s:Application>
    


    Ex2_1.mxml
    <?xml version="1.0" encoding="utf-8"?>
    <s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
          xmlns:s="library://ns.adobe.com/flex/spark" 
          xmlns:mx="library://ns.adobe.com/flex/mx" width="100%" height="100%">
     <s:layout>
      <s:VerticalLayout horizontalAlign="center" verticalAlign="middle"/>
     </s:layout>
    
     <fx:Script>
      <![CDATA[
       import mx.controls.Alert;
       
       <!--- 値を保持する Vector -->
       private var values:Vector.<Number> = new Vector.<Number>();
       
       <!--- A + B の計算をして resultLabel に値を設定し、State2 に遷移するメソッド -->
       private function addButton_clickHandler(event:MouseEvent):void {
        // コンポーネントの値を values に格納。
        values.push(v1Ns.value);
        values.push(v2Ns.value);
        values.push(v3Ns.value);
        
        currentState = "State2";
       }
    
       <!--- State2 から State1 に戻るボタン -->
       private function backButton_clickHandler(event:MouseEvent):void {
        currentState = "State1";
       }
    
      ]]>
     </fx:Script>
    
     <!-- state を用いて画面遷移を模倣 -->
     <s:states>
      <!-- A と B の値を入力する画面 -->
      <s:State name="State1"/>
      <!-- A + B の結果を表示する画面 -->
      <s:State name="State2"/>
     </s:states>
     
     <!-- State1用コンポーネント群 -->
     <mx:Form includeIn="State1">
      <mx:FormItem label="Value 1">
       <!--- 値1 の値入力用 NumericStepper -->
       <s:NumericStepper id="v1Ns"/>
      </mx:FormItem>
      <mx:FormItem label="Value 2">
       <!--- 値2 の値入力用 NumericStepper -->
       <s:NumericStepper id="v2Ns"/>
      </mx:FormItem>
      <mx:FormItem label="Value 3">
       <!--- 値3 の値入力用 NumericStepper -->
       <s:NumericStepper id="v3Ns"/>
      </mx:FormItem>
     </mx:Form>
     <s:Button includeIn="State1" label="add" id="addButton" click="addButton_clickHandler(event)"/>
     
     <!-- State2用コンポーネント群 -->
     <!--- v1 + v2 + v3 = (v1+v2+v3の結果) という文字列を表示するためのラベル -->
     <s:Label id="resultLabel" includeIn="State2" text="{values[0]} + {values[1]} + {values[2]} = {values[0] + values[1] + values[2]}"/>
     <s:Button includeIn="State2" label="back" id="backButton" click="backButton_clickHandler(event)"/>
     
    </s:Application>
    


    Ex2_2.mxml
    <?xml version="1.0" encoding="utf-8"?>
    <s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
          xmlns:s="library://ns.adobe.com/flex/spark" 
          xmlns:mx="library://ns.adobe.com/flex/mx" width="100%" height="100%">
     <s:layout>
      <s:VerticalLayout horizontalAlign="center" verticalAlign="middle"/>
     </s:layout>
    
     <fx:Script>
      <![CDATA[
       import mx.collections.ArrayCollection;
       import mx.controls.Alert;
       
       [Bindable]
       /**
        * 値を保持する ArrayCollection
        */
       private var values:ArrayCollection = new ArrayCollection(new Array(3));
       
       /**
        * A + B の計算をして resultLabel に値を設定し、State2 に遷移するメソッド
        */
       private function addButton_clickHandler(event:MouseEvent):void {
        // コンポーネントの値を values に格納。
        values.setItemAt(v1Ns.value, 0);
        values.setItemAt(v2Ns.value, 1);
        values.setItemAt(v3Ns.value, 2);
        
        currentState = "State2";
       }
    
       <!--- State2 から State1 に戻るボタン -->
       private function backButton_clickHandler(event:MouseEvent):void {
        currentState = "State1";
       }
    
      ]]>
     </fx:Script>
    
     <!-- state を用いて画面遷移を模倣 -->
     <s:states>
      <!-- A と B の値を入力する画面 -->
      <s:State name="State1"/>
      <!-- A + B の結果を表示する画面 -->
      <s:State name="State2"/>
     </s:states>
     
     <!-- State1用コンポーネント群 -->
     <mx:Form includeIn="State1">
      <mx:FormItem label="Value 1">
       <!--- 値1 の値入力用 NumericStepper -->
       <s:NumericStepper id="v1Ns"/>
      </mx:FormItem>
      <mx:FormItem label="Value 2">
       <!--- 値2 の値入力用 NumericStepper -->
       <s:NumericStepper id="v2Ns"/>
      </mx:FormItem>
      <mx:FormItem label="Value 3">
       <!--- 値3 の値入力用 NumericStepper -->
       <s:NumericStepper id="v3Ns"/>
      </mx:FormItem>
     </mx:Form>
     <s:Button includeIn="State1" label="add" id="addButton" click="addButton_clickHandler(event)"/>
     
     <!-- State2用コンポーネント群 -->
     <!--- v1 + v2 + v3 = (v1+v2+v3の結果) という文字列を表示するためのラベル -->
     <s:Label id="resultLabel" includeIn="State2" text="{values.getItemAt(0)} + {values.getItemAt(1)} + {values.getItemAt(2)} = {values.getItemAt(0) + values.getItemAt(1) + values.getItemAt(2)}"/>
     <s:Button includeIn="State2" label="back" id="backButton" click="backButton_clickHandler(event)"/>
     
    </s:Application>
    


    Ex2_3.mxml
    <?xml version="1.0" encoding="utf-8"?>
    <s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
          xmlns:s="library://ns.adobe.com/flex/spark" 
          xmlns:mx="library://ns.adobe.com/flex/mx" width="100%" height="100%">
     <s:layout>
      <s:VerticalLayout horizontalAlign="center" verticalAlign="middle"/>
     </s:layout>
    
     <fx:Script>
      <![CDATA[
       import mx.controls.Alert;
       
       <!--- 値を保持する Vector -->
       private var values:Vector.<Number> = new Vector.<Number>();
       
       <!--- A + B の計算をして resultLabel に値を設定し、State2 に遷移するメソッド -->
       private function addButton_clickHandler(event:MouseEvent):void {
        // コンポーネントの値を values に格納。
        if (values.length == 0) {
         values.push(v1Ns.value);
         values.push(v2Ns.value);
         values.push(v3Ns.value);
        } else {
         values[0] = v1Ns.value;
         values[1] = v2Ns.value;
         values[2] = v3Ns.value;
        }
            
        currentState = "State2";
        
        // ラベルの値を直接指定する。
        resultLabel.text = values[0].toString() + ' + ' + values[1].toString() + ' + ' + values[2].toString() + ' = ' + (values[0] + values[1] + values[2]);
    
       }
    
       <!--- State2 から State1 に戻るボタン -->
       private function backButton_clickHandler(event:MouseEvent):void {
        currentState = "State1";
       }
      ]]>
     </fx:Script>
    
     <!-- state を用いて画面遷移を模倣 -->
     <s:states>
      <!-- A と B の値を入力する画面 -->
      <s:State name="State1"/>
      <!-- A + B の結果を表示する画面 -->
      <s:State name="State2"/>
     </s:states>
     
     <!-- State1用コンポーネント群 -->
     <mx:Form includeIn="State1">
      <mx:FormItem label="Value 1">
       <!--- 値1 の値入力用 NumericStepper -->
       <s:NumericStepper id="v1Ns"/>
      </mx:FormItem>
      <mx:FormItem label="Value 2">
       <!--- 値2 の値入力用 NumericStepper -->
       <s:NumericStepper id="v2Ns"/>
      </mx:FormItem>
      <mx:FormItem label="Value 3">
       <!--- 値3 の値入力用 NumericStepper -->
       <s:NumericStepper id="v3Ns"/>
      </mx:FormItem>
     </mx:Form>
     <s:Button includeIn="State1" label="add" id="addButton" click="addButton_clickHandler(event)"/>
     
     <!-- State2用コンポーネント群 -->
     <!--- v1 + v2 + v3 = (v1+v2+v3の結果) という文字列を表示するためのラベル -->
     <s:Label id="resultLabel" includeIn="State2"/>
     <s:Button includeIn="State2" label="back" id="backButton" click="backButton_clickHandler(event)"/>
     
    </s:Application>
    


    Ex2_4.mxml
    <?xml version="1.0" encoding="utf-8"?>
    <s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
          xmlns:s="library://ns.adobe.com/flex/spark" 
          xmlns:mx="library://ns.adobe.com/flex/mx" width="100%" height="100%">
     <s:layout>
      <s:VerticalLayout horizontalAlign="center" verticalAlign="middle"/>
     </s:layout>
    
     <fx:Script>
      <![CDATA[
       import mx.controls.Alert;
       
       <!--- 値を保持する Vector -->
       private var values:Vector.<Number> = new Vector.<Number>();
       
       <!--- A + B の計算をして resultLabel に値を設定し、State2 に遷移するメソッド -->
       private function addButton_clickHandler(event:MouseEvent):void {
        // コンポーネントの値を values に格納。
        if (values.length == 0) {
         values.push(v1Ns.value);
         values.push(v2Ns.value);
         values.push(v3Ns.value);
        } else {
         values[0] = v1Ns.value;
         values[1] = v2Ns.value;
         values[2] = v3Ns.value;
        }
            
        currentState = "State2";
       }
    
       <!--- State2 から State1 に戻るボタン -->
       private function backButton_clickHandler(event:MouseEvent):void {
        currentState = "State1";
       }
      ]]>
     </fx:Script>
    
     <!-- state を用いて画面遷移を模倣 -->
     <s:states>
      <!-- A と B の値を入力する画面 -->
      <s:State name="State1"/>
      <!-- A + B の結果を表示する画面 -->
      <s:State name="State2" enterState="resultLabel.text = values[0].toString() + ' + ' + values[1].toString() + ' + ' + values[2].toString() + ' = ' + (values[0] + values[1] + values[2])"/>
     </s:states>
     
     <!-- State1用コンポーネント群 -->
     <mx:Form includeIn="State1">
      <mx:FormItem label="Value 1">
       <!--- 値1 の値入力用 NumericStepper -->
       <s:NumericStepper id="v1Ns"/>
      </mx:FormItem>
      <mx:FormItem label="Value 2">
       <!--- 値2 の値入力用 NumericStepper -->
       <s:NumericStepper id="v2Ns"/>
      </mx:FormItem>
      <mx:FormItem label="Value 3">
       <!--- 値3 の値入力用 NumericStepper -->
       <s:NumericStepper id="v3Ns"/>
      </mx:FormItem>
     </mx:Form>
     <s:Button includeIn="State1" label="add" id="addButton" click="addButton_clickHandler(event)"/>
     
     <!-- State2用コンポーネント群 -->
     <!--- v1 + v2 + v3 = (v1+v2+v3の結果) という文字列を表示するためのラベル -->
     <s:Label id="resultLabel" includeIn="State2"/>
     <s:Button includeIn="State2" label="back" id="backButton" click="backButton_clickHandler(event)"/>
     
    </s:Application>
    

    2010年5月6日木曜日

    flex で creationComplete 時に stage == null になる問題。

    ドキュメントによると、少なくとも activate されている間は stage が生きているらしいです。

    そのため、Event.ACTIVATE と Event.RESIZE イベント内なら確実に stage を扱えそうです。

    2010年5月2日日曜日

    GIMPを使い始めてみようと思ったり思わなかったり。

    以下、GIMPを使おうと思い立ってから以降のメモ書き。

    今は何でも屋としてサイト開発をしているので、グラフィック系のチームに画像やレイアウトの注文を任せるようなことができない今日この頃。

    グラフィックもやらにゃいかんだろうと思い、どうしたものかと思案中。

    PhotoShop をいきなり購入しても、3日で飽きたとかしゃれにならんし。

    ということで、GIMPに挑戦してみる。


    公式ページ

    http://www.gimp.org/


    Windows版ダウンロードページ

    http://www.gimp.org/windows/


    ダウンロード&インストール

    アプリとマニュアルの両方をダウンロードして実行。本体は国際化されているので、何語版というのはありません。

    2010年4月22日木曜日

    作業メモ:BlazeDS + Flex 環境の Enum ルール

    今回は、BlazeDS + Flex 環境で Enum をどのように扱うかについて考察してみます。

    基本事項

    基本的な設定やその他基本事項については、以下に準じます。

    ActionScript 側の enum 名格納変数について

    ActionScript 側の enum 名格納変数を name とします。AMFで通信される情報も java の Enum#name() に準じたものにします。そのため、以下のような独自のEnumProxyを定義します。
    package com.objectfanatics.flex.messaging.io;
    
    import java.util.Collections;
    import java.util.List;
    
    import flex.messaging.io.AbstractProxy;
    import flex.messaging.io.BeanProxy;
    
    public class EnumProxy extends BeanProxy {
    
        private static final String ACTION_SCRIPT_ENUM_NAME_PROPERTY_NAME = "name";
        private static final List<String> propertyNames = Collections.singletonList(ACTION_SCRIPT_ENUM_NAME_PROPERTY_NAME);
    
        @SuppressWarnings("unchecked")
        public Object createInstance(String className) {
            return new EnumStub((Class<Enum>) AbstractProxy.getClassFromClassName(className));
        }
    
        public Object getValue(Object instance, String propertyName) {
            if (propertyName.equals(ACTION_SCRIPT_ENUM_NAME_PROPERTY_NAME)) {
                return Enum.class.cast(instance).name();
            } else {
                throw new IllegalArgumentException("No property named: " + propertyName + " on enum type");
            }
        }
    
        public void setValue(Object instance, String propertyName, Object value) {
            if (propertyName.equals(ACTION_SCRIPT_ENUM_NAME_PROPERTY_NAME)) {
                EnumStub.class.cast(instance).value = String.class.cast(value);
            } else {
                throw new IllegalArgumentException("no EnumStub property: " + propertyName);
            }
        }
    
        @SuppressWarnings("unchecked")
        public Object instanceComplete(Object instance) {
            EnumStub es = EnumStub.class.cast(instance);
            return Enum.valueOf((Class) es.cl, es.value);
        }
    
        @SuppressWarnings("unchecked")
        public List getPropertyNames(Object instance) {
            if (!(instance instanceof Enum)) {
                throw new IllegalArgumentException("getPropertyNames called with non Enum object");
            } else {
                return propertyNames;
            }
        }
    
        private class EnumStub {
            private Class<?> cl;
            private String value;
            private EnumStub(Class<?> c) {
                cl = c;
            }
        }
    }
    

    同時に HttpFlexSession の static ブロックで以下のように定義し、あらゆる Enum 型に対して EnumProxy が適用されるようにします。
    // patch for enum serialization
      static {
          PropertyProxyRegistry.getRegistry().register(Enum.class, new EnumProxy());
      }
    

    ActionScript 側の enum ルール

    ActionScript 側の enum は、以下のような形式で作成します。
    // Sample enum class.
    package com.objectfanatics.englishfanatics.dataobject {
        import com.objectfanatics.flex.EnumBase;
        
        // fqcn of java counterpart enum class
        [RemoteClass(alias="com.objectfanatics.sample.SampleEnum")]
        
        // class name should be the same as the name of the java counterpart enum class.
        public class SampleEnum extends EnumBase {
            
            // enums
            //  - const name and the value of the first parameter of the constructor should be the same.
            public static const KUMA_SAN:SampleEnum = new SampleEnum("KUMA_SAN", "くまさん");
            public static const TANUKI_SAN:SampleEnum = new SampleEnum("TANUKI_SAN", "たぬきさん");
            
            // constructor
            //  - default parameter values are required for deserialized enums.
            //  - this must be invoked only for enum constant creation in this class and deserialized enum creation.
            public function SampleEnum(name:String = null, string:String = null) {
                super(name, string);
            }
            
            // returns all enum constants in this class.
            //  - this method is workaround for ActionScript3.0's limitation. 
            //    static methods are not inherited and cannot be overridden in ActionScript 3.0.
            public static function values():Array {
                return superValues();
            }
        }
    }
    

    すべての enum は下記のベースクラスを継承します。
    package com.objectfanatics.flex {
        import flash.utils.getDefinitionByName;
        import flash.utils.getQualifiedClassName;
        
        /**
         * Base class of enum object.
         */
        public class EnumBase {
            // key = enum class name, value = enum array.
            private static const valuesMap:Object = new Object();
            
            // enum class name
            private var enumClassName:String;
            
            // enum name
            public var name:String;
            
            // string for presentation
            protected var string:String;
            
            public function EnumBase(name:String = null, string:String = null) {
                this.name = name;
                this.string = string;
                enumClassName = getQualifiedClassName(this);
                if (string != null) {
                    // for normal enum
                    var values:Array = valuesMap[enumClassName];
                    if (values == null) values = new Array();
                    valuesMap[enumClassName] = values;
                    values.push(this);
                } else {
                    // for deserialized enum
                    return;
                }
            }
            
            // This method name is weird because of ActionScript 3.0's limitation.
            //   - Static methods are not inherited and cannot be overridden in ActionScript 3.0.
            public static function superValues():Array {
                // TODO: check.
                var blocks:Array = new Error().getStackTrace().split("at ");
                var block:String = blocks[2];
                var startIndex:int = block.indexOf("at ");// start of first line
                var endIndex:int = block.indexOf("$");    // end of class name
                var enumClassName:String = block.substring(startIndex + 1, endIndex);
                return valuesMap[enumClassName];
            }
            
            public function toString():String {
                if (string == null) {
                    var enumClass:Class = getDefinitionByName(enumClassName) as Class;
                    this.string = enumClass[name].string;
                }
                return string;
            }
            
            //use equality instead of identity.
            public function equals(other:EnumBase):Boolean {
                return this.name == other.name;
            }
        }
    }
    

    java 側の enum ルール

    Java 側の enum は、ActionScript 側の name プロパティと Java 側の Enum#name() の値が対応していれば、どのような形でもかまいません。

    以下例。
    package com.objectfanatics.sample;
    
    public enum SampleEnum {
        KUMA_SAN, TANUKI_SAN;
    }
    

    (´・ω・`) まだまともにに使ってないから、落とし穴がいっぱいありそうな気がする今日この頃。

    2010年4月21日水曜日

    作業メモ:FlexのRemotingでjavaからにActionScript型をマッピングする方法

    前回は、Flex の Remoting で ActionScript から java に型をマッピングする方法について、実際に動作を確認しながら考えてみました。

    今回は、その逆の方向ということで、java から ActionScript に型をマッピングする方法について、実際に動作を確認しながら考えてみます。

    基本的な事項は作業メモ:FlexのRemotingでActionScriptからjavaに型をマッピングする方法参照のこと。


    Private Fields in Java to Public Fields in ActionScript

    基本的に動くようです。

    以下、JavaBeans規約を勘違いしてやらかした例。(´・ω・`)

    前回 services-config.xml ファイルのチャネルの serialization の設定として include-read-only オプションを追加したのですが、うまく動作しないようです。

    シリアライズ対象の Java のクラスは以下のフィールドを持っていて、すべてのフィールドは getter を持っています。
    private long id;
    private String eText;
    private PartOfSpeech partOfSpeech;
    private String jText;
    

    仕様どおりなら、public な getter があるので正しく動作するはずです。実装的にも Bean の introspection を利用し判断しているのであれば、問題なく動作するはずなのですが、、、。

    しかも、気持ち悪いことに、id だけは正しく伝わっていました。

    試しに Java 側の private フィールドを public に変更してみたところ、ActionScript 側にデータが正しく伝わりました。

    ということで、原因を調査してみることにしました。

    結論。JavaBeans規約的に、おいらが間違ってました。
    (´・ω・`) 先頭の2文字が大文字の場合、プロパティ名の先頭は小文字に変換されないのです。

    JavaBeans API specification(1.01) の 8.8 Capitalization of inferred names にかかれています。
    すっかり忘れてました。Sun出身なのにw


    Enums in Java to Enum Like Objects in ActionScript

    (´・ω・`) TypeError: Error #1034: 強制型変換に失敗しました。

    おいらはいままでのところ、作業ログ:BlazeDSのRemotingServiceでJavaのenumを扱う方法で書いたような方法で Enum を扱っていました。そのため、Java 側から String として渡ってくる情報を ActionScript 上で Enum 的オブジェクトに変換しなければなりません。しかし、FlashPlayer 側に手を入れるわけにも行きません。

    少し調べてみると、BLZ-17 に java の Enum と AS3 のクラスのマッピングができるようになっているという話を発見。

    そちらに書かれている方法どおりにやってみたところ、問題なく動作しました。

    初期化用の static ブロックは、web.xml の先頭で listener として読み込まれているという理由で、flex.messaging.HttpFlexSession クラスの先頭に追加しました。以下、追加されたソースです。
    // patch for enum serialization
        static {
                PropertyProxyRegistry.getRegistry().register(Enum.class, new EnumProxy());
        }
    

    2010年4月20日火曜日

    作業メモ:FlexのRemotingでActionScriptからjavaに型をマッピングする方法

    今回は、Flex の Remoting で ActionScript から java に型をマッピングする方法について、実際に動作を確認しながら考えてみます。

    基本情報

    基本的な情報は以下に書かれています。

    以下、各型についてマッピングの実験をしてみます。


    ActonScript to Java : Boolean, String of "true" or "false" to java.lang.Boolean

    Java side test code.
    public boolean asToJavaBooleanTest(boolean asBooleanTrue, boolean asBooleanFalse, boolean stringTrue, boolean stringFalse) {
        return 
            (asBooleanTrue  == true ) && 
            (asBooleanFalse == false) &&
            (stringTrue     == true ) &&
            (stringFalse    == false);
    }
    

    Flex side test code.
    public static function asToJavaBooleanTest():void {
        remoteObjectWrapper.invoke(true, false, "true", "false");
    }
    


    ActonScript to Java : flash.utils.ByteArray to byte []

    Java side test code.
    public boolean asToJavaByteArrayTest(byte[] bytes) {
        return 
            (bytes[0] == 0 ) && 
            (bytes[1] == 1 ) && 
            (bytes[2] == 2 );
    }
    

    Flex side test code.
    public static function asToJavaByteArrayTest():void {
        var byteArray:ByteArray = new ByteArray();
        byteArray.writeByte(0);
        byteArray.writeByte(1);
        byteArray.writeByte(2);
        remoteObjectWrapper.invoke(byteArray);
    }
    


    ActonScript to Java : Date to java.util.Date

    Java side test code.
    public boolean asToJavaDateTest(Date date) {
        return date.getTime() == 1271699264375L;
    }
    

    Flex side test code.
    public static function asToJavaDateTest():void {
        var date:Date = new Date();
        date.time = 1271699264375;
        remoteObjectWrapper.invoke(date);
    }
    


    ActonScript to Java : int/uint to java.lang.Integer

    Java side test code.
    public boolean asToJavaIntTest(int intValue, int uintValue1, int uintValue2) {
        return
            (intValue   == -1        ) &&
            (uintValue1 == 2147483647) && // rounded from 2147483648
            (uintValue2 == 2147483647);   // rounded from 2147483649
    }
    

    Flex side test code.
    public static function asToJavaIntTest():void {
        var intValue:int = -1;
        // java: Integer.MAX_VALUE = 2147483647.
        var uintValue1:uint = 2147483648; // this will be rounded to 2147483647!
        var uintValue2:uint = 2147483649; // this will be rounded to 2147483647!
        remoteObjectWrapper.invoke(intValue, uintValue1, uintValue2);
    }
    

    このテストの結果から、以下のことが分かります。
    • ActionScript 側 の uint は Integer.MAX_VALUE = 2147483647 よりも大きい値を扱うことができる。
    • Java 側は、Integer.MAX_VALUE より大きい値が渡されると Integer.MAX_VALUE に丸めてしまう。
    • このことから、Java との連携が決まっている場合、uint は使わないほうが無難のようです。


    ActonScript to Java : Number to java.lang.Double

    Java side test code.
    public boolean asToJavaNumberTest(double numberValue) {
        return
            (numberValue == -1.5);
    }
    

    Flex side test code.
    public static function asToJavaNumberTest():void {
        remoteObjectWrapper.invoke(new Number(-1.5));
    }
    


    ActonScript to Java : String to java.lang.String

    Java side test code.
    public boolean asToJavaStringTest(String stringValue1, String stringValue2, String stringValue3) {
        return
            (stringValue1.equals("")) &&
            (stringValue2.equals("foo")) &&
            (stringValue3 == null); 
    }
    

    Flex side test code.
    public static function asToJavaStringTest():void {
        var stringValue1:String = "";
        var stringValue2:String = "foo";
        var undefinedValue:String;   // Note: undefined is converted to null.
        var nullValue:String = null; // Note: null is converted to null.
        remoteObjectWrapper.invoke(stringValue1, stringValue2, undefinedValue, nullValue);
    }
    

    ActionScript の undefined と null は、Java の null に変換されるようです。


    ActonScript to Java : Object (generic) to java.util.Map

    Java side test code.
    public boolean asToJavaGenericObjectTest(Map<String,Object> nullValue, Map<String,Object> simpleObject, Map<String,Object> hasNullProperty, Map<String,Object> hasStringProperty, Map<String,Object> hasDateProperty, Map<String,Object> hasNestedProperty) {
        return
            (nullValue == null) &&
            (simpleObject != null) &&
            (hasNullProperty.get("property") == null) &&
            (hasStringProperty.get("property").equals("foo")) &&
            (Date.class.isInstance(hasDateProperty.get("property"))) &&
            (Date.class.isInstance(Map.class.cast(hasNestedProperty.get("property")).get("nestedProperty")));
    }
    

    Flex side test code.
    public static function asToJavaGenericObjectTest():void {
        // null
        var nullValue:Object = null;
        
        // simple object
        var simpleObject:Object = new Object();
        
        // has null property
        var hasNullProperty:Object = new Object();
        hasNullProperty.property = null;
        
        // has String object
        var hasStringProperty:Object = new Object();
        hasStringProperty.property = "foo"
            
        // has Data object
        var hasDateProperty:Object = new Object();
        hasDateProperty.property = new Date();
        
        // has nested property
        var hasNestedProperty:Object = new Object();
        hasNestedProperty.property = new Object();
        hasNestedProperty.property.nestedProperty = new Date();
        
        remoteObjectWrapper.invoke(nullValue, simpleObject, hasNullProperty, hasStringProperty, hasDateProperty, hasNestedProperty);
    }
    
    ネストされても、特に問題なく動作するようです。


    ActonScript to Java : typed Object to typed Object

    private コンストラクタのみのJavaクラスを対象にしてみたところ、以下のように怒られてしまいました。
    Unable to create a new instance of type 'experiment.SimpleObject'. 
    Types must have a public, no arguments constructor.
    

    ObjectFanatics 的方針としては、public のデフォルトコンストラクタの強制など容認できるはずもありません。
    ということで、ソースにパッチを当てて対応しました。

    引数無しの private コンストラクタが必要になってしまうのと、そのために final の変数の利用がほぼ絶望的になるという点がかなり厳しいのですが、とりあえずは妥協することにしましょう。将来的には、引数のある private コンストラクタに対応しようと思います。(TODO: 引数なしのプライベートコンストラクタを new してから値を格納する方法ではなく、private コンストラクタの引数にシリアライズされたオブジェクトを復元していく方法を実装する。)


    お次は、java 側で private field に値を入れることを期待していたのですが、以下のように怒られてしまいました。
    Cannot create class of type 'experiment.WithPrivateFileds'
    
    ObjectFanatics 的方針としては、private field が使えない制約など容認できるはずもありません。

    ちょっと調べてみると、こんな issue がありました。
    BLZ-427 - Add configuration option for BeanProxy#includeReadOnly property.これか!?
    とおもいきや、これは Java から ActionScript 側への変換に関してだけみたいですね。
    しかし、BeanProxyのフィールドに対する判断基準になる設定なので、必要です。

    ということで、services-config.xml ファイルのチャネル定義に serialization の設定として以下のように include-read-only オプションを追加します。
    <channels>
      <channel-definition id="amf" class="mx.messaging.channels.AMFChannel">
        <endpoint url="http://{server.name}:{server.port}/{context.root}/messagebroker/amf" class="flex.messaging.endpoints.AMFEndpoint" />
        <properties>
          <serialization>
            <include-read-only>true</include-read-only>
          </serialization>
        </properties>
      </channel-definition>
    </channels>
    


    また、ActionScript 側から渡された値を Java 側の private フィールドに格納する処理も必要です。

    ということで、ソースにパッチを当てて対応しました。

    Java side test code.
    public boolean asToJavaTypedObjectTest(SimpleObject nullValue, SimpleObject simpleObject, SimpleObjectWithPrivateConstructor simpleObjectWithPrivateConstructor, SimpleObjectWithProperties simpleObjectWithProperties, NestableObject nestableObject, WithPrivateFiled withPrivateFiled) {
        return
            (nullValue                               == null) &&
            (simpleObject                            != null) &&
            (simpleObjectWithPrivateConstructor      != null) &&
            (simpleObjectWithProperties.intValue     == 10 ) &&
            (simpleObjectWithProperties.stringValue.equals("foo")) &&
            (simpleObjectWithProperties.simpleObject != null) &&
            (nestableObject.nestableObject.nestableObject.nestableObject == null) &&
            (withPrivateFiled.getStringValue().equals("foo"));
    }
    

    Flex side test code.
    public static function asToJavaTypedObjectTest():void {
        // null
        var nullValue:Object = null;
        
        // simple object
        var simpleObject:Object = new SimpleObject();
        
        // simple object with private constructor
        var simpleObjectWithPrivateConstructor:SimpleObjectWithPrivateConstructor = new SimpleObjectWithPrivateConstructor();
        
        // simple object with properties
        var simpleObjectWithProperties:SimpleObjectWithProperties = new SimpleObjectWithProperties(10, "foo", new SimpleObject());
        
        // nested properties
        var nestableObject:NestableObject = new NestableObject(new NestableObject(new NestableObject(null)));
        
        // java object has private fields
        var withPrivateFiled:WithPrivateFiled = new WithPrivateFiled("foo");
        
        // enum
        var enumObject:EnumObject = EnumObject.KUMA_SAN;
    
        remoteObjectWrapper.invoke(nullValue, simpleObject, simpleObjectWithPrivateConstructor, simpleObjectWithProperties, nestableObject, withPrivateFiled);
    }
    


    ActonScript to Java : Object to enum

    基本方針は、Enums in Java to Enum Like Objects in ActionScriptと同じです。

    ActionScript側ではpublicなプロパティを1つだけ持ったオブジェクトをEnumとして扱い、明示的に java 側のEnumにマッピングしないようにします。プロパティの値は java 側の Enum の name と同じにしておきます。

    Java side test code.
    public boolean asToJavaEnumTest(EnumObject enumValue) {
        return
            (enumValue.equals(EnumObject.KUMA_SAN));
    }
    

    Flex side test code.
    public static function asToJavaEnumTest():void {
        var enumValue:EnumObject = EnumObject.KUMA_SAN;
        remoteObjectWrapper.invoke(enumValue);
    }
    


    以下、未検証
    • Array (dense) to java.util.List
    • Array (sparse) to java.util.Map
    • XML to org.w3c.dom.Document
    • XMLDocument(legacy XML type)  to org.w3c.dom.Document


    Patch for Private Constructor

    変更前
    110: Object instance = cls.newInstance();
    

    変更後
    try {
        instance = cls.newInstance();
    } catch (IllegalAccessException e){
        for (Constructor<? extends Object> constructor : cls.getDeclaredConstructors()) {
            if (constructor.getParameterTypes().length != 0) continue;
            constructor.setAccessible(true);
            instance = constructor.newInstance();
            break;
        }
        if (instance == null) throw e;
    }
    



    Patch for Private Field Serialization

    flex.messaging.io.BeanProxy#setValue(Object instance, String propertyName, Object value)
    public void setValue(Object instance, String propertyName, Object value)
        {
            BeanProperty bp = getBeanProperty(instance, propertyName);
    
            if (bp != null)
            {
    // patch for private field.
    //            if (bp.isWrite())
    //            {
                    try
                    {
                        Class desiredPropClass = bp.getType();
                        TypeMarshaller marshaller = TypeMarshallingContext.getTypeMarshaller();
                        value = marshaller.convert(value, desiredPropClass);
                        ClassUtil.validateAssignment(instance, propertyName, value);
                        bp.set(instance, value);
                    }
                    catch (Exception e)
                    {
                        SerializationContext context = getSerializationContext();
    
                        // Log ignore failed property set errors
                        if (Log.isWarn() && logPropertyErrors(context))
                        {
                            Logger log = Log.getLogger(LOG_CATEGORY);
                            log.warn("Failed to set property {0} on type {1}.",
                                    new Object[] {propertyName, getAlias(instance)}, e);
                        }
    
                        if (!ignorePropertyErrors(context))
                        {
                            // Failed to get property '{propertyName}' on type '{className}'.
                            MessageException ex = new MessageException();
                            ex.setMessage(FAILED_PROPERTY_WRITE_ERROR, new Object[] {propertyName, getAlias(instance)});
                            ex.setRootCause(e);
                            throw ex;
                        }
                    }
    // patch for private field.
    //            }
    //            else
    //            {
    //                SerializationContext context = getSerializationContext();
    //
    //                if (Log.isWarn() && logPropertyErrors(context))
    //                {
    //                    Logger log = Log.getLogger(LOG_CATEGORY);
    //                    log.warn("Property {0} not writable on class {1}",
    //                            new Object[] {propertyName, getAlias(instance)});
    //                }
    //
    //                if (!ignorePropertyErrors(context))
    //                {
    //                    //Property '{propertyName}' not writable on class '{alias}'.
    //                    MessageException ex = new MessageException();
    //                    ex.setMessage(NON_WRITABLE_PROPERTY_ERROR, new Object[] {propertyName, getAlias(instance)});
    //                    throw ex;
    //                }
    //            }
            }
            else
            {
                SerializationContext context = getSerializationContext();
    
                if (Log.isWarn() && logPropertyErrors(context))
                {
                    Logger log = Log.getLogger(LOG_CATEGORY);
                    log.warn("Ignoring set property {0} for type {1} as a setter could not be found.",
                                new Object[] {propertyName, getAlias(instance)});
                }
    
                if (!ignorePropertyErrors(context))
                {
                    // Property '{propertyName}' not found on class '{alias}'.
                    MessageException ex = new MessageException();
                    ex.setMessage(UNKNOWN_PROPERTY_ERROR, new Object[] {propertyName, getAlias(instance)});
                    throw ex;
                }
            }
        }
    


    Test source code(Java)

    experiment/EnumObject.java
    package experiment;
    
    public enum EnumObject {
        KUMA_SAN("くまさん"),
        TANUKI_SAN("たぬきさん");
        private final String string;
        private EnumObject(String string) {
            this.string = string;
        }
    }
    

    experiment/NestableObject.java
    package experiment;
    
    public class NestableObject {
        public NestableObject nestableObject;
        private NestableObject(){}
        public NestableObject(NestableObject nestableObject) {
            this.nestableObject = nestableObject;
        }
    }
    

    experiment/SerializationTestService.java
    package test {    
        import flash.utils.ByteArray;
        
        import mx.controls.Alert;
    
        public final class SerializationTestService {
            private static var remoteObjectWrapper:RemoteObjectWrapper = new RemoteObjectWrapper("SerializationTestService");
            
            public static function asToJavaBooleanTest():void {
                remoteObjectWrapper.invoke(true, false, "true", "false");
            }
            
            public static function asToJavaByteArrayTest():void {
                var byteArray:ByteArray = new ByteArray();
                byteArray.writeByte(0);
                byteArray.writeByte(1);
                byteArray.writeByte(2);
                remoteObjectWrapper.invoke(byteArray);
            }
            
            public static function asToJavaDateTest():void {
                var date:Date = new Date();
                date.time = 1271699264375;
                remoteObjectWrapper.invoke(date);
            }
            
            public static function asToJavaIntTest():void {
                var intValue:int = -1;
                // java: Integer.MAX_VALUE = 2147483647.
                var uintValue1:uint = 2147483648; // this will be rounded to 2147483647!
                var uintValue2:uint = 2147483649; // this will be rounded to 2147483647!
                remoteObjectWrapper.invoke(intValue, uintValue1, uintValue2);
            }
            
            public static function asToJavaNumberTest():void {
                remoteObjectWrapper.invoke(new Number(-1.5));
            }
            
            public static function asToJavaStringTest():void {
                var stringValue1:String = "";
                var stringValue2:String = "foo";
                var undefinedValue:String;   // Note: undefined is converted to null.
                var nullValue:String = null; // Note: null is converted to null.
                remoteObjectWrapper.invoke(stringValue1, stringValue2, undefinedValue, nullValue);
            }
            
            public static function asToJavaGenericObjectTest():void {
                // null
                var nullValue:Object = null;
                
                // simple object
                var simpleObject:Object = new Object();
                
                // has null property
                var hasNullProperty:Object = new Object();
                hasNullProperty.property = null;
                
                // has String object
                var hasStringProperty:Object = new Object();
                hasStringProperty.property = "foo"
                    
                // has Data object
                var hasDateProperty:Object = new Object();
                hasDateProperty.property = new Date();
                
                // has nested property
                var hasNestedProperty:Object = new Object();
                hasNestedProperty.property = new Object();
                hasNestedProperty.property.nestedProperty = new Date();
                
                remoteObjectWrapper.invoke(nullValue, simpleObject, hasNullProperty, hasStringProperty, hasDateProperty, hasNestedProperty);
            }
            
            public static function asToJavaTypedObjectTest():void {
                // null
                var nullValue:Object = null;
                
                // simple object
                var simpleObject:Object = new SimpleObject();
                
                // simple object with private constructor
                var simpleObjectWithPrivateConstructor:SimpleObjectWithPrivateConstructor = new SimpleObjectWithPrivateConstructor();
                
                // simple object with properties
                var simpleObjectWithProperties:SimpleObjectWithProperties = new SimpleObjectWithProperties(10, "foo", new SimpleObject());
                
                // nested properties
                var nestableObject:NestableObject = new NestableObject(new NestableObject(new NestableObject(null)));
                
                // java object has private fields
                var withPrivateFiled:WithPrivateFiled = new WithPrivateFiled("foo");
                
                // enum
                var enumObject:EnumObject = EnumObject.KUMA_SAN;
    
                remoteObjectWrapper.invoke(nullValue, simpleObject, simpleObjectWithPrivateConstructor, simpleObjectWithProperties, nestableObject, withPrivateFiled);
            }
            
            public static function asToJavaEnumTest():void {
                var enumValue:EnumObject = EnumObject.KUMA_SAN;
                remoteObjectWrapper.invoke(enumValue);
            }
            
            private static function createResultHandler(methodName:String):Function {
                return function(result:Boolean):void{Alert.show("methodName : " + (result ? "passed" : "failed"))}
            }
        }
    }
    

    experiment/SimpleObject.java
    package experiment;
    
    public class SimpleObject {
    }
    

    experiment/SimpleObjectWithPrivateConstructor.java
    package experiment;
    
    public class SimpleObjectWithPrivateConstructor {
        private SimpleObjectWithPrivateConstructor(){};
    }
    

    experiment/SimpleObjectWithProperties.java
    package experiment;
    
    public class SimpleObjectWithProperties {
        public int intValue;
        public String stringValue;
        public SimpleObject simpleObject;
        private SimpleObjectWithProperties(){}
        public SimpleObjectWithProperties(int intValue, String stringValue, SimpleObject simpleObject) {
            this.intValue = intValue;
            this.stringValue = stringValue;
            this.simpleObject = simpleObject;
        }    
    }
    

    experiment/WithPrivateFiled.java
    package experiment;
    
    public class WithPrivateFiled {
        private String stringValue;
        private WithPrivateFiled(){}
        public WithPrivateFiled(String stringValue) { this.stringValue = stringValue; }
        public String getStringValue() { return stringValue; }
    }
    


    Test source code(Flex)

    test/EnumObject.as
    package test {
        
        [RemoteClass(alias="experiment.EnumObject")]
        public class EnumObject {
            
            // Enum constants
            public static const KUMA_SAN:EnumObject = new EnumObject("KUMA_SAN", "くまさん");
            public static const TANUKI_SAN:EnumObject = new EnumObject("TANUKI_SAN", "たぬきさん");
            
            // Enum name. This must be public and not static valiable.
            public var val:String;
            
            [Transient]
            public var string:String;
            
            // Constructor
            public function EnumObject(name:String, string:String) {
                this.val = name;
                this.string = string;
            }
            
            // for displaying.
            public function toString():String {
                return string;
            }
            
            // use equality instead of identity because of the limitation of ActionScript.
            public function equals(object:Object):Boolean {
                return this.toString() == object.toString();
            }
        }
    }
    

    test/NestableObject.as
    package test {
        
        [RemoteClass(alias="experiment.NestableObject")]
        public class NestableObject {
            
            // nested value
            public var nestableObject:NestableObject;
            
            // constructor
            public function NestableObject(nestableObject:NestableObject) {
                this.nestableObject = nestableObject;
            }
        }
    }
    

    test/RemoteObjectWrapper.as
    package test {
        import mx.controls.Alert;
        import mx.rpc.events.FaultEvent;
        import mx.rpc.events.ResultEvent;
        import mx.rpc.remoting.Operation;
        import mx.rpc.remoting.RemoteObject;
    
        public class RemoteObjectWrapper {
            private var remoteObject:RemoteObject;
            public function RemoteObjectWrapper(destination:String):void {
                this.remoteObject = new RemoteObject(destination);
            }
            public function invoke(...args):void {
                var mathodName:String = getCallerSimpleName();
                remoteObject[mathodName].addEventListener("result", function (event:ResultEvent):void{Alert.show(mathodName + " : " + (event.result ? "passed" : "failed"))});
                remoteObject.addEventListener(FaultEvent.FAULT, function(faultEvent:FaultEvent):void{Alert.show("Fault : " + faultEvent)});
                var operation:Operation = remoteObject[mathodName];
                operation.arguments = args;
                operation.send();
            }
            private function getCallerSimpleName():String {
                var block:String = new Error().getStackTrace().split("at ")[3];
                var startIndex:int = block.indexOf("at ");// start of first line
                var endIndex:int = block.indexOf("()");   // end of function name
                var fullName:String = block.substring(startIndex + 3, endIndex);
                var callerSimpleName:String = fullName.substring(fullName.indexOf("/") + 1, endIndex);
                return callerSimpleName; // this is caller's caller.
            }
        }
    }
    

    test/SerializationTestService.as
    package test {    
        import flash.utils.ByteArray;
        
        import mx.controls.Alert;
    
        public final class SerializationTestService {
            private static var remoteObjectWrapper:RemoteObjectWrapper = new RemoteObjectWrapper("SerializationTestService");
            
            public static function asToJavaBooleanTest():void {
                remoteObjectWrapper.invoke(true, false, "true", "false");
            }
            
            public static function asToJavaByteArrayTest():void {
                var byteArray:ByteArray = new ByteArray();
                byteArray.writeByte(0);
                byteArray.writeByte(1);
                byteArray.writeByte(2);
                remoteObjectWrapper.invoke(byteArray);
            }
            
            public static function asToJavaDateTest():void {
                var date:Date = new Date();
                date.time = 1271699264375;
                remoteObjectWrapper.invoke(date);
            }
            
            public static function asToJavaIntTest():void {
                var intValue:int = -1;
                // java: Integer.MAX_VALUE = 2147483647.
                var uintValue1:uint = 2147483648; // this will be rounded to 2147483647!
                var uintValue2:uint = 2147483649; // this will be rounded to 2147483647!
                remoteObjectWrapper.invoke(intValue, uintValue1, uintValue2);
            }
            
            public static function asToJavaNumberTest():void {
                remoteObjectWrapper.invoke(new Number(-1.5));
            }
            
            public static function asToJavaStringTest():void {
                var stringValue1:String = "";
                var stringValue2:String = "foo";
                var undefinedValue:String;   // Note: undefined is converted to null.
                var nullValue:String = null; // Note: null is converted to null.
                remoteObjectWrapper.invoke(stringValue1, stringValue2, undefinedValue, nullValue);
            }
            
            public static function asToJavaGenericObjectTest():void {
                // null
                var nullValue:Object = null;
                
                // simple object
                var simpleObject:Object = new Object();
                
                // has null property
                var hasNullProperty:Object = new Object();
                hasNullProperty.property = null;
                
                // has String object
                var hasStringProperty:Object = new Object();
                hasStringProperty.property = "foo"
                    
                // has Data object
                var hasDateProperty:Object = new Object();
                hasDateProperty.property = new Date();
                
                // has nested property
                var hasNestedProperty:Object = new Object();
                hasNestedProperty.property = new Object();
                hasNestedProperty.property.nestedProperty = new Date();
                
                remoteObjectWrapper.invoke(nullValue, simpleObject, hasNullProperty, hasStringProperty, hasDateProperty, hasNestedProperty);
            }
            
            public static function asToJavaTypedObjectTest():void {
                // null
                var nullValue:Object = null;
                
                // simple object
                var simpleObject:Object = new SimpleObject();
                
                // simple object with private constructor
                var simpleObjectWithPrivateConstructor:SimpleObjectWithPrivateConstructor = new SimpleObjectWithPrivateConstructor();
                
                // simple object with properties
                var simpleObjectWithProperties:SimpleObjectWithProperties = new SimpleObjectWithProperties(10, "foo", new SimpleObject());
                
                // nested properties
                var nestableObject:NestableObject = new NestableObject(new NestableObject(new NestableObject(null)));
                
                // java object has private fields
                var withPrivateFiled:WithPrivateFiled = new WithPrivateFiled("foo");
                
                // enum
                var enumObject:EnumObject = new EnumObject("");
    
                remoteObjectWrapper.invoke(nullValue, simpleObject, simpleObjectWithPrivateConstructor, simpleObjectWithProperties, nestableObject, withPrivateFiled);
            }
            
            public static function asToJavaEnumTest():void {
                var enumValue:EnumObject = EnumObject.KUMA_SAN;
                remoteObjectWrapper.invoke(enumValue);
            }
            
            private static function createResultHandler(methodName:String):Function {
                return function(result:Boolean):void{Alert.show("methodName : " + (result ? "passed" : "failed"))}
            }
        }
    }
    

    test/SerializationTestServiceApp.mxml
    <?xml version="1.0" encoding="utf-8"?>
    <!-- Application to invoke serialization tests. -->
    
    <s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
                   xmlns:s="library://ns.adobe.com/flex/spark" 
                   xmlns:mx="library://ns.adobe.com/flex/mx" width="100%" height="100%">
        <s:layout>
            <s:VerticalLayout horizontalAlign="center" verticalAlign="middle"/>
        </s:layout>
        <fx:Script>
            <![CDATA[
                import test.SerializationTestService;
                protected function test():void {
                    SerializationTestService.asToJavaBooleanTest();
                    SerializationTestService.asToJavaByteArrayTest();
                    SerializationTestService.asToJavaDateTest();
                    SerializationTestService.asToJavaIntTest();
                    SerializationTestService.asToJavaNumberTest();
                    SerializationTestService.asToJavaStringTest();
                    SerializationTestService.asToJavaGenericObjectTest();
                    SerializationTestService.asToJavaTypedObjectTest();
                    SerializationTestService.asToJavaEnumTest();
                }
            ]]>
        </fx:Script>
        <s:Button id="testButton" label="Test" click="test()"/>
    </s:Application>
    

    test/SimpleObject.as
    package test {
        [RemoteClass(alias="experiment.SimpleObject")]
        public class SimpleObject {
            public function SimpleObject() {}
        }
    }
    

    test/SimpleObjectWithPrivateConstructor.as
    package test {
        [RemoteClass(alias="experiment.SimpleObjectWithPrivateConstructor")]
        public class SimpleObjectWithPrivateConstructor {
            public function SimpleObjectWithPrivateConstructor() {
            }
        }
    }
    

    test/SimpleObjectWithProperties.as
    package test {
     [RemoteClass(alias="experiment.SimpleObjectWithProperties")]
     public class SimpleObjectWithProperties {
      
      // properties
      public var intValue:int;
      public var stringValue:String;
      public var simpleObject:SimpleObject;
      
      // constructor
      public function SimpleObjectWithProperties(intValue:int, stringValue:String, simpleObject:SimpleObject):void {
       this.intValue = intValue;
       this.stringValue = stringValue;
       this.simpleObject = simpleObject;
      }
     }
    }
    

    test/WithPrivateFiled.as
    package test {
        [RemoteClass(alias="experiment.WithPrivateFiled")]
        public class WithPrivateFiled {
            
            // The field of ActionSide is public. Java side of that is private.
            public var stringValue:String;
            
            // constructor
            public function WithPrivateFiled(stringValue:String) {
                this.stringValue = stringValue;
            }
        }
    }