Introduction

AS3Commons-metadata aims to make it easier for developers to work with custom metadata annotations in actionscript source code.

There are two main interfaces in this library:

  • IMetadataProcessorRegistry - A centralized registry for IMetadataProcessors that can decide which processors will be used for which classes or instances.
  • IMetadataProcessor - Implementations that contain the actual logic that will be associated with certain metadata annotations.

The IMetadataProcessorRegistry interface looks like this:

public interface IMetadataProcessorRegistry {
	function addProcessor(processor:IMetadataProcessor):void;
	function getProcessorsForMetadata(metadataName:String):Vector.<IMetadataProcessor>;
	function process(target:Object, params:Array=null):*;
	function removeProcessor(processor:IMetadataProcessor):void;
}

The addProcessor() and removeProcessor() methods speak mostly for themselves. They register or unregister a specified IMetadataProcessor with the registry.
Once any processors have been added the IMetadataProcessorRegistry will be able to invoke these processors on any object instances or classes passed to its process() method.
The process() method can also receieve an optional Array of extra parameters which will be passed into each IMetadataProcessor.process() invocation.
The IMetadataProcessorRegistry implementation will require some kind of mechanism to examine the specified objects for the existence of metadata. AS3Commons-metadata provides three reference implementations that use AS3Commons-reflect, Spicelib Reflect and Flash's native describeType() method do perform this task.

The IMetadataProcessor interface itself is even smaller:

public interface IMetadataProcessor {
	function get metadataNames():Vector.<String>;
	function canProcess(metadataName:String):Boolean;
	function process(target:Object, metadataName:String, params:Array=null):*;
}

The metadataNames property indicates one or more metadata annotations that this prcoessor needs to be associated with.
If the decision process is slightly more complex, the canProcess() method may be used by an IMetadataProcessorReistry implementation as well, perhaps the ability to perform its task by an IMetadataProcessor is depenendent on some kind of application state. In that case the canProcess() offers some greater flexibility.

The process() method contains the actual business logic that is associated with the metadata annotations. The first argument is the instance that is annotated with the metadata that is specified by the second argument. The third argument is optional and may be used by an IMetadataProcessorRegistry to pass extra parameters to each IMetadataProcessor. For example, the AS3ReflectMetadataProcessorRegistry and BytecodeMetadataProcessorRegistry will pass in a Type, Method, Property, Accessor or Constant instance. The SpicelibMetadataProcessorRegistry will pass in either a ClassInfo, Method or Property instance.
The DescribeTypeMetadataProcessorRegistry simply passes in the XML node containing the reflection data for the specific member or class that contains the annotation.

IMetadataProcessor example

Let's say certain classes in a project have been annotated with custom [Validated] metadata, each of these instances need to be fed into a ValidationManager instance upon instantiation.

The metadata procesor responsible for doing so could be implemented like this:

public class ValidatedMetadataProcessor extends AbstractMetadataProcessor {
	private var _validationManager:IValidationManager;

	public function ValidatedMetadataProcessor(validationManager:IValidationManager) {
		super();
		_validationManager = validationManager;
		metadataNames[metadataNames.length] = "Validated";
	}
			
	public function process(target:Object, metadataName:String, params:Array=null):* {
		_validationManager.register(instance);
	}
}

The example extends the AbstractMetadataProcessor convenience base class provided by as3commons-metadata. But it is, of course, not required to extend this.

Another example would be a processor that replaces registered class aliases with an alternative class. The implementation of such a procesor could look like this:

public class RemoteClassReplacerMetadataProcessor extends AbstractMetadataProcessor {

	public function RemoteClassReplacerMetadataProcessor() {
		super();
		metadataNames[metadataNames.length] = "RemoteClass";
	}
			
	public function process(target:Object, metadataName:String, params:Array=null):* {
		var classAlias:String = getClassAlias();
		var alternativeClass:Class = getAlternativeClass(classAlias);
		registerClassAlias(classAlias, alternativeClass);
	}
	
	protected function getClassAlias():String {
		//logic ommitted
	}

	protected function getAlternativeClass():Class {
		//logic ommitted
	}
}

Reference implementations

AS3Commons-metadata offers a few IMetadataProcessorRegistry reference implementation. The AS3Commons team encourages developers to create their own though.
The AS3ReflectMetadataRegistry uses the AS3Commons-reflect library to determine the various bits of metadata that may or may not be part of a speciic class.
When metadata is encountered that has been registered in the AS3ReflectMetadataRegistry it will invoke the process() method on each IMetadataProcessor that is associated with this metadata.

The SpicelibMetadataRegistry is very similar, the only difference is that internally it uses the Spicelib Reflect library to retrieve the class information.
These two implementations are meant to show how the reflection logic can be abstracted and how easily a reflection library can be swapped out.
For completion sake, the DescribeTypeMetadataRegistry is also offered. This implementation doesn't depend on any thrid-party reflection library but simple uses the native describeType() output to retrieve and examine the class information.

Lastly, there is the BytecodeMetadataRegistry, this showcases how metadata processing doesn't necessarily need to be done on instances, but can be done on classes as well.
This implementation uses the ByteCodeType.metaDataLookup property to find any classes that have been annotated with metadata that has been registred in the BytecodeMetadataRegistry.
If one or more class names have been found it invokes its IMetadataProcessors as usual, only now it will pass in a Class object into their process() methods instead of an instance of that class.
This implementation assumes that before its execute() method is invoked BytecodeType.metadataLookupFromLoader() has been invoked.

Working with IMetadataProcessorRegistries

Here's a very simple example of how to integrate an IMetadataProcessorRegistry instance into an application. Suppose you have an object factory that is responsible for creating and configuring certain types.
This could look something like this:

public class MyClassFactory extends ClassFactory {
	
	private var _metadataRegistry:IMetadataProcessorRegistry;

	public function MyClassFactory() {
		super();
	}

	public function get metadataRegistry():IMetadataProcessorRegistry {
		return _metadataRegistry;
	}

	public function set metadataRegistry(value:IMetadataProcessorRegistry):void {
		_metadataRegistry = value;
	}

	override public function newInstance():* {
		var instance:MyComplexClass = new MyComplexClass();
		//creation logic omitted...
		_metadataRegistry.process(instance);
		return instance;
	}
}

So, after creation the instance will be examined by the IMetadataProcessorRegistry and processed accordingly. Obviously this is a very simplified example, but hopefully it shows off how to combine an IMetadataProcessorRegistry with other application logic.

Another usage example would be to inject an IMetadataProcessorRegistry into a IStageObjectProcessor, as defined in the AS3Commons-stageprocessing library.
Such a stage processor could look like this:

public class MetadataStageObjectProcessor implements IStageObjectProcessor {

	private var _metadataRegistry:IMetadataProcessorRegistry;

	public function MetadataStageObjectProcessor() {
		super()
	}

	public function get metadataRegistry():IMetadataProcessorRegistry {
		return _metadataRegistry;
	}

	public function set metadataRegistry(value:IMetadataProcessorRegistry):void {
		_metadataRegistry = value;
	}

	public function process(displayObject:DisplayObject):DisplayObject {
		_metadataRegistry.process(displayObject);
		return displayObject;
	}
}

So in this case each display object that gets added to the stage (and is approved by the StageObjectProcessorRegistry's IObjectSelector) will be processed by the IMetadataProcessorRegistry.

And that is all there is to it!

The GenericMetadaProcessor

To avoid having to implement the IMetadataProcessor for all processors (perhaps there are existing processors that you do not have source code access to) as3commons-metadata provides the GenericMetadataProcessor.
This is an IMetadataProcessor implementation that is capable of wrapping an arbitrary object that is capable of processing metadata.
The constructor for this GenericMetadataProcessor looks like this:

function GenericMetadataProcessor(wrappedMetadataProcessor:Object, methodName:String="process", applicationDomain:ApplicationDomain=null, namespace:String=null)

The first parameter is, naturally, the object that will be wrapped by the GenericMetadataProcessor, the second parameter is the method that will invoked when the GenericMetadataProcessor.process() method is invoked.
The third parameter is an optional ApplicationDomain that the wrapped object might live in. It defaults internally to the current domain. And the last parameter is an optional custom namespace for the wrapped method.

The metadata names that the wrapped object should be invoked for can then be added to the GenericMetadataProcessor.metadataNames property.

Internally the GenericMetadataProcessor will inspect the wrapped object's process method signature and look for parameters of type Object, String and Array. Not all of these parameters need to exist. If the given method only expects an Object then only this parameter will be used during invocation.

The MetadataMetadaProcessor

As an addition to the GenericMetadataProcessor there is also the MetadataMetadaProcessor. This is an IMetadataProcessor implementation that inspects classes that have been annoated with the [MetadataProcessor] metadata.

It looks for the [MetadataProcessor(metadataNames='MyMetadata,MyOtherMetadata')] argument to determine which metadata will trigger this processor. By default it will assume that the process method is called 'process'. If this is not the case then the correct name can be added like this: [MetadataProcessor(metadataNames='MyMetadata,MyOtherMetadata', methodName='myCustomProcessMethod')].

The MetadataMetadaProcessor holds a reference to an IMetadataProcessorRegistry, when it is invoked for an object that is annotated with the [MetadataProcessor] metadata it will create a GenericMetadataProcessor for the given instance and register this with its IMetadataProcessorRegistry.