2014-09-30 14:56:33 -04:00
|
|
|
import {Map, List, MapWrapper, ListWrapper} from 'facade/collection';
|
|
|
|
import {Binding, BindingBuilder, bind} from './binding';
|
2014-10-06 10:13:33 -04:00
|
|
|
import {ProviderError, NoProviderError, InvalidBindingError,
|
|
|
|
AsyncBindingError, CyclicDependencyError, InstantiationError} from './exceptions';
|
2014-09-30 14:56:33 -04:00
|
|
|
import {Type, isPresent, isBlank} from 'facade/lang';
|
|
|
|
import {Future, FutureWrapper} from 'facade/async';
|
|
|
|
import {Key} from './key';
|
|
|
|
|
2014-10-06 16:24:12 -04:00
|
|
|
var _constructing = new Object();
|
|
|
|
|
|
|
|
class _Waiting {
|
2014-10-06 11:36:22 -04:00
|
|
|
constructor(future:Future) {
|
|
|
|
this.future = future;
|
|
|
|
}
|
|
|
|
}
|
2014-10-06 16:24:12 -04:00
|
|
|
function _isWaiting(obj) {
|
|
|
|
return obj instanceof _Waiting;
|
2014-10-06 11:36:22 -04:00
|
|
|
}
|
|
|
|
|
2014-10-06 10:13:33 -04:00
|
|
|
|
2014-09-30 14:56:33 -04:00
|
|
|
export class Injector {
|
|
|
|
constructor(bindings:List) {
|
|
|
|
var flatten = _flattenBindings(bindings);
|
|
|
|
this._bindings = this._createListOfBindings(flatten);
|
|
|
|
this._instances = this._createInstances();
|
|
|
|
this._parent = null; //TODO: vsavkin make a parameter
|
|
|
|
|
2014-10-06 11:36:22 -04:00
|
|
|
this._asyncStrategy = new _AsyncInjectorStrategy(this);
|
|
|
|
this._syncStrategy = new _SyncInjectorStrategy(this);
|
2014-09-30 14:56:33 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
get(token) {
|
|
|
|
return this.getByKey(Key.get(token));
|
|
|
|
}
|
|
|
|
|
|
|
|
asyncGet(token) {
|
|
|
|
return this.asyncGetByKey(Key.get(token));
|
|
|
|
}
|
|
|
|
|
|
|
|
getByKey(key:Key) {
|
2014-10-06 13:45:24 -04:00
|
|
|
return this._getByKey(key, false, false);
|
2014-09-30 14:56:33 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
asyncGetByKey(key:Key) {
|
2014-10-06 13:45:24 -04:00
|
|
|
return this._getByKey(key, true, false);
|
2014-09-30 14:56:33 -04:00
|
|
|
}
|
|
|
|
|
2014-10-06 11:36:22 -04:00
|
|
|
createChild(bindings:List):Injector {
|
|
|
|
var inj = new Injector(bindings);
|
|
|
|
inj._parent = this; //TODO: vsavkin: change it when optional parameters are working
|
|
|
|
return inj;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_createListOfBindings(flattenBindings):List {
|
|
|
|
var bindings = ListWrapper.createFixedSize(Key.numberOfKeys() + 1);
|
|
|
|
MapWrapper.forEach(flattenBindings, (keyId, v) => bindings[keyId] = v);
|
|
|
|
return bindings;
|
|
|
|
}
|
|
|
|
|
|
|
|
_createInstances():List {
|
|
|
|
return ListWrapper.createFixedSize(Key.numberOfKeys() + 1);
|
|
|
|
}
|
|
|
|
|
2014-10-06 13:45:24 -04:00
|
|
|
_getByKey(key:Key, returnFuture, returnLazy) {
|
|
|
|
if (returnLazy) {
|
|
|
|
return () => this._getByKey(key, returnFuture, false);
|
|
|
|
}
|
|
|
|
|
2014-10-06 11:36:22 -04:00
|
|
|
var strategy = returnFuture ? this._asyncStrategy : this._syncStrategy;
|
2014-09-30 14:56:33 -04:00
|
|
|
|
2014-10-06 11:36:22 -04:00
|
|
|
var instance = strategy.readFromCache(key);
|
|
|
|
if (isPresent(instance)) return instance;
|
|
|
|
|
|
|
|
instance = strategy.instantiate(key);
|
2014-09-30 14:56:33 -04:00
|
|
|
if (isPresent(instance)) return instance;
|
|
|
|
|
|
|
|
if (isPresent(this._parent)) {
|
2014-10-06 13:45:24 -04:00
|
|
|
return this._parent._getByKey(key, returnFuture, returnLazy);
|
2014-09-30 14:56:33 -04:00
|
|
|
}
|
2014-10-03 17:26:49 -04:00
|
|
|
throw new NoProviderError(key);
|
2014-09-30 14:56:33 -04:00
|
|
|
}
|
|
|
|
|
2014-10-06 11:36:22 -04:00
|
|
|
_getInstance(key:Key){
|
|
|
|
if (this._instances.length <= key.id) return null;
|
|
|
|
return ListWrapper.get(this._instances, key.id);
|
2014-09-30 14:56:33 -04:00
|
|
|
}
|
|
|
|
|
2014-10-06 11:36:22 -04:00
|
|
|
_setInstance(key:Key, obj){
|
|
|
|
ListWrapper.set(this._instances, key.id, obj);
|
2014-09-30 14:56:33 -04:00
|
|
|
}
|
|
|
|
|
2014-10-06 11:36:22 -04:00
|
|
|
_getBinding(key:Key){
|
|
|
|
if (this._bindings.length <= key.id) return null;
|
|
|
|
return ListWrapper.get(this._bindings, key.id);
|
2014-09-30 14:56:33 -04:00
|
|
|
}
|
2014-10-06 11:36:22 -04:00
|
|
|
}
|
2014-09-30 14:56:33 -04:00
|
|
|
|
2014-10-06 10:13:33 -04:00
|
|
|
|
2014-10-06 11:36:22 -04:00
|
|
|
class _SyncInjectorStrategy {
|
|
|
|
constructor(injector:Injector) {
|
|
|
|
this.injector = injector;
|
|
|
|
}
|
|
|
|
|
|
|
|
readFromCache(key:Key) {
|
|
|
|
if (key.token === Injector) {
|
|
|
|
return this.injector;
|
2014-09-30 14:56:33 -04:00
|
|
|
}
|
|
|
|
|
2014-10-06 11:36:22 -04:00
|
|
|
var instance = this.injector._getInstance(key);
|
|
|
|
|
2014-10-06 16:24:12 -04:00
|
|
|
if (instance === _constructing) {
|
2014-10-06 11:36:22 -04:00
|
|
|
throw new CyclicDependencyError(key);
|
2014-10-06 16:24:12 -04:00
|
|
|
} else if (isPresent(instance) && !_isWaiting(instance)) {
|
2014-10-06 11:36:22 -04:00
|
|
|
return instance;
|
2014-09-30 14:56:33 -04:00
|
|
|
} else {
|
2014-10-06 11:36:22 -04:00
|
|
|
return null;
|
2014-09-30 14:56:33 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-10-06 11:36:22 -04:00
|
|
|
instantiate(key:Key) {
|
|
|
|
var binding = this.injector._getBinding(key);
|
|
|
|
if (isBlank(binding)) return null;
|
|
|
|
|
|
|
|
if (binding.providedAsFuture) throw new AsyncBindingError(key);
|
|
|
|
|
|
|
|
//add a marker so we can detect cyclic dependencies
|
2014-10-06 16:24:12 -04:00
|
|
|
this.injector._setInstance(key, _constructing);
|
2014-10-06 11:36:22 -04:00
|
|
|
|
|
|
|
var deps = this._resolveDependencies(key, binding);
|
|
|
|
return this._createInstance(key, binding, deps);
|
|
|
|
}
|
|
|
|
|
|
|
|
_resolveDependencies(key:Key, binding:Binding) {
|
2014-10-06 10:13:33 -04:00
|
|
|
try {
|
2014-10-06 13:45:24 -04:00
|
|
|
var getDependency = d => this.injector._getByKey(d.key, d.asFuture, d.lazy);
|
2014-10-06 11:36:22 -04:00
|
|
|
return ListWrapper.map(binding.dependencies, getDependency);
|
2014-10-06 10:13:33 -04:00
|
|
|
} catch (e) {
|
2014-10-06 16:24:12 -04:00
|
|
|
this.injector._setInstance(key, null);
|
2014-10-06 10:13:33 -04:00
|
|
|
if (e instanceof ProviderError) e.addKey(key);
|
|
|
|
throw e;
|
|
|
|
}
|
2014-10-06 11:36:22 -04:00
|
|
|
}
|
2014-10-06 10:13:33 -04:00
|
|
|
|
2014-10-06 11:36:22 -04:00
|
|
|
_createInstance(key:Key, binding:Binding, deps:List) {
|
2014-10-03 17:26:49 -04:00
|
|
|
try {
|
|
|
|
var instance = binding.factory(deps);
|
2014-10-06 11:36:22 -04:00
|
|
|
this.injector._setInstance(key, instance);
|
2014-10-03 17:26:49 -04:00
|
|
|
return instance;
|
|
|
|
} catch (e) {
|
2014-10-06 10:13:33 -04:00
|
|
|
throw new InstantiationError(e, key);
|
2014-09-30 14:56:33 -04:00
|
|
|
}
|
|
|
|
}
|
2014-10-06 11:36:22 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class _AsyncInjectorStrategy {
|
|
|
|
constructor(injector:Injector) {
|
|
|
|
this.injector = injector;
|
|
|
|
}
|
|
|
|
|
|
|
|
readFromCache(key:Key) {
|
|
|
|
if (key.token === Injector) {
|
|
|
|
return FutureWrapper.value(this.injector);
|
|
|
|
}
|
|
|
|
|
|
|
|
var instance = this.injector._getInstance(key);
|
2014-10-06 16:24:12 -04:00
|
|
|
|
|
|
|
if (instance === _constructing) {
|
|
|
|
throw new CyclicDependencyError(key);
|
|
|
|
} else if (_isWaiting(instance)) {
|
2014-10-06 11:36:22 -04:00
|
|
|
return instance.future;
|
|
|
|
} else if (isPresent(instance)) {
|
|
|
|
return FutureWrapper.value(instance);
|
|
|
|
} else {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
instantiate(key:Key) {
|
|
|
|
var binding = this.injector._getBinding(key);
|
|
|
|
if (isBlank(binding)) return null;
|
|
|
|
|
2014-10-06 16:24:12 -04:00
|
|
|
//add a marker so we can detect cyclic dependencies
|
|
|
|
this.injector._setInstance(key, _constructing);
|
|
|
|
|
2014-10-06 11:36:22 -04:00
|
|
|
var deps = this._resolveDependencies(key, binding);
|
|
|
|
var future = FutureWrapper.wait(deps).
|
|
|
|
then(deps => this._findOrCreate(key, binding, deps)).
|
|
|
|
then(instance => this._cacheInstance(key, instance));
|
|
|
|
|
2014-10-06 16:24:12 -04:00
|
|
|
this.injector._setInstance(key, new _Waiting(future));
|
2014-10-06 11:36:22 -04:00
|
|
|
return future;
|
|
|
|
}
|
|
|
|
|
|
|
|
_resolveDependencies(key:Key, binding:Binding):List {
|
2014-10-06 16:24:12 -04:00
|
|
|
try {
|
|
|
|
var getDependency = d => this.injector._getByKey(d.key, true, d.lazy);
|
|
|
|
return ListWrapper.map(binding.dependencies, getDependency);
|
|
|
|
} catch (e) {
|
|
|
|
this.injector._setInstance(key, null);
|
|
|
|
if (e instanceof ProviderError) e.addKey(key);
|
|
|
|
throw e;
|
|
|
|
}
|
2014-10-06 11:36:22 -04:00
|
|
|
}
|
2014-09-30 14:56:33 -04:00
|
|
|
|
2014-10-06 11:36:22 -04:00
|
|
|
_findOrCreate(key:Key, binding: Binding, deps:List) {
|
|
|
|
var instance = this.injector._getInstance(key);
|
2014-10-06 16:24:12 -04:00
|
|
|
if (! _isWaiting(instance)) return instance;
|
2014-10-06 11:36:22 -04:00
|
|
|
return binding.factory(deps);
|
|
|
|
}
|
|
|
|
|
|
|
|
_cacheInstance(key, instance) {
|
|
|
|
this.injector._setInstance(key, instance);
|
|
|
|
return instance
|
2014-09-30 14:56:33 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-10-06 11:36:22 -04:00
|
|
|
|
|
|
|
|
2014-09-30 14:56:33 -04:00
|
|
|
function _flattenBindings(bindings:List) {
|
|
|
|
var res = {};
|
|
|
|
ListWrapper.forEach(bindings, function (b){
|
|
|
|
if (b instanceof Binding) {
|
|
|
|
MapWrapper.set(res, b.key.id, b);
|
|
|
|
|
|
|
|
} else if (b instanceof Type) {
|
|
|
|
var s = bind(b).toClass(b);
|
|
|
|
MapWrapper.set(res, s.key.id, s);
|
|
|
|
|
|
|
|
} else if (b instanceof BindingBuilder) {
|
|
|
|
throw new InvalidBindingError(b.token);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
throw new InvalidBindingError(b);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return res;
|
|
|
|
}
|