    /**
     * @class Ext.data.Tree
     *
     * This class is used as a container for a series of nodes. The nodes themselves maintain
     * the relationship between parent/child. The tree itself acts as a manager. It gives functionality
     * to retrieve a node by its identifier: {@link #getNodeById}.
     *
     * The tree also relays events from any of it's child nodes, allowing them to be handled in a
     * centralized fashion. In general this class is not used directly, rather used internally
     * by other parts of the framework.
     *
     */
    Ext.define('Ext.data.Tree', {
        alias: 'data.tree',

        mixins: {
            observable: "Ext.util.Observable"
        },

        /**
         * @property {Ext.data.NodeInterface}
         * The root node for this tree
         */
        root: null,

        /**
         * Creates new Tree object.
         * @param {Ext.data.NodeInterface} root (optional) The root node
         */
        constructor: function(root) {
            var me = this;

            me.mixins.observable.constructor.call(me);

            if (root) {
                me.setRootNode(root);
            }

            // All these events from tree nodes bubbble up and fire on this Tree
            me.on({
                scope: me,
                idchanged: me.onNodeIdChanged,
                insert: me.onNodeInsert,
                append: me.onNodeAppend,
                remove: me.onNodeRemove
            });
        },

        /**
         * Returns the root node for this tree.
         * @return {Ext.data.NodeInterface}
         */
        getRootNode : function() {
            return this.root;
        },

        /**
         * Sets the root node for this tree.
         * @param {Ext.data.NodeInterface} node
         * @return {Ext.data.NodeInterface} The root node
         */
        setRootNode : function(node) {
            var me = this;

            me.root = node;

            // If the passed node is currently the root of another Tree, remove it.
            if (node.rootOf) {
                node.rootOf.removeRootNode();
            }

            // If the passed node is owned by some other node, remove it.
            else if (node.parentNode) {
                node.parentNode.removeChild(node);
            }

            // Insert upward link to owning Tree
            node.rootOf = me;

            if (node.fireEventArgs('beforeappend', [null, node]) !== false) {
                node.set('root', true);
                // root node should never be phantom or dirty, so commit it
                node.updateInfo(true, {
                    isFirst: true,
                    isLast: true,
                    depth: 0,
                    index: 0,
                    parentId: null
                });

                // The following events are fired on this TreePanel by the bubbling from NodeInterface.fireEvent
                /**
                 * @event append
                 * @inheritdoc Ext.data.NodeInterface#append
                 */
                /**
                 * @event remove
                 * @inheritdoc Ext.data.NodeInterface#remove
                 */
                /**
                 * @event move
                 * @inheritdoc Ext.data.NodeInterface#move
                 */
                /**
                 * @event insert
                 * @inheritdoc Ext.data.NodeInterface#insert
                 */
                /**
                 * @event beforeappend
                 * @inheritdoc Ext.data.NodeInterface#beforeappend
                 */
                /**
                 * @event beforeremove
                 * @inheritdoc Ext.data.NodeInterface#beforeremove
                 */
                /**
                 * @event beforemove
                 * @inheritdoc Ext.data.NodeInterface#beforemove
                 */
                /**
                 * @event beforeinsert
                 * @inheritdoc Ext.data.NodeInterface#beforeinsert
                 */
                /**
                 * @event expand
                 * @inheritdoc Ext.data.NodeInterface#expand
                 */
                /**
                 * @event collapse
                 * @inheritdoc Ext.data.NodeInterface#collapse
                 */
                /**
                 * @event beforeexpand
                 * @inheritdoc Ext.data.NodeInterface#beforeexpand
                 */
                /**
                 * @event beforecollapse
                 * @inheritdoc Ext.data.NodeInterface#beforecollapse
                 */
                /**
                 * @event sort
                 * @inheritdoc Ext.data.NodeInterface#event-sort
                 */
                /**
                 * @event rootchange
                 * Fires whenever the root node is changed in the tree.
                 * @param {Ext.data.Model} root The new root
                */

                me.nodeHash = {};
                node.fireEvent('append', null, node);
                node.fireEvent('rootchange', node);
            }

            return node;
        },

        /**
         * Removes the root node from this tree.
         * @return {Ext.data.NodeInterface} The root node
         */
        removeRootNode: function() {
            var me = this,
                root = me.root;

            root.set('root', false);
            root.fireEvent('remove', null, root, false);
            root.fireEvent('rootchange', null);

            // Unlink root after events so that the required bubbling propagates to all handlers.
            // This unregisters the node and its descendants.
            root.rootOf = me.root = null;
            return root;
        },

        /**
         * Flattens all the nodes in the tree into an array.
         * @private
         * @return {Ext.data.NodeInterface[]} The flattened nodes.
         */
        flatten: function(){
            return Ext.Object.getValues(this.nodeHash);
        },

        /**
         * Fired when a node is inserted into the root or one of it's children
         * @private
         * @param {Ext.data.NodeInterface} parent The parent node
         * @param {Ext.data.NodeInterface} node The inserted node
         */
        onNodeInsert: function(parent, node) {
            this.registerNode(node, true);
        },

        /**
         * Fired when a node is appended into the root or one of it's children
         * @private
         * @param {Ext.data.NodeInterface} parent The parent node
         * @param {Ext.data.NodeInterface} node The appended node
         */
        onNodeAppend: function(parent, node) {
            this.registerNode(node, true);
        },

        /**
         * Fired when a node is removed from the root or one of it's children
         * @private
         * @param {Ext.data.NodeInterface} parent The parent node
         * @param {Ext.data.NodeInterface} node The removed node
         */
        onNodeRemove: function(parent, node) {
            this.unregisterNode(node, true);
        },

        /**
         * Fired when a node's id changes.  Updates the node's id in the node hash.
         * @private
         * @param {Ext.data.NodeInterface} node
         * @param {Number} oldId The old id
         * @param {Number} newId The new id
         */
        onNodeIdChanged: function(node, oldId, newId, oldInternalId) {
            var nodeHash = this.nodeHash;

            nodeHash[node.internalId] = node;
            delete nodeHash[oldInternalId];
        },

        /**
         * Gets a node in this tree by its id.
         * @param {String} id
         * @return {Ext.data.NodeInterface} The match node.
         */
        getNodeById : function(id) {
            return this.nodeHash[id];
        },

        /**
         * Registers a node with the tree
         * @private
         * @param {Ext.data.NodeInterface} node The node to register
         * @param {Boolean} [includeChildren] True to unregister any child nodes
         */
        registerNode : function(node, includeChildren) {
            var me = this,
                children, length, i;

            me.nodeHash[node.internalId] = node;
            if (includeChildren === true) {
                children = node.childNodes;
                length = children.length;
                for (i = 0; i < length; i++) {
                    me.registerNode(children[i], true);
                }
            }
        },

        /**
         * Unregisters a node with the tree
         * @private
         * @param {Ext.data.NodeInterface} node The node to unregister
         * @param {Boolean} [includeChildren] True to unregister any child nodes
         */
        unregisterNode : function(node, includeChildren) {
            var me = this,
                children, length, i;

            delete me.nodeHash[node.internalId];
            if (includeChildren === true) {
                children = node.childNodes;
                length = children.length;
                for (i = 0; i < length; i++) {
                    me.unregisterNode(children[i], true);
                }
            }
        },

        /**
         * Sorts this tree
         * @private
         * @param {Function} sorterFn The function to use for sorting
         * @param {Boolean} recursive True to perform recursive sorting
         */
        sort: function(sorterFn, recursive) {
            this.getRootNode().sort(sorterFn, recursive);
        },

         /**
         * Filters this tree
         * @private
         * @param {Function} sorterFn The function to use for filtering
         * @param {Boolean} recursive True to perform recursive filtering
         */
        filter: function(filters, recursive) {
            this.getRootNode().filter(filters, recursive);
        }
    });