package org.oddjob.monitor.model; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import java.util.Vector; import java.util.concurrent.Executor; import javax.swing.ImageIcon; import javax.swing.tree.TreeNode; import org.apache.log4j.Logger; import org.oddjob.Iconic; import org.oddjob.Structural; import org.oddjob.images.IconEvent; import org.oddjob.images.IconHelper; import org.oddjob.images.IconListener; import org.oddjob.monitor.context.ExplorerContext; import org.oddjob.structural.StructuralEvent; import org.oddjob.structural.StructuralListener; /** * This class encapsulates the model of a job to be * used in the monitor. * * @author Rob Gordon */ public class JobTreeNode implements TreeNode { private static final Logger logger = Logger.getLogger(JobTreeNode.class); /** How to dispatch tree model changes. */ private final Executor executor; /** For list of children from the AWT Event Thread perspective. */ private final Vector<JobTreeNode> nodeList = new Vector<JobTreeNode>(); /** From the Job perspective. */ private final Vector<JobTreeNode> currentList = new Vector<JobTreeNode>(); /** Parent node */ final private JobTreeNode parent; /** Save the JobTreeModel. */ final private JobTreeModel model; /** Save icon information */ private final OurIconListener iconListener = new OurIconListener(); private volatile ImageIcon iconTip = IconHelper.nullIcon; /** The job this is modelling. */ final private Object component; final private ExplorerContext explorerContext; private String nodeName; private boolean visible; private boolean listening; private final StructuralListener structuralListner = new StructuralListener() { /* * (non-Javadoc) * @see org.oddjob.structural.StructuralListener#childAdded(org.oddjob.structural.StructuralEvent) */ public synchronized void childAdded(final StructuralEvent e) { final int index = e.getIndex(); Object childJob = e.getChild(); final JobTreeNode childNode = new JobTreeNode(JobTreeNode.this, childJob); logger.debug("Received add event for [" + childNode.getComponent() + "]"); // If this node is visible, then this must be the result of a // external insert (paste), so our child will be visible to. if (visible) { childNode.setVisible(true); } currentList.add(index, childNode); executor.execute(new Runnable() { public void run() { logger.debug("Adding node for [" + childNode.getComponent() + "]"); nodeList.add(index, childNode); model.fireTreeNodesInserted(JobTreeNode.this, childNode, index); } }); } /* * (non-Javadoc) * @see org.oddjob.structural.StructuralListener#childRemoved(org.oddjob.structural.StructuralEvent) */ public synchronized void childRemoved(final StructuralEvent e) { final int index = e.getIndex(); final JobTreeNode child = currentList.remove(index); logger.debug("Received remove event for [" + child.getComponent() + "]"); child.destroy(); executor.execute(new Runnable() { public void run() { logger.debug("Removing node for [" + child.getComponent() + "]"); JobTreeNode child = nodeList.remove(index); model.fireTreeNodesRemoved(JobTreeNode.this, child, index); } }); } }; /** * Constructor for the root node. * * @param explorerModel The ExplorerModel. * @param model The JobTreeModel. */ public JobTreeNode(ExplorerModel explorerModel, JobTreeModel model) { this(explorerModel, model, new EventThreadLaterExecutor(), ExplorerContextImpl.FACTORY); } /** * Constructor for testing so we can change the {@link Executor} and * {@link ExplorerContextFactory}. * * @param explorerModel * @param model * @param executor * @param contextFactory */ public JobTreeNode(ExplorerModel explorerModel, JobTreeModel model, Executor executor, ExplorerContextFactory contextFactory) { this.parent = null; this.model = model; this.component = explorerModel.getOddjob(); this.explorerContext = contextFactory.createFrom(explorerModel); this.executor = executor; } /** * Constructor for child nodes. * * @param parent The parent node. * @param node The structure node this is modelling. */ public JobTreeNode(JobTreeNode parent, Object node) { if (parent == null) { throw new NullPointerException("Parent must not be null!"); } this.parent = parent; this.model = parent.model; this.component = node; this.explorerContext = parent.explorerContext.addChild(node); this.executor = parent.executor; } /** * Called when a node is made visible. This is to reduce the * amount of listeners added to the job tree. * * @param visible True if visible. */ public void setVisible(boolean visible) { if (this.visible == visible) { return; } if (visible) { if (!listening && component instanceof Structural) { ((Structural)component).addStructuralListener(structuralListner); listening = true; } iconListener.listen(); } else { iconListener.dont(); } this.visible = visible; } public boolean isVisible() { return visible; } /** * Called from Icon Listener. * * @param icon */ void setIcon(ImageIcon icon) { synchronized (this) { this.iconTip = icon; } executor.execute(new Runnable() { @Override public void run() { model.fireTreeNodesChanged(JobTreeNode.this); } }); } public Object getComponent() { return component; } // TreeNode methods public Enumeration<JobTreeNode> children() { return nodeList.elements(); } public boolean getAllowsChildren() { return true; } public TreeNode getChildAt(int index) { return nodeList.get(index); } public int getChildCount() { return nodeList.size(); } public boolean isLeaf() { return nodeList.size() == 0 ? true : false; } public int getIndex(TreeNode child) { return nodeList.indexOf(child); } public TreeNode getParent() { return parent; } public String toString() { if (nodeName == null) { nodeName = component.toString(); } return nodeName; } synchronized public ImageIcon getIcon() { return iconTip; } public JobTreeNode[] getChildren() { synchronized (nodeList) { return (JobTreeNode[]) nodeList.toArray(new JobTreeNode[0]); } } /** * Destroy the node. Remove listeners and destroy any remaining child * nodes. Child node will remain in situations where a child is removed * from it's parent before being destroyed This happens with both the * {@link org.oddjob.jobs.structural.ForEachJob} and the * {@link org.oddjob.jmx.JMXClientJob} jobs. */ public void destroy() { logger.debug("Destroying node for [" + getComponent() + "]"); if (component instanceof Structural) { ((Structural)component).removeStructuralListener(structuralListner); } iconListener.dont(); for (int i = currentList.size(); i > 0; --i) { final int index = i - 1; final JobTreeNode child = currentList.remove(index); child.destroy(); executor.execute(new Runnable() { public void run() { logger.debug("Removing node for [" + child.getComponent() + "]"); JobTreeNode child = nodeList.remove(index); model.fireTreeNodesRemoved(JobTreeNode.this, child, index); } }); } } class OurIconListener implements IconListener { private boolean listening; private final Map<String, ImageIcon> icons = new HashMap<String, ImageIcon>(); void listen() { if (listening) { return; } if (component instanceof Iconic) { ((Iconic)component).addIconListener(this); } listening = true; } public void iconEvent(IconEvent event) { String iconId = event.getIconId(); ImageIcon it = icons.get(iconId); if (it == null) { it = ((Iconic)component).iconForId(iconId); if (it == null) { throw new NullPointerException("No icon for " + iconId); } icons.put(iconId, it); } setIcon(it); } public void dont() { if (component instanceof Iconic) { ((Iconic)component).removeIconListener(this); } listening = false; } } /** * @return Returns the context. */ public ExplorerContext getExplorerContext() { return explorerContext; } }