Introduction – as3-commons-eventbus

In a de-coupled environment, its not always easy to let various components communicate with each other. In order to make this task slightly easier AS3Commons offers the EventBus.

The EventBus is used as a publish/subscribe event mechanism that lets objects communicate with eachother in a loosely coupled way.

A dependency injection mechanism can be used to inject a shared EventBus instance into the necessary application components.

Multiple EventBus instances can operate independently of each other or can optionally be chained to allow for inter-application or -module communication.

EventBus listeners

There are a number of different ways to subscribe to events that are dispatched through the EventBus:

Global listeners using the IEventBusListener interface

The first is to listen to all events that are dispatched through the EventBus. This is possible through the EventBus.addListener() method. This method expects an IEventBusListener instance as an argument. The IEventBusListener interface looks like this:

public interface IEventBusListener {

 function onEvent(event:Event):void;

}

Every event dispatched by the EventBus will be passed into the onEvent method.

eventBus.addListener(listener);

Event type listeners

The second method is to only listen for events of a specific type. Use the EventBus.addEventListener() method for this task. The addEventListener() method expects two arguments, the first is a string representing the event type, and the second is a Function instance which will be invoked after every event dispatch of the specified type.

eventBus.addEventListener("myCustomEvent", listenerFunction);

Instead of a Function it is also possible to supply a proxy instead. This is what the addEventListenerProxy() method is for. Instead of a Function this expects a MethodInvoker instance. The MethodInvokerclass is part of the as3-commons-reflect project.

var methodInvoker:MethodInvoker = new MethodInvoker();
methodInvoker.target = someInstance;
methodInvoker.method = "listenerMethodName";
eventBus.addEventListenerProxy("myCustomEvent", methodInvoker);

Event class listeners

The last option is to add a listener for events of a certain class. To get this to happen use the addEventClassListener() or addEventClassListenerProxy() methods. The same arguments apply to these as for their addEventListener() and addEventListenerProxy() neighbours, except they expect a Class instance instead of a type.

eventBus.addEventClassListener(MyCustomEvent, listenerFunction);

Removing listeners

All these methods naturally have a removal counterpart: removeListener(), removeEventListener(), removeEventListenerProxy(), removeEventClassListener() and removeEventClassListenerProxy().

To clear all types of registered eventlisteners at once simply call the removeAll() method.

Adding weak listeners

Each add*() method has an extra optional argument called useWeakReference that defaults to false. Setting this to true determines that the event listener will be added using a weak reference, which will make it eligible for garbage collection if no other non-weak references exist for it anymore.

eventBus.addListener(listener, true)

Filtering events using topics

Each add*() and remove*() method has one last extra optional argument: topic:Object.

By setting this argument to a non-null value determines that the listener will only be invoked if an event associated with the same topic is dispatched.

In the simplest case a topic is just a String:

eventBus.addListener(eventBusListener, false, "myTopic");

This topic however can also be a complex object, in which case the topic can be used as a specific context.

For instance, the object could be a security token in which case a listener can only be added for a certain secure events if it has received this token instance.

EventBus dispatchers

This is the easiest part, to dispatch an event through anEventBusinvoke either the dispatchEvent() or dispatch() methods. The former expects an Event instance while the latter expects a string that indicates a certain type of Event. This event will be created by this method and subsequently dispatched. For example:

eventBus.dispatchEvent(new MyCustomEvent("myCustomEventType"));

or:

eventBus.dispatch("myCustomEventType");

Dispatching events using topics

The dispatchEvent() and dispatch() methods have an optional topic argument, like the add*() and remove*() methods. Setting this topic will associate the dispatched event with the specified topic.

eventBus.dispatchEvent(new MyCustomEvent("myCustomEventType"), "myTopic");

EventBus event interceptin

To prevent events from passing through the EventBus, or to change their properties before they do, an IEventBus implementation accepts IEventInterceptor registrations.

This process is almost the same as for event listeners: Interceptors can be added globally, for specific event types or specific classes and for specific topics.

eventBus.addInterceptor(interceptor);
eventBus.addEventInterceptor("myCustomEvent", interceptor);
eventBus.addEventClassInterceptor(MyCustomEvent, interceptor);
//And for topic specific interception:
eventBus.addInterceptor(interceptor, "myTopic");
eventBus.addEventInterceptor("myCustomEvent", interceptor, "myTopic");
eventBus.addEventClassInterceptor(MyCustomEvent, interceptor, "myTopic");

The IEventInterceptor interface is a very simple one and looks like this:

public interface IEventInterceptor extends IEventBusAware {
	function get blockEvent():Boolean;
	function set blockEvent(value:Boolean):void;
	function intercept(event:Event):void;
}

The intercept() method will be invoked for each event that passes through the EventBus and that adheres to the registration parameters of the IEventInterceptor.

To prevent the event to continue to be dispatched the blockEvent property can be set to true.

An IEventInterceptor also receives a reference to the current EventBus each time its intercept() method is invoked, this allows an implementation to block certain events and replace them with a different instance. Events could be split up into multiple events, etc.

AS3Comons-eventbus offers the AbstractEventInterceptor to be used as a general base class for event interceptors.

EventBus event listener intercepting

To log, modify or block event listeners from being added an IEventListenerInterceptor can be registered with the EventBus.

This process is almost the same as for event listeners and interceptors: Listener interceptors can be added globally, for specific event types or specific classes and for specific topics.

eventBus.addListenerInterceptor(interceptor);
eventBus.addEventListenerInterceptor("myCustomEvent", interceptor);
eventBus.addClassListenerInterceptor(MyCustomEvent, interceptor);
//And for topic specific interception:
eventBus.addListenerInterceptor(interceptor, "myTopic");
eventBus.addEventListenerInterceptor("myCustomEvent", interceptor, "myTopic");
eventBus.addClassListenerInterceptor(MyCustomEvent, interceptor, "myTopic");

To prevent the listener from being added, simply set the blockListener property to true.

An IEventListenerInterceptor also receives a reference to the current EventBus each time its interceptListener() or interceptListenerProxy() method is invoked, allowing the interceptor to examine the EventBus before deciding to allow the listener to be added or not.

AS3Comons-eventbus offers the AbstractEventListenerInterceptor to be used as a general base class for eventlistener interceptors.

Static EventBus implementation

In a select few cases it might be easier to have one static eventbus in an application, for this purpose as3-commons-eventbus offers the StaticEventBus.

The StaticEventBus has static equivalents of all the IEventBus methods, so its usage is exactly the same.

EventBus chaining

The EventBus itself is also an implementation of IEventBusListener. Therefore it is possible to chain multiple EventBus instances by adding an EventBus as a global listener to another EventBus.

In that case every event dispatched by the EventBus will be redispatched by the listening EventBus.

var eventBus1:EventBus = new EventBus();
var eventBus2:EventBus = new EventBus();
eventBus1.addListener(eventBus2, true);
eventBus1.dispatchEvent(new MyCustomEvent());
//MyCustomEvent will now also be dispatched through eventBus2...

Welcome to as3-commons-eventbus

The EventBus is utilized as a publish/subscribe event device that lets things communicate with eachother in a loosely combined way.

It offers event registration based upon occasion name, event class or for particular topics. An interception mechanism is in place to block or change incoming events.

A similar interception device is in area to obstruct occasion listeners, plus event postprocessing for executing reasoning after an event has actually been sent out to its audiences.

The collection is thoroughly examined as well as is being made use of in the Spring Actionscript task.

Welcome to as3-commons-async

AS3Commons-ASync is a pure actionscript 3 collection that aims to earn collaborating with asynchronous procedures much easier by abstracting numerous different executions encountered in the Flash setting away behind a few easy-to-understand interfaces.

This collection used to be an integral part of Spring Actionscript, but has actually given that been released as an independent API.

The collection is extensively checked as well as is being made use of in the Spring Actionscript job.

Welcome to as3-commons-logging

AS3Commons-logging is an open-source library giving an abstraction over logging framework executions. Being a pure actionscript collection it can be utilized for any kind of Flash/Flex/AIR job. Its use is advised for usage with other libraries/framework trying to be logging framework agnostic.

AS3-commons-logging

The library provides an usual interface to logger creation and also logging as well as it gives some fundamental loggers executions:

  • a default logger utilizing trace().
  • a null logger that can be made use of to disregard logging.
  • a flex logger (mx.logging.ILogger) application.

Various other implementations could be developed by users for any other logging framework.

Introduction to the emit API

As3commons-bytecode offers an API that enables a developer to generate classes at run-time.
All the necessary classes for this functionality can be found in the org.as3commons.bytecode.emit.* package.
This API can form the basis for mocking or run-time proxy libraries.

Sections

  • Generating class
  • Generating properties
  • Generating getters and setters
  • Generating methods
  • Defining method bodies
  • Defining method bodies using a string source
  • Generating a constructor
  • Generating metadata
  • Generating interfaces
  • Generating namespaces
  • Loading the generated classes into the AVM
  • Exporting the generated classes to file

Generating classes

The main entry point for run-time class generation is the IAbcBuilder interface.
The AbcBuilder class is an implementation of this interface and accepts an optional AbcBuilder instance for its constructor. When no AbcBuilder is passed in it will create a new AbcBuilder instance during its build process, otherwise the specified AbcBuilder will be used to add the generated classes and interfaces to.

Below is an example of a simple run-time class.
Before defining a class there first needs to be a package definition:

var abcBuilder:IAbcBuilder = new AbcBuilder();
var packageBuilder:IPackageBuilder = abcBuilder.definePackage("com.classes.generated");

Once the package has been defined, a class can be defined:

var classBuilder:IClassBuilder = packageBuilder.defineClass("RuntimeClass");

A super class can also be defined, for this a fully qualified name is required:

var classBuilder:IClassBuilder = packageBuilder.defineClass("RuntimeByteArraySubClass","flash.utils.ByteArray");

To have the generated class implement one or more interfaces use the IClassBuilder.implementInterface() method, the interface names are assumed to be fully qualified names:

classBuilder.implementInterface("flash.events.IEventDispatcher");

Note: Invoking implementInterface() does *not* automatically add the required methods to the class, this needs to be done manually.Other properties that can be set on the IClassBuilder include:

  • isDynamic
  • isFinal

The actionscript source equivalent of the above statements would look like this:

package com.classes.generated {
  public class RuntimeByteArraySubClass extends ByteArray implements IEventDispatcher {
  }
}

Generating properties

Next step is defining properties on the class. Use the defineProperty() method on the IClassBuilder instance for this:

var propertyBuilder:IPropertyBuilder = classBuilder.defineProperty("name","String");

The first argument is the name of the property, the second is the type, this always needs to be a fully qualified name.
To define a default value for the property add a third argument:

var propertyBuilder:IPropertyBuilder = classBuilder.defineProperty("name","String","defaultName");

The actionscript source equivalent of those statements would look like this:

public var name:String = "defaultName";

Properties with complex types can have their initial values defined by setting the memberInitialization property to a valid MemberInitializationinstance.

To simply have the class instantiated all that is needed is a new MemberInitialization instance:

propertyBuilder.memberInitialization = new MemberInitialization();

When the constructor needs one or more constructor arguments, add them using the constructorArguments property:

propertyBuilder.memberInitialization = new MemberInitialization();
propertyBuilder.memberInitialization.constructorArguments = ["myString", true, 100];

Attention: Only constructor arguments of a simple type are supported (i.e. String, Number, etc).By default the generated property will be public, to change this use the IPropertyBuilder.visibility property:

propertyBuilder.visibility = MemberVisibility.PRIVATE;

To assign a custom namespace to the property, assign the specified namespace URL to the IPropertyBuilder.namespace property and the name to the IPropertyBuilder.scopeName property:

propertyBuilder.namespace = "http://www.somedomain.com/customnamespace";
propertyBuilder.scopeName = "my_custom_namespace";

This assumes that the namespace would have been declared like this:

public namespace my_custom_namespace = "http://www.somedomain.com/customnamespace";

Other properties that can be set on the IPropertyBuilder include:

  • isOverride
  • isFinal
  • isStatic
  • isConstant

Generating getters and setters

Getters and setters, also known as accessors, are added using the defineAccessor() method, its similar to the defineProperty() method:

var accessorBuilder:IAccessorBuilder = classBuilder.defineAccessor("count","int",100);

The actionscript source equivalent of this statement would look like this:

private var _count:int = 100;

public function get count():int {
  return _count;
}

public function set count(value:int):void {
  _count =  value;
}

By default the accessor will be read/write, this can be changed using the access property on the IAccessorBuilder interface:

accessorBuilder.access = AccessorAccess.READ_ONLY;

By default a private property will be generated with a name in the format “_” + accessorName.
This private property is used to store the actual value of the accessor.

To use a custom IPropertyBuilder for this create one and assign it to the IAccessorBuilder.property property:

accessorBuilder.property = new PropertyBuilder("count","int",1000);

Overriding accessor body creation

To completely override the creation of the getter and setter method bodies add listeners for the AccessorBuilderEvent.BUILD_GETTER and AccessorBuilderEvent.BUILD_SETTER events.
When either of these events are handled the IAccessorBuilder will delegate the creation of the IMethodBuilder instances to the event handlers assigned to these events.
In these event handlers logic can be placed to create a valid IMethodBuilder and its method body, after creation such an instance needs to be assigned to the AccessorBuilderEvent.builder property.

accessorBuilder.addEventLister(AccessorBuilderEvent.BUILD_GETTER, buildGetterHandler);
public function buildGetterHandler(event:AccessorBuilderEvent):void {
	var methodBuilder:IMethodBuilder = new MethodBuilder():
	//logic ommitted...
	event.builder = methodBuilder;
}

Visibility and namespace assignment is the same as for property generation.

Other properties that can be set on the IAccessorBuilder include:

  • isOverride
  • isFinal
  • isStatic

Generating methods

The IMethodBuilder interface supplies the required API to start adding methods to a generated class.

To receive an instance of this interface invoke the defineMethod() on the IClassBuilder instance:

var methodBuilder:IMethodBuilder = classBuilder.defineMethod("multiplyByHundred");

Setting visibility and namespaces on the method works the same way as for properties and accessors.

To define method arguments invoke the defineArgument() method:

var argument:MethodArgument = methodBuilder.defineArgument("int");

Further properties for an argument are isOptional and defaultValue, their names are self-explanatory.

By default a generated method has a return type of void, to change this set the returnType property:

methodBuilder.returnType = "int";

Defining method bodies

To add a method body a certain knowledge about AVM opcodes is required:

methodBuilder.addOpcode(Opcode.getlocal_0)
             .addOpcode(Opcode.pushscope)
             .addOpcode(Opcode.getlocal_1)
             .addOpcode(Opcode.pushint, [100])
             .addOpcode(Opcode.multiply)
             .addOpcode(Opcode.setlocal_1)
             .addOpcode(Opcode.getlocal_1)
             .addOpcode(Opcode.returnvalue);

For more information on AVM instructions follow this link: AVM2 instructions.

The actionscript source equivalent of these statements would look like this:

public function multiplyByHundred(value:int):int {
  value = (value * 100);
  return value;
}

To define jumps between opcodes, for use in if statements for example, use the defineJump method.

Let’s add an extra boolean argument to the method which will determine if the given integer value will be multiplied by a hundred or a thousand:

methodBuilder.defineArgument("Boolean");

And then change the method body generation like this:

var iffalse:Op = new Op(Opcode.iffalse,[0]);
var jump:Op = new Op(Opcode.jump,[0]);

methodBuilder.addOpcode(Opcode.getlocal_0)
             .addOpcode(Opcode.pushscope)
             .addOpcode(Opcode.getlocal_2)
             .addOp(iffalse)
             .addOpcode(Opcode.getlocal_1)
             .addOpcode(Opcode.pushbyte,[100])
             .addOpcode(Opcode.multiply)
             .addOpcode(Opcode.convert_i)
             .addOpcode(Opcode.setlocal_1)
             .addOp(jump)
             .defineJump(iffalse, new Op(Opcode.getlocal_1))
             .addOpcode(Opcode.pushshort,[1000])
             .addOpcode(Opcode.multiply)
             .addOpcode(Opcode.convert_i)
             .addOpcode(Opcode.setlocal_1)
             .defineJump(jump, new Op(Opcode.getlocal_1))
             .addOpcode(Opcode.returnvalue);

The actionscript source equivalent of these statements would look like this:

public function multiplyByHundred(value:int, reallyDoIt:Boolean):int {
  if (reallyDoIt) {
    value = value * 100;
  } else {
    value = value * 1000;
  }
  return value;
}

Defining method bodies using a string source

A second way of adding the method body is by defining the assembly source fully as a string:

var source:String = (<![CDATA[
		     getlocal_0
		     pushscope
		     getlocal_2
		     iffalse L0
		     getlocal_1
		     pushbyte 100
		     multiply
		     convert_i
		     setlocal_1
		     jump L1
		     L0:
		     getlocal_1
		     pushshort 1000
		     multiply
		     convert_i
		     setlocal_1
		     L1:
		     getlocal_1
		     returnvalue
	]]>).toString();

methodBuilder.addAsmSource(source);

The format of the source is quite similar to the results found in a swfdump output.

The source needs to adhere to a few simple rules:

  • Each instruction and its operands need to be on a separate line.
  • Separate instructions and operands with spaces and/or tabs.
  • Labels are referenced by name. I.e. iffalse L1
  • Labels are defined by a name suffixed by a colon. I.e. L1:

Any exceptions referenced in the opcode collection can be added using the defineExceptionInfo() method:

var exceptionInfoBuilder:IExceptionInfoBuilder = methodBuilder.defineExceptionInfo();

Other properties that can be set on the IMethodBuilder include:

  • isOverride
  • isFinal
  • isStatic

Generating a constructor

By default a parameterless constructor is generated for the class, if a different constructor is needed invoke the defineConstructor() method and use the resulting ICtorBuilder instance to define it:

var ctorBuilder:ICtorBuilder = classBuilder.defineConstructor();

The ICtorBuilder instance works the same as an IMethodBuilder instance with the exception that any returnType or visibility assignments will be ignored.

Multiple calls to defineConstructor() will yield the same ICtorBuilder instance.

Generating metadata

Methods, properties, accessors and classes themselves can be annotated with metadata using an IMetadataBuilder instance:

var metadataBuilder:IMetadataBuilder = methodBuilder.defineMetadata("Inject");

To add arguments to the metadata definition invoke the defineArgument() method:

var metadataArgument:MetadataArgument = metadataBuilder.defineArgument();
metadataArgument.name = "name";
metadataArgument.value = "objectName";

The actionscript source equivalent of this statement would look like this:

[Inject(name="objectName")]

Generating interfaces

Generating interfaces is mostly the same as generating classes, with the exception that constructors and properties cannot be defined and any method or accessor that is added will automatically receive public visibility.

var interfaceBuilder:IInterfaceBuilder = packageBuilder.defineInterface("RuntimeInterface");

If the interface needs to extend other interfaces pass a list of their fully qualified names to the constructor like this:

var interfaceBuilder:IInterfaceBuilder = packageBuilder.defineInterface("RuntimeInterface",["flash.events.IEventDispatcher"]);

The actionscript source equivalent of this statement would look like this:

package com.classes.generated {
  public interface RuntimeInterface extends IEventDispatcher {
  }
}

Generating namespaces

To generate a new namespace use the defineNamespace() method on the IPackageBuilder.

abcBuilder.addPackage("com.myclasses.namespaces").addNamespace('my_custom_namespace','http://www.mynamespaces.com/custom');

The actionscript source equivalent of this statement would look like this:

package com.myclasses.namespaces {
  public namespace my_custom_namespace = "http://www.mynamespaces.com/custom";
}

Loading the generated classes into the AVM

Finally, after defining the necessary classes and interfaces they need to be loaded into the AVM so they can be instantiated.
There are two ways of doing this, the shortest way is using the buildAndLoad() method on the IAbcBuilder instance.

The process of loading class definitions is asynchronous so first these event listeners need to be added:

abcBuilder.addEventListener(Event.COMPLETE, loadedHandler);
abcBuilder.addEventListener(IOErrorEvent.IO_ERROR, errorHandler);
abcBuilder.addEventListener(IOErrorEvent.VERIFY_ERROR, errorHandler);

After that invoke the buildAndLoad() method:

abcBuilder.buildAndLoad();

In the loadedHandler method a reference to a generated class can be retrieved and instantiated like this:

function loadedHandler(event:Event):void {
  var clazz:Class = ApplicationDomain.currentDomain.getDefinition("com.classes.generated.RuntimeClass") as Class;
  var instance:Object = new clazz();
  var i:int = instance.multiplyByHundred(10);
  // i == 1000
}

The buildAndLoad() method has two optional arguments:

public function buildAndLoad(applicationDomain:ApplicationDomain = null, newApplicationDomain:ApplicationDomain = null):AbcFile;

The first ApplicationDomain argument will be the domain that contains any class definitions that are used as superclasses in the generated classes. This domain will be used to retrieve reflection information about the superclasses needed by the class generation process. If no ApplicationDomainis passed then ApplicationDomain.currentDomain will be used.

The second ApplicationDomain argument will be used to load the generated classes into. Again, when no ApplicationDomain is passed then ApplicationDomain.currentDomain will be used.

The second way to load the generated classes is like this:

var abcFile:AbcFile = abcBuilder.build();
var abcLoader:AbcClassLoader = new AbcClassLoader();

abcLoader.addEventListener(Event.COMPLETE, loadedHandler);
abcLoader.addEventListener(IOErrorEvent.IO_ERROR, errorHandler);
abcLoader.addEventListener(IOErrorEvent.VERIFY_ERROR, errorHandler);

abcLoader.loadAbcFile(abcFile);

Exporting the generated classes to file

If there is a need to persist the generated classes to disk, follow these steps:

var binarySwf:ByteArray = abcBuilder.buildAndExport();
var file:FileReference = new FileReference();
file.save(binarySwf, "MyGeneratedClasses.swf");

Introduction

The 2 most important classes in this library are the Type and ClassUtils classes. The Type class will give you all the information of a class while the ClassUtils class contains utility methods for working with class objects.

Using as3commons-reflect is really easy, after reading this short introduction the only thing you might need is having a look at the API to navigate through the properties and methods exposed by the library. Here below we give some short instructions on how to use the library.

Assuming you have a class called Person in the package com.company.domain) you want to use reflection on, you start by getting its “Type” in one of the following methods:

By class…

var type:Type = Type.forClass(Person);

… by instance

var person:Person = new Person();
var type:Type = Type.forInstance(person);

… or by class name…

var type:Type = Type.forName(“com.company.domain.Person”);
// or var type:Type = Type.forName(“com.company.domain::Person”);

Once you have a type instance you can get access to all variables, constants, accessors (getters/setters) and methods defined by that type and the properties of the type:

type.accessors
type.constants
type.fields
type.fullName
type.isDynamic
type.isFinal
type.isStatic
type.methods
type.name
type.staticConstants
type.staticVariables
type.variables
method:Method = type.methods[0];
retType:Type = method.getReturnType();
method.invoke(object, [arg1, arg2]);
accessor:Accessor = type.accessors[0];
metadata:Metadata = accessor.getMetadata(“myMetadata”);
arg:MetadataArgument = metadata.getArgument(“myArg”);

Welcome to as3-commons-reflect

AS3Commons-reflect depends on the AS3Commons-lang library, you can download it here.

AS3Commons-reflect is an open-source library providing a reflection API for ActionScript 3.0. Being a pure actionscript library it can be used for any Flash/Flex/AIR project.

AS3-commons-reflect

The library provides clean, easy and complete introspection on class variables, accessors, methods, metadata etc. etc. by parsing the xml returned by flash.utils.describeType. Parsing results are cached so that possibly resource expensive parsing won’t occur next time for the same class.

When Flash player 10.1 or higher is installed, instead of using the native describeType method the new describeTypeJSON method is used to retrieve the type information.

This yields a significant speed improvement.

Metadata, MetadataArgument, Accessor, Constant, Variable and Parameter arguments are immutable. So, for instance, if the Bindable metadata is used on two different classes and/or members AS3Commons-reflect will return the same Metadata instance in order to preserve memory.

The library is thoroughly tested and is being intensively used for the Spring Actionscript project.