Mercurial > pub > ImplabJs
diff src/implab/di/ServiceDescriptor.js @ 0:fc2517695ee1
Initial commit, draft import of existing work
author | cin |
---|---|
date | Thu, 01 Jun 2017 13:20:03 +0300 |
parents | |
children | 93fb6c09f2e1 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/implab/di/ServiceDescriptor.js Thu Jun 01 13:20:03 2017 +0300 @@ -0,0 +1,291 @@ +define( + [ + "dojo/_base/declare", + "../safe", + "dojo/_base/lang", + "dojo/_base/array", + "./Descriptor", + "./ValueDescriptor" + ], + + function (declare, safe, lang, array, Descriptor, Value) { + var SINGLETON_ACTIVATION = 1, + CONTAINER_ACTIVATION = 2, + CONTEXT_ACTIVATION = 3, + CALL_ACTIVATION = 4, + HIERARCHY_ACTIVATION = 5; + + var injectMethod = function (target, method, context, args) { + var m = target[method]; + if (!m) + throw new Error("Method '" + method + "' not found"); + + if (args instanceof Array) + m.apply(target, context.parse(args, "." + method)); + else + m.call(target, context.parse(args, "." + method)); + }; + + var makeClenupCallback = function (target, method) { + if (typeof (method) === "string") { + return function () { + target[method](); + }; + } else { + return function () { + method(target); + }; + } + }; + + var cacheId = 0; + + var cls = declare( + Descriptor, { + _instance: null, + _hasInstance: false, + _activationType: CALL_ACTIVATION, + _services: null, + _type: null, + _typeMap: null, + _factory: null, + _params: undefined, + _inject: null, + _cleanup: null, + _cacheId: null, + _owner: null, + + constructor: function (opts) { + safe.argumentNotNull(opts, "opts"); + safe.argumentNotNull(opts.owner, "opts.owner"); + + this._owner = opts.owner; + + if (!(opts.type || opts.factory)) + throw new Error( + "Either a type or a factory must be specified"); + + if (typeof (opts.type) === "string" && !opts.typeMap) + throw new Error( + "The typeMap is required when the type is specified by its name"); + + if (opts.activation) + this._activationType = opts.activation; + if (opts.type) + this._type = opts.type; + if (opts.params) + this._params = opts.params; + if (opts.inject) + this._inject = opts.inject instanceof Array ? opts.inject : [opts.inject]; + if (opts.services) + this._services = opts.services; + if (opts.factory) + this._factory = opts.factory; + if (opts.typeMap) + this._typeMap = opts.typeMap; + if (opts.cleanup) { + if (!(typeof (opts.cleanup) === "string" || opts.cleanup instanceof Function)) + throw new Error( + "The cleanup parameter must be either a function or a function name"); + + this._cleanup = opts.cleanup; + } + + this._cacheId = ++cacheId; + }, + + activate: function (context, name) { + + // if we have a local service records, register them first + + var instance; + + switch (this._activationType) { + case 1: // SINGLETON + // if the value is cached return it + if (this._hasInstance) + return this._instance; + + var tof = this._type || this._factory; + + // create the persistent cache identifier for the type + if (safe.isPrimitive(tof)) + this._cacheId = this._type; + else + this._cacheId = safe.oid(tof); + + // singletons are bound to the root container + var container = context.container.getRootContainer(); + + if (container.has(this._cacheId)) { + instance = container.get(this._cacheId); + } else { + instance = this._create(context, name); + container.store(this._cacheId, instance); + if (this._cleanup) + container.onDispose( + makeClenupCallback(instance, this._cleanup)); + } + + this._hasInstance = true; + return (this._instance = instance); + + case 2: // CONTAINER + //return a cached value + if (this._hasInstance) + return this._instance; + + // create an instance + instance = this._create(context, name); + + // the instance is bound to the container + if (this._cleanup) + this._owner.onDispose( + makeClenupCallback(instance, this._cleanup)); + + // cache and return the instance + this._hasInstance = true; + return (this._instance = instance); + case 3: // CONTEXT + //return a cached value if one exists + if (context.has(this._cacheId)) + return context.get(this._cacheId); + // context context activated instances are controlled by callers + return context.store(this._cacheId, this._create( + context, + name)); + case 4: // CALL + // per-call created instances are controlled by callers + return this._create(context, name); + case 5: // HIERARCHY + // hierarchy activated instances are behave much like container activated + // except they are created and bound to the child container + + // return a cached value + if (context.container.has(this._cacheId)) + return context.container.get(this._cacheId); + + instance = this._create(context, name); + + if (this._cleanup) + context.container.onDispose(makeClenupCallback( + instance, + this._cleanup)); + + return context.container.store(this._cacheId, instance); + default: + throw "Invalid activation type: " + this._activationType; + } + }, + + isInstanceCreated: function () { + return this._hasInstance; + }, + + getInstance: function () { + return this._instance; + }, + + _create: function (context, name) { + context.enter(name, this, Boolean(this._services)); + + if (this._activationType != CALL_ACTIVATION && + context.visit(this._cacheId) > 0) + throw new Error("Recursion detected"); + + if (this._services) { + for (var p in this._services) { + var sv = this._services[p]; + context.register(p, sv instanceof Descriptor ? sv : new Value(sv, false)); + } + } + + var instance; + + if (!this._factory) { + var ctor, type = this._type; + + if (typeof (type) === "string") { + ctor = this._typeMap[type]; + if (!ctor) + throw new Error("Failed to resolve the type '" + + type + "'"); + } else { + ctor = type; + } + + if (this._params === undefined) { + this._factory = function () { + return new ctor(); + }; + } else if (this._params instanceof Array) { + this._factory = function () { + var inst = lang.delegate(ctor.prototype); + var ret = ctor.apply(inst, arguments); + return typeof (ret) === "object" ? ret : inst; + }; + } else { + this._factory = function (param) { + return new ctor(param); + }; + } + } + + if (this._params === undefined) { + instance = this._factory(); + } else if (this._params instanceof Array) { + instance = this._factory.apply(this, context.parse( + this._params, + ".params")); + } else { + instance = this._factory(context.parse( + this._params, + ".params")); + } + + if (this._inject) { + array.forEach(this._inject, function (spec) { + for (var m in spec) + injectMethod(instance, m, context, spec[m]); + }); + } + + context.leave(); + + return instance; + }, + + // @constructor {singleton} foo/bar/Baz + // @factory {singleton} + toString: function () { + var parts = []; + + parts.push(this._type ? "@constructor" : "@factory"); + + parts.push(activationNames[this._activationType]); + + if (typeof (this._type) === "string") + parts.push(this._type); + + return parts.join(" "); + } + + }); + + cls.SINGLETON = SINGLETON_ACTIVATION; + cls.CONTAINER = CONTAINER_ACTIVATION; + cls.CONTEXT = CONTEXT_ACTIVATION; + cls.CALL = CALL_ACTIVATION; + cls.HIERARCHY = HIERARCHY_ACTIVATION; + + var activationNames = [ + "", + "{singleton}", + "{container}", + "{context}", + "{call}", + "{hierarchy}" + ]; + + return cls; + }); \ No newline at end of file