Skip to content

Commit

Permalink
initial code commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Jeremy Saenz committed Mar 15, 2011
1 parent e74ef65 commit a881935
Show file tree
Hide file tree
Showing 8 changed files with 312 additions and 0 deletions.
11 changes: 11 additions & 0 deletions examples/AnotherExampleClass.as
@@ -0,0 +1,11 @@
package
{
public class AnotherExampleClass
{
public var name:String = "AnotherClassInstance";

public function AnotherExampleClass()
{
}
}
}
11 changes: 11 additions & 0 deletions examples/ExampleClass.as
@@ -0,0 +1,11 @@
package
{
public class ExampleClass
{
public var name:String = "ExampleClassInstance";

public function ExampleClass()
{
}
}
}
44 changes: 44 additions & 0 deletions examples/ImbueExample.as
@@ -0,0 +1,44 @@
package
{
import com.zaalabs.imbue.IInjector;

import com.zaalabs.imbue.Injector;

import flash.display.Sprite;

public class ImbueExample extends Sprite
{

[Inject] // this will inject an instance of ExampleClass into this property
public var myFirstInjectedInstance:ExampleClass;

[Inject] // this will inject an instance of AnotherExampleClass into this property
public var mySecondInjectedInstance:AnotherExampleClass;

public function ImbueExample()
{
// create an instance of the classes we want to inject
var myLocalMember:ExampleClass = new ExampleClass();
var anotherLocalMember:AnotherExampleClass = new AnotherExampleClass();

// This is actually all it takes to inject something
var injector:IInjector = new Injector();
injector.mapValue(myLocalMember, ExampleClass);
injector.mapValue(anotherLocalMember, AnotherExampleClass);
// apply the injections!
injector.apply(this);
}

[PostInject]
// This gets called after an injection has occurred on the target
public function onPostInject():void
{
trace("We are all done injecting, now we can use our instance!");
trace("Name of first injected instance:",myFirstInjectedInstance.name);
trace("Name of second injected instance:",mySecondInjectedInstance.name)
}

}
}


34 changes: 34 additions & 0 deletions src/com/zaalabs/imbue/IInjector.as
@@ -0,0 +1,34 @@
package com.zaalabs.imbue
{
/**
* IInjector is an interface that specifies a simple Dependency
* Injection API.
*
* Features in this simple DI framework include metadata based
* injections.
*
*/
public interface IInjector
{
/**
* Maps a value to a certain class, so when <code>apply()</code>
* is called, the value will be injected into any property with the
* [Inject] tag over it and that matches the class definition specified
* in the <code>toClass</code> parameter.
*
* @param value The dependency that will be injected
* @param toClass The class that the dependency will be injected into
*/
function mapValue(value:Object, toClass:Class):void;

/**
* Applies the injection to the specified object. If you wish to have a
* property injected into the specified object, make sure that an [Inject]
* metadata tag is added to the desired target property and the value is
* mapped in this injector using the <code>mapValue()</code> method.
*
* @param value the object that will be injected
*/
function apply(value:Object):void;
}
}
9 changes: 9 additions & 0 deletions src/com/zaalabs/imbue/InjectionTarget.as
@@ -0,0 +1,9 @@
package com.zaalabs.imbue
{
public class InjectionTarget
{
public var property:String;
public var definition:Class;
public var uri:String;
}
}
164 changes: 164 additions & 0 deletions src/com/zaalabs/imbue/Injector.as
@@ -0,0 +1,164 @@
package com.zaalabs.imbue
{
import flash.utils.Dictionary;
import flash.utils.describeType;
import flash.utils.getDefinitionByName;

/**
* The Default implementation for the IInjector interface. It provides
* some basic functionality for getting an dependency injection system
* going. Nothing fancy or magical going on here, Pretty straightforward
*/
public class Injector implements IInjector
{

/**
* @inheritDoc
*/
public function mapValue(value:Object, toClass:Class):void
{
mappedValues[toClass] = value;
}

/**
* @inheritDoc
*/
public function apply(value:Object):void
{
var definition:Class = getConstructor(value);
var targets:Vector.<InjectionTarget> = getInjectionTargets(definition) as Vector.<InjectionTarget>;
var postConstruct:Vector.<PostInjectionTarget> = getPostConstructTargets(definition) as Vector.<PostInjectionTarget>;

// apply injections to each target
for each(var target:InjectionTarget in targets) {
applyToTarget(target,value);
}

if(targets.length)
{
// apply post construct
for each(var pcTarget:PostInjectionTarget in postConstruct)
{
var f:Function = value[pcTarget.method];
if(f != null) f();
}
}
}

//_____________________________________________________________________
// Constructor
//_____________________________________________________________________
public function Injector()
{
// Initialize the member properties
mappedValues = new Dictionary();
}

//_____________________________________________________________________
// Protected Properties
//_____________________________________________________________________
/**
* @private
*/
protected var mappedValues:Dictionary;

/**
* @private
*/
protected static var definitionCache:Dictionary = new Dictionary();

/**
* @private
*/
protected static var injectionTargetCache:Dictionary = new Dictionary();

/**
* @private
*/
protected static var postConstructTargetCache:Dictionary = new Dictionary();

//_____________________________________________________________________
// Protected methods
//_____________________________________________________________________
/**
* Returns InjectionTargets for the specified class, from either a cached
* instance or calculated on demand. This function also stores caches for
* DescribeType calls.
*
* Structuring calls this way makes the Injectors api more extendable by
* exposing our caching mechanisms and abstracting it.
*
* @param value
* @return
*/
public static function getInjectionTargets(value:Class):Vector.<InjectionTarget>
{
// Return it is we have it in the cache
if (injectionTargetCache[value])
{
return injectionTargetCache[value] as Vector.<InjectionTarget>;
}

// Look over the xml type definition and create injector targets
var result:Vector.<InjectionTarget> = new Vector.<InjectionTarget>();

// do a describe type if it is not in the cache
if (!definitionCache[value])
definitionCache[value] = describeType(value);

var definition:XML = definitionCache[value];
var target:InjectionTarget;

for each (var node:XML in definition.factory.*.(name() == 'variable' || name() == 'accessor').metadata.(@name == 'Inject'))
{
target = new InjectionTarget();
target.property = node.parent().@name;
target.definition = getDefinitionByName(node.parent().@type) as Class;
target.uri = node.parent().@uri;
result.push(target);
}

return result;
}

public static function getPostConstructTargets(value:Class):Vector.<PostInjectionTarget>
{
// try to find it in the cache
if(postConstructTargetCache[value])
{
return postConstructTargetCache[value] as Vector.<PostInjectionTarget>;
}

// Look over the xml type definition and create injector targets
var result:Vector.<PostInjectionTarget> = new Vector.<PostInjectionTarget>();

// do a describe type if it is not in the cache
if (!definitionCache[value])
definitionCache[value] = describeType(value);

var definition:XML = definitionCache[value];
var target:PostInjectionTarget;

for each (var node:XML in definition.factory.*.(name() == 'method').metadata.(@name == 'PostInject')) {
target = new PostInjectionTarget();
target.method = node.parent().@name;
result.push(target);
}

return result;
}

protected function applyToTarget(target:InjectionTarget,value:Object):void
{
var injectedValue:Object = mappedValues[target.definition];

if(!injectedValue) return;

if(target.uri.length == 0)
{
// Inject the regular way
value[target.property] = injectedValue;
}
}
}
}
11 changes: 11 additions & 0 deletions src/com/zaalabs/imbue/PostInjectionTarget.as
@@ -0,0 +1,11 @@
package com.zaalabs.imbue
{
public class PostInjectionTarget
{
public var method:String;

public function PostInjectionTarget()
{
}
}
}
28 changes: 28 additions & 0 deletions src/com/zaalabs/imbue/getConstructor.as
@@ -0,0 +1,28 @@
package com.zaalabs.imbue
{
import flash.utils.Proxy;
import flash.utils.getDefinitionByName;
import flash.utils.getQualifiedClassName;

/**
* This is a clever discovery in the SwiftSuspenders library, so I copied it :P
*
* Basically, some classes throw exeptions and such when we try to get the
* constructor, so canstructor getting is then abstracted, and a simple is
* check is used to see if we can get the costructor or use the more costly
* <code>getQualifiedClassName()</code> method to get the class definition
*
* @param value
* @return
*/
public function getConstructor(value:Object):Class
{
if (value is Proxy || value is Number || value is XML || value is XMLList)
{
var qname:String = getQualifiedClassName(value);
return Class(getDefinitionByName(qname));
}

return value.constructor;
}
}

0 comments on commit a881935

Please sign in to comment.