    /**
     * @author Jacky Nguyen <jacky@sencha.com>
     * @docauthor Jacky Nguyen <jacky@sencha.com>
     * @class Ext.Object
     *
     * A collection of useful static methods to deal with objects.
     *
     * @singleton
     */

    (function() {

    // The "constructor" for chain:
    var TemplateClass = function(){},
        ExtObject = Ext.Object = {

        /**
         * Returns a new object with the given object as the prototype chain.
         * @param {Object} object The prototype chain for the new object.
         */
        chain: function (object) {
            TemplateClass.prototype = object;
            var result = new TemplateClass();
            TemplateClass.prototype = null;
            return result;
        },

        /**
         * Converts a `name` - `value` pair to an array of objects with support for nested structures. Useful to construct
         * query strings. For example:
         *
         *     var objects = Ext.Object.toQueryObjects('hobbies', ['reading', 'cooking', 'swimming']);
         *
         *     // objects then equals:
         *     [
         *         { name: 'hobbies', value: 'reading' },
         *         { name: 'hobbies', value: 'cooking' },
         *         { name: 'hobbies', value: 'swimming' },
         *     ];
         *
         *     var objects = Ext.Object.toQueryObjects('dateOfBirth', {
         *         day: 3,
         *         month: 8,
         *         year: 1987,
         *         extra: {
         *             hour: 4
         *             minute: 30
         *         }
         *     }, true); // Recursive
         *
         *     // objects then equals:
         *     [
         *         { name: 'dateOfBirth[day]', value: 3 },
         *         { name: 'dateOfBirth[month]', value: 8 },
         *         { name: 'dateOfBirth[year]', value: 1987 },
         *         { name: 'dateOfBirth[extra][hour]', value: 4 },
         *         { name: 'dateOfBirth[extra][minute]', value: 30 },
         *     ];
         *
         * @param {String} name
         * @param {Object/Array} value
         * @param {Boolean} [recursive=false] True to traverse object recursively
         * @return {Array}
         */
        toQueryObjects: function(name, value, recursive) {
            var self = ExtObject.toQueryObjects,
                objects = [],
                i, ln;

            if (Ext.isArray(value)) {
                for (i = 0, ln = value.length; i < ln; i++) {
                    if (recursive) {
                        objects = objects.concat(self(name + '[' + i + ']', value[i], true));
                    }
                    else {
                        objects.push({
                            name: name,
                            value: value[i]
                        });
                    }
                }
            }
            else if (Ext.isObject(value)) {
                for (i in value) {
                    if (value.hasOwnProperty(i)) {
                        if (recursive) {
                            objects = objects.concat(self(name + '[' + i + ']', value[i], true));
                        }
                        else {
                            objects.push({
                                name: name,
                                value: value[i]
                            });
                        }
                    }
                }
            }
            else {
                objects.push({
                    name: name,
                    value: value
                });
            }

            return objects;
        },

        /**
         * Takes an object and converts it to an encoded query string.
         *
         * Non-recursive:
         *
         *     Ext.Object.toQueryString({foo: 1, bar: 2}); // returns "foo=1&bar=2"
         *     Ext.Object.toQueryString({foo: null, bar: 2}); // returns "foo=&bar=2"
         *     Ext.Object.toQueryString({'some price': '$300'}); // returns "some%20price=%24300"
         *     Ext.Object.toQueryString({date: new Date(2011, 0, 1)}); // returns "date=%222011-01-01T00%3A00%3A00%22"
         *     Ext.Object.toQueryString({colors: ['red', 'green', 'blue']}); // returns "colors=red&colors=green&colors=blue"
         *
         * Recursive:
         *
         *     Ext.Object.toQueryString({
         *         username: 'Jacky',
         *         dateOfBirth: {
         *             day: 1,
         *             month: 2,
         *             year: 1911
         *         },
         *         hobbies: ['coding', 'eating', 'sleeping', ['nested', 'stuff']]
         *     }, true); // returns the following string (broken down and url-decoded for ease of reading purpose):
         *     // username=Jacky
         *     //    &dateOfBirth[day]=1&dateOfBirth[month]=2&dateOfBirth[year]=1911
         *     //    &hobbies[0]=coding&hobbies[1]=eating&hobbies[2]=sleeping&hobbies[3][0]=nested&hobbies[3][1]=stuff
         *
         * @param {Object} object The object to encode
         * @param {Boolean} [recursive=false] Whether or not to interpret the object in recursive format.
         * (PHP / Ruby on Rails servers and similar).
         * @return {String} queryString
         */
        toQueryString: function(object, recursive) {
            var paramObjects = [],
                params = [],
                i, j, ln, paramObject, value;

            for (i in object) {
                if (object.hasOwnProperty(i)) {
                    paramObjects = paramObjects.concat(ExtObject.toQueryObjects(i, object[i], recursive));
                }
            }

            for (j = 0, ln = paramObjects.length; j < ln; j++) {
                paramObject = paramObjects[j];
                value = paramObject.value;

                if (Ext.isEmpty(value)) {
                    value = '';
                }
                else if (Ext.isDate(value)) {
                    value = Ext.Date.toString(value);
                }

                params.push(encodeURIComponent(paramObject.name) + '=' + encodeURIComponent(String(value)));
            }

            return params.join('&');
        },

        /**
         * Converts a query string back into an object.
         *
         * Non-recursive:
         *
         *     Ext.Object.fromQueryString("foo=1&bar=2"); // returns {foo: 1, bar: 2}
         *     Ext.Object.fromQueryString("foo=&bar=2"); // returns {foo: null, bar: 2}
         *     Ext.Object.fromQueryString("some%20price=%24300"); // returns {'some price': '$300'}
         *     Ext.Object.fromQueryString("colors=red&colors=green&colors=blue"); // returns {colors: ['red', 'green', 'blue']}
         *
         * Recursive:
         *
         *     Ext.Object.fromQueryString(
         *         "username=Jacky&"+
         *         "dateOfBirth[day]=1&dateOfBirth[month]=2&dateOfBirth[year]=1911&"+
         *         "hobbies[0]=coding&hobbies[1]=eating&hobbies[2]=sleeping&"+
         *         "hobbies[3][0]=nested&hobbies[3][1]=stuff", true);
         *
         *     // returns
         *     {
         *         username: 'Jacky',
         *         dateOfBirth: {
         *             day: '1',
         *             month: '2',
         *             year: '1911'
         *         },
         *         hobbies: ['coding', 'eating', 'sleeping', ['nested', 'stuff']]
         *     }
         *
         * @param {String} queryString The query string to decode
         * @param {Boolean} [recursive=false] Whether or not to recursively decode the string. This format is supported by
         * PHP / Ruby on Rails servers and similar.
         * @return {Object}
         */
        fromQueryString: function(queryString, recursive) {
            var parts = queryString.replace(/^\?/, '').split('&'),
                object = {},
                temp, components, name, value, i, ln,
                part, j, subLn, matchedKeys, matchedName,
                keys, key, nextKey;

            for (i = 0, ln = parts.length; i < ln; i++) {
                part = parts[i];

                if (part.length > 0) {
                    components = part.split('=');
                    name = decodeURIComponent(components[0]);
                    value = (components[1] !== undefined) ? decodeURIComponent(components[1]) : '';

                    if (!recursive) {
                        if (object.hasOwnProperty(name)) {
                            if (!Ext.isArray(object[name])) {
                                object[name] = [object[name]];
                            }

                            object[name].push(value);
                        }
                        else {
                            object[name] = value;
                        }
                    }
                    else {
                        matchedKeys = name.match(/(\[):?([^\]]*)\]/g);
                        matchedName = name.match(/^([^\[]+)/);

                        //<debug error>
                        if (!matchedName) {
                            throw new Error('[Ext.Object.fromQueryString] Malformed query string given, failed parsing name from "' + part + '"');
                        }
                        //</debug>

                        name = matchedName[0];
                        keys = [];

                        if (matchedKeys === null) {
                            object[name] = value;
                            continue;
                        }

                        for (j = 0, subLn = matchedKeys.length; j < subLn; j++) {
                            key = matchedKeys[j];
                            key = (key.length === 2) ? '' : key.substring(1, key.length - 1);
                            keys.push(key);
                        }

                        keys.unshift(name);

                        temp = object;

                        for (j = 0, subLn = keys.length; j < subLn; j++) {
                            key = keys[j];

                            if (j === subLn - 1) {
                                if (Ext.isArray(temp) && key === '') {
                                    temp.push(value);
                                }
                                else {
                                    temp[key] = value;
                                }
                            }
                            else {
                                if (temp[key] === undefined || typeof temp[key] === 'string') {
                                    nextKey = keys[j+1];

                                    temp[key] = (Ext.isNumeric(nextKey) || nextKey === '') ? [] : {};
                                }

                                temp = temp[key];
                            }
                        }
                    }
                }
            }

            return object;
        },

        /**
         * Iterates through an object and invokes the given callback function for each iteration.
         * The iteration can be stopped by returning `false` in the callback function. For example:
         *
         *     var person = {
         *         name: 'Jacky'
         *         hairColor: 'black'
         *         loves: ['food', 'sleeping', 'wife']
         *     };
         *
         *     Ext.Object.each(person, function(key, value, myself) {
         *         console.log(key + ":" + value);
         *
         *         if (key === 'hairColor') {
         *             return false; // stop the iteration
         *         }
         *     });
         *
         * @param {Object} object The object to iterate
         * @param {Function} fn The callback function.
         * @param {String} fn.key
         * @param {Object} fn.value
         * @param {Object} fn.object The object itself
         * @param {Object} [scope] The execution scope (`this`) of the callback function
         */
        each: function(object, fn, scope) {
            for (var property in object) {
                if (object.hasOwnProperty(property)) {
                    if (fn.call(scope || object, property, object[property], object) === false) {
                        return;
                    }
                }
            }
        },

        /**
         * Merges any number of objects recursively without referencing them or their children.
         *
         *     var extjs = {
         *         companyName: 'Ext JS',
         *         products: ['Ext JS', 'Ext GWT', 'Ext Designer'],
         *         isSuperCool: true,
         *         office: {
         *             size: 2000,
         *             location: 'Palo Alto',
         *             isFun: true
         *         }
         *     };
         *
         *     var newStuff = {
         *         companyName: 'Sencha Inc.',
         *         products: ['Ext JS', 'Ext GWT', 'Ext Designer', 'Sencha Touch', 'Sencha Animator'],
         *         office: {
         *             size: 40000,
         *             location: 'Redwood City'
         *         }
         *     };
         *
         *     var sencha = Ext.Object.merge(extjs, newStuff);
         *
         *     // extjs and sencha then equals to
         *     {
         *         companyName: 'Sencha Inc.',
         *         products: ['Ext JS', 'Ext GWT', 'Ext Designer', 'Sencha Touch', 'Sencha Animator'],
         *         isSuperCool: true,
         *         office: {
         *             size: 40000,
         *             location: 'Redwood City',
         *             isFun: true
         *         }
         *     }
         *
         * @param {Object} destination The object into which all subsequent objects are merged.
         * @param {Object...} object Any number of objects to merge into the destination.
         * @return {Object} merged The destination object with all passed objects merged in.
         */
        merge: function(destination) {
            var i = 1,
                ln = arguments.length,
                mergeFn = ExtObject.merge,
                cloneFn = Ext.clone,
                object, key, value, sourceKey;

            for (; i < ln; i++) {
                object = arguments[i];

                for (key in object) {
                    value = object[key];
                    if (value && value.constructor === Object) {
                        sourceKey = destination[key];
                        if (sourceKey && sourceKey.constructor === Object) {
                            mergeFn(sourceKey, value);
                        }
                        else {
                            destination[key] = cloneFn(value);
                        }
                    }
                    else {
                        destination[key] = value;
                    }
                }
            }

            return destination;
        },

        /**
         * @private
         * @param destination
         */
        mergeIf: function(destination) {
            var i = 1,
                ln = arguments.length,
                cloneFn = Ext.clone,
                object, key, value;

            for (; i < ln; i++) {
                object = arguments[i];

                for (key in object) {
                    if (!(key in destination)) {
                        value = object[key];

                        if (value && value.constructor === Object) {
                            destination[key] = cloneFn(value);
                        }
                        else {
                            destination[key] = value;
                        }
                    }
                }
            }

            return destination;
        },

        /**
         * Returns the first matching key corresponding to the given value.
         * If no matching value is found, null is returned.
         *
         *     var person = {
         *         name: 'Jacky',
         *         loves: 'food'
         *     };
         *
         *     alert(Ext.Object.getKey(person, 'food')); // alerts 'loves'
         *
         * @param {Object} object
         * @param {Object} value The value to find
         */
        getKey: function(object, value) {
            for (var property in object) {
                if (object.hasOwnProperty(property) && object[property] === value) {
                    return property;
                }
            }

            return null;
        },

        /**
         * Gets all values of the given object as an array.
         *
         *     var values = Ext.Object.getValues({
         *         name: 'Jacky',
         *         loves: 'food'
         *     }); // ['Jacky', 'food']
         *
         * @param {Object} object
         * @return {Array} An array of values from the object
         */
        getValues: function(object) {
            var values = [],
                property;

            for (property in object) {
                if (object.hasOwnProperty(property)) {
                    values.push(object[property]);
                }
            }

            return values;
        },

        /**
         * Gets all keys of the given object as an array.
         *
         *     var values = Ext.Object.getKeys({
         *         name: 'Jacky',
         *         loves: 'food'
         *     }); // ['name', 'loves']
         *
         * @param {Object} object
         * @return {String[]} An array of keys from the object
         * @method
         */
        getKeys: (typeof Object.keys == 'function')
            ? function(object){
                if (!object) {
                    return [];
                }
                return Object.keys(object);
            }
            : function(object) {
                var keys = [],
                    property;

                for (property in object) {
                    if (object.hasOwnProperty(property)) {
                        keys.push(property);
                    }
                }

                return keys;
            },

        /**
         * Gets the total number of this object's own properties
         *
         *     var size = Ext.Object.getSize({
         *         name: 'Jacky',
         *         loves: 'food'
         *     }); // size equals 2
         *
         * @param {Object} object
         * @return {Number} size
         */
        getSize: function(object) {
            var size = 0,
                property;

            for (property in object) {
                if (object.hasOwnProperty(property)) {
                    size++;
                }
            }

            return size;
        },

        /**
         * @private
         */
        classify: function(object) {
            var prototype = object,
                objectProperties = [],
                propertyClassesMap = {},
                objectClass = function() {
                    var i = 0,
                        ln = objectProperties.length,
                        property;

                    for (; i < ln; i++) {
                        property = objectProperties[i];
                        this[property] = new propertyClassesMap[property]();
                    }
                },
                key, value;

            for (key in object) {
                if (object.hasOwnProperty(key)) {
                    value = object[key];

                    if (value && value.constructor === Object) {
                        objectProperties.push(key);
                        propertyClassesMap[key] = ExtObject.classify(value);
                    }
                }
            }

            objectClass.prototype = prototype;

            return objectClass;
        }
    };

    /**
     * A convenient alias method for {@link Ext.Object#merge}.
     *
     * @member Ext
     * @method merge
     * @inheritdoc Ext.Object#merge
     */
    Ext.merge = Ext.Object.merge;

    /**
     * @private
     * @member Ext
     */
    Ext.mergeIf = Ext.Object.mergeIf;

    /**
     *
     * @member Ext
     * @method urlEncode
     * @inheritdoc Ext.Object#toQueryString
     * @deprecated 4.0.0 Use {@link Ext.Object#toQueryString} instead
     */
    Ext.urlEncode = function() {
        var args = Ext.Array.from(arguments),
            prefix = '';

        // Support for the old `pre` argument
        if ((typeof args[1] === 'string')) {
            prefix = args[1] + '&';
            args[1] = false;
        }

        return prefix + ExtObject.toQueryString.apply(ExtObject, args);
    };

    /**
     * Alias for {@link Ext.Object#fromQueryString}.
     *
     * @member Ext
     * @method urlDecode
     * @inheritdoc Ext.Object#fromQueryString
     * @deprecated 4.0.0 Use {@link Ext.Object#fromQueryString} instead
     */
    Ext.urlDecode = function() {
        return ExtObject.fromQueryString.apply(ExtObject, arguments);
    };

    }());