Package com.alee.extended.tree

Source Code of com.alee.extended.tree.ExTreeModel

/*
* 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 <http://www.gnu.org/licenses/>.
*/

package com.alee.extended.tree;

import com.alee.laf.tree.TreeState;
import com.alee.laf.tree.UniqueNode;
import com.alee.laf.tree.WebTree;
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.tree.MutableTreeNode;
import javax.swing.tree.TreeNode;
import java.util.*;

/**
* @author Mikle Garin
* @see com.alee.extended.tree.WebExTree
* @see com.alee.extended.tree.ExTreeDataProvider
*/

public class ExTreeModel<E extends UniqueNode> extends WebTreeModel<E>
{
    /**
     * Ex tree that uses this model.
     */
    protected final WebTree<E> tree;

    /**
     * Ex tree data provider.
     */
    protected final ExTreeDataProvider<E> dataProvider;

    /**
     * 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<String, Boolean> nodeCached = new HashMap<String, Boolean> ();

    /**
     * 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<String, List<E>> rawNodeChildsCache = new HashMap<String, List<E>> ();

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

    /**
     * Constructs default ex tree model using custom data provider.
     *
     * @param tree         asynchronous tree
     * @param dataProvider data provider
     */
    public ExTreeModel ( final WebTree<E> tree, final ExTreeDataProvider<E> dataProvider )
    {
        super ( null );
        this.tree = tree;
        this.dataProvider = dataProvider;
        loadTreeData ( getRootNode () );
    }

    /**
     * Returns ex tree data provider.
     *
     * @return data provider
     */
    public ExTreeDataProvider<E> 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 );
        }
        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 )
    {
        return ( E ) super.getChild ( parent, index );
    }

    /**
     * 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;
        }
    }

    /**
     * Forces model to cache the whole structure so any node can be accessed right away.
     * Note that this might take some time in case tree structure is large.
     * Though this doesn't force any repaints or other visual updates, so the speed depends only on ExTreeDataProvider.
     */
    protected void loadTreeData ( final E node )
    {
        final int childCount = getChildCount ( node );
        for ( int i = 0; i < childCount; i++ )
        {
            loadTreeData ( getChild ( node, i ) );
        }
    }

    /**
     * Reloads node childs.
     *
     * @param node node
     */
    @Override
    public void reload ( final TreeNode node )
    {
        final E reloadedNode = ( E ) node;

        // Cancels tree editing
        tree.cancelEditing ();

        // Cleaning up nodes cache
        clearNodeChildsCache ( reloadedNode, false );

        // Removing all old childs if such exist
        // We don't need to inform about child nodes removal here due to later structural update call
        reloadedNode.removeAllChildren ();

        // Forcing childs reload
        super.reload ( reloadedNode );

        // Forcing structure reload
        loadTreeData ( reloadedNode );
    }

    /**
     * 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<E> childs = rawNodeChildsCache.remove ( node.getId () );

            // Clears chld nodes cache
            if ( childs != null )
            {
                clearNodeChildsCache ( childs, 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<E> 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<E> 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 )
    {
        // Loading childs
        final List<E> childs = dataProvider.getChilds ( parent );

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

        // Filtering and sorting raw childs
        final List<E> 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 );
        }

        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<E> childs )
    {
        // Caching raw childs
        synchronized ( cacheLock )
        {
            rawNodeChildsCache.put ( parent.getId (), childs );
            cacheNodesById ( childs );
        }

        // Filtering and sorting raw childs
        final List<E> 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 );
                }
            }
        } );
    }

    /**
     * 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<E> childs )
    {
        // Adding new raw childs
        synchronized ( cacheLock )
        {
            List<E> cachedChilds = rawNodeChildsCache.get ( parent.getId () );
            if ( cachedChilds == null )
            {
                cachedChilds = new ArrayList<E> ( 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
        if ( parentNode == null )
        {
            return;
        }

        // Removing raw childs
        synchronized ( cacheLock )
        {
            final List<E> 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<E> 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;

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

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

        // Inserting node
        insertNodeIntoImpl ( childNode, parentNode, index );

        // 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<E> children, final E parent, final int index )
    {
        // Inserting new raw childs
        synchronized ( cacheLock )
        {
            List<E> childs = rawNodeChildsCache.get ( parent.getId () );
            if ( childs == null )
            {
                childs = new ArrayList<E> ( 1 );
                rawNodeChildsCache.put ( parent.getId (), childs );
            }
            childs.addAll ( index, children );
            cacheNodesById ( 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 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 )
    {
        // Inserting new raw childs
        synchronized ( cacheLock )
        {
            List<E> childs = rawNodeChildsCache.get ( parent.getId () );
            if ( childs == null )
            {
                childs = new ArrayList<E> ( 1 );
                rawNodeChildsCache.put ( parent.getId (), childs );
            }
            for ( int i = children.length - 1; i >= 0; i-- )
            {
                childs.add ( index, children[ i ] );
            }
            cacheNodesById ( Arrays.asList ( children ) );
        }

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

        // Inserting nodes
        insertNodesIntoImpl ( children, parent, index );

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

    /**
     * Inserts a child node into parent node.
     *
     * @param child  new child node
     * @param parent parent node
     * @param index  insert index
     */
    protected void insertNodeIntoImpl ( final E child, final E parent, final int index )
    {
        super.insertNodeInto ( child, parent, index );
    }

    /**
     * Inserts a list of child nodes into parent node.
     *
     * @param children list of new child nodes
     * @param parent   parent node
     * @param index    insert index
     */
    protected void insertNodesIntoImpl ( final List<E> children, final E parent, final int index )
    {
        super.insertNodesInto ( children, parent, index );
    }

    /**
     * Inserts an array of child nodes into parent node.
     *
     * @param children array of new child nodes
     * @param parent   parent node
     * @param index    insert index
     */
    protected void insertNodesIntoImpl ( final E[] children, final E parent, final int index )
    {
        super.insertNodesInto ( children, parent, index );
    }

    /**
     * Updates nodes sorting and filtering for all 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 )
        {
            performSortingAndFiltering ( parentNode, recursively );
        }
    }

    /**
     * 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<E> 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<E> 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<E> filterAndSort ( final E parentNode, List<E> childs )
    {
        // Simply return an empty array if there is no childs
        if ( childs == null || childs.size () == 0 )
        {
            return new ArrayList<E> ( 0 );
        }

        // Filter and sort childs
        final Filter<E> filter = dataProvider.getChildsFilter ( parentNode );
        final Comparator<E> comparator = dataProvider.getChildsComparator ( parentNode );
        if ( filter != null )
        {
            final List<E> 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<String, E> getNodesCache ()
    {
        return MapUtils.copyDoubleMap ( nodeById );
    }
}
TOP

Related Classes of com.alee.extended.tree.ExTreeModel

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.