You’ve probably seen built-in metadata tags like [SWF] and [Embed], but have you ever wondered how you could add your very own, custom tags? Today’s article shows you how to add custom annotations to your AS3 classes and methods. Mark old methods of your library with [Deprecated], file format classes with [Version], or anything else you’d like. Today’s article shows you how.

You can find out which metadata tags (a.k.a. annotations) are on a class by using flash.utils.describeType():

describeType(Classy);

Let’s try it on this class:

[Version(major=1,minor=2)]
class Classy
{
	private var printer:Function;
 
	public function Classy(printer:Function)
	{
		this.printer = printer;
	}
 
	[Important(priority=2)]
	public function foo(): void { printer("foo"); }
 
	public function goo(): void { printer("goo"); }
 
	[Important(priority=1)]
	public function bar(): void { printer("bar"); }
}

You’ll get back an XML object that looks like this:

<type name="FilePrivateNS:Annotations::Classy" base="Class" isDynamic="true" isFinal="true" isStatic="true">
  <extendsClass type="Class"/>
  <extendsClass type="Object"/>
  <accessor name="prototype" access="readonly" type="*" declaredBy="Class"/>
  <factory type="FilePrivateNS:Annotations::Classy">
    <extendsClass type="Object"/>
    <constructor>
      <parameter index="1" type="Function" optional="false"/>
    </constructor>
    <method name="goo" declaredBy="FilePrivateNS:Annotations::Classy" returnType="void"/>
    <method name="foo" declaredBy="FilePrivateNS:Annotations::Classy" returnType="void">
      <metadata name="Important">
        <arg key="priority" value="2"/>
      </metadata>
    </method>
    <method name="bar" declaredBy="FilePrivateNS:Annotations::Classy" returnType="void">
      <metadata name="Important">
        <arg key="priority" value="1"/>
      </metadata>
    </method>
    <metadata name="Version">
      <arg key="major" value="1"/>
      <arg key="minor" value="2"/>
    </metadata>
  </factory>
</type>

All of the interesting parts are under the <factory> element. That’s the same XML you’d get if you passed an instance of Classy to describeType like this:

describeType(new Classy(printer));

Here’s that XML:

<type name="FilePrivateNS:Annotations::Classy" base="Object" isDynamic="false" isFinal="false" isStatic="false">
  <extendsClass type="Object"/>
  <constructor>
    <parameter index="1" type="Function" optional="false"/>
  </constructor>
  <method name="goo" declaredBy="FilePrivateNS:Annotations::Classy" returnType="void"/>
  <method name="foo" declaredBy="FilePrivateNS:Annotations::Classy" returnType="void">
    <metadata name="Important">
      <arg key="priority" value="2"/>
    </metadata>
  </method>
  <method name="bar" declaredBy="FilePrivateNS:Annotations::Classy" returnType="void">
    <metadata name="Important">
      <arg key="priority" value="1"/>
    </metadata>
  </method>
  <metadata name="Version">
    <arg key="major" value="1"/>
    <arg key="minor" value="2"/>
  </metadata>
</type>

The metadata for the class itself is under the <metadata> element. Its name attribute is the name of the metadata tag (“Version”) and each parameter to it is listed as a sub-element: <arg>. Those are allowed any string key-value pairs with clearly-named attributes.

Metadata for the methods is stored along with them under the <method> elements. Each has the exact same sub-elements as with the class: <metadata> and <arg>.

Now let’s see what happens when we try to get the metadata of a method directly:

describeType(instance["foo"]);
<type name="builtin.as$0::MethodClosure" base="Function" isDynamic="false" isFinal="true" isStatic="false">
  <extendsClass type="Function"/>
  <extendsClass type="Object"/>
  <accessor name="length" access="readonly" type="int" declaredBy="Function"/>
  <accessor name="prototype" access="readwrite" type="*" declaredBy="builtin.as$0::MethodClosure"/>
</type>

The resulting XML object didn’t tell us anything about the method’s annotations. This means that it’s important to always pass the Class or class instance to describeType when looking for metadata/annotations.

Putting this all together, here’s a little app that retrieves and uses the metadata of the Classy object:

package
{
	import flash.display.*;
	import flash.utils.*;
	import flash.text.*;
	import flash.utils.*;
 
	public class Annotations extends Sprite
	{
		private var logger:TextField = new TextField();
		private function row(...cols): void
		{
			logger.appendText(cols.join(",") + "\n");
		}
 
		public function Annotations()
		{
			stage.align = StageAlign.TOP_LEFT;
			stage.scaleMode = StageScaleMode.NO_SCALE;
 
			logger.autoSize = TextFieldAutoSize.LEFT;
			addChild(logger);
 
			var instance:Classy = new Classy(row);
			var version:Object = getVersion(instance);
			row("Version: " + version.major + "." + version.minor);
			var importantMethods:Array = getImportantMethods(instance);
			for each (var method:Function in importantMethods)
			{
				method();
			}
		}
 
		private function getVersion(instance:*): Object
		{
			var desc:XML = describeType(instance);
			return {
				major: int(desc.metadata.arg.(@key=="major").@value),
				minor: int(desc.metadata.arg.(@key=="minor").@value)
			};
		}
 
		private function getImportantMethods(instance:*): Array
		{
			var methods:Array = [];
 
			var desc:XML = describeType(instance);
			for each (var methodNode:XML in desc.method)
			{
				if (methodNode.metadata.@name == "Important")
				{
					var func:Function = instance[String(methodNode.@name)];
					var priority:int = int(methodNode.metadata.arg.(@key=="priority").@value);
					methods.push({ func: func, priority: priority });
				}
			}
 
			return methods
				.sortOn("priority", Array.NUMERIC)
				.map(function(item:*, index:int, array:Array):Function{return item.func});
		}
	}
}
 
[Version(major=1,minor=2)]
class Classy
{
	private var printer:Function;
 
	public function Classy(printer:Function)
	{
		this.printer = printer;
	}
 
	[Important(priority=2)]
	public function foo(): void { printer("foo"); }
 
	public function goo(): void { printer("goo"); }
 
	[Important(priority=1)]
	public function bar(): void { printer("bar"); }
}

The version is printed first and then the methods marked [Important] are called in order of priority.

Version: 1.2
bar
foo

Finally, there are a couple of caveats to mention. All metadata/annotations you want to keep need to be explicitly specified when you compile your SWF. This allows for metadata to be stripped out for different build configurations such as “debug” or “release”. Here’s how:

mxmlc -keep-as3-metadata Version -keep-as3-metadata Important Annotations.as

Also, keep in mind that describeType is quite slow and its results should probably be cached if you’re going to use them more than once. It should certainly be avoided in any performance-critical code.

Spot a bug? Have a question or suggestion? Post a comment!