| var Module = require('./module'); |
| var autoAnnotate = require('./annotation').parse; |
| |
| |
| var Injector = function(modules, parent) { |
| parent = parent || { |
| get: function(name) { |
| currentlyResolving.push(name); |
| throw error('No provider for "' + name + '"!'); |
| } |
| }; |
| |
| var currentlyResolving = []; |
| var providers = this._providers = Object.create(parent._providers || null); |
| var instances = this._instances = Object.create(null); |
| |
| instances.injector = this; |
| |
| var error = function(msg) { |
| var stack = currentlyResolving.join(' -> '); |
| currentlyResolving.length = 0; |
| return new Error(stack ? msg + ' (Resolving: ' + stack + ')' : msg); |
| }; |
| |
| var get = function(name) { |
| if (!providers[name] && name.indexOf('.') !== -1) { |
| var parts = name.split('.'); |
| var pivot = get(parts.shift()); |
| |
| while(parts.length) { |
| pivot = pivot[parts.shift()]; |
| } |
| |
| return pivot; |
| } |
| |
| if (Object.hasOwnProperty.call(instances, name)) { |
| return instances[name]; |
| } |
| |
| if (Object.hasOwnProperty.call(providers, name)) { |
| if (currentlyResolving.indexOf(name) !== -1) { |
| currentlyResolving.push(name); |
| throw error('Can not resolve circular dependency!'); |
| } |
| |
| currentlyResolving.push(name); |
| instances[name] = providers[name][0](providers[name][1]); |
| currentlyResolving.pop(); |
| |
| return instances[name]; |
| } |
| |
| return parent.get(name); |
| }; |
| |
| var instantiate = function(Type) { |
| var instance = Object.create(Type.prototype); |
| var returned = invoke(Type, instance); |
| |
| return typeof returned === 'object' ? returned : instance; |
| }; |
| |
| var invoke = function(fn, context) { |
| if (typeof fn !== 'function') { |
| throw error('Can not invoke "' + fn + '". Expected a function!'); |
| } |
| |
| var inject = fn.$inject && fn.$inject || autoAnnotate(fn); |
| var dependencies = inject.map(function(dep) { |
| return get(dep); |
| }); |
| |
| // TODO(vojta): optimize without apply |
| return fn.apply(context, dependencies); |
| }; |
| |
| var createChild = function(modules, providersFromParent) { |
| if (providersFromParent && providersFromParent.length) { |
| var fromParentModule = Object.create(null); |
| |
| providersFromParent.forEach(function(name) { |
| if (!providers[name]) { |
| throw new Error('No provider for "' + name + '". Can not use provider from the parent!'); |
| } |
| |
| fromParentModule[name] = [providers[name][2], providers[name][1]]; |
| }); |
| |
| modules.unshift(fromParentModule); |
| } |
| |
| return new Injector(modules, this); |
| }; |
| |
| var factoryMap = { |
| factory: invoke, |
| type: instantiate, |
| value: function(value) { |
| return value; |
| } |
| }; |
| |
| modules.forEach(function(module) { |
| // TODO(vojta): handle wrong inputs (modules) |
| if (module instanceof Module) { |
| module.forEach(function(provider) { |
| var name = provider[0]; |
| var type = provider[1]; |
| var value = provider[2]; |
| |
| providers[name] = [factoryMap[type], value, type]; |
| }); |
| } else if (typeof module === 'object') { |
| Object.keys(module).forEach(function(name) { |
| var type = module[name][0]; |
| var value = module[name][1]; |
| |
| providers[name] = [factoryMap[type], value, type]; |
| }); |
| } |
| }); |
| |
| // public API |
| this.get = get; |
| this.invoke = invoke; |
| this.instantiate = instantiate; |
| this.createChild = createChild; |
| }; |
| |
| module.exports = Injector; |