diff core/src/js/di/Container.js @ 29:acdcdf1a8d21

repository reorganized
author cin
date Tue, 26 Jun 2018 19:35:44 +0300
parents src/implab/di/Container.js@fcc63f34d0a2
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/core/src/js/di/Container.js	Tue Jun 26 19:35:44 2018 +0300
@@ -0,0 +1,299 @@
+define([
+    "../declare",
+    "../safe",
+    "../Uuid",
+    "../Deferred",
+    "./ActivationContext",
+    "./Descriptor",
+    "./ValueDescriptor",
+    "./ReferenceDescriptor",
+    "./ServiceDescriptor",
+    "./ActivationError"
+], function (
+    declare,
+    safe,
+    Uuid,
+    Deferred,
+    ActivationContext,
+    Descriptor,
+    Value,
+    Reference,
+    Service,
+    ActivationError) {
+    var Container = declare(null, {
+        _services: null,
+        _cache: null,
+        _cleanup: null,
+        _root: null,
+        _parent: null,
+
+        constructor: function (parent) {
+            this._parent = parent;
+            this._services = parent ? Object.create(parent._services) : {};
+            this._cache = {};
+            this._cleanup = [];
+            this._root = parent ? parent.getRootContainer() : this;
+            this._services.container = new Value(this, true);
+        },
+
+        getRootContainer: function () {
+            return this._root;
+        },
+
+        getParent: function () {
+            return this._parent;
+        },
+
+        /**
+         * 
+         */
+        getService: function (name, def) {
+            var d = this._services[name];
+            if (!d)
+                if (arguments.length > 1)
+                    return def;
+                else
+                    throw new Error("Service '" + name + "' isn't found");
+            if (d.isInstanceCreated())
+                return d.getInstance();
+
+            var context = new ActivationContext(this, this._services);
+
+            try {
+                return d.activate(context, name);
+            } catch (error) {
+                throw new ActivationError(name, context.getStack(), error);
+            }
+        },
+
+        register: function (name, service) {
+            if (arguments.length == 1) {
+                var data = name;
+                for (name in data)
+                    this.register(name, data[name]);
+            } else {
+                if (!(service instanceof Descriptor))
+                    service = new Value(service, true);
+                this._services[name] = service;
+            }
+            return this;
+        },
+
+        onDispose: function (callback) {
+            if (!(callback instanceof Function))
+                throw new Error("The callback must be a function");
+            this._cleanup.push(callback);
+        },
+
+        dispose: function () {
+            if (this._cleanup) {
+                for (var i = 0; i < this._cleanup.length; i++)
+                    this._cleanup[i].call(null);
+                this._cleanup = null;
+            }
+        },
+
+        /**
+         * @param{String|Object} config
+         *  The configuration of the contaier. Can be either a string or an object,
+         *  if the configuration is an object it's treated as a collection of
+         *  services which will be registed in the contaier.
+         * 
+         * @param{Function} opts.contextRequire
+         *  The function which will be used to load a configuration or types for services.
+         *  
+         */
+        configure: function (config, opts) {
+            var p, me = this,
+                contextRequire = (opts && opts.contextRequire);
+
+            if (typeof (config) === "string") {
+                p = new Deferred();
+                if (!contextRequire) {
+                    var shim = [config, new Uuid()].join(config.indexOf("/") != -1 ? "-" : "/");
+                    define(shim, ["require", config], function (ctx, data) {
+                        p.resolve([data, {
+                            contextRequire: ctx
+                        }]);
+                    });
+                    require([shim]);
+                } else {
+                    // TODO how to get correct contextRequire for the relative config module?
+                    contextRequire([config], function (data) {
+                        p.resolve([data, {
+                            contextRequire: contextRequire
+                        }]);
+                    });
+                }
+
+                return p.then(function (args) {
+                    return me._configure.apply(me, args);
+                });
+            } else {
+                return me._configure(config, opts);
+            }
+        },
+
+        createChildContainer: function () {
+            return new Container(this);
+        },
+
+        has: function (id) {
+            return id in this._cache;
+        },
+
+        get: function (id) {
+            return this._cache[id];
+        },
+
+        store: function (id, value) {
+            return (this._cache[id] = value);
+        },
+
+        _configure: function (data, opts) {
+            var typemap = {},
+                d = new Deferred(),
+                me = this,
+                p,
+                contextRequire = (opts && opts.contextRequire) || require;
+
+            var services = {};
+
+            for (p in data) {
+                var service = me._parse(data[p], typemap);
+                if (!(service instanceof Descriptor))
+                    service = new Value(service, false);
+                services[p] = service;
+            }
+
+            me.register(services);
+
+            var names = [];
+
+            for (p in typemap)
+                names.push(p);
+
+            if (names.length) {
+                contextRequire(names, function () {
+                    for (var i = 0; i < names.length; i++)
+                        typemap[names[i]] = arguments[i];
+                    d.resolve(me);
+                });
+            } else {
+                d.resolve(me);
+            }
+            return d.promise;
+        },
+
+        _parse: function (data, typemap) {
+            if (safe.isPrimitive(data) || data instanceof Descriptor)
+                return data;
+            if (data.$dependency)
+                return new Reference(
+                    data.$dependency,
+                    data.lazy,
+                    data.optional,
+                    data["default"],
+                    data.services && this._parseObject(data.services, typemap));
+            if (data.$value) {
+                var raw = !data.parse;
+                return new Value(raw ? data.$value : this._parse(
+                    data.$value,
+                    typemap), raw);
+            }
+            if (data.$type || data.$factory)
+                return this._parseService(data, typemap);
+            if (data instanceof Array)
+                return this._parseArray(data, typemap);
+
+            return this._parseObject(data, typemap);
+        },
+
+        _parseService: function (data, typemap) {
+            var me = this,
+                opts = {
+                    owner: this
+                };
+            if (data.$type) {
+
+                opts.type = data.$type;
+
+                if (typeof (data.$type) === "string") {
+                    typemap[data.$type] = null;
+                    opts.typeMap = typemap;
+                }
+            }
+
+            if (data.$factory)
+                opts.factory = data.$factory;
+
+            if (data.services)
+                opts.services = me._parseObject(data.services, typemap);
+            if (data.inject)
+                opts.inject = data.inject instanceof Array ? data.inject.map(function (x) {
+                    return me._parseObject(x, typemap);
+                }) : me._parseObject(data.inject, typemap);
+            if (data.params)
+                opts.params = me._parse(data.params, typemap);
+
+            if (data.activation) {
+                if (typeof (data.activation) === "string") {
+                    switch (data.activation.toLowerCase()) {
+                        case "singleton":
+                            opts.activation = Service.SINGLETON;
+                            break;
+                        case "container":
+                            opts.activation = Service.CONTAINER;
+                            break;
+                        case "hierarchy":
+                            opts.activation = Service.HIERARCHY;
+                            break;
+                        case "context":
+                            opts.activation = Service.CONTEXT;
+                            break;
+                        case "call":
+                            opts.activation = Service.CALL;
+                            break;
+                        default:
+                            throw new Error("Unknown activation type: " +
+                                data.activation);
+                    }
+                } else {
+                    opts.activation = Number(data.activation);
+                }
+            }
+
+            if (data.cleanup)
+                opts.cleanup = data.cleanup;
+
+            return new Service(opts);
+        },
+
+        _parseObject: function (data, typemap) {
+            if (data.constructor &&
+                data.constructor.prototype !== Object.prototype)
+                return new Value(data, true);
+
+            var o = {};
+
+            for (var p in data)
+                o[p] = this._parse(data[p], typemap);
+
+            return o;
+        },
+
+        _parseArray: function (data, typemap) {
+            if (data.constructor &&
+                data.constructor.prototype !== Array.prototype)
+                return new Value(data, true);
+
+            var me = this;
+            return data.map(function (x) {
+                return me._parse(x, typemap);
+            });
+        }
+
+    });
+
+    return Container;
+});
\ No newline at end of file