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,7 +1,9 @@
var shell = require('gulp-shell');
var gulp = require('gulp');
var rename = require('gulp-rename');
var watch = require('gulp-watch');
var mergeStreams = require('event-stream').merge;
var es = require('event-stream');
var connect = require('gulp-connect');
var clean = require('gulp-rimraf');
var runSequence = require('run-sequence');
@ -225,22 +227,58 @@ gulp.task('analyze/dartanalyzer', function(done) {
// ------------------
// BENCHMARKS
// BENCHMARKS JS
var benchmarksBuildPath = 'build/benchpress';
var benchmarksCompiledJsPath = 'build/js/benchmarks/lib';
gulp.task('benchmarks/build.benchpress', function () {
gulp.task('benchmarks/build.benchpress.js', function () {
benchpress.build({
benchmarksPath: benchmarksCompiledJsPath,
buildPath: benchmarksBuildPath
benchmarksPath: 'build/js/benchmarks/lib',
buildPath: 'build/benchpress/js'
})
});
gulp.task('benchmarks/build', function() {
gulp.task('benchmarks/build.js', function() {
runSequence(
['jsRuntime/build', 'modules/build.prod.js'],
'benchmarks/build.benchpress'
'benchmarks/build.benchpress.js'
);
});
// ------------------
// BENCHMARKS DART
gulp.task('benchmarks/build.dart2js.dart', function () {
return gulp.src([
"build/dart/benchmarks/lib/**/benchmark.dart"
]).pipe(shell(['dart2js --package-root="build/dart/benchmarks/packages" -o "<%= file.path %>.js" <%= file.path %>']));
});
gulp.task('benchmarks/create-bpconf.dart', function () {
var bpConfContent = "module.exports = function(c) {c.set({scripts: [{src: 'benchmark.dart.js'}]});}";
var createBpConfJs = es.map(function(file, cb) {
var dir = path.dirname(file.path);
fs.writeFileSync(path.join(dir, "bp.conf.js"), bpConfContent);
cb();
});
return gulp.src([
"build/dart/benchmarks/lib/**/benchmark.dart"
]).pipe(createBpConfJs);
});
gulp.task('benchmarks/build.benchpress.dart', function () {
benchpress.build({
benchmarksPath: 'build/dart/benchmarks/lib',
buildPath: 'build/benchpress/dart'
})
});
gulp.task('benchmarks/build.dart', function() {
runSequence(
'modules/build.dart',
'benchmarks/build.dart2js.dart',
'benchmarks/create-bpconf.dart',
'benchmarks/build.benchpress.dart'
);
});

View File

@ -0,0 +1,10 @@
name: bnehcmarks
environment:
sdk: '>=1.4.0'
dependencies:
facade:
path: ../facade
di:
path: ../di
core:
path: ../core

View File

@ -0,0 +1,11 @@
library injector_get_benchmark;
import './injector_instantiate_benchmark.dart' as b;
import 'dart:js' as js;
main () {
js.context['benchmarkSteps'].add(new js.JsObject.jsify({
"name": "Injector.instantiate",
"fn": new js.JsFunction.withThis((_) => b.run())
}));
}

View File

@ -10,8 +10,6 @@ export function run () {
var child = injector.createChild([E]);
child.get(E);
}
console.log(count)
}
class A {

View File

@ -0,0 +1,11 @@
library element_injector_benchmark;
import './instantiate_benchmark.dart' as ib;
import 'dart:js' as js;
main () {
js.context['benchmarkSteps'].add(new js.JsObject.jsify({
"name": "ElementInjector.instantiate + instantiateDirectives",
"fn": new js.JsFunction.withThis((_) => ib.run())
}));
}

View File

@ -0,0 +1,7 @@
System.import('benchmarks/element_injector/instantiate_benchmark').then(function (bm) {
window.benchmarkSteps.push({name: 'ElementInjector.instantiate + instantiateDirectives', fn: bm.run});
}, console.log.bind(console));
System.import('benchmarks/element_injector/instantiate_directive_benchmark').then(function (bm) {
window.benchmarkSteps.push({name: 'ElementInjector.instantiateDirectives', fn: bm.run});
}, console.log.bind(console));

View File

@ -0,0 +1,11 @@
module.exports = function(config) {
config.set({
scripts: [
{src: '/js/traceur-runtime.js'},
{src: '/js/es6-module-loader-sans-promises.src.js'},
{src: '/js/extension-register.js'},
{src: 'register_system.js'},
{src: 'benchmark.js'}
]
});
};

View File

@ -0,0 +1,33 @@
import {Injector} from 'di/di';
import {ProtoElementInjector} from 'core/compiler/element_injector';
var count = 0;
export function run () {
var appInjector = new Injector([]);
var bindings = [A, B, C];
var proto = new ProtoElementInjector(null, bindings, []);
for (var i = 0; i < 20000; ++i) {
var ei = proto.instantiate();
ei.instantiateDirectives(appInjector);
}
}
class A {
constructor() {
count++;
}
}
class B {
constructor() {
count++;
}
}
class C {
constructor(a:A, b:B) {
count++;
}
}

View File

@ -0,0 +1,35 @@
import {Injector} from 'di/di';
import {ProtoElementInjector} from 'core/compiler/element_injector';
var count = 0;
export function run () {
var appInjector = new Injector([]);
var bindings = [A, B, C];
var proto = new ProtoElementInjector(null, bindings, []);
var ei = proto.instantiate();
for (var i = 0; i < 20000; ++i) {
ei.clearDirectives();
ei.instantiateDirectives(appInjector);
}
}
class A {
constructor() {
count++;
}
}
class B {
constructor() {
count++;
}
}
class C {
constructor(a:A, b:B) {
count++;
}
}

View File

@ -0,0 +1,10 @@
System.paths = {
'core/*': '/js/core/lib/*.js',
'change_detection/*': '/js/change_detection/lib/*.js',
'facade/*': '/js/facade/lib/*.js',
'di/*': '/js/di/lib/*.js',
'rtts_assert/*': '/js/rtts_assert/lib/*.js',
'test_lib/*': '/js/test_lib/lib/*.js',
'benchmarks/*': '/js/benchmarks/lib/*.js'
};
register(System);

View File

@ -0,0 +1,22 @@
import {CONST} from 'facade/lang';
import {DependencyAnnotation} from 'di/di';
/**
* The directive can only be injected from the current element
* or from its parent.
*/
export class Parent extends DependencyAnnotation {
@CONST()
constructor() {
}
}
/**
* The directive can only be injected from the current element
* or from its ancestor.
*/
export class Ancestor extends DependencyAnnotation {
@CONST()
constructor() {
}
}

View File

@ -1,4 +1,72 @@
import {FIELD} from 'facade/lang';
import {FIELD, isPresent, isBlank, Type, int} from 'facade/lang';
import {Math} from 'facade/math';
import {List, ListWrapper} from 'facade/collection';
import {Injector, Key, Dependency, bind, Binding, NoProviderError, ProviderError} from 'di/di';
import {Parent, Ancestor} from 'core/annotations/visibility';
var MAX_DEPTH = Math.pow(2, 30) - 1;
class TreeNode {
@FIELD('_parent:TreeNode')
@FIELD('_head:TreeNode')
@FIELD('_tail:TreeNode')
@FIELD('_next:TreeNode')
@FIELD('_prev:TreeNode')
constructor(parent:TreeNode) {
this._parent = parent;
this._head = null;
this._tail = null;
this._next = null;
this._prev = null;
if (isPresent(parent)) parent._addChild(this);
}
/**
* Adds a child to the parent node. The child MUST NOT be a part of a tree.
*/
_addChild(child:TreeNode) {
if (isPresent(this._tail)) {
this._tail._next = child;
child._prev = this._tail;
this._tail = child;
} else {
this._tail = this._head = child;
}
}
get parent() {
return this._parent;
}
get children() {
var res = [];
var child = this._head;
while (child != null) {
ListWrapper.push(res, child);
child = child._next;
}
return res;
}
}
class DirectiveDependency extends Dependency {
constructor(key:Key, asPromise:boolean, lazy:boolean, properties:List, depth:int) {
super(key, asPromise, lazy, properties);
this.depth = depth;
}
static createFrom(d:Dependency):Dependency {
return new DirectiveDependency(d.key, d.asPromise, d.lazy,
d.properties, DirectiveDependency._depth(d.properties));
}
static _depth(properties):int {
if (properties.length == 0) return 0;
if (ListWrapper.any(properties, p => p instanceof Parent)) return 1;
if (ListWrapper.any(properties, p => p instanceof Ancestor)) return MAX_DEPTH;
return 0;
}
}
/**
@ -18,35 +86,34 @@ ElementInjector (ElementModule):
- 1:1 to DOM structure.
PERF BENCHMARK: http://www.williambrownstreet.net/blog/2014/04/faster-angularjs-rendering-angularjs-and-reactjs/
*/
export class ProtoElementInjector {
export class ProtoElementInjector extends TreeNode {
/**
parent:ProtoDirectiveInjector;
next:ProtoDirectiveInjector;
prev:ProtoDirectiveInjector;
head:ProtoDirectiveInjector;
tail:ProtoDirectiveInjector;
DirectiveInjector cloneingInstance;
DirectiveInjector cloningInstance;
KeyMap keyMap;
/// Because DI tree is sparse, this shows how far away is the Parent DI
parentDistance:int = 1; /// 1 for non-sparse/normal depth.
cKey:int; cFactory:Function; cParams:List<int>;
keyId0:int; factory0:Function; params0:List<int>;
keyId1:int; factory1:Function; params1:List<int>;
keyId2:int; factory2:Function; params2:List<int>;
keyId3:int; factory3:Function; params3:List<int>;
keyId4:int; factory4:Function; params4:List<int>;
keyId5:int; factory5:Function; params5:List<int>;
keyId6:int; factory6:Function; params6:List<int>;
keyId7:int; factory7:Function; params7:List<int>;
keyId8:int; factory8:Function; params8:List<int>;
keyId9:int; factory9:Function; params9:List<int>;
_keyId0:int; factory0:Function; params0:List<int>;
_keyId1:int; factory1:Function; params1:List<int>;
_keyId2:int; factory2:Function; params2:List<int>;
_keyId3:int; factory3:Function; params3:List<int>;
_keyId4:int; factory4:Function; params4:List<int>;
_keyId5:int; factory5:Function; params5:List<int>;
_keyId6:int; factory6:Function; params6:List<int>;
_keyId7:int; factory7:Function; params7:List<int>;
_keyId8:int; factory8:Function; params8:List<int>;
_keyId9:int; factory9:Function; params9:List<int>;
queryKeyId0:int;
queryKeyId1:int;
query_keyId0:int;
query_keyId1:int;
textNodes:List<int>;
hasProperties:boolean;
@ -54,17 +121,91 @@ export class ProtoElementInjector {
elementInjector:ElementInjector;
*/
constructor(parent:ProtoElementInjector) {
@FIELD('_elementInjector:ElementInjector')
@FIELD('_binding0:Binding')
@FIELD('_binding1:Binding')
@FIELD('_binding2:Binding')
@FIELD('_binding3:Binding')
@FIELD('_binding4:Binding')
@FIELD('_binding5:Binding')
@FIELD('_binding6:Binding')
@FIELD('_binding7:Binding')
@FIELD('_binding8:Binding')
@FIELD('_binding9:Binding')
@FIELD('_key0:int')
@FIELD('_key1:int')
@FIELD('_key2:int')
@FIELD('_key3:int')
@FIELD('_key4:int')
@FIELD('_key5:int')
@FIELD('_key6:int')
@FIELD('_key7:int')
@FIELD('_key8:int')
@FIELD('_key9:int')
@FIELD('textNodes:List<int>')
constructor(parent:ProtoElementInjector, directiveTypes:List, textNodes:List) {
super(parent);
this._elementInjector = null;
this._binding0 = null; this._keyId0 = null;
this._binding1 = null; this._keyId1 = null;
this._binding2 = null; this._keyId2 = null;
this._binding3 = null; this._keyId3 = null;
this._binding4 = null; this._keyId4 = null;
this._binding5 = null; this._keyId5 = null;
this._binding6 = null; this._keyId6 = null;
this._binding7 = null; this._keyId7 = null;
this._binding8 = null; this._keyId8 = null;
this._binding9 = null; this._keyId9 = null;
var length = directiveTypes.length;
if (length > 0) {this._binding0 = this._createBinding(directiveTypes[0]); this._keyId0 = this._binding0.key.id;}
if (length > 1) {this._binding1 = this._createBinding(directiveTypes[1]); this._keyId1 = this._binding1.key.id;}
if (length > 2) {this._binding2 = this._createBinding(directiveTypes[2]); this._keyId2 = this._binding2.key.id;}
if (length > 3) {this._binding3 = this._createBinding(directiveTypes[3]); this._keyId3 = this._binding3.key.id;}
if (length > 4) {this._binding4 = this._createBinding(directiveTypes[4]); this._keyId4 = this._binding4.key.id;}
if (length > 5) {this._binding5 = this._createBinding(directiveTypes[5]); this._keyId5 = this._binding5.key.id;}
if (length > 6) {this._binding6 = this._createBinding(directiveTypes[6]); this._keyId6 = this._binding6.key.id;}
if (length > 7) {this._binding7 = this._createBinding(directiveTypes[7]); this._keyId7 = this._binding7.key.id;}
if (length > 8) {this._binding8 = this._createBinding(directiveTypes[8]); this._keyId8 = this._binding8.key.id;}
if (length > 9) {this._binding9 = this._createBinding(directiveTypes[9]); this._keyId9 = this._binding9.key.id;}
if (length > 10) {
throw 'Maximum number of directives per element has been reached.';
}
// dummy fields to make analyzer happy
this.textNodes = [];
this.hasProperties = false;
this.textNodes = null;
}
instantiate():ElementInjector {
return new ElementInjector(this);
var p = this._parent;
var parentElementInjector = p == null ? null : p._elementInjector;
this._elementInjector = new ElementInjector({
proto: this,
parent: parentElementInjector
});
return this._elementInjector;
}
_createBinding(directiveType:Type) {
var b = bind(directiveType).toClass(directiveType);
var deps = ListWrapper.map(b.dependencies, DirectiveDependency.createFrom);
return new Binding(b.key, b.factory, deps, b.providedAsPromise);
}
clearElementInjector() {
this._elementInjector = null;
}
get hasBindings():boolean {
return isPresent(this._binding0);
}
}
export class ElementInjector {
export class ElementInjector extends TreeNode {
/*
_protoInjector:ProtoElementInjector;
injector:Injector;
@ -107,10 +248,141 @@ export class ElementInjector {
_query1:Query;
*/
@FIELD('final protoInjector:ProtoElementInjector')
constructor(protoInjector:ProtoElementInjector) {
this.protoInjector = protoInjector;
@FIELD('_proto:ProtoElementInjector')
@FIELD('_appInjector:Injector')
@FIELD('_obj0:Object')
@FIELD('_obj1:Object')
@FIELD('_obj2:Object')
@FIELD('_obj3:Object')
@FIELD('_obj4:Object')
@FIELD('_obj5:Object')
@FIELD('_obj6:Object')
@FIELD('_obj7:Object')
@FIELD('_obj8:Object')
@FIELD('_obj9:Object')
constructor({proto, parent}) {
super(parent);
this._proto = proto;
//we cannot call clearDirectives because fields won't be detected
this._appInjector = null;
this._obj0 = null;
this._obj1 = null;
this._obj2 = null;
this._obj3 = null;
this._obj4 = null;
this._obj5 = null;
this._obj6 = null;
this._obj7 = null;
this._obj8 = null;
this._obj9 = null;
}
clearDirectives() {
this._appInjector = null;
this._obj0 = null;
this._obj1 = null;
this._obj2 = null;
this._obj3 = null;
this._obj4 = null;
this._obj5 = null;
this._obj6 = null;
this._obj7 = null;
this._obj8 = null;
this._obj9 = null;
}
instantiateDirectives(appInjector:Injector) {
this._appInjector = appInjector;
var p = this._proto;
if (isPresent(p._keyId0)) this._obj0 = this._new(p._binding0);
if (isPresent(p._keyId1)) this._obj1 = this._new(p._binding1);
if (isPresent(p._keyId2)) this._obj2 = this._new(p._binding2);
if (isPresent(p._keyId3)) this._obj3 = this._new(p._binding3);
if (isPresent(p._keyId4)) this._obj4 = this._new(p._binding4);
if (isPresent(p._keyId5)) this._obj5 = this._new(p._binding5);
if (isPresent(p._keyId6)) this._obj6 = this._new(p._binding6);
if (isPresent(p._keyId7)) this._obj7 = this._new(p._binding7);
if (isPresent(p._keyId8)) this._obj8 = this._new(p._binding8);
if (isPresent(p._keyId9)) this._obj9 = this._new(p._binding9);
}
get(token) {
return this._getByKey(Key.get(token), 0);
}
_new(binding:Binding) {
var factory = binding.factory;
var deps = binding.dependencies;
var length = deps.length;
var d0,d1,d2,d3,d4,d5,d6,d7,d8,d9;
try {
d0 = length > 0 ? this._getByDependency(deps[0]) : null;
d1 = length > 1 ? this._getByDependency(deps[1]) : null;
d2 = length > 2 ? this._getByDependency(deps[2]) : null;
d3 = length > 3 ? this._getByDependency(deps[3]) : null;
d4 = length > 4 ? this._getByDependency(deps[4]) : null;
d5 = length > 5 ? this._getByDependency(deps[5]) : null;
d6 = length > 6 ? this._getByDependency(deps[6]) : null;
d7 = length > 7 ? this._getByDependency(deps[7]) : null;
d8 = length > 8 ? this._getByDependency(deps[8]) : null;
d9 = length > 9 ? this._getByDependency(deps[9]) : null;
} catch(e) {
if (e instanceof ProviderError) e.addKey(binding.key);
throw e;
}
var obj;
switch(length) {
case 0: obj = factory(); break;
case 1: obj = factory(d0); break;
case 2: obj = factory(d0, d1); break;
case 3: obj = factory(d0, d1, d2); break;
case 4: obj = factory(d0, d1, d2, d3); break;
case 5: obj = factory(d0, d1, d2, d3, d4); break;
case 6: obj = factory(d0, d1, d2, d3, d4, d5); break;
case 7: obj = factory(d0, d1, d2, d3, d4, d5, d6); break;
case 8: obj = factory(d0, d1, d2, d3, d4, d5, d6, d7); break;
case 9: obj = factory(d0, d1, d2, d3, d4, d5, d6, d7, d8); break;
case 10: obj = factory(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9); break;
default: throw `Directive ${binding.key.token} can only have up to 10 dependencies.`;
}
return obj;
}
_getByDependency(dep:DirectiveDependency) {
return this._getByKey(dep.key, dep.depth);
}
_getByKey(key:Key, depth:int) {
var ei = this;
while (ei != null && depth >= 0) {
var obj = ei._getDirectiveByKey(key);
if (isPresent(obj)) return obj;
ei = ei._parent;
depth -= 1;
}
return this._appInjector.get(key);
}
_getDirectiveByKey(key:Key) {
var p = this._proto;
var keyId= key.id;
if (p._keyId0 === keyId) return this._obj0;
if (p._keyId1 === keyId) return this._obj1;
if (p._keyId2 === keyId) return this._obj2;
if (p._keyId3 === keyId) return this._obj3;
if (p._keyId4 === keyId) return this._obj4;
if (p._keyId5 === keyId) return this._obj5;
if (p._keyId6 === keyId) return this._obj6;
if (p._keyId7 === keyId) return this._obj7;
if (p._keyId8 === keyId) return this._obj8;
if (p._keyId9 === keyId) return this._obj9;
return null;
}
}

View File

@ -0,0 +1,177 @@
import {describe, ddescribe, it, iit, xit, xdescribe, expect, beforeEach} from 'test_lib/test_lib';
import {isBlank, FIELD} from 'facade/lang';
import {ListWrapper, MapWrapper, List} from 'facade/collection';
import {ProtoElementInjector} from 'core/compiler/element_injector';
import {Parent, Ancestor} from 'core/annotations/visibility';
import {Injector, Inject, bind} from 'di/di';
class Directive {
}
class NeedsDirective {
@FIELD("dependency:Directive")
constructor(dependency:Directive){
this.dependency = dependency;
}
}
class NeedDirectiveFromParent {
@FIELD("dependency:Directive")
constructor(@Parent() dependency:Directive){
this.dependency = dependency;
}
}
class NeedDirectiveFromAncestor {
@FIELD("dependency:Directive")
constructor(@Ancestor() dependency:Directive){
this.dependency = dependency;
}
}
class NeedsService {
@FIELD("service:Object")
constructor(@Inject("service") service) {
this.service = service;
}
}
export function main() {
function humanize(tree, names:List) {
var lookupName = (item) =>
ListWrapper.last(
ListWrapper.find(names, (pair) => pair[0] === item));
if (tree.children.length == 0) return lookupName(tree);
var children = tree.children.map(m => humanize(m, names));
return [lookupName(tree), children];
}
describe("ElementInjector", function () {
describe("proto injectors", function () {
it("should construct a proto tree", function () {
var p = new ProtoElementInjector(null, [], []);
var c1 = new ProtoElementInjector(p, [], []);
var c2 = new ProtoElementInjector(p, [], []);
expect(humanize(p, [
[p, 'parent'],
[c1, 'child1'],
[c2, 'child2']
])).toEqual(["parent", ["child1", "child2"]]);
});
});
describe("instantiate", function () {
it("should create an element injector", function () {
var protoParent = new ProtoElementInjector(null, [], []);
var protoChild1 = new ProtoElementInjector(protoParent, [], []);
var protoChild2 = new ProtoElementInjector(protoParent, [], []);
var p = protoParent.instantiate();
var c1 = protoChild1.instantiate();
var c2 = protoChild2.instantiate();
expect(humanize(p, [
[p, 'parent'],
[c1, 'child1'],
[c2, 'child2']
])).toEqual(["parent", ["child1", "child2"]]);
});
});
describe("hasBindings", function () {
it("should be true when there are bindings", function () {
var p = new ProtoElementInjector(null, [Directive], []);
expect(p.hasBindings).toBeTruthy();
});
it("should be false otherwise", function () {
var p = new ProtoElementInjector(null, [], []);
expect(p.hasBindings).toBeFalsy();
});
});
describe("instantiateDirectives", function () {
function injector(bindings, appInjector = null) {
var proto = new ProtoElementInjector(null, bindings, []);
var inj = proto.instantiate();
if (isBlank(appInjector)) appInjector = new Injector([]);
inj.instantiateDirectives(appInjector);
return inj;
}
function parentChildInjectors(parentBindings, childBindings) {
var inj = new Injector([]);
var protoParent = new ProtoElementInjector(null, parentBindings, []);
var parent = protoParent.instantiate();
parent.instantiateDirectives(inj);
var protoChild = new ProtoElementInjector(protoParent, childBindings, []);
var child = protoChild.instantiate();
child.instantiateDirectives(inj);
return child;
}
it("should instantiate directives that have no dependencies", function () {
var inj = injector([Directive]);
expect(inj.get(Directive)).toBeAnInstanceOf(Directive);
});
it("should instantiate directives that depend on other directives", function () {
var inj = injector([Directive, NeedsDirective]);
var d = inj.get(NeedsDirective);
expect(d).toBeAnInstanceOf(NeedsDirective);
expect(d.dependency).toBeAnInstanceOf(Directive);
});
it("should instantiate directives that depend on app services", function () {
var appInjector = new Injector([
bind("service").toValue("service")
]);
var inj = injector([NeedsService], appInjector);
var d = inj.get(NeedsService);
expect(d).toBeAnInstanceOf(NeedsService);
expect(d.service).toEqual("service");
});
it("should return app services", function () {
var appInjector = new Injector([
bind("service").toValue("service")
]);
var inj = injector([], appInjector);
expect(inj.get('service')).toEqual('service');
});
it("should get directives from parent", function () {
var child = parentChildInjectors([Directive], [NeedDirectiveFromParent]);
var d = child.get(NeedDirectiveFromParent);
expect(d).toBeAnInstanceOf(NeedDirectiveFromParent);
expect(d.dependency).toBeAnInstanceOf(Directive);
});
it("should get directives from ancestor", function () {
var child = parentChildInjectors([Directive], [NeedDirectiveFromAncestor]);
var d = child.get(NeedDirectiveFromAncestor);
expect(d).toBeAnInstanceOf(NeedDirectiveFromAncestor);
expect(d.dependency).toBeAnInstanceOf(Directive);
});
it("should throw when no directive found", function () {
expect(() => injector([NeedDirectiveFromParent])).
toThrowError('No provider for Directive! (NeedDirectiveFromParent -> Directive)');
});
});
});
}

View File

@ -25,10 +25,10 @@ export function main() {
'</div>' +
'</section>');
var module:Module = null;
var sectionPI = new ProtoElementInjector(null);
var sectionPI = new ProtoElementInjector(null, null, null);
sectionPI.textNodes = [0];
var divPI = new ProtoElementInjector(null);
var spanPI = new ProtoElementInjector(null);
var divPI = new ProtoElementInjector(null, null, null);
var spanPI = new ProtoElementInjector(null, null, null);
spanPI.hasProperties = true;
var protoElementInjector:List<ProtoElementInjector> = [sectionPI, divPI, spanPI];
var protoWatchGroup:ProtoWatchGroup = null;

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);
}
}

View File

@ -0,0 +1,23 @@
import {ddescribe, describe, it, iit, expect} from 'test_lib/test_lib';
import {Key, Inject, DependencyAnnotation} from 'di/di';
import {CONST} from 'facade/lang';
import {reflector, Token} from 'di/reflector';
class Parent extends DependencyAnnotation {
@CONST()
constructor() {
}
}
export function main() {
describe("reflector", function () {
describe("dependencies", function () {
it('should collect annotations implementing DependencyAnnotation as properties', function () {
function f(@Parent() arg:Function) {}
var dep = reflector.dependencies(f)[0];
expect(dep.properties[0]).toBeAnInstanceOf(Parent);
});
});
});
}

View File

@ -22,6 +22,8 @@ class ListWrapper {
static void set(m, k, v) { m[k] = v; }
static contains(m, k) => m.containsKey(k);
static map(list, fn) => list.map(fn).toList();
static find(List list, fn) => list.firstWhere(fn, orElse:() => null);
static any(List list, fn) => list.any(fn);
static forEach(list, fn) {
list.forEach(fn);
}

View File

@ -41,6 +41,18 @@ export class ListWrapper {
if (!array || array.length == 0) return null;
return array[array.length - 1];
}
static find(list:List, pred:Function) {
for (var i = 0 ; i < list.length; ++i) {
if (pred(list[i])) return list[i];
}
return null;
}
static any(list:List, pred:Function) {
for (var i = 0 ; i < list.length; ++i) {
if (pred(list[i])) return true;
}
return false;
}
static reversed(array) {
var a = ListWrapper.clone(array);
return a.reverse();

View File

@ -0,0 +1,9 @@
library angular.core.facade.math;
import 'dart:math' as math;
class Math {
static num pow(num x, num exponent) {
return math.pow(x, exponent);
}
}

View File

@ -0,0 +1 @@
export var Math = window.Math;

View File

@ -14,6 +14,7 @@
"gulp": "^3.8.8",
"gulp-rename": "^1.2.0",
"gulp-watch": "^1.0.3",
"gulp-shell": "^0.2.10",
"karma-cli": "^0.0.4",
"karma": "^0.12.23",
"karma-chrome-launcher": "^0.1.4",