Source

ilib.js

/*
 * ilib.js - define the ilib name space
 *
 * Copyright © 2012-2021, JEDLSoft
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * The global namespace that contains general ilib functions useful
 * to all of ilib
 * @namespace
 */
var ilib = ilib || {};

/**
 * @version // !macro ilibVersion
 */
/** @private */
ilib._ver = function() {
    return // !macro ilibVersion
    ;
};

/**
 * Return the current version of ilib.
 *
 * @static
 * @return {string} a version string for this instance of ilib
 */
ilib.getVersion = function () {
    if (ilib._dyncode) {
        try {
            var pkg;
            pkg = require("../package.json");
            return pkg.version;
        } catch (e) {
            // ignore
        }
    }
    return ilib._ver() || "14.0";
};

/**
 * Place where resources and such are eventually assigned.
 */
ilib.data = {
    /** @type {{ccc:Object.<string,number>,nfd:Object.<string,string>,nfc:Object.<string,string>,nfkd:Object.<string,string>,nfkc:Object.<string,string>}} */
    norm: {
        ccc: {},
        nfd: {},
        nfc: {},
        nfkd: {},
        nfkc: {}
    },
    zoneinfo: {
        "Etc/UTC":{"o":"0:0","f":"UTC"},
        "local":{"f":"local"}
    },
    /** @type {Object.<string,{to:Object.<string,string>,from:Object.<string,number>}>} */ charmaps: {},
    /** @type {null|Object.<string,Array.<Array.<number>>>} */ ctype: null,
    /** @type {null|Object.<string,Array.<Array.<number>>>} */ ctype_c: null,
    /** @type {null|Object.<string,Array.<Array.<number>>>} */ ctype_l: null,
    /** @type {null|Object.<string,Array.<Array.<number>>>} */ ctype_m: null,
    /** @type {null|Object.<string,Array.<Array.<number>>>} */ ctype_p: null,
    /** @type {null|Object.<string,Array.<Array.<number>>>} */ ctype_z: null,
    /** @type {null|Object.<string,Array.<Array.<number>>>} */ scriptToRange: null,
    /** @type {null|Object.<string,string|Object.<string|Object.<string,string>>>} */ dateformats: null,
    /** @type {null|Array.<string>} */ timezones: [],
    cache: {}
};

/*
if (typeof(window) !== 'undefined') {
    window["ilib"] = ilib;
}
*/

// export ilib for use as a module in nodejs
if (typeof(module) !== 'undefined') {
    module.exports = ilib;
    module.exports.ilib = ilib;  // for backwards compatibility with older versions of ilib
}

/**
 * Sets the pseudo locale. Pseudolocalization (or pseudo-localization) is used for testing
 * internationalization aspects of software. Instead of translating the text of the software
 * into a foreign language, as in the process of localization, the textual elements of an application
 * are replaced with an altered version of the original language.These specific alterations make
 * the original words appear readable, but include the most problematic characteristics of
 * the world's languages: varying length of text or characters, language direction, and so on.
 * Regular Latin pseudo locale: eu-ES and RTL pseudo locale: ps-AF
 *
 * @param {string|undefined|null} localename the locale specifier for the pseudo locale
 */
ilib.setAsPseudoLocale = function (localename) {
   if (localename) {
       ilib.pseudoLocales.push(localename)
   }
};

/**
 * Reset the list of pseudo locales back to the default single locale of zxx-XX.
 * @static
 */
ilib.clearPseudoLocales = function() {
    ilib.pseudoLocales = [
        "zxx-XX",
        "zxx-Cyrl-XX",
        "zxx-Hans-XX",
        "zxx-Hebr-XX"
    ];
};

ilib.clearPseudoLocales();

/**
 * Return the name of the platform
 * @private
 * @static
 * @return {string} string naming the platform
 */
ilib._getPlatform = function () {
    if (!ilib._platform) {
        try {
            if (typeof(java.lang.Object) !== 'undefined') {
                ilib._platform = (typeof(process) !== 'undefined') ? "trireme" : "rhino";
                return ilib._platform;
            }
        } catch (e) {}

        if (typeof(global) !== 'undefined' && global.process &&
                ((global.process.versions && global.process.versions.node && typeof(module) !== 'undefined') ||
                (typeof(global.process.iotjs) !== "undefined"))) {
            ilib._platform = "nodejs";
        } else if (typeof(Qt) !== 'undefined') {
            ilib._platform = "qt";
            ilib._cacheMerged = true; // qt is too slow, so we need to cache the already-merged locale data
        } else if (typeof(PalmSystem) !== 'undefined') {
            ilib._platform = (typeof(window) !== 'undefined') ? "webos-webapp" : "webos";
        } else if (typeof(window) !== 'undefined') {
            ilib._platform = "browser";
        } else {
            ilib._platform = "unknown";
        }
    }
    return ilib._platform;
};

/**
 * If this ilib is running in a browser, return the name of that browser.
 * @private
 * @static
 * @return {string|undefined} the name of the browser that this is running in ("firefox", "chrome", "ie",
 * "safari", or "opera"), or undefined if this is not running in a browser or if
 * the browser name could not be determined
 */
ilib._getBrowser = function () {
    var browser = undefined;
    if (ilib._getPlatform() === "browser") {
        if (navigator && navigator.userAgent) {
            if (navigator.userAgent.indexOf("Firefox") > -1) {
                return "firefox";
            }
            if (navigator.userAgent.search(/Opera|OPR/) > -1 ) {
                return "opera";
            }
            if (navigator.userAgent.indexOf("Chrome") > -1) {
                return "chrome";
            }
            if (navigator.userAgent.indexOf(" .NET") > -1) {
                return "ie";
            }
            if (navigator.userAgent.indexOf("Safari") > -1) {
                // chrome also has the string Safari in its userAgent, but the chrome case is
                // already taken care of above
                return "safari";
            }
            if (navigator.userAgent.indexOf("Edge") > -1) {
                return "Edge";
            }
            if (navigator.userAgent.search(/iPad|iPhone|iPod/) > -1) {
                // Due to constraints of the iOS platform,
                // all browser must be built on top of the WebKit rendering engine
                return "iOS";
            }
        }
    }
    return "unknown";
};

/**
 * Return the value of the top object in the system. This could be global
 * for node, or window for browsers, etc.
 * @private
 * @static
 * @return {Object|undefined} the top variable, or undefined if there is none on this
 * platform
 */
ilib._top = function() {
    if (typeof(this.top) === 'undefined') {
        this.top = null;
        switch (ilib._getPlatform()) {
            case "rhino":
                this.top = (function() {
                  return (typeof global === 'object') ? global : this;
                })();
                break;
            case "nodejs":
            case "trireme":
                this.top = typeof(global) !== 'undefined' ? global : this;
                //console.log("ilib._top: top is " + (typeof(global) !== 'undefined' ? "global" : "this"));
                break;
            default:
                this.top = window;
                break;
        }
    }

    return this.top || undefined;
};

/**
 * Return the value of a global variable given its name in a way that works
 * correctly for the current platform.
 * @private
 * @static
 * @param {string} name the name of the variable to return
 * @return {*} the global variable, or undefined if it does not exist
 */
ilib._global = function(name) {
    var top = this._top();
    try {
        return top[name];
    } catch (e) {
        return undefined;
    }
};

/**
 * Return true if the global variable is defined on this platform.
 * @private
 * @static
 * @param {string} name the name of the variable to check
 * @return {boolean} true if the global variable is defined on this platform, false otherwise
 */
ilib._isGlobal = function(name) {
    return typeof(ilib._global(name)) !== 'undefined';
};

/**
 * Clear the file load cache. This is mainly used by the unit tests,
 * but could be used by regular callers if you want to free up memory
 * for garbage collecting.
 */
ilib.clearCache = function() {
    ilib.data.cache = {};
};

/**
 * Sets the default locale for all of ilib. This locale will be used
 * when no explicit locale is passed to any ilib class. If the default
 * locale is not set, ilib will attempt to use the locale of the
 * environment it is running in, if it can find that. If not, it will
 * default to the locale "en-US". If a type of parameter is string,
 * ilib will take only well-formed BCP-47 tag  <p>
 *
 *
 * @static
 * @param {string|undefined|null} spec the locale specifier for the default locale
 */
ilib.setLocale = function (spec) {
    if (typeof(spec) === 'string' || !spec) {
        ilib.locale = spec;
    }
    // else ignore other data types, as we don't have the dependencies
    // to look into them to find a locale
};

/**
 * @private
 */
function parseLocale(str) {
    if (!str) return str;

    // take care of the libc style locale with a dot + script at the end
    var dot = str.indexOf('.')
    if (dot > -1) {
        str = str.substring(0, dot);
    }

    // handle the posix default locale
    if (str === "C") return "en-US";

    var parts = str.replace(/_/g, '-').split(/-/g);
    var localeParts = [];

    if (parts.length > 0) {
        if (parts[0].length === 2 || parts[0].length === 3) {
            // language
            localeParts.push(parts[0].toLowerCase());

            if (parts.length > 1) {
                if (parts[1].length === 4) {
                    // script
                    localeParts.push(parts[1][0].toUpperCase() + parts[1].substring(1).toLowerCase());
                } else if (parts[1].length === 2 || parts[1].length == 3) {
                    // region
                    localeParts.push(parts[1].toUpperCase());
                }

                if (parts.length > 2) {
                    if (parts[2].length === 2 || parts[2].length == 3) {
                        // region
                        localeParts.push(parts[2].toUpperCase());
                    }
                }
            }
        }
    }

    return localeParts.join('-');
}

/**
 * Return the default locale for all of ilib if one has been set. This
 * locale will be used when no explicit locale is passed to any ilib
 * class. If the default
 * locale is not set, ilib will attempt to use the locale of the
 * environment it is running in, if it can find that. If not, it will
 * default to the locale "en-US".<p>
 *
 *
 * @static
 * @return {string} the locale specifier for the default locale
 */
ilib.getLocale = function () {
    var lang, dot;
    if (typeof(ilib.locale) !== 'string') {
        var plat = ilib._getPlatform();
        switch (plat) {
            case 'browser':
                // running in a browser
                if(typeof(navigator.language) !== 'undefined') {
                    ilib.locale = parseLocale(navigator.language);  // FF/Opera/Chrome/Webkit
                }
                if (!ilib.locale) {
                    // IE on Windows
                    lang = typeof(navigator.browserLanguage) !== 'undefined' ?
                        navigator.browserLanguage :
                            (typeof(navigator.userLanguage) !== 'undefined' ?
                                navigator.userLanguage :
                                    (typeof(navigator.systemLanguage) !== 'undefined' ?
                                        navigator.systemLanguage :
                                            undefined));
                    if (typeof(lang) !== 'undefined' && lang) {
                        // for some reason, MS uses lower case region tags
                        ilib.locale = parseLocale(lang);
                    } else {
                        ilib.locale = undefined;
                    }
                }
                break;
            case 'webos-webapp':
            case 'webos':
                // webOS
                if (typeof(PalmSystem.locales) !== 'undefined' &&
                    typeof(PalmSystem.locales.UI) != 'undefined' &&
                    PalmSystem.locales.UI.length > 0) {
                    ilib.locale = parseLocale(PalmSystem.locales.UI);
                } else if (typeof(PalmSystem.locale) !== 'undefined') {
                    ilib.locale = parseLocale(PalmSystem.locale);
                } else {
                    ilib.locale = undefined;
                }
                break;
            case 'rhino':
                if (typeof(environment) !== 'undefined' && environment.user && typeof(environment.user.language) === 'string' && environment.user.language.length > 0) {
                    // running under plain rhino
                    var l = [environment.user.language];
                    if (typeof(environment.user.country) === 'string' && environment.user.country.length > 0) {
                        l.push(environment.user.country);
                    }
                    ilib.locale = l.join("-");
                }
                break;
            case "trireme":
                // under trireme on rhino emulating nodejs
                lang = process.env.LANG || process.env.LANGUAGE || process.env.LC_ALL;
                // the LANG variable on unix is in the form "lang_REGION.CHARSET"
                // where language and region are the correct ISO codes separated by
                // an underscore. This translate it back to the BCP-47 form.
                ilib.locale = parseLocale(lang);
                break;
            case 'nodejs':
                // running under nodejs
                lang = global.process.env.LANG || global.process.env.LC_ALL;
                // the LANG variable on unix is in the form "lang_REGION.CHARSET"
                // where language and region are the correct ISO codes separated by
                // an underscore. This translate it back to the BCP-47 form.
                ilib.locale = parseLocale(lang);
                break;
            case 'qt':
                // running in the Javascript engine under Qt/QML
                var locobj = Qt.locale();
                ilib.locale = parseLocale(locobj.name || "en-US");
                break;
        }
        // test for posix "C" locale
        ilib.locale = typeof(ilib.locale) === 'string' && ilib.locale.length && ilib.locale !== "C" ? ilib.locale : 'en-US';
        if (ilib.locale === "en") {
            ilib.locale = "en-US"; // hack to get various platforms working correctly
        }
    }
    return ilib.locale;
};

/**
 * Sets the default time zone for all of ilib. This time zone will be used when
 * no explicit time zone is passed to any ilib class. If the default time zone
 * is not set, ilib will attempt to use the time zone of the
 * environment it is running in, if it can find that. If not, it will
 * default to the the UTC zone "Etc/UTC".<p>
 *
 *
 * @static
 * @param {string} tz the name of the time zone to set as the default time zone
 */
ilib.setTimeZone = function (tz) {
    ilib.tz = tz || ilib.tz;
};

/**
 * Return the default time zone for all of ilib if one has been set. This
 * time zone will be used when no explicit time zone is passed to any ilib
 * class. If the default time zone
 * is not set, ilib will attempt to use the locale of the
 * environment it is running in, if it can find that. If not, it will
 * default to the the zone "local".<p>
 *
 *
 * @static
 * @return {string} the default time zone for ilib
 */
ilib.getTimeZone = function() {
    if (typeof(ilib.tz) === 'undefined') {
        if (typeof(Intl) !== 'undefined' && typeof(Intl.DateTimeFormat) !== 'undefined') {
            var ro = new Intl.DateTimeFormat().resolvedOptions();
            ilib.tz = ro && ro.timeZone;
            return ilib.tz;
        }

        switch (ilib._getPlatform()) {
            case 'browser':
                // running in a browser
                if (navigator.timezone && navigator.timezone.length > 0) {
                    ilib.tz = navigator.timezone;
                }
                break;
            case 'webos-webapp':
            case 'webos':
                // running in webkit on webOS
                if (PalmSystem.timezone && PalmSystem.timezone.length > 0) {
                    ilib.tz = PalmSystem.timezone;
                }
                break;
            case 'rhino':
                // running under rhino
                if (typeof(environment.user.timezone) !== 'undefined' && environment.user.timezone.length > 0) {
                    ilib.tz = environment.user.timezone;
                }
                break;
            case 'nodejs':
                if (global.process.env && typeof(global.process.env.TZ) !== "undefined") {
                    ilib.tz = global.process.env.TZ;
                }
                break;
        }

        ilib.tz = ilib.tz || "local";
    }

    return ilib.tz;
};

/**
 * @class
 * Defines the interface for the loader class for ilib. The main method of the
 * loader object is loadFiles(), which loads a set of requested locale data files
 * from where-ever it is stored.
 * @interface
 */
ilib.Loader = function() {};

/**
 * Load a set of files from where-ever it is stored.<p>
 *
 * This is the main function define a callback function for loading missing locale
 * data or resources.
 * If this copy of ilib is assembled without including the required locale data
 * or resources, then that data can be lazy loaded dynamically when it is
 * needed by calling this method. Each ilib class will first
 * check for the existence of data under ilib.data, and if it is not there,
 * it will attempt to load it by calling this method of the loader, and then place
 * it there.<p>
 *
 * Suggested implementations of this method might load files
 * directly from disk under nodejs or rhino, or within web pages, to load
 * files from the server with XHR calls.<p>
 *
 * The first parameter to this method, paths, is an array of relative paths within
 * the ilib dir structure for the
 * requested data. These paths will already have the locale spec integrated
 * into them, so no further tweaking needs to happen to load the data. Simply
 * load the named files. The second
 * parameter tells the loader whether to load the files synchronously or asynchronously.
 * If the sync parameters is false, then the onLoad function must also be specified.
 * The third parameter gives extra parameters to the loader passed from the calling
 * code. This may contain any property/value pairs.  The last parameter, callback,
 * is a callback function to call when all of the data is finishing loading. Make
 * sure to call the callback with the context of "this" so that the caller has their
 * context back again.<p>
 *
 * The loader function must be able to operate either synchronously or asychronously.
 * If the loader function is called with an undefined callback function, it is
 * expected to load the data synchronously, convert it to javascript
 * objects, and return the array of json objects as the return value of the
 * function. If the loader
 * function is called with a callback function, it may load the data
 * synchronously or asynchronously (doesn't matter which) as long as it calls
 * the callback function with the data converted to a javascript objects
 * when it becomes available. If a particular file could not be loaded, the
 * loader function should put undefined into the corresponding entry in the
 * results array.
 * Note that it is important that all the data is loaded before the callback
 * is called.<p>
 *
 * An example implementation for nodejs might be:
 *
 * <pre>
 * var fs = require("fs");
 *
 * var myLoader = function() {};
 * myLoader.prototype = new Loader();
 * myLoader.prototype.constructor = myLoader;
 * myLoader.prototype.loadFiles = function(paths, sync, params, callback) {
 *    if (sync) {
 *        var ret = [];
 *        // synchronous load -- just return the result
 *        paths.forEach(function (path) {
 *            var json = fs.readFileSync(path, "utf-8");
 *            ret.push(json ? JSON.parse(json) : undefined);
 *        });
 *
 *        return ret;
 *    }
 *    this.callback = callback;
 *
 *    // asynchronous
 *    this.results = [];
 *    this._loadFilesAsync(paths);
 * }
 * myLoader.prototype._loadFilesAsync = function (paths) {
 *    if (paths.length > 0) {
 *        var file = paths.shift();
 *        fs.readFile(file, "utf-8", function(err, json) {
 *            this.results.push(err ? undefined : JSON.parse(json));
 *            // call self recursively so that the callback is only called at the end
 *            // when all the files are loaded sequentially
 *            if (paths.length > 0) {
 *                this._loadFilesAsync(paths);
 *            } else {
 *                this.callback(this.results);
 *            }
 *        });
 *     }
 * }
 *
 * // bind to "this" so that "this" is relative to your own instance
 * ilib.setLoaderCallback(new myLoader());
 * </pre>

 * @param {Array.<string>} paths An array of paths to load from wherever the files are stored
 * @param {Boolean} sync if true, load the files synchronously, and false means asynchronously
 * @param {Object} params an object with any extra parameters for the loader. These can be
 * anything. The caller of the ilib class passes these parameters in. Presumably, the code that
 * calls ilib and the code that provides the loader are together and can have a private
 * agreement between them about what the parameters should contain.
 * @param {function(Object)} callback function to call when the files are all loaded. The
 * parameter of the callback function is the contents of the files.
 */
ilib.Loader.prototype.loadFiles = function (paths, sync, params, callback) {};

/**
 * Return all files available for loading using this loader instance.
 * This method returns an object where the properties are the paths to
 * directories where files are loaded from and the values are an array
 * of strings containing the relative paths under the directory of each
 * file that can be loaded.<p>
 *
 * Example:
 *  <pre>
 *  {
 *      "/usr/share/javascript/ilib/locale": [
 *          "dateformats.json",
 *          "aa/dateformats.json",
 *          "af/dateformats.json",
 *          "agq/dateformats.json",
 *          "ak/dateformats.json",
 *          ...
 *          "zxx/dateformats.json"
 *      ]
 *  }
 *  </pre>
 * @returns {Object} a hash containing directory names and
 * paths to file that can be loaded by this loader
 */
ilib.Loader.prototype.listAvailableFiles = function() {};

/**
 * Return true if the file in the named path is available for loading using
 * this loader. The path may be given as an absolute path, in which case
 * only that file is checked, or as a relative path, in which case, the
 * relative path may appear underneath any of the directories that the loader
 * knows about.
 * @returns {boolean} true if the file in the named path is available for loading, and
 * false otherwise
 */
ilib.Loader.prototype.isAvailable = function(path) {};

/**
 * Set the custom loader used to load ilib's locale data in your environment.
 * The instance passed in must implement the Loader interface. See the
 * Loader class documentation for more information about loaders.
 *
 * @static
 * @param {ilib.Loader} loader class to call to access the requested data.
 * @return {boolean} true if the loader was installed correctly, or false
 * if not
 */
ilib.setLoaderCallback = function(loader) {
    // only a basic check
    if ((typeof(loader) === 'object' && typeof(loader.loadFiles) === 'function') ||
            typeof(loader) === 'function' || typeof(loader) === 'undefined') {
        //console.log("setting callback loader to " + (loader ? loader.name : "undefined"));
        ilib._load = loader;
        return true;
    }
    return false;
};

/**
 * Return the custom Loader instance currently in use with this instance
 * of ilib. If there is no loader, this method returns undefined.
 *
 * @protected
 * @static
 * @return {ilib.Loader|undefined} the loader instance currently in use, or
 * undefined if there is no such loader
 */
ilib.getLoader = function() {
    return ilib._load;
};

/**
 * Test whether an object is an javascript array.
 *
 * @static
 * @param {*} object The object to test
 * @return {boolean} return true if the object is an array
 * and false otherwise
 */
ilib.isArray = function(object) {
    if (typeof(object) === 'object') {
        return Object.prototype.toString.call(object) === '[object Array]';
    }
    return false;
};

/**
 * Extend object1 by mixing in everything from object2 into it. The objects
 * are deeply extended, meaning that this method recursively descends the
 * tree in the objects and mixes them in at each level. Arrays are extended
 * by concatenating the elements of object2 onto those of object1.
 *
 * @static
 * @param {Object} object1 the target object to extend
 * @param {Object=} object2 the object to mix in to object1
 * @return {Object} returns object1
 */
ilib.extend = function (object1, object2) {
    var prop = undefined;
    if (object2) {
        for (prop in object2) {
            // don't extend object with undefined or functions
            if (prop && typeof(object2[prop]) !== 'undefined' && typeof(object2[prop]) !== "function") {
                if (ilib.isArray(object1[prop]) && ilib.isArray(object2[prop])) {
                    //console.log("Merging array prop " + prop);
                    object1[prop] = object1[prop].concat(object2[prop]);
                } else if (typeof(object1[prop]) === 'object' && typeof(object2[prop]) === 'object') {
                    //console.log("Merging object prop " + prop);
                    if (prop !== "ilib") {
                        object1[prop] = ilib.extend(object1[prop], object2[prop]);
                    }
                } else {
                    //console.log("Copying prop " + prop);
                    // for debugging. Used to determine whether or not json files are overriding their parents unnecessarily
                    object1[prop] = object2[prop];
                }
            }
        }
    }
    return object1;
};

ilib.extend2 = function (object1, object2) {
    var prop = undefined;
    if (object2) {
        for (prop in object2) {
            // don't extend object with undefined or functions
            if (prop && typeof(object2[prop]) !== 'undefined') {
                if (ilib.isArray(object1[prop]) && ilib.isArray(object2[prop])) {
                    //console.log("Merging array prop " + prop);
                    object1[prop] = object1[prop].concat(object2[prop]);
                } else if (typeof(object1[prop]) === 'object' && typeof(object2[prop]) === 'object') {
                    //console.log("Merging object prop " + prop);
                    if (prop !== "ilib") {
                        object1[prop] = ilib.extend2(object1[prop], object2[prop]);
                    }
                } else {
                    //console.log("Copying prop " + prop);
                    // for debugging. Used to determine whether or not json files are overriding their parents unnecessarily
                    object1[prop] = object2[prop];
                }
            }
        }
    }
    return object1;
};

/**
 * If Function.prototype.bind does not exist in this JS engine, this
 * function reimplements it in terms of older JS functions.
 * bind() doesn't exist in many older browsers.
 *
 * @static
 * @param {Object} scope object that the method should operate on
 * @param {function(...)} method method to call
 * @return {function(...)|undefined} function that calls the given method
 * in the given scope with all of its arguments properly attached, or
 * undefined if there was a problem with the arguments
 */
ilib.bind = function(scope, method/*, bound arguments*/){
    if (!scope || !method) {
        return undefined;
    }

    /** @protected
     * @param {Arguments} inArrayLike
     * @param {number=} inOffset
     */
    function cloneArray(inArrayLike, inOffset) {
        var arr = [];
        for(var i = inOffset || 0, l = inArrayLike.length; i<l; i++){
            arr.push(inArrayLike[i]);
        }
        return arr;
    }

    if (typeof(method) === 'function') {
        var func, args = cloneArray(arguments, 2);
        if (typeof(method.bind) === 'function') {
            func = method.bind.apply(method, [scope].concat(args));
        } else {
            func = function() {
                var nargs = cloneArray(arguments);
                // invoke with collected args
                return method.apply(scope, args.concat(nargs));
            };
        }
        return func;
    }
    return undefined;
};

/**
 * @private
 */
ilib._dyncode = false;

/**
 * Return true if this copy of ilib is using dynamically loaded code. It returns
 * false for pre-assembled code.
 *
 * @static
 * @return {boolean} true if this ilib uses dynamically loaded code, and false otherwise
 */
ilib.isDynCode = function() {
    return ilib._dyncode;
};

/**
 * @private
 */
ilib._dyndata = false;

/**
 * Return true if this copy of ilib is using dynamically loaded locale data. It returns
 * false for pre-assembled data.
 *
 * @static
 * @return {boolean} true if this ilib uses dynamically loaded locale data, and false otherwise
 */
ilib.isDynData = function() {
    return ilib._dyndata;
};

/**
 * When true, this will cause ilib to cache merged locale data. Merged data is created
 * whenever a locale is specified where the data for a locale data resides in various
 * files, and these need to be merged together to create the overall data for that locale.<p>
 *
 * For example, if the ilib locale is "fr-CA", the final locale data is assembled from the
 * following locale parts:
 *
 * <ul>
 * <li>root - the root/default locale data shared by every locale
 * <li>fr - the locale data shared by every flavour of French (eg. translations or date formats)
 * <li>und/CA - the language-independent locale data for Canada (eg. time zone or official currency)
 * <li>fr/CA - the locale data that is unique to French for Canada (eg. date or currency formats)
 * </ul>
 *
 * On some platforms, the data loaded from disk is cached and then merged each time it is
 * needed to create the whole locale data for the current locale. In other platforms, the
 * merging is too slow, so the already-merged data is cached as well after the first time it
 * is requested. In this way, we sacrifice the memory footprint for the sake of speed.
 */

ilib._cacheMerged = false;

ilib._loadtime = new Date().getTime();