feat(ElementInjector): implement ElementInjector

This commit is contained in:
vsavkin
2014-10-14 16:00:35 -04:00
parent ea0df352be
commit e3548b497f
27 changed files with 865 additions and 68 deletions

View File

@ -1,5 +1,13 @@
import {CONST} from "facade/lang";
/**
* A parameter annotation that creates a synchronous eager dependency.
*
* class AComponent {
* constructor(@Inject('aServiceToken') aService) {}
* }
*
*/
export class Inject {
@CONST()
constructor(token) {
@ -7,6 +15,16 @@ export class Inject {
}
}
/**
* A parameter annotation that creates an asynchronous eager dependency.
*
* class AComponent {
* constructor(@InjectPromise('aServiceToken') aServicePromise) {
* aServicePromise.then(aService => ...);
* }
* }
*
*/
export class InjectPromise {
@CONST()
constructor(token) {
@ -14,9 +32,48 @@ export class InjectPromise {
}
}
/**
* A parameter annotation that creates a synchronous lazy dependency.
*
* class AComponent {
* constructor(@InjectLazy('aServiceToken') aServiceFn) {
* aService = aServiceFn();
* }
* }
*
*/
export class InjectLazy {
@CONST()
constructor(token) {
this.token = token;
}
}
/**
* `DependencyAnnotation` is used by the framework to extend DI.
*
* Only annotations implementing `DependencyAnnotation` will be added
* to the list of dependency properties.
*
* For example:
*
* class Parent extends DependencyAnnotation {}
* class NotDependencyProperty {}
*
* class AComponent {
* constructor(@Parent @NotDependencyProperty aService:AService) {}
* }
*
* will create the following dependency:
*
* new Dependency(Key.get(AService), [new Parent()])
*
* The framework can use `new Parent()` to handle the `aService` dependency
* in a specific way.
*
*/
export class DependencyAnnotation {
@CONST()
constructor() {
}
}

View File

@ -7,10 +7,11 @@ export class Dependency {
@FIELD('final key:Key')
@FIELD('final asPromise:bool')
@FIELD('final lazy:bool')
constructor(key:Key, asPromise:boolean, lazy:boolean) {
constructor(key:Key, asPromise:boolean, lazy:boolean, properties:List) {
this.key = key;
this.asPromise = asPromise;
this.lazy = lazy;
this.properties = properties;
}
}
@ -44,7 +45,7 @@ export class BindingBuilder {
toValue(value):Binding {
return new Binding(
Key.get(this.token),
(_) => value,
() => value,
[],
false
);
@ -53,7 +54,7 @@ export class BindingBuilder {
toFactory(factoryFunction:Function, dependencies:List = null):Binding {
return new Binding(
Key.get(this.token),
reflector.convertToFactory(factoryFunction),
factoryFunction,
this._constructDependencies(factoryFunction, dependencies),
false
);
@ -62,7 +63,7 @@ export class BindingBuilder {
toAsyncFactory(factoryFunction:Function, dependencies:List = null):Binding {
return new Binding(
Key.get(this.token),
reflector.convertToFactory(factoryFunction),
factoryFunction,
this._constructDependencies(factoryFunction, dependencies),
true
);
@ -71,6 +72,6 @@ export class BindingBuilder {
_constructDependencies(factoryFunction:Function, dependencies:List) {
return isBlank(dependencies) ?
reflector.dependencies(factoryFunction) :
ListWrapper.map(dependencies, (t) => new Dependency(Key.get(t), false, false));
ListWrapper.map(dependencies, (t) => new Dependency(Key.get(t), false, false, []));
}
}

View File

@ -3,3 +3,4 @@ export * from './injector';
export * from './binding';
export * from './key';
export * from './module';
export * from './exceptions';

View File

@ -5,6 +5,7 @@ import {ProviderError, NoProviderError, InvalidBindingError,
import {Type, isPresent, isBlank} from 'facade/lang';
import {Promise, PromiseWrapper} from 'facade/async';
import {Key} from './key';
import {reflector} from './reflector';
var _constructing = new Object();
@ -150,7 +151,7 @@ class _SyncInjectorStrategy {
_createInstance(key:Key, binding:Binding, deps:List) {
try {
var instance = binding.factory(deps);
var instance = reflector.invoke(binding.factory, deps);
this.injector._setInstance(key, instance);
return instance;
} catch (e) {
@ -211,7 +212,7 @@ class _AsyncInjectorStrategy {
try {
var instance = this.injector._getInstance(key);
if (!_isWaiting(instance)) return instance;
return binding.factory(deps);
return reflector.invoke(binding.factory, deps);
} catch (e) {
this.injector._clear(key);
throw new InstantiationError(e, key);

View File

@ -1,26 +1,49 @@
library facade.di.reflector;
import 'dart:mirrors';
import 'annotations.dart' show Inject, InjectPromise, InjectLazy;
import 'annotations.dart' show Inject, InjectPromise, InjectLazy, DependencyAnnotation;
import 'key.dart' show Key;
import 'binding.dart' show Dependency;
import 'exceptions.dart' show NoAnnotationError;
class Reflector {
Function factoryFor(Type type) {
return _generateFactory(type);
}
Function convertToFactory(Function factory) {
return (args) => Function.apply(factory, args);
}
Function _generateFactory(Type type) {
ClassMirror classMirror = reflectType(type);
MethodMirror ctor = classMirror.declarations[classMirror.simpleName];
Function create = classMirror.newInstance;
Symbol name = ctor.constructorName;
return (args) => create(name, args).reflectee;
int length = ctor.parameters.length;
switch (length) {
case 0: return () =>
create(name, []).reflectee;
case 1: return (a1) =>
create(name, [a1]).reflectee;
case 2: return (a1, a2) =>
create(name, [a1, a2]).reflectee;
case 3: return (a1, a2, a3) =>
create(name, [a1, a2, a3]).reflectee;
case 4: return (a1, a2, a3, a4) =>
create(name, [a1, a2, a3, a4]).reflectee;
case 5: return (a1, a2, a3, a4, a5) =>
create(name, [a1, a2, a3, a4, a5]).reflectee;
case 6: return (a1, a2, a3, a4, a5, a6) =>
create(name, [a1, a2, a3, a4, a5, a6]).reflectee;
case 7: return (a1, a2, a3, a4, a5, a6, a7) =>
create(name, [a1, a2, a3, a4, a5, a6, a7]).reflectee;
case 8: return (a1, a2, a3, a4, a5, a6, a7, a8) =>
create(name, [a1, a2, a3, a4, a5, a6, a7, a8]).reflectee;
case 9: return (a1, a2, a3, a4, a5, a6, a7, a8, a9) =>
create(name, [a1, a2, a3, a4, a5, a6, a7, a8, a9]).reflectee;
case 10: return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) =>
create(name, [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10]).reflectee;
};
throw "Factory cannot take more than 10 arguments";
}
invoke(Function factory, List args) {
return Function.apply(factory, args);
}
List<Dependency> dependencies(typeOrFunc) {
@ -38,16 +61,17 @@ class Reflector {
var injectLazy = metadata.firstWhere((m) => m is InjectLazy, orElse: () => null);
if (inject != null) {
return new Dependency(Key.get(inject.token), false, false);
return new Dependency(Key.get(inject.token), false, false, []);
} else if (injectPromise != null) {
return new Dependency(Key.get(injectPromise.token), true, false);
return new Dependency(Key.get(injectPromise.token), true, false, []);
} else if (injectLazy != null) {
return new Dependency(Key.get(injectLazy.token), false, true);
return new Dependency(Key.get(injectLazy.token), false, true, []);
} else if (p.type.qualifiedName != #dynamic) {
return new Dependency(Key.get(p.type.reflectedType), false, false);
var depProps = metadata.where((m) => m is DependencyAnnotation).toList();
return new Dependency(Key.get(p.type.reflectedType), false, false, depProps);
} else {
throw new NoAnnotationError(typeOrFunc);

View File

@ -1,17 +1,43 @@
import {Type, isPresent} from 'facade/lang';
import {List} from 'facade/collection';
import {Inject, InjectPromise, InjectLazy} from './annotations';
import {Inject, InjectPromise, InjectLazy, DependencyAnnotation} from './annotations';
import {Key} from './key';
import {Dependency} from './binding';
import {NoAnnotationError} from './exceptions';
class Reflector {
factoryFor(type:Type):Function {
return (args) => new type(...args);
var length = type.parameters ? type.parameters.length : 0;
switch (length) {
case 0: return () =>
new type();
case 1: return (a1) =>
new type(a1);
case 2: return (a1, a2) =>
new type(a1, a2);
case 3: return (a1, a2, a3) =>
new type(a1, a2, a3);
case 4: return (a1, a2, a3, a4) =>
new type(a1, a2, a3, a4);
case 5: return (a1, a2, a3, a4, a5) =>
new type(a1, a2, a3, a4, a5);
case 6: return (a1, a2, a3, a4, a5, a6) =>
new type(a1, a2, a3, a4, a5, a6);
case 7: return (a1, a2, a3, a4, a5, a6, a7) =>
new type(a1, a2, a3, a4, a5, a6, a7);
case 8: return (a1, a2, a3, a4, a5, a6, a7, a8) =>
new type(a1, a2, a3, a4, a5, a6, a7, a8);
case 9: return (a1, a2, a3, a4, a5, a6, a7, a8, a9) =>
new type(a1, a2, a3, a4, a5, a6, a7, a8, a9);
case 10: return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) =>
new type(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10);
};
throw "Factory cannot take more than 10 arguments";
}
convertToFactory(factoryFunction:Function):Function {
return (args) => factoryFunction(...args);
invoke(factory:Function, args:List) {
return factory(...args);
}
dependencies(typeOrFunc):List {
@ -23,31 +49,35 @@ class Reflector {
_extractToken(typeOrFunc, annotations) {
var type;
var depProps = [];
for (var paramAnnotation of annotations) {
if (paramAnnotation instanceof Type) {
type = paramAnnotation;
} else if (paramAnnotation instanceof Inject) {
return this._createDependency(paramAnnotation.token, false, false);
return this._createDependency(paramAnnotation.token, false, false, []);
} else if (paramAnnotation instanceof InjectPromise) {
return this._createDependency(paramAnnotation.token, true, false);
return this._createDependency(paramAnnotation.token, true, false, []);
} else if (paramAnnotation instanceof InjectLazy) {
return this._createDependency(paramAnnotation.token, false, true);
return this._createDependency(paramAnnotation.token, false, true, []);
} else if (paramAnnotation instanceof DependencyAnnotation) {
depProps.push(paramAnnotation);
}
}
if (isPresent(type)) {
return this._createDependency(type, false, false);
return this._createDependency(type, false, false, depProps);
} else {
throw new NoAnnotationError(typeOrFunc);
}
}
_createDependency(token, asPromise, lazy):Dependency {
return new Dependency(Key.get(token), asPromise, lazy);
_createDependency(token, asPromise, lazy, depProps):Dependency {
return new Dependency(Key.get(token), asPromise, lazy, depProps);
}
}