While working on the upcoming AmfPHP 2.2 I came upon an interesting challenge: Sending AMF typed objects without knowing the types at compile time.

This would allow users of the service browser to send typed objects both with JSON and AMF.
The difficulty is the following: For Flash to send an object as typed, you must provide an alias for its class. This can be done either with the RemoteClass meta data tag, or with registerClassAlias. This works fine, but is designed with the idea that you know what the alias shall be at compile time. So I had to find a workaround so that the user can state:

“take this object, use this alias, and send it to the server. “

So I ended up doing the following:

  1. I created some dynamic empty dummy classes, and made sure they were included in the compiled SWF by including a reference to them in the code.
    package
    {
    public dynamic class Dummy0
    {
    
    }
    }
    

    The classes are public so that I can find them with getDefinitionByName ,and dynamic so that I can add things to them at runtime.
    and the reference in my main class:

    var dummyRef:Class = Dummy0;
    
  2. For each new object that needs to be sent that is marked with an alias, I replace it with one of my dummy classes, and call registerClassAlias so that the used dummy is sent with the right alias. The dummyClass is stored in a dictionary, so that I can reuse it for another object with the same alias.
    var dummyClass:Class = null;
    
    if(!type2Class[explicitType]){
    dummyClass = getDefinitionByName("Dummy" + numUsedDummyClasses) as Class;
    type2Class[explicitType] = dummyClass;
    numUsedDummyClasses++;
    }else{
    dummyClass = type2Class[explicitType];
    }
    
  3. And I tunnel down the object recursively to make sure this works on many levels.

Is this a hack? Indeed? I would very much like to find a cleaner way to do this. I have considered messing directly with the byte code, but this seems a bit heavy handed.

It’s also only part of the problem: I would very much like to be able to receive typed objects from the server about which the client knows nothing and still be able to retrieve the type. The Flash player unfortunately does not allow this.

The full code as it stands is below. It can also be found online here.

package
{

import flash.display.Sprite;
import flash.external.ExternalInterface;
import flash.net.NetConnection;
import flash.net.Responder;
import flash.net.getClassByAlias;
import flash.net.registerClassAlias;
import flash.utils.Dictionary;
import flash.utils.getDefinitionByName;
import flash.utils.getQualifiedClassName;

/**
* provides AMF calling functionality to the service browser via External Interface.
* exposes a "call" method, a "isAlive" method, and uses 1 callback: "onResult"
* */
public class AmfCaller extends Sprite
{
private var numUsedDummyClasses:int = 0;
private var type2Class:Dictionary = new Dictionary();

public function AmfCaller()
{
ExternalInterface.addCallback("call", call);
ExternalInterface.addCallback("isAlive", isAlive);

//need this to make sure compiler includes dummy classes
var dummyRef:Class = Dummy0;
dummyRef = Dummy1;
dummyRef = Dummy2;
dummyRef = Dummy3;
dummyRef = Dummy4;
dummyRef = Dummy5;
dummyRef = Dummy6;
dummyRef = Dummy7;
dummyRef = Dummy8;
dummyRef = Dummy9;
dummyRef = Dummy10;
dummyRef = Dummy11;
dummyRef = Dummy12;
dummyRef = Dummy13;
dummyRef = Dummy14;
dummyRef = Dummy15;
dummyRef = Dummy16;
dummyRef = Dummy17;
dummyRef = Dummy18;
dummyRef = Dummy19;
}

/**
* a quick way for JS to check that AmfCaller is properly loaded and avaliable
* */
public function isAlive():Boolean{
return true;
}
/**
* make an AMF call
* */
public function call(url:String, command:String, parameters:Array):void{
var netConnection:NetConnection = new NetConnection();
netConnection.connect(url);
var callArgs:Array = new Array(command, new Responder(resultHandler, resultHandler));
for each(var param:* in parameters){
callArgs.push(convertObjectUsingExplicitType(param));

}
netConnection.call.apply(netConnection, callArgs);
}

/**
* callback used both for error and success. calls onResult in JS.
* */
private function resultHandler(obj:Object):void{

ExternalInterface.call("onResult", obj);

}

/**
* replaces an object marked with _explicitType by a Dummy class, and registers the Dummy class with an alias set to _explicitType.
* Yes this is absolutely bending over backwards, and is limited to 20 different explicit types.
* A possible way to override this would be to manipulate the byte code directly but it's more trouble than it's worth.
* Any suggestion on how to do this in a cleaner fashion is welcome.
* */
private function convertObjectUsingExplicitType(obj:*):*{
var type:String = getQualifiedClassName(obj);
if((type != "Array") && (type != "Object")){
return obj;
}

var explicitType:String = obj["_explicitType"];
if(!explicitType){
return obj;
}

//if we're here it means that the object needs to be replaced with one with which we can call registerClassAlias
//so replace by a typed one
var dummyClass:Class = null;

if(!type2Class[explicitType]){
dummyClass = getDefinitionByName("Dummy" + numUsedDummyClasses) as Class;
type2Class[explicitType] = dummyClass;
numUsedDummyClasses++;
}else{
dummyClass = type2Class[explicitType];
}
var ret:Object = new dummyClass();
registerClassAlias(explicitType, dummyClass);

for(var key:String in obj){
if(key == "_explicitType"){
continue;
}
var subObj:* = obj[key];

ret[key] = convertObjectUsingExplicitType(subObj);
}

return ret;
}

}

}

Pin It