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

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

0 件のコメント:

コメントを投稿