1 /*
  2  * ilib.js - define the ilib name space
  3  *
  4  * Copyright © 2012-2018, JEDLSoft
  5  *
  6  * Licensed under the Apache License, Version 2.0 (the "License");
  7  * you may not use this file except in compliance with the License.
  8  * You may obtain a copy of the License at
  9  *
 10  *     http://www.apache.org/licenses/LICENSE-2.0
 11  *
 12  * Unless required by applicable law or agreed to in writing, software
 13  * distributed under the License is distributed on an "AS IS" BASIS,
 14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 15  *
 16  * See the License for the specific language governing permissions and
 17  * limitations under the License.
 18  */
 19 
 20 /**
 21  * @namespace The global namespace that contains general ilib functions useful
 22  * to all of ilib
 23  *
 24  * @version // !macro ilibVersion
 25  */
 26 var ilib = ilib || {};
 27 
 28 /** @private */
 29 ilib._ver = function() {
 30     return // !macro ilibVersion
 31     ;
 32 };
 33 
 34 /**
 35  * Return the current version of ilib.
 36  *
 37  * @static
 38  * @return {string} a version string for this instance of ilib
 39  */
 40 ilib.getVersion = function () {
 41     if (ilib._dyncode) {
 42         try {
 43             var pkg;
 44             pkg = require("../package.json");
 45             return pkg.version;
 46         } catch (e) {
 47             // ignore
 48         }
 49     }
 50     return ilib._ver() || "14.0";
 51 };
 52 
 53 /**
 54  * Place where resources and such are eventually assigned.
 55  */
 56 ilib.data = {
 57     /** @type {{ccc:Object.<string,number>,nfd:Object.<string,string>,nfc:Object.<string,string>,nfkd:Object.<string,string>,nfkc:Object.<string,string>}} */
 58     norm: {
 59         ccc: {},
 60         nfd: {},
 61         nfc: {},
 62         nfkd: {},
 63         nfkc: {}
 64     },
 65     zoneinfo: {
 66         "Etc/UTC":{"o":"0:0","f":"UTC"},
 67         "local":{"f":"local"}
 68     },
 69     /** @type {Object.<string,{to:Object.<string,string>,from:Object.<string,number>}>} */ charmaps: {},
 70     /** @type {null|Object.<string,Array.<Array.<number>>>} */ ctype: null,
 71     /** @type {null|Object.<string,Array.<Array.<number>>>} */ ctype_c: null,
 72     /** @type {null|Object.<string,Array.<Array.<number>>>} */ ctype_l: null,
 73     /** @type {null|Object.<string,Array.<Array.<number>>>} */ ctype_m: null,
 74     /** @type {null|Object.<string,Array.<Array.<number>>>} */ ctype_p: null,
 75     /** @type {null|Object.<string,Array.<Array.<number>>>} */ ctype_z: null,
 76     /** @type {null|Object.<string,Array.<Array.<number>>>} */ scriptToRange: null,
 77     /** @type {null|Object.<string,string|Object.<string|Object.<string,string>>>} */ dateformats: null,
 78     /** @type {null|Array.<string>} */ timezones: [],
 79     cache: {}
 80 };
 81 
 82 /*
 83 if (typeof(window) !== 'undefined') {
 84     window["ilib"] = ilib;
 85 }
 86 */
 87 
 88 // export ilib for use as a module in nodejs
 89 if (typeof(module) !== 'undefined') {
 90     module.exports = ilib;
 91     module.exports.ilib = ilib;  // for backwards compatibility with older versions of ilib
 92 }
 93 
 94 /**
 95  * Sets the pseudo locale. Pseudolocalization (or pseudo-localization) is used for testing
 96  * internationalization aspects of software. Instead of translating the text of the software
 97  * into a foreign language, as in the process of localization, the textual elements of an application
 98  * are replaced with an altered version of the original language.These specific alterations make
 99  * the original words appear readable, but include the most problematic characteristics of
100  * the world's languages: varying length of text or characters, language direction, and so on.
101  * Regular Latin pseudo locale: eu-ES and RTL pseudo locale: ps-AF
102  *
103  * @param {string|undefined|null} localename the locale specifier for the pseudo locale
104  */
105 ilib.setAsPseudoLocale = function (localename) {
106    if (localename) {
107        ilib.pseudoLocales.push(localename)
108    }
109 };
110 
111 /**
112  * Reset the list of pseudo locales back to the default single locale of zxx-XX.
113  * @static
114  */
115 ilib.clearPseudoLocales = function() {
116     ilib.pseudoLocales = [
117         "zxx-XX",
118         "zxx-Cyrl-XX",
119         "zxx-Hans-XX",
120         "zxx-Hebr-XX"
121     ];
122 };
123 
124 ilib.clearPseudoLocales();
125 
126 /**
127  * Return the name of the platform
128  * @private
129  * @static
130  * @return {string} string naming the platform
131  */
132 ilib._getPlatform = function () {
133     if (!ilib._platform) {
134         try {
135             if (typeof(java.lang.Object) !== 'undefined') {
136                 ilib._platform = (typeof(process) !== 'undefined') ? "trireme" : "rhino";
137                 return ilib._platform;
138             }
139         } catch (e) {}
140 
141         if (typeof(global) !== 'undefined' && global.process && global.process.versions && global.process.versions.node && typeof(module) !== 'undefined') {
142             ilib._platform = "nodejs";
143         } else if (typeof(Qt) !== 'undefined') {
144             ilib._platform = "qt";
145         } else if (typeof(window) !== 'undefined') {
146             ilib._platform = (typeof(PalmSystem) !== 'undefined') ? "webos" : "browser";
147         } else {
148             ilib._platform = "unknown";
149         }
150     }
151     return ilib._platform;
152 };
153 
154 /**
155  * If this ilib is running in a browser, return the name of that browser.
156  * @private
157  * @static
158  * @return {string|undefined} the name of the browser that this is running in ("firefox", "chrome", "ie",
159  * "safari", or "opera"), or undefined if this is not running in a browser or if
160  * the browser name could not be determined
161  */
162 ilib._getBrowser = function () {
163     var browser = undefined;
164     if (ilib._getPlatform() === "browser") {
165         if (navigator && navigator.userAgent) {
166             if (navigator.userAgent.indexOf("Firefox") > -1) {
167                 browser = "firefox";
168             }
169             if (navigator.userAgent.search(/Opera|OPR/) > -1 ) {
170                 browser = "opera";
171             }
172             if (navigator.userAgent.indexOf("Chrome") > -1) {
173                 browser = "chrome";
174             }
175             if (navigator.userAgent.indexOf(" .NET") > -1) {
176                 browser = "ie";
177             }
178             if (navigator.userAgent.indexOf("Safari") > -1) {
179                 // chrome also has the string Safari in its userAgent, but the chrome case is
180                 // already taken care of above
181                 browser = "safari";
182             }
183             if (navigator.userAgent.indexOf("Edge") > -1) {
184                 browser = "Edge";
185             }
186             if (navigator.userAgent.search(/iPad|iPhone|iPod/) > -1) {
187                 // Due to constraints of the iOS platform,
188                 // all browser must be built on top of the WebKit rendering engine
189                 browser = "iOS";
190             }
191         }
192     }
193     return browser;
194 };
195 
196 /**
197  * Return the value of the top object in the system. This could be global
198  * for node, or window for browsers, etc.
199  * @private
200  * @static
201  * @return {Object|undefined} the top variable, or undefined if there is none on this
202  * platform
203  */
204 ilib._top = function() {
205     if (typeof(this.top) === 'undefined') {
206         this.top = null;
207         switch (ilib._getPlatform()) {
208             case "rhino":
209                 this.top = (function() {
210                   return (typeof global === 'object') ? global : this;
211                 })();
212                 break;
213             case "nodejs":
214             case "trireme":
215                 this.top = typeof(global) !== 'undefined' ? global : this;
216                 //console.log("ilib._top: top is " + (typeof(global) !== 'undefined' ? "global" : "this"));
217                 break;
218             default:
219                 this.top = window;
220                 break;
221         }
222     }
223 
224     return this.top || undefined;
225 };
226 
227 /**
228  * Return the value of a global variable given its name in a way that works
229  * correctly for the current platform.
230  * @private
231  * @static
232  * @param {string} name the name of the variable to return
233  * @return {*} the global variable, or undefined if it does not exist
234  */
235 ilib._global = function(name) {
236     var top = this._top();
237     try {
238         return top[name];
239     } catch (e) {
240         return undefined;
241     }
242 };
243 
244 /**
245  * Return true if the global variable is defined on this platform.
246  * @private
247  * @static
248  * @param {string} name the name of the variable to check
249  * @return {boolean} true if the global variable is defined on this platform, false otherwise
250  */
251 ilib._isGlobal = function(name) {
252     return typeof(ilib._global(name)) !== 'undefined';
253 };
254 
255 /**
256  * Clear the file load cache. This is mainly used by the unit tests,
257  * but could be used by regular callers if you want to free up memory
258  * for garbage collecting.
259  */
260 ilib.clearCache = function() {
261     ilib.data.cache = {};
262 };
263 
264 /**
265  * Sets the default locale for all of ilib. This locale will be used
266  * when no explicit locale is passed to any ilib class. If the default
267  * locale is not set, ilib will attempt to use the locale of the
268  * environment it is running in, if it can find that. If not, it will
269  * default to the locale "en-US". If a type of parameter is string,
270  * ilib will take only well-formed BCP-47 tag  <p>
271  *
272  *
273  * @static
274  * @param {string|undefined|null} spec the locale specifier for the default locale
275  */
276 ilib.setLocale = function (spec) {
277     if (typeof(spec) === 'string' || !spec) {
278         ilib.locale = spec;
279     }
280     // else ignore other data types, as we don't have the dependencies
281     // to look into them to find a locale
282 };
283 
284 /**
285  * Return the default locale for all of ilib if one has been set. This
286  * locale will be used when no explicit locale is passed to any ilib
287  * class. If the default
288  * locale is not set, ilib will attempt to use the locale of the
289  * environment it is running in, if it can find that. If not, it will
290  * default to the locale "en-US".<p>
291  *
292  *
293  * @static
294  * @return {string} the locale specifier for the default locale
295  */
296 ilib.getLocale = function () {
297     if (typeof(ilib.locale) !== 'string') {
298         var plat = ilib._getPlatform();
299         switch (plat) {
300             case 'browser':
301                 // running in a browser
302                 if(typeof(navigator.language) !== 'undefined') {
303                     ilib.locale = navigator.language.substring(0,3) + navigator.language.substring(3,5).toUpperCase();  // FF/Opera/Chrome/Webkit
304                 }
305                 if (!ilib.locale) {
306                     // IE on Windows
307                     lang = typeof(navigator.browserLanguage) !== 'undefined' ?
308                         navigator.browserLanguage :
309                             (typeof(navigator.userLanguage) !== 'undefined' ?
310                                 navigator.userLanguage :
311                                     (typeof(navigator.systemLanguage) !== 'undefined' ?
312                                         navigator.systemLanguage :
313                                             undefined));
314                     if (typeof(lang) !== 'undefined' && lang) {
315                         // for some reason, MS uses lower case region tags
316                         ilib.locale = lang.substring(0,3) + lang.substring(3,5).toUpperCase();
317                     }
318                 }
319                 break;
320             case 'webos':
321                 // webOS
322                 if (typeof(PalmSystem.locales) !== 'undefined' &&
323                     typeof(PalmSystem.locales.UI) != 'undefined' &&
324                     PalmSystem.locales.UI.length > 0) {
325                     ilib.locale = PalmSystem.locales.UI;
326                 } else if (typeof(PalmSystem.locale) !== 'undefined') {
327                     ilib.locale = PalmSystem.locale;
328                 }
329                 break;
330             case 'rhino':
331                 if (typeof(environment) !== 'undefined' && environment.user && typeof(environment.user.language) === 'string' && environment.user.language.length > 0) {
332                     // running under plain rhino
333                     ilib.locale = environment.user.language;
334                     if (typeof(environment.user.country) === 'string' && environment.user.country.length > 0) {
335                         ilib.locale += '-' + environment.user.country;
336                     }
337                 }
338                 break;
339             case "trireme":
340                 // under trireme on rhino emulating nodejs
341                 lang = process.env.LANG || process.env.LANGUAGE || process.env.LC_ALL;
342                 // the LANG variable on unix is in the form "lang_REGION.CHARSET"
343                 // where language and region are the correct ISO codes separated by
344                 // an underscore. This translate it back to the BCP-47 form.
345                 if (lang && typeof(lang) !== 'undefined') {
346                     ilib.locale = lang.substring(0,2).toLowerCase() + '-' + lang.substring(3,5).toUpperCase();
347                 }
348                 break;
349             case 'nodejs':
350                 // running under nodejs
351                 lang = global.process.env.LANG || global.process.env.LC_ALL;
352                 // the LANG variable on unix is in the form "lang_REGION.CHARSET"
353                 // where language and region are the correct ISO codes separated by
354                 // an underscore. This translate it back to the BCP-47 form.
355                 if (lang && typeof(lang) !== 'undefined') {
356                     ilib.locale = lang.substring(0,2).toLowerCase() + '-' + lang.substring(3,5).toUpperCase();
357                 }
358                 break;
359             case 'qt':
360                 // running in the Javascript engine under Qt/QML
361                 var locobj = Qt.locale();
362                 var lang = locobj.name && locobj.name.replace("_", "-") || "en-US";
363                 break;
364         }
365         ilib.locale = typeof(ilib.locale) === 'string' && ilib.locale ? ilib.locale : 'en-US';
366         if (ilib.locale === "en") {
367             ilib.locale = "en-US"; // hack to get various platforms working correctly
368         }
369     }
370     return ilib.locale;
371 };
372 
373 /**
374  * Sets the default time zone for all of ilib. This time zone will be used when
375  * no explicit time zone is passed to any ilib class. If the default time zone
376  * is not set, ilib will attempt to use the time zone of the
377  * environment it is running in, if it can find that. If not, it will
378  * default to the the UTC zone "Etc/UTC".<p>
379  *
380  *
381  * @static
382  * @param {string} tz the name of the time zone to set as the default time zone
383  */
384 ilib.setTimeZone = function (tz) {
385     ilib.tz = tz || ilib.tz;
386 };
387 
388 /**
389  * Return the default time zone for all of ilib if one has been set. This
390  * time zone will be used when no explicit time zone is passed to any ilib
391  * class. If the default time zone
392  * is not set, ilib will attempt to use the locale of the
393  * environment it is running in, if it can find that. If not, it will
394  * default to the the zone "local".<p>
395  *
396  *
397  * @static
398  * @return {string} the default time zone for ilib
399  */
400 ilib.getTimeZone = function() {
401     if (typeof(ilib.tz) === 'undefined') {
402         if (typeof(Intl) !== 'undefined' && typeof(Intl.DateTimeFormat) !== 'undefined') {
403             var ro = new Intl.DateTimeFormat().resolvedOptions();
404             ilib.tz = ro && ro.timeZone;
405         }
406 
407         switch (ilib._getPlatform()) {
408             case 'browser':
409                 // running in a browser
410                 if (navigator.timezone && navigator.timezone.length > 0) {
411                     ilib.tz = navigator.timezone;
412                 }
413                 break;
414             case 'webos':
415                 // running in webkit on webOS
416                 if (PalmSystem.timezone && PalmSystem.timezone.length > 0) {
417                     ilib.tz = PalmSystem.timezone;
418                 }
419                 break;
420             case 'rhino':
421                 // running under rhino
422                 if (typeof(environment.user.timezone) !== 'undefined' && environment.user.timezone.length > 0) {
423                     ilib.tz = environment.user.timezone;
424                 }
425                 break;
426             case 'nodejs':
427                 if (global.process.env && typeof(global.process.env.TZ) !== "undefined") {
428                     ilib.tz = global.process.env.TZ;
429                 }
430                 break;
431         }
432 
433         ilib.tz = ilib.tz || "local";
434     }
435 
436     return ilib.tz;
437 };
438 
439 /**
440  * @class
441  * Defines the interface for the loader class for ilib. The main method of the
442  * loader object is loadFiles(), which loads a set of requested locale data files
443  * from where-ever it is stored.
444  * @interface
445  */
446 ilib.Loader = function() {};
447 
448 /**
449  * Load a set of files from where-ever it is stored.<p>
450  *
451  * This is the main function define a callback function for loading missing locale
452  * data or resources.
453  * If this copy of ilib is assembled without including the required locale data
454  * or resources, then that data can be lazy loaded dynamically when it is
455  * needed by calling this method. Each ilib class will first
456  * check for the existence of data under ilib.data, and if it is not there,
457  * it will attempt to load it by calling this method of the laoder, and then place
458  * it there.<p>
459  *
460  * Suggested implementations of this method might load files
461  * directly from disk under nodejs or rhino, or within web pages, to load
462  * files from the server with XHR calls.<p>
463  *
464  * The first parameter to this method, paths, is an array of relative paths within
465  * the ilib dir structure for the
466  * requested data. These paths will already have the locale spec integrated
467  * into them, so no further tweaking needs to happen to load the data. Simply
468  * load the named files. The second
469  * parameter tells the loader whether to load the files synchronously or asynchronously.
470  * If the sync parameters is false, then the onLoad function must also be specified.
471  * The third parameter gives extra parameters to the loader passed from the calling
472  * code. This may contain any property/value pairs.  The last parameter, callback,
473  * is a callback function to call when all of the data is finishing loading. Make
474  * sure to call the callback with the context of "this" so that the caller has their
475  * context back again.<p>
476  *
477  * The loader function must be able to operate either synchronously or asychronously.
478  * If the loader function is called with an undefined callback function, it is
479  * expected to load the data synchronously, convert it to javascript
480  * objects, and return the array of json objects as the return value of the
481  * function. If the loader
482  * function is called with a callback function, it may load the data
483  * synchronously or asynchronously (doesn't matter which) as long as it calls
484  * the callback function with the data converted to a javascript objects
485  * when it becomes available. If a particular file could not be loaded, the
486  * loader function should put undefined into the corresponding entry in the
487  * results array.
488  * Note that it is important that all the data is loaded before the callback
489  * is called.<p>
490  *
491  * An example implementation for nodejs might be:
492  *
493  * <pre>
494  * var fs = require("fs");
495  *
496  * var myLoader = function() {};
497  * myLoader.prototype = new Loader();
498  * myLoader.prototype.constructor = myLoader;
499  * myLoader.prototype.loadFiles = function(paths, sync, params, callback) {
500  *    if (sync) {
501  *        var ret = [];
502  *        // synchronous load -- just return the result
503  *        paths.forEach(function (path) {
504  *            var json = fs.readFileSync(path, "utf-8");
505  *            ret.push(json ? JSON.parse(json) : undefined);
506  *        });
507  *
508  *        return ret;
509  *    }
510  *    this.callback = callback;
511  *
512  *    // asynchronous
513  *    this.results = [];
514  *    this._loadFilesAsync(paths);
515  * }
516  * myLoader.prototype._loadFilesAsync = function (paths) {
517  *    if (paths.length > 0) {
518  *        var file = paths.shift();
519  *        fs.readFile(file, "utf-8", function(err, json) {
520  *            this.results.push(err ? undefined : JSON.parse(json));
521  *            // call self recursively so that the callback is only called at the end
522  *            // when all the files are loaded sequentially
523  *            if (paths.length > 0) {
524  *                this._loadFilesAsync(paths);
525  *            } else {
526  *                this.callback(this.results);
527  *            }
528  *        });
529  *     }
530  * }
531  *
532  * // bind to "this" so that "this" is relative to your own instance
533  * ilib.setLoaderCallback(new myLoader());
534  * </pre>
535 
536  * @param {Array.<string>} paths An array of paths to load from wherever the files are stored
537  * @param {Boolean} sync if true, load the files synchronously, and false means asynchronously
538  * @param {Object} params an object with any extra parameters for the loader. These can be
539  * anything. The caller of the ilib class passes these parameters in. Presumably, the code that
540  * calls ilib and the code that provides the loader are together and can have a private
541  * agreement between them about what the parameters should contain.
542  * @param {function(Object)} callback function to call when the files are all loaded. The
543  * parameter of the callback function is the contents of the files.
544  */
545 ilib.Loader.prototype.loadFiles = function (paths, sync, params, callback) {};
546 
547 /**
548  * Return all files available for loading using this loader instance.
549  * This method returns an object where the properties are the paths to
550  * directories where files are loaded from and the values are an array
551  * of strings containing the relative paths under the directory of each
552  * file that can be loaded.<p>
553  *
554  * Example:
555  *  <pre>
556  *  {
557  *      "/usr/share/javascript/ilib/locale": [
558  *          "dateformats.json",
559  *          "aa/dateformats.json",
560  *          "af/dateformats.json",
561  *          "agq/dateformats.json",
562  *          "ak/dateformats.json",
563  *          ...
564  *          "zxx/dateformats.json"
565  *      ]
566  *  }
567  *  </pre>
568  * @returns {Object} a hash containing directory names and
569  * paths to file that can be loaded by this loader
570  */
571 ilib.Loader.prototype.listAvailableFiles = function() {};
572 
573 /**
574  * Return true if the file in the named path is available for loading using
575  * this loader. The path may be given as an absolute path, in which case
576  * only that file is checked, or as a relative path, in which case, the
577  * relative path may appear underneath any of the directories that the loader
578  * knows about.
579  * @returns {boolean} true if the file in the named path is available for loading, and
580  * false otherwise
581  */
582 ilib.Loader.prototype.isAvailable = function(path) {};
583 
584 /**
585  * Set the custom loader used to load ilib's locale data in your environment.
586  * The instance passed in must implement the Loader interface. See the
587  * Loader class documentation for more information about loaders.
588  *
589  * @static
590  * @param {ilib.Loader} loader class to call to access the requested data.
591  * @return {boolean} true if the loader was installed correctly, or false
592  * if not
593  */
594 ilib.setLoaderCallback = function(loader) {
595     // only a basic check
596     if ((typeof(loader) === 'object' && typeof(loader.loadFiles) === 'function') ||
597             typeof(loader) === 'function' || typeof(loader) === 'undefined') {
598         //console.log("setting callback loader to " + (loader ? loader.name : "undefined"));
599         ilib._load = loader;
600         return true;
601     }
602     return false;
603 };
604 
605 /**
606  * Return the custom Loader instance currently in use with this instance
607  * of ilib. If there is no loader, this method returns undefined.
608  *
609  * @protected
610  * @static
611  * @return {ilib.Loader|undefined} the loader instance currently in use, or
612  * undefined if there is no such loader
613  */
614 ilib.getLoader = function() {
615     return ilib._load;
616 };
617 
618 /**
619  * Test whether an object is an javascript array.
620  *
621  * @static
622  * @param {*} object The object to test
623  * @return {boolean} return true if the object is an array
624  * and false otherwise
625  */
626 ilib.isArray = function(object) {
627     if (typeof(object) === 'object') {
628         return Object.prototype.toString.call(object) === '[object Array]';
629     }
630     return false;
631 };
632 
633 /**
634  * Extend object1 by mixing in everything from object2 into it. The objects
635  * are deeply extended, meaning that this method recursively descends the
636  * tree in the objects and mixes them in at each level. Arrays are extended
637  * by concatenating the elements of object2 onto those of object1.
638  *
639  * @static
640  * @param {Object} object1 the target object to extend
641  * @param {Object=} object2 the object to mix in to object1
642  * @return {Object} returns object1
643  */
644 ilib.extend = function (object1, object2) {
645     var prop = undefined;
646     if (object2) {
647         for (prop in object2) {
648             // don't extend object with undefined or functions
649             if (prop && typeof(object2[prop]) !== 'undefined' && typeof(object2[prop]) !== "function") {
650                 if (ilib.isArray(object1[prop]) && ilib.isArray(object2[prop])) {
651                     //console.log("Merging array prop " + prop);
652                     object1[prop] = object1[prop].concat(object2[prop]);
653                 } else if (typeof(object1[prop]) === 'object' && typeof(object2[prop]) === 'object') {
654                     //console.log("Merging object prop " + prop);
655                     if (prop !== "ilib") {
656                         object1[prop] = ilib.extend(object1[prop], object2[prop]);
657                     }
658                 } else {
659                     //console.log("Copying prop " + prop);
660                     // for debugging. Used to determine whether or not json files are overriding their parents unnecessarily
661                     object1[prop] = object2[prop];
662                 }
663             }
664         }
665     }
666     return object1;
667 };
668 
669 ilib.extend2 = function (object1, object2) {
670     var prop = undefined;
671     if (object2) {
672         for (prop in object2) {
673             // don't extend object with undefined or functions
674             if (prop && typeof(object2[prop]) !== 'undefined') {
675                 if (ilib.isArray(object1[prop]) && ilib.isArray(object2[prop])) {
676                     //console.log("Merging array prop " + prop);
677                     object1[prop] = object1[prop].concat(object2[prop]);
678                 } else if (typeof(object1[prop]) === 'object' && typeof(object2[prop]) === 'object') {
679                     //console.log("Merging object prop " + prop);
680                     if (prop !== "ilib") {
681                         object1[prop] = ilib.extend2(object1[prop], object2[prop]);
682                     }
683                 } else {
684                     //console.log("Copying prop " + prop);
685                     // for debugging. Used to determine whether or not json files are overriding their parents unnecessarily
686                     object1[prop] = object2[prop];
687                 }
688             }
689         }
690     }
691     return object1;
692 };
693 
694 /**
695  * If Function.prototype.bind does not exist in this JS engine, this
696  * function reimplements it in terms of older JS functions.
697  * bind() doesn't exist in many older browsers.
698  *
699  * @static
700  * @param {Object} scope object that the method should operate on
701  * @param {function(...)} method method to call
702  * @return {function(...)|undefined} function that calls the given method
703  * in the given scope with all of its arguments properly attached, or
704  * undefined if there was a problem with the arguments
705  */
706 ilib.bind = function(scope, method/*, bound arguments*/){
707     if (!scope || !method) {
708         return undefined;
709     }
710 
711     /** @protected
712      * @param {Arguments} inArrayLike
713      * @param {number=} inOffset
714      */
715     function cloneArray(inArrayLike, inOffset) {
716         var arr = [];
717         for(var i = inOffset || 0, l = inArrayLike.length; i<l; i++){
718             arr.push(inArrayLike[i]);
719         }
720         return arr;
721     }
722 
723     if (typeof(method) === 'function') {
724         var func, args = cloneArray(arguments, 2);
725         if (typeof(method.bind) === 'function') {
726             func = method.bind.apply(method, [scope].concat(args));
727         } else {
728             func = function() {
729                 var nargs = cloneArray(arguments);
730                 // invoke with collected args
731                 return method.apply(scope, args.concat(nargs));
732             };
733         }
734         return func;
735     }
736     return undefined;
737 };
738 
739 /**
740  * @private
741  */
742 ilib._dyncode = false;
743 
744 /**
745  * Return true if this copy of ilib is using dynamically loaded code. It returns
746  * false for pre-assembled code.
747  *
748  * @static
749  * @return {boolean} true if this ilib uses dynamically loaded code, and false otherwise
750  */
751 ilib.isDynCode = function() {
752     return ilib._dyncode;
753 };
754 
755 /**
756  * @private
757  */
758 ilib._dyndata = false;
759 
760 /**
761  * Return true if this copy of ilib is using dynamically loaded locale data. It returns
762  * false for pre-assembled data.
763  *
764  * @static
765  * @return {boolean} true if this ilib uses dynamically loaded locale data, and false otherwise
766  */
767 ilib.isDynData = function() {
768     return ilib._dyndata;
769 };
770 
771 ilib._loadtime = new Date().getTime();
772