com.alee.extended.tree.AsyncTreeModel Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of weblaf-ui Show documentation
Show all versions of weblaf-ui Show documentation
WebLaf is a Java Swing Look and Feel and extended components library for cross-platform applications
/*
* 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.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
* @see com.alee.extended.tree.WebAsyncTree
* @see com.alee.extended.tree.AsyncTreeDataProvider
*/
public class AsyncTreeModel extends WebTreeModel
{
/**
* todo 1. Add AsyncTreeDataUpdater support
*/
/**
* 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 final WebAsyncTree tree;
/**
* Asynchronous tree data provider.
*/
protected final AsyncTreeDataProvider dataProvider;
/**
* Whether to load children asynchronously or not.
*/
protected boolean asyncLoading = true;
// /**
// * 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 -> children 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 children nodes returned by data provider (parent ID -> list of raw child nodes).
* This map contains raw children which weren't affected by sorting and filtering operations.
* If children needs to be re-sorted or re-filtered they are simply taken from the cache and re-organized once again.
*/
protected final Map> rawNodeChildrenCache = 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 children are loaded asynchronously or not.
*
* @return true if children are loaded asynchronously, false otherwise
*/
public boolean isAsyncLoading ()
{
return asyncLoading;
}
/**
* Sets whether to load children asynchronously or not.
*
* @param asyncLoading whether to load children 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
rootNode.attachLoadIconObserver ( tree );
}
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 children count for specified node.
*
* @param parent parent node
* @return children count
*/
@Override
public int getChildCount ( final Object parent )
{
final E node = ( E ) parent;
if ( isLeaf ( node ) )
{
return 0;
}
else if ( areChildrenLoaded ( node ) )
{
return super.getChildCount ( parent );
}
else
{
return loadChildren ( 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 ( areChildrenLoaded ( node ) )
{
return ( E ) super.getChild ( parent, index );
}
else
{
return null;
}
}
/**
* Returns whether children for the specified node are already loaded or not.
*
* @param node node to process
* @return true if children for the specified node are already loaded, false otherwise
*/
public boolean areChildrenLoaded ( final E node )
{
synchronized ( cacheLock )
{
final Boolean cached = nodeCached.get ( node.getId () );
return cached != null && cached;
}
}
/**
* Reloads node children.
*
* @param node node
*/
@Override
public void reload ( final TreeNode node )
{
// Cancels tree editing
tree.cancelEditing ();
// Cleaning up nodes cache
clearNodeChildrenCache ( ( E ) node, false );
// Forcing children reload
super.reload ( node );
}
/**
* Clears node and all of its child nodes children cached states.
*
* @param node node to clear cache for
* @param clearNode whether should clear node cache or not
*/
protected void clearNodeChildrenCache ( final E node, final boolean clearNode )
{
synchronized ( cacheLock )
{
// Clears node cache
if ( clearNode )
{
nodeById.remove ( node.getId () );
}
// Clears node children cached state
nodeCached.remove ( node.getId () );
// Clears node raw children cache
final List children = rawNodeChildrenCache.remove ( node.getId () );
// Clears chld nodes cache
if ( children != null )
{
clearNodeChildrenCache ( children, true );
}
}
}
/**
* Clears nodes children cached states.
*
* @param nodes nodes to clear cache for
* @param clearNodes whether should clear nodes cache or not
*/
protected void clearNodeChildrenCache ( final List nodes, final boolean clearNodes )
{
synchronized ( cacheLock )
{
for ( final E node : nodes )
{
clearNodeChildrenCache ( node, clearNodes );
}
}
}
/**
* Clears nodes children cached states.
*
* @param nodes nodes to clear cache for
* @param clearNodes whether should clear nodes cache or not
*/
protected void clearNodeChildrenCache ( final E[] nodes, final boolean clearNodes )
{
synchronized ( cacheLock )
{
for ( final E node : nodes )
{
clearNodeChildrenCache ( 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 children and returns zero or children count if async mode is off.
* This is base method that uses installed AsyncTreeDataProvider to retrieve tree node children.
*
* @param parent node to load children for
* @return zero or children count if async mode is off
* @see AsyncTreeDataProvider#loadChildren(AsyncUniqueNode, ChildrenListener)
*/
protected int loadChildren ( final E parent )
{
// todo Use when moved to JDK8?
// final SecondaryLoop loop = Toolkit.getDefaultToolkit ().getSystemEventQueue ().createSecondaryLoop ();
// loop.enter/exit
// 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
fireChildrenLoadStarted ( parent );
// todo This should actually be called on node reload?
// Removing all old children if such exist
final int childCount = parent.getChildCount ();
if ( childCount > 0 )
{
final int[] indices = new int[ childCount ];
final Object[] children = new Object[ childCount ];
for ( int i = childCount - 1; i >= 0; i-- )
{
indices[ i ] = i;
children[ i ] = parent.getChildAt ( i );
parent.remove ( i );
}
nodesWereRemoved ( parent, indices, children );
}
// Loading node children
if ( asyncLoading )
{
// Executing children 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 children
dataProvider.loadChildren ( parent, new ChildrenListener ()
{
@Override
public void loadCompleted ( final List children )
{
// Caching raw children
synchronized ( cacheLock )
{
rawNodeChildrenCache.put ( parent.getId (), children );
cacheNodesById ( children );
}
// Filtering and sorting raw children
final List realChildren = filterAndSort ( parent, children );
// 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 ( realChildren != null && realChildren.size () > 0 )
{
// Inserting loaded nodes
insertNodesIntoImpl ( realChildren, parent, 0 );
}
// Releasing node busy state
synchronized ( busyLock )
{
parent.setState ( AsyncNodeState.loaded );
nodeChanged ( parent );
}
// Firing load completed event
fireChildrenLoadCompleted ( parent, realChildren );
}
} );
}
@Override
public void loadFailed ( final Throwable cause )
{
// Caching children
synchronized ( cacheLock )
{
rawNodeChildrenCache.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
fireChildrenLoadFailed ( parent, cause );
}
} );
}
} );
}
} );
return 0;
}
else
{
// Loading children
dataProvider.loadChildren ( parent, new ChildrenListener ()
{
@Override
public void loadCompleted ( final List children )
{
// Caching raw children
synchronized ( cacheLock )
{
rawNodeChildrenCache.put ( parent.getId (), children );
cacheNodesById ( children );
}
// Filtering and sorting raw children
final List realChildren = filterAndSort ( parent, children );
// Updating cache
synchronized ( cacheLock )
{
nodeCached.put ( parent.getId (), true );
}
// Checking if any nodes loaded
if ( realChildren != null && realChildren.size () > 0 )
{
// Inserting loaded nodes
insertNodesIntoImpl ( realChildren, parent, 0 );
}
// Releasing node busy state
synchronized ( busyLock )
{
parent.setState ( AsyncNodeState.loaded );
nodeChanged ( parent );
}
// Firing load completed event
fireChildrenLoadCompleted ( parent, realChildren );
}
@Override
public void loadFailed ( final Throwable cause )
{
// Caching children
synchronized ( cacheLock )
{
rawNodeChildrenCache.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
fireChildrenLoadFailed ( parent, cause );
}
} );
return parent.getChildCount ();
}
}
/**
* Sets child nodes for the specified node.
* This method might be used to manually change tree node children without causing any structure corruptions.
*
* @param parent node to process
* @param children new node children
*/
public void setChildNodes ( final E parent, final List children )
{
// Check if the node is busy already
synchronized ( busyLock )
{
if ( parent.isLoading () )
{
return;
}
else
{
parent.setState ( AsyncNodeState.loading );
nodeChanged ( parent );
}
}
// Caching raw children
synchronized ( cacheLock )
{
rawNodeChildrenCache.put ( parent.getId (), children );
cacheNodesById ( children );
}
// Filtering and sorting raw children
final List realChildren = filterAndSort ( parent, children );
// 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 ( realChildren != null && realChildren.size () > 0 )
{
// Clearing raw nodes cache
// That might be required in case nodes were moved inside of the tree
clearNodeChildrenCache ( children, false );
// Inserting nodes
insertNodesIntoImpl ( realChildren, parent, 0 );
}
// Release node busy state
synchronized ( busyLock )
{
parent.setState ( AsyncNodeState.loaded );
nodeChanged ( parent );
}
// Firing load completed event
fireChildrenLoadCompleted ( parent, realChildren );
}
} );
}
/**
* Adds child nodes for the specified node.
* This method might be used to manually change tree node children without causing any structure corruptions.
*
* @param parent node to process
* @param children new node children
*/
public void addChildNodes ( final E parent, final List children )
{
// Simply ignore if parent node is not yet loaded
if ( !parent.isLoaded () )
{
return;
}
// Adding new raw children
synchronized ( cacheLock )
{
List cachedChildren = rawNodeChildrenCache.get ( parent.getId () );
if ( cachedChildren == null )
{
cachedChildren = new ArrayList ( children.size () );
rawNodeChildrenCache.put ( parent.getId (), cachedChildren );
}
cachedChildren.addAll ( children );
cacheNodesById ( children );
}
// Clearing nodes cache
// That might be required in case nodes were moved inside of the tree
clearNodeChildrenCache ( children, false );
// Inserting nodes
insertNodesIntoImpl ( children, 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 children
synchronized ( cacheLock )
{
final List children = rawNodeChildrenCache.get ( parentNode.getId () );
if ( children != null )
{
children.remove ( childNode );
}
}
// Clearing node cache
clearNodeChildrenCache ( childNode, true );
// Removing node children so they won't mess up anything when we place node back into tree
childNode.removeAllChildren ();
// Removing node from parent
super.removeNodeFromParent ( node );
// Updating parent node sorting and filtering
updateSortingAndFiltering ( parentNode );
// Removing image observer
childNode.detachLoadIconObserver ();
}
// 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 children
synchronized ( cacheLock )
{
List cachedChildren = rawNodeChildrenCache.get ( parentNode.getId () );
if ( cachedChildren == null )
{
cachedChildren = new ArrayList ( 1 );
rawNodeChildrenCache.put ( parentNode.getId (), cachedChildren );
}
cachedChildren.add ( index, childNode );
cacheNodeById ( childNode );
}
// Clearing node cache
// That might be required in case nodes were moved inside of the tree
clearNodeChildrenCache ( 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 children, final E parent, final int index )
{
// Simply ignore if parent node is not yet loaded
if ( !parent.isLoaded () )
{
return;
}
// Inserting new raw children
synchronized ( cacheLock )
{
List cachedChildren = rawNodeChildrenCache.get ( parent.getId () );
if ( cachedChildren == null )
{
cachedChildren = new ArrayList ( 1 );
rawNodeChildrenCache.put ( parent.getId (), cachedChildren );
}
cachedChildren.addAll ( index, children );
cacheNodesById ( children );
}
// Clearing nodes cache
// That might be required in case nodes were moved inside of the tree
clearNodeChildrenCache ( 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 )
{
// Simply ignore if parent node is not yet loaded
if ( !parent.isLoaded () )
{
return;
}
// Inserting new raw children
synchronized ( cacheLock )
{
List cachedChildren = rawNodeChildrenCache.get ( parent.getId () );
if ( cachedChildren == null )
{
cachedChildren = new ArrayList ( 1 );
rawNodeChildrenCache.put ( parent.getId (), cachedChildren );
}
for ( int i = children.length - 1; i >= 0; i-- )
{
cachedChildren.add ( index, children[ i ] );
}
cacheNodesById ( Arrays.asList ( children ) );
}
// Clearing nodes cache
// That might be required in case nodes were moved inside of the tree
clearNodeChildrenCache ( 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 );
// Adding image observers
child.attachLoadIconObserver ( tree );
}
/**
* 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 children, final E parent, final int index )
{
super.insertNodesInto ( children, parent, index );
// Adding image observers
for ( final E child : children )
{
child.attachLoadIconObserver ( tree );
}
}
/**
* 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 );
// Adding image observers
for ( final E child : children )
{
child.attachLoadIconObserver ( tree );
}
}
/**
* Updates nodes sorting and filtering for all loaded nodes.
*/
public void updateSortingAndFiltering ()
{
updateSortingAndFiltering ( getRoot (), true );
}
/**
* Updates sorting and filtering for the specified node children.
*
* @param parentNode node which children sorting and filtering should be updated
*/
public void updateSortingAndFiltering ( final E parentNode )
{
updateSortingAndFiltering ( parentNode, false );
}
/**
* Updates sorting and filtering for the specified node children.
*
* @param parentNode node which children sorting and filtering should be updated
* @param recursively whether should update the whole children 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 )
{
updateSortingAndFilteringImpl ( parentNode, recursively, true );
}
}
/**
* Updates sorting and filtering for the specified node children.
*
* @param parentNode node which children sorting and filtering should be updated
* @param recursively whether should update the whole children structure recursively or not
* @param performUpdates whether tree updates should be triggered within this method
*/
protected void updateSortingAndFilteringImpl ( final E parentNode, final boolean recursively, final boolean performUpdates )
{
// Process this action only if node children are already loaded and cached
if ( parentNode.isLoaded () && rawNodeChildrenCache.containsKey ( parentNode.getId () ) )
{
// Children are already loaded, simply updating their sorting and filtering
performSortingAndFiltering ( parentNode, recursively, performUpdates );
}
else if ( parentNode.isLoading () )
{
// Children are being loaded, wait until the operation finishes
addAsyncTreeModelListener ( new AsyncTreeModelAdapter ()
{
@Override
public void loadCompleted ( final AsyncUniqueNode parent, final List children )
{
if ( parentNode.getId ().equals ( parent.getId () ) )
{
removeAsyncTreeModelListener ( this );
performSortingAndFiltering ( parentNode, recursively, performUpdates );
}
}
@Override
public void loadFailed ( final AsyncUniqueNode parent, final Throwable cause )
{
if ( parentNode.getId ().equals ( parent.getId () ) )
{
removeAsyncTreeModelListener ( this );
}
}
} );
}
}
/**
* Updates node children using current comparator and filter.
* Updates the whole node children structure if recursive update requested.
*
* @param parentNode node which children sorting and filtering should be updated
* @param recursively whether should update the whole children structure recursively or not
* @param performUpdates whether tree updates should be triggered within this method
*/
protected void performSortingAndFiltering ( final E parentNode, final boolean recursively, final boolean performUpdates )
{
// todo Restore tree state only for the updated node
// todo This also won't work if some of the children updates are delayed using listener
// Saving tree state to restore it right after children update
final TreeState treeState = tree.getTreeState ();
// Updating node children sorting and filtering
// Process this action only if node children are already loaded and cached
final List cachedChildren = rawNodeChildrenCache.get ( parentNode.getId () );
if ( cachedChildren != null )
{
// Removing old children
parentNode.removeAllChildren ();
// Filtering and sorting raw children
final List children = filterAndSort ( parentNode, cachedChildren );
// Inserting new children
for ( final E child : children )
{
parentNode.add ( child );
}
}
// Updating children's children
if ( recursively )
{
for ( int i = 0; i < parentNode.getChildCount (); i++ )
{
updateSortingAndFilteringImpl ( ( E ) parentNode.getChildAt ( i ), true, false );
}
}
// Performing tree updates
if ( performUpdates )
{
// Forcing tree structure update for the node
nodeStructureChanged ( parentNode );
// Restoring tree state including all selections and expansions
tree.setTreeState ( treeState );
}
}
/**
* Returns list of filtered and sorted raw children.
*
* @param parentNode parent node
* @param children children to filter and sort
* @return list of filtered and sorted children
*/
protected List filterAndSort ( final E parentNode, List children )
{
// Simply return an empty array if there is no children
if ( children == null || children.size () == 0 )
{
return new ArrayList ( 0 );
}
// Filter and sort children
final Filter filter = dataProvider.getChildrenFilter ( parentNode );
final Comparator comparator = dataProvider.getChildrenComparator ( parentNode );
if ( filter != null )
{
final List filtered = CollectionUtils.filter ( children, filter );
if ( comparator != null )
{
Collections.sort ( filtered, comparator );
}
return filtered;
}
else
{
if ( comparator != null )
{
children = CollectionUtils.copy ( children );
Collections.sort ( children, comparator );
}
return children;
}
}
/**
* 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 );
}
/**
* 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 children load start event.
*
* @param parent node which children are being loaded
*/
protected void fireChildrenLoadStarted ( final E parent )
{
final List listeners;
synchronized ( modelListenersLock )
{
listeners = CollectionUtils.copy ( asyncTreeModelListeners );
}
for ( final AsyncTreeModelListener listener : listeners )
{
listener.loadStarted ( parent );
}
}
/**
* Fires children load complete event.
*
* @param parent node which children were loaded
* @param children loaded child nodes
*/
protected void fireChildrenLoadCompleted ( final E parent, final List children )
{
final List listeners;
synchronized ( modelListenersLock )
{
listeners = CollectionUtils.copy ( asyncTreeModelListeners );
}
for ( final AsyncTreeModelListener listener : listeners )
{
listener.loadCompleted ( parent, children );
}
}
/**
* Fires children load failed event.
*
* @param parent node which children were loaded
* @param cause children load failure cause
*/
protected void fireChildrenLoadFailed ( final E parent, final Throwable cause )
{
final List listeners;
synchronized ( modelListenersLock )
{
listeners = CollectionUtils.copy ( asyncTreeModelListeners );
}
for ( final AsyncTreeModelListener listener : listeners )
{
listener.loadFailed ( parent, cause );
}
}
}