├── .gitignore ├── .travis.yml ├── CHANGELOG ├── CONTRIBUTORS ├── LICENSE ├── README.mkd ├── build.properties ├── build.xml ├── libs ├── as3commons-lang-0.3.4.swc ├── as3commons-logging-2.0.swc ├── as3commons-reflect-1.4.1.swc └── flexunit │ ├── flexUnitTasks-4.2.0-20140410-javadoc.jar │ ├── flexUnitTasks-4.2.0-20140410-sources.jar │ ├── flexUnitTasks-4.2.0-20140410.jar │ ├── flexunit-4.2.0-20140410-as3_4.12.0.swc │ ├── flexunit-4.2.0-20140410-flex_4.12.0.swc │ ├── flexunit-aircilistener-4.2.0-20140410-4.12.0.swc │ ├── flexunit-cilistener-4.2.0-20140410-4.12.0.swc │ ├── flexunit-uilistener-4.2.0-20140410-4.12.0.swc │ ├── fluint-1_2.swc │ └── fluint-extensions-4.2.0-20140410-4.12.0.swc ├── src └── org │ └── osflash │ └── vanilla │ ├── InjectionDetail.as │ ├── InjectionMap.as │ ├── MarshallingError.as │ ├── VANILLA_INSTANCE.as │ ├── Vanilla.as │ └── extract.as └── test-src ├── TestRunner.as └── org └── osflash └── vanilla ├── TestExtractTypedArray.as ├── TestExtractVector.as ├── TestVanilla.as ├── outside └── TestExtractMethod.as └── testdata ├── Address.as ├── NestedComplexVectorList.as ├── NestedNumberVectorList.as ├── NumberArrayList.as ├── NumberVectorList.as ├── PersonConstructorMetadata.as ├── PersonImplicitFields.as ├── PersonMutlipleArgumentSetterMetadata.as ├── PersonPublicFields.as ├── PersonPublicFieldsMetadata.as ├── PersonSetterMetadata.as ├── PersonTransientFields.as ├── PersonWithAddressConstructor.as ├── PersonWithAddressFields.as ├── PersonWithMultipleAddressesArrayField.as ├── PersonWithMultipleAddressesVectorField.as ├── StringArrayList.as └── StringVectorList.as /.gitignore: -------------------------------------------------------------------------------- 1 | # Build artifacts. 2 | bin 3 | dist 4 | 5 | # IDE crud. 6 | .project 7 | .idea 8 | .DS_Store 9 | .settings 10 | 11 | # User should generate their own copy. 12 | user.properties 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # required to get an OSX environment 2 | language: objective-c 3 | 4 | before_script: 5 | # set the JAVA_HOME which is not set by default for OSX Travis-CI workers 6 | - export "JAVA_HOME=`/usr/libexec/java_home`" 7 | 8 | # Get hold of ANT. 9 | - brew update 10 | - brew install ant 11 | 12 | # Grab Flex SDK 13 | - curl -L http://download.macromedia.com/pub/flex/sdk/flex_sdk_4.6.zip > flex_sdk.zip 14 | - unzip flex_sdk.zip -d ./flex_sdk 15 | - export "FLEX_HOME=`pwd`/flex_sdk" 16 | 17 | # Download and install a specific Flash Player from the Adobe Archives: 18 | # http://helpx.adobe.com/flash-player/kb/archived-flash-player-versions.html 19 | - curl -L https://raw.githubusercontent.com/Larusso/travis-CI-actionscript-demo/master/getFpFromArchive.sh > getFpFromArchive.sh 20 | - sh getFpFromArchive.sh 'http://download.macromedia.com/pub/flashplayer/installers/archive/fp_11.7.700.225_archive.zip' 21 | - export FLASH_CMD="`pwd`/Flash Player Debugger.app/Contents/MacOS/Flash Player Debugger" 22 | 23 | script: ant test -DFLASH_PLAYER_CMD="${FLASH_CMD}" -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | == 0.2.0 / 2014-12-18 2 | 3 | * Cache InjectionMap for performance improvement 4 | 5 | == 0.1.3 / 2013-05-22 6 | 7 | * Added support for parsing nested Vectors 8 | 9 | 10 | == 0.1.2 / 2013-02-24 11 | 12 | * Added support for ignoring fields marked with [Transient] 13 | 14 | == 0.1.1 / 2011-08-14 15 | 16 | * Initial release 17 | -------------------------------------------------------------------------------- /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | AS3 Vanilla Contributors 2 | 3 | * [Jonny Reeves](http://www.jonnyreeves.co.uk) 4 | * [MattesGroeger](https://github.com/MattesGroeger) 5 | * [Eamonn Faherty](http://eamonnfaherty.co.uk) - Transient support 6 | * [Ilya Malanin](https://github.com/mayakwd) - Nested Vector support 7 | * [nguyenbs](https://github.com/nguyenbs) - Reflection map caching -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2011 the original author or authors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.mkd: -------------------------------------------------------------------------------- 1 | AS3 Vanilla 2 | =========== 3 | 4 | [![Build Status](https://travis-ci.org/jonnyreeves/as3-vanilla.svg?branch=master)](https://travis-ci.org/jonnyreeves/as3-vanilla) 5 | 6 | A lightweight library which enables a developer to extract a strongly typed Model Object from a untyped dynamic object 7 | without having to write a single line of parsing or marshalling code. An example use case would be turning data 8 | returned from a JSON endpoint into a Model, if you've ever written the following code, then you can benefit from this 9 | library: 10 | 11 | ```actionscript 12 | // Use a JSON library to convert a JSON String into an AS3 Object. 13 | var jsonObject : Object = JSON.decode('{"name":"Jonny", "age": 28, "music":["nin","mew"]}'); 14 | 15 | // Copy all the data into a new PersonVO so the rest of the system can use it. 16 | var myPerson : PersonVO = new PersonVO(); 17 | myPerson.name = jsonObject["name"]; 18 | myPerson.age = jsonObject["age"]; 19 | myPerson.music = jsonObject["music"]; 20 | 21 | trace(myPerson.name) // Jonny. 22 | ``` 23 | 24 | Using the Vanilla library, you can turn the above code into this: 25 | 26 | ```actionscript 27 | // Use a JSON library to convert a JSON String into an AS3 Object. 28 | var jsonObject : Object = JSON.decode('{"name":"Jonny", "age": 28, "music":["nin","mew"]}'); 29 | 30 | // Use Vanilla to convert it into a PersonVO. 31 | var myPerson : PersonVO = new Vanilla().extract(jsonObject, PersonVO); 32 | 33 | trace(myPerson.name); // "Jonny" 34 | ``` 35 | 36 | Things get even easier when you make use of the `extract` convenience method: 37 | 38 | ```actionscript 39 | var jsonObject : Object = JSON.decode('{"name":"Jonny", "age": 28, "music":["nin","mew"]}'); 40 | var myPerson : PersonVO = extract(jsonObject, PersonVO); 41 | ``` 42 | 43 | Got a complex object graph? Well that's where Vanilla really shines, making light work of parsing and 44 | marshalling nested objects, for example: 45 | 46 | ```actionscript 47 | // The PersonVO Model contains an 'address' field which is a complex datatype (AddressVO). 48 | package app.model { 49 | class PersonVO { 50 | public var name : String; 51 | public var address : AddressVO; 52 | } 53 | } 54 | 55 | // Here's the class definition for AddressVO. 56 | package app.model { 57 | class AddressVO { 58 | public var line1 : String; 59 | public var line2 : String; 60 | public var city : String; 61 | } 62 | } 63 | 64 | var jsonObject : Object = JSON.decode('{"name":"Jonny","address":{"line1":"My House","line2":"My Road","city":"London"}}'); 65 | var myPerson : PersonVO = extract(jsonObject, PersonVO); 66 | 67 | trace(myPerson.address.city); // "London" 68 | ``` 69 | 70 | Although Vanilla library does make use of Metadata, it is by no means required - the goal of this library 71 | is to make the marshalling as transparent and painless as possible; if the source Object's fields maps perfectly to 72 | the fields of your Model object (as in the example above) then everything should 'just work'(tm). 73 | 74 | 75 | Mapping Fields 76 | -------------- 77 | Sometimes the fields on your Model object don't quite match up to the fields in your source object; not a problem, 78 | you can use Metadata to define the mappings in your Model object: 79 | 80 | ```actionscript 81 | package app.model { 82 | public class PersonVO { 83 | public var name : String; 84 | public var age : uint; 85 | [Marshall(field="music")] public var musicTastes : Array; 86 | } 87 | } 88 | ``` 89 | 90 | 91 | Mapping to Constructor Arguments 92 | -------------------------------- 93 | If you're a fan of immutable models then you will want to define some Metadata to map the fields in your source object 94 | to the constructor arguments of your Model object: 95 | 96 | ```actionscript 97 | package app.model { 98 | // Don't forget, constructor metadata is annotated to the class, not the constructor method! 99 | [Marshall(field="name", field="age", field="music")] 100 | public class PersonModel { 101 | private var _name : String; 102 | private var _age : uint; 103 | private var _musicTastes : Array; 104 | 105 | public function PersonModel(name, age, music : Array) { 106 | _name = name; 107 | _age = age; 108 | _music = music; 109 | } 110 | } 111 | } 112 | ``` 113 | 114 | 115 | Mapping to Methods / Mutators 116 | ----------------------------- 117 | You can map the fields of your source object to a setter method in your Model object: 118 | 119 | ```actionscript 120 | package app.model { 121 | public class PersonModel { 122 | private var _name : String; 123 | private var _age : uint; 124 | private var _music : Array; 125 | 126 | [Marshall(field="name", field="age")] 127 | public function init(name : String, age : uint) : void { 128 | _name = name; 129 | _age = age; 130 | } 131 | 132 | [Marshall(field="music")] 133 | public function setMusic(value : Array) : void { 134 | _music = value; 135 | } 136 | } 137 | ``` 138 | 139 | 140 | Ignoring fields 141 | ----------------------------- 142 | You can ignore fields by using [Transient] metadata: 143 | 144 | ```actionscript 145 | package app.model { 146 | public class PersonModel { 147 | [Transient] 148 | public var name : String; 149 | public var age : uint; 150 | } 151 | } 152 | ``` 153 | The name field is ignored from the mapping but the age is not. 154 | 155 | 156 | Automatic Coercion 157 | ------------------ 158 | Vanilla will automatically coerce Arrays found in your source object into Vectors defined in your target Model 159 | Class, take the following example: 160 | 161 | ```actionscript 162 | // The Target Model Class Definition. 163 | class app.model { 164 | public class ColourList { 165 | public var colours : Vector.; 166 | } 167 | } 168 | 169 | var source : Object = { colours: [ "red", "white", "blue" ] }; 170 | var result : ColourList = extract(source, ColourList); 171 | 172 | trace(result.colours); // "red","white","blue" 173 | trace(getQualifiedClassName(result.colours)); // __AS3__.vec::Vector. 174 | ``` 175 | 176 | If you aren't using Vectors in your project, then you will have to give Vanilla a hand and provide it with a type hint 177 | as to what the Array is expected to contain, for example: 178 | 179 | ```actionscript 180 | // The Target Model Class Definition. 181 | class app.model { 182 | public class Contact { 183 | public var name : String; 184 | 185 | [Marshall (type="app.model.PhoneNumber")] 186 | public var phoneNumbers : Array; 187 | } 188 | } 189 | 190 | // A DTO which the Contact class makes use of. 191 | class app.model { 192 | public class PhoneNumber { 193 | public var number : Number; 194 | public var type : String; 195 | } 196 | } 197 | 198 | // Let's extract this JSON representation into a instance of the Contact model. 199 | var source : Object = { 200 | name: "Jonny", 201 | phoneNumbers: [ 202 | { number: 114752471, type: "home" }, 203 | { number: 12344122, type: "mobile" } 204 | ] 205 | }; 206 | 207 | var result : Contact = extract(source, Contact); 208 | trace(result.phoneNumbers); // [object PhoneNumber],[object PhoneNumber] 209 | trace((result.phoneNumbers[0] as PhoneNumber).type) // "home". 210 | ``` 211 | 212 | 213 | 214 | Dependencies 215 | ------------ 216 | `as3commons-reflect` is used for all reflection; in order to use as3-vanilla you will need to download and add 217 | the following SWCs to your project's libs folder. 218 | 219 | * [ascommons-reflect-1.4.1](http://projects.yoolab.org/maven/content/repositories/releases/org/as3commons/as3commons-reflect/1.4.1/as3commons-reflect-1.4.1.swc) 220 | * [as3commons-lang-0.3.4](http://projects.yoolab.org/maven/content/repositories/releases/org/as3commons/as3commons-lang/0.3.4/as3commons-lang-0.3.4.swc) 221 | * [as3commons-logging-2.0](http://projects.yoolab.org/maven/content/repositories/releases/org/as3commons/as3commons-logging/2.0/as3commons-logging-2.0.swc) 222 | 223 | 224 | Future Features / Wish List 225 | --------------------------- 226 | * The user should be able to define mapping rules without having to use Metadata. 227 | * Java style mutators (eg: `setFoo()`) could be mapped automatically. 228 | * By using `as3commons-bytecode` we could perform ByteCode reflection to retrieve the names of parameters; this would 229 | would remove the need for constructor marshalling metadata. 230 | * Possibly move away from as3commons-reflect (It has too many dependencies). 231 | -------------------------------------------------------------------------------- /build.properties: -------------------------------------------------------------------------------- 1 | # If you don't want to use an environment variable for your FLEX_HOME, you can define FLEX_HOME in a `user.properties` 2 | # file instead. 3 | FLEX_HOME = ${env.FLEX_HOME} 4 | 5 | # Versioning 6 | project.name = as3-vanilla 7 | project.version = 0.2.0 8 | project.versionedname = ${project.name}-${project.version} 9 | 10 | # Folder layout 11 | test-src.dir = test-src 12 | src.dir = src 13 | bin.dir = bin 14 | report.dir = ${bin.dir}/report 15 | lib.dir = libs 16 | dist.dir = dist 17 | -------------------------------------------------------------------------------- /build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | true 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 65 | 66 | 67 | 68 | 69 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /libs/as3commons-lang-0.3.4.swc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonnyreeves/as3-vanilla/4795b1b18cf70ef2bdcbdb558bdb5a211f75b11f/libs/as3commons-lang-0.3.4.swc -------------------------------------------------------------------------------- /libs/as3commons-logging-2.0.swc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonnyreeves/as3-vanilla/4795b1b18cf70ef2bdcbdb558bdb5a211f75b11f/libs/as3commons-logging-2.0.swc -------------------------------------------------------------------------------- /libs/as3commons-reflect-1.4.1.swc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonnyreeves/as3-vanilla/4795b1b18cf70ef2bdcbdb558bdb5a211f75b11f/libs/as3commons-reflect-1.4.1.swc -------------------------------------------------------------------------------- /libs/flexunit/flexUnitTasks-4.2.0-20140410-javadoc.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonnyreeves/as3-vanilla/4795b1b18cf70ef2bdcbdb558bdb5a211f75b11f/libs/flexunit/flexUnitTasks-4.2.0-20140410-javadoc.jar -------------------------------------------------------------------------------- /libs/flexunit/flexUnitTasks-4.2.0-20140410-sources.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonnyreeves/as3-vanilla/4795b1b18cf70ef2bdcbdb558bdb5a211f75b11f/libs/flexunit/flexUnitTasks-4.2.0-20140410-sources.jar -------------------------------------------------------------------------------- /libs/flexunit/flexUnitTasks-4.2.0-20140410.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonnyreeves/as3-vanilla/4795b1b18cf70ef2bdcbdb558bdb5a211f75b11f/libs/flexunit/flexUnitTasks-4.2.0-20140410.jar -------------------------------------------------------------------------------- /libs/flexunit/flexunit-4.2.0-20140410-as3_4.12.0.swc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonnyreeves/as3-vanilla/4795b1b18cf70ef2bdcbdb558bdb5a211f75b11f/libs/flexunit/flexunit-4.2.0-20140410-as3_4.12.0.swc -------------------------------------------------------------------------------- /libs/flexunit/flexunit-4.2.0-20140410-flex_4.12.0.swc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonnyreeves/as3-vanilla/4795b1b18cf70ef2bdcbdb558bdb5a211f75b11f/libs/flexunit/flexunit-4.2.0-20140410-flex_4.12.0.swc -------------------------------------------------------------------------------- /libs/flexunit/flexunit-aircilistener-4.2.0-20140410-4.12.0.swc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonnyreeves/as3-vanilla/4795b1b18cf70ef2bdcbdb558bdb5a211f75b11f/libs/flexunit/flexunit-aircilistener-4.2.0-20140410-4.12.0.swc -------------------------------------------------------------------------------- /libs/flexunit/flexunit-cilistener-4.2.0-20140410-4.12.0.swc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonnyreeves/as3-vanilla/4795b1b18cf70ef2bdcbdb558bdb5a211f75b11f/libs/flexunit/flexunit-cilistener-4.2.0-20140410-4.12.0.swc -------------------------------------------------------------------------------- /libs/flexunit/flexunit-uilistener-4.2.0-20140410-4.12.0.swc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonnyreeves/as3-vanilla/4795b1b18cf70ef2bdcbdb558bdb5a211f75b11f/libs/flexunit/flexunit-uilistener-4.2.0-20140410-4.12.0.swc -------------------------------------------------------------------------------- /libs/flexunit/fluint-1_2.swc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonnyreeves/as3-vanilla/4795b1b18cf70ef2bdcbdb558bdb5a211f75b11f/libs/flexunit/fluint-1_2.swc -------------------------------------------------------------------------------- /libs/flexunit/fluint-extensions-4.2.0-20140410-4.12.0.swc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonnyreeves/as3-vanilla/4795b1b18cf70ef2bdcbdb558bdb5a211f75b11f/libs/flexunit/fluint-extensions-4.2.0-20140410-4.12.0.swc -------------------------------------------------------------------------------- /src/org/osflash/vanilla/InjectionDetail.as: -------------------------------------------------------------------------------- 1 | package org.osflash.vanilla 2 | { 3 | internal class InjectionDetail 4 | { 5 | private var _fieldName : String; 6 | private var _type : Class; 7 | private var _required : Boolean; 8 | private var _arrayTypeHint : Class; 9 | 10 | public function InjectionDetail(fieldName : String, type : Class, required : Boolean, arrayTypeHint : Class) { 11 | _fieldName = fieldName; 12 | _type = type; 13 | _required = required; 14 | _arrayTypeHint = arrayTypeHint; 15 | } 16 | 17 | public function get name() : String { 18 | return _fieldName; 19 | } 20 | 21 | public function get type() : Class 22 | { 23 | return _type; 24 | } 25 | 26 | public function get isRequired() : Boolean 27 | { 28 | return _required; 29 | } 30 | 31 | public function toString() : String 32 | { 33 | var result : String = _fieldName + "<" + type + ">"; 34 | if (arrayTypeHint) { 35 | result += "->" + arrayTypeHint; 36 | } 37 | return result; 38 | } 39 | 40 | public function get arrayTypeHint() : Class 41 | { 42 | return _arrayTypeHint; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/org/osflash/vanilla/InjectionMap.as: -------------------------------------------------------------------------------- 1 | package org.osflash.vanilla 2 | { 3 | internal class InjectionMap 4 | { 5 | private var _constructorFields : Array = []; 6 | private var _fields : Object = {}; 7 | private var _methods : Object = {}; 8 | 9 | public function addConstructorField(injectionDetails : InjectionDetail) : void 10 | { 11 | _constructorFields.push(injectionDetails); 12 | } 13 | 14 | public function getConstructorFields() : Array 15 | { 16 | return _constructorFields; 17 | } 18 | 19 | public function addField(fieldName : String, injectionDetails : InjectionDetail) : void 20 | { 21 | _fields[fieldName] = injectionDetails; 22 | } 23 | 24 | public function getFieldNames() : Array 25 | { 26 | const result : Array = []; 27 | for (var fieldName : String in _fields) { 28 | result.push(fieldName); 29 | } 30 | return result; 31 | } 32 | 33 | public function getField(fieldName : String) : InjectionDetail { 34 | return _fields[fieldName]; 35 | } 36 | 37 | public function addMethod(methodName : String, injectionDetails : InjectionDetail) : void 38 | { 39 | _methods[methodName] ||= []; 40 | (_methods[methodName] as Array).push(injectionDetails); 41 | } 42 | 43 | public function getMethodsNames() : Array 44 | { 45 | const result : Array = []; 46 | for (var methodName : String in _methods) { 47 | result.push(methodName); 48 | } 49 | return result; 50 | } 51 | 52 | public function getMethod(methodName : String) : Array 53 | { 54 | return _methods[methodName]; 55 | } 56 | 57 | public function toString() : String 58 | { 59 | var result : String = "[FieldMap "; 60 | 61 | result += "ctor:{" + _constructorFields +"}, "; 62 | result += "fields:{" + _fields + "}, "; 63 | 64 | result += "methods:{"; 65 | for (var methodName : String in _methods) { 66 | result += methodName + "(" + getMethod(methodName) + "),"; 67 | } 68 | result += "}"; 69 | 70 | result += "]"; 71 | return result; 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/org/osflash/vanilla/MarshallingError.as: -------------------------------------------------------------------------------- 1 | package org.osflash.vanilla 2 | { 3 | public class MarshallingError extends Error 4 | { 5 | public static const TYPE_MISMATCH : uint = 150000; 6 | public static const MISSING_REQUIRED_FIELD : uint = 150001; 7 | 8 | public function MarshallingError(message : String, id : uint) { 9 | super(message, id); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/org/osflash/vanilla/VANILLA_INSTANCE.as: -------------------------------------------------------------------------------- 1 | package org.osflash.vanilla 2 | { 3 | /** 4 | * Global, singleton instance used by the extract() convenience method. 5 | */ 6 | internal var VANILLA_INSTANCE : Vanilla = new Vanilla(); 7 | } 8 | -------------------------------------------------------------------------------- /src/org/osflash/vanilla/Vanilla.as: -------------------------------------------------------------------------------- 1 | package org.osflash.vanilla 2 | { 3 | import flash.utils.Dictionary; 4 | import org.as3commons.lang.ClassUtils; 5 | import org.as3commons.lang.ObjectUtils; 6 | import org.as3commons.reflect.Accessor; 7 | import org.as3commons.reflect.Field; 8 | import org.as3commons.reflect.Metadata; 9 | import org.as3commons.reflect.MetadataArgument; 10 | import org.as3commons.reflect.Method; 11 | import org.as3commons.reflect.Parameter; 12 | import org.as3commons.reflect.Type; 13 | import org.as3commons.reflect.Variable; 14 | 15 | import flash.utils.getQualifiedClassName; 16 | 17 | public class Vanilla 18 | { 19 | private static const METADATA_TAG : String = "Marshall"; 20 | private static const METADATA_FIELD_KEY : String = "field"; 21 | private static const METADATA_TYPE_KEY : String = "type"; 22 | 23 | //Cache InjectionMap instances 24 | private var injectionMapCache : Dictionary = new Dictionary(); 25 | 26 | /** 27 | * Attempts to extract properties from the supplied source object into an instance of the supplied targetType. 28 | * 29 | * @param source Object which contains properties that you wish to transfer to a new instance of the 30 | * supplied targetType Class. 31 | * @param targetType The target Class of which an instance will be returned. 32 | * @return An instance of the supplied targetType containing all the properties extracted from 33 | * the supplied source object. 34 | */ 35 | public function extract(source : Object, targetType : Class) : * 36 | { 37 | // Catch the case where we've been asked to extract a value which is already of the intended targetType; 38 | // this can often happen when Vanilla is recursing, in which case there is nothing to do. 39 | if (source is targetType) { 40 | return source; 41 | } 42 | 43 | if (!injectionMapCache[targetType]) 44 | { 45 | injectionMapCache[targetType] = new InjectionMap(); 46 | addReflectedRules(injectionMapCache[targetType], targetType, Type.forClass(targetType)); 47 | } 48 | 49 | const injectionMap : InjectionMap = injectionMapCache[targetType]; 50 | 51 | // Create a new instance of the targetType; and then inject the values from the source object into it 52 | const target : * = instantiate(targetType, fetchConstructorArgs(source, injectionMap.getConstructorFields())); 53 | injectFields(source, target, injectionMap); 54 | injectMethods(source, target, injectionMap); 55 | 56 | return target; 57 | } 58 | 59 | private function fetchConstructorArgs(source : Object, constructorFields : Array) : Array 60 | { 61 | const result : Array = []; 62 | for (var i : uint = 0; i < constructorFields.length; i++) { 63 | result.push(extractValue(source, constructorFields[i])); 64 | } 65 | return result; 66 | } 67 | 68 | private function injectFields(source : Object, target : *, injectionMap : InjectionMap) : void 69 | { 70 | const fieldNames : Array = injectionMap.getFieldNames(); 71 | for each (var fieldName : String in fieldNames) { 72 | target[fieldName] = extractValue(source, injectionMap.getField(fieldName)); 73 | } 74 | } 75 | 76 | private function injectMethods(source : Object, target : *, injectionMap : InjectionMap) : void 77 | { 78 | const methodNames : Array = injectionMap.getMethodsNames(); 79 | for each (var methodName : String in methodNames) 80 | { 81 | const values : Array = []; 82 | for each (var injectionDetail : InjectionDetail in injectionMap.getMethod(methodName)) { 83 | values.push(extractValue(source, injectionDetail)); 84 | } 85 | (target[methodName] as Function).apply(null, values); 86 | } 87 | } 88 | 89 | private function extractValue(source : Object, injectionDetail : InjectionDetail) : * 90 | { 91 | var value : * = source[injectionDetail.name]; 92 | 93 | // Is this a required injection? 94 | if (injectionDetail.isRequired && value === undefined) { 95 | throw new MarshallingError("Required value " + injectionDetail + " does not exist in the source object.", MarshallingError.MISSING_REQUIRED_FIELD); 96 | } 97 | 98 | if (value) 99 | { 100 | // automatically coerce simple types. 101 | if (!ObjectUtils.isSimple(value)) { 102 | value = extract(value, injectionDetail.type); 103 | } 104 | 105 | // Collections are harder, we need to coerce the contents. 106 | else if (value is Array) { 107 | if(isVector(injectionDetail.type)) { 108 | value = extractVector(value, injectionDetail.type, injectionDetail.arrayTypeHint); 109 | } 110 | else if (injectionDetail.arrayTypeHint) { 111 | value = extractTypedArray(value, injectionDetail.arrayTypeHint); 112 | } 113 | } 114 | 115 | // refuse to allow any automatic coercing to occur. 116 | if (!(value is injectionDetail.type)) { 117 | throw new MarshallingError("Could not coerce `" + injectionDetail.name + "` (value: " + value + " <" + Type.forInstance(value).clazz + "]>) from source object to " + injectionDetail.type + " on target object", MarshallingError.TYPE_MISMATCH); 118 | } 119 | } 120 | 121 | return value; 122 | } 123 | 124 | private function extractTypedArray(source : Array, targetClassType : Class) : Array 125 | { 126 | const result : Array = new Array(source.length); 127 | for (var i : uint = 0; i < source.length; i++) { 128 | result[i] = extract(source[i], targetClassType); 129 | } 130 | return result; 131 | } 132 | 133 | private function extractVector(source : Array, targetVectorClass : Class, targetClassType : Class) : * 134 | { 135 | const result : * = ClassUtils.newInstance(targetVectorClass); 136 | for (var i : uint = 0; i < source.length; i++) { 137 | if (isVector(targetClassType)) { 138 | const type : Type = Type.forClass(targetClassType); 139 | result[i] = extractVector(source[i], targetClassType, type.parameters[0]); 140 | } else { 141 | result[i] = extract(source[i], targetClassType); 142 | } 143 | } 144 | return result; 145 | } 146 | 147 | 148 | private function instantiate(targetType : Class, ctorArgs : Array) : * 149 | { 150 | return ClassUtils.newInstance(targetType, ctorArgs); 151 | } 152 | 153 | private function addReflectedRules(injectionMap : InjectionMap, targetType : Class, reflectionMap : Type) : void 154 | { 155 | addReflectedConstructorRules(injectionMap, reflectionMap); 156 | addReflectedFieldRules(injectionMap, reflectionMap.fields); 157 | addReflectedSetterRules(injectionMap, reflectionMap.methods); 158 | } 159 | 160 | private function addReflectedConstructorRules(injectionMap : InjectionMap, reflectionMap : Type) : void 161 | { 162 | const clazzMarshallingMetadata : Array = reflectionMap.getMetadata(METADATA_TAG); 163 | if (!clazzMarshallingMetadata) { 164 | return; 165 | } 166 | 167 | const marshallingMetadata : Metadata = clazzMarshallingMetadata[0]; 168 | const numArgs : uint = marshallingMetadata.arguments.length; 169 | 170 | for (var i : uint = 0; i < numArgs; i++) { 171 | var argument : MetadataArgument = marshallingMetadata.arguments[i]; 172 | if (argument.key == METADATA_FIELD_KEY) { 173 | const param : Parameter = reflectionMap.constructor.parameters[i]; 174 | const arrayTypeHint : Class = extractArrayTypeHint(param.type); 175 | injectionMap.addConstructorField(new InjectionDetail(argument.value, param.type.clazz, true, arrayTypeHint)); 176 | } 177 | } 178 | } 179 | 180 | private function addReflectedFieldRules(injectionMap : InjectionMap, fields : Array) : void 181 | { 182 | for each (var field : Field in fields) { 183 | if (!field.hasMetadata(Metadata.TRANSIENT) && canAccess(field)) { 184 | const fieldMetadataEntries : Array = field.getMetadata(METADATA_TAG); 185 | const fieldMetadata : Metadata = (fieldMetadataEntries) ? fieldMetadataEntries[0] : null; 186 | const arrayTypeHint : Class = extractArrayTypeHint(field.type, fieldMetadata); 187 | const sourceFieldName : String = extractFieldName(field, fieldMetadata); 188 | 189 | injectionMap.addField(field.name, new InjectionDetail(sourceFieldName, field.type.clazz, false, arrayTypeHint)); 190 | } 191 | } 192 | } 193 | 194 | private function addReflectedSetterRules(injectionMap : InjectionMap, methods : Array) : void 195 | { 196 | for each (var method : Method in methods) { 197 | 198 | const methodMarshallingMetadata : Array = method.getMetadata(METADATA_TAG); 199 | if (methodMarshallingMetadata == null) { 200 | continue; 201 | } 202 | 203 | const metadata : Metadata = methodMarshallingMetadata[0]; 204 | const numArgs : uint = metadata.arguments.length; 205 | 206 | for (var i : uint = 0; i < numArgs; i++) { 207 | var argument : MetadataArgument = metadata.arguments[i]; 208 | if (argument.key == METADATA_FIELD_KEY) { 209 | const param : Parameter = method.parameters[i]; 210 | const arrayTypeHint : Class = extractArrayTypeHint(param.type, metadata); 211 | injectionMap.addMethod(method.name, new InjectionDetail(argument.value, param.type.clazz, false, arrayTypeHint)); 212 | } 213 | } 214 | } 215 | } 216 | 217 | private function extractFieldName(field : Field, metadata : Metadata) : String 218 | { 219 | // See if a taget fieldName has been defined in the Metadata. 220 | if (metadata) { 221 | const numArgs : uint = metadata.arguments.length; 222 | for (var i : uint = 0; i < numArgs; i++) { 223 | var argument : MetadataArgument = metadata.arguments[i]; 224 | if (argument.key == METADATA_FIELD_KEY) { 225 | return argument.value; 226 | } 227 | } 228 | } 229 | 230 | // Assume it's a 1 to 1 mapping. 231 | return field.name; 232 | } 233 | 234 | private function extractArrayTypeHint(type : Type, metadata : Metadata = null) : Class 235 | { 236 | // Vectors carry their own type hint. 237 | if (type.parameters && type.parameters[0] is Class) { 238 | return type.parameters[0]; 239 | } 240 | 241 | // Otherwise we will look for some "type" metadata, if it was defined. 242 | else if (metadata) { 243 | const numArgs : uint = metadata.arguments.length; 244 | for (var i : uint = 0; i < numArgs; i++) { 245 | var argument : MetadataArgument = metadata.arguments[i]; 246 | if (argument.key == METADATA_TYPE_KEY) { 247 | const clazz : Class = ClassUtils.forName(argument.value); 248 | return clazz; 249 | } 250 | } 251 | } 252 | 253 | // No type hint. 254 | return null; 255 | } 256 | 257 | private function canAccess(field : Field) : Boolean 258 | { 259 | if (field is Variable) { 260 | return true; 261 | } 262 | else if (field is Accessor) { 263 | return (field as Accessor).writeable; 264 | } 265 | return false; 266 | } 267 | 268 | private function isVector(obj : *) : Boolean 269 | { 270 | return (getQualifiedClassName(obj).indexOf('__AS3__.vec::Vector') == 0); 271 | } 272 | } 273 | } 274 | -------------------------------------------------------------------------------- /src/org/osflash/vanilla/extract.as: -------------------------------------------------------------------------------- 1 | package org.osflash.vanilla 2 | { 3 | /** 4 | * Attempts to extract properties from the supplied source object into an instance of the supplied targetType. 5 | * 6 | * @param source Object which contains properties that you wish to transfer to a new instance of the 7 | * supplied targetType Class. 8 | * @param targetType The target Class of which an instance will be returned. 9 | * @return An instance of the supplied targetType containing all the properties extracted from 10 | * the supplied source object. 11 | */ 12 | public function extract(source : Object, targetType : Class) : * 13 | { 14 | return VANILLA_INSTANCE.extract(source, targetType); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test-src/TestRunner.as: -------------------------------------------------------------------------------- 1 | package 2 | { 3 | import org.osflash.vanilla.TestExtractTypedArray; 4 | import org.flexunit.internals.TraceListener; 5 | import org.flexunit.listeners.CIListener; 6 | import org.flexunit.runner.FlexUnitCore; 7 | import org.osflash.vanilla.TestExtractVector; 8 | import org.osflash.vanilla.TestVanilla; 9 | import org.osflash.vanilla.outside.TestExtractMethod; 10 | 11 | import flash.display.Sprite; 12 | import flash.display.Stage; 13 | import flash.events.Event; 14 | 15 | public class TestRunner extends Sprite 16 | { 17 | public static var STAGE : Stage; 18 | 19 | private var core : FlexUnitCore; 20 | 21 | public function TestRunner() 22 | { 23 | addEventListener(Event.ADDED_TO_STAGE, onAddedToStage); 24 | } 25 | 26 | private function onAddedToStage(event : Event) : void 27 | { 28 | STAGE = stage; 29 | 30 | core = new FlexUnitCore(); 31 | core.addListener(new TraceListener()); 32 | core.addListener(new CIListener()); 33 | 34 | core.run([ 35 | TestVanilla, 36 | TestExtractMethod, 37 | TestExtractVector, 38 | TestExtractTypedArray 39 | ]); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /test-src/org/osflash/vanilla/TestExtractTypedArray.as: -------------------------------------------------------------------------------- 1 | package org.osflash.vanilla 2 | { 3 | import org.flexunit.asserts.assertEquals; 4 | import org.flexunit.asserts.assertNotNull; 5 | import org.flexunit.asserts.assertTrue; 6 | import org.osflash.vanilla.testdata.Address; 7 | import org.osflash.vanilla.testdata.NumberArrayList; 8 | import org.osflash.vanilla.testdata.PersonWithMultipleAddressesArrayField; 9 | import org.osflash.vanilla.testdata.StringArrayList; 10 | 11 | /** 12 | * @author Jonny 13 | */ 14 | public class TestExtractTypedArray 15 | { 16 | [Test] 17 | public function numbers() : void 18 | { 19 | const source : Object = {numbers:[21, 44, 1847]}; 20 | const result : NumberArrayList = extract(source, NumberArrayList); 21 | 22 | assertNotNull(result.numbers); 23 | assertEquals(3, result.numbers.length); 24 | 25 | assertEquals(21, result.numbers[0]); 26 | assertEquals(44, result.numbers[1]); 27 | assertEquals(1847, result.numbers[2]); 28 | } 29 | 30 | [Test] 31 | public function strings() : void 32 | { 33 | const source : Object = {strings:["hello", "world"]}; 34 | const result : StringArrayList = extract(source, StringArrayList); 35 | 36 | assertNotNull(result.getStrings()); 37 | assertEquals(2, result.getStrings().length); 38 | 39 | assertEquals("hello", result.getStrings()[0]); 40 | assertEquals("world", result.getStrings()[1]); 41 | } 42 | 43 | [Test] 44 | public function complex_viaField() : void 45 | { 46 | const source : Object = { 47 | name: "Jonny", 48 | addresses: [ 49 | { 50 | address1: "Address 1", 51 | city: "City 1" 52 | }, 53 | { 54 | address1: "Address 2", 55 | city: "City 2" 56 | } 57 | ] 58 | }; 59 | 60 | const result : PersonWithMultipleAddressesArrayField = extract(source, PersonWithMultipleAddressesArrayField); 61 | assertNotNull(result.addresses); 62 | assertEquals(2, result.addresses.length); 63 | assertTrue(result.addresses[0] is Address); 64 | assertEquals(source["addresses"][0]["address1"], (result.addresses[0] as Address).address1); 65 | assertEquals(source["addresses"][0]["city"], (result.addresses[0] as Address).city); 66 | assertEquals(source["addresses"][1]["address1"], (result.addresses[1] as Address).address1); 67 | assertEquals(source["addresses"][1]["city"], (result.addresses[1] as Address).city); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /test-src/org/osflash/vanilla/TestExtractVector.as: -------------------------------------------------------------------------------- 1 | package org.osflash.vanilla 2 | { 3 | import org.flexunit.asserts.assertEquals; 4 | import org.flexunit.asserts.assertNotNull; 5 | import org.flexunit.asserts.assertTrue; 6 | import org.osflash.vanilla.testdata.Address; 7 | import org.osflash.vanilla.testdata.NumberVectorList; 8 | import org.osflash.vanilla.testdata.NestedNumberVectorList; 9 | import org.osflash.vanilla.testdata.NestedComplexVectorList; 10 | import org.osflash.vanilla.testdata.PersonWithMultipleAddressesVectorField; 11 | import org.osflash.vanilla.testdata.StringVectorList; 12 | 13 | public class TestExtractVector 14 | { 15 | [Test] 16 | public function strings() : void 17 | { 18 | const source : Object = { strings: ["red", "white", "blue"] }; 19 | const result : StringVectorList = extract(source, StringVectorList); 20 | 21 | assertNotNull(result.strings); 22 | assertEquals(3, result.strings.length); 23 | assertEquals("red", result.strings[0]); 24 | assertEquals("white", result.strings[1]); 25 | assertEquals("blue", result.strings[2]); 26 | } 27 | 28 | [Test] 29 | public function numbers() : void 30 | { 31 | const source : Object = { numbers: [22, 18, 10294] }; 32 | const result : NumberVectorList = extract(source, NumberVectorList); 33 | 34 | assertNotNull(result.numbers); 35 | assertEquals(3, result.numbers.length); 36 | assertEquals(22, result.numbers[0]); 37 | assertEquals(18, result.numbers[1]); 38 | assertEquals(10294, result.numbers[2]); 39 | } 40 | 41 | [Test] 42 | public function complex_viaField() : void 43 | { 44 | const source : Object = { 45 | name: "Jonny", 46 | addresses: [ 47 | { 48 | address1: "Address 1", 49 | city: "City 1" 50 | }, 51 | { 52 | address1: "Address 2", 53 | city: "City 2" 54 | } 55 | ] 56 | }; 57 | 58 | const result : PersonWithMultipleAddressesVectorField = extract(source, PersonWithMultipleAddressesVectorField); 59 | assertNotNull(result.addresses); 60 | assertEquals(2, result.addresses.length); 61 | assertTrue(result.addresses[0] is Address); 62 | assertEquals(source["addresses"][0]["address1"], result.addresses[0].address1); 63 | assertEquals(source["addresses"][0]["city"], result.addresses[0].city); 64 | assertEquals(source["addresses"][1]["address1"], result.addresses[1].address1); 65 | assertEquals(source["addresses"][1]["city"], result.addresses[1].city); 66 | } 67 | 68 | [Test] 69 | public function nested_numbers() : void 70 | { 71 | const source : Object = { 72 | numbers: [ 73 | [1, 2], 74 | [4, 5], 75 | [7, 8, 9] 76 | ] 77 | }; 78 | 79 | const result : NestedNumberVectorList = extract(source, NestedNumberVectorList); 80 | assertEquals(3, result.numbers.length); 81 | 82 | assertEquals(1, result.numbers[0][0]); 83 | assertEquals(2, result.numbers[0][1]); 84 | assertEquals(4, result.numbers[1][0]); 85 | assertEquals(5, result.numbers[1][1]); 86 | assertEquals(7, result.numbers[2][0]); 87 | assertEquals(8, result.numbers[2][1]); 88 | assertEquals(9, result.numbers[2][2]); 89 | } 90 | 91 | [Test] 92 | public function nested_complex() : void 93 | { 94 | const source : Object = { 95 | people: [ 96 | [ 97 | { 98 | name: "jonny", 99 | age: 28, 100 | artists: [ "mew", "tool" ] 101 | }, 102 | { 103 | name: "mayakwd", 104 | age: 26, 105 | address: { 106 | adress1: "Somewhere", 107 | city: "Tomsk" 108 | } 109 | } 110 | ] 111 | ] 112 | }; 113 | 114 | const result: NestedComplexVectorList = extract(source, NestedComplexVectorList); 115 | 116 | assertEquals("jonny", result.people[0][0].name); 117 | assertEquals("Tomsk", result.people[0][1].address.city); 118 | 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /test-src/org/osflash/vanilla/TestVanilla.as: -------------------------------------------------------------------------------- 1 | package org.osflash.vanilla 2 | { 3 | import org.flexunit.asserts.assertEquals; 4 | import org.flexunit.asserts.assertNotNull; 5 | import org.flexunit.asserts.assertNull; 6 | import org.flexunit.asserts.assertTrue; 7 | import org.osflash.vanilla.testdata.PersonConstructorMetadata; 8 | import org.osflash.vanilla.testdata.PersonImplicitFields; 9 | import org.osflash.vanilla.testdata.PersonMutlipleArgumentSetterMetadata; 10 | import org.osflash.vanilla.testdata.PersonPublicFields; 11 | import org.osflash.vanilla.testdata.PersonPublicFieldsMetadata; 12 | import org.osflash.vanilla.testdata.PersonSetterMetadata; 13 | import org.osflash.vanilla.testdata.PersonTransientFields; 14 | import org.osflash.vanilla.testdata.PersonWithAddressConstructor; 15 | import org.osflash.vanilla.testdata.PersonWithAddressFields; 16 | 17 | /** 18 | * @author Jonny 19 | */ 20 | public class TestVanilla 21 | { 22 | /** 23 | * Plain old AS3 Object which we want to Marhsall into a strong type. 24 | */ 25 | public static const SOURCE : Object = { 26 | name: "jonny", 27 | age: 28, 28 | artists: [ "mew", "tool" ] 29 | }; 30 | 31 | private var _marshaller : Vanilla; 32 | 33 | [Before] 34 | public function setup() : void 35 | { 36 | _marshaller = new Vanilla(); 37 | } 38 | 39 | [Test] 40 | public function noNeedToMarshall() : void { 41 | assertEquals("string value", _marshaller.extract("string value", String)); 42 | assertEquals(42, _marshaller.extract(42, Number)); 43 | assertEquals(true, _marshaller.extract(true, Boolean)); 44 | } 45 | 46 | [Test] 47 | public function withPublicFields() : void 48 | { 49 | const result : PersonPublicFields = _marshaller.extract(SOURCE, PersonPublicFields); 50 | assertEquals(SOURCE["name"], result.name); 51 | assertEquals(SOURCE["age"], result.age); 52 | assertEquals(SOURCE["artists"], result.artists); 53 | } 54 | 55 | [Test] 56 | public function withImplicitFields() : void 57 | { 58 | const result : PersonImplicitFields = _marshaller.extract(SOURCE, PersonImplicitFields); 59 | assertEquals(SOURCE["name"], result.name); 60 | assertEquals(SOURCE["age"], result.age); 61 | assertEquals(SOURCE["artists"], result.artists); 62 | } 63 | 64 | 65 | [Test (description="Marshalling doesn't require all fields in the TargetType to be present in the source object")] 66 | public function missingFieldsUsingFieldInection() : void 67 | { 68 | const source : Object = { name: "dave" }; 69 | const result : PersonPublicFields = _marshaller.extract(source, PersonPublicFields); 70 | assertEquals(source["name"], result.name); 71 | assertEquals(0, result.age); 72 | assertEquals(null, result.artists); 73 | } 74 | 75 | [Test (description="Marshalling doesn't require all fields in the TargetType to be present in the source object")] 76 | public function missingFieldsUsingMutatorInection() : void 77 | { 78 | const source : Object = { name: "dave" }; 79 | const result : PersonSetterMetadata = _marshaller.extract(source, PersonSetterMetadata); 80 | assertEquals(source["name"], result.getName()); 81 | assertEquals(0, result.getAge()); 82 | assertEquals(null, result.getArtists()); 83 | } 84 | 85 | [Test (description="If the values dont exist in the source object, the AVM will either use the default value (if defined) or assign the default empty value for the method param's Type")] 86 | public function missingFieldsUsingMultipleArgumentSetter() : void 87 | { 88 | const source : Object = { name: "dave" }; 89 | const result : PersonMutlipleArgumentSetterMetadata = _marshaller.extract(source, PersonMutlipleArgumentSetterMetadata); 90 | assertEquals(source["name"], result.getName()); 91 | assertEquals(0, result.getAge()); 92 | assertEquals(null, result.getArtists()); 93 | } 94 | 95 | [Test (description="Marshalling will ignore any extra fields present in the source object that don't exist in the TargetType")] 96 | public function withAdditionalFieldsInSource() : void 97 | { 98 | const source : Object = { name: "bob", age: 27, artists: [ "nin", "qotsa" ], gender: "m" }; 99 | const result : PersonPublicFields = _marshaller.extract(source, PersonPublicFields); 100 | assertEquals(source["name"], result.name); 101 | assertEquals(source["age"], result.age); 102 | assertEquals(source["artists"], result.artists); 103 | } 104 | 105 | [Test] 106 | public function mismatchedDataType() : void 107 | { 108 | var errorThrown : Boolean; 109 | try { 110 | const source : Object = { name: "Jonny", age: "27" }; 111 | _marshaller.extract(source, PersonPublicFields); 112 | } 113 | catch (e : MarshallingError) { 114 | errorThrown = true; 115 | assertEquals(MarshallingError.TYPE_MISMATCH, e.errorID); 116 | } 117 | assertTrue(errorThrown); 118 | } 119 | 120 | [Test] 121 | public function missingRequiredFieldDefinedByConstructorInjection() : void 122 | { 123 | var errorThrown : Boolean; 124 | try { 125 | const source : Object = { name: "Jonny" }; 126 | _marshaller.extract(source, PersonConstructorMetadata); 127 | } 128 | catch (e : MarshallingError) { 129 | errorThrown = true; 130 | assertEquals(MarshallingError.MISSING_REQUIRED_FIELD, e.errorID); 131 | } 132 | assertTrue(errorThrown); 133 | } 134 | 135 | 136 | [Test] 137 | public function withMetadataDefinedConstructorArguments() : void 138 | { 139 | const result : PersonConstructorMetadata = _marshaller.extract(SOURCE, PersonConstructorMetadata); 140 | assertEquals(SOURCE["name"], result.name); 141 | assertEquals(SOURCE["age"], result.age); 142 | assertEquals(SOURCE["artists"], result.artists); 143 | } 144 | 145 | 146 | [Test] 147 | public function withMetadataDefinedSetters() : void 148 | { 149 | const result : PersonSetterMetadata = _marshaller.extract(SOURCE, PersonSetterMetadata); 150 | assertEquals(SOURCE["name"], result.getName()); 151 | assertEquals(SOURCE["age"], result.getAge()); 152 | assertEquals(SOURCE["artists"], result.getArtists()); 153 | } 154 | 155 | [Test] 156 | public function withMetadataDefinedMultipleArgumentSetter() : void 157 | { 158 | const result : PersonMutlipleArgumentSetterMetadata = _marshaller.extract(SOURCE, PersonMutlipleArgumentSetterMetadata); 159 | assertEquals(SOURCE["name"], result.getName()); 160 | assertEquals(SOURCE["age"], result.getAge()); 161 | assertEquals(SOURCE["artists"], result.getArtists()); 162 | } 163 | 164 | [Test] 165 | public function withMetadataFields() : void 166 | { 167 | const source : Object = { 168 | myName: "Dave", 169 | myAge: 28 170 | }; 171 | const result : PersonPublicFieldsMetadata = _marshaller.extract(source, PersonPublicFieldsMetadata); 172 | assertEquals(source["myName"], result.name); 173 | assertEquals(source["myAge"], result.age); 174 | } 175 | 176 | [Test] 177 | public function withNestedObjectFields() : void 178 | { 179 | const source : Object = { 180 | name: "Jonny", 181 | address: { 182 | address1: "My House", 183 | city: "London" 184 | } 185 | }; 186 | 187 | const result : PersonWithAddressFields = _marshaller.extract(source, PersonWithAddressFields); 188 | assertEquals(source["name"], result.name); 189 | assertNotNull(result.address); 190 | assertEquals(source["address"]["address1"], result.address.address1); 191 | assertEquals(source["address"]["city"], result.address.city); 192 | } 193 | 194 | [Test] 195 | public function withNestedObjectConstructorMetadata() : void 196 | { 197 | const source : Object = { 198 | name: "Jonny", 199 | address: { 200 | address1: "My House", 201 | city: "London" 202 | } 203 | }; 204 | 205 | const result : PersonWithAddressConstructor = _marshaller.extract(source, PersonWithAddressConstructor); 206 | assertEquals(source["name"], result.name); 207 | assertNotNull(result.address); 208 | assertEquals(source["address"]["address1"], result.address.address1); 209 | assertEquals(source["address"]["city"], result.address.city); 210 | } 211 | 212 | [Test (description="Marshalling ignores Transient variables")] 213 | public function transientVariablesAreIgnored() : void 214 | { 215 | const source : Object = { name: "dave", age:22 }; 216 | const result : PersonTransientFields = _marshaller.extract(source, PersonTransientFields); 217 | assertNull(result.name); 218 | assertEquals(22, result.age); 219 | } 220 | } 221 | } -------------------------------------------------------------------------------- /test-src/org/osflash/vanilla/outside/TestExtractMethod.as: -------------------------------------------------------------------------------- 1 | package org.osflash.vanilla.outside 2 | { 3 | import org.flexunit.asserts.assertEquals; 4 | import org.osflash.vanilla.extract; 5 | /** 6 | * @author Jonny 7 | */ 8 | public class TestExtractMethod 9 | { 10 | [Test] 11 | public function useConvenienceMethod() : void 12 | { 13 | const source : Object = { 14 | name: "Jonny", 15 | age: 28 16 | }; 17 | 18 | const result : PersonVO = extract(source, PersonVO); 19 | assertEquals("Jonny", result.name); 20 | assertEquals(28, result.age); 21 | } 22 | } 23 | } 24 | 25 | class PersonVO { 26 | public var name : String; 27 | public var age : uint; 28 | } -------------------------------------------------------------------------------- /test-src/org/osflash/vanilla/testdata/Address.as: -------------------------------------------------------------------------------- 1 | package org.osflash.vanilla.testdata 2 | { 3 | /** 4 | * @author Jonny 5 | */ 6 | public class Address 7 | { 8 | public var address1 : String; 9 | public var city : String; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test-src/org/osflash/vanilla/testdata/NestedComplexVectorList.as: -------------------------------------------------------------------------------- 1 | package org.osflash.vanilla.testdata 2 | { 3 | public class NestedComplexVectorList 4 | { 5 | public var people : Vector.>; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /test-src/org/osflash/vanilla/testdata/NestedNumberVectorList.as: -------------------------------------------------------------------------------- 1 | package org.osflash.vanilla.testdata 2 | { 3 | public class NestedNumberVectorList 4 | { 5 | public var numbers : Vector.>; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /test-src/org/osflash/vanilla/testdata/NumberArrayList.as: -------------------------------------------------------------------------------- 1 | package org.osflash.vanilla.testdata 2 | { 3 | /** 4 | * @author Jonny 5 | */ 6 | public class NumberArrayList 7 | { 8 | [Marshall (type="Number")] 9 | public var numbers : Array; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test-src/org/osflash/vanilla/testdata/NumberVectorList.as: -------------------------------------------------------------------------------- 1 | package org.osflash.vanilla.testdata 2 | { 3 | /** 4 | * @author Jonny 5 | */ 6 | public class NumberVectorList 7 | { 8 | public var numbers : Vector.; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test-src/org/osflash/vanilla/testdata/PersonConstructorMetadata.as: -------------------------------------------------------------------------------- 1 | package org.osflash.vanilla.testdata 2 | { 3 | /** 4 | * This imultable model requires the fields to be set in the constructor, here we define Marshalling Metadata 5 | * to define rules to map from the source object into constructor arguments. 6 | */ 7 | [Marshall(field="name", field="age", field="artists")] 8 | public class PersonConstructorMetadata 9 | { 10 | private var _name : String; 11 | private var _age : uint; 12 | private var _artists : Array; 13 | 14 | public function PersonConstructorMetadata(name : String, age : uint, artists : Array) { 15 | _name = name; 16 | _age = age; 17 | _artists = artists; 18 | } 19 | 20 | public function get name() : String { return _name; } 21 | public function get age() : uint { return _age; } 22 | public function get artists() : Array { return _artists; } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /test-src/org/osflash/vanilla/testdata/PersonImplicitFields.as: -------------------------------------------------------------------------------- 1 | package org.osflash.vanilla.testdata 2 | { 3 | /** 4 | * Implicit setter can be used without additional mapping rules as long as the setter's name maps to the field 5 | * name in the source object. 6 | */ 7 | public class PersonImplicitFields 8 | { 9 | private var _name : String; 10 | private var _age : uint; 11 | private var _artists : Array; 12 | 13 | public function get name() : String { return _name; } 14 | public function set name(name : String) : void { _name = name; } 15 | 16 | public function get age() : uint { return _age; } 17 | public function set age(age : uint) : void { _age = age; } 18 | 19 | public function get artists() : Array { return _artists; } 20 | public function set artists(value : Array) : void { _artists = value; } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /test-src/org/osflash/vanilla/testdata/PersonMutlipleArgumentSetterMetadata.as: -------------------------------------------------------------------------------- 1 | package org.osflash.vanilla.testdata 2 | { 3 | /** 4 | * This class uses a single setter method to supply all the values to the object. 5 | */ 6 | public class PersonMutlipleArgumentSetterMetadata 7 | { 8 | private var _name : String; 9 | private var _age : uint; 10 | private var _artists : Array; 11 | 12 | [Marshall(field="name", field="age", field="artists")] 13 | public function init(name : String, age : uint, artists : Array) : void 14 | { 15 | _name = name; 16 | _age = age; 17 | _artists = artists; 18 | } 19 | 20 | public function getName() : String { return _name; } 21 | public function getAge() : uint { return _age; } 22 | public function getArtists() : Array { return _artists; } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /test-src/org/osflash/vanilla/testdata/PersonPublicFields.as: -------------------------------------------------------------------------------- 1 | package org.osflash.vanilla.testdata 2 | { 3 | /** 4 | * The fields of this VO map directly to the properties of the source object; all Marshalling is automagik. 5 | */ 6 | public class PersonPublicFields 7 | { 8 | public var name : String; 9 | public var age : uint; 10 | public var artists : Array; 11 | public var address : Address; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test-src/org/osflash/vanilla/testdata/PersonPublicFieldsMetadata.as: -------------------------------------------------------------------------------- 1 | package org.osflash.vanilla.testdata 2 | { 3 | /** 4 | * The fields of this VO don't map to the properties of the source object, we defined Marshalling rules in the 5 | * Metadata to create the correct mappings. 6 | */ 7 | public class PersonPublicFieldsMetadata 8 | { 9 | [Marshall (field="myName")] 10 | public var name : String; 11 | 12 | [Marshall (field="myAge")] 13 | public var age : uint; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /test-src/org/osflash/vanilla/testdata/PersonSetterMetadata.as: -------------------------------------------------------------------------------- 1 | package org.osflash.vanilla.testdata 2 | { 3 | /** 4 | * This class uses Java style mutators; here we define the marshalling rule above each mutator as Metadata, listing 5 | * the filed in the source object to read the value from when invoking the method. 6 | */ 7 | public class PersonSetterMetadata 8 | { 9 | private var _name : String; 10 | private var _age : uint; 11 | private var _artists : Array; 12 | 13 | [Marshall(field="name")] 14 | public function setName(value : String) : void { _name = value; } 15 | public function getName() : String { return _name; } 16 | 17 | [Marshall(field="age")] 18 | public function setAge(value : uint) : void { _age = value; } 19 | public function getAge() : uint { return _age; } 20 | 21 | [Marshall(field="artists")] 22 | public function setArtists(value : Array) : void { _artists = value; } 23 | public function getArtists() : Array { return _artists; } 24 | 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /test-src/org/osflash/vanilla/testdata/PersonTransientFields.as: -------------------------------------------------------------------------------- 1 | package org.osflash.vanilla.testdata 2 | { 3 | /** 4 | * The fields of this VO map directly to the properties of the source object; all Marshalling is automagik. 5 | */ 6 | public class PersonTransientFields 7 | { 8 | [Transient] 9 | public var name : String; 10 | public var age : uint; 11 | public var artists : Array; 12 | public var address : Address; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test-src/org/osflash/vanilla/testdata/PersonWithAddressConstructor.as: -------------------------------------------------------------------------------- 1 | package org.osflash.vanilla.testdata 2 | { 3 | [Marshall(field="name", field="address")] 4 | public class PersonWithAddressConstructor 5 | { 6 | private var _name : String; 7 | private var _address : Address; 8 | 9 | public function PersonWithAddressConstructor(name : String, address : Address) { 10 | _name = name; 11 | _address = address; 12 | } 13 | 14 | public function get name() : String { 15 | return _name; 16 | } 17 | 18 | public function get address() : Address { 19 | return _address; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /test-src/org/osflash/vanilla/testdata/PersonWithAddressFields.as: -------------------------------------------------------------------------------- 1 | package org.osflash.vanilla.testdata 2 | { 3 | /** 4 | * @author Jonny 5 | */ 6 | public class PersonWithAddressFields 7 | { 8 | public var name : String; 9 | public var address : Address; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test-src/org/osflash/vanilla/testdata/PersonWithMultipleAddressesArrayField.as: -------------------------------------------------------------------------------- 1 | package org.osflash.vanilla.testdata 2 | { 3 | /** 4 | * @author Jonny 5 | */ 6 | public class PersonWithMultipleAddressesArrayField 7 | { 8 | public var name : String; 9 | 10 | [Marshall (type="org.osflash.vanilla.testdata.Address")] 11 | public var addresses : Array; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test-src/org/osflash/vanilla/testdata/PersonWithMultipleAddressesVectorField.as: -------------------------------------------------------------------------------- 1 | package org.osflash.vanilla.testdata 2 | { 3 | /** 4 | * @author Jonny 5 | */ 6 | public class PersonWithMultipleAddressesVectorField 7 | { 8 | public var name : String; 9 | public var addresses : Vector.
; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test-src/org/osflash/vanilla/testdata/StringArrayList.as: -------------------------------------------------------------------------------- 1 | package org.osflash.vanilla.testdata 2 | { 3 | /** 4 | * @author Jonny 5 | */ 6 | public class StringArrayList 7 | { 8 | private var _strings : Array; 9 | 10 | [Marshall(field="strings", type="String")] 11 | public function setStrings(value : Array) : void { 12 | _strings = value; 13 | } 14 | 15 | public function getStrings() : Array { 16 | return _strings; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /test-src/org/osflash/vanilla/testdata/StringVectorList.as: -------------------------------------------------------------------------------- 1 | package org.osflash.vanilla.testdata 2 | { 3 | /** 4 | * @author Jonny 5 | */ 6 | public class StringVectorList 7 | { 8 | public var strings : Vector.; 9 | } 10 | } 11 | --------------------------------------------------------------------------------