Skip to content

Loom Lives!

About fifteen minutes ago, I successfully weaved the final opcodes required to create an AOP dynamic proxy with Loom. I am totally stoked.

It’s been almost three months of work, admittedly on and off. I think if I strung together all the hours I have put in to the project, it would be about 120-160 hours altogether here and there. The first revision of the ABC parser that I worked on in January and February worked great but wasn’t very usable, so it ended up getting replaced by the new version. The new version of the parser and serializer along with the opcode enumeration are a big improvement, and were so easy to use that I was able to weave a dynamic proxy in just a few hours (most of which was spent tackling the learning curve of AVM “assembly” and diagnosing/fixing an opcode bug I found along the way).

So without further ado, here’s a sneak peak in to the unfinished API for Loom. Below is a copy of a really quick n’ dirty test case that I put together to make sure that Loom was working as expected. The test case does as follows.

1) Loads the ABC bytecode for a class called loom.template:BaseClass.
2) Parses the ABC code and enhances it with several Loom-related items: a Dictionary to hold anonymous advise handling functions, a setHandler() method to accept these functions, and a method call proxyInvocation() that handles dynamic method invocation.
3) Stomps the opcodes for all the instance methods with dynamically weaved opcodes that tell the method to invoke proxyInvocation().
4) Re-serializes the enhanced class definition to ABC bytecode.
5) Loads the enhanced bytecode in to the Flash Player/AVM

Then we run the tests. The bytecode loading in step #5 is currently asynchronous so I have to do this through an event listener, but the next item for me to write is a Loom SWF loader that will enhance bytecode on the fly, making it appear to the app as if the proxy definitions were added by the compiler.

Without further ado, here’s the code. I’ll be releasing Loom as a private alpha this weekend (so touch base via the contact form on this blog if you want to be added to the list of testers), and a public beta will be made available once the SWF loader is finished.

BaseClass.as

package loom.template
{
  public class BaseClass
  {    
    public function BaseClass(constructorArg1 : String, constructorArg2 : String)
    {
      trace("BaseClass::constructor()");
    }
 
    public function methodCallOne(arg1 : String, arg2 : Number) : int
    {
      trace("BaseClass::methodCallOne()");
      return 100;
    }
 
    public function methodCallTwo(arg1 : String, arg2 : Number, arg3 : Object) : void
    {
      trace("BaseClass::methodCallTwo()");
    } 
 
    public function methodCallThree(arg1 : String, arg2 : Number) : String
    {
      trace("BaseClass::methodCallThree()");
      return "stringValue";
    }
  }
}

Quick n’ Dirty Test Demonstrating Loom Weaving a Dynamic Subclass

package loom.util
{
  import flash.events.Event;
  import flash.utils.describeType;
  import flash.utils.getDefinitionByName;
 
  import loom.TestConstants;
  import loom.abc.AbcFile;
  import loom.loom_namespace;
  import loom.swf.AbcClassLoader;
  import loom.template.BaseClass;
  import loom.template.MethodInvocation;
 
  import net.digitalprimates.fluint.tests.TestCase;
 
  public class DynamicProxyFactoryTest extends TestCase
  {
    public function testCreateProxy() : void
    {
      var fixture : DynamicProxyFactory = new DynamicProxyFactory();
      var proxyFile : AbcFile = new AbcDeserializer(TestConstants.getBaseClassTemplate()).deserialize();
 
      // We enhance the loaded ABC file to save the work the compiler already did, and just add to/replace it
      fixture.createProxy(proxyFile);
 
      var classLoader : AbcClassLoader = new AbcClassLoader();
      classLoader.addEventListener(
        Event.COMPLETE,
        function (event : Event) : void
        {
          var classRef : * = getDefinitionByName("loom.template::DynamicSubClass");
          trace(describeType(classRef));
 
          // This would blow up if the types are not compatible
          var instance : BaseClass = new classRef(null, null);
          assertTrue(instance is BaseClass); // redundant, yet satisfying :)
 
          // Run original method invocation
          trace(">>> Original method invocation");
          trace(instance.methodCallOne("Hello Original Method Invocation!", -1));
 
          // Set up a handler
          instance.loom_namespace::setHandler(
            "methodCallOne",
            function (invocation : MethodInvocation) : *
            {
              trace("Before advice for: " + invocation);
 
              try
              {
                return invocation.proceed();
              }
              catch (e : Error)
              {
                trace("Throws advice for: " + e);
              }
              finally
              {
                trace("After advice for: " + invocation);
              }
            }
          );
          trace(">>> AOP method invocation");
          trace(instance.methodCallOne("Hello Dynamic Method Invocation!", 1234));
        }
      );
 
      // Load in the proxied class definition
      classLoader.loadClassDefinitionsFromBytecode([
       new AbcSerializer().serializeAbcFile(proxyFile)
      ]);
    }
  }
}

Output

<type name="loom.template::DynamicSubClass" base="Class" isDynamic="true" isFinal="true" isStatic="true">
  <extendsClass type="Class"/>
  <extendsClass type="Object"/>
  <accessor name="prototype" access="readonly" type="*" declaredBy="Class"/>
  <factory type="loom.template::DynamicSubClass">
    <extendsClass type="loom.template::BaseClass"/>
    <extendsClass type="Object"/>
    <constructor>
      <parameter index="1" type="String" optional="false"/>
      <parameter index="2" type="String" optional="false"/>
    </constructor>
    <method name="methodCallThree" declaredBy="loom.template::DynamicSubClass" returnType="String">
      <parameter index="1" type="String" optional="false"/>
      <parameter index="2" type="Number" optional="false"/>
    </method>
    <variable name="handlerMappings" type="flash.utils::Dictionary" uri="http://loom.ninjitsoft.com"/>
    <method name="setHandler" declaredBy="loom.template::DynamicSubClass" returnType="void" uri="http://loom.ninjitsoft.com">
      <parameter index="1" type="String" optional="false"/>
      <parameter index="2" type="Function" optional="false"/>
    </method>
    <method name="methodCallOne" declaredBy="loom.template::DynamicSubClass" returnType="int">
      <parameter index="1" type="String" optional="false"/>
      <parameter index="2" type="Number" optional="false"/>
    </method>
    <method name="methodCallTwo" declaredBy="loom.template::DynamicSubClass" returnType="void">
      <parameter index="1" type="String" optional="false"/>
      <parameter index="2" type="Number" optional="false"/>
      <parameter index="3" type="Object" optional="false"/>
    </method>
    <method name="proxyInvocation" declaredBy="loom.template::DynamicSubClass" returnType="*" uri="http://loom.ninjitsoft.com">
      <parameter index="1" type="loom.template::MethodInvocation" optional="false"/>
    </method>
  </factory>
</type>
BaseClass::constructor()
>>> Original method invocation
BaseClass::methodCallOne()
100
>>> AOP method invocation
Before advice for: methodCallOne(Hello Dynamic Method Invocation!, 1234)
BaseClass::methodCallOne()
After advice for: methodCallOne(Hello Dynamic Method Invocation!, 1234)
100

Post a Comment

Your email is never published nor shared. Required fields are marked *