1 /*
  2  * 	Copyright (C) 2012-2013 DFKI GmbH
  3  * 	Deutsches Forschungszentrum fuer Kuenstliche Intelligenz
  4  * 	German Research Center for Artificial Intelligence
  5  * 	http://www.dfki.de
  6  * 
  7  * 	Permission is hereby granted, free of charge, to any person obtaining a 
  8  * 	copy of this software and associated documentation files (the 
  9  * 	"Software"), to deal in the Software without restriction, including 
 10  * 	without limitation the rights to use, copy, modify, merge, publish, 
 11  * 	distribute, sublicense, and/or sell copies of the Software, and to 
 12  * 	permit persons to whom the Software is furnished to do so, subject to 
 13  * 	the following conditions:
 14  * 
 15  * 	The above copyright notice and this permission notice shall be included 
 16  * 	in all copies or substantial portions of the Software.
 17  * 
 18  * 	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 
 19  * 	OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 
 20  * 	MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 
 21  * 	IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 
 22  * 	CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 
 23  * 	TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 
 24  * 	SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 25  */
 26 
 27 
 28 
 29 define( [ 'dictionary', 'constants', 'commonUtils', 'jquery' ],
 30 	/**
 31 	 * 
 32 	 * A class for managing the models of the application (MVC-Component). <br>
 33 	 * It's purpose is to load the models automatically.
 34 	 * 
 35 	 * This "class" is a singleton - so that only one instance is in
 36 	 * use.<br>
 37 	 * 
 38 	 * TODO add example for usage (models as "class" / models as "singleton")
 39 	 * 
 40 	 * @class
 41 	 * @name ModelManager
 42 	 * @memberOf mmir
 43 	 * @static
 44 	 * 
 45 	 * @requires jQuery.Deferred
 46 	 * 
 47 	 */
 48 	function( 
 49     		Dictionary,  constants, commonUtils, $
 50 ){
 51 	//the next comment enables JSDoc2 to map all functions etc. to the correct class description
 52 	/** @scope mmir.ModelManager.prototype */
 53 
 54 	// private members
 55     /**
 56      * Array of models
 57      * 
 58      * @type Dictionary
 59      * @private
 60      * 
 61 	 * @memberOf mmir.ModelManager#
 62      */
 63 	var models = new Dictionary();
 64 	
 65 	/**
 66      * Name of the default namespace 
 67      * (within the global space) 
 68      * into which Models will be loaded
 69      * 
 70      * @constant
 71      * @type String
 72      * @private
 73      * 
 74 	 * @memberOf mmir.ModelManager#
 75      */
 76 	var MODEL_DEFAULT_NAMESPACE_NAME = 'mmir';
 77 	
 78 	/**
 79      * The global namespace
 80      * 
 81      * @constant
 82      * @type Object
 83      * @private
 84      * 
 85 	 * @memberOf mmir.ModelManager#
 86      */
 87 	var GLOBAL_NAMESPACE = window;
 88 
 89 	/**
 90 	 * 
 91 	 * This function returns the fully qualified model name (including namespace(s)).
 92 	 * 
 93 	 * @function
 94 	 * @param {String}
 95 	 *            modelClassName the model's class-name (i.e. without namespace)
 96 	 * @returns {String} fully qualified name for the model
 97 	 * @private
 98 	 * 
 99 	 * @memberOf mmir.ModelManager#
100 	 */
101     function getFullModelName(modelClassName){
102     	if( ! MODEL_DEFAULT_NAMESPACE_NAME){
103     		return modelClassName;
104     	}
105     	else {
106     		return MODEL_DEFAULT_NAMESPACE_NAME + '.' + modelClassName;
107     	}
108     }
109     
110     /**
111 	 * 
112 	 * This function returns all loaded models. 
113 	 * 
114 	 * @function
115 	 * @returns {Array<String>} all loaded model names
116 	 * @public
117 	 * @memberOf mmir.ModelManager#
118 	 */
119     function getModelNames(){//TODO export this function on _instance?
120     	return models.getKeys();
121     }
122 
123 	/**
124 	 * This function invokes the method
125 	 * {@link mmir.ModelManager#foundModelsCallBack} to load all
126 	 * models in the path specified by *modelPath*.
127 	 * 
128 	 * @function
129 	 * @param {Function}
130 	 *            myCallbackFunction The callback function from the constructor
131 	 *            which shall be called after the initialization of the
132 	 *            {@link mmir.ModelManager}.
133 	 * @private
134 	 * @memberOf mmir.ModelManager#
135 	 */
136 	function _init(myCallbackFunction) {
137 		
138 		/** @scope mmir.ModelManager.prototype */
139 
140 //		delete _instance.init;
141 		/**
142 		 * <code>init</code> as alias for #getInstance
143 		 * @private
144 		 * @function
145 		 * @name init
146 		 * @memberOf mmir.ModelManager#
147 		 */
148 		_instance.init = _instance.getInstance;
149 
150 		/**
151 		 * 
152 		 * This function returns the fully qualified model name (incl. namespace(s)). 
153 		 * 
154 		 * @function
155 		 * @name getModelByName
156 		 * @param {String|Array<String>} fullModelName the fully qualified model name (i.e. with namespace(s))
157 		 * 								Note, if {String} components/namespaces are separated by a <tt>.</tt> (dot)
158 		 * 								If {Array<String>} the entries correspond to the namespace components (without dots),
159 		 * 								  where the last entry corresponds to the class/singleton name
160 		 * @returns {Object} the "raw" model object (may be a constructor or the main-singleton-namespace).
161 		 * 					 Or <tt>null</tt> if there is no Model with the name.
162 		 * @private
163 		 * 
164 		 * @requires mmir.CommonUtils#isArray
165 		 * 
166 		 * @see mmir.ModelManager#getFullModelName
167 		 * @see mmir.ModelManager#doGetModelInstance
168 		 * 
169 		 * @memberOf mmir.ModelManager#
170 		 */
171         function getModelByName(fullModelName){
172         	var components;
173         	if(commonUtils.isArray(fullModelName)){
174         		components = fullModelName;
175         	}
176         	else {
177         		components = fullModelName.split('.');
178         	}
179         	
180         	var currentNameSpace = GLOBAL_NAMESPACE;
181         	for(var i=0, size = components.length; i < size; ++i){
182         		currentNameSpace = currentNameSpace[components[i]]; 
183     			if(typeof currentNameSpace !== 'undefined' ){
184             		if(i === size-1){
185             			return currentNameSpace;
186             		}
187     			}
188     			else {
189     				console.error('ModelManager.getModelByName: could not find model "'
190     						+(components.join('.'))
191     						+'": invalid namespace/class: '
192     						+components[i]
193     				);
194     				break;
195     			}
196         	}
197         	return null;
198         }
199         
200         /**
201          * Returns the instance for a model implementation:
202          * 
203          * If the model-object is a constructor (i.e. a function),
204          * a new instance is created and returned.
205          * 
206          * Otherwise the model-object itself is returned (e.g. for 
207          * singleton pattern models).
208          * 
209          * @function
210          * @private
211          * 
212 		 * @see mmir.ModelManager#getModelByName
213 		 * 
214 		 * @memberOf mmir.ModelManager#
215          */
216         function doGetModelInstance(modelImplObject){
217         	
218         	
219         	if(typeof modelImplObject === 'function'){
220         		return new modelImplObject();
221         	}
222         	//TODO allow alternative initialization methods for models?:
223 //        	else if(typeof modelImplObject.getInstance === 'function'){
224 //        		return modelImplObject.getInstance();
225 //        	}
226 //        	else if(typeof modelImplObject.init === 'function'){
227 //        		return modelImplObject.init();
228 //        	}
229         	else{
230         		return modelImplObject;
231         	}
232         	
233         	//TODO export to requirejs?
234         	//define(modelImplObject.toString(), function(){ return THE_MODEL_INSTANCE;});
235         	
236         }
237 
238 		var _defer = $.Deferred();
239 		if(myCallbackFunction){
240 			_defer.always(myCallbackFunction);
241 		}
242 
243 		commonUtils.loadImpl(
244 
245 				constants.getModelPath(), 
246 
247 				false, 
248 
249 				function () {
250 					
251 					console.log('[loadModels] done');
252 
253 					_defer.resolve(_instance);
254 				},
255 
256 				function isAlreadyLoaded(name) {
257 					return false; // ( _instance && _instance.getModel(name) ); TODO
258 				}, 
259 
260 				function callbackStatus(status, fileName, msg) {
261 					
262 					if (status === 'info') {
263 						
264 						console.info('[loadModel] "' + fileName);
265 						
266 						var modelName = fileName.charAt(0).toUpperCase() + fileName.slice(1).replace(/\.[^.]+$/g, "");
267 						var fullName = getFullModelName(modelName);
268 						var modelImpl = getModelByName(fullName);
269 						var modelInstance;
270 						if (modelImpl) {
271 							modelInstance = doGetModelInstance(modelImpl);
272 						} else {
273 							console.error('ModelManager.load: Could not find implementation for Model "' + modelName + '" (' + fullName + ') for file ' + fileName);
274 							modelInstance = modelName;
275 						}
276 						models.put(fullName, modelInstance);
277 						
278 					}
279 					else if (status === 'warning') {
280 						console.warn('[loadModel] "' + fileName + '": ' + msg);
281 					}
282 					else if (status === 'error') {
283 						console.error('[loadModel] "' + fileName + '": ' + msg);
284 					}
285 					else {
286 						console.error('[loadModel] ' + status + ' (UNKNOWN STATUS) -> "' + fileName + '": ' + msg);
287 					}
288 				}
289 
290 //				, function callbackAfterLoading(jsfile) {
291 //					var modelName = jsfile.charAt(0).toUpperCase() + jsfile.slice(1).replace(/\.[^.]+$/g, "");
292 //					var fullName = getFullModelName(modelName);
293 //					var modelImpl = getModelByName(fullName);
294 //					var modelInstance;
295 //					if (modelImpl) {
296 //						modelInstance = doGetModelInstance(modelImpl);
297 //					} else {
298 //						console.error('ModelManager.load: Could not find implementation for Model "' + modelName + '" (' + fullName + ') for file ' + jsfile);
299 //						modelInstance = modelName;
300 //					}
301 //					models.put(fullName, modelInstance);
302 //				}
303 		);
304 
305 
306 		return _defer.promise(_instance);
307 	};
308 
309 	/**
310 	 * Object containing the instance of the class {@link mmir.ModelManager}
311 	 * 
312 	 * @type Object
313 	 * @private
314 	 * @ignore
315 	 */
316 	var _instance = {
317 			/** @scope mmir.ModelManager.prototype */
318 
319 			/**
320 			 * @deprecated use ModelManager object directly, e.g. instead of: mmir.ModelManager.getInstance().getModel()
321 			 * 				use: mmir.ModelManager.getModel()
322 			 * 
323 			 * NOTE: ModelManager must be initialized before it can be used.
324 			 * 
325 			 * @memberOf mmir.ModelManager.prototype
326 			 */
327 			getInstance : function () {
328 				return this;
329 			},
330 
331 			// public members
332 			/**
333 			 * This function gets the model by name.
334 			 * 
335 			 * @function
336 			 * @param {String}
337 			 *            modelName Name of the model which should be returned
338 			 * @returns {Object} The model if found, null else
339 			 * @public
340 			 */
341 			getModel : function(modelName) {
342 				var retModel = null;
343 
344 				// TODO implement mechanism for multiple/configurable model namespaces
345 				// (add optional namespace argument to getModel)
346 				var fullModelName = getFullModelName(modelName);
347 
348 				retModel = models.get(fullModelName);
349 				if (!retModel) {
350 					console.error('Could not find Model "' + modelName + '" at ' + fullModelName);
351 					return null;
352 				}
353 				return retModel;
354 			},
355 
356 
357 			/**
358 			 * This function returns all loaded models.
359 			 * 
360 			 * @function
361 			 * @returns {Array} All loaded models
362 			 * @public
363 			 */
364 			getModels : function() {
365 				return models;
366 			},
367 
368 			/**
369 			 * This function must be called before using the {@link mmir.ModelManager}. The Initialization process is asynchronous, 
370 			 * because javascript-files must be loaded (the models), so it forces a synchronous behavior by using
371 			 * a callback function containing the instructions, which rely on the presence of the loaded models.<br>   
372 			 * 
373 			 * It loads the models and then calls the callback functions and returns the instance of this class.
374 			 * 
375 			 * <div class="box important">
376 			 * <b>Note:</b>
377 			 * The callback function should contain all (!) instructions which require the prior loading of the models.<br> 
378 			 * The callback mechanism is necessary, because loading the models is asynchronous.<br><br>
379 			 * If provided, the callback function is invoked with 1 argument, the ModelManager instance:<br>
380 			 * <code> callbackFunction(modelManagerInstance) </code>
381 			 * </div>
382 			 * 
383 			 * NOTE: use EITHER callback-function OR returned Promise -- do not use both!
384 			 * 
385 			 * @function
386 			 * @param {Function} [callbackFunction] 
387 			 * 					The function which should be called after loading all controllers
388 			 * @returns {Promise} 
389 			 * 					a Deferred.promise that gets fulfilled when models are loaded.
390 			 * @example
391 			 * 	function afterLoadingModels(modelManagerInstance){
392 			 * 		var userModel = modelManagerInstance.getModel('User');
393 			 * 		//do something...
394 			 * 	} 
395 			 * 	mmir.ModelManager.create(afterLoadingModels);
396 			 * @public
397 			 */
398 			init: _init
399 
400 	};
401 
402 	return _instance;
403 	
404 });
405