2010年12月1日水曜日

asdocでjavadocの-linkオプションのようなことをしようと思ったが、、、

自作ライブラリの asdoc から as3 の core に含まれている Number とか Boolean とか Vector などの asdoc へのリンクを貼りたくて、調べてみました。

すると、同様の要求が下記のように登録されていました。

ASDoc: Hyperlinks to Actionscript Language Reference documentation
For example, if a method in my custom class returns a Boolean, I would like to be able to click on the word "Boolean", which would take me to the Boolean documentation in the AS3.0 Language Reference:
http://help.adobe.com/en_US/AS3LCR/Flash_10.0/Boolean.html
まさにこの機能! (`・ω・´)

しかし、type が Minor Enhancement として登録されています。

マイナーらしいです。 (´・ω・`)

しかも、
Nice request but out of scope for this release. Deferring.
として、クローズされてます。

as3commonsのasdocでも対応してないところをみると、今のところ有効な手段はないかもしれませんね。

2010年11月27日土曜日

flex-mojos と asdoc

公式ページ
http://flexmojos.sonatype.org/

flexmojos-maven-plugin のある場所
http://repository.sonatype.org/content/groups/flexgroup/org/sonatype/flexmojos/flexmojos-maven-plugin/

com.adobe.flex.framework系のある場所
以下あたりを参考にして、バージョンを決めればよさそう。
https://repository.sonatype.org/content/groups/flexgroup/com/adobe/flex/framework/framework/

mvn test と mvn verify ができる最低限の pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    
    <!-- pom.xml参考 : http://dev.yoolab.org/maven/content/repositories/releases/org/as3commons/as3commons-project/1.0.1/as3commons-project-1.0.1.pom -->
    
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.objectfanatics</groupId>
    <artifactId>sandbox</artifactId>
    <packaging>swc</packaging>
    <name>ObjectFanatics Sandbox Project</name>
    <version>0.0.1-SNAPSHOT</version>
    <inceptionYear>2010</inceptionYear>
    <url>http://www.objectfanatics.com/</url>
    <description>ObjectFanatics Sandbox Project</description>
    
    <properties>
        <!-- flex-mojos version -->
        <flex-mojos.version>3.5.0</flex-mojos.version>

        <!-- flex version -->
        <flex.version>3.5.0.12683</flex.version>

        <!-- flashplayer version -->
        <flashplayer.version>${flashplayer.version.major}.${flashplayer.version.minor}.${flashplayer.version.revision}</flashplayer.version>
        <flashplayer.version.major>10</flashplayer.version.major>
        <flashplayer.version.minor>0</flashplayer.version.minor>
        <flashplayer.version.revision>0</flashplayer.version.revision>
    </properties>
    
    <!-- 開発者情報 -->
    <developers>
        <developer>
            <id>makoto.sato</id>
            <name>Makoto Sato</name>
            <email>makoto.sato [at] objectfanatics.com</email>
            <url>http://www.objectfanatics.com</url>
            <organization>ObjectFanatics Ltd.</organization>
            <organizationUrl>http://www.objectfanatics.com</organizationUrl>
            <timezone>+9</timezone>
        </developer>
    </developers>

    <dependencies>
        
        <!-- Player/Air Global dependency. -->
        <dependency>
            <groupId>com.adobe.flex.framework</groupId>
            <artifactId>playerglobal</artifactId>
            <version>${flex.version}</version>
            <scope>external</scope>
            <type>swc</type>
            <classifier>${flashplayer.version.major}</classifier>
            <!--<classifier>${flashplayer.version.major}.${flashplayer.version.minor}</classifier>-->
        </dependency>
        
        <!-- for unit testing. http://asunit.org/ -->
        <dependency>
            <groupId>com.asunit</groupId>
            <artifactId>asunit</artifactId>
            <version>20071011</version>
            <type>swc</type>
            <scope>test</scope>
        </dependency>
        
        <!-- Dependencies for Flex. This includes flex classes. ex: ListCollectionView. -->
        <dependency>
            <groupId>com.adobe.flex.framework</groupId>
            <artifactId>framework</artifactId>
            <version>${flex.version}</version>
            <scope>external</scope>
            <type>swc</type>
        </dependency>
        
        <!-- Dependencies for Flex. This includes resource bundle. Not having this dependency causes some errors. ex: [ERROR] Unable to resolve resource bundle "collections" for locale "en_US". -->
        <dependency>
            <groupId>com.adobe.flex.framework</groupId>
            <artifactId>framework</artifactId>
            <version>${flex.version}</version>
            <type>rb.swc</type>
            <scope>external</scope>
        </dependency>

        <dependency>
            <groupId>com.adobe.flex.framework</groupId>
            <artifactId>utilities</artifactId>
            <version>${flex.version}</version>
            <type>swc</type>
            <scope>external</scope>
        </dependency>

        <dependency>
            <groupId>com.adobe.flex.framework</groupId>
            <artifactId>flex</artifactId>
            <version>${flex.version}</version>
            <type>swc</type>
            <scope>external</scope>
        </dependency>

        
        <dependency>
            <groupId>com.adobe.flex.framework</groupId>
            <artifactId>framework</artifactId>
            <version>${flex.version}</version>
            <scope>external</scope>
            <type>zip</type>
            <classifier>configs</classifier>
        </dependency>
        
        <dependency>
            <groupId>com.adobe.flex.framework</groupId>
            <artifactId>rpc</artifactId>
            <version>${flex.version}</version>
            <scope>external</scope>
            <type>swc</type>
        </dependency>
        
        <dependency>
            <groupId>com.adobe.flex.framework</groupId>
            <artifactId>rpc</artifactId>
            <version>${flex.version}</version>
            <type>rb.swc</type>
            <scope>external</scope>
        </dependency>

    </dependencies>
    
    <build>
        <sourceDirectory>src/main/actionscript</sourceDirectory>
        <testSourceDirectory>src/test/actionscript</testSourceDirectory>
        <defaultGoal>test</defaultGoal>

        <plugins>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-resources-plugin</artifactId>
                <configuration>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.sonatype.flexmojos</groupId>
                <artifactId>flexmojos-maven-plugin</artifactId>
                <version>${flex-mojos.version}</version>
                <extensions>true</extensions>
                <dependencies>
                    <dependency>
                        <groupId>com.adobe.flex</groupId>
                        <artifactId>compiler</artifactId>
                        <version>${flex.version}</version>
                        <type>pom</type>
                    </dependency>
                </dependencies>
                <configuration>
                    <configurationReport>true</configurationReport>
                    <targetPlayer>${flashplayer.version}</targetPlayer>
                    <locales>
                        <param>en_US</param>
                    </locales>
                    <updateSecuritySandbox>true</updateSecuritySandbox>
                    <includeAsClasses>
                        <source>
                            <directory>src/main/actionscript</directory>
                        </source>
                    </includeAsClasses>
                </configuration>
                </plugin>
                <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-site-plugin</artifactId>
            </plugin>
        </plugins>

    </build>
    
    <repositories>
        <repository>
            <id>flex-mojos-repository</id>
            <url>http://repository.sonatype.org/content/groups/flexgroup</url>
        </repository>
    </repositories>
    
</project>

asDocの生成
flex-mojosでやろうとしたが、3系だとVectorとかのクラスを認識せず、4系だとspark.cssが見つからないとか言われる。本家のバグレポートでは、3系でバグレポートがでてるのに4.0でFixedということでバグがクローズされてる。このような開発状況を見るかぎりは flex-mojo を利用するのはリスクが高いですね、、、。今回はとりあえずasDocの生成をflex-sdkを直接使って行うだけに留めるけど、将来的には構成管理全般からflex-mojosを外すことにしよう。
"C:\Program Files\flex_sdk_4.1.0.16076\bin\asdoc.exe" -source-path . -doc-sources .
ちなみに、as3のソースコードの内容によって以下のようなエラーが出ることがある。
エラー: toplevel.xml を作成できませんでした : String index out of range: -41
原因は未確認。

2010年11月10日水曜日

非同期呼び出しについてのメモ

非同期呼び出しが絡んだ設計方法についてのメモです。

※以下、実装がマルチスレッドなのかシングルスレッドなのかは無視して考えましょう。

いい感じの本

Enterprise Integration Patterns とかいい感じっす。ECMA Script 系で必要とされるような非同期処理を扱う雰囲気の本ではなくて JMS や MQ 系のメッセージイングサーバを意識した本なのですが、最初の方の章が非同期処理に関する説明がいい感じで載っています。

おいらも読みなおしたいんだけど、日本に置いてきたので今は読めません(´・ω・`)

同期呼び出しのみの場合
スレッド1:処理A -> 処理B -> 処理C のように実行される。

ある処理が非同期処理に依存する場合

すべての処理が非同期だが、すべての処理が依存関係を持つ場合。
スレッド1:処理Aに処理Bをコールバック登録してフォーク 
スレッド2:     処理A実行 -> 処理Bに処理Cをコールバック登録してフォーク
スレッド3:            処理B実行 -> 処理Cにフォーク
スレッド4:                   処理C実行
この手の処理が、かなり面倒。そもそも概念的に非同期処理でないものを実装上強引に非同期処理にしているだけであり、同時並行処理のメリットが全く無い(シングルスレッド環境などの制約条件下でしかたなく対応するケースがほとんどだと思われ)。しかし、依存する処理をコールバックに変換するだけなので、同期呼び出しの場合からの機械的な変換は可能。この手の設計になる場合、同期処理で擬似コードを書いてから、その後機械的に非同期処理に変換したほうがいいかも。

非同期呼び出しにより処理がフォークする場合

各処理に依存関係がない場合は以下のように4つのスレッドが別々に動作する。
スレッド1:処理Aフォーク -> 処理Bフォーク -> 処理Cフォーク 
スレッド2:              -> 処理A
スレッド3:                               -> 処理B
スレッド4:                                               → 処理C
スレッド1の動作は一瞬にして行われるので、処理A,B,Cはほぼ同時に開始する。このような非同期処理は、人間から見てもそこそこ直感的。多くの場合シーケンシャルに動作させた場合よりも高い性能が得られる。

非同期呼び出し処理がジョインする場合

処理A,B,Cのすべてが完了したあとに次の処理が行われる場合の例
スレッド1:処理Aフォーク -> 処理Bフォーク -> 処理Cフォーク 
スレッド2:              -> 処理A -> 合流条件確認 -> (失敗)
スレッド3:                               -> 処理B                         -> 合流条件確認 -> (成功) -> 合流後の処理
スレッド4:                                                -> 処理C -> 合流条件確認 -> (失敗)
非同期処理のパフォーマンスを享受しつつ、処理の同期もおこなってしまうというおいしいところ取りですね。ジョインする条件の確認や、その条件を構成する情報の登録などをする必要があるのでコーディングレベルでは少しめんどくさいのですが、事前に設計をきちんとやっておけば問題ないでしょう。

設計方針考察

まずは、fork と join の発生する処理は避けて実装する。非同期処理を利用するとしても陸上のリレーのようにコールバックを用いて処理を継続させる方法であれば、join 条件のチェックが必要無いので設計がシンプルになる。この方法で、一通り実装して、実装した機能が仕様を満たしていることを確認する。

以下書きかけ。
  1. 設計対象の1つの処理を決定する。
  2. 処理内にて順次処理とフォークする処理とジョイン後の処理を、最外殻のレベルで分類し、各々の処理の塊をグループとする。
  3. 上記グループのうち、最外殻のジョイン処理とそれにジョインされる処理を1つのグループとして扱う。
  4. 複数のグループが残った場合、上記の各グループをメソッド化し、それそれを順次実行する。(各々が依存関係を持たないため並行動作が可能)
  5. 単一の順次処理の場合はそのまま記述すればよい。非同期処理は必要ない。
  6. 単一のジョインされるグループが残った場合、ジョイン実行条件のための情報(ジョイン実行条件判断クロージャから見えるスコープのフラグなど)を用意し、ジョイン実行条件を判断するクロージャを用意し、ジョイン実行条件のための情報の変更を通知するためのクロージャを用意し、ジョイン後の処理のクロージャを用意し、残りのグループを各メソッドとして作成して引数にジョイン実行条件判断用のクロージャと情報通知用クロージャとジョイン後の処理のクロージャを渡すようにする。
以降、上記の1からをネストして行う。
この説明じゃおいら自身以外にはわからんよねw まぁ、個人的なメモなので、、、。

まとめ

同一スレッドでの順次処理、フォーク、ジョインの3種類を適切に考えて設計すればよいということですね。

このような概念を用いた設計はプログラミング言語で記述することは直感的ではないと思うので(少なくともおいらには)、コーディングに入る前にアクティビティ図のような形で設計してみたほうがよいと思います。

2010年11月4日木曜日

Flash IDE 上でのアルファを変えるだけの簡単なボタンの作成

今回は、Flashの勘所を友人に教わりつつ、簡単なボタンのコンポーネントを作成してみました。

概要
任意のSpriteを継承したオブジェクトに対して、マウスのロールオーバー時に alpha の値を変更する振る舞いを付加するコンポーネントおよびライブラリです。

仕様
AlphaTransitiveButtonクラス
  • 概要
    • ロールオーバー時に alpha が変化する Sprite です。
    • ボタンモードはオンになります。
  • メリット
    • Flash IDE 上のシンボルからリンクするだけで、シンボルに対してロールオーバー時に alpha が 0.5 に変化する振る舞いを与えることができます。
    • 自前の ActionScript を作成する必要がありません。
  • 備考
    以下の場合、AlphaTransitiveButton クラスを使用せずに ButtonUtils の addXxxBehavior() 系のメソッドを利用の検討を推奨します。
    • 既に継承を使用しているために継承が使えない場合。
    • Alpha を独自に設定したい場合。
    • 継承を使いたくない場合。(例:10種類の振る舞いを追加するために10回継承するということを避けたい場合)
ButtonUtilsクラス
  • 概要
    • 指定された Sprite クラス(もしくは Sprite を継承したクラス)にロールオーバー時に alpha が変化する振る舞いを付加します。
    • ボタンモードはオンになります。
  • 備考
    • Flash IDE 上のシンボルに対して本メソッドのデフォルトの振る舞いを追加する場合は AlphaTransitiveButton クラスの利用も検討してみてください。
    サンプル

    • 上のボタンは AlphaTransitiveButton クラスにリンクしています。
    • 下のボタンは、ボタンのシンボルの1フレーム目で ButtonUtils#addAlphaTransitiveButtonBehavior(target, rollOverAlpha) を呼び出し、rollOverAlpha を0.8に設定しています。
    ソース

    AlphaTransitiveButtonクラス
    package {
        
        import flash.display.Sprite;
        import flash.events.MouseEvent;
        
        /**
         * ロールオーバー時に alpha が変化する Sprite です。
         * ボタンモードはオンになります。
         * 
         * メリット
         *  ・ Flash IDE 上のシンボルからリンクするだけで、シンボルに対してロールオーバー時に alpha が 0.5 に変化する振る舞いを与えることができます。
         *  ・ 自前の ActionScript を作成する必要がありません。
         * 
         * 備考
         *  ・ 以下の場合、AlphaTransitiveButton クラスを使用せずに ButtonUtils の addXxxBehavior() 系のメソッドを利用の検討を推奨します。
         *    ・ 既に継承を使用しているために継承が使えない場合。
         *    ・ Alpha を独自に設定したい場合。
         *    ・ 継承を使いたくない場合。(例:10種類の振る舞いを追加するために10回継承するということを避けたい場合)
         * 
         * @see ButtonUtils#addAlphaTransitiveButtonBehavior(Sprite, Number)
         */
        public class AlphaTransitiveButton extends Sprite {
            
            public function AlphaTransitiveButton() {
                ButtonUtils.addAlphaTransitiveButtonBehavior(this);
            }
        }
    }
    

    ButtonUtilsクラス
    package {
        
        import flash.display.Sprite;
        import flash.events.MouseEvent;
        
        /**
         * ボタン関連のユーティリティクラスです。
         */
        public class ButtonUtils extends Sprite {
            
            /**
             * 指定された Sprite クラス(もしくは Sprite を継承したクラス)にロールオーバー時に alpha が変化する振る舞いを付加します。
             * ボタンモードはオンになります。
             * Flash IDE 上のシンボルに対して本メソッドのデフォルトの振る舞いを追加する場合は AlphaTransitiveButton クラスの利用も検討してみてください。
             * 
             * 注: target が Dynamic Text の TextArea の場合など、特定の条件下でマウスオーバー時にマウスカーソルの形状が変化しない場合があります。(flash側の問題)
             * 
             * @param target 対象となる Sprite
             * @param rollOverAlpha ロールオーバー時の alpha の値
             * @see AlphaTransitiveButton
             */
            public static function addAlphaTransitiveButtonBehavior(target:Sprite, rollOverAlpha:Number = 0.5) {
                target.buttonMode = true;
                target.addEventListener(MouseEvent.ROLL_OVER, function(e:MouseEvent):void {
                    e.currentTarget.alpha = rollOverAlpha;
                });
                target.addEventListener(MouseEvent.ROLL_OUT, function(e:MouseEvent):void {
                    e.currentTarget.alpha = 1;
                });
            }
        }
    }
    
    アーカイブ

    2010年11月1日月曜日

    Flash IDE 上でのMVCについて考えてみた

    Flash IDE 上でのMVCについて考えてみた(その2) が最新です。

    近々、Flash IDE で開発を行うことになりました。

    開発に入る前の実験として、ステージ上に色々とコンポーネントを配置して、Documentクラスに ActionScript のコードを記述して、なんとなく動くものを作っていたというのがこれまでの話。しかし、これでは保守性に難があるなぁと思う今日この頃。

    保守性を向上させようと考えた場合に、すぐに思い浮かぶのはコンポーネント化。コンポーネント内部はシンプルなMVCがよさそう。

    ということで、実験してみました。

    方式
    • Flash IDE 上で作成したシンボルから as ファイルにリンクを張る方式。
    • 名前空間は Flash IDE 上の LIBRARY 内にフォルダを作成して対応する。

    今回作成するサンプル
    • TextArea に最初 0 が表示され、マウスでクリックするたびに値が 1 増えるアプリ。


    シンボル
    • LIBRARY 内のフォルダ名
      • com.objectfanatics.samplecomponent
    • シンボル名
      • View
    • リンク先のクラス
      • com.objectfanatics.samplecomponent.view.View

    クラス構成
    • com.objectfanatics.samplecomponent.model パッケージ
      • com.objectfanatics.samplecomponent.model.Model
        • コンポーネントのモデルのインタフェース。
        • モデルは可能な限り実装を意識しないで設計したいため、あえてインタフェースを作成。
      • com.objectfanatics.samplecomponent.model.ModelImpl
        • コンポーネントのモデルの実装。
      • com.objectfanatics.samplecomponent.model.ModelListener
        • Model の変更内容を監視するためのリスナーインタフェース
    • com.objectfanatics.samplecomponent.view パッケージ
      • com.objectfanatics.samplecomponent.view.View
        • Flash IDE 上で作成したシンボルからリンクされるクラス。
        • View として存在すると同時に、コンポーネント全体を構築する役割も持つ。
      • com.objectfanatics.samplecomponent.view.ModelListenerImpl
        • Model の変更を監視し、Model の変更内容に応じて View を更新するクラス。
        • ModelListenerインタフェースの実装クラス。
    • com.objectfanatics.samplecomponent.controller パッケージ
      • com.objectfanatics.samplecomponent.controller.Controller
        • View の内部要素に対してリスナーを登録し、イベントを監視する。
        • View からのイベントを処理し、必要に応じて Model を更新する。 

    クラスのソース
    package com.objectfanatics.samplecomponent.model {
     
     public interface Model {
      
      /**
       * ModelListener をセットします。
       * @param modelListener ModelListener
       */
      function setModelListener(modelListener:ModelListener):void;
      
      /**
       * 値を返します。
       * @return 値
       */
      function getNumber():uint;
      
      /**
       * 値を1増やします。
       */
      function increment():void;
     }
    }
    

    package com.objectfanatics.samplecomponent.model {
     
     public class ModelImpl implements Model {
      
      private var modelListener:ModelListener;
      
      /**
       * 保持される値
       */
      private var number:uint = 0;
      
      /**
       * Model#getNumber():uint の実装
       */
      public function getNumber():uint {
       return number;
      }
      
      /**
       * Model#setModelListener(modelListener:ModelListener):void の実装
       */
      public function setModelListener(modelListener:ModelListener):void {
       this.modelListener = modelListener;
      }
      
      /**
       * Model#increment():void の実装
       */
      public function increment():void {
       this.modelListener.numberChanged(++number);
      }
     }
    }
    

    package com.objectfanatics.samplecomponent.model {
     
     /**
      * Model の変更内容を監視するためのリスナー
      */
     public interface ModelListener {
      
      /**
       * 値が変更された場合に呼び出されます。
       * @param number 現在の値
       */
      function numberChanged(number:uint);
     }
    }
    

    package com.objectfanatics.samplecomponent.view {
     
     import flash.display.Sprite;
     
     import com.objectfanatics.samplecomponent.model.Model;
     import com.objectfanatics.samplecomponent.model.ModelImpl;
     import com.objectfanatics.samplecomponent.model.ModelListener;
     import com.objectfanatics.samplecomponent.controller.Controller;
     
     /**
      * FlashIDE 上の LIBRARY にて、フォルダ名が com.objectfanatics.samplecomponent、名前が View のシンボルに対応するクラス。
      * View という名前ですが、FlashIDE上で作成したシンボルからのリンクとなっているため、コンポーネント全体の初期化も行っています。
      * 純粋な View としての処理は、 Model には依存しますが Controller には依存しません。
      */
     public class View extends Sprite {
      
      private const model:Model = new ModelImpl();
      
      public function View():void {
       initView();
       model.setModelListener(new ModelListenerImpl(this));
       new Controller(this, model);
      }
      
      private function initView():void {
       this.numberText.text = "" + model.getNumber();
      }
     }
    }
    

    package com.objectfanatics.samplecomponent.view {
    
     import com.objectfanatics.samplecomponent.model.ModelListener;
      
     /**
      * Model の変更内容を監視し View に反映させるための ModelListener の実装クラス。
      */
     public class ModelListenerImpl implements ModelListener {
      
      /**
       * 変更対象の View
       */
      private var view:View;
      
      /**
       * コンストラクタ
       * @param view 変更対象の View
       */
      public function ModelListenerImpl(view:View):void {
       this.view = view;
      }
      
      /**
       * Model の値が変更された時に呼び出され、View 上の表示を変更します。
       * @param currentNumber 現在のの Model の値
       */
      public function numberChanged(currentNumber:uint) {
       view.numberText.text = "" + currentNumber;
      }
     }
    }
    

    package com.objectfanatics.samplecomponent.controller {
     
     import flash.events.MouseEvent;
     
     import com.objectfanatics.samplecomponent.view.View;
     import com.objectfanatics.samplecomponent.model.Model;
     
     public class Controller { 
      
      private var model:Model;
      
      private var view:View;
      
      public function Controller(view:View, model:Model):void {
       
       // Model と View の格納
       this.model = model;
       this.view = view;
       
       // View のクリックの検出設定
       view.addEventListener(MouseEvent.CLICK, function():void{model.increment()});
      }
     }
    }
    

    今回の所感

    単純な仕様のコンポーネントであれば、この方法でも十分な保守性が得られるのではないかと思いました。しかし、複雑になるとどうなるか全く見当も付きません。実際に複雑なケースに適用してみて実際に問題が生じたときに、その問題をシンプルに表現できる例題を作成して考察してみようと思います。

    全体的な仕組みとして再利用が効きそうなポイントが多々あるのですが、ことごとくうまくいきませんでした。たとえば、Viewクラスの内部は Template Method を適用すればコード量抑えられるしロジックを完全に隠蔽できるので保守性が上がりそうなものです。しかし、abstract class が使えないために Template Method のメリットがかなり小さくなってしまいます。通常のクラスで Template Method を試してもみたのですが、Generics が使えないために手動での強引な型変換が必要となり、ソースがかなり長く見辛くなってしまいました。

    まだ as3 を触り始めてまもないので、当面は粛々と実験していきたいと思います。

    アーカイブ

    2010年7月30日金曜日

    カナダネタ:ホテル予約編

    突然ですが、業務として8月から半年ほどバンクーバー(カナダ)に行くことになりました。

    ということで以下、ホテルの予約メモ。
    なにげに結構大変でしたw

    まず、安い宿がほとんどありません。数年前シリコンバレーにいた時は、ダウンタウンの交通の便がよいところでさえシングルルーム(風呂、トイレ、朝食、電話、高速インターネット回線付き)で$1,050/month、ダブルでも$1,200/month程度で借りられました。飛び込みでも普通のシングルルームで45$あれば借りることができました。月極のフラットを1室借りる場合は、$1,000/month前後でした(電話回線の契約とか自分でする必要がありますが)。しかし、バンクーバーではそうはいかないようです。


    ホテルを物色してみた

    Shaughnessy Village Vancouver

    かなり安い宿です。8月は最高値なのですが、それでも$1045/momth。安いです。よさそうなので、メールでいくつか質問してみました。すると、3ヶ月で$915/month, 6ヶ月で$885/monthとのこと。さらに安い! しかし、途中でのキャンセルについては支払った金額が全く払い戻されないということでした。個人的に、過去にこのような厳しい払い戻しルールの長期滞在用ホテルは経験したことも聞いたこともなく、メールが英文メールとしてもあまりにもぶっきらぼうであり、また、いくつかの質問をさらっと無視されてしまったので、念のため、評判をググッてみました。う~む。賛否両論ですが、事前に長期予約をするにはリスクが大きそうな感じですね。


    YWCA of Vancouver

    口コミの評価がかなり高いです。Single (1 single bed) with semi- private bath*, TV の weekly rate が $74/night ということで、かなりリーズナブル。semi- private bath* は、2部屋で共有とのこと。"We also offer residence rate if you stay for a minimum of 30 days"とのことで、もっと長く止まるなら、もう少し安くなりそうです。


    The Kingston Hotel

    シャワールームが共有で、シングルルームが $75 - $125 plus tax とのこと。8月はハイシーズンなので、125$/nightになりそうですね。monthlyのrateも乗っていないので、今回は見送り。


    結局・・・

    結局、とりあえずYWCAを1ヶ月だけおさえることにして、その間に家具付きのフラットを借りる方向で進めてみることにしました。その際、もし途中キャンセルが可能なら6ヶ月間の予約を入れるという選択肢も残しておきます。

    ということで、availability と monthly rate の確認と、6ヶ月の予約を入れるが途中キャンセルする可能性があるという状況でよい案がないかなという質問をメールで確認したところ、非常に丁寧な回答メールを頂きました。以下要約。
    • 30日以上連続して宿泊する場合は、月単位のResidence形式を選択することが可能。その際、ホテルスタッフからの承認が必要になる(Residenceの情報)。しかし、8月はハイシーズンなので daily のレートのみ。
    • 長期滞在で部屋を出る日付が未定の場合は、ひとまず長期の予約をしておいて、部屋を出る1ヶ月前に話せばよい。
    • 全館禁煙。
    • 無線LANはロビーと各部屋で利用可能。価格は $6.00/day, $22.00/week, or $33.00/month。
    weekly rate も residence rate も適用できないのは残念ですが、とりあえず8末まではここを押さえようとしたところ、シングルが空いてませんでした(´・ω・`)

    となると、ダブルになるので1日 $98.00 です。う~む、高いな。シャワー共有でこの値段は割に合わないなぁ、、、。しかし、今後ここに residence rate で宿泊する可能性も考えると、事前調査という意味ではアリか、、、、。このシーズンはどこも満室だし、中級ホテルでも$200/day弱するし、、、。

    ということで、到着日の8日から、某お祭りが終わって落ち着く頃ということで18日まで10夜分だけ予約を取ることにしました。(そして、PDF付きの確認メールがスパムフィルタにかかるのはお約束w)

    2010年7月28日水曜日

    就業規則の届出をしていない場合の旅費規程について

    弊社は現在常時10人以上の労働者を使用していないため、労働基準監督署に就業規則を届けていません。そのため、旅費規程などについても届出をしていません。その場合、旅費規程により経費として扱われることが妥当な場合でも、所得税が課税される可能性がありうるのではという疑問があります。

    ということで、労働基準監督署に聞いてみました。

    労働基準監督署曰く
    • 旅費規定等を単独で届け出ることはできず、就業規則を含めて提出する必要がある。
    • 税金については関知しない。
    • 今回の件では特に届けでの必要はない。
    とのこと。今度は税務署に聞いてみました。

    税務署曰く
    • 労働基準監督署に届けるかどうかは関係ない。
    • 調査が必要な場合などに、該当する書類を見せられるようにしておけばよい。
    • 書類の内容の妥当性は、地域性その他を含めた全体的な視点で判断される。
    とのことでした。

    妥当な規則を事前に用意していれば、特に問題なさそうです。

    2010年6月7日月曜日

    Utils.as

    package {
        import flash.filesystem.File;
        import flash.filesystem.FileMode;
        import flash.filesystem.FileStream;
        
        /**
         * とりあえず、静的な便利メソッドを入れ込んでおくクラス。
         * 
         * TODO: Air 以外の開発をする際に、Air 専用とそれ以外を分離しましょう。
         * 
         * 方針
         *         ・ActionScriptはオーバーロードができないので、メソッド名に引数情報もいれる。
         *         ・見かけの簡潔さを求めず、分かりやすさを求める。初めて見る人でもシグニチャだけで機能を確実に理解できることを目指す。
         *         ・管理しきれなくなって『具体的に困る』ことになったら別ライブラリへの分割を考える。
         */
        public class Utils {
            
            /**
             * 指定された XML ファイルを読み込み XML オブジェクトを返します。
             */
            public static function readXmlFromFile(xmlFile:File):XML {
                return new XML(readUTFBytesFromFile(xmlFile));
            }
            
            /**
             * 指定されたファイルを読み込み UTFBytes を返します。
             */
            public static function readUTFBytesFromFile(utfFile:File):String {
                var fileStream:FileStream = new FileStream();
                fileStream.open(utfFile, FileMode.READ);
                var utfBytes:String = fileStream.readUTFBytes(utfFile.size);
                fileStream.close();
                return utfBytes;
            }
            
            /**
             * 指定されたファイルに対して指定された XML を書き込みます。
             */
            public static function writeXmlToFile(xml:XML, file:File):void {
                writeUTFBytesToFile(xml.toXMLString(), file);
            }
            
            /**
             * 指定されたファイルに対して UTFBytes を書き込みます。
             */
            public static function writeUTFBytesToFile(utfBytes:String, file:File):void {
                var fileStream:FileStream = new FileStream();
                fileStream.open(file, FileMode.WRITE);
                fileStream.writeUTFBytes(utfBytes);
                fileStream.close();
            }
            
            /**
             * 指定されたファイルをファイルシステム中に作成します。既に指定されたファイルが存在する場合には何もしません。
             */
            public static function createFile(file:File):void {
                if (!file.exists) {
                    var fileStream:FileStream = new FileStream();
                    fileStream.open(file, FileMode.WRITE);
                    fileStream.close();
                }
            }
        }
    }
    

    2010年6月2日水曜日

    FlashのUI系コンポーネント調査

    FlashのUI系のコンポーネントについて全く知識がないので、少し調査してみました。

    クラス階層

    Object
    ・言うまでもなく、あらゆるクラスのベースとなるクラス。特にUIであるかどうかとは無関係。
    ・パッケージはトップレベル。

    flash.events.EventDispatcher
    flash.events.IEventDispatcher の実装クラス。
    ・イベントを扱うための汎用的な機能を持つクラス。リスナーの追加削除とかイベントの送信とか。
    ・特にUIであるかどうかとは無関係。

    flash.display.DisplayObject
    ・display list に配置されるすべてのオブジェクトのベースクラス。
    ・x,y,zとかalphaとかwidthとかvisibleとかマウス位置とか、表示されるものとしての基本機能を持っている。

    flash.display.InteractiveObject
    ・表示されるオブジェクトでマウスやキーボードやその他の入力デバイスを扱うものすべての抽象クラス。
    ・このクラスはnewできないし、上記までのクラスもUIを作成するうえでプログラマが明示的に new することはなさそうです。

    flash.display.DisplayObjectContainer
    ・display object の入れ物クラス。
    ・z 軸上に複数の display object を child として配置できる。
    ・このサブクラスには Loader, Sprite, Stage, TextLine があります。

    flash.display.Loader
    ・java の ClassLoader のようなものを想像していると DisplayObjectContainer のサブクラスということに違和感を感じるが、実際は swf ファイルおよび画像ファイルをロードして表示するものなので、表示オブジェクトのローダーと言える。
    ・flex 用のサブクラスとして mx.core.FlexLoader があり、toString() メソッドが DisplayObjects の階層を表すようになっている。

    flash.text.engine.TextLine
    ・display text 上に文字列を表示するためのオブジェクト。
    ・final クラスなので継承できないし、ユーザが直接 new することもできない。
    ・flash.text.engine 系のパッケージと関わらない限りは必要なさそう。

    flash.display.Stage
    ・ブラウザ内なら flash の表示領域が Stage で、Airなら各 NativeWindow が Stage を持つ。

    flash.display.Sprite
    ・UI 系の一般的なベースクラスで、MovieClip からtimelineを抜いたようなもの。

    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;
              }
          }
      }
      

      2010年4月19日月曜日

      作業ログ:BlazeDSのRemotingServiceでJavaのenumを扱う方法

      ActionScrip側からAMFで送られた文字列をBlazeDS4側でenumに変換しようとしたところ、
      No enum const class 
      
      と怒られてしまい、うまくいきませんでした。

      flex 側で IExternalizable を実装して String を投げるようにしてみたのですが、そうすると今度は BlazeDS 側で配列に変更しようとしてエラーで落ちました。

      方法が間違っていたのかもしれませんが、どうも解決法が見つけられなかったので、とりあえずはワークアラウンドで対応することにしました。

      (´・ω・`) 正規の方法を教えてください、、、

      4.21.2010追記:正規の方法がありました。詳細はこちらを参照のこと。


      workaround

      • ActionScript 側の Enum (のような)実装では、内部に必ず Enum.valueOf() メソッドの第二引数に対応する文字列を持つpublicなインスタンス変数(or getter/setter)を配置する。
      • ActionScript 側の Enum (のような)実装では、必ず public var name:String を定義し、java 側の Enum#getName() と対応させる。
      • それ以外の変数(or getter/setter)を配置してもよいが、すべてメタタグ [Transient] をつける。
      • BlazeDS4のソースにパッチを当てる。
      AMFから受け取った情報が、値がString型のエントリを一つだけ持つASObjectになればOKです。
      この方法だと、副作用として flex 側で IExternalizable を実装する必要がないのでラクチンです。


      パッチ

      対象のソースコードは、flex.messaging.io.amf.translator.decoder.EnumDecoder クラスです。

      元のコードは以下のようになっています。

      55: Enum value = Enum.valueOf(desiredClass, encodedObject.toString());
      

      encodedObject の型が flex.messaging.io.amf.ASObject 型なので、encodedObject.toString() の値は以下のようなフォーマットの文字列に変換されます。

      ASObject(33469162){name=HOGE}
      

      上記のワークアラウンドを適用すると、ASObjectはひとつだけエントリを持つマップとなるはずなので、そのエントリの値を使えば Enum が復元できます。

      以下が変更内容です。ASObject以外が来る場合もあるかもしれないので(まだそこまでソースを追ってません)一応、オリジナルのコードも残しておきます。

      final Enum value;
      if (ASObject.class.isInstance(encodedObject)) {
       // patch for workaround
       value = Enum.valueOf(desiredClass, ASObject.class.cast(encodedObject).values().iterator().next().toString());
      } else {
       // original
       value = Enum.valueOf(desiredClass, encodedObject.toString());
      }
      


      ActionScript 側のEnum(のようなもの)の例

      public final class Hoge {
       public var name:String;
       public function Hoge(name:String) { this.name = name; }
       public static const HOGETAROU:Hoge = new Hoge("HOGETAROU");
       public static const HOGEJIROU:Hoge = new Hoge("HOGEJIROU");
       public static const HOGENOSUKE:Hoge = new Hoge("HOGENOSUKE");
      }