基本情報
基本的な情報は以下に書かれています。
- ActionScriptからJavaへのデータマッピング規則
Converting data from ActionScript to Java - JavaとActionScriptのデータマッピングを明示的に指定する方法
Explicitly mapping ActionScript and Java objects - JavaからActionScriptへのデータマッピング規則
Converting data from Java to ActionScript
以下、各型についてマッピングの実験をしてみます。
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; } } }
0 件のコメント:
コメントを投稿