comparison src/implab/di/Container.js @ 0:fc2517695ee1

Initial commit, draft import of existing work
author cin
date Thu, 01 Jun 2017 13:20:03 +0300
parents
children 93fb6c09f2e1
comparison
equal deleted inserted replaced
-1:000000000000 0:fc2517695ee1
1 define([
2 "../declare",
3 "../safe",
4 "../UUID",
5 "dojo/Deferred",
6 "./ActivationContext",
7 "./Descriptor",
8 "./ValueDescriptor",
9 "./ReferenceDescriptor",
10 "./ServiceDescriptor",
11 "./ActivationError"
12 ], function (
13 declare,
14 array,
15 safe,
16 UUID,
17 Deferred,
18 ActivationContext,
19 Descriptor,
20 Value,
21 Reference,
22 Service,
23 ActivationError) {
24 var Container = declare(null, {
25 _services: null,
26 _cache: null,
27 _cleanup: null,
28 _root: null,
29 _parent: null,
30
31 constructor: function (parent) {
32 this._parent = parent;
33 this._services = parent ? safe.create(parent._services) : {};
34 this._cache = {};
35 this._cleanup = [];
36 this._root = parent ? parent.getRootContainer() : this;
37 this._services.container = new Value(this, true);
38 },
39
40 getRootContainer: function () {
41 return this._root;
42 },
43
44 getParent: function () {
45 return this._parent;
46 },
47
48 getService: function (name, def) {
49 var d = this._services[name];
50 if (!d)
51 if (arguments.length > 1)
52 return def;
53 else
54 throw new Error("Service '" + name + "' isn't found");
55 if (d.isInstanceCreated())
56 return d.getInstance();
57
58 var context = new ActivationContext(this, this._services);
59
60 try {
61 return d.activate(context, name);
62 } catch (error) {
63 throw new ActivationError(name, context.getStack(), error);
64 }
65 },
66
67 register: function (name, service) {
68 if (arguments.length == 1) {
69 var data = name;
70 for (name in data)
71 this.register(name, data[name]);
72 } else {
73 if (!(service instanceof Descriptor))
74 service = new Value(service, true);
75 this._services[name] = service;
76 }
77 return this;
78 },
79
80 onDispose: function (callback) {
81 if (!(callback instanceof Function))
82 throw new Error("The callback must be a function");
83 this._cleanup.push(callback);
84 },
85
86 dispose: function () {
87 if (this._cleanup) {
88 for (var i = 0; i < this._cleanup.length; i++)
89 this._cleanup[i].call(null);
90 this._cleanup = null;
91 }
92 },
93
94 /**
95 * @param{String|Object} config
96 * The configuration of the contaier. Can be either a string or an object,
97 * if the configuration is an object it's treated as a collection of
98 * services which will be registed in the contaier.
99 *
100 * @param{Function} opts.contextRequire
101 * The function which will be used to load a configuration or types for services.
102 *
103 */
104 configure: function (config, opts) {
105 var p, me = this,
106 contextRequire = (opts && opts.contextRequire);
107
108 if (typeof (config) === "string") {
109 p = new Deferred();
110 if (!contextRequire) {
111 var shim = config + "-" + UUID();
112 define(shim, ["require", config], function (ctx, data) {
113 p.resolve([data, {
114 contextRequire: ctx
115 }]);
116 });
117 require([shim]);
118 } else {
119 // TODO how to get correct contextRequire for the relative config module?
120 contextRequire([config], function (data) {
121 p.resolve([data, {
122 contextRequire: contextRequire
123 }]);
124 });
125 }
126
127 return p.then(function (args) {
128 return me._configure.apply(me, args);
129 });
130 } else {
131 return me._configure(config, opts);
132 }
133 },
134
135 createChildContainer: function () {
136 return new Container(this);
137 },
138
139 has: function (id) {
140 return id in this._cache;
141 },
142
143 get: function (id) {
144 return this._cache[id];
145 },
146
147 store: function (id, value) {
148 return (this._cache[id] = value);
149 },
150
151 _configure: function (data, opts) {
152 var typemap = {},
153 d = new Deferred(),
154 me = this,
155 p,
156 contextRequire = (opts && opts.contextRequire) || require;
157
158 var services = {};
159
160 for (p in data) {
161 var service = me._parse(data[p], typemap);
162 if (!(service instanceof Descriptor))
163 service = new Value(service, false);
164 services[p] = service;
165 }
166
167 me.register(services);
168
169 var names = [];
170
171 for (p in typemap)
172 names.push(p);
173
174 if (names.length) {
175 contextRequire(names, function () {
176 for (var i = 0; i < names.length; i++)
177 typemap[names[i]] = arguments[i];
178 d.resolve(me);
179 });
180 } else {
181 d.resolve(me);
182 }
183 return d.promise;
184 },
185
186 _parse: function (data, typemap) {
187 if (safe.isPrimitive(data) || data instanceof Descriptor)
188 return data;
189 if (data.$dependency)
190 return new Reference(
191 data.$dependency,
192 data.lazy,
193 data.optional,
194 data["default"],
195 data.services && this._parseObject(data.services, typemap));
196 if (data.$value) {
197 var raw = !data.parse;
198 return new Value(raw ? data.$value : this._parse(
199 data.$value,
200 typemap), raw);
201 }
202 if (data.$type || data.$factory)
203 return this._parseService(data, typemap);
204 if (data instanceof Array)
205 return this._parseArray(data, typemap);
206
207 return this._parseObject(data, typemap);
208 },
209
210 _parseService: function (data, typemap) {
211 var me = this,
212 opts = {
213 owner: this
214 };
215 if (data.$type) {
216
217 opts.type = data.$type;
218
219 if (typeof (data.$type) === "string") {
220 typemap[data.$type] = null;
221 opts.typeMap = typemap;
222 }
223 }
224
225 if (data.$factory)
226 opts.factory = data.$factory;
227
228 if (data.services)
229 opts.services = me._parseObject(data.services, typemap);
230 if (data.inject)
231 opts.inject = data.inject instanceof Array ? array.map(
232 data.inject,
233 function (x) {
234 return me._parseObject(x, typemap);
235 }) : me._parseObject(data.inject, typemap);
236 if (data.params)
237 opts.params = me._parse(data.params, typemap);
238
239 if (data.activation) {
240 if (typeof (data.activation) === "string") {
241 switch (data.activation.toLowerCase()) {
242 case "singleton":
243 opts.activation = Service.SINGLETON;
244 break;
245 case "container":
246 opts.activation = Service.CONTAINER;
247 break;
248 case "hierarchy":
249 opts.activation = Service.HIERARCHY;
250 break;
251 case "context":
252 opts.activation = Service.CONTEXT;
253 break;
254 case "call":
255 opts.activation = Service.CALL;
256 break;
257 default:
258 throw new Error("Unknown activation type: " +
259 data.activation);
260 }
261 } else {
262 opts.activation = Number(data.activation);
263 }
264 }
265
266 if (data.cleanup)
267 opts.cleanup = data.cleanup;
268
269 return new Service(opts);
270 },
271
272 _parseObject: function (data, typemap) {
273 if (data.constructor &&
274 data.constructor.prototype !== Object.prototype)
275 return new Value(data, true);
276
277 var o = {};
278
279 for (var p in data)
280 o[p] = this._parse(data[p], typemap);
281
282 return o;
283 },
284
285 _parseArray: function (data, typemap) {
286 if (data.constructor &&
287 data.constructor.prototype !== Array.prototype)
288 return new Value(data, true);
289
290 var me = this;
291 return array.map(data, function (x) {
292 return me._parse(x, typemap);
293 });
294 }
295
296 });
297
298 return Container;
299 });