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 define(['constants', 'stringExtension', 'jquery', 'paramsParseFunc', 'logger', 'module'],
 29 	/**
 30 	 * A Utility class to support various functions.<br>
 31 	 * 
 32 	 * 
 33 	 * @class mmir.CommonUtils
 34 	 * @name mmir.CommonUtils
 35 	 * @static
 36 	 * 
 37 	 * @public
 38 	 * 
 39 	 * @requires StringExtensions
 40 	 * @requires Constants (optionally: jQuery)
 41 	 * @requires mmir.SemanticInterpreter (in {@link mmir.CommonUtils#loadCompiledGrammars})
 42 	 * 
 43      * @requires jQuery.isArray	 in #isArrayHelper
 44      * @requires jQuery.Deferred	 in #loadImpl, #loadDirectoryStructure, #setToCompatibilityMode
 45      * @requires jQuery.ajax		 in #loadDirectoryStructure
 46      * 
 47      * 
 48      * @requires jQuery	 in #resizeFitToSourroundingBox
 49      * 
 50      * 
 51      * @example var isList = mmir.CommonUtils.isArray(list);
 52 	 * 
 53 	 */
 54 	function(
 55 		constants, stringExt, $, paramsParseFunc, Logger, module
 56 ) {
 57 	/** @scope mmir.CommonUtils.prototype *///for jsdoc2
 58 	
 59 	/**
 60 	 * @private
 61 	 * @type CommonUtils
 62      * @memberOf mmir.CommonUtils#
 63 	 */
 64 	var instance = null;
 65 	
 66 	/**
 67 	 * @private
 68 	 * @type Logger
 69      * @memberOf mmir.CommonUtils#
 70 	 */
 71 	var logger = Logger.create(module);
 72 
 73     /**
 74      * JSON-Object containing the directory Structure of the application. Only
 75      * directories defined by the Property
 76      * {@link CommonUtils-constructor-directoriesToParse} are contained
 77      * within the JSON-Object.
 78      * 
 79      * @type JSON
 80      * @private
 81      * @memberOf mmir.CommonUtils#
 82      */
 83     this.directoryStructure;
 84 
 85     /**
 86      * This helper initializes a function for detecting if an Object is an
 87      * Array.
 88      * 
 89      * The helper tries to find functions of JavaScript libraries for this; if
 90      * none can be found, a custom implementation is used.
 91      * 
 92      * The returned function is used by {@link mmir.CommonUtils#isArray}
 93      * 
 94      * NOTE: The current implementation checks jQuery.isArray for presences
 95      * 
 96      * @function
 97      * @private
 98      * @returns {Function} a function that takes one parameter (Object) and
 99      *          returns true if this parameter is an Array (false otherwise)
100      *          
101      * @memberOf mmir.CommonUtils#
102      */
103     var isArrayHelper = function(obj) {
104 
105 		// this is the initializer: the following will overwrite the
106 		// isArray-function
107 		// with the appropriate version (use jQuery method, if present,
108 		// otherwise use alternative)
109 	
110 		// if present, use jQuery method:
111 		if (typeof $ !== 'undefined' && typeof $.isArray === 'function') {
112 		    isArrayHelper = $.isArray;
113 		}
114 		else {
115 		    // use the toString method with well-defined return-value from
116 		    // Object:
117 		    var staticToString = Object.prototype.toString;
118 	
119 		    isArrayHelper = function(obj) {
120 		    	return staticToString.call(obj) === '[object Array]';
121 		    };
122 		}
123     };
124     // initialize the isArray-function
125     isArrayHelper();
126 
127     /**
128      * Constructor-Method of Class {@link mmir.CommonUtils}
129      * 
130      * @param {jQuery}
131      *            [$] the jQuery instance/object (OPTIONAL); some few function
132      *            need jQuery to work correctly (see requires annotations)
133      * 
134      * @constructs mmir.CommonUtils
135      * @memberOf mmir.CommonUtils#
136      * @function
137      * @private
138      */
139     function constructor($, constants) {
140 		// private members.
141     	
142 		/**
143 		 * The Prefix for the file names of partial-files.<br>
144 		 * Files named <PARTIAL-PREFIX>filename.ehtml inside a
145 		 * views-directory are recognized as partials.
146 		 * 
147 		 * @type String
148 		 * @private
149 		 */
150 		var partialsPrefix = '~';
151 	
152 		/**
153 		 * Array of Directories (Strings) to parse at the starting process<br>
154 		 * those directories are then accessable by the functions
155 		 * {@link mmir.CommonUtils#getDirectoryContents} and
156 		 * {@link mmir.CommonUtils#getDirectoryContentsWithFilter}
157 		 * 
158 		 * TODO read from properties (implement mechanism such that
159 		 * \build.settings and this refer to the same resource)
160 		 * 
161 		 * @type Array
162 		 * @private
163 		 */
164 		var directoriesToParse = [
165 			 "www/controllers", 
166 			 "www/views", 
167 			 "www/models", 
168 			 "www/config", 
169 			 "www/mmirf/plugins", 
170 			 "www/helpers"
171 		 ];
172 	
173 		/** @lends mmir.CommonUtils.prototype */
174 		return {
175 			
176 		    /**
177 		     * This function is used by
178 		     * {@link mmir.CommonUtils#getDirectoryContents} and
179 		     * {@link mmir.CommonUtils#getDirectoryContentsWithFilter} to strip the
180 		     * pathname parameter
181 		     * 
182 		     * @function
183 		     * @private
184 		     * @param {string}
185 		     *            pathname The path that should be stripped of "file://" and a
186 		     *            beginning or trailing "/"
187 		     * @returns {String} The stripped pathname - devoid of beginning "file://"
188 		     *          or "/" and trailing "/"
189 		     * 
190 	    	 * @memberOf mmir.CommonUtils.prototype
191 		     */
192 		    stripPathName: function(pathname) {
193 	
194 				// FIXME this is a HACK; TODO handle this in a general way!
195 				var basePath = constants.getBasePath();
196 		
197 				if (pathname.startsWith(basePath)) {
198 				    pathname = pathname.substring(basePath.length);
199 				}
200 		
201 				if (pathname.indexOf("file://") != -1) {
202 				    pathname = pathname.replace("file://", "");
203 				}
204 				if (pathname[pathname.length - 1] == "/") {
205 				    pathname = pathname.substring(0, pathname.length - 1);
206 				}
207 				if (pathname[0] != "/") {
208 				    pathname = "/" + pathname;
209 				}
210 		
211 				return pathname;
212 		    },
213 		    
214 		    // public members.
215 		    /**
216 		     * @function
217 		     * @public
218 		     * @returns {String} The Prefix for the file names of partial-files
219 	    	 * @memberOf mmir.CommonUtils.prototype
220 		     */
221 		    getPartialsPrefix : function() {
222 		    	return partialsPrefix;
223 		    },
224 	
225 		    /**
226 		     * @function
227 		     * @public
228 		     * @returns {Object} Directory structure as json object
229 	    	 * @memberOf mmir.CommonUtils.prototype
230 		     */
231 		    getDirectoryStructure : function() {
232 		    	return this.directoryStructure;
233 		    },
234 		    /**
235 		     * extracts all the strings from a String-Array into a single string
236 		     * 
237 		     * @function
238 		     * @public
239 		     * @returns {string} text
240 	    	 * @memberOf mmir.CommonUtils#
241 		     */
242 		    concatArray : function(array) {
243 				return array.join(', ');
244 		    },
245 		    /**
246 		     * Regular Expression for matching HTML comments.<br>
247 		     * 
248 		     * This RegExp also matches multi-line comments.
249 		     * 
250 		     * Note upon using the RegExp that it does not consider if a HTML
251 		     * comment is specified within a String or data-definition (i.e. the
252 		     * comment is matched regardless were its defined).
253 		     * 
254 		     * @type String|RegExp
255 		     * @public
256 	    	 * @memberOf mmir.CommonUtils.prototype
257 	    	 * 
258 		     * @example <!-- some comment -->
259 		     */
260 		    regexHTMLComment : /<!--([\r\n]|.)*?-->/igm,
261 	
262 		    /**
263 		     * Similar to the jQuery.getScript() function - appending a url of a
264 		     * javascript-source to the header of the main document.<br>
265 		     * This function also calls a callback if the script was loaded.
266 		     * 
267 		     * @function
268 		     * @param {String}
269 		     *            url source of javascript-file
270 		     * @param {Function}
271 		     *            callback callback function
272 		     * @public
273 		     * @async
274 		     * @deprecated instead use  #getLocalScript()
275 	    	 * @memberOf mmir.CommonUtils.prototype
276 		     */
277 		    loadScript : function(url, callback) {
278 				var script = document.createElement("script");
279 				script.type = "text/javascript";
280 				script.src = url;
281 	
282 				if (typeof callback === 'function') {
283 					/** @ignore */
284 					script.onload = function() {
285 					callback();
286 					};
287 				}
288 				document.getElementsByTagName("head")[0].appendChild(script);
289 		    },
290 	
291 		    /**
292 		     * Load all plugins (i.e. JavaScript interfaces for
293 		     * Cordova/Java-Impl. plugins).
294 		     * 
295 		     * @function
296 		     * @param {String} [pluginsPath] OPTIONAL 
297 		     *            Path of the plugins which should be
298 		     *            loaded, e.g.
299 		     *            <b>mmirf/plugins/</b>
300 		     *            
301 		     *            If omitted: the default plugin-path is used
302 		     *            (see {@link mmir.Constants#getPluginsPath})
303 		     *            
304 		     * @param {Function} [cbFunction] OPTIONAL 
305 		     *            The function that should be executed after
306 		     *            the plugins are loaded. If the execution of following
307 		     *            functions is dependent on the present of plugins, they
308 		     *            should be triggered from inside the callback-function
309 		     *            
310 		     * @returns {Promise} a Deferred.promise (see loadImpl())
311 		     * 
312 		     * @async
313 		     * @public
314 	    	 * @memberOf mmir.CommonUtils.prototype
315 		     */
316 		    loadAllCordovaPlugins : function(pluginsPath, cbFunction) {
317 
318 		    	if(typeof pluginsPath === 'function'){
319 		    		cbFunction = pluginsPath;
320 		    		pluginsPath = null;
321 		    	}
322 		    	
323 		    	if(typeof pluginsPath !== 'string'){
324 		    		pluginsPath = constants.getPluginsPath();
325 		    	}
326 		    	
327 		    	// reads all *.js files in /assets/www/mmirf/plugins
328 	        	// and loads them dynamically
329 	        	// IMPORTANT: /assets/www/config/directories.json must be up-to-date!
330 	        	//				(it contains the list of JS-files for the plugins)
331 	        	//				-> use ANT /build.xml for updating 
332 	        	// IMPORTANT: the Java-side implementations of the plugins must be enabled 
333 	        	//				by corresponding entries in /res/plugins.xml file!
334 	        	
335 		    	
336 		    	//FIXME: Cordova 2.x mechanism!!! (remove when switching to 3.x ?)
337 		    	window.plugins = window.plugins || {};
338 		    	
339 		    	
340 				return instance.loadImpl(
341 	            	  pluginsPath, 
342 	            	  false, 
343 	            	  cbFunction,
344 					  function isPluginAlreadyLoaded(pluginFileName) {
345 					      if (window.plugins[pluginFileName.replace(/\.[^.]+$/g, "")]) {//<- regexpr for removing file extension
346 					    	  return true;
347 					      }
348 						  else {
349 							  return false;
350 					      }
351 					  },
352 					  function(status, fileName, msg){
353 					      if (status === 'info') {
354 					    	  if(logger.isInfo()) logger.info('CommonUtils', 'loadAllCordovaPlugins', 'loaded "'+ fileName + '": ' + msg);
355 					      }
356 						  else if (status === 'warning') {
357 							  if(logger.isWarn()) logger.warn('CommonUtils', 'loadAllCordovaPlugins', 'loading "'+ fileName + '": ' + msg);
358 					      }
359 						  else if (status === 'error') {
360 							  logger.error('CommonUtils', 'loadAllCordovaPlugins', 'loading "'+ fileName + '": ' + msg);
361 					      }
362 						  else {
363 							  logger.error('CommonUtils', 'loadAllCordovaPlugins', status + ' (UNKNOWN STATUS) -> "'+ fileName + '": ' + msg);
364 					      }
365 					  }
366 				);
367 		    },
368 	
369 		    /**
370 			 * Get the file path/name for a compiled grammar (executable JavaScript grammars).
371 			 * 
372 			 * @function
373 			 * @param {String} generatedGrammarsPath Path of the grammars which should be loaded, e.g. <b>gen/grammar/</b> 
374 			 * @param {String} grammarId the ID (e.g. language code) for the grammar
375 			 * @param {Boolean} [isFileNameOnly] OPTIONAL
376 			 * 					if TRUE then only the file name will be returned, otherwise the full path is returned
377 			 * 
378 		     * @returns {String} file path / name for the compiled grammar
379 		     *                   (returns an empty string, if there is no compile grammar for the specified grammar ID)
380 		     * 
381 			 * @public
382 	    	 * @memberOf mmir.CommonUtils.prototype
383 			 */
384 		    getCompiledGrammarPath : function(generatedGrammarsPath, grammarId, isFileNameOnly) {
385 		    	var files = instance.getDirectoryContentsWithFilter(generatedGrammarsPath, "*.js");
386 		    	if(!files){
387 		    		return '';
388 		    	}
389 		    	var f, index, id;
390 		    	for(var i=0,size=files.length; i < size; ++i){
391 		    		f = files[i];
392 		    		index = f.lastIndexOf('_');
393 					if (index !== -1) {
394 						id = f.substring(0, index);
395 						if(id === grammarId){
396 							return isFileNameOnly? files[i] : generatedGrammarsPath + files[i];
397 						}
398 					}
399 		    	}
400 		    	return '';
401 		    },
402 		    /**
403 			 * Load all compiled grammars (executable JavaScript grammars).
404 			 * 
405 			 * @function
406 			 * @param {String} generatedGrammarsPath Path of the grammars which should be loaded, e.g. <b>gen/grammar/</b> 
407 			 * @param {Function} cbFunction The function that should be executed after the plugins are loaded. 
408 			 * 					 If the execution of following functions is dependent on the presence of the grammars, 
409 			 * 					 they should be triggered from inside the callback-function.
410 			 * @param {Array<String>} [ignoreGrammarIds] OPTIONAL
411 			 * 					grammar IDs that should be ignored, i.e. not loaded, even if there is a file available
412 			 * 
413 		     * @returns {Promise} a Deferred.promise (see loadImpl())
414 		     * 
415 		     * @requires mmir.SemanticInterpreter (must be loaded as dependency "semanticInterpreter" at least once before this function is loaded)
416 		     * 
417 			 * @async
418 			 * @public
419 	    	 * @memberOf mmir.CommonUtils.prototype
420 			 */
421 		    loadCompiledGrammars : function(generatedGrammarsPath, cbFunction, ignoreGrammarIds) {
422 	
423 				return instance.loadImpl(
424 					generatedGrammarsPath,
425 					false,
426 					cbFunction,
427 					function isGrammarAlreadyLoaded(grammarFileName) {
428 						var i = grammarFileName.lastIndexOf('_');
429 						if (i !== -1) {
430 							var id = grammarFileName.substring(0, i);
431 							if(ignoreGrammarIds){
432 								for(var p in ignoreGrammarIds){
433 									if(ignoreGrammarIds.hasOwnProperty(p) && ignoreGrammarIds[p] == id){
434 										return true;
435 									}
436 								}
437 							}
438 							return require('semanticInterpreter').hasGrammar(id);
439 						} else {
440 							return false;
441 						}
442 					},
443 					function loadCompiledGrammarsStatus(status, fileName, msg) {
444 						if (status === 'info') {
445 							if(logger.isInfo()) logger.info('CommonUtils', 'loadCompiledGrammars', 'loaded "'+ fileName + '": ' + msg);
446 						}
447 						else if (status === 'warning') {
448 							
449 							//filter "already loaded" warnings for ignored files:
450 							if(ignoreGrammarIds && /already loaded/.test(msg)){
451 								for(var p in ignoreGrammarIds){
452 									if(ignoreGrammarIds.hasOwnProperty(p) && fileName.indexOf(ignoreGrammarIds[p]) === 0){
453 										return;/////////////////////// EARLY EXIT ////////////////
454 									}
455 								}
456 							}
457 							
458 							if(logger.isWarn()) logger.warn('CommonUtils', 'loadCompiledGrammars', 'loading "'+ fileName + '": ' + msg);
459 						}
460 						else if (status === 'error') {
461 							logger.error('CommonUtils', 'loadCompiledGrammars', 'loading "' + fileName + '": ' + msg);
462 						}
463 						else {
464 							logger.error('CommonUtils', 'loadCompiledGrammars', status + ' (UNKNOWN STATUS) -> "' + fileName + '": ' + msg);
465 						}
466 					}
467 				);
468 	
469 		    },
470 	
471 		    /**
472 			 * Load implementation files (i.e. JavaScript files) from a directory (if <tt>librariesPath</tt> is a String) or
473 			 * or a list of files-names (if <tt>librariesPath</tt> is an Array of Strings).
474 			 * 
475 			 * 
476 			 * 
477 			 * @function
478 			 * @param {String|Array<String>} librariesPath 
479 			 * 				Path (or list of  of the plugins which should be loaded, e.g. <b>mmirf/plugins/</b>
480 			 * 				NOTE: The (String) path must be an entry in directories.json! 
481 			 *                    (directories.json is used to generate/"query" the file-list for the path)
482 			 * 
483 			 * @param {Boolean} isSerial
484 			 * 				Set <code>true</code> if the libraries should be loaded serially, i.e. synchronously, that is "one after the other" (later ones may depend on earlier ones).
485 			 * 				set <code>false</code> if libraries should be loaded in parallel, i.e. "asychronously" (NOTE in this case, the libraries must not depend on each other).
486 			 * 				
487 			 * 				NOTE: The loading process as a hole is asynchronous (regardless of parameter <tt>isSerial</tt>),
488 			 * 				      i.e. loading is completed when <tt>completedCallback()</tt> is invoked,
489 			 * 				      NOT when this function returns!
490 			 * 
491 			 * @param {Function} [completedCallback] 
492 			 * 				The function that should be executed after the libraries are loaded. 
493 			 * 				If the execution of following functions is dependent on the presence of the libraries,
494 			 * 				they should be capsuled inside this callback-function.
495 			 * @param {Function} [checkIsAlreadyLoadedFunc] 
496 			 * 				If provided, this function checks (based on the file-name), if the library is already
497 			 * 				loaded.
498 			 * 				The signature for the callback is  <code>checkIsAlreadyLoadedFunc(String fileName) return [true|false]</code>,
499 			 * 				i.e. the function may check - based on the file-name - if the library is already loaded.
500 			 * 				If the function returns <tt>true</tt>, the library will not be loaded, and loading continues
501 			 * 				with the next library-file.
502 			 * 
503 			 * 				NOTE: if <tt>isSerial</tt> is <tt>flase</tt>, libraries with lower indices in the list may
504 			 * 				      still be loading, when later entries are checked with this callback. In consequence,
505 			 * 				      the "is already loaded"-check may not be accurate, in case parallel loading is
506 			 * 				      used and the library-list contains "duplicate" entries.
507 			 * @param {Function} [statusCallback] 
508 			 * 				If provided, this function is invoked, when a library was loaded loaded (INFO) or an
509 			 * 				error occurs.
510 			 * 				The signature for the callback is 
511 			 * 				<code>statusCallback(String statusLevel, String fileName, String message)</code>
512 			 * 				where <tt>statusLevel</tt> is one of <tt>info, warning, error</tt>,
513 			 * 				      <tt>fileName</tt> is the file-name for the library that this status message concerns, and
514 			 * 				      <tt>message</tt> is a message text with details concerning the status
515 			 * 
516 			 * @returns {Promise} a Deferred.promise that will be fullfilled when loadImpl() has finished.
517 			 * 
518 			 * @async
519 			 * @public
520 	    	 * @memberOf mmir.CommonUtils.prototype
521 			 */
522 		    loadImpl: function (librariesPath, isSerial, completedCallback, checkIsAlreadyLoadedFunc, statusCallback){
523 	
524 		    	var _defer = $.Deferred();
525 				
526 				if(completedCallback){
527 					_defer.always(completedCallback);
528 				}
529 				
530 				var isPath = true;//TODO use this for creating absolute paths (-> in case librariesPath is an Array)!
531 				var theFileList;
532 				if(typeof librariesPath === 'string'){
533 					theFileList = instance.getDirectoryContentsWithFilter(librariesPath, "*.js");
534 				}
535 				else {
536 					isPath = false;
537 					theFileList = librariesPath;
538 					librariesPath = '';
539 				}
540 				
541 				var size = theFileList.length;
542 				var progress = 0;
543 				
544 				var doLoadImplFile = function doLoadImplFile(fileList, index){
545 					
546 					if( ! index){
547 						index = 0;
548 					}
549 						
550 					var fileName = fileList[index];
551 					
552 					if ( checkIsAlreadyLoadedFunc && checkIsAlreadyLoadedFunc(fileName) ){
553 						
554 						if(statusCallback){
555 							statusCallback('warning', fileName, 'already loaded ' + librariesPath+fileName);
556 						}
557 						
558 						++progress;
559 						//synchronous load: load next recursively
560 						if(isSerial){
561 							doLoadImplFile(fileList, index+1);
562 						}
563 						
564 					} else {
565 						
566 						//handler that is invoked after file has been loaded:
567 						var handleScriptDone = function(){
568 							//"notify" that this file has been DONE:
569 							++progress;
570 							
571 							//check: are all entries of the list done?
572 							if (progress < size){
573 								
574 								if( isSerial ){
575 									//synchronous load: load next entry recursively, when previous, i.e. this, one has finished:
576 									doLoadImplFile(fileList, index+1);
577 								}
578 								//all entries already have been processed -> stop now.
579 								return;
580 							}
581 							
582 							//ASSERT: all entries of the file-list are DONE -> triggere completedCallback
583 							
584 	//						if (typeof completedCallback == 'function'){
585 	//							completedCallback();
586 	//						} else {
587 	//							if(statusCallback){
588 	//								statusCallback('warning', fileName, 'provided callback for COMPLETION is not a function: '+completedCallback);
589 	//							}
590 	//							else {
591 	//								logger.warn('[loadImpl] callback for COMPLETION is not a function: '+completedCallback);
592 	//							}
593 	//						}
594 							_defer.resolve();
595 						};
596 					
597 						/// ATTENTION: $.getScript --> mobileDS.CommonUtils.getInstance().getLocalScript
598 						/// under Android 4.0 getScript is not wokring properly
599 						instance.getLocalScript(librariesPath+fileName, 
600 							function(){
601 								
602 								if(statusCallback){
603 									statusCallback('info', fileName, 'done loading ' + librariesPath+fileName);
604 								}
605 								
606 								handleScriptDone();
607 							},
608 							function(exception) {
609 								if(statusCallback){
610 									statusCallback('error', fileName, 'could not load "' + librariesPath+fileName + '": ' + exception);
611 								}
612 								else {
613 									// print out an error message
614 									logger.error('[loadImpl] Could not load "' + librariesPath+fileName + '": ', exception);
615 								}
616 								
617 								//NOTE: in case of an error, will still try to load the other files from the list:
618 	
619 								handleScriptDone();
620 							}
621 						);//END: getLocalScript(callbacks)
622 					}
623 				};//END: doLoadImplFile(name,index)
624 				
625 				if(logger.isVerbose()) logger.verbose('about to load all libraries from path "'+librariesPath+'"...');
626 				
627 				if(size < 1){
628 					//if there are no files to resolve: 
629 					// immediately resolve Promise / trigger callback
630 					_defer.resolve();
631 				}
632 				else if( ! isSerial){
633 					//asynchronous load: trigger loading for all at once:
634 					for(var counter=0; counter < size; ++counter){
635 						doLoadImplFile(theFileList, counter);
636 					}
637 				}
638 				else {
639 					//synchronous load: start with first (the next one will be loaded recursively, when the first one was loaded)
640 					doLoadImplFile(theFileList);
641 				}
642 	
643 				return _defer.promise();
644 			},
645 	
646 		    /**
647 		     * Detects via the user-agent-string if the application is running
648 		     * on Android.
649 		     * 
650 		     * @function
651 		     * @public
652 		     * @returns {Boolean} <b>True</b> if application is running on
653 		     *          Android, <b>False</b> otherwise
654 		     *          
655 	    	 * @memberOf mmir.CommonUtils.prototype
656 		     */
657 		    isRunningOnAndroid : function() {
658 				// Testing if user-Agent-/ or appVersion-String contains 'android'
659 				if ((navigator.userAgent.toLowerCase().indexOf("android") > -1)
660 					|| (navigator.appVersion.toLowerCase().indexOf("android") > -1)) {
661 					
662 					return true;
663 				}
664 				else {
665 					return false;
666 				}
667 		    },
668 	
669 		    /**
670 		     * Should detect - via the user-agent-string - if the application is
671 		     * running on Android, Symbian or iOS; in other words: on a
672 		     * smartphone.
673 		     * 
674 		     * @function
675 		     * @public
676 		     * @returns {Boolean} <b>True</b> if application is running on
677 		     *          smartphone, <b>False</b> otherwise
678 		     *          
679 	    	 * @memberOf mmir.CommonUtils.prototype
680 		     */
681 		    isRunningOnSmartphone : function() {
682 				// Testing if user-Agent-/ or appVersion-String contains
683 				// 'Android' or 'iOS'
684 				// at the moment only Android-, iOS and Symbian-strings are 'implemented'
685 				var testString = navigator.userAgent.toLowerCase()
686 									+ navigator.appVersion.toLowerCase();
687 				
688 				if ((testString.indexOf("android") > -1)
689 					|| (testString.indexOf("ios") > -1)
690 					|| (testString.indexOf("symbian") > -1)) {
691 					
692 					return true;
693 				}
694 				else {
695 					return false;
696 				}
697 		    },
698 	
699 		    /**
700 		     * <div class="box important"> <b>Note:</b> On Android 4.0
701 		     * jQuery.getScript() is not working properly - so use this function instead!
702 		     * </div>
703 		     * 
704 		     * Similar to the jQuery.getScript() function - appending a url of a
705 		     * javascript-source to the header of the main document.<br>
706 		     * This function also calls a success-callback if the script was
707 		     * successfully loaded or a fail-callback.<br>
708 		     * 
709 		     * 
710 		     * @function
711 		     * @param {String}
712 		     *            scriptUrl source of javascript-file
713 		     * @param {Function}
714 		     *            success success callback function
715 		     * @param {Function}
716 		     *            fail fail callback function
717 		     * @async
718 		     * @public
719 	    	 * @memberOf mmir.CommonUtils.prototype
720 		     */
721 		    getLocalScript : function(scriptUrl, success, fail) {
722 				var head = document.getElementsByTagName('head')[0];
723 				var script = document.createElement('script');
724 				script.type = 'text/javascript';
725 				script.src = scriptUrl;
726 				script.onload = function() {
727 					if(success){
728 						success.apply(this, arguments);
729 					} 
730 				};
731 				script.onerror = function(e) {
732 					if(fail){
733 						fail.apply(this, arguments);
734 					}
735 					else {
736 						logger.error('CommonUtils', 'getLocalScript', 'Loading Script Failed from "' + scriptUrl + '"', e);
737 					}
738 				};
739 				head.appendChild(script);
740 		    },
741 	
742 		    /**
743 		     * This function returns an array of strings with the contents of a
744 		     * directory.
745 		     * 
746 		     * @function
747 		     * @param {String}
748 		     *            pathname Path of the directory which contents should
749 		     *            be returned
750 		     * @public
751 		     * @returns {Array} Array of Strings which contains the contents of
752 		     *          the directory
753 		     *          
754 	    	 * @memberOf mmir.CommonUtils.prototype
755 		     */
756 		    getDirectoryContents : function(pathname) {
757 				var retValue;
758 	
759 				pathname = this.stripPathName(pathname);
760 	
761 				try {
762 					retValue = this.directoryStructure[pathname];
763 				} catch (e) {
764 					logger.error(e);
765 					retValue = null;
766 				}
767 				return retValue;
768 		    },
769 	
770 		    /**
771 		     * This function returns an array of strings with the contents of a
772 		     * directory, giving only those files which match the filter.
773 		     * 
774 		     * @function
775 		     * @param {String} pathname
776 		     *            Path of the directory which's contents should be
777 		     *            returned
778 		     * @param {String} filter
779 		     *            Filter for file-names which may contain a wildcard, 
780 		     *            e.g.: <b>*.js</b>, <b>*</b> or <b>*.ehtml</b>
781 		     * @public
782 		     * @returns {Array} Array of Strings which contains the contents of
783 		     *          the directory.
784 		     *          Or <code>null</code>, if no matching contents could be
785 		     *          found.
786 		     *          
787 	    	 * @memberOf mmir.CommonUtils.prototype
788 		     */
789 		    getDirectoryContentsWithFilter : function(pathname, filter) {
790 		    	
791 				var retValue = [];
792 	
793 				var tmpfilter = '^' + filter.replace('.', '\\.').replace('*', '.*').replace('\$', '\\$') + '$'; // e.g.,// '^.*\.js$'
794 	
795 				var filterRegExp = new RegExp(tmpfilter, 'gi');
796 	
797 				pathname = this.stripPathName(pathname);
798 	
799 				try {
800 					var tmp = this.directoryStructure[pathname];
801 					if (typeof tmp === 'undefined') {
802 						if(logger.isInfo()) logger.info('CommonUtils', 'getDirectoryContentsWithFilter', '[' + pathname + ' | ' + filter + ']  not found.');
803 						retValue = null;
804 					} 
805 					else {
806 						for (var i = 0; i < tmp.length; i++) {
807 							if (tmp[i].match(filterRegExp)) {
808 								retValue.push(tmp[i]);
809 							}
810 						}
811 					}
812 				} catch (e) {
813 					logger.error('CommonUtils', 'getDirectoryContentsWithFilter', '[' + pathname + ' | ' + filter + '] ', e);
814 					retValue = null;
815 				}
816 				return retValue;
817 		    },
818 	
819 		    /**
820 		     * Checks if an object is an <code>Array</code>.
821 		     * 
822 		     * <p>
823 		     * This function can be savely run in arbirtray contexts, e.g.
824 		     * 
825 		     * <pre>
826 		     *  var checkArray = mmir.CommonUtils.getInstance().isArray;
827 		     * if( checkArray(someObject) ){
828 		     *   ...
829 		     * </pre>
830 		     * 
831 		     * @function
832 		     * @param {Object}
833 		     *            object the Object for checking if it is an Array
834 		     * @public
835 		     * @returns {Boolean} <code>true</code> if <code>object</code>
836 		     *          is an <code>Array</code>, otherwise
837 		     *          <code>false</code>.
838 		     *          
839 	    	 * @memberOf mmir.CommonUtils.prototype
840 		     */
841 		    isArray : function(object) {
842 				return isArrayHelper(object);
843 		    },
844 	
845 		    /**
846 		     * This function iterates over all elements of a specific class and
847 		     * changes the font-size of the contained text to the maximal
848 		     * possible size - while still being small enough to fit in the
849 		     * element.
850 		     * 
851 		     * @function
852 		     * @param {String}
853 		     *            class_name Name of the class which inner text should
854 		     *            be fitted to the size of the element
855 		     * 
856 		     * @requires jQuery
857 		     * @public
858 	    	 * @memberOf mmir.CommonUtils.prototype
859 		     */
860 		    resizeFitToSourroundingBox : function(class_name) {
861 				// resize the font in box_fit-class, so that it won't overlap its div-box
862 				$(function() {
863 	
864 					var smallest_font = 1000;
865 					$(class_name).each(function(i, box) {
866 						var width = $( box ).width(),
867         					html = '<span style="white-space:nowrap">',
868         					line = $( box ).wrapInner( html ).children()[ 0 ],
869         					n = parseInt($( box ).css("font-size"), 10);
870         				
871         				$( box ).css( 'font-size', n );
872 
873         				while ( $( line ).width() > width ) {
874         					$( box ).css( 'font-size', --n );
875         				}
876 
877         				$( box ).text( $( line ).text() );
878         				
879         				n = parseInt($( box ).css("font-size"), 10);
880         				
881 						if (n < smallest_font) {
882 							smallest_font = n;
883 						}
884 					});
885 	
886 					$(class_name).each(function(i, box) {
887 						$(box).css('font-size', smallest_font);
888 					});
889 				});
890 		    },
891 	
892 		    /**
893 		     * Converts the object to a valid JSON String value.
894 		     * 
895 		     * Ensures that the returned value does not contain (un-escaped)
896 		     * double-quotes, so that the returned value can be used as a JSON
897 		     * value, e.g. </br>
898 		     * 
899 		     * @function
900 		     * @param {Object}
901 		     *            theObjectValue the object to convert to a JSON String
902 		     *            value. If NULL or UNDEFINED, an EMPTY String will be
903 		     *            returned
904 		     * @returns {String} the String value
905 		     * @public
906 	    	 * @memberOf mmir.CommonUtils.prototype
907 	    	 * 
908 		     * @example
909 		     *  var jsonValue = mmir.CommonUtils.toJSONStringValue(someValue);
910 		     *  var data = JSON.parse('"theValue":"' + jsonValue + '"');
911 		     */
912 		    toJSONStringValue : function(theObjectValue) {
913 				if (typeof theObjectValue !== 'undefined' && theObjectValue !== null) {
914 					if (typeof theObjectValue !== 'string') {
915 						theObjectValue = theObjectValue.toString();
916 					}
917 					theObjectValue = theObjectValue.escapeDoubleQuotes();
918 				}
919 				else {
920 					theObjectValue = '';
921 				}
922 				return theObjectValue;
923 		    },
924 	
925 		    /**
926 		     * Converts the object to a valid JSON String value.
927 		     * 
928 		     * Ensures that the returned value does not contain (un-escaped)
929 		     * double-quotes, so that the returned value can be used as a JSON
930 		     * value, also does replace all newlines with the HTML-equivalent
931 		     * '<br/>', e.g.
932 		     * 
933 		     * @function
934 		     * @param {Object}
935 		     *            theObjectValue the object to convert to a JSON String
936 		     *            value. If NULL or UNDEFINED, an EMPTY String will be
937 		     *            returned
938 		     * @returns {String} the String value
939 		     * @public
940 	    	 * @memberOf mmir.CommonUtils.prototype
941 	    	 * 
942 		     * @example 
943 		     *  var jsonValue = mmir.CommonUtils.convertJSONStringValueToHTML(someValue);
944 		     *  var data = JSON.parse('"theValue":"' + jsonValue + '"');
945 		     *  ...
946 		     */
947 		    convertJSONStringValueToHTML : function(str) {
948 				if (typeof str !== 'undefined' && str !== null) {
949 				
950 					if (typeof str !== 'string') {
951 						str = str.toString();
952 					}
953 					// escape double-quotes, if necessary
954 					// replace (all variants of) newlines with HTML-newlines
955 					str = str.escapeDoubleQuotes().replaceAll('\r\n', '<br/>')
956 							.replaceAll('\n', '<br/>')
957 							.replaceAll('\r', '<br/>');
958 				} else {
959 					str = '';
960 				}
961 				return str;
962 	
963 		    },
964 	
965 		    /**
966 		     * Converts the object's direct properties to a valid JSON String
967 		     * (i.e. no recursion for Object properties).
968 		     * 
969 		     * @function
970 		     * @param {Object}
971 		     *            _o the object to convert to a JSON String.
972 		     * @returns {String} the String value
973 		     * @public
974 	    	 * @memberOf mmir.CommonUtils.prototype
975 		     */
976 		    convertJSONStringToHTML : function(_o) {
977 				// var parse = function(_o){
978 				var a = new Array(), t;
979 				for ( var p in _o) {
980 					if (_o.hasOwnProperty(p)) {
981 						t = _o[p];
982 						if (t != null) {
983 							if (t && typeof t == "object") {
984 								a[a.length] = p + ":{ " + arguments.callee(t).join(", ") + "}";
985 							}
986 							else {
987 								if (typeof t == "string") {
988 									a[a.length] = [ p + ": \"" + t.toString() + "\"" ];
989 								} 
990 								else {
991 									a[a.length] = [ p + ": " + t.toString() ];
992 								}
993 							}
994 						}
995 					}
996 				}
997 				// return a;
998 				// };
999 				// return "{" + parse(o).join(", ") + "}";
1000 	
1001 				return "{" + a.join(", ") + "}";
1002 		    },
1003 	
1004 		    /**
1005 		     * 
1006 		     * IMPORTED FROM paramsParseFunc.js
1007 		     * <p>
1008 		     * 
1009 		     * Convert parameter-part of an URL to a "dictionary", containing
1010 		     * the parameter keys and values
1011 		     * <p>
1012 		     * 
1013 		     * 	<code>?id=5&name=heinz&name=kunz</code> →
1014 		     * 	<pre>
1015 		     * 	{
1016 		     * 	  id: "5",
1017 		     * 	  name: ["heinz", "kunz"],
1018 		     *    
1019 		     * 	  //utility functions
1020 		     * 	  has: function(key) : Boolean,
1021 		     * 	  isMultiple: function(key) : Boolean,// is entry an Array of values
1022 		     * 	  getKeys: function() : String[],     // get list of all keys
1023 		     * 	}
1024 		     * 	</pre>
1025 		     * <p>
1026 		     * 
1027 		     * The returnd "dictionary" has the following functions:
1028 		     * <ul>
1029 		     * <li>has(String key): returns <code>true</code> if the
1030 		     * dictionary contains an entry for <code>key</code></li>
1031 		     * <li>isMultiple(String key): returns <code>true</code> if the
1032 		     * entry for <code>key</code> is an Array (i.e. the URL contained
1033 		     * multiple values for this key)</li>
1034 		     * <li>getKeys(): returns an Array with the keys (String) for all
1035 		     * entries</li>
1036 		     * </ul>
1037 		     * 
1038 		     * @function
1039 		     * @param {String} urlParamsPartStrings
1040 		     *            the parameter-part of the URL, i.e. <code>&...</code>
1041 		     * @return {Object} a "dictionary" for the parameters
1042 		     * @public
1043 			 * @memberOf mmir.CommonUtils.prototype
1044 		     */
1045 		    parseParamsToDictionary : paramsParseFunc,
1046 		    
1047 		    /**
1048 		     * This function is used check whether a network connection is
1049 		     * enabled. </br> This version of checking the network connection is
1050 		     * based on the cordova 2.3.0 API.
1051 		     * 
1052 		     * TODO implement with HTML5 functions (in addition to / instead of
1053 		     * cordova)?
1054 		     * 
1055 		     * @requires Cordova: org.apache.cordova.network-information
1056 		     * 
1057 		     * @function
1058 		     * @public
1059 		     * @returns {Boolean} <code>true</code> if a network connection is enabled
1060 		     * 
1061 	    	 * @memberOf mmir.CommonUtils.prototype
1062 		     */
1063 		    checkNetworkConnection : function() {
1064 		    	
1065 	        	if(logger.isVerbose()) logger.verbose("Checking network status...");
1066 				
1067 				if(typeof navigator === 'undefined'){
1068 					logger.error('Cannot check network status: navigator object is not available!');
1069 					return 'UNKNOWN';
1070 				}
1071 				
1072 				//ASSERT: navigator exists
1073 				
1074 				if(!navigator.connection){
1075 					if(logger.isInfo()) logger.warn('Cannot check network status: object navigator.connection is not available');
1076 					if(typeof navigator.onLine !== 'undefined'){
1077 						return navigator.onLine;
1078 					}
1079 					else {
1080 						return 'UNKNOWN';
1081 					}
1082 				}
1083 				var networkState = navigator.connection.type;
1084 	
1085 				//TODO make states-obj a 'private' field of CommonUtils 
1086 				var states = {};
1087 				states[Connection.UNKNOWN]  = 'Unknown connection';
1088 				states[Connection.ETHERNET] = 'Ethernet connection';
1089 				states[Connection.WIFI]     = 'WiFi connection';
1090 				states[Connection.CELL_2G]  = 'Cell 2G connection';
1091 				states[Connection.CELL_3G]  = 'Cell 3G connection';
1092 				states[Connection.CELL_4G]  = 'Cell 4G connection';
1093 				states[Connection.CELL]     = 'Cell generic connection';
1094 				states[Connection.NONE]     = 'No network connection';
1095 	
1096 				if (Connection.NONE === networkState){
1097 					//alert('Connection type: ' + states[networkState]);
1098 					return false;
1099 				}
1100 				return true;
1101 		    },
1102 		    
1103 			/**
1104 			 * Parses the directory structure - paths given by property {@link mmir.CommonUtils-constructor-directoriesToParse} - and storing the result in the class-property {@link mmir.CommonUtils-directoryStructure}
1105 			 * 
1106 			 * @function
1107 			 * @param {Function} [success] The function that should be executed after the diretories are parsed - it's best to include all following functions inside the callback-function.
1108 			 * @param {Function} [errorFunc] callback function that is invoked if an error occured during initialization. 
1109 			 * @async
1110 			 * @public
1111 	    	 * @memberOf mmir.CommonUtils.prototype
1112 			 */
1113 		    loadDirectoryStructure: function (success, errorFunc) {
1114 				var _defer = $.Deferred();
1115 				var self = this;
1116 				
1117 				if(success){
1118 					_defer.done(success);
1119 				}
1120 				if(errorFunc){
1121 					_defer.fail(errorFunc);
1122 				}
1123 
1124 				var directoryFileUrl = constants.getDirectoriesFileUrl();
1125 				
1126 				//load configuration file asynchronously: 
1127 				$.ajax({
1128 					async: true,
1129 					dataType: "json",
1130 					url: directoryFileUrl,
1131 					success: function(data){
1132 						if(logger.isVerbose()) logger.verbose("DirectoryListing.getDirectoryStructure: loaded file from "+directoryFileUrl);
1133 						
1134 						if(data){
1135 							if(logger.isVerbose()) logger.verbose("DirectoryListing.getDirectoryStructure: Succeeded to load directory structure from '"+directoryFileUrl+"'! Data: "+ JSON.stringify(data));
1136 							
1137 							self.directoryStructure = data;
1138 							
1139 							if(logger.isVerbose()) logger.verbose("[getDirectoryStructure] finished.");//debug
1140 							
1141 							_defer.resolve(self);
1142 						}
1143 					},
1144 					error: function(jqXHR, textStatus, errorThrown){
1145 						if(logger.isVerbose()) logger.verbose("DirectoryListing.getDirectoryStructure: failed to load file from '"+directoryFileUrl+"'! Status "+textStatus+": "+ errorThrown+ ", "+JSON.stringify(jqXHR));
1146 						
1147 						var msg = "[ERROR] " + textStatus+": failed to load file from '"+directoryFileUrl+"' - "+ errorThrown;
1148 						if( ! errorFunc){
1149 							logger.error('CommonUtils', 'loadDirectoryStructure', msg);
1150 						}
1151 						
1152 						_defer.fail(msg);
1153 					}
1154 				});
1155 				
1156 				return _defer.promise();
1157 		    },
1158 		    
1159 			init: function(success, errorFunc){
1160 				
1161 				var initPromise;
1162 				
1163 				//use the Deferred from load-dir-struct, since this is the only async initialization atm:
1164 				initPromise = this.loadDirectoryStructure.apply(this, arguments);
1165 				
1166 				//replace init so that we do not ivoke load-dir-struct multiple times
1167 				this.__initDeferred = initPromise;
1168 				this.init = function initCompleted(onsuccess, onerror){
1169 					if(onsuccess){
1170 						this.__initDeferred.done(success);
1171 					}
1172 					if(onerror){
1173 						this.__initDeferred.fail(errorFunc);
1174 					}
1175 					return this.__initDeferred;
1176 				};
1177 				
1178 				return initPromise;
1179 			}
1180 		    
1181 		    /**
1182 		     * Set to "backwards compatibility mode" (for pre version 2.0).
1183 		     * 
1184 		     * This function re-adds deprecated and removed functions and
1185 		     * properties to the CommonUtils instance.
1186 		     * 
1187 		     * NOTE that once set to compatibility mode, it cannot be reset to
1188 		     * non-compatibility mode.
1189 		     * 
1190 		     * @deprecated use only for backward compatibility
1191 		     * 
1192 		     * @async
1193 		     * @requires jQuery.Deferred
1194 		     * @requires mmir.CommonUtils.setToCompatibilityModeExtension
1195 		     * 
1196 		     * @param {Function} [success]
1197 		     * 				a callback function that is invoked, after compatibility mode
1198 		     * 				was set (alternatively the returned promise can be used).
1199 		     * @returns {jQuery.Promise}
1200 		     * 				a Deffered.promise that is resolved, after compatibility mode
1201 		     * 				was set
1202 		     * 
1203 	    	 * @memberOf mmir.CommonUtils.prototype
1204 	    	 * 
1205 		     * @see mmir.CommonUtils.setToCompatibilityModeExtension
1206 		     * 
1207 		     */
1208 		    , setToCompatibilityMode : function(success) {
1209 		    	
1210 		    	var defer = $.Deferred();
1211 		    	if(success){
1212 		    		defer.always(success);
1213 		    	}
1214 		    	
1215 		    	require(['commonUtilsCompatibility'],function(setCompatibility){
1216 		    		
1217 		    		setCompatibility(instance);
1218 		    		
1219 		    		defer.resolve();
1220 		    	});
1221 		    	
1222 		    	return defer.promise();
1223 		    }
1224 			
1225 		};// END: return {...
1226 	
1227     }// END: constructor()
1228 
1229     
1230 	instance = new constructor($, constants);
1231 	
1232 	return instance;
1233     
1234 	
1235 
1236 });
1237