/***************************************************
*
* cismet GmbH, Saarbruecken, Germany
*
* ... and it just works.
*
****************************************************/
package Sirius.navigator.ui.tree;
import Sirius.navigator.NavigatorConcurrency;
import Sirius.navigator.method.MethodManager;
import Sirius.navigator.types.treenode.ClassTreeNode;
import Sirius.navigator.types.treenode.DefaultMetaTreeNode;
import Sirius.navigator.types.treenode.ObjectTreeNode;
import Sirius.navigator.types.treenode.PureTreeNode;
import Sirius.navigator.types.treenode.RootTreeNode;
import Sirius.navigator.types.treenode.WaitTreeNode;
import Sirius.navigator.ui.status.DefaultStatusChangeSupport;
import Sirius.navigator.ui.status.Status;
import Sirius.navigator.ui.status.StatusChangeListener;
import Sirius.navigator.ui.status.StatusChangeSupport;
import Sirius.server.middleware.types.MetaClassNode;
import Sirius.server.middleware.types.MetaNode;
import Sirius.server.middleware.types.MetaObjectNode;
import Sirius.server.middleware.types.Node;
import Sirius.server.newuser.permission.PermissionHolder;
import org.apache.log4j.Logger;
import org.openide.util.WeakListeners;
import java.awt.EventQueue;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.dnd.Autoscroll;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JComponent;
import javax.swing.JTree;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import javax.swing.event.TreeExpansionEvent;
import javax.swing.event.TreeExpansionListener;
import javax.swing.event.TreeModelListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import de.cismet.cids.navigator.utils.MetaTreeNodeVisualization;
import de.cismet.commons.concurrency.CismetExecutors;
import de.cismet.tools.CismetThreadPool;
/**
* DefaultMetaTree ist ein Navigationsbaum.
*
* @version $Revision$, $Date$
*/
public class MetaCatalogueTree extends JTree implements StatusChangeSupport, Autoscroll {
//~ Static fields/initializers ---------------------------------------------
private static final transient Logger LOG = Logger.getLogger(MetaCatalogueTree.class);
//~ Instance fields --------------------------------------------------------
protected final DefaultStatusChangeSupport statusChangeSupport;
protected final DefaultTreeModel defaultTreeModel;
protected final boolean useThread;
private BufferedImage dragImage = null;
// HELL
private int margin = 12;
private final transient MetaTreeRefreshCache refreshCache;
private final transient ExecutorService treePool;
//~ Constructors -----------------------------------------------------------
/**
* Creates a new MetaCatalogueTree object.
*
* @param rootTreeNode DOCUMENT ME!
* @param editable DOCUMENT ME!
*/
public MetaCatalogueTree(final RootTreeNode rootTreeNode, final boolean editable) {
this(rootTreeNode, editable, true, 3);
}
/**
* Creates a new MetaCatalogueTree object.
*
* @param rootTreeNode DOCUMENT ME!
* @param editable DOCUMENT ME!
* @param useThread DOCUMENT ME!
* @param maxThreadCount DOCUMENT ME!
*/
public MetaCatalogueTree(final RootTreeNode rootTreeNode,
final boolean editable,
final boolean useThread,
final int maxThreadCount) {
this.setModel(new DefaultTreeModel(rootTreeNode, true));
this.setEditable(editable);
this.useThread = useThread;
this.statusChangeSupport = new DefaultStatusChangeSupport(this);
this.defaultTreeModel = (DefaultTreeModel)this.getModel();
this.refreshCache = new MetaTreeRefreshCache();
this.defaultTreeModel.addTreeModelListener(WeakListeners.create(
TreeModelListener.class,
refreshCache,
defaultTreeModel));
this.treePool = CismetExecutors.newFixedThreadPool(
maxThreadCount,
NavigatorConcurrency.createThreadFactory("meta-tree")); // NOI18N
this.getSelectionModel().setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION);
this.setCellRenderer(new MetaTreeNodeRenderer());
this.putClientProperty("JTree.lineStyle", "Angled"); // NOI18N
this.setShowsRootHandles(true);
this.setRootVisible(false);
this.addTreeSelectionListener(new MetaCatalogueSelectionListener());
this.addTreeExpansionListener(new MetaCatalogueExpansionListener());
if (editable) {
final InputMap inputMap = this.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
final ActionMap actionMap = this.getActionMap();
inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), "deleteNode()"); // NOI18N
actionMap.put("deleteNode()", new AbstractAction() { // NOI18N
@Override
public void actionPerformed(final ActionEvent e) {
if (LOG.isDebugEnabled()) {
LOG.debug("performing delete node action"); // NOI18N
}
final DefaultMetaTreeNode selectedNode = MetaCatalogueTree.this.getSelectedNode();
if ((selectedNode != null) && selectedNode.isLeaf()) {
if (MethodManager.getManager().checkPermission(
selectedNode.getNode(),
PermissionHolder.WRITEPERMISSION)) {
MethodManager.getManager().deleteNode(MetaCatalogueTree.this, selectedNode);
} else if (LOG.isDebugEnabled()) {
LOG.warn("actionPerformed() deleting not possible, no node selected"); // NOI18N
}
}
}
});
}
addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(final MouseEvent e) {
super.mouseClicked(e);
if (e.getClickCount() > 1) {
try {
final ArrayList<DefaultMetaTreeNode> v = new ArrayList<DefaultMetaTreeNode>();
final DefaultMetaTreeNode[] resultNodes = getSelectedNodesArray();
for (int i = 0; i < resultNodes.length; ++i) {
if (LOG.isDebugEnabled()) {
LOG.debug("resultNodes:" + resultNodes[i]); // NOI18N
}
if (resultNodes[i].getNode() instanceof MetaObjectNode) {
final DefaultMetaTreeNode otn = resultNodes[i];
v.add(otn);
}
}
if (v.size() > 0) {
MetaTreeNodeVisualization.getInstance().addVisualization(v);
}
} catch (Throwable t) {
LOG.warn("Error of displaying map", t); // NOI18N
}
}
}
});
}
//~ Methods ----------------------------------------------------------------
/**
* Expandiert alle geladenen Knoten.
*/
public void expandAll() {
int row = 0;
while (row < this.getRowCount()) {
this.expandRow(row);
row++;
}
}
/**
* Schliesst alle expandierten Knoten.
*/
public void collapseAll() {
int row = this.getRowCount() - 1;
while (row >= 0) {
this.collapseRow(row);
row--;
}
}
/**
* DOCUMENT ME!
*
* @param artificialId DOCUMENT ME!
*
* @return DOCUMENT ME!
*/
public Set<Future> requestRefreshNode(final String artificialId) {
if (LOG.isDebugEnabled()) {
LOG.debug("refresh for artificial id requested: " + artificialId); // NOI18N
}
final Set<Future> futures = new HashSet<Future>();
if (refreshCache.isValid()) {
final Set<DefaultMetaTreeNode> nodes = refreshCache.get(artificialId);
final Iterator<DefaultMetaTreeNode> it = nodes.iterator();
while (it.hasNext()) {
final DefaultMetaTreeNode node = it.next();
if ((node == null) || !node.isExplored()) {
// we won't do anything, the node is not in cache or has not been explored yet, so an update would
// be pointless
} else {
futures.add(treePool.submit(new RefreshWorker(node)));
}
}
} else {
LOG.warn("cannot refresh nodes, because the cache is invalid"); // NOI18N
}
return futures;
}
/**
* DOCUMENT ME!
*
* @param treePath DOCUMENT ME!
*
* @return DOCUMENT ME!
*/
public Future exploreSubtree(final TreePath treePath) {
final Set<Future> futures = new HashSet<Future>();
final Object[] nodes = treePath.getPath();
final Object rootNode = this.getModel().getRoot();
final ArrayList<DefaultMetaTreeNode> dmtnNodeList = new ArrayList<DefaultMetaTreeNode>();
if ((rootNode != null) && (nodes != null) && (nodes.length > 1)) {
if (LOG.isDebugEnabled()) {
LOG.debug("exploring subtree: " + nodes.length); // NOI18N
}
final List<?> nodeList = Arrays.asList(nodes);
for (final Object o : nodeList) {
if (!(o instanceof DefaultMetaTreeNode)) {
nodeList.remove(o);
LOG.warn("Node " + o // NOI18N
+ " is not instance of DefaultMetaTreeNode and has been removed from the Collection."); // NOI18N
}
}
final Iterator<DefaultMetaTreeNode> childrenIterator = (Iterator<DefaultMetaTreeNode>)nodeList.iterator();
// Root Node entfernen
childrenIterator.next();
final SubTreeExploreThread subTreeExploreThread = new SubTreeExploreThread((DefaultMetaTreeNode)rootNode,
childrenIterator);
return CismetThreadPool.submit(subTreeExploreThread);
} else {
LOG.warn("could not explore subtree"); // NOI18N
}
return null;
}
/**
* Refreshes every node on a tree path. The refresh of each node happens in an individual RefreshWorker. Those
* workers are wrapped in Futures, which are put in a set. That set will be returned. If nothing will be refreshed
* or if something went wrong, an empty set will be returned.
*
* @param treePath the treePath to refresh
*
* @return a set with Futures in which the refresh of each node happens
*/
public Set<Future> refreshTreePath(final TreePath treePath) {
final Set<Future> futures = new HashSet<Future>();
final Object[] nodes = treePath.getPath();
if ((nodes != null) && (nodes.length > 1)) {
if (LOG.isDebugEnabled()) {
LOG.debug("exploring subtree: " + nodes.length); // NOI18N
}
// ignore root node, therefor start index is 1
for (int i = 1; i < nodes.length; ++i) {
if (nodes[i] instanceof DefaultMetaTreeNode) {
final DefaultMetaTreeNode node = (DefaultMetaTreeNode)nodes[i];
futures.add(treePool.submit(new RefreshWorker(node)));
} else {
LOG.warn("Node " + nodes[i] // NOI18N
+ " is not instance of DefaultMetaTreeNode and has been removed from the Collection."); // NOI18N
}
}
return futures;
} else {
LOG.warn("could not explore subtree"); // NOI18N
}
return futures;
}
/**
* DOCUMENT ME!
*
* @return DOCUMENT ME!
*/
public int getSelectedNodeCount() {
final TreePath[] selectedPaths = this.getSelectionPaths();
if ((selectedPaths == null) || (selectedPaths.length == 0)) {
return 0;
}
int j = 0;
for (int i = 0; i < selectedPaths.length; i++) {
if (!((DefaultMetaTreeNode)selectedPaths[i].getLastPathComponent()).isWaitNode()) {
j++;
}
}
if (LOG.isDebugEnabled()) {
LOG.debug("<TREE> getSelectedNodeCount(): " + j); // NOI18N
}
return j;
}
/**
* DOCUMENT ME!
*
* @return DOCUMENT ME!
*/
public DefaultMetaTreeNode getSelectedNode() {
final Object object = this.getLastSelectedPathComponent();
if ((object != null) && !((DefaultMetaTreeNode)object).isWaitNode()
&& !((DefaultMetaTreeNode)object).isRootNode()) {
return (DefaultMetaTreeNode)object;
} else {
return null;
}
}
/**
* DOCUMENT ME!
*
* @return DOCUMENT ME!
*/
public Collection getSelectedNodes() {
final TreePath[] selectedPaths = this.getSelectionPaths();
if ((selectedPaths != null) && (selectedPaths.length > 0)) {
final ArrayList selectedNodes = new ArrayList(selectedPaths.length);
for (int i = 0; i < selectedPaths.length; i++) {
selectedNodes.add(selectedPaths[i].getLastPathComponent());
}
return selectedNodes;
} else {
return new LinkedList();
}
}
/**
* DOCUMENT ME!
*
* @param selectedNodes DOCUMENT ME!
* @param expandTree DOCUMENT ME!
*/
public void setSelectedNodes(final Collection<DefaultMutableTreeNode> selectedNodes, final boolean expandTree) {
if (LOG.isDebugEnabled()) {
LOG.info("setSelectedNodes(): selecting " + selectedNodes.size() + " nodes, expanding tree: "
+ expandTree); // NOI18N
}
final ArrayList treePaths = new ArrayList();
this.setExpandsSelectedPaths(expandTree);
for (final Iterator<DefaultMutableTreeNode> iterator = selectedNodes.iterator(); iterator.hasNext();) {
final DefaultMutableTreeNode next = iterator.next();
if (next != null) {
treePaths.add(new TreePath(next.getPath()));
}
}
if (treePaths.size() > 0) {
this.setSelectionPaths((TreePath[])treePaths.toArray(new TreePath[treePaths.size()]));
} else if (LOG.isDebugEnabled()) {
LOG.warn("setSelectedNodes(): collections of nodes is empty"); // NOI18N
}
// vor Messe
if (selectedNodes.isEmpty()) {
this.removeSelectionPaths(getSelectionPaths());
}
}
@Override
public void autoscroll(final Point p) {
int realrow = getRowForLocation(p.x, p.y);
final Rectangle outer = getBounds();
realrow = (((p.y + outer.y) <= margin) ? ((realrow < 1) ? 0 : (realrow - 1))
: ((realrow < (getRowCount() - 1)) ? (realrow + 1) : realrow));
scrollRowToVisible(realrow);
}
@Override
public Insets getAutoscrollInsets() {
final Rectangle outer = getBounds();
final Rectangle inner = getParent().getBounds();
return new Insets(inner.y - outer.y + margin,
inner.x
- outer.x
+ margin,
outer.height
- inner.height
- inner.y
+ outer.y
+ margin,
outer.width
- inner.width
- inner.x
+ outer.x
+ margin);
}
/**
* DOCUMENT ME!
*
* @return DOCUMENT ME!
*/
public DefaultMetaTreeNode[] getSelectedNodesArray() {
final Collection selectedNodes = this.getSelectedNodes();
return (DefaultMetaTreeNode[])selectedNodes.toArray(new DefaultMetaTreeNode[selectedNodes.size()]);
}
@Override
public void addStatusChangeListener(final StatusChangeListener listener) {
this.statusChangeSupport.addStatusChangeListener(listener);
}
@Override
public void removeStatusChangeListener(final StatusChangeListener listener) {
this.statusChangeSupport.removeStatusChangeListener(listener);
}
@Override
public boolean isPathEditable(final TreePath treePath) {
return false;
}
/**
* DOCUMENT ME!
*
* @return DOCUMENT ME!
*/
public BufferedImage getDragImage() {
return dragImage;
}
/**
* DOCUMENT ME!
*
* @param dragImage DOCUMENT ME!
*/
public void setDragImage(final BufferedImage dragImage) {
this.dragImage = dragImage;
}
/**
* DOCUMENT ME!
*
* @param node DOCUMENT ME!
*
* @return DOCUMENT ME!
*
* @throws IllegalArgumentException DOCUMENT ME!
*/
public static DefaultMetaTreeNode createTreeNode(final Node node) {
if (node instanceof MetaObjectNode) {
return new ObjectTreeNode((MetaObjectNode)node);
} else if (node instanceof MetaNode) {
return new PureTreeNode((MetaNode)node);
} else if (node instanceof MetaClassNode) {
return new ClassTreeNode((MetaClassNode)node);
} else {
throw new IllegalArgumentException("unknown node type: " + node); // NOI18N
}
}
//~ Inner Classes ----------------------------------------------------------
/**
* DOCUMENT ME!
*
* @version $Revision$, $Date$
*/
private final class RefreshWorker implements Runnable {
//~ Instance fields ----------------------------------------------------
private final transient DefaultMetaTreeNode node;
//~ Constructors -------------------------------------------------------
/**
* Creates a new RefreshWorker object.
*
* @param node DOCUMENT ME!
*/
public RefreshWorker(final DefaultMetaTreeNode node) {
this.node = node;
}
//~ Methods ------------------------------------------------------------
@Override
public void run() {
synchronized (node) {
if (node.getParent() == null) {
return;
}
final Node thisNode = node.getNode();
assert thisNode != null : "DefaultMetaTreeNode without backing node: " + node; // NOI18N
if (thisNode.isSqlSort() && (thisNode.getDynamicChildrenStatement() != null)) {
if (LOG.isInfoEnabled()) {
LOG.info("these children are sorted via SQL, thus soft refresh is not possible: "
+ thisNode); // NOI18N
}
node.refreshChildren();
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
defaultTreeModel.reload(node);
}
});
} else {
try {
final Enumeration<DefaultMetaTreeNode> currentChildren = node.children();
final Node[] dbChildren = node.getChildren();
final List<Node> foundDbNodes = new ArrayList<Node>(dbChildren.length);
while (currentChildren.hasMoreElements()) {
final DefaultMetaTreeNode treeNode = currentChildren.nextElement();
final Node currentNode = treeNode.getNode();
assert currentNode != null : "found DefaultMetaTreeNode without backing Node"; // NOI18N
boolean found = false;
for (final Node dbNode : dbChildren) {
if (currentNode.deepEquals(dbNode)) {
foundDbNodes.add(dbNode);
found = true;
break;
}
}
if (!found) {
scheduleRemoval(treeNode);
}
}
for (final Node dbNode : dbChildren) {
if (!foundDbNodes.contains(dbNode)) {
scheduleAddition(createTreeNode(dbNode));
}
}
} catch (final Exception e) {
LOG.error("cannot refresh node: " + node, e); // NOI18N
}
}
}
}
/**
* DOCUMENT ME!
*
* @param toRemove DOCUMENT ME!
*
* @throws IllegalStateException DOCUMENT ME!
*/
private void scheduleRemoval(final DefaultMetaTreeNode toRemove) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
if (node.getParent() != null) {
final int index = node.removeNode(toRemove);
if (index == -1) {
throw new IllegalStateException(
"trying to remove a node that is not present: [node=" // NOI18N
+ node
+ "|removalCandidate=" // NOI18N
+ toRemove
+ "]"); // NOI18N
}
defaultTreeModel.nodesWereRemoved(node, new int[] { index }, new Object[] { toRemove });
defaultTreeModel.nodeStructureChanged(node);
}
}
});
}
/**
* DOCUMENT ME!
*
* @param toAdd DOCUMENT ME!
*
* @throws IllegalStateException DOCUMENT ME!
*/
private void scheduleAddition(final DefaultMetaTreeNode toAdd) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
final int index = node.insertNode(toAdd);
if (index == -1) {
throw new IllegalStateException(
"trying to add a node failed: [node=" // NOI18N
+ node
+ "|additionCandidate=" // NOI18N
+ toAdd
+ "]"); // NOI18N
}
defaultTreeModel.nodesWereInserted(node, new int[] { index });
defaultTreeModel.nodeStructureChanged(node);
}
});
}
}
/**
* DOCUMENT ME!
*
* @version $Revision$, $Date$
*/
private final class MetaCatalogueSelectionListener implements TreeSelectionListener {
//~ Methods ------------------------------------------------------------
@Override
public void valueChanged(final TreeSelectionEvent e) {
statusChangeSupport.fireStatusChange(
org.openide.util.NbBundle.getMessage(
MetaCatalogueTree.class,
"MetaCatalogueTree.valueChanged().objectsSelected",
new Object[] { MetaCatalogueTree.this.getSelectedNodeCount() }), // NOI18N
Status.MESSAGE_POSITION_1);
}
}
/**
* Dieser innere Klasse sorgt dafuer, dass an den Knoten der expandiert wurde seine Children vom Server (oder aus
* dem Cache) geladen und angehaengt werden indem die <b>explore()</b> des Knotens ausgefuehrt wird. Fuer einen
* Knoten der bereits expandiert wurde (d.h. dessen Children bereits vom Server geladen wurden) muss die Funktion
* <b>explore()</b> nicht mehr ausgefuehrt werden.
*
* <p><b>Wenn der DefaultMetaTree Multithreading benutzen soll, wird an den expandierten Knoten eine <b>
* DefaultMetaTreeNode</b> vom Typ <b>WaitNode</b> aengehaengt. Anschliessend wird <b>nodeStructureChanged(node)</b>
* aufgerufen, um diesen Knoten anzuzeigen.<br>
* Die <b>WaitNode</b> wird zwar sofort innerhalb der <b>explore</b> Funktion der selektierten <b>
* DefaultMetaTreeNode</b> wieder entfernt (<b>removeChildren</b>) aber da der <b>TreeExploreThread</b> das GUI
* Update asynchron zum <b>EventDispatchThread</b> ausfuehrt, verschwindet die <b>WaitNode</b> im TreeView erst,
* wenn alle Children vom Server geladen wurden.<br>
* </b></p>
*
* <p>Wenn keine Threads benutzt werden bzw. die maximale Anzahl kokurrierender Threads erreicht wurde, wird <b>
* explore()</b> sofort ausgefuehrt und das GUI bleibt blockiert, bis alle Children geladen und visualisiert wurden.
* </p>
*
* @version $Revision$, $Date$
*/
private class MetaCatalogueExpansionListener implements TreeExpansionListener {
//~ Instance fields ----------------------------------------------------
final WaitTreeNode waitNode = new WaitTreeNode();
//~ Methods ------------------------------------------------------------
@Override
public void treeCollapsed(final TreeExpansionEvent e) {
}
@Override
public void treeExpanded(final TreeExpansionEvent e) {
final DefaultMetaTreeNode selectedNode = (DefaultMetaTreeNode)(e.getPath().getLastPathComponent());
if (LOG.isDebugEnabled()) {
LOG.debug("treeExpanded() Expanding Node: " + selectedNode.toString()); // NOI18N
}
if (!selectedNode.isLeaf() && !selectedNode.isExplored()) // && selectedNode.getChildCount() == 0)
{
if (LOG.isDebugEnabled()) {
LOG.debug("treeExpanded() Expanding Node: " + selectedNode.toString() + " ok."); // NOI18N
}
if (useThread) {
new SwingWorker<Void, Void>() {
@Override
protected Void doInBackground() throws Exception {
selectedNode.add(waitNode);
return null;
}
@Override
protected void done() {
defaultTreeModel.nodeStructureChanged(selectedNode);
treePool.execute(new TreeExploreThread(selectedNode, defaultTreeModel));
}
}.execute();
} else {
synchronized (selectedNode) {
try {
statusChangeSupport.fireStatusChange(
org.openide.util.NbBundle.getMessage(
MetaCatalogueTree.class,
"MetaCatalogueTree.treeExpanded().loadingObjects"), // NOI18N
Status.MESSAGE_POSITION_1,
Status.ICON_IGNORE,
Status.ICON_BLINKING);
selectedNode.explore();
defaultTreeModel.nodeStructureChanged(selectedNode);
statusChangeSupport.fireStatusChange(
org.openide.util.NbBundle.getMessage(
MetaCatalogueTree.class,
"MetaCatalogueTree.treeExpanded().dataLoadedFromServer"), // NOI18N
Status.MESSAGE_POSITION_1,
Status.ICON_ACTIVATED,
Status.ICON_DEACTIVATED);
} catch (Exception exp) {
statusChangeSupport.fireStatusChange(
org.openide.util.NbBundle.getMessage(
MetaCatalogueTree.class,
"MetaCatalogueTree.treeExpanded().loadingError"), // NOI18N
Status.MESSAGE_POSITION_1,
Status.ICON_DEACTIVATED,
Status.ICON_ACTIVATED);
LOG.fatal("treeExpanded() could not load nodes", exp); // NOI18N
selectedNode.removeChildren();
}
}
}
} else {
if (LOG.isDebugEnabled()) {
LOG.debug("treeExpanded() " + selectedNode.getNode() + "'s children loaded from cache"); // NOI18N
}
statusChangeSupport.fireStatusChange(
org.openide.util.NbBundle.getMessage(
MetaCatalogueTree.class,
"MetaCatalogueTree.treeExpanded().dataLoadedFromCache"), // NOI18N
Status.MESSAGE_POSITION_1,
Status.ICON_ACTIVATED,
Status.ICON_DEACTIVATED);
}
}
}
/**
* Der <b>TreeExploreThread</b> sorgt dafuer, dass die TreeNodes im Hintergrund geladen werden. Innerhalb des
* Threads wird die <b>explore()</b> Funktion der selektierten DefaultMetaTreeNode ausgefuehrt. Waehrend des
* Ladevorgangs der Daten vom Server wird der TreeView nicht aktualisiert, da ansonsten das GUI w\u00E4hrend dieser
* Zeit komplett blockiert wuerde (kein repaint() meher!). Deshalb geschieht die eigentlich Aktualisierung des Views
* asynchron zum <b>EventDispatchThread</b>.<br>
* Das bedeutet, dass das GUI nicht innerhalb des EventDispatchThreads (in diesem Fall der <b>
* TreeExpansionEvent</b>) oder aus einem aus ihm heraus gestarteten Thread (z.B. der TreeExploreThread)
* aktuakisiert wird. Die Aktualisierung erfolgt durch <b>nodeStructureChanged(node)</b>. Diese Funktion wird in
* einem weiteren Thread innerhalb des TreeExploreThreads ausgefuehrt, der durch <b>
* SwingUtilities.invokeLater(runnable)</b> erst gestartet wird, wenn der <b>TreeExpansionEvent</b> bzw. der <b>
* TreeExploreThread</b> beendet
*
* @version $Revision$, $Date$
*/
private class TreeExploreThread extends Thread {
//~ Instance fields ----------------------------------------------------
private final transient Runnable treeSwingUpdater;
private final transient DefaultMetaTreeNode node;
//~ Constructors -------------------------------------------------------
/**
* Creates a new TreeExploreThread object.
*
* @param selectedNode DOCUMENT ME!
* @param defaultTreeModel DOCUMENT ME!
*/
public TreeExploreThread(final DefaultMetaTreeNode selectedNode, final DefaultTreeModel defaultTreeModel) {
super("TreeExploreThread");
if (LOG.isDebugEnabled()) {
LOG.debug("<THREAD>: TreeExploreThread"); // NOI18N
}
node = selectedNode;
treeSwingUpdater = new Runnable() {
@Override
public void run() {
MetaCatalogueTree.this.defaultTreeModel.nodeStructureChanged(node);
if (LOG.isDebugEnabled()) {
LOG.debug("<THREAD>: TreeExploreThread GUI update done"); // NOI18N
}
}
};
}
//~ Methods ------------------------------------------------------------
@Override
public void run() {
synchronized (node) {
try {
statusChangeSupport.fireStatusChange(
org.openide.util.NbBundle.getMessage(
MetaCatalogueTree.class,
"MetaCatalogueTree.TreeExploreThread.loadingObjects"), // NOI18N
Status.MESSAGE_POSITION_1,
Status.ICON_BLINKING,
Status.ICON_IGNORE);
node.explore();
statusChangeSupport.fireStatusChange(
org.openide.util.NbBundle.getMessage(
MetaCatalogueTree.class,
"MetaCatalogueTree.TreeExploreThread.dataLoadedFromServer"), // NOI18N
Status.MESSAGE_POSITION_1,
Status.ICON_ACTIVATED,
Status.ICON_DEACTIVATED);
} catch (final Exception exp) {
LOG.error("could not load nodes", exp); // NOI18N
statusChangeSupport.fireStatusChange(
org.openide.util.NbBundle.getMessage(
MetaCatalogueTree.class,
"MetaCatalogueTree.TreeExploreThread.loadingError"), // NOI18N
Status.MESSAGE_POSITION_1,
Status.ICON_DEACTIVATED,
Status.ICON_ACTIVATED);
node.removeChildren();
} finally {
SwingUtilities.invokeLater(treeSwingUpdater);
}
}
if (LOG.isDebugEnabled()) {
LOG.debug("<THREAD> TreeExploreThread done"); // NOI18N
}
}
}
/**
* DOCUMENT ME!
*
* @version $Revision$, $Date$
*/
private class SubTreeExploreThread extends Thread {
//~ Instance fields ----------------------------------------------------
private final DefaultMetaTreeNode node;
private final Iterator<DefaultMetaTreeNode> childrenNodes;
//~ Constructors -------------------------------------------------------
/**
* Creates a new SubTreeExploreThread object.
*
* @param rootNode DOCUMENT ME!
* @param childrenNodes DOCUMENT ME!
*/
public SubTreeExploreThread(final DefaultMetaTreeNode rootNode,
final Iterator<DefaultMetaTreeNode> childrenNodes) {
super("SubTreeExploreThread");
this.node = rootNode;
this.childrenNodes = childrenNodes;
}
//~ Methods ------------------------------------------------------------
@Override
public void run() {
synchronized (node) {
try {
statusChangeSupport.fireStatusChange(
org.openide.util.NbBundle.getMessage(
MetaCatalogueTree.class,
"MetaCatalogueTree.SubTreeExploreThread.loadingObjects"), // NOI18N
Status.MESSAGE_POSITION_1,
Status.ICON_BLINKING,
Status.ICON_IGNORE);
final TreePath selectionPath = this.node.explore(this.childrenNodes);
SwingUtilities.invokeLater(new TreeUpdateThread(selectionPath));
statusChangeSupport.fireStatusChange(
org.openide.util.NbBundle.getMessage(
MetaCatalogueTree.class,
"MetaCatalogueTree.SubTreeExploreThread.dataLoadedFromServer"), // NOI18N
Status.MESSAGE_POSITION_1,
Status.ICON_ACTIVATED,
Status.ICON_DEACTIVATED);
} catch (final Exception exp) {
LOG.error("SubTreeExploreThread: could not load nodes", exp); // NOI18N
statusChangeSupport.fireStatusChange(
org.openide.util.NbBundle.getMessage(
MetaCatalogueTree.class,
"MetaCatalogueTree.SubTreeExploreThread.loadingError"), // NOI18N
Status.MESSAGE_POSITION_1,
Status.ICON_DEACTIVATED,
Status.ICON_ACTIVATED);
node.removeChildren();
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
MetaCatalogueTree.this.defaultTreeModel.nodeStructureChanged(node);
}
});
}
}
if (LOG.isDebugEnabled()) {
LOG.debug("SubTreeExploreThread: done"); // NOI18N
}
}
//~ Inner Classes ------------------------------------------------------
/**
* DOCUMENT ME!
*
* @version $Revision$, $Date$
*/
private class TreeUpdateThread implements Runnable {
//~ Instance fields ------------------------------------------------
private final TreePath selectionPath;
//~ Constructors ---------------------------------------------------
/**
* Creates a new TreeUpdateThread object.
*
* @param selectionPath DOCUMENT ME!
*/
private TreeUpdateThread(final TreePath selectionPath) {
this.selectionPath = selectionPath;
}
//~ Methods --------------------------------------------------------
@Override
public void run() {
if (!EventQueue.isDispatchThread()) {
throw new IllegalStateException("Tree Update Thread can only be scheduled in EDT"); // NOI18N
}
expandPath(selectionPath);
setSelectionPath(this.selectionPath);
scrollPathToVisible(selectionPath);
defaultTreeModel.nodeStructureChanged((TreeNode)selectionPath.getLastPathComponent());
if (LOG.isDebugEnabled()) {
LOG.debug("GUI Update done"); // NOI18N
}
}
}
}
}