Dynamically Generated Static Methods

Posted in Actionscript, Adobe on August 7th, 2008 by dan

If you made it here based solely on the title — good for you, you are apparently endowed with a fair bit of programming-intestinal fortitude. I figured out this little hack months ago and I just got around to writing it up over the past week or so — I’ve been busy, sue me.

I was inspired to investigate this topic by Derek Wischusen’s post “method_missing in ActionScript.”  I saw it and immediately set out to build a port of ActiveRecord to Actionscript.

I’m not convinced this is the “best” implementation for this type of functionality but after much toying about it is the best one I was able to come up with.  Also, you have to remember you can’t eval anything in AS3, so in reality this is just creating Static Methods that point to an existing method that knows how to do something based on the new function’s name, but that’s pretty much what the Rails framework does so, I view that as just fine.

Goal

The initial goal was to set out and get a method like this to work:

var item:Blog = Blog.find_by_title(“Dynamically Generated Static Methods”);

The class “Blog” should have as little, preferably no, extra coding in it.  It should just attempt to call that method with no burden on the programming trying to create the class “Blog.”

Strike One

The first place to start is with Derek’s post that I referenced earlier, I created a class called “ActiveRecord” that was the same as his “BaseProxy” class.

ActiveRecord v0.1

  1. package
  2. {
  3.     import flash.utils.flash_proxy;
  4.     import flash.utils.Proxy;
  5.     import flash.utils.getDefinitionByName;
  6.     import flash.utils.getQualifiedClassName;
  7.  
  8.     public dynamic class ActiveRecord extends Proxy
  9.     {
  10.         public function ActiveRecord()
  11.         {
  12.            
  13.         }
  14.         flash_proxy override function callProperty(method: *, ...args): * {
  15.             try {         
  16.                 var clazz : Class = getDefinitionByName(getQualifiedClassName(this)) as Class;
  17.                 return clazz.prototype[method].apply(method, args);
  18.             }
  19.             catch (e : Error) {
  20.                 return methodMissing (method, args);
  21.             }
  22.         }
  23.          protected function methodMissing(method : *, args : Array) : Object{
  24.             throw( new Error("Method Missing"));
  25.             return null;
  26.         }
  27.     }
  28. }

The above code is taken from this “method_missing in ActionScript.” 

Then I subclass “ActiveRecord” to get a class called “Blog.”

Blog.as v0.1

  1. package
  2. {
  3.     dynamic public class Blog extends ActiveRecord
  4.     {
  5.         public function Blog()
  6.         {
  7.         }
  8.  
  9.     }
  10. }

TestApp.mxml v0.1

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"
  3.     applicationComplete="testObject()">
  4.     <mx:Script>
  5.         <![CDATA[
  6.             private function testObject():void{
  7.                 var blog:Blog;
  8.                 blog = Blog.find_by_name("Some Text Here.");               
  9.             }
  10.         ]]>
  11.     </mx:Script>
  12. </mx:Application>

To even get the above syntax to run you have to go to Project->Properties then Flex Compiler and un-check “Enable Strict type checking.”  This will allow the file to compile. 

However, when you run the TestApp you will see an exception thrown, not by Derek’s code like we’d hope but by the player, “TypeError: Error #1006: find_by_name is not a function.”

Tweak the Syntax

Just to keep things on the up and up I want you to go back and change that project property back ( Project->Properties then Flex Compiler and check “Enable Strict type checking ).  Then we can tweak the syntax of the TestApp.mxml line 8( blog = Blog.find_by_name… ) to be:

blog = Blog["find_by_name"]("Some Text Here.");

We still get a type error from the player, “TypeError: Error #1006: value is not a function.” but at least our syntax compiles cleanly.

Why doesn’t this work

To answer this question you might first want to go read “Object-oriented programming in ActionScript Advanced Topics” but to summarize, the “callProperty” method Derek overrode only works on instances of the class not on the “prototype” of the class which is where the static methods live.  So some how we need to access the prototype before execution.  Sadly, there is no proxy method like “callProperty” for the class’s prototype, so we have to go in a more ghetto direction.

staticInitializer

The direction to move in is to use a static initializer in the Blog class to kick off building the static methods we need.  I will jump straight in the flow of the working solution.

Blog v0.2

  1. package
  2. {
  3.         dynamic public class Blog extends ActiveRecord
  4.         {
  5.                 staticInitializer(prototype.constructor);              
  6.                 public function Blog()
  7.                 {
  8.                 }
  9.  
  10.         }
  11. }

The only thing we’ve now added to Blog.as is a call to staticInitializer passing in the prototype.constructor of our current class.  ( To understand what that means, check the OOP in AS article mentioned previously ).  This will allow us to insert some calls before anything in this object is ever called.  Think of methods like this as constructors for the Class object, not an instance of the class, but of the definition of the class — remember everything is an object.  This method is actually defined in the base class ActiveRecord.as:

ActiveRecord v0.2

  1. package
  2. {
  3.         import flash.utils.Proxy;
  4.         import flash.utils.describeType;
  5.         import flash.utils.getDefinitionByName;
  6.         import flash.utils.getQualifiedClassName;
  7.         import flash.utils.flash_proxy;
  8.  
  9. //      import flash.utils.flash_proxy;
  10.        
  11.         public dynamic class ActiveRecord extends Proxy
  12.         {
  13.                 private static var functionsToAdd:Array = ["find_by_name","find_by_id","find_all"];
  14.                 protected static var methodFactory:DynamicMethodFactory;
  15.                 public function ActiveRecord()
  16.                 {
  17.                        
  18.                 }
  19.                 public static function staticInitializer(klass:Class):void{
  20.                         var typeInfo:XML = describeType(klass);  
  21.                         for each(var s:String in functionsToAdd){
  22.                                 klass[s] = getMethod(typeInfo.@name,s);
  23.                         }
  24.                 }
  25.                 public static function getMethod(objectName:String,methodName:String):Function{
  26.                         return function(...args):Object{return endPoint(objectName,methodName,args);};
  27.                 }
  28.                 public static function endPoint(objectName:String,methodName:String,...args):Object{
  29.                         trace("You called: " + objectName + "." + methodName + "(" + args + ")");
  30.                         var klass:Class = getDefinitionByName(objectName) as Class;
  31.                         var o:ActiveRecord = new klass();
  32.                         return o;
  33.                 }
  34.                 flash_proxy override function callProperty(method: *, ...args): * {
  35.                         try {     
  36.                                 var clazz : Class = getDefinitionByName(getQualifiedClassName(this)) as Class;
  37.                                 return clazz.prototype[method].apply(method, args);
  38.                         }
  39.                         catch (e : Error) {
  40.                                 return methodMissing (method, args);
  41.                         }
  42.                 }
  43.                protected function methodMissing(method : *, args : Array) : Object{
  44.                         throw( new Error("Method Missing"));
  45.                         return null;
  46.                 }
  47.         }
  48. }

So you can see a few things have been added, line 13, is just a list of methods we want to add to the object, you could generated these from anything you want(hint hint).  The definition for staticInitializer in line 19 is where the real stuff starts to happen.  In this simple example we will loop across the functions we want to add then create a new anonymous function that remaps a bunch of parameters to our function called endPoint, which really handles the function call.  This allows us to easily do anything we want based on, the class name, method name and argument list.

Tweak the Syntax, Again

If you want to make it look like a normal method call again, you can change that compiler flag back to disable strict type checking and then call your new dynamic static functions like normal.

There maybe more posts in this area, extending this concept out to create some very useful Rails-ish Actionscript libraries.

Some Useful Flex/AIR Tidbits

Posted in AIR, Adobe, Flex on April 22nd, 2008 by dan

Recently I had the “pleasure” of learning the ins and outs of building Flex and AIR applications via the SDK on a Linux machine.  I wanted to do this for some of the libraries that underpin my project so that they could be built and tested automatically.  So, I setup SVN, Trac and CruiseControl on a Ubuntu VM on my mac.  To get started there are tons of resources about getting SVN and Trac setup under Apache on Ubuntu so, just Google for them.  Setting up CruiseControl was really easy thanks to their good documents, I started with the source distro but, do whatever works.

Now, onto why I’m posting today, along the way I found some annoying things about how FlexBuilder played with the SDK.  For instance, the .flexLibProperties file that is hidden in your FB project, is not the right format to be taken in as a parameter to “compc” ( the library compiler, which seems to require a list of class names to include ), so I wrote a quick Python script to convert that file into a config file that is readable by compc.  Here is how to include it in your Ant script:


<target name="setupClassList">
    <exec executable="python" failonerror="yes">
        <arg line="${helperDir}/classFileConverter.py ${lib_root_dir}/.flexLibProperties classes.xml"/>
    </exec>
</target>


 

It takes in two parameters so you can manipulate the output location of the new file.
Another odd part is that the application descriptor for an AIR project doesn’t get correctly populated.  FB itself fills in the <content> tag for you when it goes to compile.  If you look in your source directory the application descriptor has this string, “[This value will be overwritten by Flex Builder in the output app.xml]” instead of the name of the SWF.  One more little python script:


<exec executable="python" failonerror="yes">
    <arg line="${helperDir}/appDescrFix.py ../bin-debug/${app_descriptor}"/>
</exec>



 

Of course, I am going to give you the scripts!  Here are the links:
.flexLibProperties Converter
Application Descriptor Fixer

 

If someone knows a much easier way to do this just using the command line tools and/or Ant, I’d love to hear it!