blob: c6f4ce4976de1b6000cf8d1322647ed38cb0b2c9 [file] [log] [blame]
/*
* Copyright 2004-2006 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Sun designates this
* particular file as subject to the "Classpath" exception as provided
* by Sun in the LICENSE file that accompanied this code.
*
* This code 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
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
* CA 95054 USA or visit www.sun.com if you need additional information or
* have any questions.
*/
package sun.tools.jconsole.inspector;
import java.awt.EventQueue;
import java.util.*;
import javax.management.*;
import javax.swing.*;
import javax.swing.tree.*;
import sun.tools.jconsole.JConsole;
import sun.tools.jconsole.MBeansTab;
import sun.tools.jconsole.Resources;
import sun.tools.jconsole.inspector.XNodeInfo;
import sun.tools.jconsole.inspector.XNodeInfo.Type;
@SuppressWarnings("serial")
public class XTree extends JTree {
private static final List<String> orderedKeyPropertyList =
new ArrayList<String>();
static {
String keyPropertyList =
System.getProperty("com.sun.tools.jconsole.mbeans.keyPropertyList");
if (keyPropertyList == null) {
orderedKeyPropertyList.add("type");
orderedKeyPropertyList.add("j2eeType");
} else {
StringTokenizer st = new StringTokenizer(keyPropertyList, ",");
while (st.hasMoreTokens()) {
orderedKeyPropertyList.add(st.nextToken());
}
}
}
private MBeansTab mbeansTab;
private Map<String, DefaultMutableTreeNode> nodes =
new HashMap<String, DefaultMutableTreeNode>();
public XTree(MBeansTab mbeansTab) {
this(new DefaultMutableTreeNode("MBeanTreeRootNode"), mbeansTab);
}
public XTree(TreeNode root, MBeansTab mbeansTab) {
super(root);
this.mbeansTab = mbeansTab;
setRootVisible(false);
setShowsRootHandles(true);
ToolTipManager.sharedInstance().registerComponent(this);
}
/**
* This method removes the node from its parent
*/
// Call on EDT
private synchronized void removeChildNode(DefaultMutableTreeNode child) {
DefaultTreeModel model = (DefaultTreeModel) getModel();
model.removeNodeFromParent(child);
}
/**
* This method adds the child to the specified parent node
* at specific index.
*/
// Call on EDT
private synchronized void addChildNode(
DefaultMutableTreeNode parent,
DefaultMutableTreeNode child,
int index) {
// Tree does not show up when there is only the root node
//
DefaultTreeModel model = (DefaultTreeModel) getModel();
DefaultMutableTreeNode root = (DefaultMutableTreeNode) model.getRoot();
boolean rootLeaf = root.isLeaf();
model.insertNodeInto(child, parent, index);
if (rootLeaf) {
model.nodeStructureChanged(root);
}
}
/**
* This method adds the child to the specified parent node.
* The index where the child is to be added depends on the
* child node being Comparable or not. If the child node is
* not Comparable then it is added at the end, i.e. right
* after the current parent's children.
*/
// Call on EDT
private synchronized void addChildNode(
DefaultMutableTreeNode parent, DefaultMutableTreeNode child) {
int childCount = parent.getChildCount();
if (childCount == 0) {
addChildNode(parent, child, 0);
} else if (child instanceof ComparableDefaultMutableTreeNode) {
ComparableDefaultMutableTreeNode comparableChild =
(ComparableDefaultMutableTreeNode)child;
int i = 0;
for (; i < childCount; i++) {
DefaultMutableTreeNode brother =
(DefaultMutableTreeNode) parent.getChildAt(i);
//child < brother
if (comparableChild.compareTo(brother) < 0) {
addChildNode(parent, child, i);
break;
}
//child = brother
else if (comparableChild.compareTo(brother) == 0) {
addChildNode(parent, child, i);
break;
}
}
//child < all brothers
if (i == childCount) {
addChildNode(parent, child, childCount);
}
} else {
//not comparable, add at the end
addChildNode(parent, child, childCount);
}
}
/**
* This method removes all the displayed nodes from the tree,
* but does not affect actual MBeanServer contents.
*/
// Call on EDT
public synchronized void removeAll() {
DefaultTreeModel model = (DefaultTreeModel) getModel();
DefaultMutableTreeNode root = (DefaultMutableTreeNode) model.getRoot();
root.removeAllChildren();
model.nodeStructureChanged(root);
nodes.clear();
}
public void delMBeanFromView(final ObjectName mbean) {
EventQueue.invokeLater(new Runnable() {
public void run() {
// We assume here that MBeans are removed one by one (on MBean
// unregistered notification). Deletes the tree node associated
// with the given MBean and recursively all the node parents
// which are leaves and non XMBean.
//
synchronized (XTree.this) {
DefaultMutableTreeNode node = null;
Dn dn = buildDn(mbean);
if (dn.size() > 0) {
DefaultTreeModel model = (DefaultTreeModel) getModel();
Token token = dn.getToken(0);
String hashKey = dn.getHashKey(token);
node = nodes.get(hashKey);
if ((node != null) && (!node.isRoot())) {
if (hasMBeanChildren(node)) {
removeNonMBeanChildren(node);
String label = token.getValue().toString();
XNodeInfo userObject = new XNodeInfo(
Type.NONMBEAN, label,
label, token.toString());
changeNodeValue(node, userObject);
} else {
DefaultMutableTreeNode parent =
(DefaultMutableTreeNode) node.getParent();
model.removeNodeFromParent(node);
nodes.remove(hashKey);
delParentFromView(dn, 1, parent);
}
}
}
}
}
});
}
/**
* Returns true if any of the children nodes is an MBean.
*/
private boolean hasMBeanChildren(DefaultMutableTreeNode node) {
for (Enumeration e = node.children(); e.hasMoreElements(); ) {
DefaultMutableTreeNode n = (DefaultMutableTreeNode) e.nextElement();
if (((XNodeInfo) n.getUserObject()).getType().equals(Type.MBEAN)) {
return true;
}
}
return false;
}
/**
* Remove all the children nodes which are not MBean.
*/
private void removeNonMBeanChildren(DefaultMutableTreeNode node) {
Set<DefaultMutableTreeNode> metadataNodes =
new HashSet<DefaultMutableTreeNode>();
DefaultTreeModel model = (DefaultTreeModel) getModel();
for (Enumeration e = node.children(); e.hasMoreElements(); ) {
DefaultMutableTreeNode n = (DefaultMutableTreeNode) e.nextElement();
if (!((XNodeInfo) n.getUserObject()).getType().equals(Type.MBEAN)) {
metadataNodes.add(n);
}
}
for (DefaultMutableTreeNode n : metadataNodes) {
model.removeNodeFromParent(n);
}
}
/**
* Removes only the parent nodes which are non MBean and leaf.
* This method assumes the child nodes have been removed before.
*/
private DefaultMutableTreeNode delParentFromView(
Dn dn, int index, DefaultMutableTreeNode node) {
if ((!node.isRoot()) && node.isLeaf() &&
(!(((XNodeInfo) node.getUserObject()).getType().equals(Type.MBEAN)))) {
DefaultMutableTreeNode parent =
(DefaultMutableTreeNode) node.getParent();
removeChildNode(node);
String hashKey = dn.getHashKey(dn.getToken(index));
nodes.remove(hashKey);
delParentFromView(dn, index + 1, parent);
}
return node;
}
public synchronized void addMBeanToView(final ObjectName mbean) {
final XMBean xmbean;
try {
xmbean = new XMBean(mbean, mbeansTab);
if (xmbean == null) {
return;
}
} catch (Exception e) {
// Got exception while trying to retrieve the
// given MBean from the underlying MBeanServer
//
if (JConsole.isDebug()) {
e.printStackTrace();
}
return;
}
EventQueue.invokeLater(new Runnable() {
public void run() {
synchronized (XTree.this) {
// Add the new nodes to the MBean tree from leaf to root
Dn dn = buildDn(mbean);
if (dn.size() == 0) return;
Token token = dn.getToken(0);
DefaultMutableTreeNode node = null;
boolean nodeCreated = true;
//
// Add the node or replace its user object if already added
//
String hashKey = dn.getHashKey(token);
if (nodes.containsKey(hashKey)) {
//already in the tree, means it has been created previously
//when adding another node
node = nodes.get(hashKey);
//sets the user object
final Object data = createNodeValue(xmbean, token);
final String label = data.toString();
final XNodeInfo userObject =
new XNodeInfo(Type.MBEAN, data, label, mbean.toString());
changeNodeValue(node, userObject);
nodeCreated = false;
} else {
//create a new node
node = createDnNode(dn, token, xmbean);
if (node != null) {
nodes.put(hashKey, node);
nodeCreated = true;
} else {
return;
}
}
//
// Add (virtual) nodes without user object if necessary
//
for (int i = 1; i < dn.size(); i++) {
DefaultMutableTreeNode currentNode = null;
token = dn.getToken(i);
hashKey = dn.getHashKey(token);
if (nodes.containsKey(hashKey)) {
//node already present
if (nodeCreated) {
//previous node created, link to do
currentNode = nodes.get(hashKey);
addChildNode(currentNode, node);
return;
} else {
//both nodes already present
return;
}
} else {
//creates the node that can be a virtual one
if (token.getKeyDn().equals("domain")) {
//better match on keyDn that on Dn
currentNode = createDomainNode(dn, token);
if (currentNode != null) {
final DefaultMutableTreeNode root =
(DefaultMutableTreeNode) getModel().getRoot();
addChildNode(root, currentNode);
}
} else {
currentNode = createSubDnNode(dn, token);
if (currentNode == null) {
//skip
continue;
}
}
nodes.put(hashKey, currentNode);
addChildNode(currentNode, node);
nodeCreated = true;
}
node = currentNode;
}
}
}
});
}
// Call on EDT
private synchronized void changeNodeValue(
final DefaultMutableTreeNode node, XNodeInfo nodeValue) {
if (node instanceof ComparableDefaultMutableTreeNode) {
// should it stay at the same place?
DefaultMutableTreeNode clone =
(DefaultMutableTreeNode) node.clone();
clone.setUserObject(nodeValue);
if (((ComparableDefaultMutableTreeNode) node).compareTo(clone) == 0) {
// the order in the tree didn't change
node.setUserObject(nodeValue);
DefaultTreeModel model = (DefaultTreeModel) getModel();
model.nodeChanged(node);
} else {
// delete the node and re-order it in case the
// node value modifies the order in the tree
DefaultMutableTreeNode parent =
(DefaultMutableTreeNode) node.getParent();
removeChildNode(node);
node.setUserObject(nodeValue);
addChildNode(parent, node);
}
} else {
// not comparable stays at the same place
node.setUserObject(nodeValue);
DefaultTreeModel model = (DefaultTreeModel) getModel();
model.nodeChanged(node);
}
// Load the MBean metadata if type is MBEAN
if (nodeValue.getType().equals(Type.MBEAN)) {
XMBeanInfo.loadInfo(node);
DefaultTreeModel model = (DefaultTreeModel) getModel();
model.nodeStructureChanged(node);
}
// Clear the current selection and set it
// again so valueChanged() gets called
if (node == getLastSelectedPathComponent()) {
TreePath selectionPath = getSelectionPath();
clearSelection();
setSelectionPath(selectionPath);
}
}
//creates the domain node, called on a domain token
private DefaultMutableTreeNode createDomainNode(Dn dn, Token token) {
DefaultMutableTreeNode node = new ComparableDefaultMutableTreeNode();
String label = dn.getDomain();
XNodeInfo userObject =
new XNodeInfo(Type.NONMBEAN, label, label, label);
node.setUserObject(userObject);
return node;
}
//creates the node corresponding to the whole Dn
private DefaultMutableTreeNode createDnNode(
Dn dn, Token token, XMBean xmbean) {
DefaultMutableTreeNode node = new ComparableDefaultMutableTreeNode();
Object data = createNodeValue(xmbean, token);
String label = data.toString();
XNodeInfo userObject = new XNodeInfo(Type.MBEAN, data, label,
xmbean.getObjectName().toString());
node.setUserObject(userObject);
XMBeanInfo.loadInfo(node);
return node;
}
//creates a node with the token value, call for each non domain sub
//dn token
private DefaultMutableTreeNode createSubDnNode(Dn dn, Token token) {
DefaultMutableTreeNode node = new ComparableDefaultMutableTreeNode();
String label = isKeyValueView() ? token.toString() :
token.getValue().toString();
XNodeInfo userObject =
new XNodeInfo(Type.NONMBEAN, label, label, token.toString());
node.setUserObject(userObject);
return node;
}
private Object createNodeValue(XMBean xmbean, Token token) {
String label = isKeyValueView() ? token.toString() :
token.getValue().toString();
xmbean.setText(label);
return xmbean;
}
/**
* Parses MBean ObjectName comma-separated properties string and put the
* individual key/value pairs into the map. Key order in the properties
* string is preserved by the map.
*/
private Map<String,String> extractKeyValuePairs(
String properties, ObjectName mbean) {
String props = properties;
Map<String,String> map = new LinkedHashMap<String,String>();
int eq = props.indexOf("=");
while (eq != -1) {
String key = props.substring(0, eq);
String value = mbean.getKeyProperty(key);
map.put(key, value);
props = props.substring(key.length() + 1 + value.length());
if (props.startsWith(",")) {
props = props.substring(1);
}
eq = props.indexOf("=");
}
return map;
}
/**
* Returns the ordered key property list that will be used to build the
* MBean tree. If the "com.sun.tools.jconsole.mbeans.keyPropertyList" system
* property is not specified, then the ordered key property list used
* to build the MBean tree will be the one returned by the method
* ObjectName.getKeyPropertyListString() with "type" as first key,
* and "j2eeType" as second key, if present. If any of the keys specified
* in the comma-separated key property list does not apply to the given
* MBean then it will be discarded.
*/
private String getKeyPropertyListString(ObjectName mbean) {
String props = mbean.getKeyPropertyListString();
Map<String,String> map = extractKeyValuePairs(props, mbean);
StringBuilder sb = new StringBuilder();
// Add the key/value pairs to the buffer following the
// key order defined by the "orderedKeyPropertyList"
for (String key : orderedKeyPropertyList) {
if (map.containsKey(key)) {
sb.append(key + "=" + map.get(key) + ",");
map.remove(key);
}
}
// Add the remaining key/value pairs to the buffer
for (Map.Entry<String,String> entry : map.entrySet()) {
sb.append(entry.getKey() + "=" + entry.getValue() + ",");
}
String orderedKeyPropertyListString = sb.toString();
orderedKeyPropertyListString = orderedKeyPropertyListString.substring(
0, orderedKeyPropertyListString.length() - 1);
return orderedKeyPropertyListString;
}
/**
* Builds the Dn for the given MBean.
*/
private Dn buildDn(ObjectName mbean) {
String domain = mbean.getDomain();
String globalDn = getKeyPropertyListString(mbean);
Dn dn = buildDn(domain, globalDn, mbean);
//update the Dn tokens to add the domain
dn.updateDn();
//reverse the Dn (from leaf to root)
dn.reverseOrder();
//compute the hashDn
dn.computeHashDn();
return dn;
}
/**
* Builds the Dn for the given MBean.
*/
private Dn buildDn(String domain, String globalDn, ObjectName mbean) {
Dn dn = new Dn(domain, globalDn);
String keyDn = "no_key";
if (isTreeView()) {
String props = globalDn;
Map<String,String> map = extractKeyValuePairs(props, mbean);
for (Map.Entry<String,String> entry : map.entrySet()) {
dn.addToken(new Token(keyDn,
entry.getKey() + "=" + entry.getValue()));
}
} else {
//flat view
dn.addToken(new Token(keyDn, "properties=" + globalDn));
}
return dn;
}
//
//utility objects
//
public static class ComparableDefaultMutableTreeNode
extends DefaultMutableTreeNode
implements Comparable<DefaultMutableTreeNode> {
public int compareTo(DefaultMutableTreeNode node) {
return (this.toString().compareTo(node.toString()));
}
}
//
//tree preferences
//
private boolean treeView;
private boolean treeViewInit = false;
public boolean isTreeView() {
if (!treeViewInit) {
treeView = getTreeViewValue();
treeViewInit = true;
}
return treeView;
}
private boolean getTreeViewValue() {
String treeView = System.getProperty("treeView");
return ((treeView == null) ? true : !(treeView.equals("false")));
}
//
//MBean key-value preferences
//
private boolean keyValueView = Boolean.getBoolean("keyValueView");
public boolean isKeyValueView() {
return keyValueView;
}
//
//utility classes
//
public static class Dn {
private String domain;
private String dn;
private String hashDn;
private ArrayList<Token> tokens = new ArrayList<Token>();
public Dn(String domain, String dn) {
this.domain = domain;
this.dn = dn;
}
public void clearTokens() {
tokens.clear();
}
public void addToken(Token token) {
tokens.add(token);
}
public void addToken(int index, Token token) {
tokens.add(index, token);
}
public void setToken(int index, Token token) {
tokens.set(index, token);
}
public void removeToken(int index) {
tokens.remove(index);
}
public Token getToken(int index) {
return tokens.get(index);
}
public void reverseOrder() {
ArrayList<Token> newOrder = new ArrayList<Token>(tokens.size());
for (int i = tokens.size() - 1; i >= 0; i--) {
newOrder.add(tokens.get(i));
}
tokens = newOrder;
}
public int size() {
return tokens.size();
}
public String getDomain() {
return domain;
}
public String getDn() {
return dn;
}
public String getHashDn() {
return hashDn;
}
public String getHashKey(Token token) {
final int begin = getHashDn().indexOf(token.getHashToken());
return getHashDn().substring(begin, getHashDn().length());
}
public void computeHashDn() {
final StringBuilder hashDn = new StringBuilder();
final int tokensSize = tokens.size();
for (int i = 0; i < tokensSize; i++) {
Token token = tokens.get(i);
String hashToken = token.getHashToken();
if (hashToken == null) {
hashToken = token.getToken() + (tokensSize - i);
token.setHashToken(hashToken);
}
hashDn.append(hashToken);
hashDn.append(",");
}
if (tokensSize > 0) {
this.hashDn = hashDn.substring(0, hashDn.length() - 1);
} else {
this.hashDn = "";
}
}
/**
* Adds the domain as the first token in the Dn.
*/
public void updateDn() {
addToken(0, new Token("domain", "domain=" + getDomain()));
}
public String toString() {
return tokens.toString();
}
}
public static class Token {
private String keyDn;
private String token;
private String hashToken;
private String key;
private String value;
public Token(String keyDn, String token) {
this.keyDn = keyDn;
this.token = token;
buildKeyValue();
}
public Token(String keyDn, String token, String hashToken) {
this.keyDn = keyDn;
this.token = token;
this.hashToken = hashToken;
buildKeyValue();
}
public String getKeyDn() {
return keyDn;
}
public String getToken() {
return token;
}
public void setValue(String value) {
this.value = value;
this.token = key + "=" + value;
}
public void setKey(String key) {
this.key = key;
this.token = key + "=" + value;
}
public void setKeyDn(String keyDn) {
this.keyDn = keyDn;
}
public void setHashToken(String hashToken) {
this.hashToken = hashToken;
}
public String getHashToken() {
return hashToken;
}
public String getKey() {
return key;
}
public String getValue() {
return value;
}
public String toString(){
return getToken();
}
public boolean equals(Object object) {
if (object instanceof Token) {
return token.equals(((Token) object));
} else {
return false;
}
}
private void buildKeyValue() {
int index = token.indexOf("=");
if (index < 0) {
key = token;
value = token;
} else {
key = token.substring(0, index);
value = token.substring(index + 1, token.length());
}
}
}
}