package jadex.base.gui.componenttree; import jadex.commons.Future; import jadex.commons.IFuture; import java.util.ArrayList; import java.util.Collections; import java.util.List; import javax.swing.Icon; import javax.swing.JComponent; import javax.swing.JTree; import javax.swing.SwingUtilities; import javax.swing.tree.TreePath; /** * Basic node object. */ public abstract class AbstractComponentTreeNode implements IComponentTreeNode { //-------- attributes -------- /** The parent node. */ private final IComponentTreeNode parent; /** The tree model. */ private final ComponentTreeModel model; /** The tree. */ // Hack!!! Model should not have access to ui, required for refresh only on expanded nodes. private final JTree tree; /** The component description. */ private List children; /** Flag to indicate search in progress. */ private boolean searching; /** Flag to indicate recursive refresh. */ private boolean recurse; //-------- constructors -------- /** * Create a node. */ public AbstractComponentTreeNode(IComponentTreeNode parent, ComponentTreeModel model, JTree tree) { this.parent = parent; this.model = model; this.tree = tree; } //-------- IComponentTreeNode interface -------- /** * Called when the node is removed or the tree is closed. */ public void dispose() { } /** * Get the parent node. */ public IComponentTreeNode getParent() { return parent; } /** * Get the child count. */ public int getChildCount() { assert SwingUtilities.isEventDispatchThread(); if(children==null && !searching) { searching = true; searchChildren(false); } return children==null ? 0 : children.size(); } /** * Get the given child. */ public IComponentTreeNode getChild(int index) { assert SwingUtilities.isEventDispatchThread(); if(children==null && !searching) { searching = true; searchChildren(false); } return children==null ? null : (IComponentTreeNode)children.get(index); } /** * Get the index of a child. */ public int getIndexOfChild(IComponentTreeNode child) { assert SwingUtilities.isEventDispatchThread(); if(children==null && !searching) { searching = true; searchChildren(false); } return children==null ? -1 : children.indexOf(child); } /** * Check if the node is a leaf. */ public boolean isLeaf() { assert SwingUtilities.isEventDispatchThread(); return getChildCount()==0; } /** * Refresh the node. * @param recurse Recursively refresh subnodes, if true. */ public void refresh(boolean recurse, boolean force) { assert SwingUtilities.isEventDispatchThread(); if(!searching) { searching = true; this.recurse = recurse; searchChildren(force); } else { // If search in progress upgrade to recursive, but do not downgrade. this.recurse = this.recurse || recurse; } } /** * Get the cached children, i.e. do not start any background processes for updating the children. */ public List getCachedChildren() { assert SwingUtilities.isEventDispatchThread(); return children!=null ? children : Collections.EMPTY_LIST; } /** * True, if the node has properties that can be displayed. */ public boolean hasProperties() { return false; } /** * Get or create a component displaying the node properties. * Only to be called if hasProperties() is true; */ public JComponent getPropertiesComponent() { throw new UnsupportedOperationException("Node has no properties: "+this); } //-------- template methods -------- /** * Get the icon for a node. */ public abstract Icon getIcon(); /** * Asynchronously search for children. * Called once for each node. * Should call setChildren() once children are found. */ protected abstract void searchChildren(boolean force); /** * Set the children. * No children should be represented as empty list to avoid * ongoing search for children. * Method may be called from any thread. */ protected IFuture setChildren(final List children) { final Future ret = new Future(); // For debugging: todo:remove final RuntimeException rte; try { throw new RuntimeException(); } catch (RuntimeException e) { rte = e; } // System.err.println(""+model.hashCode()+" setChildren queued: "+children); SwingUtilities.invokeLater(new Runnable() { public void run() { boolean dorecurse = recurse; searching = false; recurse = false; List oldcs = AbstractComponentTreeNode.this.children; AbstractComponentTreeNode.this.children = children; List added = new ArrayList(); List removed = new ArrayList(); if(oldcs!=null) { removed.addAll(oldcs); } if(children!=null) { added.addAll(children); removed.removeAll(children); } if(oldcs!=null) { added.removeAll(oldcs); } try { if(!added.isEmpty() && !removed.isEmpty()) { for(int i=0; oldcs!=null && i<oldcs.size(); i++) { model.deregisterNode((IComponentTreeNode)oldcs.get(i)); } for(int i=0; children!=null && i<children.size(); i++) { model.addNode((IComponentTreeNode)children.get(i)); } // System.err.println(""+model.hashCode()+" tree change: "+AbstractComponentTreeNode.this+"#"+AbstractComponentTreeNode.this.hashCode()); // System.err.println(""+model.hashCode()+" added: "+added); // System.err.println(""+model.hashCode()+" removed: "+removed); // System.err.println(""+model.hashCode()+" children: "+children); // System.err.println(""+model.hashCode()+" oldcs: "+oldcs); model.fireTreeChanged(AbstractComponentTreeNode.this); } else if(!added.isEmpty()) { for(int i=0; i<added.size(); i++) { IComponentTreeNode node = (IComponentTreeNode)added.get(i); model.addNode(node); // System.err.println(""+model.hashCode()+" setChildren->fireNodeAdded: "+node+", "+added); model.fireNodeAdded(AbstractComponentTreeNode.this, node, children.indexOf(node)); } model.fireNodeChanged(AbstractComponentTreeNode.this); } else if(!removed.isEmpty()) { for(int i=removed.size()-1; i>=0; i--) { IComponentTreeNode node = (IComponentTreeNode)removed.get(i); model.deregisterNode(node); model.fireNodeRemoved(AbstractComponentTreeNode.this, node, oldcs.indexOf(node)); } model.fireNodeChanged(AbstractComponentTreeNode.this); } } catch(RuntimeException e) { System.err.println("node problem: "+AbstractComponentTreeNode.this+"#"+AbstractComponentTreeNode.this.hashCode()); System.err.println("added: "+added); System.err.println("removed: "+removed); System.err.println("children: "+children); System.err.println("oldcs: "+oldcs); rte.printStackTrace(); throw e; } if(dorecurse && tree.isExpanded(new TreePath(model.buildTreePath(AbstractComponentTreeNode.this).toArray()))) { for(int i=0; children!=null && i<children.size(); i++) { ((IComponentTreeNode)children.get(i)).refresh(dorecurse, false); } } ret.setResult(null); } }); return ret; } /** * Get the model. */ public ComponentTreeModel getModel() { return model; } /** * Get the tree. */ public JTree getTree() { return tree; } /** * Add a child and update the tree. * Must be called from swing thread. */ public void addChild(int index, IComponentTreeNode node) { assert SwingUtilities.isEventDispatchThread(); // Ignore when node already removed. if(!model.isZombieNode(node.getId())) { if(children==null) children = new ArrayList(); children.add(index, node); model.addNode(node); model.fireNodeAdded(this, node, index); } else { model.deregisterNode(node); } } /** * Add a child and update the tree. * Must be called from swing thread. */ public void addChild(IComponentTreeNode node) { assert SwingUtilities.isEventDispatchThread(); addChild(getChildCount(), node); } /** * Remove a child and update the tree. * Must be called from swing thread. */ public void removeChild(IComponentTreeNode node) { assert SwingUtilities.isEventDispatchThread(); int index = getIndexOfChild(node); if(index!=-1) { children.remove(node); model.deregisterNode(node); model.fireNodeRemoved(this, node, index); } } }