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 //TODO additional dependency on LanguageManager for 29 // * getLanguage() -> languageManager.getLanguage() 30 // * setLanguage(lang) -> languageManager.setLanguage(lang) 31 // 32 // should the dependency on LanguageManager be made OPTIONAL? 33 // 34 define(['constants', 'jquery' ], 35 /** 36 * A class for managing the configuration. <br> 37 * It's purpose is to load the configuration and settings automatically. 38 * 39 * This "class" is structured as a singleton - so that only one instance is in use.<br> 40 * 41 * @name ConfigurationManager 42 * @memberOf mmir 43 * @static 44 * @class 45 * 46 * @requires jQuery.ajax 47 * @requires jQuery.isArray 48 * 49 */ 50 function ( 51 constants, $ 52 ){ 53 54 //the next comment enables JSDoc2 to map all functions etc. to the correct class description 55 /** @scope ConfigurationManager.prototype */ 56 57 /** 58 * Object containing the instance of the class {@link mmir.ConfigurationManager}. 59 * 60 * @type Object 61 * @private 62 * 63 * @memberOf ConfigurationManager# 64 */ 65 var instance = null; 66 67 /** 68 * Constructor-Method of Singleton mmir.ConfigurationManager. 69 * 70 * @private 71 * 72 * @memberOf ConfigurationManager# 73 */ 74 function constructor(){ 75 76 /** @scope ConfigurationManager.prototype */ 77 78 /** 79 * the configuration data (i.e. properties) 80 * @type Object 81 * @private 82 * 83 * @memberOf ConfigurationManager# 84 */ 85 var configData = null; 86 87 88 /** 89 * Helper that loads configuration file synchronously. 90 * 91 * @private 92 * @memberOf ConfigurationManager# 93 */ 94 //FIXME change implementation to async-loading? 95 // -> would need to add init()-function, with callback and/or return Deferred.promise 96 function _loadConfigFile(){ 97 $.ajax({ 98 async: false, 99 dataType: "json", 100 url: constants.getConfigurationFileUrl(), 101 success: function(data){ 102 103 // console.debug("ConfigurationManager.constructor: loaded language settings from "+constants.getConfigurationFileUrl());//debug 104 105 if(data){ 106 configData = data; 107 } 108 }, 109 error: function(data){ 110 console.error("ConfigurationManager.constructor: failed to load configuration from '"+constants.getConfigurationFileUrl()+"'! ERROR: "+ JSON.stringify(data)); 111 } 112 }); 113 } 114 115 //immediately load the configuration: 116 _loadConfigFile(); 117 118 /** 119 * "Normalizes" a string or an array into a path: 120 * the result is a single, flat array where each string has 121 * been separated at dots (i.e. each path component is a separate entry). 122 * 123 * @example 124 * //result is ['dot', 'separated', 'list'] 125 * _getAsPath('dot.separated.list'); 126 * _getAsPath(['dot', 'separated.list']); 127 * _getAsPath(['dot', 'separated', 'list']); 128 * 129 * @private 130 * @param {String|Array<String>} propertyName 131 * resolves a dot-separated property-name into an array. 132 * If <code>propertyName</code> is an Array, all contained 133 * String entries will be resolved, if necessary 134 * 135 * @memberOf ConfigurationManager# 136 */ 137 function _getAsPath(propertyName){ 138 139 var path = propertyName; 140 if( ! $.isArray(path)){ 141 path = propertyName.split('.'); 142 } 143 else { 144 path = _toPathArray(propertyName); 145 } 146 147 return path; 148 } 149 150 /** 151 * "Normalizes" an array of Strings by separating 152 * each String at dots and creating one single (flat) array where 153 * each path-component is a single entry. 154 * 155 * @private 156 * @param {Array<String>} pathList 157 * resolves an array with paths, i.e. dot-separated property-names 158 * into a single, flat array where each path component is a separate Strings 159 * 160 * @memberOf ConfigurationManager# 161 */ 162 function _toPathArray(pathList){ 163 164 var entry; 165 var increase = 0; 166 var size = pathList.length; 167 var tempPath; 168 169 for(var i=0; i < size; ++i){ 170 171 entry = pathList[i]; 172 tempPath = entry.split('.'); 173 174 //if entry contained dot-separated path: 175 // replace original entry with the new sub-path 176 if(tempPath.length > 1){ 177 pathList[i] = tempPath; 178 increase += (tempPath.length - 1); 179 } 180 } 181 182 //if sup-paths were inserted: flatten the array 183 if(increase > 0){ 184 185 //create new array that can hold all entries 186 var newPath = new Array(size + increase); 187 var index = 0; 188 for(var i=0; i < size; ++i){ 189 190 entry = pathList[i]; 191 192 //flatten sub-paths into the new array: 193 if( $.isArray(entry) ){ 194 195 for(var j=0, len=entry.length; j < len; ++j){ 196 newPath[index++] = entry[j]; 197 } 198 } 199 else { 200 //for normal entries: just insert 201 newPath[index++] = entry; 202 } 203 } 204 205 pathList = newPath; 206 } 207 208 return pathList; 209 } 210 211 /** @lends ConfigurationManager */ 212 return { 213 214 // public members 215 216 /** 217 * Returns the currently used language. 218 * 219 * <p>This does not return the language of the configuration, but is a 220 * shortcut for {@link mmir.LanguageManager#getLanguage}. 221 * 222 * 223 * @deprecated use {@link mmir.LanguageManager#getLanguage}() instead! 224 * 225 * @requires mmir.LanguageManager 226 * 227 * @function 228 * @returns {String} The currently used language 229 * @public 230 * 231 * @memberOf ConfigurationManager.prototype 232 */ 233 getLanguage: function(){ 234 return require('languageManager').getInstance().getLanguage(); 235 }, 236 /** 237 * Sets the currently used language. 238 * 239 * <p>This does not set the language of the configuration, but is a 240 * shortcut for {@link mmir.LanguageManager#setLanguage}. 241 * 242 * 243 * @deprecated use {@link mmir.LanguageManager#setLanguage}(lang) instead! 244 * 245 * @requires mmir.LanguageManager 246 * 247 * @function 248 * @param {String} lang The language which is to be used 249 * @public 250 */ 251 setLanguage: function(lang){ 252 require('languageManager').getInstance().setLanguage(lang); 253 }, 254 /** 255 * Returns the value of a property. 256 * 257 * @function 258 * @param {String} propertyName 259 * The name of the property. 260 * NOTE: If the property does not exists at the root-level, 261 * dot-separated names will be resolved into 262 * object-structures, e.g. 263 * <code>some.property</code> will be resolved 264 * so that the <code>value</code> at: 265 * <code>some: {property: <value>}</code> 266 * will be returned 267 * @param {Boolean} [useSafeAccess] OPTIONAL 268 * if <code>true</code>, resolution of dot-separated paths 269 * will be done "safely", i.e. if a path-element does not 270 * exists, no <code>error</code> will be thrown, but instead 271 * the function will return the <code>defaultValue</code> 272 * (which will be <code>undefined</code> if the argument is not given). 273 * @param {any} [defaultValue] OPTIONAL 274 * a default value that will be returned, in case there is no property 275 * <code>propertyName</code>. 276 * 277 * NOTE: if this argument is used, <code>useSafeAccess</code> must also be given! 278 * 279 * @returns {any} 280 * The value of the property 281 * @public 282 */ 283 get: function(propertyName, useSafeAccess, defaultValue){ 284 285 if(configData){ 286 287 if(typeof configData[propertyName] !== 'undefined'){ 288 return configData[propertyName];//////////// EARLY EXIT /////////////////// 289 } 290 291 var path = _getAsPath(propertyName); 292 293 //ASSERT path.length == 1: already handled by if(configData[propertyName]... 294 295 if(path.length > 1){ 296 297 if(useSafeAccess && typeof configData[ path[0] ] === 'undefined'){ 298 return defaultValue;///////////// EARLY EXIT ///////////////////////// 299 } 300 301 var obj = configData; 302 var prop; 303 while(path.length > 1){ 304 prop = path.shift(); 305 obj = obj[prop]; 306 307 if(useSafeAccess && typeof obj === 'undefined'){ 308 return defaultValue;///////////// EARLY EXIT ///////////////////// 309 } 310 } 311 312 //ASSERT now: path.length === 1 313 314 if(typeof obj[path[0]] === 'undefined'){ 315 return defaultValue;///////////// EARLY EXIT ///////////////////// 316 } 317 return obj[path[0]]; 318 319 } 320 } 321 322 return defaultValue; 323 }, 324 /** 325 * Sets a property to a given value. 326 * 327 * @function 328 * @param {String|Array<String>} propertyName 329 * 330 * The name of the property. 331 * 332 * If <code>propertyName</code> is an Array, it 333 * will be treated as if its entries were path-elements 334 * analogous to a dot-separated String propertyName. 335 * 336 * NOTE: dot-separated names will be resolved into 337 * object-structures, e.g. 338 * <code>some.property</code> will be resolved 339 * so that the <code>value</code> will set to: 340 * <code>some: {property: <value>}</code> 341 * 342 * @param {any} value 343 * The value of the property 344 * 345 * @throws {Error} if the propertyName is dot-separated AND 346 * one of its path-elements (except for the last) 347 * already exists AND its type is not 'object' 348 * 349 * @public 350 */ 351 set: function(propertyName, value){ 352 if(!configData){ 353 configData = {}; 354 } 355 356 var path = _getAsPath(propertyName); 357 358 if(path.length > 1){ 359 var obj = configData; 360 var prop; 361 while(path.length > 1){ 362 363 prop = path.shift(); 364 365 if(typeof obj[prop] === 'undefined' || obj[prop] === null){ 366 obj[prop] = {}; 367 } 368 else if(typeof obj[prop] !== 'object'){ 369 throw new Error('Cannot expand path "'+propertyName+'": path-element "'+prop+'" already exist and has type "'+(typeof obj[prop])+'"'); 370 } 371 372 obj = obj[prop]; 373 } 374 375 //ASSERT path.length == 1 376 377 obj[path[0]] = value; 378 } 379 else { 380 configData[propertyName] = value; 381 } 382 }, 383 384 /** 385 * Uses {@link #get}. 386 * 387 * If the propertyName does not exists, returns <code>undefined</code>, 388 * otherwise values will be converted into Boolean values. 389 * 390 * Special case for Strings: 391 * the String <code>"false"</code> will be converted to 392 * Boolean value <code>false</code>. 393 * 394 * @public 395 * @param {any} [defaultValue] OPTIONAL 396 * 397 * if a default value is specified and there exists 398 * no property <code>propertyName</code>, the 399 * specified default value will be returned. 400 * 401 * NOTE: if this argument is used, <code>useSafeAccess</code> must also be given! 402 * 403 * NOTE: the default value will also be converted 404 * to a Boolean value, if necessary. 405 * 406 * @see {@link #get} 407 */ 408 getBoolean: function(propertyName, useSafeAccess, defaultValue){ 409 410 var val = this.get(propertyName, useSafeAccess); 411 412 if(typeof val === 'undefined' && typeof defaultValue !== 'undefined' ){ 413 val = defaultValue; 414 } 415 416 if(typeof val !== 'undefined'){ 417 418 if( val === 'false'){ 419 return false; 420 } 421 else { 422 return val? true : false; 423 } 424 } 425 426 }, 427 428 /** 429 * Uses {@link #get}. 430 * 431 * If the property does not exists, returns <code>undefined</code>, 432 * otherwise values will be converted into String values. 433 * 434 * If the value has not the type <code>"string"</code>, it will 435 * be converted by <code>JSON.stringify</code>. 436 * 437 * @public 438 * @param {any} [defaultValue] OPTIONAL 439 * if a default value is specified and there exists 440 * no property <code>propertyName</code>, the 441 * specified default value will be returned. 442 * 443 * NOTE: if this argument is used, <code>useSafeAccess</code> must also be given! 444 * 445 * NOTE: the default value will also be converted 446 * to a String value, if necessary. 447 * 448 * @see {@link #get} 449 */ 450 getString: function(propertyName, useSafeAccess, defaultValue){ 451 452 var val = this.get(propertyName, useSafeAccess); 453 454 if(typeof val === 'undefined' && typeof defaultValue !== 'undefined' ){ 455 val = defaultValue; 456 } 457 458 if(typeof val !== 'undefined'){ 459 460 if(typeof val === 'string'){ 461 return val; 462 } 463 else { 464 return JSON.stringify(val); 465 } 466 } 467 468 } 469 470 };//END: return {... 471 472 }//END: construcor = function(){... 473 474 475 instance = new constructor(); 476 477 /** 478 * @deprecated instead: use mmir.ConfigurationManager directly 479 * 480 * @function 481 * @name getInstance 482 * @public 483 * @memberOf ConfigurationManager# 484 */ 485 instance.getInstance = function(){ 486 return instance; 487 }; 488 489 return instance; 490 }); 491