    /**
     * @private
     */
    Ext.define('Ext.util.Collection', {
        /**
         * @cfg {Object[]} filters
         * Array of {@link Ext.util.Filter Filters} for this collection.
         */

        /**
         * @cfg {Object[]} sorters
         * Array of {@link Ext.util.Sorter Sorters} for this collection.
         */

        config: {
            autoFilter: true,
            autoSort: true
        },

        mixins: {
            sortable: 'Ext.mixin.Sortable',
            filterable: 'Ext.mixin.Filterable'
        },

        constructor: function(keyFn, config) {
            var me = this;

            /**
             * @property {Array} [all=[]]
             * An array containing all the items (unsorted, unfiltered)
             */
            me.all = [];

            /**
             * @property {Array} [items=[]]
             * An array containing the filtered items (sorted)
             */
            me.items = [];

            /**
             * @property {Array} [keys=[]]
             * An array containing all the filtered keys (sorted)
             */
            me.keys = [];

            /**
             * @property {Object} [indices={}]
             * An object used as map to get a sorted and filtered index of an item
             */
            me.indices = {};

            /**
             * @property {Object} [map={}]
             * An object used as map to get an object based on its key
             */
            me.map = {};

            /**
             * @property {Number} [length=0]
             * The count of items in the collection filtered and sorted
             */
            me.length = 0;

            if (keyFn) {
                me.getKey = keyFn;
            }

            this.initConfig(config);
        },

        updateAutoSort: function(autoSort, oldAutoSort) {
            if (oldAutoSort === false && autoSort && this.items.length) {
                this.sort();
            }
        },

        updateAutoFilter: function(autoFilter, oldAutoFilter) {
            if (oldAutoFilter === false && autoFilter && this.all.length) {
                this.runFilters();
            }
        },

        insertSorters: function() {
            // We override the insertSorters method that exists on the Sortable mixin. This method always
            // gets called whenever you add or insert a new sorter. We do this because we actually want
            // to sort right after this happens.
            this.mixins.sortable.insertSorters.apply(this, arguments);
            if (this.getAutoSort() && this.items.length) {
                this.sort();
            }
            return this;
        },

        removeSorters: function(sorters) {
            // We override the removeSorters method that exists on the Sortable mixin. This method always
            // gets called whenever you remove a sorter. If we are still sorted after we removed this sorter,
            // then we have to resort the whole collection.
            this.mixins.sortable.removeSorters.call(this, sorters);
            if (this.sorted && this.getAutoSort() && this.item.length) {
                this.sort();
            }
            return this;
        },

        applyFilters: function(filters) {
            var collection = this.mixins.filterable.applyFilters.call(this, filters);
            if (!filters && this.all.length && this.getAutoFilter()) {
                this.filter();
            }
            return collection;
        },

        addFilters: function(filters) {
            // We override the insertFilters method that exists on the Filterable mixin. This method always
            // gets called whenever you add or insert a new filter. We do this because we actually want
            // to filter right after this happens.
            this.mixins.filterable.addFilters.call(this, filters);
            if (this.items.length && this.getAutoFilter()) {
                this.runFilters();
            }
            return this;
        },

        removeFilters: function(filters) {
            // We override the removeFilters method that exists on the Filterable mixin. This method always
            // gets called whenever you remove a filter. If we are still filtered after we removed this filter,
            // then we have to refilter the whole collection.
            this.mixins.filterable.removeFilters.call(this, filters);
            if (this.filtered && this.all.length && this.getAutoFilter()) {
                this.filter();
            }
            return this;
        },

        filter: function(property, value, anyMatch, caseSensitive) {
            // Support for the simple case of filtering by property/value
            if (property) {
                if (Ext.isString(property)) {
                    this.addFilters({
                        property     : property,
                        value        : value,
                        anyMatch     : anyMatch,
                        caseSensitive: caseSensitive
                    });
                    return this.items;
                }
                else {
                    this.addFilters(property);
                    return this.items;
                }
            }

            this.items = this.mixins.filterable.filter.call(this, this.all.slice());
            this.updateAfterFilter();

            if (this.sorted && this.getAutoSort()) {
                this.sort();
            }
        },

        runFilters: function() {
            this.items = this.mixins.filterable.filter.call(this, this.items);
            this.updateAfterFilter();
        },

        updateAfterFilter: function() {
            var items = this.items,
                keys = this.keys,
                indices = this.indices = {},
                ln = items.length,
                i, item, key;

            keys.length = 0;

            for (i = 0; i < ln; i++) {
                item = items[i];
                key = this.getKey(item);
                indices[key] = i;
                keys[i] = key;
            }

            this.length = items.length;
            this.dirtyIndices = false;
        },

        sort: function(sorters, defaultDirection) {
            var items = this.items,
                keys = this.keys,
                indices = this.indices,
                ln = items.length,
                i, item, key;

            // If we pass sorters to this method we have to add them first.
            // Because adding a sorter automatically sorts the items collection
            // we can just return items after we have added the sorters
            if (sorters) {
                this.addSorters(sorters, defaultDirection);
                return this.items;
            }

            // We save the keys temporarily on each item
            for (i = 0; i < ln; i++) {
                items[i]._current_key = keys[i];
            }

            // Now we sort our items array
            this.handleSort(items);

            // And finally we update our keys and indices
            for (i = 0; i < ln; i++) {
                item = items[i];
                key = item._current_key;

                keys[i] = key;
                indices[key] = i;

                delete item._current_key;
            }

            this.dirtyIndices = true;
        },

        handleSort: function(items) {
            this.mixins.sortable.sort.call(this, items);
        },

        /**
         * Adds an item to the collection. Fires the {@link #add} event when complete.
         * @param {String} key
         *
         * The key to associate with the item, or the new item.
         *
         * If a {@link #getKey} implementation was specified for this MixedCollection, or if the key of the stored items is
         * in a property called **id**, the MixedCollection will be able to _derive_ the key for the new item. In this case
         * just pass the new item in this parameter.
         * @param {Object} item The item to add.
         * @return {Object} The item added.
         */
        add: function(key, item) {
            var me = this,
                filtered = this.filtered,
                sorted = this.sorted,
                all = this.all,
                items = this.items,
                keys = this.keys,
                indices = this.indices,
                filterable = this.mixins.filterable,
                currentLength = items.length,
                index = currentLength;

            if (arguments.length == 1) {
                item = key;
                key = me.getKey(item);
            }

            if (typeof key != 'undefined' && key !== null) {
                if (typeof me.map[key] != 'undefined') {
                    return me.replace(key, item);
                }
                me.map[key] = item;
            }

            all.push(item);

            if (filtered && this.getAutoFilter() && filterable.isFiltered.call(me, item)) {
                return null;
            }

            me.length++;

            if (sorted && this.getAutoSort()) {
                index = this.findInsertionIndex(items, item);
            }

            if (index !== currentLength) {
                this.dirtyIndices = true;

                Ext.Array.splice(keys, index, 0, key);
                Ext.Array.splice(items, index, 0, item);
            } else {
                indices[key] = currentLength;

                keys.push(key);
                items.push(item);
            }

            return item;
        },

        /**
         * MixedCollection has a generic way to fetch keys if you implement getKey. The default implementation simply
         * returns **`item.id`** but you can provide your own implementation to return a different value as in the following
         * examples:
         *
         *     // normal way
         *     var mc = new Ext.util.MixedCollection();
         *     mc.add(someEl.dom.id, someEl);
         *     mc.add(otherEl.dom.id, otherEl);
         *     //and so on
         *
         *     // using getKey
         *     var mc = new Ext.util.MixedCollection();
         *     mc.getKey = function(el){
         *        return el.dom.id;
         *     };
         *     mc.add(someEl);
         *     mc.add(otherEl);
         *
         *     // or via the constructor
         *     var mc = new Ext.util.MixedCollection(false, function(el){
         *        return el.dom.id;
         *     });
         *     mc.add(someEl);
         *     mc.add(otherEl);
         * @param {Object} item The item for which to find the key.
         * @return {Object} The key for the passed item.
         */
        getKey: function(item) {
             return item.id;
        },

        /**
         * Replaces an item in the collection. Fires the {@link #replace} event when complete.
         * @param {String} oldKey
         *
         * The key associated with the item to replace, or the replacement item.
         *
         * If you supplied a {@link #getKey} implementation for this MixedCollection, or if the key of your stored items is
         * in a property called **id**, then the MixedCollection will be able to _derive_ the key of the replacement item.
         * If you want to replace an item with one having the same key value, then just pass the replacement item in this
         * parameter.
         * @param {Object} item {Object} item (optional) If the first parameter passed was a key, the item to associate with
         * that key.
         * @return {Object} The new item.
         */
        replace: function(oldKey, item) {
            var me = this,
                sorted = me.sorted,
                filtered = me.filtered,
                filterable = me.mixins.filterable,
                items = me.items,
                keys = me.keys,
                all = me.all,
                map = me.map,
                returnItem = null,
                oldItemsLn = items.length,
                oldItem, index, newKey;

            if (arguments.length == 1) {
                item = oldKey;
                oldKey = newKey = me.getKey(item);
            } else {
                newKey = me.getKey(item);
            }

            oldItem = map[oldKey];
            if (typeof oldKey == 'undefined' || oldKey === null || typeof oldItem == 'undefined') {
                 return me.add(newKey, item);
            }

            me.map[newKey] = item;
            if (newKey !== oldKey) {
                delete me.map[oldKey];
            }

            if (sorted && me.getAutoSort()) {
                Ext.Array.remove(items, oldItem);
                Ext.Array.remove(keys, oldKey);
                Ext.Array.remove(all, oldItem);

                all.push(item);

                me.dirtyIndices = true;

                if (filtered && me.getAutoFilter()) {
                    // If the item is now filtered we check if it was not filtered
                    // before. If that is the case then we subtract from the length
                    if (filterable.isFiltered.call(me, item)) {
                        if (oldItemsLn !== items.length) {
                            me.length--;
                        }
                        return null;
                    }
                    // If the item was filtered, but now it is not anymore then we
                    // add to the length
                    else if (oldItemsLn === items.length) {
                        me.length++;
                        returnItem = item;
                    }
                }

                index = this.findInsertionIndex(items, item);

                Ext.Array.splice(keys, index, 0, newKey);
                Ext.Array.splice(items, index, 0, item);
            } else {
                if (filtered) {
                    if (me.getAutoFilter() && filterable.isFiltered.call(me, item)) {
                        if (items.indexOf(oldItem) !== -1) {
                            Ext.Array.remove(items, oldItem);
                            Ext.Array.remove(keys, oldKey);
                            me.length--;
                            me.dirtyIndices = true;
                        }
                        return null;
                    }
                    else if (items.indexOf(oldItem) === -1) {
                        items.push(item);
                        keys.push(newKey);
                        me.indices[newKey] = me.length;
                        me.length++;
                        return item;
                    }
                }

                index = me.indexOfKey(oldKey);

                keys[index] = newKey;
                items[index] = item;
                this.dirtyIndices = true;
            }

            return returnItem;
        },

        /**
         * Adds all elements of an Array or an Object to the collection.
         * @param {Object/Array} objs An Object containing properties which will be added to the collection, or an Array of
         * values, each of which are added to the collection. Functions references will be added to the collection if {@link
         * Ext.util.MixedCollection#allowFunctions allowFunctions} has been set to true.
         */
        addAll: function(addItems) {
            var me = this,
                filtered = me.filtered,
                sorted = me.sorted,
                all = me.all,
                items = me.items,
                keys = me.keys,
                map = me.map,
                autoFilter = me.getAutoFilter(),
                autoSort = me.getAutoSort(),
                newKeys = [],
                newItems = [],
                filterable = me.mixins.filterable,
                addedItems = [],
                ln, key, i, item;

            if (Ext.isObject(addItems)) {
                for (key in addItems) {
                    if (addItems.hasOwnProperty(key)) {
                        newItems.push(items[key]);
                        newKeys.push(key);
                    }
                }
            } else {
                newItems = addItems;
                ln = addItems.length;
                for (i = 0; i < ln; i++) {
                    newKeys.push(me.getKey(addItems[i]));
                }
            }

            for (i = 0; i < ln; i++) {
                key = newKeys[i];
                item = newItems[i];

                if (typeof key != 'undefined' && key !== null) {
                    if (typeof map[key] != 'undefined') {
                        me.replace(key, item);
                        continue;
                    }
                    map[key] = item;
                }

                all.push(item);

                if (filtered && autoFilter && filterable.isFiltered.call(me, item)) {
                    continue;
                }

                me.length++;

                keys.push(key);
                items.push(item);

                addedItems.push(item);
            }

            if (addedItems.length) {
                me.dirtyIndices = true;

                if (sorted && autoSort) {
                    me.sort();
                }

                return addedItems;
            }

            return null;
        },

        /**
         * Executes the specified function once for every item in the collection, passing the following arguments:
         *
         *   - **item** : Mixed
         *
         * The collection item
         *
         *   - **index** : Number
         *
         * The item's index
         *
         *   - **length** : Number
         *
         * The total number of items in the collection
         *
         * The function should return a boolean value. Returning false from the function will stop the iteration.
         * @param {Function} fn The function to execute for each item.
         * @param {Object} scope The scope (`this` reference) in which the function is executed. Defaults to the current
         * item in the iteration.
         */
        each: function(fn, scope) {
            var items = this.items.slice(), // each safe for removal
                i = 0,
                len = items.length,
                item;

            for (; i < len; i++) {
                item = items[i];
                if (fn.call(scope || item, item, i, len) === false) {
                    break;
                }
            }
        },

        /**
         * Executes the specified function once for every key in the collection, passing each key, and its associated item
         * as the first two parameters.
         * @param {Function} fn The function to execute for each item.
         * @param {Object} scope The scope (`this` reference) in which the function is executed. Defaults to the browser
         * window.
         */
        eachKey: function(fn, scope) {
            var keys = this.keys,
                items = this.items,
                ln = keys.length, i;

            for (i = 0; i < ln; i++) {
                fn.call(scope || window, keys[i], items[i], i, ln);
            }
        },

        /**
         * Returns the first item in the collection which elicits a true return value from the passed selection function.
         * @param {Function} fn The selection function to execute for each item.
         * @param {Object} scope The scope (`this` reference) in which the function is executed. Defaults to the browser
         * window.
         * @return {Object} The first item in the collection which returned true from the selection function.
         */
        findBy: function(fn, scope) {
            var keys = this.keys,
                items = this.items,
                i = 0,
                len = items.length;

            for (; i < len; i++) {
                if (fn.call(scope || window, items[i], keys[i])) {
                    return items[i];
                }
            }
            return null;
        },

        /**
         * Filter by a function. Returns a _new_ collection that has been filtered. The passed function will be called with
         * each object in the collection. If the function returns true, the value is included otherwise it is filtered.
         * @param {Function} fn The function to be called, it will receive the args o (the object), k (the key)
         * @param {Object} scope The scope (`this` reference) in which the function is executed. Defaults to this
         * MixedCollection.
         * @return {Ext.util.MixedCollection} The new filtered collection
         */
        filterBy: function(fn, scope) {
            var me = this,
                newCollection = new this.self(),
                keys   = me.keys,
                items  = me.all,
                length = items.length,
                i;

            newCollection.getKey = me.getKey;

            for (i = 0; i < length; i++) {
                if (fn.call(scope || me, items[i], keys[i])) {
                    newCollection.add(keys[i], items[i]);
                }
            }

            return newCollection;
        },

        /**
         * Inserts an item at the specified index in the collection. Fires the {@link #add} event when complete.
         * @param {Number} index The index to insert the item at.
         * @param {String} key The key to associate with the new item, or the item itself.
         * @param {Object} item If the second parameter was a key, the new item.
         * @return {Object} The item inserted.
         */
        insert: function(index, key, item) {
            var me = this,
                sorted = this.sorted,
                filtered = this.filtered;

            if (arguments.length == 2) {
                item = key;
                key = me.getKey(item);
            }

            if (me.containsKey(key)) {
                me.removeAtKey(key);
            }

            if (index >= me.length || (sorted && me.getAutoSort())) {
                return me.add(key, item);
            }

            this.all.push(item);

            if (typeof key != 'undefined' && key !== null) {
                me.map[key] = item;
            }

            if (filtered && this.getAutoFilter() && filterable.isFiltered.call(me, item)) {
                return null;
            }

            me.length++;

            Ext.Array.splice(me.items, index, 0, item);
            Ext.Array.splice(me.keys, index, 0, key);

            me.dirtyIndices = true;

            return item;
        },

        insertAll: function(index, insertItems) {
            if (index >= this.items.length || (this.sorted && this.getAutoSort())) {
                return this.addAll(insertItems);
            }

            var me = this,
                filtered = this.filtered,
                sorted = this.sorted,
                all = this.all,
                items = this.items,
                keys = this.keys,
                map = this.map,
                autoFilter = this.getAutoFilter(),
                autoSort = this.getAutoSort(),
                newKeys = [],
                newItems = [],
                addedItems = [],
                filterable = this.mixins.filterable,
                insertedUnfilteredItem = false,
                ln, key, i, item;

            if (sorted && this.getAutoSort()) {
                // <debug>
                Ext.Logger.error('Inserting a collection of items into a sorted Collection is invalid. Please just add these items or remove the sorters.');
                // </debug>
            }

            if (Ext.isObject(insertItems)) {
                for (key in insertItems) {
                    if (insertItems.hasOwnProperty(key)) {
                        newItems.push(items[key]);
                        newKeys.push(key);
                    }
                }
            } else {
                newItems = insertItems;
                ln = insertItems.length;
                for (i = 0; i < ln; i++) {
                    newKeys.push(me.getKey(insertItems[i]));
                }
            }

            for (i = 0; i < ln; i++) {
                key = newKeys[i];
                item = newItems[i];

                if (typeof key != 'undefined' && key !== null) {
                    if (me.containsKey(key)) {
                        me.removeAtKey(key);
                    }
                    map[key] = item;
                }

                all.push(item);

                if (filtered && autoFilter && filterable.isFiltered.call(me, item)) {
                    continue;
                }

                me.length++;

                Ext.Array.splice(items, index + i, 0, item);
                Ext.Array.splice(keys, index + i, 0, key);

                insertedUnfilteredItem = true;
                addedItems.push(item);
            }

            if (insertedUnfilteredItem) {
                this.dirtyIndices = true;

                if (sorted && autoSort) {
                    this.sort();
                }

                return addedItems;
            }

            return null;
        },

        /**
         * Remove an item from the collection.
         * @param {Object} item The item to remove.
         * @return {Object} The item removed or false if no item was removed.
         */
        remove: function(item) {
            var index = this.items.indexOf(item);
            if (index === -1) {
                Ext.Array.remove(this.all, item);
                return item;
            }
            return this.removeAt(this.items.indexOf(item));
        },

        /**
         * Remove all items in the passed array from the collection.
         * @param {Array} items An array of items to be removed.
         * @return {Ext.util.MixedCollection} this object
         */
        removeAll: function(items) {
            if (items) {
                var ln = items.length, i;

                for (i = 0; i < ln; i++) {
                    this.remove(items[i]);
                }
            }

            return this;
        },

        /**
         * Remove an item from a specified index in the collection. Fires the {@link #remove} event when complete.
         * @param {Number} index The index within the collection of the item to remove.
         * @return {Object} The item removed or false if no item was removed.
         */
        removeAt: function(index) {
            var me = this,
                items = me.items,
                keys = me.keys,
                all = this.all,
                item, key;

            if (index < me.length && index >= 0) {
                item = items[index];
                key = keys[index];

                if (typeof key != 'undefined') {
                    delete me.map[key];
                }

                Ext.Array.erase(items, index, 1);
                Ext.Array.erase(keys, index, 1);
                Ext.Array.remove(all, item);

                me.length--;

                this.dirtyIndices = true;

                return item;
            }

            return false;
        },

        /**
         * Removed an item associated with the passed key fom the collection.
         * @param {String} key The key of the item to remove.
         * @return {Object} The item removed or false if no item was removed.
         */
        removeAtKey: function(key) {
            return this.removeAt(this.indexOfKey(key));
        },

        /**
         * Returns the number of items in the collection.
         * @return {Number} the number of items in the collection.
         */
        getCount: function() {
            return this.length;
        },

        /**
         * Returns index within the collection of the passed Object.
         * @param {Object} item The item to find the index of.
         * @return {Number} index of the item. Returns -1 if not found.
         */
        indexOf: function(item) {
            if (this.dirtyIndices) {
                this.updateIndices();
            }

            var index = this.indices[this.getKey(item)];
            return (index === undefined) ? -1 : index;
        },

        /**
         * Returns index within the collection of the passed key.
         * @param {String} key The key to find the index of.
         * @return {Number} index of the key.
         */
        indexOfKey: function(key) {
            if (this.dirtyIndices) {
                this.updateIndices();
            }

            var index = this.indices[key];
            return (index === undefined) ? -1 : index;
        },

        updateIndices: function() {
            var items = this.items,
                ln = items.length,
                indices = this.indices = {},
                i, item, key;

            for (i = 0; i < ln; i++) {
                item = items[i];
                key = this.getKey(item);
                indices[key] = i;
            }

            this.dirtyIndices = false;
        },

        /**
         * Returns the item associated with the passed key OR index. Key has priority over index. This is the equivalent of
         * calling {@link #getByKey} first, then if nothing matched calling {@link #getAt}.
         * @param {String/Number} key The key or index of the item.
         * @return {Object} If the item is found, returns the item. If the item was not found, returns undefined. If an item
         * was found, but is a Class, returns null.
         */
        get: function(key) {
            var me = this,
                fromMap = me.map[key],
                item;

            if (fromMap !== undefined) {
                item = fromMap;
            }
            else if (typeof key == 'number') {
                item = me.items[key];
            }

            return typeof item != 'function' || me.getAllowFunctions() ? item : null; // for prototype!
        },

        /**
         * Returns the item at the specified index.
         * @param {Number} index The index of the item.
         * @return {Object} The item at the specified index.
         */
        getAt: function(index) {
            return this.items[index];
        },

        /**
         * Returns the item associated with the passed key.
         * @param {String/Number} key The key of the item.
         * @return {Object} The item associated with the passed key.
         */
        getByKey: function(key) {
            return this.map[key];
        },

        /**
         * Returns true if the collection contains the passed Object as an item.
         * @param {Object} item The Object to look for in the collection.
         * @return {Boolean} True if the collection contains the Object as an item.
         */
        contains: function(item) {
            var key = this.getKey(item);
            if (key) {
                return this.containsKey(key);
            } else {
                return Ext.Array.contains(this.items, item);
            }
        },

        /**
         * Returns true if the collection contains the passed Object as a key.
         * @param {String} key The key to look for in the collection.
         * @return {Boolean} True if the collection contains the Object as a key.
         */
        containsKey: function(key) {
            return typeof this.map[key] != 'undefined';
        },

        /**
         * Removes all items from the collection. Fires the {@link #clear} event when complete.
         */
        clear: function(){
            var me = this;

            me.length = 0;
            me.items.length = 0;
            me.keys.length = 0;
            me.all.length = 0;
            me.map = {};
        },

        /**
         * Returns the first item in the collection.
         * @return {Object} the first item in the collection..
         */
        first: function() {
            return this.items[0];
        },

        /**
         * Returns the last item in the collection.
         * @return {Object} the last item in the collection..
         */
        last: function() {
            return this.items[this.length - 1];
        },

        /**
         * Returns a range of items in this collection
         * @param {Number} startIndex The starting index. Defaults to 0.
         * @param {Number} endIndex The ending index. Defaults to the last item.
         * @return {Array} An array of items
         */
        getRange: function(start, end) {
            var me = this,
                items = me.items,
                range = [],
                i;

            if (items.length < 1) {
                return range;
            }

            start = start || 0;
            end = Math.min(typeof end == 'undefined' ? me.length - 1 : end, me.length - 1);
            if (start <= end) {
                for (i = start; i <= end; i++) {
                    range[range.length] = items[i];
                }
            } else {
                for (i = start; i >= end; i--) {
                    range[range.length] = items[i];
                }
            }

            return range;
        },

        /**
         * Find the index of the first matching object in this collection by a function. If the function returns _true_ it
         * is considered a match.
         * @param {Function} fn The function to be called, it will receive the args o (the object), k (the key).
         * @param {Object} scope The scope (`this` reference) in which the function is executed. Defaults to this
         * MixedCollection.
         * @param {Number} start The index to start searching at (defaults to 0).
         * @return {Number} The matched index or -1
         */
        findIndexBy: function(fn, scope, start) {
            var me = this,
                keys = me.keys,
                items = me.items,
                i = start || 0,
                ln = items.length;

            for (; i < ln; i++) {
                if (fn.call(scope || me, items[i], keys[i])) {
                    return i;
                }
            }

            return -1;
        },

        /**
         * Creates a shallow copy of this collection
         * @return {Ext.util.MixedCollection}
         */
        clone: function() {
            var me = this,
                copy = new this.self(),
                keys = me.keys,
                items = me.items,
                i = 0,
                ln = items.length;

            for(; i < ln; i++) {
                copy.add(keys[i], items[i]);
            }

            copy.getKey = me.getKey;
            return copy;
        }
    });