This is an automated email from the ASF dual-hosted git repository. gregdove pushed a commit to branch amf_updates in repository https://gitbox.apache.org/repos/asf/royale-asjs.git
commit bcc4467f4febf8a279b374db86724742fada0eff Author: greg-dove <[email protected]> AuthorDate: Fri Mar 1 18:14:15 2019 +1300 Getting WIP updates in for AMFBinaryData Support for dynamic classes (with constraints based on compile settings : needs js-default-initializers) Support for IExternalizable. Will try to improve things here for IDataInput/IDataOutput imports in implementing classes. These currently needs COMPILE::JS and COMPILE::SWF variants in imports - to discuss on dev list (See mx.collections/ArrayCollection.as for example) Numerous fixes and improvements to amf serialization as a result of AMFBinaryDataTesterTest testing in UnitTests manual test project. Updates to mxroyale/RemoteObjectAMFTest to use ArrayCollection as the collection for amf serialization (instead of ArrayList) still to do: IDynamicPropertyWriter (easy) test Vectors XML Dictionary (Map ?) Decide whether ByteArray decodes to ArrayBuffer or AMFBinaryData in javascript. To discuss on dev list. --- examples/amf/SampleAmfWebApp/pom.xml | 28 +- .../RemoteObjectAMFTest/src/main/royale/App.mxml | 8 +- .../src/main/royale/valueObjects/Product.as | 18 +- .../org/apache/royale/collections/ArrayList.as | 11 +- .../main/royale/mx/collections/ArrayCollection.as | 10 +- .../src/main/royale/mx/collections/ArrayList.as | 12 +- .../royale/net/remoting/amf/AMFBinaryData.as | 1221 ++++++++++++++++++++ .../royale/net/remoting/amf/AMFNetConnection.as | 6 +- .../utils/{IExternalizable.as => IDataInput.as} | 21 +- .../utils/{IExternalizable.as => IDataOutput.as} | 25 +- .../org/apache/royale/net/utils/IExternalizable.as | 13 + .../UnitTests/src/main/royale/TestClasses.as | 5 +- .../{JiraIssuesTester.as => GithubIssuesTester.as} | 6 +- .../{JiraIssuesTester.as => NetworkTester.as} | 20 +- .../GithubTesterTest.as} | 12 +- .../network/AMFBinaryDataTesterTest.as | 335 ++++++ .../network/support/DynamicTestClass.as} | 28 +- .../network/support/TestClass1.as} | 27 +- .../network/support/TestClass2.as} | 25 +- .../network/support/TestClass3.as} | 39 +- .../testsview/image/apache-royale-main-logo.png | Bin 0 -> 58758 bytes manualtests/UnitTests/testsview/index.html | 3 +- 22 files changed, 1771 insertions(+), 102 deletions(-) diff --git a/examples/amf/SampleAmfWebApp/pom.xml b/examples/amf/SampleAmfWebApp/pom.xml index 8cf7b3f..1a05d8b 100644 --- a/examples/amf/SampleAmfWebApp/pom.xml +++ b/examples/amf/SampleAmfWebApp/pom.xml @@ -24,7 +24,7 @@ <artifactId>examples-amf-webapps</artifactId> <version>0.9.6-SNAPSHOT</version> </parent> - + <artifactId>SampleAmfWebApp</artifactId> <version>0.9.6-SNAPSHOT</version> <packaging>war</packaging> @@ -34,7 +34,7 @@ <properties> <java.version>1.7</java.version> </properties> - + <build> <sourceDirectory>src/main/java</sourceDirectory> <plugins> @@ -47,7 +47,7 @@ <target>${java.version}</target> </configuration> </plugin> - + <!-- Make Spring-Boot build an executable war --> <plugin> <groupId>org.springframework.boot</groupId> @@ -84,7 +84,25 @@ </overlays> </configuration> </plugin> - + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-antrun-plugin</artifactId> + <version>1.1</version> + <executions> + <execution> + <phase>install</phase> + <goals> + <goal>run</goal> + </goals> + <configuration> + <tasks> + <echo>To run, use:</echo> + <echo>java -jar target/${project.artifactId}-${project.version}-exec.war</echo> + </tasks> + </configuration> + </execution> + </executions> + </plugin> <!-- Test create javadocs --> <!-- <plugin> <groupId>org.apache.maven.plugins</groupId> @@ -131,7 +149,7 @@ <version>0.9.6-SNAPSHOT</version> <type>war</type> </dependency> --> - + <dependency> <groupId>org.apache.royale.examples</groupId> <artifactId>RemoteObjectAMFTest-MXRoyale</artifactId> diff --git a/examples/mxroyale/RemoteObjectAMFTest/src/main/royale/App.mxml b/examples/mxroyale/RemoteObjectAMFTest/src/main/royale/App.mxml index 7fa241a..ac5911e 100644 --- a/examples/mxroyale/RemoteObjectAMFTest/src/main/royale/App.mxml +++ b/examples/mxroyale/RemoteObjectAMFTest/src/main/royale/App.mxml @@ -34,7 +34,7 @@ limitations under the License. import mx.rpc.remoting.Operation; import mx.collections.ArrayCollection; - import org.apache.royale.collections.ArrayList; + // import org.apache.royale.collections.ArrayList; import org.apache.royale.events.Event; import org.apache.royale.reflection.registerClassAlias; import org.apache.royale.reflection.getAliasByClass; @@ -47,7 +47,7 @@ limitations under the License. private function iniApp(event:Event):void{ //swap in ArrayCollection for ArrayList - registerClassAlias(getAliasByClass(ArrayCollection), ArrayList); + // registerClassAlias(getAliasByClass(ArrayCollection), ArrayList); // Test CompressedRemoteObject // trace("init CompressedRemoteObject.includePackages"); @@ -126,7 +126,7 @@ limitations under the License. product.taxonomy = taxonomy; - var zones:ArrayList = new ArrayList(); + var zones:ArrayCollection = new ArrayCollection(); var zone:Zone= new Zone(); zone.id = 1; @@ -145,7 +145,7 @@ limitations under the License. zones.addItem(zone); - var flavors:ArrayList = new ArrayList(); + var flavors:ArrayCollection = new ArrayCollection(); flavors.addItem('X'); flavors.addItem('Y'); diff --git a/examples/mxroyale/RemoteObjectAMFTest/src/main/royale/valueObjects/Product.as b/examples/mxroyale/RemoteObjectAMFTest/src/main/royale/valueObjects/Product.as index cfb045d..66adadc 100644 --- a/examples/mxroyale/RemoteObjectAMFTest/src/main/royale/valueObjects/Product.as +++ b/examples/mxroyale/RemoteObjectAMFTest/src/main/royale/valueObjects/Product.as @@ -18,7 +18,9 @@ //////////////////////////////////////////////////////////////////////////////// package valueObjects { - import org.apache.royale.collections.ArrayList; + // import org.apache.royale.collections.ArrayList; + + import mx.collections.ArrayCollection; [RemoteClass(alias="org.apache.royale.amfsamples.valueobjects.Product")] public class Product @@ -66,29 +68,29 @@ package valueObjects _taxonomy = value; } - // collection of zones (Zone), we can use Array and ArrayList - private var _zones:ArrayList; + // collection of zones (Zone), we can use ArrayCollection + private var _zones:ArrayCollection; [Bindable("__NoChangeEvent__")] - public function get zones():ArrayList + public function get zones():ArrayCollection { return _zones; } - public function set zones(value:ArrayList):void + public function set zones(value:ArrayCollection):void { _zones = value; } - private var _flavors:ArrayList = null; + private var _flavors:ArrayCollection = null; [Bindable("__NoChangeEvent__")] - public function get flavors():ArrayList + public function get flavors():ArrayCollection { return _flavors; } - public function set flavors(value:ArrayList):void + public function set flavors(value:ArrayCollection):void { _flavors = value; } diff --git a/frameworks/projects/Collections/src/main/royale/org/apache/royale/collections/ArrayList.as b/frameworks/projects/Collections/src/main/royale/org/apache/royale/collections/ArrayList.as index 0281628..04e536d 100644 --- a/frameworks/projects/Collections/src/main/royale/org/apache/royale/collections/ArrayList.as +++ b/frameworks/projects/Collections/src/main/royale/org/apache/royale/collections/ArrayList.as @@ -26,8 +26,15 @@ package org.apache.royale.collections import org.apache.royale.events.CollectionEvent; import org.apache.royale.net.utils.IExternalizable; - import org.apache.royale.net.utils.IDataInput; - import org.apache.royale.net.utils.IDataOutput; + COMPILE::JS{ + import org.apache.royale.net.utils.IDataInput; + import org.apache.royale.net.utils.IDataOutput; + } + COMPILE::SWF{ + import flash.utils.IDataInput; + import flash.utils.IDataOutput; + } + //-------------------------------------- // Events diff --git a/frameworks/projects/MXRoyale/src/main/royale/mx/collections/ArrayCollection.as b/frameworks/projects/MXRoyale/src/main/royale/mx/collections/ArrayCollection.as index 1dc975e..0807354 100644 --- a/frameworks/projects/MXRoyale/src/main/royale/mx/collections/ArrayCollection.as +++ b/frameworks/projects/MXRoyale/src/main/royale/mx/collections/ArrayCollection.as @@ -31,8 +31,14 @@ use namespace mx_internal; */ import org.apache.royale.net.utils.IExternalizable; -import org.apache.royale.net.utils.IDataInput; -import org.apache.royale.net.utils.IDataOutput; +COMPILE::JS { + import org.apache.royale.net.utils.IDataInput; + import org.apache.royale.net.utils.IDataOutput; +} +COMPILE::SWF{ + import flash.utils.IDataInput; + import flash.utils.IDataOutput; +} [DefaultProperty("source")] diff --git a/frameworks/projects/MXRoyale/src/main/royale/mx/collections/ArrayList.as b/frameworks/projects/MXRoyale/src/main/royale/mx/collections/ArrayList.as index 763f54b..540ca0b 100644 --- a/frameworks/projects/MXRoyale/src/main/royale/mx/collections/ArrayList.as +++ b/frameworks/projects/MXRoyale/src/main/royale/mx/collections/ArrayList.as @@ -39,8 +39,14 @@ import org.apache.royale.utils.UIDUtil; import org.apache.royale.reflection.getQualifiedClassName; import org.apache.royale.net.utils.IExternalizable; -import org.apache.royale.net.utils.IDataInput; -import org.apache.royale.net.utils.IDataOutput; + COMPILE::JS { + import org.apache.royale.net.utils.IDataInput; + import org.apache.royale.net.utils.IDataOutput; + } + COMPILE::SWF{ + import flash.utils.IDataInput; + import flash.utils.IDataOutput; + } //-------------------------------------- // Events @@ -95,7 +101,7 @@ import org.apache.royale.net.utils.IDataOutput; * @productversion Flex 4 */ public class ArrayList extends EventDispatcher - implements IList//, IExternalizable, IPropertyChangeNotifier + implements IList, IExternalizable//, IPropertyChangeNotifier { //-------------------------------------------------------------------------- // diff --git a/frameworks/projects/Network/src/main/royale/org/apache/royale/net/remoting/amf/AMFBinaryData.as b/frameworks/projects/Network/src/main/royale/org/apache/royale/net/remoting/amf/AMFBinaryData.as new file mode 100644 index 0000000..dac9976 --- /dev/null +++ b/frameworks/projects/Network/src/main/royale/org/apache/royale/net/remoting/amf/AMFBinaryData.as @@ -0,0 +1,1221 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////////// +/*** + * Based on the + * AMF JavaScript library by Emil Malinov https://github.com/emilkm/amfjs + */ +package org.apache.royale.net.remoting.amf { + import org.apache.royale.net.utils.IDataInput; + import org.apache.royale.net.utils.IDataOutput; + import org.apache.royale.net.utils.IExternalizable; + import org.apache.royale.utils.BinaryData; + + /** + * A version of BinaryData specific to AMF. + * + * @langversion 3.0 + * @playerversion Flash 9 + * @playerversion AIR 1.1 + * @productversion BlazeDS 4 + * @productversion LCDS 3 + * + * @royalesuppresspublicvarwarning + */ + public class AMFBinaryData extends BinaryData implements IDataInput, IDataOutput { + //-------------------------------------------------------------------------- + // + // Class Constants + // + //-------------------------------------------------------------------------- + + + public function AMFBinaryData(bytes:Object = null) { + super(bytes); + } + + COMPILE::SWF + public function get objectEncoding():uint{ + return 3; + } + COMPILE::SWF + public function set objectEncoding(value:uint):void{ + trace('objectEncoding is always AMF3, setter is ignored'); + } + + + + COMPILE::JS + private var serializationContext:SerializationContext; + + COMPILE::JS + public function writeObject(v:*):void { + if (!serializationContext) serializationContext = new SerializationContext(this); + _position = serializationContext.writeObjectExternal(v, _position, mergeInToArrayBuffer); + var err:Error = serializationContext.getError(); + if (err) { + throw new Error(err.message); + } + } + + COMPILE::JS + public function readObject():* { + if (!serializationContext) serializationContext = new SerializationContext(this); + var value:* = serializationContext.readObjectExternal(); + var err:Error = serializationContext.getError(); + if (err) { + throw new Error(err.message); + } + return value; + } + + COMPILE::SWF + public function writeObject(v:*):void { + ba.writeObject(v); + } + + COMPILE::SWF + public function readObject():* { + return ba.readObject(); + } + } +} + +import org.apache.royale.net.remoting.amf.AMFBinaryData; +import org.apache.royale.reflection.getAliasByClass; +import org.apache.royale.reflection.getClassByAlias; +import org.apache.royale.reflection.getDynamicFields; +import org.apache.royale.reflection.isDynamicObject; +import org.apache.royale.net.utils.IDataInput; +import org.apache.royale.net.utils.IDataOutput; +import org.apache.royale.utils.BinaryData; +import org.apache.royale.net.utils.IExternalizable; +import org.apache.royale.net.utils.IDataInput; +import org.apache.royale.net.utils.IDataOutput; + + +COMPILE::JS +class SerializationContext extends BinaryData implements IDataInput, IDataOutput { + import goog.DEBUG; + + + private static const AMF0_AMF3:int = 0x11; + private static const AMF3_OBJECT_ENCODING:int = 0x03; + + private static const AMF3_UNDEFINED:int = 0x00; + private static const AMF3_NULL:int = 0x01; + private static const AMF3_BOOLEAN_FALSE:int = 0x02; + private static const AMF3_BOOLEAN_TRUE:int = 0x03; + private static const AMF3_INTEGER:int = 0x04; + private static const AMF3_DOUBLE:int = 0x05; + private static const AMF3_STRING:int = 0x06; + private static const AMF3_XMLDOCUMENT:int = 0x07; + private static const AMF3_DATE:int = 0x08; + private static const AMF3_ARRAY:int = 0x09; + private static const AMF3_OBJECT:int = 0x0A; + private static const AMF3_XML:int = 0x0B; + private static const AMF3_BYTEARRAY:int = 0x0C; + private static const AMF3_VECTOR_INT:int = 0x0D; + private static const AMF3_VECTOR_UINT:int = 0x0E; + private static const AMF3_VECTOR_DOUBLE:int = 0x0F; + private static const AMF3_VECTOR_OBJECT:int = 0x10; + private static const AMF3_DICTIONARY:int = 0x11; + + + private static const UINT29_MASK:int = 0x1FFFFFFF; + private static const INT28_MAX_VALUE:int = 268435455; + private static const INT28_MIN_VALUE:int = -268435456; + + private static const EMPTY_STRING:String = ""; + + private var owner:AMFBinaryData; + private var writeBuffer:Array; + + private var objects:Array ; + + private var traits:Object; + + private var strings:Object; + + private var stringCount:uint; + private var traitCount:uint; + private var objectCount:uint; + + + private var writeMode:Boolean = false; + + + + private var _numbers:ArrayBuffer; + + private var _numberView:DataView; + + private var _numberBytes:Uint8Array; + + private var _error:Error; + public function getError():Error{ + var _err:Error = _error; + _error = null; + return _err; + } + + + public function SerializationContext(ownerReference:AMFBinaryData){ + owner = ownerReference; + reset(); + super(); + } + + public function reset():void{ + writeBuffer = []; + objects = []; + traits = {}; + strings = {}; + stringCount = 0; + traitCount = 0; + objectCount = 0; + } + + + /** + * used internally as an override to return the writeBuffer Array for use to mimic Uint8Array during writing. + * Array is used because it is not usually known what the byte allocation should be in advance, + * and length is not mutable with javascript typed arrays, so 'growing' the buffer with each write is not + * a good strategy for performance. + * The assumption is that, while write access is slower for individual elements, increasing the length of + * the 'buffer' is not, and that using Array will be more performant. + * @royaleignorecoercion Uint8Array + */ + override protected function getTypedArray():Uint8Array{ + return writeMode ? writeBuffer as Uint8Array : super.getTypedArray(); + } + + + override protected function getDataView():DataView + { + if(!writeMode) return super.getDataView(); + //in write mode, return a utility version + if (!_numberView) { + _numbers = new ArrayBuffer(8); + _numberView = new DataView(_numbers); + _numberBytes = new Uint8Array(_numbers); + } + return _numberView; + } + + + + override protected function setBufferSize(newSize:uint):void + { + //writing variation: in this subclass, writing is always using 'Array' so length is not fixed + _len = newSize; + } + + + override public function writeByte(byte:int):void + { + writeBuffer[_position++] = byte & 255; + } + + override public function writeByteAt(idx:uint, byte:int):void + { + while (idx > _len) { + writeBuffer[_len++] = 0; + } + writeBuffer[idx] = byte & 255; + } + + public function writeUInt29(v:uint):void { + const write:Function = writeByte; + if (v < 128) { + write(v); + } else if (v < 16384) { + write(((v >> 7) & 127) | 128); + write(v & 127); + } else if (v < 2097152) { + write(((v >> 14) & 127) | 128); + write(((v >> 7) & 127) | 128); + write(v & 127); + } else if (v < 0x40000000) { + write(((v >> 22) & 127) | 128); + write(((v >> 15) & 127) | 128); + write(((v >> 8) & 127) | 128); + write(v & 255); + } else { + throw "Integer out of range: " + v; + } + } + + public function writeAll(bytes:Array):void { + for (var i:uint = 0; i < bytes.length; i++) { + writeByte(bytes[i]); + } + } + + protected function addByteSequence(array:Array):void{ + var length:uint = array.length; + if (_position == _len) { + writeBuffer = writeBuffer.concat(array); + _len = _len + length; + if (_len != writeBuffer.length) { + throw new Error('code review') + } + } else { + if (_position + length > _len) { + //overwrite beyond + //first truncate to _position + writeBuffer.length = _position; + //then append the new content + writeBuffer = writeBuffer.concat(array); + _len = _position + length; + if (_len != writeBuffer.length) { + throw new Error('code review') + } + + } else { + //overwrite within - concatenate left and right slices with the new content between + writeBuffer = writeBuffer.slice(0, _position).concat(array, writeBuffer.slice(_position + length)); + + if (_len != writeBuffer.length) { + throw new Error('code review') + } + + } + } + _position += length; + } + + + override public function writeBytes(bytes:ArrayBuffer, offset:uint = 0, length:uint = 0):void + { + if (length == 0) length = bytes.byteLength - offset ; + if (!length) return; + var src:Uint8Array = new Uint8Array(bytes, offset, offset + length); + var srcArray:Array = [].slice.call(src); + addByteSequence(srcArray); + } + + override public function writeUTF(str:String):void + { + var utcBytes:Uint8Array = getUTFBytes(str , true); + var srcArray:Array = [].slice.call(utcBytes); + addByteSequence(srcArray); + } + + override public function writeUTFBytes(str:String):void + { + var utcBytes:Uint8Array = getUTFBytes(str, false); + var srcArray:Array = [].slice.call(utcBytes); + addByteSequence(srcArray); + } + + protected function copyNumericBytes(byteCount:uint):void{ + //arr here is actually an Array, not Uint8Array + var arr:Uint8Array = getTypedArray(); + var numbers:Uint8Array = _numberBytes; + var idx:uint = 0; + while(byteCount--) { + arr[_position++] = numbers[idx++]; + } + } + + override public function writeFloat(val:Number):void + { + //always big endian + getDataView().setFloat32(0,val,false); + copyNumericBytes(4); + } + + override public function writeDouble(val:Number):void + { + //always big endian + getDataView().setFloat64(0,val,false); + copyNumericBytes(8); + } + + private function writeAMF_UTF(string:String):void{ + var utcBytes:Uint8Array = getUTFBytes(string , false); + var srcArray:Array = [].slice.call(utcBytes); + writeUInt29((srcArray.length << 1) | 1); + addByteSequence(srcArray); + } + + private function writeStringWithoutType(v:String):void { + if (v.length == 0) { + writeUInt29(1); + } else { + if (!this.stringByReference(v)) { + writeAMF_UTF(v); + } + } + } + + private function stringByReference(v:String):Boolean { + const strIndex:* = strings[v]; + const found:Boolean = strIndex !== undefined; + if (found) { + const ref:uint = strIndex; + writeUInt29(ref << 1); + } else { + strings[v] = stringCount++; + } + return found; + } + + public function objectByReference(v:Object):Boolean { + const ref:int = objects.indexOf(v); + const found:Boolean = ref !== -1; + if (found) { + writeUInt29(ref << 1); + } else { + objects.push(v); + objectCount++; + } + return found; + } + + private function traitsByReference(props:Array, alias:String):Boolean { + //@todo review this. Don't think it is necessary to do the long joins with the props + //maybe alias alone is enough...? + const s:String = alias + "|" + props.join("|"); + const traitsIndex:* = traits[s]; + const found:Boolean = traitsIndex !== undefined; + if (found) { + const ref:uint = traitsIndex; + writeUInt29((ref << 2) | 1); + } else { + traits[s] = traitCount++; + } + return found; + } + + private function writeAmfInt(v:int):void { + if (v >= INT28_MIN_VALUE && v <= INT28_MAX_VALUE) { + v = v & UINT29_MASK; + writeByte(AMF3_INTEGER); + writeUInt29(v); + } else { + writeByte(AMF3_DOUBLE); + writeDouble(v); + } + } + + private function writeDate(v:Date):void { + writeByte(AMF3_DATE); + if (!objectByReference(v)) { + writeUInt29(1); + writeDouble(v.getTime()); + } + } + + private function filterSerializableMembers(fieldSet:Object, accessChecks:Object, localTraits:Traits, asAccessors:Boolean = false, excludeTransient:Boolean = true):Array { + var l:uint; + var metas:Array; + var exclude:Boolean; + var fieldName:String; + const into:Array = localTraits.props; + + for (fieldName in fieldSet) { + //exclude all static props + if (fieldName.charAt(0) == '|') continue; + var field:Object = fieldSet[fieldName]; + exclude = false; + if (asAccessors) { + exclude = field.access != 'readwrite'; + if (exclude && into.indexOf(fieldName) == -1) { //<-- if at some level we already have read-write access, then that wins + //check: does it combine to provide 'readwrite' permissions via accessChecks through inheritance chain + if (accessChecks[fieldName] && accessChecks[fieldName] != field.access) { + //readonly or writeonly overridde at one level and different at another == readwrite + exclude = false; + } else { + if (!accessChecks[fieldName]) { + //cache for subsequent cross-checks as above + accessChecks[fieldName] = field.access; + } + } + } + } + if (!exclude && excludeTransient && field.metadata != null) { + //exclude anything marked as Transient + metas = field.metadata(); + l = metas.length; + while (l--) { + if (metas[l].name == 'Transient') { + exclude = true; + } + } + if (exclude && into.indexOf(fieldName) != -1) { + //?possible case where it is marked transient on an ancestor but not in a subclass override + //it will not have been excluded when processing the subclass, which occurs first, so remove it now + //@todo untested : check this scenario, assume it should be removed + into.splice(into.indexOf(fieldName), 1); + } + } + if (!exclude) { + //set up null/undefined value lookups for undefined field values (when encoding) + var nullValues:Object = localTraits.nullValues; + if (field.type == 'Number') { + nullValues[fieldName] = Number.NaN; + } else if (field.type == 'Boolean') { + nullValues[fieldName] = false; + } else if (field.type == 'int' || field.type == 'uint') { + nullValues[fieldName] = 0; + } else if (field.type == '*') { + nullValues[fieldName] = undefined; + } else { + nullValues[fieldName] = null; + } + into.push(fieldName); + if (asAccessors) { + localTraits.getterSetters[fieldName] = Traits.createInstanceAccessorGetterSetter(fieldName); + } else { + //variable + localTraits.getterSetters[fieldName] = Traits.createInstanceVariableGetterSetter(field.get_set, field.type); + } + } + } + return into; + } + + private function populateSerializableMembers(reflectionInfo:Object, accessChecks:Object, localTraits:Traits):Array { + if (!reflectionInfo) return localTraits.props; + var fields:Object = reflectionInfo.variables(); + filterSerializableMembers(fields, accessChecks, localTraits, false, true); + fields = reflectionInfo.accessors(); + filterSerializableMembers(fields, accessChecks, localTraits, true, true); + return localTraits.props; + } + + private function getLocalTraitsInfo(instance:Object):Traits { + var classInfo:Object = instance.ROYALE_CLASS_INFO; + var originalClassInfo:Object; + var localTraits:Traits; + if (classInfo) { + localTraits = classInfo.localTraits; + if (localTraits) { + //implementation note: @todo a class may have more than one alias point to it + //update alias, in case of registration of alias changed since traits was last cached. + localTraits.alias = classInfo.alias || ''; + return classInfo.localTraits; + } + originalClassInfo = classInfo; + localTraits = new Traits(); + var alias:String = classInfo.alias;// getAliasByClass(instance.constructor as Class); //<- @todo possible optimization: registerClassAlias implementation stores in the classInfo Object, access directly + if (alias) localTraits.alias = alias; + else localTraits.alias = ''; + localTraits.qName = classInfo.names[0].qName; + localTraits.isDynamic = Boolean(classInfo.names[0].isDynamic); + localTraits.externalizable = instance is IExternalizable; + + if (localTraits.externalizable) { + localTraits.count = 0; + } else { + var accessChecks:Object = {}; + var c:Object = instance; + while (classInfo) { + var reflectionInfo:Object = c.ROYALE_REFLECTION_INFO(); + populateSerializableMembers(reflectionInfo, accessChecks, localTraits); + if (!c.constructor.superClass_ || !c.constructor.superClass_.ROYALE_CLASS_INFO) + break; + classInfo = c.constructor.superClass_.ROYALE_CLASS_INFO; + c = c.constructor.superClass_; + } + localTraits.count = localTraits.props.length; + //not required, but useful when testing: + localTraits.props.sort(); + } + //cache in the classInfo for faster lookups next time + originalClassInfo.localTraits = localTraits; + } else { + //assume dynamic, anon object + if (Object == instance.constructor) { + localTraits = Traits.getBaseObjectTraits(); + } else { + //could be a class object + var anonFields:Array = []; + for (var key:String in instance) { + if (key !== "") { + anonFields.push(key); + } + } + localTraits = Traits.getDynObjectTraits(anonFields); + } + //not required, but useful when testing: + localTraits.props.sort(); + + } + return localTraits; + } + + public function writeObjectExternal(v:*, position:uint, mergeIntoOwner:Function):uint { + writeMode = true; + _position = 0; + _len = 0; + try{ + writeObject(v); + } catch (e:Error) { + _error = e; + } + var output:Uint8Array = new Uint8Array(writeBuffer); + reset(); + writeMode = false; + return mergeIntoOwner(position, output); + } + + /** + * @royaleignorecoercion Class + * @royaleignorecoercion String + * @royaleignorecoercion Number + * @royaleignorecoercion Array + */ + public function writeObject(v:*):void { + if (v == null) { + writeByte(AMF3_NULL); + return; + } + if (v is Function) { + //output function value as undefined + writeByte(AMF3_UNDEFINED); + return; + } + if (v is String) { + writeByte(AMF3_STRING); + writeStringWithoutType(v as String); + } else if (v is Number) { + var n:Number = v as Number; + if (n === +n && n === (n | 0)) { + writeAmfInt(n); + } else { + writeByte(AMF3_DOUBLE); + writeDouble(n); + } + } else if (v is Boolean) + writeByte((v + ? AMF3_BOOLEAN_TRUE + : AMF3_BOOLEAN_FALSE)); + else if (v is Date) + writeDate(v as Date); + else { + if (v is Array) { + if (v.toString().indexOf("[Vector") == 0) + writeVector(v); + else + writeArray(v as Array); + } else writeObjectVariant(v); + } + } + + /** + * + * @royaleignorecoercion BinaryData + */ + private function writeObjectVariant(v:Object):void { + if (v is AMFBinaryData || v is BinaryData) { + writeByte(AMF3_BYTEARRAY); + var binaryData:BinaryData = v as BinaryData; + var len:uint = binaryData.length; + writeUInt29(len); + writeBinaryData(binaryData, 0, len); + return; + } + + writeByte(AMF3_OBJECT); + if (!this.objectByReference(v)) { + const localTraits:Traits = getLocalTraitsInfo(v); + if (localTraits.externalizable && !localTraits.alias) { + //in flash player if you try to write an object with no alias that is externalizable it does this: + throw new Error("ArgumentError: Error #2004: One of the parameters is invalid."); + } + writeTypedObject(v, localTraits); + } + } + + + private function writeTypedObject(v:Object, localTraits:Traits):void { + var encodedName:String = localTraits.alias && localTraits.alias.length ? localTraits.alias : ']:' + localTraits.qName + ":["; + + if (!traitsByReference(localTraits.props, encodedName)) { + this.writeUInt29(3 | (localTraits.externalizable ? 4 : 0) | (localTraits.isDynamic ? 8 : 0) | (localTraits.count << 4)); + this.writeStringWithoutType(localTraits.alias); + + if (!localTraits.externalizable) { + var l:uint = localTraits.count; + for (var i:uint = 0; i < l; i++) { + this.writeStringWithoutType(localTraits.props[i]); + } + } + } + + if (localTraits.externalizable) { + v.writeExternal(this); + } else { + l = localTraits.count; + for (i = 0; i < l; i++) { + //sealed props + var val:* = localTraits.getterSetters[localTraits.props[i]].getValue(v); + if (val === null || val === undefined) { + //coerce null values to the 'correct' types + val = localTraits.nullValues[localTraits.props[i]]; + + //handle '*' type which can be undefined or explicitly null + if (val === undefined && localTraits.getterSetters[localTraits.props[i]].getValue(v) === null) { + val = null; + } + } + this.writeObject(val); + } + + if (localTraits.isDynamic) { + var dynFields:Array = getDynamicFields(v); + i = 0; + l = dynFields.length; + for (; i < l; i++) { + this.writeStringWithoutType(dynFields[i]); + this.writeObject(v[dynFields[i]]); + } + this.writeStringWithoutType(EMPTY_STRING); + } + } + } + + /*private function writeDynamicObject(v:Object, localTraits:Traits):void { + if (!this.traitsByReference([], '$DynObject$')) { //<-something to represent an anonymous object + this.writeUInt29(11 /!* 3 | 8 == dynamic *!/); + this.writeStringWithoutType(EMPTY_STRING); //class name + } + var i:uint = 0; + var l:uint = localTraits.props.length; + for (; i < l; i++) { + this.writeStringWithoutType(localTraits.props[i]); + this.writeObject(v[localTraits.props[i]]); + } + this.writeStringWithoutType(EMPTY_STRING); + }*/ + + /** + * + * @royaleignorecoercion String + */ + private function writeArray(v:Array):void { + writeByte(AMF3_ARRAY); + var len:uint = v.length; + var i:uint = 0; + var akl:uint = 0; //associative keys length + if (!this.objectByReference(v)) { + var denseLength:uint = len; + var keys:Array = Object.keys(v); + var associativeKeys:Array; + //profile the array + if (keys.length != len) { + //Array is not strict + if (len) { + associativeKeys = []; + var kl:uint = keys.length; + //find denseLength + for (i=0;i<len;i++) { + if (keys.indexOf(''+i) == -1) break; + } + denseLength = i; + + for (i=0;i<kl;i++) { + + var key:String = keys[i] as String; + var numKey:Number = Number(key); + if (isNaN(numKey) || numKey > denseLength || numKey<0 || uint(numKey) != numKey) { + associativeKeys[akl++] = key; + } + } + + } else associativeKeys = keys; + } + this.writeUInt29((denseLength << 1) | 1); + + if (akl) { + //name-value pairs of associative keys + for (i = 0; i < akl; i++) { + this.writeStringWithoutType(associativeKeys[i] as String); + this.writeObject(v[associativeKeys[i]]); + } + } + //empty string 'terminates' associative keys block - no more associative keys (if there were any) + writeStringWithoutType(EMPTY_STRING); + if (denseLength) { + for (i = 0; i < denseLength; i++) { + writeObject(v[i]); + } + } + } + } + + private function writeVector(v:Object):void { + writeByte(v.type); + var i:uint; + var len:uint = v.length; + if (!this.objectByReference(v)) { + this.writeUInt29((len << 1) | 1); + this.writeBoolean(v.fixed); + } + if (v.type == AMF3_VECTOR_OBJECT) { + var className:String = ""; + if (len > 0) { + // TODO: how much of the PHP logic can we do here + className = v[0].constructor.name; + } + this.writeStringWithoutType(className); + for (i = 0; i < len; i++) { + writeObject(v[i]); + } + } else if (v.type == AMF3_VECTOR_INT) { + for (i = 0; i < len; i++) { + writeInt(v[i]); + } + } else if (v.type == AMF3_VECTOR_UINT) { + for (i = 0; i < len; i++) { + writeUnsignedInt(v[i]); + } + } else if (v.type == AMF3_VECTOR_DOUBLE) { + for (i = 0; i < len; i++) { + writeDouble(v[i]); + } + } + } + + public function readUInt29():int { + const read:Function = readUnsignedByte; + var b:uint = read() & 255; + if (b < 128) { + return b; + } + var value:uint = (b & 127) << 7; + b = read() & 255; + if (b < 128) + return (value | b); + value = (value | (b & 127)) << 7; + b = read() & 255; + if (b < 128) + return (value | b); + value = (value | (b & 127)) << 8; + b = read() & 255; + return (value | b); + } + + /** + * + * @royaleignorecoercion ArrayBuffer + */ + public function readObjectExternal():* { + if (ba != owner.data) { + ba = owner.data as ArrayBuffer; + _typedArray = new Uint8Array(ba); + } + _position = owner.position; + _len = owner.length; + try{ + var result:* = readObject(); + } catch (e:Error) { + _error = e; + } + reset(); + owner.position = _position; + return result; + } + + public function readObject():* { + var amfType:uint = readUnsignedByte(); + return readObjectValue(amfType); + } + + public function readString():String { + var ref:uint = readUInt29(); + if ((ref & 1) == 0) { + return getString(ref >> 1); + } else { + var len:uint = (ref >> 1); + if (len == 0) { + return EMPTY_STRING; + } + var str:String = readUTFBytes(len); + rememberString(str); + return str; + } + } + + private function rememberString(v:String):void { + strings[stringCount++] = v; + } + + private function getString(v:uint):String { + return strings[v]; + } + + private function getObject(v:uint):Object { + + return objects[v]; + } + + + private function getTraits(v:uint):Traits { + return traits[v] as Traits; + } + + private function rememberTraits(v:Traits):void { + traits[traitCount++] = v; + } + + + private function rememberObject(v:Object):void { + objects.push(v); + } + + private function readTraits(ref:uint):Traits { + var ti:Traits; + if ((ref & 3) == 1) { + ti = getTraits(ref >> 2); + return ti; + } else { + ti = new Traits(); + ti.externalizable = ((ref & 4) == 4); + ti.isDynamic = ((ref & 8) == 8); + ti.count = (ref >> 4); + var className:String = readString(); + if (className != null && className != "") { + ti.alias = className; + } + + for (var i:int = 0; i < ti.count; i++) { + ti.props.push(readString()); + } + + rememberTraits(ti); + return ti; + } + } + + private function readScriptObject():Object { + var ref:uint = readUInt29(); + if ((ref & 1) == 0) { + //retrieve object from object reference table + return getObject(ref >> 1); + } else { + var decodedTraits:Traits = readTraits(ref); + var obj:Object; + var localTraits:Traits; + if (decodedTraits.alias) { + var c:Class = getClassByAlias(decodedTraits.alias); + if (c) { + obj = new c(); + localTraits = getLocalTraitsInfo(obj); + } else { + obj = {}; + } + } else { + obj = {}; + } + rememberObject(obj); + if (decodedTraits.externalizable) { + obj.readExternal(this); + } else { + const l:uint = decodedTraits.props.length; + var hasProp:Boolean; + for (var i:uint = 0; i < l; i++) { + var fieldValue:* = readObject(); + var prop:String = decodedTraits.props[i]; + hasProp = localTraits && (localTraits.hasProp(prop) || localTraits.isDynamic); + if (hasProp) { + localTraits.getterSetters[prop].setValue(obj, fieldValue); + } else { + if (!localTraits) { + obj[prop] = fieldValue; + } else { + //@todo add debug-only logging for error checks (e.g. ReferenceError: Error #1074: Illegal write to read-only property) + if (goog.DEBUG) { + trace('ReferenceError: Error #1056: Cannot create property ' + prop + ' on ' + decodedTraits.alias); + } + } + } + } + if (decodedTraits.isDynamic) { + for (; ;) { + var name:String = readString(); + if (name == null || name.length == 0) { + break; + } + obj[name] = readObject(); + } + } + } + return obj; + } + } + + /** + * @royaleignorecoercion Array + */ + public function readArray():Array { + var ref:uint = readUInt29(); + if ((ref & 1) == 0) + return getObject(ref >> 1) as Array; + var denseLength:uint = (ref >> 1); + var array:Array = []; + rememberObject(array); + while (true) { + var name:String = readString(); + if (!name) + break; + //associative keys first + array[name] = readObject(); + } + //then dense array keys + for (var i:uint = 0; i < denseLength; i++) { + array[i] = readObject(); + } + return array; + } + + /** + * @royaleignorecoercion Array + */ + public function readDate():Date { + var ref:uint = readUInt29(); + if ((ref & 1) == 0) + return getObject(ref >> 1) as Date; + var time:Number = readDouble(); + var date:Date = new Date(time); + rememberObject(date); + return date; + } + + public function readByteArray():AMFBinaryData { + var ref:uint = readUInt29(); + if ((ref & 1) == 0) + return getObject(ref >> 1) as AMFBinaryData; + else { + var len:uint = (ref >> 1); + var bytes:Uint8Array = new Uint8Array(len); + bytes.set(new Uint8Array(getTypedArray(), _position, len)); + _position += len; + var ba:AMFBinaryData = new AMFBinaryData(bytes.buffer); + rememberObject(ba); + return ba; + } + } + + private function toVector(type:uint, array:Array, fixed:Boolean):Array { + // TODO (aharui) handle vectors + return array; + } + + private function readAmf3Vector(amfType:uint):Object { + var ref:uint = readUInt29(); + if ((ref & 1) == 0) + return getObject(ref >> 1); + var len:uint = (ref >> 1); + var vector:Array = toVector(amfType, [], readBoolean()); + var i:uint; + if (amfType === AMF3_VECTOR_OBJECT) { + readString(); //className + for (i = 0; i < len; i++) + vector.push(readObject()); + } else if (amfType === AMF3_VECTOR_INT) { + for (i = 0; i < len; i++) + vector.push(readInt()); + } else if (amfType === AMF3_VECTOR_UINT) { + for (i = 0; i < len; i++) + vector.push(readUnsignedInt()); + } else if (amfType === AMF3_VECTOR_DOUBLE) { + for (i = 0; i < len; i++) + vector.push(readDouble()); + } + rememberObject(vector); + return vector; + } + + private function readObjectValue(amfType:uint):Object { + var value:Object = null; + var u:uint; + + switch (amfType) { + case AMF3_STRING: + value = readString(); + break; + case AMF3_OBJECT: + try { + value = readScriptObject(); + } catch (e:Error) { + throw new Error("Failed to deserialize: " + e); + } + break; + case AMF3_ARRAY: + value = readArray(); + break; + case AMF3_BOOLEAN_FALSE: + value = false; + break; + case AMF3_BOOLEAN_TRUE: + value = true; + break; + case AMF3_INTEGER: + u = readUInt29(); + // Symmetric with writing an integer to fix sign bits for + // negative values... + value = (u << 3) >> 3; + break; + case AMF3_DOUBLE: + value = readDouble(); + break; + case AMF3_UNDEFINED: + case AMF3_NULL: + break; + case AMF3_DATE: + value = readDate(); + break; + case AMF3_BYTEARRAY: + value = readByteArray(); + break; + case AMF3_VECTOR_INT: + case AMF3_VECTOR_UINT: + case AMF3_VECTOR_DOUBLE: + case AMF3_VECTOR_OBJECT: + value = readAmf3Vector(amfType); + break; + case AMF0_AMF3: + value = readObject(); + break; + default: + throw new Error("Unsupported AMF type: " + amfType); + } + return value; + } +} + +COMPILE::JS +/** + * @royalesuppresspublicvarwarning + */ +class Traits { + import goog.DEBUG; + +/* public static function createInstanceVariableGetter(fromFunc:Function, type:String):Function{ + if (type == "*") { + return function(inst:Object):* { + return fromFunc(inst, fromFunc); + } + } else { + return function(inst:Object):* { + fromFunc(inst); + } + } + } + + public static function createInstanceVariableSetter(toFunc:Function, type:String):Function{ + return function(inst:Object, value:*):void { + toFunc(inst, value); + } + }*/ + + public static function createInstanceVariableGetterSetter(reflectionFunction:Function, type:String):Object{ + var ret:Object = { + setValue: function(inst:Object, value:*):void { + reflectionFunction(inst, value); + } + }; + + if (type == "*") { + ret.getValue = + function(inst:Object):* { + return reflectionFunction(inst, reflectionFunction); + } + } else { + ret.getValue = + function(inst:Object):* { + return reflectionFunction(inst); + } + } + return ret; + } + + public static function createInstanceAccessorGetterSetter(fieldName:String):Object{ + return { + getValue: function(inst:Object):* { + return inst[fieldName]; + }, + setValue: function(inst:Object, value:*):void { + inst[fieldName] = value; + } + }; + } + + private static var _emtpy_object:Traits; + + + + public static function getClassTraits(fields:Array, qName:String):Traits{ + var traits:Traits = new Traits(); + traits.qName = '[Class] '+ qName; + traits.isDynamic = true; + traits.externalizable = false; + traits.props = fields; + + return traits; + } + + public static function getBaseObjectTraits():Traits { + if (_emtpy_object) return _emtpy_object; + var traits:Traits = _emtpy_object = new Traits(); + traits.qName = 'Object'; + traits.externalizable = false; + traits.isDynamic = true; + return traits; + } + + public static function getDynObjectTraits(fields:Array):Traits { + var traits:Traits; + traits = new Traits(); + traits.qName = 'Object'; + traits.externalizable = false; + traits.isDynamic = true; + traits.props = fields; + return traits; + } + + public var alias:String = ''; + public var qName:String; + public var externalizable:Boolean; + public var isDynamic:Boolean; + public var count:uint = 0; + public var props:Array = []; + public var nullValues:Object = {}; + + public var getterSetters:Object = {}; + + public function hasProp(prop:String):Boolean { + return props.indexOf(prop) != -1; + } + + public function toString():String { + if (goog.DEBUG) { + return 'Traits for \'' + qName + '\'\n' + + 'alias: \'' + alias + '\'\n' + + 'externalizable:' + Boolean(externalizable) + '\n' + + 'isDynamic:' + Boolean(isDynamic) + '\n' + + 'count:' + count + '\n' + + 'props:\n\t' + props.join('\n\t'); + } else { + return 'Traits'; + } + } +} + + diff --git a/frameworks/projects/Network/src/main/royale/org/apache/royale/net/remoting/amf/AMFNetConnection.as b/frameworks/projects/Network/src/main/royale/org/apache/royale/net/remoting/amf/AMFNetConnection.as index e845098..d176fcc 100644 --- a/frameworks/projects/Network/src/main/royale/org/apache/royale/net/remoting/amf/AMFNetConnection.as +++ b/frameworks/projects/Network/src/main/royale/org/apache/royale/net/remoting/amf/AMFNetConnection.as @@ -467,13 +467,13 @@ public class AMFNetConnection //reader.pos += 4; //length //reader.reset(); var len:uint = reader.readUnsignedInt(); - trace('readHeader len',len); + //trace('readHeader len',len); var type:uint = reader.readUnsignedByte(); if (type != 2) { //amf0 string throw "Only string header data supported."; } header.data = reader.readUTF(); - trace('readHeader data:',header.data); + //trace('readHeader data:',header.data); return header; } @@ -485,7 +485,7 @@ public class AMFNetConnection body.responseURI = reader.readUTF(); //reader.pos += 4; //length var len:uint = reader.readUnsignedInt(); - trace('readBody len',len); + //trace('readBody len',len); //reader.reset(); body.data = reader.readObject(); return body; diff --git a/frameworks/projects/Network/src/main/royale/org/apache/royale/net/utils/IExternalizable.as b/frameworks/projects/Network/src/main/royale/org/apache/royale/net/utils/IDataInput.as similarity index 72% copy from frameworks/projects/Network/src/main/royale/org/apache/royale/net/utils/IExternalizable.as copy to frameworks/projects/Network/src/main/royale/org/apache/royale/net/utils/IDataInput.as index 19364f4..f3e4ce4 100644 --- a/frameworks/projects/Network/src/main/royale/org/apache/royale/net/utils/IExternalizable.as +++ b/frameworks/projects/Network/src/main/royale/org/apache/royale/net/utils/IDataInput.as @@ -18,13 +18,24 @@ //////////////////////////////////////////////////////////////////////////////// package org.apache.royale.net.utils { + import org.apache.royale.utils.IBinaryDataInput; + COMPILE::SWF{ + import flash.utils.IDataInput + } + /** - * replacement for flash.utils.IExternalizable + * initial work on replacement for flash.utils.IDataInput */ - public interface IExternalizable + COMPILE::JS + public interface IDataInput extends IBinaryDataInput { - function readExternal(input:IDataInput):void; - - function writeExternal(output:IDataOutput):void; + function readObject():*; } + + COMPILE::SWF + public interface IDataInput extends IBinaryDataInput, flash.utils.IDataInput + { + + } + } diff --git a/frameworks/projects/Network/src/main/royale/org/apache/royale/net/utils/IExternalizable.as b/frameworks/projects/Network/src/main/royale/org/apache/royale/net/utils/IDataOutput.as similarity index 66% copy from frameworks/projects/Network/src/main/royale/org/apache/royale/net/utils/IExternalizable.as copy to frameworks/projects/Network/src/main/royale/org/apache/royale/net/utils/IDataOutput.as index 19364f4..98edd55 100644 --- a/frameworks/projects/Network/src/main/royale/org/apache/royale/net/utils/IExternalizable.as +++ b/frameworks/projects/Network/src/main/royale/org/apache/royale/net/utils/IDataOutput.as @@ -18,13 +18,28 @@ //////////////////////////////////////////////////////////////////////////////// package org.apache.royale.net.utils { + import org.apache.royale.utils.IBinaryDataOutput; + COMPILE::SWF{ + import flash.utils.IDataOutput + } + /** - * replacement for flash.utils.IExternalizable + * initial work on replacement for flash.utils.IDataOutput */ - public interface IExternalizable + COMPILE::JS + public interface IDataOutput extends IBinaryDataOutput { - function readExternal(input:IDataInput):void; - - function writeExternal(output:IDataOutput):void; + + function writeObject(object:*):void; } + + + COMPILE::SWF + public interface IDataOutput extends IBinaryDataOutput, flash.utils.IDataOutput + { + /*function get objectEncoding():uint; + function set objectEncoding(value:uint):void; + function writeObject(object:*):void;*/ + } + } diff --git a/frameworks/projects/Network/src/main/royale/org/apache/royale/net/utils/IExternalizable.as b/frameworks/projects/Network/src/main/royale/org/apache/royale/net/utils/IExternalizable.as index 19364f4..c9ee92d 100644 --- a/frameworks/projects/Network/src/main/royale/org/apache/royale/net/utils/IExternalizable.as +++ b/frameworks/projects/Network/src/main/royale/org/apache/royale/net/utils/IExternalizable.as @@ -18,13 +18,26 @@ //////////////////////////////////////////////////////////////////////////////// package org.apache.royale.net.utils { + COMPILE::SWF{ + import flash.utils.IExternalizable; + } + /** * replacement for flash.utils.IExternalizable */ + COMPILE::JS public interface IExternalizable { function readExternal(input:IDataInput):void; function writeExternal(output:IDataOutput):void; } + + COMPILE::SWF + public interface IExternalizable extends flash.utils.IExternalizable + { + /*function readExternal(input:IDataInput):void; + + function writeExternal(output:IDataOutput):void;*/ + } } diff --git a/manualtests/UnitTests/src/main/royale/TestClasses.as b/manualtests/UnitTests/src/main/royale/TestClasses.as index 336e258..61462dc 100644 --- a/manualtests/UnitTests/src/main/royale/TestClasses.as +++ b/manualtests/UnitTests/src/main/royale/TestClasses.as @@ -22,7 +22,7 @@ package import flexUnitTests.*; - public class TestClasses + public class TestClasses { public static function get testClasses():Array { @@ -30,7 +30,8 @@ package CoreTester, ReflectionTester, ObservedBugsTester, - JiraIssuesTester + GithubIssuesTester, + NetworkTester ]; } } diff --git a/manualtests/UnitTests/src/main/royale/flexUnitTests/JiraIssuesTester.as b/manualtests/UnitTests/src/main/royale/flexUnitTests/GithubIssuesTester.as similarity index 93% copy from manualtests/UnitTests/src/main/royale/flexUnitTests/JiraIssuesTester.as copy to manualtests/UnitTests/src/main/royale/flexUnitTests/GithubIssuesTester.as index fc37533..72b5ee1 100644 --- a/manualtests/UnitTests/src/main/royale/flexUnitTests/JiraIssuesTester.as +++ b/manualtests/UnitTests/src/main/royale/flexUnitTests/GithubIssuesTester.as @@ -18,12 +18,12 @@ //////////////////////////////////////////////////////////////////////////////// package flexUnitTests { - import flexUnitTests.jira.* + import flexUnitTests.github.* [Suite] [RunWith("org.flexunit.runners.Suite")] - public class JiraIssuesTester + public class GithubIssuesTester { - + } } diff --git a/manualtests/UnitTests/src/main/royale/flexUnitTests/JiraIssuesTester.as b/manualtests/UnitTests/src/main/royale/flexUnitTests/NetworkTester.as similarity index 60% rename from manualtests/UnitTests/src/main/royale/flexUnitTests/JiraIssuesTester.as rename to manualtests/UnitTests/src/main/royale/flexUnitTests/NetworkTester.as index fc37533..ef8e73b 100644 --- a/manualtests/UnitTests/src/main/royale/flexUnitTests/JiraIssuesTester.as +++ b/manualtests/UnitTests/src/main/royale/flexUnitTests/NetworkTester.as @@ -18,12 +18,26 @@ //////////////////////////////////////////////////////////////////////////////// package flexUnitTests { - import flexUnitTests.jira.* - + import flexUnitTests.network.* + [Suite] [RunWith("org.flexunit.runners.Suite")] - public class JiraIssuesTester + public class NetworkTester { + public function NetworkTester() + { + // for JS, force-link these classes in the output + var arr:Array = [AMFBinaryDataTesterTest]; + } + // in JS, using a class as a type won't include the class in + // the output since types are not chcked in JS. It is when + // the actual class is referenced that it will be included + // in the output. + // Is there a reason to use reflection to gather the set + // of tests? I would think an array of tests would wokr + // better and allow you to define order. + public var amfBinaryDataTesterTest:AMFBinaryDataTesterTest; + } } diff --git a/manualtests/UnitTests/src/main/royale/flexUnitTests/jira/JiraTesterTest.as b/manualtests/UnitTests/src/main/royale/flexUnitTests/github/GithubTesterTest.as similarity index 95% rename from manualtests/UnitTests/src/main/royale/flexUnitTests/jira/JiraTesterTest.as rename to manualtests/UnitTests/src/main/royale/flexUnitTests/github/GithubTesterTest.as index c361f39..0cf850d 100644 --- a/manualtests/UnitTests/src/main/royale/flexUnitTests/jira/JiraTesterTest.as +++ b/manualtests/UnitTests/src/main/royale/flexUnitTests/github/GithubTesterTest.as @@ -16,13 +16,13 @@ // limitations under the License. // //////////////////////////////////////////////////////////////////////////////// -package flexUnitTests.jira +package flexUnitTests.github { import flexunit.framework.Assert; - public class JiraTesterTest - { + public class GithubTesterTest + { public static var isJS:Boolean; [BeforeClass] public static function setUpBeforeClass():void @@ -60,14 +60,14 @@ package flexUnitTests.jira /* - // TEST METHODS + // TEST METHODS */ - + //example, postfix the test method with JIRA issue reference: /*[Test] public function testJIRA_FLEX_9999():void { - }*/ + }*/ } } diff --git a/manualtests/UnitTests/src/main/royale/flexUnitTests/network/AMFBinaryDataTesterTest.as b/manualtests/UnitTests/src/main/royale/flexUnitTests/network/AMFBinaryDataTesterTest.as new file mode 100644 index 0000000..af446a8 --- /dev/null +++ b/manualtests/UnitTests/src/main/royale/flexUnitTests/network/AMFBinaryDataTesterTest.as @@ -0,0 +1,335 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to You under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////////// +package flexUnitTests.network +{ + + + import flexUnitTests.network.support.TestClass1; + import flexUnitTests.network.support.TestClass2; + import flexUnitTests.network.support.TestClass3; + import flexUnitTests.network.support.DynamicTestClass; + + import flexunit.framework.Assert; + import org.apache.royale.net.remoting.amf.AMFBinaryData; + + import org.apache.royale.reflection.*; + + + public class AMFBinaryDataTesterTest + { + + [Before] + public function setUp():void { + } + + [After] + public function tearDown():void { + } + + [BeforeClass] + public static function setUpBeforeClass():void { + } + + [AfterClass] + public static function tearDownAfterClass():void { + } + + + //util check functions + private static function bytesMatchExpectedData(bd:AMFBinaryData,expected:Array,offset:int=0):Boolean{ + var len:uint = expected.length; + var end:uint=offset+len; + for (var i:int=offset;i<end;i++) { + var check:uint = bd.readByteAt(i); + if (expected[i-offset]!=check) { + // trace('failed at ',i,expected[i-offset],check); + return false; + } + } + return true; + } + + private static function dynamicKeyCountMatches(forObject:Object, expectedCount:uint):Boolean{ + var keyCount:uint=0; + for (var key:String in forObject) { + keyCount++; + } + return keyCount === expectedCount; + + } + + [Test] + public function testStringObjectEncoding():void{ + var ba:AMFBinaryData = new AMFBinaryData(); + var testString:String = 'testString'; + + ba.writeObject(testString); + + Assert.assertEquals("post-write length was not correct", ba.length, 12); + Assert.assertEquals("post-write position was not correct", ba.position, 12); + ba.position = 0; + var readString:String = ba.readObject() as String; + Assert.assertEquals("post-write read of written string was not correct", readString, testString); + } + + [Test] + public function testBooleanObjectEncoding():void{ + var ba:AMFBinaryData = new AMFBinaryData(); + + ba.writeObject(false); + ba.writeObject(true); + + Assert.assertEquals("post-write length was not correct", ba.length, 2); + Assert.assertEquals("post-write position was not correct", ba.position, 2); + ba.position = 0; + + Assert.assertTrue("post-write read of written boolean was not correct", ba.readObject() === false); + Assert.assertTrue("post-write read of written boolean was not correct", ba.readObject() === true); + } + + [Test] + public function testNumberEncoding():void{ + var ba:AMFBinaryData = new AMFBinaryData(); + + + ba.writeObject(NaN); + ba.writeObject(1.0); + ba.writeObject(-1.0); + ba.writeObject(1.5); + ba.writeObject(-1.5); + ba.writeObject(Infinity); + ba.writeObject(-Infinity); + + Assert.assertEquals("post-write length was not correct", ba.length, 52); + Assert.assertEquals("post-write position was not correct", ba.position, 52); + ba.position = 0; + + var num:Number = ba.readObject(); + Assert.assertTrue("post-write read of written Number was not correct", (num is Number)); + Assert.assertTrue("post-write read of written Number was not correct", isNaN(num)); + num = ba.readObject(); + Assert.assertTrue("post-write read of written Number was not correct", (num is Number)); + Assert.assertTrue("post-write read of written Number was not correct", num === 1.0); + num = ba.readObject(); + Assert.assertTrue("post-write read of written Number was not correct", (num is Number)); + Assert.assertTrue("post-write read of written Number was not correct", num === -1.0); + num = ba.readObject(); + Assert.assertTrue("post-write read of written Number was not correct", (num is Number)); + Assert.assertTrue("post-write read of written Number was not correct", num === 1.5); + num = ba.readObject(); + Assert.assertTrue("post-write read of written Number was not correct", (num is Number)); + Assert.assertTrue("post-write read of written Number was not correct", num === -1.5); + num = ba.readObject(); + Assert.assertTrue("post-write read of written Number was not correct", (num is Number)); + Assert.assertTrue("post-write read of written Number was not correct", !isFinite(num)); + Assert.assertTrue("post-write read of written Number was not correct", (num > 0)); + num = ba.readObject(); + Assert.assertTrue("post-write read of written Number was not correct", (num is Number)); + Assert.assertTrue("post-write read of written Number was not correct", !isFinite(num)); + Assert.assertTrue("post-write read of written Number was not correct", (num < 0)); + } + + + [Test] + public function testArrayInstance():void { + var ba:AMFBinaryData = new AMFBinaryData(); + var instance:Array = []; + ba.writeObject(instance); + + Assert.assertEquals("post-write length was not correct", ba.length, 3); + Assert.assertEquals("post-write position was not correct", ba.position, 3); + + instance = [99]; + ba.length = 0; + ba.writeObject(instance); + ba.position = 0; + Assert.assertTrue("post-write bytes did not match expected data", bytesMatchExpectedData(ba,[9, 3, 1, 4, 99])); + instance = ba.readObject() as Array; + Assert.assertTrue("post-write read did not match expected result", instance.length == 1 && instance[0] == 99); + //sparse array + instance =[]; + instance[100]='100'; + ba.length = 0; + ba.writeObject(instance); + Assert.assertEquals("post-write length was not correct", ba.length, 9); + Assert.assertEquals("post-write position was not correct", ba.position, 9); + Assert.assertTrue("post-write bytes did not match expected data", bytesMatchExpectedData(ba,[9, 1, 7, 49, 48, 48, 6, 0, 1])); + //check that read matches + ba.position = 0; + instance = ba.readObject(); + + Assert.assertEquals("post-write read was not correct", instance.length, 101); + Assert.assertEquals("post-write read was not correct", instance[100], '100'); + Assert.assertTrue("post-write read was not correct", instance[0] === undefined); + //sparse with associative content + instance=[]; + instance['test'] = true; + instance[10] = 'I am number 10'; + ba.length = 0; + ba.writeObject(instance); + Assert.assertEquals("post-write length was not correct", ba.length, 28); + Assert.assertEquals("post-write position was not correct", ba.position, 28); + Assert.assertTrue("post-write bytes did not match expected data", bytesMatchExpectedData(ba,[9, 1, 5, 49, 48, 6, 29, 73, 32, 97, 109, 32, 110, 117, 109, 98, 101, 114, 32, 49, 48, 9, 116, 101, 115, 116, 3, 1])); + + //check that read matches + ba.position = 0; + instance = ba.readObject(); + Assert.assertEquals("post-write read was not correct", instance.length, 11); + Assert.assertEquals("post-write read was not correct", instance[10], 'I am number 10'); + Assert.assertEquals("post-write read was not correct", instance['test'], true); + Assert.assertTrue("post-write read was not correct", instance[0] === undefined); + + } + + + [Test] + public function testAnonObject():void{ + var ba:AMFBinaryData = new AMFBinaryData(); + + var instance:Object = {}; + ba.writeObject(instance); + + Assert.assertEquals("post-write length was not correct", ba.length, 4); + Assert.assertEquals("post-write position was not correct", ba.position, 4); + ba.position = 0; + + Assert.assertTrue("post-write bytes did not match expected data", bytesMatchExpectedData(ba,[10, 11, 1, 1])); + instance = ba.readObject(); + Assert.assertTrue("post-write read did not match expected result", dynamicKeyCountMatches(instance, 0)); + + var obj1:Object = {test:true}; + var obj2:Object = {test:'maybe'}; + var obj3:Object = {test:true}; + ba.length = 0; + ba.writeObject([obj1, obj2, obj3]); + ba.position = 0; + Assert.assertTrue("post-write bytes did not match expected data", bytesMatchExpectedData(ba,[9, 7, 1, 10, 11, 1, 9, 116, 101, 115, 116, 3, 1, 10, 1, 0, 6, 11, 109, 97, 121, 98, 101, 1, 10, 1, 0, 3, 1])); + + } + + + [Test] + /** + * @royaleigrnorecoercion TestClass1 + */ + public function testBasicClassInstance():void + { + var ba:AMFBinaryData = new AMFBinaryData(); + + var instance:TestClass1 = new TestClass1(); + ba.writeObject(instance); + + Assert.assertEquals("post-write length was not correct", ba.length, 16); + Assert.assertEquals("post-write position was not correct", ba.position, 16); + + ba.position = 0; + Assert.assertTrue("post-write bytes did not match expected data", bytesMatchExpectedData(ba,[10, 19, 1, 21, 116, 101, 115, 116, 70, 105, 101, 108, 100, 49, 6, 1])); + ba.position = 0; + + var anonObject:Object = ba.readObject(); + + Assert.assertTrue('post-write read did not match expected value', anonObject['testField1'] === instance.testField1 ); + + var multipleDifferentInstances:Array = [new TestClass1(), new TestClass2()]; + ba.length = 0; + ba.writeObject(multipleDifferentInstances); + + Assert.assertEquals("post-write length was not correct", ba.length, 24); + Assert.assertEquals("post-write position was not correct", ba.position, 24); + ba.position = 0; + + Assert.assertTrue("post-write bytes did not match expected data", bytesMatchExpectedData(ba,[9, 5, 1, 10, 19, 1, 21, 116, 101, 115, 116, 70, 105, 101, 108, 100, 49, 6, 1, 10, 19, 1, 0, 3])); + + } + + [Test] + public function testDynamicClassInstance():void + { + var ba:AMFBinaryData = new AMFBinaryData(); + var instance:DynamicTestClass = new DynamicTestClass(); + ba.writeObject(instance); + + Assert.assertEquals("post-write length was not correct", ba.length, 25); + Assert.assertEquals("post-write position was not correct", ba.position, 25); + ba.position = 0; + Assert.assertTrue("post-write bytes did not match expected data", bytesMatchExpectedData(ba,[10, 27, 1, 39, 115, 101, 97, 108, 101, 100, 73, 110, 115, 116, 97, 110, 99, 101, 80, 114, 111, 112, 49, 2, 1])); + + instance['someDynamicField'] = 'nonSealedPropValue'; + + ba.writeObject(instance); + Assert.assertEquals("post-write length was not correct", ba.length, 62); + Assert.assertEquals("post-write position was not correct", ba.position, 62); + ba.position = 0; + Assert.assertTrue("post-write bytes did not match expected data", bytesMatchExpectedData(ba,[10, 27, 1, 39, 115, 101, 97, 108, 101, 100, 73, 110, 115, 116, 97, 110, 99, 101, 80, 114, 111, 112, 49, 2, 33, 115, 111, 109, 101, 68, 121, 110, 97, 109, 105, 99, 70, 105, 101, 108, 100, 6, 37, 110, 111, 110, 83, 101, 97, 108, 101, 100, 80, 114, 111, 112, 86, 97, 108, 117, 101, 1])); + + var instanceAnon:Object = ba.readObject(); + Assert.assertTrue('post-write read did not match expected value', instanceAnon['someDynamicField'] === 'nonSealedPropValue' ); + + + } + + + [Test] + public function testExternalizable():void + { + var ba:AMFBinaryData = new AMFBinaryData(); + var test3:TestClass3 = new TestClass3(); + //TestClass3 is externalizable and does not have an alias, this is an error in flash + + var err:Error; + try { + ba.writeObject(test3); + } catch(e:Error) { + err = e; + } + + Assert.assertTrue("externalizable writing should fail without an alias registered", err != null); + Assert.assertEquals("post-write error length was not correct", ba.length, 1); + Assert.assertEquals("post-write error position was not correct", ba.position, 1); + ba.position = 0; + Assert.assertTrue("post-write bytes did not match expected data", bytesMatchExpectedData(ba,[10])); + + ba.length=0; + //register an alias + registerClassAlias('TestClass3', TestClass3); + ba.writeObject(test3); + Assert.assertEquals("post-write length was not correct", ba.length, 18); + Assert.assertEquals("post-write position was not correct", ba.position, 18); + + ba.position = 0; + Assert.assertTrue("post-write bytes did not match expected data", bytesMatchExpectedData(ba,[10, 7, 21, 84, 101, 115, 116, 67, 108, 97, 115, 115, 51, 9, 3, 1, 6, 0])); + + test3.content[0] = (test3.content[0]).split("").reverse().join(""); + ba.writeObject(test3); + Assert.assertEquals("post-write length was not correct", ba.length, 28); + Assert.assertEquals("post-write position was not correct", ba.position, 28); + Assert.assertTrue("post-write bytes did not match expected data", bytesMatchExpectedData(ba,[10, 7, 21, 84, 101, 115, 116, 67, 108, 97, 115, 115, 51, 9, 3, 1, 6, 21, 51, 115, 115, 97, 108, 67, 116, 115, 101, 84])); + + ba.position=0; + var test3Read:TestClass3 = ba.readObject() as TestClass3; + + //proof that it created a new instance, and that the reversed content string content is present in the new instance + Assert.assertTrue("post-write read did not match expected data", test3Read.content[0] == test3.content[0]); + + } + + + + } +} diff --git a/manualtests/UnitTests/src/main/royale/TestClasses.as b/manualtests/UnitTests/src/main/royale/flexUnitTests/network/support/DynamicTestClass.as similarity index 65% copy from manualtests/UnitTests/src/main/royale/TestClasses.as copy to manualtests/UnitTests/src/main/royale/flexUnitTests/network/support/DynamicTestClass.as index 336e258..2032bc0 100644 --- a/manualtests/UnitTests/src/main/royale/TestClasses.as +++ b/manualtests/UnitTests/src/main/royale/flexUnitTests/network/support/DynamicTestClass.as @@ -16,22 +16,28 @@ // limitations under the License. // //////////////////////////////////////////////////////////////////////////////// -package +package flexUnitTests.network.support { - //test groups - import flexUnitTests.*; - public class TestClasses + + dynamic public class DynamicTestClass { + //Note: do not change this test class unless you change the related tests to + //support any changes that might appear when testing with it + + + public var sealedInstanceProp1:Boolean; - public static function get testClasses():Array { - return [ - CoreTester, - ReflectionTester, - ObservedBugsTester, - JiraIssuesTester - ]; + + /*private var _sealedInstanceAccessor1:String; + public function get sealedInstanceAccessor1():String{ + return _sealedInstanceAccessor1; } + + public function set sealedInstanceAccessor1(value:String):void{ + _sealedInstanceAccessor1 = value; + }*/ + } } diff --git a/manualtests/UnitTests/src/main/royale/TestClasses.as b/manualtests/UnitTests/src/main/royale/flexUnitTests/network/support/TestClass1.as similarity index 80% copy from manualtests/UnitTests/src/main/royale/TestClasses.as copy to manualtests/UnitTests/src/main/royale/flexUnitTests/network/support/TestClass1.as index 336e258..ce78f96 100644 --- a/manualtests/UnitTests/src/main/royale/TestClasses.as +++ b/manualtests/UnitTests/src/main/royale/flexUnitTests/network/support/TestClass1.as @@ -16,22 +16,21 @@ // limitations under the License. // //////////////////////////////////////////////////////////////////////////////// -package +package flexUnitTests.network.support { - //test groups - import flexUnitTests.*; - - - public class TestClasses + + public class TestClass1 { - - public static function get testClasses():Array { - return [ - CoreTester, - ReflectionTester, - ObservedBugsTester, - JiraIssuesTester - ]; + public function TestClass1() + { } + + public var testField1:String = ''; + //public var testField2:Boolean; + //public var testField3:Number; + /*public var testField4:Function;*/ + + + } } diff --git a/manualtests/UnitTests/src/main/royale/TestClasses.as b/manualtests/UnitTests/src/main/royale/flexUnitTests/network/support/TestClass2.as similarity index 71% copy from manualtests/UnitTests/src/main/royale/TestClasses.as copy to manualtests/UnitTests/src/main/royale/flexUnitTests/network/support/TestClass2.as index 336e258..69154fc 100644 --- a/manualtests/UnitTests/src/main/royale/TestClasses.as +++ b/manualtests/UnitTests/src/main/royale/flexUnitTests/network/support/TestClass2.as @@ -16,22 +16,23 @@ // limitations under the License. // //////////////////////////////////////////////////////////////////////////////// -package +package flexUnitTests.network.support { - //test groups - import flexUnitTests.*; - - public class TestClasses + + public class TestClass2 { + //Note: do not change this test class unless you change the related tests to + //support any changes that might appear when testing reflection into it - public static function get testClasses():Array { - return [ - CoreTester, - ReflectionTester, - ObservedBugsTester, - JiraIssuesTester - ]; + public function TestClass2(){ + } + + + public var testField1:Boolean = true; + //public var testField2:Boolean; + //public var testField3:Number; + /*public var testField4:Function;*/ } } diff --git a/manualtests/UnitTests/src/main/royale/TestClasses.as b/manualtests/UnitTests/src/main/royale/flexUnitTests/network/support/TestClass3.as similarity index 60% copy from manualtests/UnitTests/src/main/royale/TestClasses.as copy to manualtests/UnitTests/src/main/royale/flexUnitTests/network/support/TestClass3.as index 336e258..715ad0c 100644 --- a/manualtests/UnitTests/src/main/royale/TestClasses.as +++ b/manualtests/UnitTests/src/main/royale/flexUnitTests/network/support/TestClass3.as @@ -16,22 +16,35 @@ // limitations under the License. // //////////////////////////////////////////////////////////////////////////////// -package +package flexUnitTests.network.support { - //test groups - import flexUnitTests.*; - - - public class TestClasses + import org.apache.royale.net.utils.IExternalizable; + COMPILE::JS{ + import org.apache.royale.net.utils.IDataInput; + import org.apache.royale.net.utils.IDataOutput; + } + + COMPILE::SWF{ + import flash.utils.IDataInput; + import flash.utils.IDataOutput; + } + + + public class TestClass3 implements IExternalizable { - public static function get testClasses():Array { - return [ - CoreTester, - ReflectionTester, - ObservedBugsTester, - JiraIssuesTester - ]; + public var content:Array=["TestClass3"]; + + + public function readExternal(input:IDataInput):void{ + var content:Array = input.readObject() as Array; + this.content = content; + } + + public function writeExternal(output:IDataOutput):void { + output.writeObject(content); } + } + } diff --git a/manualtests/UnitTests/testsview/image/apache-royale-main-logo.png b/manualtests/UnitTests/testsview/image/apache-royale-main-logo.png new file mode 100644 index 0000000..fd7a201 Binary files /dev/null and b/manualtests/UnitTests/testsview/image/apache-royale-main-logo.png differ diff --git a/manualtests/UnitTests/testsview/index.html b/manualtests/UnitTests/testsview/index.html index 2806402..9370b27 100644 --- a/manualtests/UnitTests/testsview/index.html +++ b/manualtests/UnitTests/testsview/index.html @@ -60,6 +60,7 @@ border: 0; margin: 0; padding: 0; + border-radius:0; } .titleContent { white-space:nowrap; @@ -124,7 +125,7 @@ <body class="pageStyles"> <div> <div > - <img class="logo" src="image/Royale.png" alt="Apache Royale logo"> + <img class="logo" src="image/apache-royale-main-logo.png" alt="Apache Royale logo"> <div class="titleContent"> <h1>Royale Framework Development Unit Tests</h1> </div>
