package com.ibm.nmon.gui.tree;
import java.util.List;
import java.util.Map;
import javax.swing.BorderFactory;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.ToolTipManager;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import javax.swing.tree.DefaultTreeModel;
import com.ibm.nmon.data.DataSet;
import com.ibm.nmon.data.ProcessDataSet;
import com.ibm.nmon.data.DataSetListener;
import com.ibm.nmon.data.DataType;
import com.ibm.nmon.data.SubDataType;
import com.ibm.nmon.data.ProcessDataType;
import com.ibm.nmon.data.Process;
import com.ibm.nmon.gui.dnd.TreeTransferHandler;
import com.ibm.nmon.gui.main.NMONVisualizerGui;
import com.ibm.nmon.util.DataHelper;
public final class TreePanel extends JScrollPane implements DataSetListener {
private static final long serialVersionUID = 8763622839346467286L;
static final String ROOT_NAME = "All Systems";
protected final JTree tree;
public void addTreeSelectionListener(TreeSelectionListener tsl) {
tree.addTreeSelectionListener(tsl);
}
public TreePanel(NMONVisualizerGui gui) {
DefaultMutableTreeNode root = new DefaultMutableTreeNode(ROOT_NAME);
tree = new JTree(root);
tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
tree.setCellRenderer(new TreeCellRenderer());
setViewportView(tree);
// make sure panel takes up entire parent, but the actual tree is offset slightly
setBorder(null);
tree.setBorder(BorderFactory.createEmptyBorder(2, 0, 2, 0));
// data changes modify the tree
gui.addDataSetListener(this);
tree.addMouseListener(new TreeMouseListener(gui, tree));
tree.setDragEnabled(true);
tree.setTransferHandler(new TreeTransferHandler(gui));
tree.setCellRenderer(new TreeCellRenderer());
ToolTipManager.sharedInstance().registerComponent(tree);
}
@Override
public final void dataAdded(DataSet data) {
DefaultMutableTreeNode dataNode = null;
TreePath selectedPath = null;
List<TreePath> expandedPaths = null;
boolean existing = false;
int insertIdx = 0;
// find the existing data set node, if any
DefaultMutableTreeNode root = (DefaultMutableTreeNode) ((DefaultTreeModel) tree.getModel()).getRoot();
for (int i = 0; i < root.getChildCount(); i++) {
DefaultMutableTreeNode toSearch = (DefaultMutableTreeNode) root.getChildAt(i);
DataSet currentData = (DataSet) toSearch.getUserObject();
if (data.toString().compareTo(currentData.toString()) > 0) {
insertIdx = i + 1;
}
if (currentData.equals(data)) {
dataNode = toSearch;
existing = true;
// if the DataSet node has children, remove them since the DataTypes could have
// changed; save the existing tree state first
TreePath current = tree.getSelectionPath();
if ((current != null) && new TreePath(dataNode.getPath()).isDescendant(current)) {
selectedPath = current;
}
java.util.Enumeration<TreePath> temp = tree.getExpandedDescendants(new TreePath(tree.getModel()
.getRoot()));
if (temp != null) {
expandedPaths = java.util.Collections.list(temp);
}
else {
expandedPaths = java.util.Collections.emptyList();
}
dataNode.removeAllChildren();
break;
}
}
// new data set, new tree node
if (dataNode == null) {
dataNode = new DefaultMutableTreeNode(data);
}
buildDataSetTree(dataNode, data);
// adding to an existing data set, alert the tree the structure has changed and make
// sure the same nodes are expanded / selected
if (existing) {
((javax.swing.tree.DefaultTreeModel) tree.getModel()).nodeStructureChanged(dataNode);
for (TreePath path : expandedPaths) {
tree.expandPath(rebuildPath(path));
}
if (selectedPath != null) {
tree.setSelectionPath(rebuildPath(selectedPath));
}
}
else {
if (insertIdx > root.getChildCount()) {
insertIdx = root.getChildCount() - 1;
}
((DefaultTreeModel) tree.getModel()).insertNodeInto(dataNode, root, insertIdx);
if (root.getChildCount() == 1) {
// expand the root node now that there is something in the tree
tree.expandRow(0);
}
selectedPath = tree.getSelectionPath();
if ((selectedPath == null) || (selectedPath.getLastPathComponent() == root)) {
// reselect the root node to ensure any TreeSelectionListeners (TreePathParsers)
// are updated
tree.setSelectionPath(null);
tree.setSelectionPath(new TreePath(root));
}
// else leave the current selection alone
}
}
@Override
public final void dataRemoved(DataSet data) {
DefaultMutableTreeNode root = (DefaultMutableTreeNode) ((DefaultTreeModel) tree.getModel()).getRoot();
for (int i = 0; i < root.getChildCount(); i++) {
DataSet toSearch = (DataSet) ((DefaultMutableTreeNode) root.getChildAt(i)).getUserObject();
if (toSearch.equals(data)) {
Object removed = root.getChildAt(i);
root.remove(i);
((javax.swing.tree.DefaultTreeModel) tree.getModel()).nodesWereRemoved(root, new int[] { i },
new Object[] { removed });
// to remove a node requires it to be selected, so move the selection to the root
tree.setSelectionPath(new TreePath(root));
break;
}
}
}
@Override
public void dataChanged(DataSet data) {
dataAdded(data);
}
@Override
public final void dataCleared() {
DefaultMutableTreeNode root = (DefaultMutableTreeNode) ((DefaultTreeModel) tree.getModel()).getRoot();
// select the root node if any path is selected
boolean pathSelected = false;
TreePath path = tree.getSelectionPath();
if (path != null) {
pathSelected = true;
}
root.removeAllChildren();
((javax.swing.tree.DefaultTreeModel) tree.getModel()).reload();
if (pathSelected) {
tree.setSelectionPath(new TreePath(root));
}
}
// since saved TreePaths may included nodes that no longer exist, rebuild the path with the
// new, correct nodes
@SuppressWarnings("unchecked")
private TreePath rebuildPath(TreePath oldPath) {
Object[] oldPaths = oldPath.getPath();
DefaultMutableTreeNode[] newPath = new DefaultMutableTreeNode[oldPaths.length];
DefaultMutableTreeNode parent = ((DefaultMutableTreeNode) tree.getModel().getRoot());
newPath[0] = parent;
int pathIdx = 1;
while ((parent != null) && (pathIdx < newPath.length)) {
List<DefaultMutableTreeNode> children = java.util.Collections
.list((java.util.Enumeration<DefaultMutableTreeNode>) parent.children());
for (DefaultMutableTreeNode child : children) {
if (((DefaultMutableTreeNode) oldPaths[pathIdx]).getUserObject().equals(child.getUserObject())) {
newPath[pathIdx] = child;
++pathIdx;
parent = child;
break;
}
}
}
return new TreePath(newPath);
}
private void buildDataSetTree(DefaultMutableTreeNode dataNode, DataSet data) {
boolean buildProcessNode = false;
List<SubDataType> gcTypes = null;
Map<String, DefaultMutableTreeNode> subtypes = null;
// add types and processes
for (DataType type : data.getTypes()) {
if (type.getClass().equals(SubDataType.class)) {
if (type.getId().startsWith("GC")) {
if (gcTypes == null) {
gcTypes = new java.util.ArrayList<SubDataType>();
}
gcTypes.add((SubDataType) type);
}
else {
if (subtypes == null) {
subtypes = new java.util.HashMap<String, DefaultMutableTreeNode>();
}
// create a single TreeNode for the subtype's id and cache it
SubDataType subtype = (SubDataType) type;
String id = subtype.getPrimaryId();
DefaultMutableTreeNode typeNode = subtypes.get(id);
if (typeNode == null) {
typeNode = new DefaultMutableTreeNode(id);
subtypes.put(id, typeNode);
dataNode.add(typeNode);
}
// add a sub-node for each type
typeNode.add(new TypeTreeNode(subtype, true));
}
}
else if (type.getClass().equals(ProcessDataType.class)) {
buildProcessNode = true;
}
else {
dataNode.add(new TypeTreeNode(type));
}
}
if (buildProcessNode) {
dataNode.add(buildTopTree((ProcessDataSet) data));
}
if (gcTypes != null) {
dataNode.add(buildGCTree(gcTypes));
}
}
private DefaultMutableTreeNode buildTopTree(ProcessDataSet data) {
// TOP
// |_ process [single instance] (as ProcessDataType)
// ...|_ TOP field 1
// ...|_ TOP field 2
// ...|_ TOP ...
// ...|_ TOP field n
// |_ process name [multiple instances] (String)
// ...|_ aggregated process (as Process DataType)
// ......|_ TOP fields
// ...|_ process 1 (as Process DataType)
// ......|_ TOP fields
// ...|_ process 2
// ......|_ TOP fields
// ...|_ ...
// ...|_ process n
// ......|_ TOP fields
// format topNode's name like TypeTreeNode
DefaultMutableTreeNode topNode = new DefaultMutableTreeNode(data.getTypeIdPrefix());
Map<String, List<Process>> processNameToProcesses = DataHelper.getProcessesByName(data, true);
for (String processName : processNameToProcesses.keySet()) {
List<Process> processes = processNameToProcesses.get(processName);
DefaultMutableTreeNode processNode = null;
// more than 1 process with the given name => add a subtree
if (processes.size() > 1) {
// process node will be an AggregateDataType; sub-tree contains processs
processNode = new DefaultMutableTreeNode(processes.get(0).getName());
List<DataType> types = new java.util.ArrayList<DataType>();
for (Process process : processes) {
DefaultMutableTreeNode processInstance = new DefaultMutableTreeNode(process);
DataType type = data.getType(process);
types.add(type);
for (String field : type.getFields()) {
processInstance.add(new DefaultMutableTreeNode(field));
}
processNode.add(processInstance);
}
}
else {
// process node is the process; no sub-tree
processNode = new DefaultMutableTreeNode(processes.get(0));
for (String field : data.getType(processes.get(0)).getFields()) {
processNode.add(new DefaultMutableTreeNode(field));
}
}
topNode.add(processNode);
}
return topNode;
}
private DefaultMutableTreeNode buildGCTree(List<SubDataType> gcTypes) {
// create a top-level GC node
DefaultMutableTreeNode gcNode = new DefaultMutableTreeNode("GC");
Map<String, DefaultMutableTreeNode> jvmNodes = new java.util.HashMap<String, DefaultMutableTreeNode>();
// under GC, create a node for each JVM to contain all the data types for that JVM
for (SubDataType type : gcTypes) {
String jvmName = ((SubDataType) type).getSubId();
DefaultMutableTreeNode jvmNode = jvmNodes.get(jvmName);
if (jvmNode == null) {
jvmNode = new DefaultMutableTreeNode(jvmName);
gcNode.add(jvmNode);
jvmNodes.put(jvmName, jvmNode);
}
jvmNode.add(new TypeTreeNode(type, false));
}
return gcNode;
}
private final class TypeTreeNode extends DefaultMutableTreeNode {
private static final long serialVersionUID = -3704741016840510282L;
private final String toDisplay;
TypeTreeNode(DataType type) {
super(type);
toDisplay = type.getId();
for (String field : type.getFields()) {
add(new DefaultMutableTreeNode(field));
}
}
TypeTreeNode(SubDataType type, boolean showSubId) {
super(type);
if (showSubId) {
toDisplay = type.getSubId();
}
else {
toDisplay = type.getPrimaryId();
}
for (String field : type.getFields()) {
add(new DefaultMutableTreeNode(field));
}
}
@Override
public String toString() {
return toDisplay;
}
}
}