All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.alee.extended.tree.AsyncTreeModel Maven / Gradle / Ivy

There is a newer version: 1.2.14
Show newest version
/*
 * This file is part of WebLookAndFeel library.
 *
 * WebLookAndFeel library is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * WebLookAndFeel library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with WebLookAndFeel library.  If not, see .
 */

package com.alee.extended.tree;

import com.alee.laf.tree.TreeState;
import com.alee.laf.tree.WebTreeModel;
import com.alee.utils.CollectionUtils;
import com.alee.utils.MapUtils;
import com.alee.utils.SwingUtils;
import com.alee.utils.collection.DoubleMap;
import com.alee.utils.compare.Filter;

import javax.swing.*;
import javax.swing.tree.MutableTreeNode;
import javax.swing.tree.TreeNode;
import java.util.*;

/**
 * Special model for asynchronous tree that provides asynchronous data loading.
 * This class also controls the loading animation in elements.
 *
 * @param  custom node type
 * @author Mikle Garin
 */

public class AsyncTreeModel extends WebTreeModel
{
    /**
     * todo 1. Add AsyncTreeDataUpdater support
     */

    /**
     * Cache key for root node.
     */
    protected static final String ROOT_CACHE = "root";

    /**
     * Lock object for asynchronous tree listeners.
     */
    protected final Object modelListenersLock = new Object ();

    /**
     * Asynchronous tree listeners.
     */
    protected final List asyncTreeModelListeners = new ArrayList ( 1 );

    /**
     * Asynchronous tree that uses this model.
     */
    protected WebAsyncTree tree;

    /**
     * Whether to load childs asynchronously or not.
     */
    protected boolean asyncLoading = true;

    /**
     * Asynchronous tree data provider.
     */
    protected AsyncTreeDataProvider dataProvider;

    //    /**
    //     * Data updater for this asynchronous tree.
    //     */
    //    protected AsyncTreeDataUpdater dataUpdater;

    /**
     * Root node cache.
     * Cached when root is requested for the first time.
     */
    protected E rootNode = null;

    /**
     * Lock object for cache changes.
     */
    protected final Object cacheLock = new Object ();

    /**
     * Nodes cached states (parent ID -> childs cached state).
     * If child nodes for some parent node are cached then this map contains "true" value under that parent node ID as a key.
     */
    protected final Map nodeCached = new HashMap ();

    /**
     * Cache for childs nodes returned by data provider (parent ID -> list of raw child nodes).
     * This map contains raw childs which weren't affected by sorting and filtering operations.
     * If childs needs to be re-sorted or re-filtered they are simply taken from the cache and re-organized once again.
     */
    protected final Map> rawNodeChildsCache = new HashMap> ();

    /**
     * Direct nodes cache (node ID -> node).
     * Used for quick node search within the tree.
     */
    protected final DoubleMap nodeById = new DoubleMap ();

    /**
     * Lock object for busy state changes.
     */
    protected final Object busyLock = new Object ();

    /**
     * Constructs default asynchronous tree model using custom data provider.
     *
     * @param tree         asynchronous tree
     * @param dataProvider data provider
     */
    public AsyncTreeModel ( final WebAsyncTree tree, final AsyncTreeDataProvider dataProvider )
    {
        super ( null );
        this.tree = tree;
        this.dataProvider = dataProvider;
    }

    //    /**
    //     * Returns data updater for this asynchronous tree.
    //     *
    //     * @return data updater
    //     */
    //    public AsyncTreeDataUpdater getDataUpdater ()
    //    {
    //        return dataUpdater;
    //    }
    //
    //    /**
    //     * Changes data updater for this asynchronous tree.
    //     *
    //     * @param dataUpdater new data updater
    //     */
    //    public void setDataUpdater ( final AsyncTreeDataUpdater dataUpdater )
    //    {
    //        this.dataUpdater = dataUpdater;
    //    }

    /**
     * Returns whether childs are loaded asynchronously or not.
     *
     * @return true if childs are loaded asynchronously, false otherwise
     */
    public boolean isAsyncLoading ()
    {
        return asyncLoading;
    }

    /**
     * Sets whether to load childs asynchronously or not.
     *
     * @param asyncLoading whether to load childs asynchronously or not
     */
    public void setAsyncLoading ( final boolean asyncLoading )
    {
        this.asyncLoading = asyncLoading;
    }

    /**
     * Returns asynchronous tree data provider.
     *
     * @return data provider
     */
    public AsyncTreeDataProvider getDataProvider ()
    {
        return dataProvider;
    }

    /**
     * Returns tree root node.
     *
     * @return root node
     */
    @Override
    public E getRoot ()
    {
        if ( rootNode == null )
        {
            // Retrieving and caching root node
            rootNode = dataProvider.getRoot ();

            // Caching root node by ID
            cacheNodeById ( rootNode );

            // Adding image observer
            registerObserver ( rootNode );
        }
        return rootNode;
    }

    /**
     * Returns whether the specified node is leaf or not.
     *
     * @param node node
     * @return true if node is leaf, false otherwise
     */
    @Override
    public boolean isLeaf ( final Object node )
    {
        return dataProvider.isLeaf ( ( E ) node );
    }

    /**
     * Returns childs count for specified node.
     *
     * @param parent parent node
     * @return childs count
     */
    @Override
    public int getChildCount ( final Object parent )
    {
        final E node = ( E ) parent;
        if ( isLeaf ( node ) )
        {
            return 0;
        }
        else if ( areChildsLoaded ( node ) )
        {
            return super.getChildCount ( parent );
        }
        else
        {
            return loadChildsCount ( node );
        }
    }

    /**
     * Returns child node for parent node at the specified index.
     *
     * @param parent parent node
     * @param index  child node index
     * @return child node
     */
    @Override
    public E getChild ( final Object parent, final int index )
    {
        final E node = ( E ) parent;
        if ( areChildsLoaded ( node ) )
        {
            return ( E ) super.getChild ( parent, index );
        }
        else
        {
            return null;
        }
    }

    /**
     * Returns whether childs for the specified node are already loaded or not.
     *
     * @param node node to process
     * @return true if childs for the specified node are already loaded, false otherwise
     */
    public boolean areChildsLoaded ( final E node )
    {
        synchronized ( cacheLock )
        {
            final Boolean cached = nodeCached.get ( node.getId () );
            return cached != null && cached;
        }
    }

    /**
     * Reloads node childs.
     *
     * @param node node
     */
    @Override
    public void reload ( final TreeNode node )
    {
        // Cancels tree editing
        tree.cancelEditing ();

        // Cleaning up nodes cache
        clearNodeChildsCache ( ( E ) node, false );

        // Forcing childs reload
        super.reload ( node );
    }

    /**
     * Clears node and all of its child nodes childs cached states.
     *
     * @param node      node to clear cache for
     * @param clearNode whether should clear node cache or not
     */
    protected void clearNodeChildsCache ( final E node, final boolean clearNode )
    {
        synchronized ( cacheLock )
        {
            // Clears node cache
            if ( clearNode )
            {
                nodeById.remove ( node.getId () );
            }

            // Clears node childs cached state
            nodeCached.remove ( node.getId () );

            // Clears node raw childs cache
            final List childs = rawNodeChildsCache.remove ( node.getId () );

            // Clears chld nodes cache
            if ( childs != null )
            {
                for ( final E child : childs )
                {
                    clearNodeChildsCache ( child, true );
                }
            }
        }
    }

    /**
     * Clears nodes childs cached states.
     *
     * @param nodes      nodes to clear cache for
     * @param clearNodes whether should clear nodes cache or not
     */
    protected void clearNodeChildsCache ( final List nodes, final boolean clearNodes )
    {
        synchronized ( cacheLock )
        {
            for ( final E node : nodes )
            {
                clearNodeChildsCache ( node, clearNodes );
            }
        }
    }

    /**
     * Clears nodes childs cached states.
     *
     * @param nodes      nodes to clear cache for
     * @param clearNodes whether should clear nodes cache or not
     */
    protected void clearNodeChildsCache ( final E[] nodes, final boolean clearNodes )
    {
        synchronized ( cacheLock )
        {
            for ( final E node : nodes )
            {
                clearNodeChildsCache ( node, clearNodes );
            }
        }
    }

    /**
     * Caches node by its IDs.
     *
     * @param node node to cache
     */
    protected void cacheNodeById ( final E node )
    {
        synchronized ( cacheLock )
        {
            nodeById.put ( node.getId (), node );
        }
    }

    /**
     * Caches nodes by their IDs.
     *
     * @param nodes list of nodes to cache
     */
    protected void cacheNodesById ( final List nodes )
    {
        synchronized ( cacheLock )
        {
            for ( final E node : nodes )
            {
                nodeById.put ( node.getId (), node );
            }
        }
    }

    /**
     * Loads (or reloads) node childs and returns zero or childs count if async mode is off.
     * This is base method that uses installed AsyncTreeDataProvider to retrieve tree node childs.
     *
     * @param parent node to load childs for
     * @return zero or childs count if async mode is off
     * @see AsyncTreeDataProvider#loadChilds(AsyncUniqueNode, ChildsListener)
     */
    protected int loadChildsCount ( final E parent )
    {
        // Checking if the node is busy already
        synchronized ( busyLock )
        {
            if ( parent.isLoading () )
            {
                return 0;
            }
            else
            {
                parent.setState ( AsyncNodeState.loading );
                nodeChanged ( parent );
            }
        }

        // Firing load started event
        fireChildsLoadStarted ( parent );

        // Removing all old childs if such exist
        final int childCount = parent.getChildCount ();
        if ( childCount > 0 )
        {
            final int[] indices = new int[ childCount ];
            final Object[] childs = new Object[ childCount ];
            for ( int i = childCount - 1; i >= 0; i-- )
            {
                indices[ i ] = i;
                childs[ i ] = parent.getChildAt ( i );
                parent.remove ( i );
            }
            nodesWereRemoved ( parent, indices, childs );
        }

        // Loading node childs
        if ( asyncLoading )
        {
            // Executing childs load in a separate thread to avoid locking EDT
            // This queue will also take care of amount of threads to execute async trees requests
            AsyncTreeQueue.execute ( tree, new Runnable ()
            {
                @Override
                public void run ()
                {
                    // Loading childs
                    dataProvider.loadChilds ( parent, new ChildsListener ()
                    {
                        @Override
                        public void childsLoadCompleted ( final List childs )
                        {
                            // Caching raw childs
                            synchronized ( cacheLock )
                            {
                                rawNodeChildsCache.put ( parent.getId (), childs );
                                cacheNodesById ( childs );
                            }

                            // Filtering and sorting raw childs
                            final List realChilds = filterAndSort ( parent, childs );

                            // Updating cache
                            synchronized ( cacheLock )
                            {
                                nodeCached.put ( parent.getId (), true );
                            }

                            // Performing UI updates and event notification in EDT
                            SwingUtils.invokeLater ( new Runnable ()
                            {
                                @Override
                                public void run ()
                                {
                                    // Checking if any nodes loaded
                                    if ( realChilds != null && realChilds.size () > 0 )
                                    {
                                        // Inserting loaded nodes
                                        insertNodesIntoImpl ( realChilds, parent, 0 );
                                    }

                                    // Releasing node busy state
                                    synchronized ( busyLock )
                                    {
                                        parent.setState ( AsyncNodeState.loaded );
                                        nodeChanged ( parent );
                                    }

                                    // Firing load completed event
                                    fireChildsLoadCompleted ( parent, realChilds );
                                }
                            } );
                        }

                        @Override
                        public void childsLoadFailed ( final Throwable cause )
                        {
                            // Caching childs
                            synchronized ( cacheLock )
                            {
                                rawNodeChildsCache.put ( parent.getId (), new ArrayList ( 0 ) );
                                nodeCached.put ( parent.getId (), true );
                            }

                            // Performing event notification in EDT
                            SwingUtils.invokeLater ( new Runnable ()
                            {
                                @Override
                                public void run ()
                                {
                                    // Releasing node busy state
                                    synchronized ( busyLock )
                                    {
                                        parent.setState ( AsyncNodeState.failed );
                                        parent.setFailureCause ( cause );
                                        nodeChanged ( parent );
                                    }

                                    // Firing load failed event
                                    fireChildsLoadFailed ( parent, cause );
                                }
                            } );
                        }
                    } );
                }
            } );
            return 0;
        }
        else
        {
            // Loading childs
            dataProvider.loadChilds ( parent, new ChildsListener ()
            {
                @Override
                public void childsLoadCompleted ( final List childs )
                {
                    // Caching raw childs
                    synchronized ( cacheLock )
                    {
                        rawNodeChildsCache.put ( parent.getId (), childs );
                        cacheNodesById ( childs );
                    }

                    // Filtering and sorting raw childs
                    final List realChilds = filterAndSort ( parent, childs );

                    // Updating cache
                    synchronized ( cacheLock )
                    {
                        nodeCached.put ( parent.getId (), true );
                    }

                    // Checking if any nodes loaded
                    if ( realChilds != null && realChilds.size () > 0 )
                    {
                        // Inserting loaded nodes
                        insertNodesIntoImpl ( realChilds, parent, 0 );
                    }

                    // Releasing node busy state
                    synchronized ( busyLock )
                    {
                        parent.setState ( AsyncNodeState.loaded );
                        nodeChanged ( parent );
                    }

                    // Firing load completed event
                    fireChildsLoadCompleted ( parent, realChilds );
                }

                @Override
                public void childsLoadFailed ( final Throwable cause )
                {
                    // Caching childs
                    synchronized ( cacheLock )
                    {
                        rawNodeChildsCache.put ( parent.getId (), new ArrayList ( 0 ) );
                        nodeCached.put ( parent.getId (), true );
                    }

                    // Releasing node busy state
                    synchronized ( busyLock )
                    {
                        parent.setState ( AsyncNodeState.failed );
                        parent.setFailureCause ( cause );
                        nodeChanged ( parent );
                    }

                    // Firing load failed event
                    fireChildsLoadFailed ( parent, cause );
                }
            } );
            return parent.getChildCount ();
        }
    }

    /**
     * Sets child nodes for the specified node.
     * This method might be used to manually change tree node childs without causing any structure corruptions.
     *
     * @param parent node to process
     * @param childs new node childs
     */
    public void setChildNodes ( final E parent, final List childs )
    {
        // Check if the node is busy already
        synchronized ( busyLock )
        {
            if ( parent.isLoading () )
            {
                return;
            }
            else
            {
                parent.setState ( AsyncNodeState.loading );
                nodeChanged ( parent );
            }
        }

        // Caching raw childs
        synchronized ( cacheLock )
        {
            rawNodeChildsCache.put ( parent.getId (), childs );
            cacheNodesById ( childs );
        }

        // Filtering and sorting raw childs
        final List realChilds = filterAndSort ( parent, childs );

        // Updating cache
        synchronized ( cacheLock )
        {
            nodeCached.put ( parent.getId (), true );
        }

        // Performing UI updates in EDT
        SwingUtils.invokeLater ( new Runnable ()
        {
            @Override
            public void run ()
            {
                // Checking if any nodes loaded
                if ( realChilds != null && realChilds.size () > 0 )
                {
                    // Clearing raw nodes cache
                    // That might be required in case nodes were moved inside of the tree
                    clearNodeChildsCache ( childs, false );

                    // Inserting nodes
                    insertNodesIntoImpl ( realChilds, parent, 0 );
                }

                // Release node busy state
                synchronized ( busyLock )
                {
                    parent.setState ( AsyncNodeState.loaded );
                    nodeChanged ( parent );
                }

                // Firing load completed event
                fireChildsLoadCompleted ( parent, realChilds );
            }
        } );
    }

    /**
     * Adds child nodes for the specified node.
     * This method might be used to manually change tree node childs without causing any structure corruptions.
     *
     * @param parent node to process
     * @param childs new node childs
     */
    public void addChildNodes ( final E parent, final List childs )
    {
        // Simply ignore if parent node is not yet loaded
        if ( !parent.isLoaded () )
        {
            return;
        }

        // Adding new raw childs
        synchronized ( cacheLock )
        {
            List cachedChilds = rawNodeChildsCache.get ( parent.getId () );
            if ( cachedChilds == null )
            {
                cachedChilds = new ArrayList ( childs.size () );
                rawNodeChildsCache.put ( parent.getId (), cachedChilds );
            }
            cachedChilds.addAll ( childs );
            cacheNodesById ( childs );
        }

        // Clearing nodes cache
        // That might be required in case nodes were moved inside of the tree
        clearNodeChildsCache ( childs, false );

        // Inserting nodes
        insertNodesIntoImpl ( childs, parent, parent.getChildCount () );

        // Updating parent node sorting and filtering
        updateSortingAndFiltering ( parent );
    }

    /**
     * Removes specified node from parent node.
     *
     * @param node node to remove
     */
    @Override
    public void removeNodeFromParent ( final MutableTreeNode node )
    {
        // Simply ignore null nodes
        if ( node == null )
        {
            return;
        }

        final E childNode = ( E ) node;
        final E parentNode = ( E ) childNode.getParent ();

        // Simply ignore if parent node is null or not yet loaded
        if ( parentNode == null || !parentNode.isLoaded () )
        {
            return;
        }

        // Removing raw childs
        synchronized ( cacheLock )
        {
            final List childs = rawNodeChildsCache.get ( parentNode.getId () );
            if ( childs != null )
            {
                childs.remove ( childNode );
            }
        }

        // Clearing node cache
        clearNodeChildsCache ( childNode, true );

        // Removing node from parent
        super.removeNodeFromParent ( node );

        // Updating parent node sorting and filtering
        updateSortingAndFiltering ( parentNode );
    }

    // todo Implement when those methods will be separate from single one
    //    public void removeNodesFromParent ( List nodes )
    //    {
    //        super.removeNodesFromParent ( nodes );
    //    }
    //
    //    public void removeNodesFromParent ( E[] nodes )
    //    {
    //        super.removeNodesFromParent ( nodes );
    //    }

    /**
     * Inserts new child node into parent node at the specified index.
     *
     * @param newChild new child node
     * @param parent   parent node
     * @param index    insert index
     */
    @Override
    public void insertNodeInto ( final MutableTreeNode newChild, final MutableTreeNode parent, final int index )
    {
        final E childNode = ( E ) newChild;
        final E parentNode = ( E ) parent;

        // Simply ignore if parent node is not yet loaded
        if ( !parentNode.isLoaded () )
        {
            return;
        }

        // Inserting new raw childs
        synchronized ( cacheLock )
        {
            List childs = rawNodeChildsCache.get ( parentNode.getId () );
            if ( childs == null )
            {
                childs = new ArrayList ( 1 );
                rawNodeChildsCache.put ( parentNode.getId (), childs );
            }
            childs.add ( childNode );
        }

        // Clearing node cache
        // That might be required in case nodes were moved inside of the tree
        clearNodeChildsCache ( childNode, false );

        // Inserting node
        super.insertNodeInto ( newChild, parent, index );

        // Adding image observer
        registerObserver ( childNode );

        // Updating parent node sorting and filtering
        updateSortingAndFiltering ( parentNode );
    }

    /**
     * Inserts a list of child nodes into parent node.
     *
     * @param children list of new child nodes
     * @param parent   parent node
     * @param index    insert index
     */
    @Override
    public void insertNodesInto ( final List children, final E parent, final int index )
    {
        // Simply ignore if parent node is not yet loaded
        if ( !parent.isLoaded () )
        {
            return;
        }

        // Inserting new raw childs
        synchronized ( cacheLock )
        {
            List childs = rawNodeChildsCache.get ( parent.getId () );
            if ( childs == null )
            {
                childs = new ArrayList ( 1 );
                rawNodeChildsCache.put ( parent.getId (), childs );
            }
            childs.addAll ( index, children );
        }

        // Clearing nodes cache
        // That might be required in case nodes were moved inside of the tree
        clearNodeChildsCache ( children, false );

        // Performing actual nodes insertion
        insertNodesIntoImpl ( children, parent, index );

        // Updating parent node sorting and filtering
        updateSortingAndFiltering ( parent );
    }

    /**
     * Inserts a list of child nodes into parent node.
     * This method is used by model itself to add nodes when they are loaded so it doesn't update raw nodes cache.
     *
     * @param children list of new child nodes
     * @param parent   parent node
     * @param index    insert index
     */
    protected void insertNodesIntoImpl ( final List children, final E parent, final int index )
    {
        super.insertNodesInto ( children, parent, index );

        // Adding image observers
        registerObservers ( children );
    }

    /**
     * Inserts an array of child nodes into parent node.
     *
     * @param children array of new child nodes
     * @param parent   parent node
     * @param index    insert index
     */
    @Override
    public void insertNodesInto ( final E[] children, final E parent, final int index )
    {
        // Simply ignore if parent node is not yet loaded
        if ( !parent.isLoaded () )
        {
            return;
        }

        // Inserting new raw childs
        synchronized ( cacheLock )
        {
            List childs = rawNodeChildsCache.get ( parent.getId () );
            if ( childs == null )
            {
                childs = new ArrayList ( 1 );
                rawNodeChildsCache.put ( parent.getId (), childs );
            }
            for ( int i = children.length - 1; i >= 0; i-- )
            {
                childs.add ( index, children[ i ] );
            }
        }

        // Clearing nodes cache
        // That might be required in case nodes were moved inside of the tree
        clearNodeChildsCache ( children, false );

        // Inserting nodes
        super.insertNodesInto ( children, parent, index );

        // Adding image observers
        registerObservers ( children );

        // Updating parent node sorting and filtering
        updateSortingAndFiltering ( parent );
    }

    /**
     * Updates nodes sorting and filtering for all loaded nodes.
     */
    public void updateSortingAndFiltering ()
    {
        updateSortingAndFiltering ( getRoot (), true );
    }

    /**
     * Updates sorting and filtering for the specified node childs.
     *
     * @param parentNode node which childs sorting and filtering should be updated
     */
    public void updateSortingAndFiltering ( final E parentNode )
    {
        updateSortingAndFiltering ( parentNode, false );
    }

    /**
     * Updates sorting and filtering for the specified node childs.
     *
     * @param parentNode  node which childs sorting and filtering should be updated
     * @param recursively whether should update the whole childs structure recursively or not
     */
    public void updateSortingAndFiltering ( final E parentNode, final boolean recursively )
    {
        // Process only this is not a root node
        // We don't need to update root sorting as there is always one root in the tree
        if ( parentNode != null )
        {
            // Process this action only if node childs are already loaded and cached
            if ( parentNode.isLoaded () && rawNodeChildsCache.containsKey ( parentNode.getId () ) )
            {
                // Childs are already loaded, simply updating their sorting and filtering
                performSortingAndFiltering ( parentNode, recursively );
            }
            else if ( parentNode.isLoading () )
            {
                // Childs are being loaded, wait until the operation finishes
                addAsyncTreeModelListener ( new AsyncTreeModelAdapter ()
                {
                    @Override
                    public void childsLoadCompleted ( final AsyncUniqueNode parent, final List childs )
                    {
                        if ( parentNode.getId ().equals ( parent.getId () ) )
                        {
                            removeAsyncTreeModelListener ( this );
                            performSortingAndFiltering ( parentNode, recursively );
                        }
                    }

                    @Override
                    public void childsLoadFailed ( final AsyncUniqueNode parent, final Throwable cause )
                    {
                        if ( parentNode.getId ().equals ( parent.getId () ) )
                        {
                            removeAsyncTreeModelListener ( this );
                        }
                    }
                } );
            }
        }
    }

    /**
     * Updates node childs using current comparator and filter.
     * Updates the whole node childs structure if recursive update requested.
     *
     * @param parentNode  node which childs sorting and filtering should be updated
     * @param recursively whether should update the whole childs structure recursively or not
     */
    protected void performSortingAndFiltering ( final E parentNode, final boolean recursively )
    {
        // todo Restore tree state only for the updated node
        // Saving tree state to restore it right after childs update
        final TreeState treeState = tree.getTreeState ();

        // Updating root node childs
        if ( recursively )
        {
            performSortingAndFilteringRecursivelyImpl ( parentNode );
        }
        else
        {
            performSortingAndFilteringImpl ( parentNode );
        }
        nodeStructureChanged ( parentNode );

        // Restoring tree state including all selections and expansions
        tree.setTreeState ( treeState );
    }

    /**
     * Updates node childs using current comparator and filter.
     *
     * @param parentNode node to update
     */
    protected void performSortingAndFilteringRecursivelyImpl ( final E parentNode )
    {
        performSortingAndFilteringImpl ( parentNode );
        for ( int i = 0; i < parentNode.getChildCount (); i++ )
        {
            performSortingAndFilteringRecursivelyImpl ( ( E ) parentNode.getChildAt ( i ) );
        }
    }

    /**
     * Updates node childs recursively using current comparator and filter.
     *
     * @param parentNode node to update
     */
    protected void performSortingAndFilteringImpl ( final E parentNode )
    {
        // Retrieving raw childs
        final List childs = rawNodeChildsCache.get ( parentNode.getId () );

        // Process this action only if node childs are already loaded and cached
        if ( childs != null )
        {
            // Removing old children
            parentNode.removeAllChildren ();

            // Filtering and sorting raw childs
            final List realChilds = filterAndSort ( parentNode, childs );

            // Inserting new childs
            for ( final E child : realChilds )
            {
                parentNode.add ( child );
            }
        }
    }

    /**
     * Performs raw childs filtering and sorting before they can be passed into real tree and returns list of filtered and sorted childs.
     *
     * @param childs childs to filter and sort
     * @return list of filtered and sorted childs
     */
    protected List filterAndSort ( final E parentNode, List childs )
    {
        // Simply return an empty array if there is no childs
        if ( childs == null || childs.size () == 0 )
        {
            return new ArrayList ( 0 );
        }

        // Filter and sort childs
        final Filter filter = dataProvider.getChildsFilter ( parentNode );
        final Comparator comparator = dataProvider.getChildsComparator ( parentNode );
        if ( filter != null )
        {
            final List filtered = CollectionUtils.filter ( childs, filter );
            if ( comparator != null )
            {
                Collections.sort ( filtered, comparator );
            }
            return filtered;
        }
        else
        {
            if ( comparator != null )
            {
                childs = CollectionUtils.copy ( childs );
                Collections.sort ( childs, comparator );
            }
            return childs;
        }
    }

    /**
     * Looks for the node with the specified ID in the tree model and returns it or null if it was not found.
     *
     * @param nodeId node ID
     * @return node with the specified ID or null if it was not found
     */
    public E findNode ( final String nodeId )
    {
        return nodeById.get ( nodeId );
    }

    /**
     * Returns nodes cache map copy.
     *
     * @return nodes cache map copy
     */
    public DoubleMap getNodesCache ()
    {
        return MapUtils.copyDoubleMap ( nodeById );
    }

    /**
     * Registers image observer for loader icons of the specified nodes.
     *
     * @param nodes nodes
     */
    protected void registerObservers ( final List nodes )
    {
        for ( final E newChild : nodes )
        {
            registerObserver ( newChild );
        }
    }

    /**
     * Registers image observer for loader icons of the specified nodes.
     *
     * @param nodes nodes
     */
    protected void registerObservers ( final E[] nodes )
    {
        for ( final E newChild : nodes )
        {
            registerObserver ( newChild );
        }
    }

    /**
     * Registers image observer for loader icon of the specified node.
     *
     * @param node node
     */
    protected void registerObserver ( final E node )
    {
        final ImageIcon loaderIcon = node.getLoaderIcon ();
        if ( loaderIcon != null )
        {
            loaderIcon.setImageObserver ( new NodeImageObserver ( tree, node ) );
        }
    }

    /**
     * Returns list of all available asynchronous tree model listeners.
     *
     * @return asynchronous tree model listeners list
     */
    public List getAsyncTreeModelListeners ()
    {
        synchronized ( modelListenersLock )
        {
            return CollectionUtils.copy ( asyncTreeModelListeners );
        }
    }

    /**
     * Adds new asynchronous tree model listener.
     *
     * @param listener asynchronous tree model listener to add
     */
    public void addAsyncTreeModelListener ( final AsyncTreeModelListener listener )
    {
        synchronized ( modelListenersLock )
        {
            asyncTreeModelListeners.add ( listener );
        }
    }

    /**
     * Removes asynchronous tree model listener.
     *
     * @param listener asynchronous tree model listener to remove
     */
    public void removeAsyncTreeModelListener ( final AsyncTreeModelListener listener )
    {
        synchronized ( modelListenersLock )
        {
            asyncTreeModelListeners.remove ( listener );
        }
    }

    /**
     * Fires childs load start event.
     *
     * @param parent node which childs are being loaded
     */
    protected void fireChildsLoadStarted ( final E parent )
    {
        final List listeners;
        synchronized ( modelListenersLock )
        {
            listeners = CollectionUtils.copy ( asyncTreeModelListeners );
        }
        for ( final AsyncTreeModelListener listener : listeners )
        {
            listener.childsLoadStarted ( parent );
        }
    }

    /**
     * Fires childs load complete event.
     *
     * @param parent node which childs were loaded
     * @param childs loaded child nodes
     */
    protected void fireChildsLoadCompleted ( final E parent, final List childs )
    {
        final List listeners;
        synchronized ( modelListenersLock )
        {
            listeners = CollectionUtils.copy ( asyncTreeModelListeners );
        }
        for ( final AsyncTreeModelListener listener : listeners )
        {
            listener.childsLoadCompleted ( parent, childs );
        }
    }

    /**
     * Fires childs load failed event.
     *
     * @param parent node which childs were loaded
     * @param cause  childs load failure cause
     */
    protected void fireChildsLoadFailed ( final E parent, final Throwable cause )
    {
        final List listeners;
        synchronized ( modelListenersLock )
        {
            listeners = CollectionUtils.copy ( asyncTreeModelListeners );
        }
        for ( final AsyncTreeModelListener listener : listeners )
        {
            listener.childsLoadFailed ( parent, cause );
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy