//////////////////////////////////////////////////////////////////////////////// // // 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 mx.messaging.channels.amfx { import flash.utils.ByteArray; import flash.utils.Dictionary; import flash.utils.IExternalizable; import flash.utils.describeType; import flash.xml.XMLDocument; import mx.logging.Log; import mx.utils.HexEncoder; import mx.utils.ObjectProxy; import mx.utils.ObjectUtil; [ExcludeClass] /** * Serializes an arbitrary ActionScript object graph to an XML * representation that is based on Action Message Format (AMF) * version 3. * @private */ public class AMFXEncoder { public function AMFXEncoder() { super(); settings = {}; settings.prettyPrinting = false; } public function encode(obj:Object, headers:Array = null):XML { XML.setSettings(settings); var xml:XML = new XML(""); xml.setNamespace(NAMESPACE); xml.@["ver"] = CURRENT_VERSION; var context:AMFXContext = new AMFXContext(); context.log = Log.getLogger("mx.messaging.channels.amfx.AMFXEncoder"); encodePacket(xml, obj, headers, context); return xml; } private static function encodePacket(xml:XML, obj:Object, headers:Array = null, context:AMFXContext = null):void { if (headers) encodeHeaders(xml, headers, context); encodeBody(xml, obj, context); } private static function encodeHeaders(xml:XML, headers:Array, context:AMFXContext):void { for (var i:uint = 0; i < headers.length; i++) { var header:Object = headers[i]; var element:XML =
; element.@["name"] = header.name; element.@["mustUnderstand"] = (header.mustUnderstand == true); encodeValue(element, header.content, context); xml.appendChild(element); } } private static function encodeBody(xml:XML, obj:*, context:AMFXContext):void { var element:XML = ; //element.@["targetURI"] = ""; //TODO: Support this attribute encodeValue(element, obj, context); xml.appendChild(element); } public static function encodeValue(xml:XML, obj:*, context:AMFXContext):void { if (obj != null) { if (obj is String) { encodeString(xml, String(obj), context); } else if (obj is Number) { encodeNumber(xml, Number(obj)); } else if (obj is Boolean) { encodeBoolean(xml, Boolean(obj)); } else if (obj is ByteArray) { encodeByteArray(xml, ByteArray(obj)); } else if (obj is Array) { encodeArray(xml, obj as Array, context); } else if (obj is XML || obj is XMLDocument) { encodeXML(xml, obj); } else if (obj is Date) { encodeDate(xml, obj as Date, context); } else if (obj is Class) { //TODO: Throw errors for unsupported types? if (context.log) context.log.warn("Cannot serialize type Class"); } else if (obj is Dictionary) { encodeDictionary(xml, obj as Dictionary, context); } else { encodeObject(xml, obj, context); } } else if (obj === undefined) { xml.appendChild(X_UNDEFINED.copy()); } else { xml.appendChild(X_NULL.copy()); } } private static function encodeArray(xml:XML, array:Array, context:AMFXContext):void { var ref:int = context.findObject(array); var element:XML; if (ref >= 0) { element = element.@["id"] = String(ref); } else { rememberObject(context, array); element = ; var named:Object = {}; var ordinal:Array = []; var isECMAArray:Boolean = false; // Separate named and ordinal array members for (var member:String in array) { if (isNaN(Number(member))) { named[member] = array[member]; isECMAArray = true; } else { var num:int = parseInt(member); ordinal[num] = array[num]; } } // Encode named items as early as possible for (var n:String in named) { encodeArrayItem(element, n, named[n], context); } var ordinalLength:uint = 0; var dense:Boolean = true; for (var i:uint = 0; i < ordinal.length; i++) { var o:* = ordinal[i]; // If we have an undefined slot remaining ordinal // keys will be converted to named keys to preserve dense set if (o !== undefined) { if (dense) { encodeValue(element, o, context); ordinalLength++; } else { isECMAArray = true; encodeArrayItem(element, String(i), o, context); } } else { dense = false; } } element.@["length"] = String(ordinalLength); if (isECMAArray) { element.@["ecma"] = "true"; } } xml.appendChild(element); } private static function encodeArrayItem(xml:XML, name:String, value:*, context:AMFXContext):void { var item:XML = ; item.@["name"] = name; encodeValue(item, value, context); xml.appendChild(item); } private static function encodeBoolean(xml:XML, bool:Boolean):void { if (bool) xml.appendChild(X_TRUE.copy()); else xml.appendChild(X_FALSE.copy()); } private static function encodeByteArray(xml:XML, obj:ByteArray):void { var element:XML = ; var encoder:HexEncoder = new HexEncoder(); encoder.encode(obj); var encoded:String = encoder.flush(); element.appendChild(encoded); xml.appendChild(element); } private static function encodeDate(xml:XML, date:Date, context:AMFXContext):void { var ref:int = context.findObject(date); var element:XML; if (ref >= 0) { element = element.@["id"] = String(ref); } else { rememberObject(context, date); element = ; element.appendChild(new XML(date.getTime().toString())); } xml.appendChild(element); } private static function encodeNumber(xml:XML, num:Number):void { var element:XML = null; if (num is int || num is uint) { element = ; } else { element = ; } element.appendChild(new XML(num.toString())); xml.appendChild(element); } private static function encodeDictionary(xml:XML, dict:Dictionary, context:AMFXContext):void { var ref:int = context.findObject(dict); var element:XML; if (ref >= 0) { element = ; element.@["id"] = String(ref); } else { rememberObject(context, dict); element = ; var classInfo:Object = ObjectUtil.getClassInfo(dict, null, CLASS_INFO_OPTIONS); var properties:Array = classInfo.properties; var count:uint = properties.length; for (var i:uint = 0; i < count; i++) { var prop:Object = properties[i]; encodeValue(element, prop, context); encodeValue(element, dict[prop], context); } } element.@["length"] = String(count); xml.appendChild(element); } private static function rememberObject(context:AMFXContext, obj:*):void { context.addObject(obj); } private static function encodeObject(xml:XML, obj:*, context:AMFXContext):void { var ref:int = context.findObject(obj); var element:XML; if (ref >= 0) { element = element.@["id"] = String(ref); } else { rememberObject(context, obj); element = ; var classInfo:Object = ObjectUtil.getClassInfo(obj, null, CLASS_INFO_OPTIONS); var className:String = classInfo.name; var classAlias:String = classInfo.alias; var properties:Array = classInfo.properties; var count:uint = properties.length; // We need to special case ObjectProxy as for serialization we actually need the // remote alias of ObjectProxy, not the wrapped object. if (obj is ObjectProxy) { var cinfo:XML = describeType(obj); className = cinfo.@name.toString(); classAlias = cinfo.@alias.toString(); } var remoteClassName:String = ((classAlias != null) ? classAlias : className); if (remoteClassName && remoteClassName != "Object" && remoteClassName != "Array") { element.@["type"] = remoteClassName.replace(REGEX_CLASSNAME, "."); } if (obj is IExternalizable) { classInfo.externalizable = true; encodeTraits(element, classInfo, context); var ext:IExternalizable = IExternalizable(obj); var ba:ByteArray = new ByteArray(); ext.writeExternal(ba); encodeByteArray(element, ba); } else { classInfo.externalizable = false; encodeTraits(element, classInfo, context); for (var i:uint = 0; i < count; i++) { var prop:String = properties[i]; encodeValue(element, obj[prop], context); } } } xml.appendChild(element); } private static function encodeString(xml:XML, str:String, context:AMFXContext, isTrait:Boolean = false):void { var ref:int = context.findString(str); var element:XML = ; if (ref >= 0) { element.@["id"] = String(ref); } else { //Remember string context.addString(str); if (str.length > 0) { // Traits won't contain chars that need escaping if (!isTrait) str = escapeXMLString(str); var x:XML = new XML(str); element.appendChild(x); } } xml.appendChild(element); } private static function encodeTraits(xml:XML, classInfo:Object, context:AMFXContext):void { var element:XML = ; var ref:int = context.findTraitInfo(classInfo); if (ref >= 0) { element.@["id"] = String(ref); } else { //Remember trait info context.addTraitInfo(classInfo) if (classInfo.externalizable) { element.@["externalizable"] = "true"; } else { var properties:Array = classInfo.properties; if (properties != null) { var count:uint = properties.length; for (var i:uint = 0; i < count; i++) { var prop:String = properties[i]; encodeString(element, prop, context, true); } } } } xml.appendChild(element); } private static function encodeXML(xml:XML, xmlObject:Object):void { var element:XML = ; var str:String; if (xmlObject is XML) str = XML(xmlObject).toXMLString(); else str = xmlObject.toString(); if (str.length > 0) { str = escapeXMLString(str); var x:XML = new XML(str); element.appendChild(x); } xml.appendChild(element); } private static function escapeXMLString(str:String):String { if (str.length > 0) { if ((str.indexOf("<") != -1) || (str.indexOf("&") != -1)) { if (str.indexOf("]]>") != -1) { str = str.replace(REGEX_CLOSE_CDATA, "]]>"); } str = ""; } } return str; } private var settings:Object; public static const CURRENT_VERSION:uint = 3; public static const NAMESPACE_URI:String = "http://www.macromedia.com/2005/amfx"; public static const NAMESPACE:Namespace = new Namespace("", NAMESPACE_URI); private static const REGEX_CLASSNAME:RegExp = new RegExp("\\:\\:", "g"); private static const REGEX_CLOSE_CDATA:RegExp = new RegExp("]]>", "g"); private static const CLASS_INFO_OPTIONS:Object = {includeReadOnly:false, includeTransient:false}; private static const X_FALSE:XML = ; private static const X_NULL:XML = ; private static const X_TRUE:XML = ; private static const X_UNDEFINED:XML = ; } }