/// import {Map, List, MapWrapper, ListWrapper} from 'angular2/src/facade/collection'; import {ResolvedBinding, Binding, BindingBuilder, bind} from './binding'; import { AbstractBindingError, NoBindingError, AsyncBindingError, CyclicDependencyError, InstantiationError, InvalidBindingError, OutOfBoundsError } from './exceptions'; import {FunctionWrapper, Type, isPresent, isBlank, CONST_EXPR} from 'angular2/src/facade/lang'; import {Key} from './key'; import {resolveForwardRef} from './forward_ref'; import {self, unbounded} from './annotations_impl'; const _constructing = CONST_EXPR(new Object()); const _notFound = CONST_EXPR(new Object()); // Threshold for the dynamic version const _MAX_CONSTRUCTION_COUNTER = 10; export const undefinedValue = CONST_EXPR(new Object()); export const PUBLIC = 1; export const PRIVATE = 2; export const PUBLIC_AND_PRIVATE = 3; export interface ProtoInjectorStrategy { getBindingAtIndex(index: number): ResolvedBinding; createInjectorStrategy(inj: Injector): InjectorStrategy; } export class ProtoInjectorInlineStrategy implements ProtoInjectorStrategy { binding0: ResolvedBinding = null; binding1: ResolvedBinding = null; binding2: ResolvedBinding = null; binding3: ResolvedBinding = null; binding4: ResolvedBinding = null; binding5: ResolvedBinding = null; binding6: ResolvedBinding = null; binding7: ResolvedBinding = null; binding8: ResolvedBinding = null; binding9: ResolvedBinding = null; keyId0: number = null; keyId1: number = null; keyId2: number = null; keyId3: number = null; keyId4: number = null; keyId5: number = null; keyId6: number = null; keyId7: number = null; keyId8: number = null; keyId9: number = null; visibility0: number = null; visibility1: number = null; visibility2: number = null; visibility3: number = null; visibility4: number = null; visibility5: number = null; visibility6: number = null; visibility7: number = null; visibility8: number = null; visibility9: number = null; constructor(protoEI: ProtoInjector, bd: any[]) { var length = bd.length; if (length > 0) { this.binding0 = bd[0].binding; this.keyId0 = bd[0].getKeyId(); this.visibility0 = bd[0].visibility; } if (length > 1) { this.binding1 = bd[1].binding; this.keyId1 = bd[1].getKeyId(); this.visibility1 = bd[1].visibility; } if (length > 2) { this.binding2 = bd[2].binding; this.keyId2 = bd[2].getKeyId(); this.visibility2 = bd[2].visibility; } if (length > 3) { this.binding3 = bd[3].binding; this.keyId3 = bd[3].getKeyId(); this.visibility3 = bd[3].visibility; } if (length > 4) { this.binding4 = bd[4].binding; this.keyId4 = bd[4].getKeyId(); this.visibility4 = bd[4].visibility; } if (length > 5) { this.binding5 = bd[5].binding; this.keyId5 = bd[5].getKeyId(); this.visibility5 = bd[5].visibility; } if (length > 6) { this.binding6 = bd[6].binding; this.keyId6 = bd[6].getKeyId(); this.visibility6 = bd[6].visibility; } if (length > 7) { this.binding7 = bd[7].binding; this.keyId7 = bd[7].getKeyId(); this.visibility7 = bd[7].visibility; } if (length > 8) { this.binding8 = bd[8].binding; this.keyId8 = bd[8].getKeyId(); this.visibility8 = bd[8].visibility; } if (length > 9) { this.binding9 = bd[9].binding; this.keyId9 = bd[9].getKeyId(); this.visibility9 = bd[9].visibility; } } getBindingAtIndex(index: number): any { if (index == 0) return this.binding0; if (index == 1) return this.binding1; if (index == 2) return this.binding2; if (index == 3) return this.binding3; if (index == 4) return this.binding4; if (index == 5) return this.binding5; if (index == 6) return this.binding6; if (index == 7) return this.binding7; if (index == 8) return this.binding8; if (index == 9) return this.binding9; throw new OutOfBoundsError(index); } createInjectorStrategy(injector: Injector): InjectorStrategy { return new InjectorInlineStrategy(injector, this); } } export class ProtoInjectorDynamicStrategy implements ProtoInjectorStrategy { bindings: ResolvedBinding[]; keyIds: number[]; visibilities: number[]; constructor(protoInj: ProtoInjector, bd: any[]) { var len = bd.length; this.bindings = ListWrapper.createFixedSize(len); this.keyIds = ListWrapper.createFixedSize(len); this.visibilities = ListWrapper.createFixedSize(len); for (var i = 0; i < len; i++) { this.bindings[i] = bd[i].binding; this.keyIds[i] = bd[i].getKeyId(); this.visibilities[i] = bd[i].visibility; } } getBindingAtIndex(index: number): any { if (index < 0 || index >= this.bindings.length) { throw new OutOfBoundsError(index); } return this.bindings[index]; } createInjectorStrategy(ei: Injector): InjectorStrategy { return new InjectorDynamicStrategy(this, ei); } } export class ProtoInjector { _strategy: ProtoInjectorStrategy; constructor(public parent: ProtoInjector, rb: any[], public distanceToParent: number) { this._strategy = rb.length > _MAX_CONSTRUCTION_COUNTER ? new ProtoInjectorDynamicStrategy(this, rb) : new ProtoInjectorInlineStrategy(this, rb); } getBindingAtIndex(index: number): any { return this._strategy.getBindingAtIndex(index); } } export interface InjectorStrategy { getObjByKeyId(keyId: number, visibility: number): any; getObjAtIndex(index: number): any; getMaxNumberOfObjects(): number; hydrate(): void; dehydrate(): void; } export class InjectorInlineStrategy implements InjectorStrategy { obj0: any = null; obj1: any = null; obj2: any = null; obj3: any = null; obj4: any = null; obj5: any = null; obj6: any = null; obj7: any = null; obj8: any = null; obj9: any = null; constructor(public injector: Injector, public protoStrategy: ProtoInjectorInlineStrategy) {} hydrate(): void { var p = this.protoStrategy; var inj = this.injector; if (isPresent(p.keyId0) && isBlank(this.obj0)) this.obj0 = inj._new(p.binding0); if (isPresent(p.keyId1) && isBlank(this.obj1)) this.obj1 = inj._new(p.binding1); if (isPresent(p.keyId2) && isBlank(this.obj2)) this.obj2 = inj._new(p.binding2); if (isPresent(p.keyId3) && isBlank(this.obj3)) this.obj3 = inj._new(p.binding3); if (isPresent(p.keyId4) && isBlank(this.obj4)) this.obj4 = inj._new(p.binding4); if (isPresent(p.keyId5) && isBlank(this.obj5)) this.obj5 = inj._new(p.binding5); if (isPresent(p.keyId6) && isBlank(this.obj6)) this.obj6 = inj._new(p.binding6); if (isPresent(p.keyId7) && isBlank(this.obj7)) this.obj7 = inj._new(p.binding7); if (isPresent(p.keyId8) && isBlank(this.obj8)) this.obj8 = inj._new(p.binding8); if (isPresent(p.keyId9) && isBlank(this.obj9)) this.obj9 = inj._new(p.binding9); } dehydrate() { 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; } getObjByKeyId(keyId: number, visibility: number): any { var p = this.protoStrategy; var inj = this.injector; if (p.keyId0 === keyId && (p.visibility0 & visibility) > 0) { if (isBlank(this.obj0)) { this.obj0 = inj._new(p.binding0); } return this.obj0; } if (p.keyId1 === keyId && (p.visibility1 & visibility) > 0) { if (isBlank(this.obj1)) { this.obj1 = inj._new(p.binding1); } return this.obj1; } if (p.keyId2 === keyId && (p.visibility2 & visibility) > 0) { if (isBlank(this.obj2)) { this.obj2 = inj._new(p.binding2); } return this.obj2; } if (p.keyId3 === keyId && (p.visibility3 & visibility) > 0) { if (isBlank(this.obj3)) { this.obj3 = inj._new(p.binding3); } return this.obj3; } if (p.keyId4 === keyId && (p.visibility4 & visibility) > 0) { if (isBlank(this.obj4)) { this.obj4 = inj._new(p.binding4); } return this.obj4; } if (p.keyId5 === keyId && (p.visibility5 & visibility) > 0) { if (isBlank(this.obj5)) { this.obj5 = inj._new(p.binding5); } return this.obj5; } if (p.keyId6 === keyId && (p.visibility6 & visibility) > 0) { if (isBlank(this.obj6)) { this.obj6 = inj._new(p.binding6); } return this.obj6; } if (p.keyId7 === keyId && (p.visibility7 & visibility) > 0) { if (isBlank(this.obj7)) { this.obj7 = inj._new(p.binding7); } return this.obj7; } if (p.keyId8 === keyId && (p.visibility8 & visibility) > 0) { if (isBlank(this.obj8)) { this.obj8 = inj._new(p.binding8); } return this.obj8; } if (p.keyId9 === keyId && (p.visibility9 & visibility) > 0) { if (isBlank(this.obj9)) { this.obj9 = inj._new(p.binding9); } return this.obj9; } return undefinedValue; } getObjAtIndex(index: number): any { if (index == 0) return this.obj0; if (index == 1) return this.obj1; if (index == 2) return this.obj2; if (index == 3) return this.obj3; if (index == 4) return this.obj4; if (index == 5) return this.obj5; if (index == 6) return this.obj6; if (index == 7) return this.obj7; if (index == 8) return this.obj8; if (index == 9) return this.obj9; throw new OutOfBoundsError(index); } getMaxNumberOfObjects(): number { return _MAX_CONSTRUCTION_COUNTER; } } export class InjectorDynamicStrategy implements InjectorStrategy { objs: any[]; constructor(public protoStrategy: ProtoInjectorDynamicStrategy, public injector: Injector) { this.objs = ListWrapper.createFixedSize(protoStrategy.bindings.length); } hydrate(): void { var p = this.protoStrategy; for (var i = 0; i < p.keyIds.length; i++) { if (isPresent(p.keyIds[i]) && isBlank(this.objs[i])) { this.objs[i] = this.injector._new(p.bindings[i]); } } } dehydrate(): void { ListWrapper.fill(this.objs, null); } getObjByKeyId(keyId: number, visibility: number): any { var p = this.protoStrategy; for (var i = 0; i < p.keyIds.length; i++) { if (p.keyIds[i] === keyId && (p.visibilities[i] & visibility) > 0) { if (isBlank(this.objs[i])) { this.objs[i] = this.injector._new(p.bindings[i]); } return this.objs[i]; } } return undefinedValue; } getObjAtIndex(index: number): any { if (index < 0 || index >= this.objs.length) { throw new OutOfBoundsError(index); } return this.objs[index]; } getMaxNumberOfObjects(): number { return this.objs.length; } } export class BindingData { constructor(public binding: ResolvedBinding, public visibility: number){}; getKeyId(): number { return this.binding.key.id; } } /** * A dependency injection container used for resolving dependencies. * * An `Injector` is a replacement for a `new` operator, which can automatically resolve the * constructor dependencies. * In typical use, application code asks for the dependencies in the constructor and they are * resolved by the `Injector`. * * ## Example: * * Suppose that we want to inject an `Engine` into class `Car`, we would define it like this: * * ```javascript * class Engine { * } * * class Car { * constructor(@Inject(Engine) engine) { * } * } * * ``` * * Next we need to write the code that creates and instantiates the `Injector`. We then ask for the * `root` object, `Car`, so that the `Injector` can recursively build all of that object's *dependencies. * * ```javascript * main() { * var injector = Injector.resolveAndCreate([Car, Engine]); * * // Get a reference to the `root` object, which will recursively instantiate the tree. * var car = injector.get(Car); * } * ``` * Notice that we don't use the `new` operator because we explicitly want to have the `Injector` * resolve all of the object's dependencies automatically. * * @exportedAs angular2/di */ export class Injector { /** * Turns a list of binding definitions into an internal resolved list of resolved bindings. * * A resolution is a process of flattening multiple nested lists and converting individual * bindings into a list of {@link ResolvedBinding}s. The resolution can be cached by `resolve` * for the {@link Injector} for performance-sensitive code. * * @param `bindings` can be a list of `Type`, {@link Binding}, {@link ResolvedBinding}, or a * recursive list of more bindings. * * The returned list is sparse, indexed by `id` for the {@link Key}. It is generally not useful to *application code * other than for passing it to {@link Injector} functions that require resolved binding lists, *such as * `fromResolvedBindings` and `createChildFromResolved`. */ static resolve(bindings: List>): List { var resolvedBindings = resolveBindings(bindings); var flatten = _flattenBindings(resolvedBindings, new Map()); return _createListOfBindings(flatten); } /** * Resolves bindings and creates an injector based on those bindings. This function is slower than * the corresponding `fromResolvedBindings` because it needs to resolve bindings first. See *`resolve` * for the {@link Injector}. * * Prefer `fromResolvedBindings` in performance-critical code that creates lots of injectors. * * @param `bindings` can be a list of `Type`, {@link Binding}, {@link ResolvedBinding}, or a *recursive list of more * bindings. * @param `defaultBindings` Setting to true will auto-create bindings. */ static resolveAndCreate(bindings: List>, {defaultBindings = false}: any = {}): Injector { var resolvedBindings = Injector.resolve(bindings); var bd = resolvedBindings.map(b => new BindingData(b, PUBLIC)); var proto = new ProtoInjector(null, bd, 0); var inj = new Injector(proto); return inj; } /** * Creates an injector from previously resolved bindings. This bypasses resolution and flattening. * This API is the recommended way to construct injectors in performance-sensitive parts. * * @param `bindings` A sparse list of {@link ResolvedBinding}s. See `resolve` for the * {@link Injector}. * @param `defaultBindings` Setting to true will auto-create bindings. */ static fromResolvedBindings(bindings: List, {defaultBindings = false}: any = {}): Injector { var bd = bindings.map(b => new BindingData(b, PUBLIC)); var proto = new ProtoInjector(null, bd, 0); var inj = new Injector(proto); return inj; } _strategy: InjectorStrategy; _parent: Injector; _host: Injector; _constructionCounter: number = 0; // TODO vsavkin remove it after DI and EI are merged private _ei: any; constructor(public _proto: ProtoInjector) { this._strategy = _proto._strategy.createInjectorStrategy(this); } get(token): any { return this._getByKey(Key.get(token), unbounded, false, null); } getOptional(token): any { return this._getByKey(Key.get(token), unbounded, true, null); } getObjAtIndex(index: number): any { return this._strategy.getObjAtIndex(index); } get parent(): Injector { return this._parent; } get strategy() { return this._strategy; } hydrate(parent: Injector, host: Injector, ei: any) { this._constructionCounter = 0; this._parent = parent; this._host = host; this._ei = ei; this._strategy.hydrate(); } dehydrate(): void { this._strategy.dehydrate(); } /** * Creates a child injector and loads a new set of bindings into it. * * A resolution is a process of flattening multiple nested lists and converting individual * bindings into a list of {@link ResolvedBinding}s. The resolution can be cached by `resolve` * for the {@link Injector} for performance-sensitive code. * * @param `bindings` can be a list of `Type`, {@link Binding}, {@link ResolvedBinding}, or a * recursive list of more bindings. * */ resolveAndCreateChild(bindings: List>): Injector { var resovledBindings = Injector.resolve(bindings); var bd = resovledBindings.map(b => new BindingData(b, PUBLIC)); var proto = new ProtoInjector(this._proto, bd, 1); var inj = new Injector(proto); inj._parent = this; return inj; } /** * Creates a child injector and loads a new set of {@link ResolvedBinding}s into it. * * @param `bindings`: A sparse list of {@link ResolvedBinding}s. * See `resolve` for the {@link Injector}. * @returns a new child {@link Injector}. */ createChildFromResolved(bindings: List): Injector { var bd = bindings.map(b => new BindingData(b, PUBLIC)); var proto = new ProtoInjector(this._proto, bd, 1); var inj = new Injector(proto); inj._parent = this; return inj; } _new(binding: ResolvedBinding): any { if (this._constructionCounter++ > this._strategy.getMaxNumberOfObjects()) { throw new CyclicDependencyError(binding.key); } var factory = binding.factory; var deps = binding.dependencies; var length = deps.length; var d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12, d13, d14, d15, d16, d17, d18, d19; try { d0 = length > 0 ? this._getByDependency(deps[0], binding.key) : null; d1 = length > 1 ? this._getByDependency(deps[1], binding.key) : null; d2 = length > 2 ? this._getByDependency(deps[2], binding.key) : null; d3 = length > 3 ? this._getByDependency(deps[3], binding.key) : null; d4 = length > 4 ? this._getByDependency(deps[4], binding.key) : null; d5 = length > 5 ? this._getByDependency(deps[5], binding.key) : null; d6 = length > 6 ? this._getByDependency(deps[6], binding.key) : null; d7 = length > 7 ? this._getByDependency(deps[7], binding.key) : null; d8 = length > 8 ? this._getByDependency(deps[8], binding.key) : null; d9 = length > 9 ? this._getByDependency(deps[9], binding.key) : null; d10 = length > 10 ? this._getByDependency(deps[10], binding.key) : null; d11 = length > 11 ? this._getByDependency(deps[11], binding.key) : null; d12 = length > 12 ? this._getByDependency(deps[12], binding.key) : null; d13 = length > 13 ? this._getByDependency(deps[13], binding.key) : null; d14 = length > 14 ? this._getByDependency(deps[14], binding.key) : null; d15 = length > 15 ? this._getByDependency(deps[15], binding.key) : null; d16 = length > 16 ? this._getByDependency(deps[16], binding.key) : null; d17 = length > 17 ? this._getByDependency(deps[17], binding.key) : null; d18 = length > 18 ? this._getByDependency(deps[18], binding.key) : null; d19 = length > 19 ? this._getByDependency(deps[19], binding.key) : null; } catch (e) { if (e instanceof AbstractBindingError) e.addKey(binding.key); throw e; } var obj; try { 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; case 11: obj = factory(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10); break; case 12: obj = factory(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11); break; case 13: obj = factory(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12); break; case 14: obj = factory(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12, d13); break; case 15: obj = factory(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12, d13, d14); break; case 16: obj = factory(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12, d13, d14, d15); break; case 17: obj = factory(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12, d13, d14, d15, d16); break; case 18: obj = factory(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12, d13, d14, d15, d16, d17); break; case 19: obj = factory(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12, d13, d14, d15, d16, d17, d18); break; case 20: obj = factory(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12, d13, d14, d15, d16, d17, d18, d19); break; } } catch (e) { throw new InstantiationError(e, e.stack, binding.key); } return obj; } private _getByDependency(dep: any, requestor: Key): any { var special = isPresent(this._ei) ? this._ei.getDependency(dep) : undefinedValue; if (special !== undefinedValue) { return special; } else { return this._getByKey(dep.key, dep.visibility, dep.optional, requestor); } } private _getByKey(key: Key, depVisibility: any, optional: boolean, requestor: Key): any { if (key.token === Injector) { return this; } var inj = this; var ei = this._ei; // TODO vsavkin remove after DI and EI are merged var bindingVisibility = isPresent(ei) && ei.isComponentKey(requestor) ? PUBLIC_AND_PRIVATE : PUBLIC; var depth = depVisibility.depth; if (!depVisibility.includeSelf) { depth -= inj._proto.distanceToParent; if (isPresent(inj._parent)) { inj = inj._parent; } else { inj = inj._host; bindingVisibility = depVisibility.crossComponentBoundaries ? PUBLIC : PRIVATE; } } while (inj != null && depth >= 0) { var obj = inj._strategy.getObjByKeyId(key.id, bindingVisibility); if (obj !== undefinedValue) return obj; depth -= inj._proto.distanceToParent; // we check only one mode with the PRIVATE visibility if (bindingVisibility === PRIVATE) break; if (isPresent(inj._parent)) { inj = inj._parent; } else { inj = inj._host; bindingVisibility = depVisibility.crossComponentBoundaries ? PUBLIC : PRIVATE; } } // TODO vsavkin remove after DI and EI are merged if (isPresent(ei)) { var appInj = this._ei.appInjector(requestor); if (optional) { return appInj.getOptional(key); } else { return appInj.get(key); } } if (optional) { return null; } else { throw new NoBindingError(key); } } // TODO vsavkin remove after DI and EI are merged getAppInjector(): Injector { if (isBlank(this._ei)) return this; return this._ei.appInjector(null); } } export function resolveBindings(bindings: List>): List { var resolvedList = ListWrapper.createFixedSize(bindings.length); for (var i = 0; i < bindings.length; i++) { var unresolved = resolveForwardRef(bindings[i]); var resolved; if (unresolved instanceof ResolvedBinding) { resolved = unresolved; // ha-ha! I'm easily amused } else if (unresolved instanceof Type) { resolved = bind(unresolved).toClass(unresolved).resolve(); } else if (unresolved instanceof Binding) { resolved = unresolved.resolve(); } else if (unresolved instanceof List) { resolved = resolveBindings(unresolved); } else if (unresolved instanceof BindingBuilder) { throw new InvalidBindingError(unresolved.token); } else { throw new InvalidBindingError(unresolved); } resolvedList[i] = resolved; } return resolvedList; } function _createListOfBindings( flattenedBindings: Map): List { return MapWrapper.values(flattenedBindings); } function _flattenBindings(bindings: List>, res: Map): Map { ListWrapper.forEach(bindings, function(b) { if (b instanceof ResolvedBinding) { res.set(b.key.id, b); } else if (b instanceof List) { _flattenBindings(b, res); } }); return res; }