import { Factory, FactoryResolver } from './factory'; import { Invocable, InvocableResolver } from './invocable'; import { Injection, StaticInjection, CreatableInjection, DictionaryInjection, PropertyInjection, MethodInjection, InvocableInjection, ObjectInjection, ArrayInjection } from './injection'; export default class Container { private _factoryResolver: FactoryResolver; private _invocableResolver: InvocableResolver; constructor(factoryResolver?: FactoryResolver, invocableResolver?: InvocableResolver) { this._factoryResolver = factoryResolver; this._invocableResolver = invocableResolver; } lookup(specifier: Specifier, constructorArgs?: any[]): T { let factory: Factory = this._factoryResolver.factoryFor(specifier); let instance; if (!constructorArgs) { instance = factory.instance; if (instance !== undefined) { return instance; } let constructorInjections = this._factoryResolver.constructorArgumentsFor(specifier); constructorArgs = constructorInjections ? constructorInjections.map(i => this.evaluateInjection(i)) : []; } instance = factory.create(...constructorArgs); let initializers = this._factoryResolver.initializersFor(specifier); if (initializers) { initializers.forEach(initializer => { let initializerInjections = this._factoryResolver.initializerArgumentsFor(specifier, initializer.name); let initializerArgs = initializerInjections ? initializerInjections.map(i => this.evaluateInjection(i)) : []; initializer.initialize(instance, ...initializerArgs); }); } return instance; } teardown(specifier: Specifier, instance?: any) { let i = instance || this.lookup(specifier); let destructors = this._factoryResolver.destructorsFor(specifier); if (destructors) { destructors.forEach(destructor => { let destructorInjections = this._factoryResolver.destructorArgumentsFor(specifier, destructor.name); let destructorArgs = destructorInjections ? destructorInjections.map(i => this.evaluateInjection(i)) : []; destructor.teardown(i, ...destructorArgs); }); } } invoke(specifier: Specifier, args?: any[]): any { let invocable = this._invocableResolver.invocableFor(specifier); if (!args) { let result = invocable.result; if (result !== undefined) { return result; } let injections = this._invocableResolver.invocableArgumentsFor(specifier); args = injections ? injections.map(i => this.evaluateInjection(i)) : []; } return invocable.invoke(...args); } evaluateInjection(injection: Injection): any { if (typeof injection === 'object') { switch (injection.type) { case 'static': return this.evaluateStaticInjection(injection as StaticInjection); case 'creatable': return this.evaluateCreatableInjection(injection as CreatableInjection); case 'property': return this.evaluatePropertyInjection(injection as PropertyInjection); case 'method': return this.evaluateMethodInjection(injection as MethodInjection); case 'invocable': return this.evaluateInvocableInjection(injection as InvocableInjection); case 'dictionary': return this.evaluateDictionaryInjection(injection as DictionaryInjection); case 'object': return this.evaluateObjectInjection(injection as ObjectInjection); case 'array': return this.evaluateArrayInjection(injection as ArrayInjection); default: throw new Error('Unknown injection type: ${injection.type}'); } } else { return injection; } } private evaluateStaticInjection(injection: StaticInjection): any { return injection.value; } private evaluateCreatableInjection(injection: CreatableInjection): any { return this.lookup(injection.source, injection.constructorArgs); } private evaluatePropertyInjection(injection: PropertyInjection): any { let instance = this.lookup(injection.source, injection.constructorArgs); return instance[injection.property]; } private evaluateMethodInjection(injection: MethodInjection): any { let instance = this.lookup(injection.source, injection.constructorArgs); let method = instance[injection.method]; let args = injection.args ? injection.args.map(i => this.evaluateInjection(i)) : []; return method.apply(instance, args); } private evaluateInvocableInjection(injection: InvocableInjection): any { let args = injection.args ? injection.args.map(i => this.evaluateInjection(i)) : undefined; return this.invoke(injection.source, args); } private evaluateDictionaryInjection(injection: DictionaryInjection): object { let source = injection.value; let result = {}; Object.keys(source).forEach(k => { result[k] = this.evaluateInjection(source[k]); }); return result; } private evaluateObjectInjection(injection: ObjectInjection): object { let { keys, values } = injection; let result = {}; for (let i = 0, l = keys.length; i < l; i++) { let key = keys[i]; let value = values[i]; result[this.evaluateInjection(key)] = this.evaluateInjection(value); } return result; } private evaluateArrayInjection(injection: ArrayInjection): object { return injection.values.map(i => this.evaluateInjection(i)); } }